Android Xposed 模块实战:MaskWechat 微信聊天记录隐藏方案的架构设计与技术实现

给微信一点隐私

前言

在移动端隐私保护领域,如何在不修改目标应用数据的前提下,实现敏感聊天记录的视觉隐藏,是一个兼具技术深度和实用价值的课题。本文以开源项目 MaskWechat 为例,从架构设计、Hook 策略、SQL 拦截、授权体系等维度,深入解析一个完整 Xposed 模块的工程实践。

项目基于 LSPosed / Xposed 框架,使用 Kotlin + Java 混合开发,适配微信 8.0.22 至 8.0.58 共计 20+ 个版本,核心思路是 “只遮不改”------仅在 UI 层拦截和隐藏,不对微信的用户数据做任何写入或删除操作。


一、整体架构

1.1 模块入口与插件注册

模块入口类 MainHook 实现了 Xposed 的三大接口:

public class MainHook implements IXposedHookLoadPackage, 
                                  IXposedHookZygoteInit, 
                                  IXposedHookInitPackageResources {
    // ...
}

handleLoadPackage 中,通过 Hook Application.onCreateInstrumentation.callApplicationOnCreate 两个入口点来确保初始化时机可靠。插件体系采用 注册表模式,将功能解耦为独立插件:

PluginRegistry.register(
    new CommonPlugin(),    // SQL 级拦截(聊天列表、搜索)
    new WXDbPlugin(),      // 数据库相关
    new WXConfigPlugin(),  // 配置管理
    new WXMaskPlugin()     // 主隐藏逻辑调度器
);

1.2 插件分层设计

WXMaskPlugin 作为核心调度器,内部按功能拆分为多个 PluginPart

PluginPart 职责
HideMainUIListPluginPart 主页聊天列表隐藏(ListView / RecyclerView)
EnterChattingUIPluginPart 聊天页面进入时的记录隐藏
HideSearchListUIPluginPart 搜索结果中的用户过滤
EmptySingChatHistoryGalleryPluginPart 单聊历史图片/视频过滤
HiddenChatEntryPluginPart 隐藏会话入口
SearchMagicPluginPart 搜索关键词拦截

每个 Part 独立实现 IPlugin 接口,拥有自己的 Hook 逻辑,互不干扰。


二、核心 Hook 策略

2.1 主页聊天列表:双层拦截

隐藏聊天列表条目是模块最核心的功能,采用了 数据层 + 视图层 的双重拦截策略。

数据层:Hook getItem

微信的聊天列表 Adapter 有一个获取条目数据的方法(经混淆后名称因版本而异),模块通过版本映射表定位并 Hook 该方法:

val GetItemMethodName = when (AppVersionUtil.getVersionCode()) {
    Constrant.WX_CODE_8_0_22 -> "aCW"
    in Constrant.WX_CODE_8_0_22..Constrant.WX_CODE_8_0_43 -> "k"
    Constrant.WX_CODE_8_0_58, Constrant.WX_CODE_8_0_66 -> "m"
    else -> "m"
}

afterHookedMethod 中,读取条目的 field_username 字段,若命中隐藏列表,则清空 field_contentfield_digest 等关键显示字段,并将 field_conversationTime 设为 0 使其沉底。

视图层:Hook onBindViewHolder

对于使用 RecyclerView 的新版微信,还需额外 Hook onBindViewHolder,通过 XposedHelpers.getAdditionalInstanceField 读取数据层打上的 _wxmask_hidden 标记,将对应 itemView 设为 View.GONE 并将高度置零:

if (isHidden) {
    itemView.visibility = View.GONE
    itemView.layoutParams?.let { lp ->
        lp.height = 0
        itemView.layoutParams = lp
    }
    return
}

同时处理了 RecyclerView 的视图复用问题------对非隐藏项恢复 VISIBLEWRAP_CONTENT

2.2 SQL 级拦截:从数据库层面过滤

