乐于分享
好东西不私藏

安卓逆向 -- 一款企业IM的加密协议逆向全过程

安卓逆向 -- 一款企业IM的加密协议逆向全过程

本文记录了对某企业通讯APP的完整逆向分析过程,涉及国密算法、融云SDK、Frida动态调试等技术。仅供技术学习交流,请勿用于非法用途。


一、起因

最近拿到一款企业内部通讯APP,基于融云SDK二次开发,使用了国密加密体系。出于技术研究目的,我决定深入分析其通讯协议和加密实现。

二、整体架构分析

2.1 技术栈识别

通过APK反编译,发现该应用采用了三层架构:

┌─────────────────────────────────────┐│  业务层(自定义消息协议)              │├─────────────────────────────────────┤│  融云 IM SDK (第三方即时通讯)         ││  ├─ Java API 层                     ││  └─ Native 层 (libRongIMLib.so)     │├─────────────────────────────────────┤│  加密层(SM2 + SM4 国密算法)         │└─────────────────────────────────────┘

关键发现:

  • • 主要代码使用 Java/Kotlin 编写
  • • 核心通讯基于融云SDK的TCP长连接
  • • HTTP接口采用国密SM2+SM4混合加密
  • • 本地存储使用AES加密

2.2 网络通讯方式

该应用使用了两种通讯方式:

  1. 1. TCP长连接:用于即时消息收发(融云SDK实现)
  2. 2. HTTPS接口:用于业务API调用(国密加密)

三、加密体系解密

3.1 HTTP通信加密(国密体系)

硬编码的SM2公钥

在 ParamsBuilder.java 中发现硬编码的SM2公钥:

publicstaticfinalStringSM2_PUBLIC_KEY="fdsafsdfsdafsdfasdfasdfasdfasdfasdfasdfadsasdfdas."

加密流程

  1. 1. 生成32字节随机密钥
  2. 2. 使用SM2公钥加密随机密钥
  3. 3. 使用SM4/ECB模式加密消息体
  4. 4. HTTP Header 标记:hdc-encryption-enabled: true

关键代码片段:

// SM4加密实现Ciphercipher= Cipher.getInstance("SM4/ECB/PKCS5Padding"                                   BouncyCastleProvider.INSTANCE);cipher.init(Cipher.ENCRYPT_MODE, newSecretKeySpec(keyBytes, "SM4"));byte[] encrypted = cipher.doFinal(plainText.getBytes());

3.2 本地存储加密(AES)

敏感数据(Token、密码等)使用AES加密存储:

  • • 算法:AES/ECB/PKCS5Padding
  • • 密钥处理:原始key补齐到32字节(不足部分填充"0")
  • • 编码方式:Base64 (NO_WRAP)

3.3 特殊发现:硬编码SM4密钥

在 CacheTask.java 中发现一个硬编码的SM4密钥:

privatestaticfinalStringSECRET_KEY="64536456345634565434565463";

用于特定字段的SM4解密。

四、消息协议分析

4.1 消息类型体系

所有自定义消息通过 @MessageTag 注解注册:

消息类型
Tag标识
用途
TextMessage
RC:TxtMsg
文本消息
ApprovalMessage
RCE:ApprovalMsg
审批消息
SecretChatPromptMessage
RCE:SCPM
密聊提示
GroupCommandMessage
RCE:GrpCmd
群指令
PinMessage
RCE:PinMsg
公告消息

4.2 消息JSON结构

基础消息体包含以下字段:

{"mentionedInfo":{"type":1,"userIdList":[],"mentionedContent":"..."},"user":{"id":"...","name":"...","portrait":"..."},"extra":"...","isBurnAfterRead":false,"audit":{"auditType":1,"project":"..."}}

4.3 端到端加密会话

融云SDK支持端到端加密,通过 RCEncryptedSession 维护:

classRCEncryptedSession {    String encKey;        // 加密密钥    String encXA;         // 密钥交换参数    String remoteEncId;   // 对端会话标识int encStatus;        // 状态码}

状态码含义:

  • • -1: NOT_FOUND
  • • 1: REQUEST
  • • 2: RESPONSE
  • • 3: ENCRYPTED
  • • 4: CANCELED

五、动态调试实战

5.1 初次尝试与问题

最初编写的Frida脚本只能Hook到部分信息:

// 只能看到这些[RongIMClient.sendMessage  objectName=null  convType=PRIVATE  targetId=10007585

问题分析:

  • • msg.getObjectName() 返回 null
  • • Native方法签名不匹配导致Hook失败
  • • 消息内容在传递到Native层前就被序列化

5.2 解决方案

关键突破点在于:在Java层直接调用 encode() 方法主动序列化

// 修改后的Hook代码RongIMClientImpl.sendMessage.implementation = function() {var msg = arguments[0];var mc = msg.getContent();  // 获取MessageContent对象if (mc) {var encoded = mc.encode();  // 主动序列化console.log("明文内容:");console.log(bytesToString(encoded));  // 输出明文JSON    }returnthis.sendMessage.apply(thisarguments);};

5.3 多层防御策略

最终采用了多点拦截策略:

  1. 1. API入口层RongIMClient.sendMessage()
  2. 2. 序列化层:各消息类型的 encode() 方法
  3. 3. 持久化层NativeObject.SaveMessage()
  4. 4. Native入口层sendMessageWithOption()

这样即使某一层Hook失败,其他层也能捕获到明文。

5.4 最终效果

运行脚本后的输出示例:

══ SEND  conv=PRIVATE  target=10007585 [10:23:45] ═════  Class: io.rong.message.TextMessage  [156B] 明文 JSON:{  "content": "测试消息",  "user": {    "id": "user123",    "name": "张三"  },  "extra": ""}  [HEX] 7b 22 63 6f 6e 74 65 6e 74 22 3a 22 ...

六、关键技术要点总结

6.1 加密向量汇总

参数
用途
SM2公钥
04c945cb7cc...
HTTP密钥交换
SM4密钥
bb218d79ddd...
特定字段解密
AES模式
ECB/PKCS5
本地存储
API地址
https://域名:173/rce-api
业务接口

6.2 Frida Hook技巧

1. 处理方法重载

var overloads = Class.method.overloads;for (var i = 0; i < overloads.length; i++) {    (function(idx) {        overloads[idx].implementation = function() {// Hook逻辑return overloads[idx].apply(thisarguments);        };    })(i);}

2. 主动调用对象方法

var obj = arguments[0];var result = obj.someMethod();  // 直接调用Java对象方法

3. 字节数组转字符串

functionbytesToString(bytes) {returnJava.use("java.lang.String")        .$new(bytes, "UTF-8")        .toString();}

6.3 常见坑点

❌ 错误做法:

// 直接Hook native方法容易失败NativeObject.sendMessageWithOption.implementation = ...

✅ 正确做法:

// 在Java层拦截并主动序列化var mc = msg.getContent();var plaintext = mc.encode();

七、安全建议

作为开发者,从这次逆向分析中可以得到以下安全启示:

7.1 不要硬编码密钥

// ❌ 危险privatestaticfinalStringKEY="bb218d79ddd211cf...";// ✅ 安全// 使用密钥派生函数(KDF)从用户密码/设备特征动态生成

7.2 加强代码混淆

  • • 使用ProGuard/R8进行深度混淆
  • • 对关键类名、方法名进行字符串加密
  • • 使用Native层实现核心加密逻辑

7.3 实施多层防护

  • • Root/越狱检测
  • • Frida检测(检查端口、进程、特征文件)
  • • 完整性校验(签名验证、DEX校验)
  • • 运行时环境检测

八、技术延伸

8.1 如何防御类似逆向

  1. 1. 使用VMP/Ollvm等加固方案
  2. 2. 关键逻辑下沉到Native层
  3. 3. 实施SSL Pinning防止中间人攻击
  4. 4. 动态密钥协商(避免硬编码)
  5. 5. 代码自校验(检测Hook痕迹)

8.2 进一步研究方向

  • • 融云SDK的Native层协议分析
  • • TLS层的流量解密
  • • 端到端加密的密钥交换机制
  • • 消息存储数据库的加密实现

九、总结

通过本次逆向分析,我们完整梳理了一款企业IM应用的:

✅ 整体架构设计✅ 国密加密实现✅ 消息协议结构✅ 动态调试方法

核心收获:

  1. 1. 硬编码密钥是重大安全隐患
  2. 2. Java层的加密在Frida面前形同虚设
  3. 3. 多层Hook策略能有效应对各种防护
  4. 4. 国密算法的正确实施同样重要

免责声明

本文所有内容仅供技术研究和学习交流使用,请勿将相关技术用于非法用途。对他人系统进行未授权的渗透测试属于违法行为,后果自负。


关键词: #逆向工程 #移动安全 #Frida #国密算法 #Android安全 #即时通讯 #加密协议