仅在 UI 层拦截存在一个问题:当新消息到达时,微信会执行 SQL 查询来刷新列表,可能导致隐藏用户被短暂显示。CommonPlugin 通过 Hook SQLiteDatabase 的多个方法,在 SQL 层面彻底解决:

SELECT 查询拦截:

// 聊天列表批量查询 -> 注入 NOT IN 条件
"AND rconversation.username NOT IN ($hideValueText) "

// 单条查询拦截 -> 返回空结果
param.args[1] = sql.replaceFirst("WHERE", "WHERE 1=0 AND")

UPDATE / INSERT 拦截:

当微信收到新消息更新 rconversation 表时,模块检查 ContentValueswhereArgs 中的 username,若命中隐藏列表则阻止写入:

// UPDATE 拦截:让 WHERE 条件永远不成立
param.args[2] = "1=0"

// INSERT 拦截:直接返回失败
param.result = -1L

// execSQL 拦截:替换为空操作
param.args[0] = "SELECT 1"

这套 SQL 层面的拦截确保了即使有新消息推送,隐藏用户也不会出现在列表中。

2.3 搜索结果过滤

微信的全局搜索涉及多张 FTS5 虚拟表,模块通过正则匹配识别搜索 SQL:

private val regex by lazy {
    Regex("^SELECT (FTS5MetaContact|FTS5MetaTopHits|...)\.docid, ...")
}

匹配后将原始 SQL 包装为子查询,追加 aux_index NOT IN (...) 过滤条件,从搜索结果中剔除指定用户。


三、聊天页面隐藏与临时解除

3.1 进入聊天页的拦截

通过 Hook BaseChattingUIFragment.onActivityCreated,从 Fragment 的 arguments 中提取 Chat_User,与隐藏列表比对。命中后,将聊天内容区域 (MMChattingListView) 设为 View.INVISIBLE

3.2 临时解除机制

模块支持两种临时解除方式:

快速点击解除: 在聊天记录空白处连续快速点击(默认 5 次以上,间隔不超过 150ms),通过 QuickCountClickListenerUtil 实现点击计数和时间窗口判断。

输入框口令: 在聊天输入框输入特定指令:

口令 功能
#show 临时显示聊天记录
#hide 临时隐藏
#add 将当前用户加入隐藏列表
#del 从隐藏列表移除
#copyId 复制当前用户的 wxid

四、版本适配策略

微信每次更新都可能导致类名、方法名、资源 ID 发生变化。项目采用 版本号常量映射 的方式应对:

// 20+ 个版本常量
const val WX_CODE_8_0_22 = 2140
const val WX_CODE_8_0_32 = 2300
// ...
const val WX_CODE_8_0_58 = 2841

关键 Hook 点都通过 when 表达式按版本分发:

val adapterClazzName = when (AppVersionUtil.getVersionCode()) {
    Constrant.WX_CODE_8_0_22 -> "com.tencent.mm.ui.g"
    in Constrant.WX_CODE_8_0_32..Constrant.WX_CODE_8_0_34 -> "com.tencent.mm.ui.y"
    // ... 逐版本适配
    else -> null  // 未知版本走猜测逻辑
}

对于未适配的版本,模块还实现了 自动猜测机制:通过 Hook RecyclerView.setAdapter 动态发现 Adapter 类,再根据方法签名特征(参数类型、返回值、修饰符)自动定位 getItem 方法。


五、授权与试用体系

5.1 双层授权架构

项目实现了一套完整的授权体系,分为两个检查层:

  • AuthManager(主应用进程):负责网络登录、Token 管理、Profile 拉取,写入签名文件
  • AuthChecker(微信进程):纯本地文件读取,零网络请求,通过 HMAC-SHA256 校验数据完整性

这种设计确保了在微信进程中的授权检查不依赖网络,不影响微信的启动速度。

5.2 防篡改机制

授权数据文件 auth_status.dat 采用 JSON | HMAC 的格式存储:

// 写入
val hmac = AuthChecker.computeHmac(jsonStr)
val content = "$jsonStr|$hmac"

// 读取验证
val expectedSignature = computeHmac(jsonStr)
if (signature == expectedSignature) { /* 通过 */ }

同时引入了 服务器时间估算时间倒退检测

// 用服务器时间 + 本地经过时间来估算当前真实时间
val estimatedServerNow = serverTime + (now - cachedAt)

// 检测系统时钟被回拨
if (now < cachedAt) {
    return false  // 拒绝授权
}

5.3 授权检查的性能优化

AuthChecker 使用了 5 分钟 TTL 的内存缓存,避免每次 Hook 回调都进行磁盘 IO:

private const val CACHE_TTL = 5 * 60 * 1000L

fun readAndVerifyAuth(): Boolean {
    val now = System.currentTimeMillis()
    if (now - lastCheckTime < CACHE_TTL) {
        return cachedResult  // 命中缓存
    }
    val result = doCheck()  // 读文件 + HMAC 校验
    cachedResult = result
    lastCheckTime = now
    return result
}

每个 Hook 点的授权检查调用链为:readAndVerifyAuth() -> 缓存命中则直接返回 -> 未命中则读取文件 + HMAC 验证 + VIP/试用期判断。


六、配置管理与观察者模式

隐藏列表的配置通过 ConfigUtil 管理,支持动态变更。当用户在微信内通过口令或配置中心修改隐藏列表时,通过观察者模式通知所有相关组件:

interface ConfigSetObserver {
    fun onConfigChange()
}

// WXMaskPlugin 监听配置变化,重新加载隐藏列表
override fun onConfigChange() {
    loadConfigData()
}

// HideMainUIListPluginPart 监听配置变化,刷新列表 Adapter
override fun onConfigChange() {
    refreshAllAdapters()
}

Adapter 刷新使用弱引用列表避免内存泄漏:

private val adapterRefs = mutableListOf<WeakReference<Any>>()

七、构建与版本管理

项目使用 Gradle 构建,在编译时自动将 Git 分支、提交哈希、构建时间注入 BuildConfig

buildConfigField "String", "buildInfoJson64", 
    "\"${buildInfoJson.bytes.encodeBase64()}\""

APK 输出文件名自动包含版本号和 Git 信息:

windcarMaskWechat-v1.0.1-a3b2c1-release.apk

Release 构建启用了 R8 混淆和资源压缩:

release {
    minifyEnabled true
    shrinkResources true
    proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 
                  'proguard-rules.pro'
}

八、设计亮点总结

设计点 方案 好处
双层拦截 数据层(getItem)+ 视图层(onBindViewHolder) 确保 ListView 和 RecyclerView 都能正确隐藏
SQL 注入过滤 Hook rawQuery / update / insert / execSQL 从数据库层面彻底防止隐藏用户出现
版本适配 常量映射表 + 自动猜测兜底 已知版本精确适配,未知版本也有一定概率可用
授权分层 AuthManager(网络)+ AuthChecker(本地文件) 微信进程零网络开销,不影响启动性能
配置热更新 观察者模式 + 弱引用 Adapter 列表 修改配置后即时生效,无需重启微信
数据安全 只隐藏不修改,HMAC 签名,时间倒退检测 不破坏微信数据,授权文件防篡改

九、写在最后

MaskWechat 的核心设计理念是 “最小侵入”:不修改任何微信数据,不注入任何持久化内容,所有隐藏操作都发生在内存中的 UI 层和 SQL 查询层。这种设计既保证了功能的可靠性,也将对微信的影响降到了最低。

对于 Xposed 模块开发者而言,这个项目在以下方面提供了可借鉴的实践:

  1. 插件化架构:将功能拆分为独立 PluginPart,降低耦合
  2. 多层级 Hook:UI Hook + SQL Hook 互为补充,确保无遗漏
  3. 版本兼容策略:精确映射 + 智能猜测的降级方案
  4. 跨进程授权:文件 + HMAC 的轻量级方案,避免微信进程中的网络请求

项目地址:[https://https://xoxome.online/?page_id=2861)

声明:本项目仅供学习和个人测试使用,请勿用于任何商业或非法用途。使用模块的风险由用户自行承担。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