微信MMTLS加密协议安全性分析

admin 2024年10月29日18:20:33评论230 views字数 29180阅读97分16秒阅读模式

微信MMTLS加密协议安全性分析

主要贡献

我们首次公开分析了 MMTLS 的安全性和隐私属性,MMTLS 是微信使用的主要网络协议,微信是一款每月活跃用户超过 10 亿的应用程序。

我们发现 MMTLS 是 TLS 1.3 的修改版本,微信开发人员对加密技术所做的许多修改都引入了弱点。

进一步分析发现,早期版本的微信使用了一种安全性较低、定制设计的协议,其中包含多个漏洞,我们将其描述为“业务层加密”。在现代微信版本中,除了 MMTLS 之外,仍使用这一层加密。

虽然我们无法开发出一种能够完全破解微信加密的攻击方式,但其实现方式与十亿用户使用的应用程序中所期望的加密级别不一致,例如其使用确定性 IV 和缺乏前向保密性。

这些发现有助于进一步研究国生态系统中的应用程序未能采用最佳加密实践,而是选择发明自己的、往往存在问题的系统。

我们将在配套的Github 存储库中发布技术工具和有关我们技术方法的进一步文档。这些工具和文档以及本主要报告将帮助未来的研究人员研究微信的内部工作原理。

介绍

微信每月活跃用户超过12 亿,是国最受欢迎的消息和社交媒体平台,全球排名第三。市场研究显示,2018 年微信的网络流量占我国移动流量的 34%。微信的主导地位垄断了国的消息传递,使得我国人越来越不可避免地使用它。随着功能不断扩展,微信也已经超越了其作为消息应用程序的最初目的。

尽管微信具有普遍性和重要性,但对其使用的专有网络加密协议 MMTLS 的研究却很少。这一知识空白成为研究人员的障碍,因为它阻碍了对如此关键的应用程序进行进一步的安全和隐私研究。此外,不幸的是,自加密在许多非常流行的国应用程序中 很常见,而且历史上独立于经过良好测试的标准(如 TLS)开发的加密系统一直存在问题。

这项工作深入研究了 MMTLS 背后的机制以及微信程序的核心工作原理。我们将 MMTLS 与 TLS 1.3 的安全性和性能进行了比较,并讨论了我们的总体发现。我们还提供了用于解密微信网络流量的公共文档和工具。这些工具和文档以及我们的报告将帮助未来的研究人员研究微信的隐私和安全属性,以及它的其他内部工作原理。

本报告包括对微信如何发起网络请求及其加密协议的技术描述,然后是微信协议弱点的总结,最后是对微信设计选择及其影响的高级讨论。本报告面向隐私、安全或其他有兴趣进一步研究微信隐私和安全的技术研究人员。对于非技术受众,我们已在本常见问题解答中总结了我们的研究结果。

MMTLS 和微信传输安全方面的前期工作

微信移动应用程序的内部代码将其专有 TLS 堆栈称为 MMTLS(MM 是 MicroMessenger 的缩写,是微信的中文名称微信的直接翻译),并使用它来加密大部分流量。

关于 MMTLS 协议的公开文档有限。微信开发人员的这份技术文档描述了它与 TLS 1.3 的相似之处和不同之处,并试图证明他们为简化或更改协议使用方式而做出的各种决定的合理性。在本文档中,他们指出了 MMTLS 和 TLS 1.3 之间的各种关键差异,这有助于我们了解 MMTLS 的各种使用模式。

万等人在 2015 年使用标准安全分析技术对微信传输安全性进行了最全面的研究。然而,这项分析是在微信升级的安全协议 MMTLS 部署之前进行的。2019 年,陈等人研究了微信的登录过程,并专门研究了使用 TLS 而非 MMTLS 加密的数据包。

至于 MMTLS 本身,2016 年微信开发人员发布了一份文档,从高层次描述了该协议的设计,并将该协议与 TLS 1.3 进行了比较。其他 MMTLS 出版物侧重于网站指纹识别类型的 攻击,但没有一个专门进行安全评估。一些Github 存储库和博客文章简要介绍了 MMTLS 的有线格式,但没有一个是全面的。虽然专门研究 MMTLS 的工作很少,但之前的 Citizen Lab 报告发现了腾讯设计和实施的其他 加密协议的安全漏洞。

方法论

我们分析了两个版本的微信Android版本:

  • 2022 年 5 月 26 日发布的版本 8.0.23(APK“versionCode”2160),从微信网站下载。

  • 版本 8.0.21(APK“versionCode”2103)于 2022 年 4 月 7 日发布,从 Google Play 商店下载。

本报告中的所有发现都适用于这两个版本。

我们使用了一个注册了美国电话号码的账户进行分析,与我国大陆号码相比,该应用程序的行为有所不同。我们的设置可能并不代表所有微信用户,完整的限制将在下文进一步讨论。

对于动态分析,我们分析了安装在已 root 的 Google Pixel 4 手机和模拟的 Android 操作系统上的应用程序。我们使用Frida来挂钩应用程序的功能并操纵和导出应用程序内存。我们还使用Wireshark对微信的网络流量进行了网络分析。但是,由于微信使用了 MMTLS 等非标准加密库,可能与 HTTPS/TLS 一起使用的标准网络流量分析工具并不适用于微信的所有网络活动。我们使用 Frida 对捕获我们在本报告中详述的数据和信息流至关重要。这些 Frida 脚本旨在在微信将微信的请求数据发送到其 MMTLS 加密模块之前立即拦截它。我们使用的 Frida 脚本发布在我们的 Github 存储库中:https://github.com/citizenlab/wechat-report-data。

对于静态分析,我们使用了流行的 Android 反编译器Jadx将微信的 Android Dex 文件反编译为 Java 代码。我们还使用Ghidra和IDA Pro反编译与微信捆绑在一起的本机库(用 C++ 编写)。

符号

在本报告中,我们引用了大量微信客户端的代码,引用任何代码(包括文件名和路径)时,我们都会在文本上加上 的样式monospace fonts,以表明这是代码。如果引用的是函数,我们会在函数名后加上空括号,例如:somefunction()。我们展示的变量和函数名可能来自以下三种之一:

  1. 原始反编译名称。

  2. 如果名称无法反编译为有意义的字符串(例如,符号名称未编译到代码中),我们会根据附近的内部日志消息对它的引用方式对其进行重命名。

  3. 在没有足够的信息让我们说出原始名称的情况下,我们会根据对代码的理解来命名。在这种情况下,我们会注意到这些名字是我们自己给起的。

在函数的反编译名和日志信息名可得的情况下,一般是一致的。加粗或斜体的术语可以指代我们命名的更高层级的概念或参数。

利用开源组件

我们还确定了该项目使用的开源组件,其中最大的两个是OpenSSL和腾讯 Mars。根据我们对反编译的微信代码的分析,其大部分代码与 Mars 相同。Mars 是移动应用程序的“基础设施组件”,提供移动应用程序所需的通用功能和抽象,例如网络和日志记录。

通过使用调试符号分别编译这些库,我们能够将函数和类定义导入 Ghidra 进行进一步分析。这对我们了解微信中其他非开源代码有很大帮助。例如,当我们分析从微信反编译的网络函数时,我们发现其中很多函数与开源 Mars 非常相似,因此我们只需阅读源代码和注释即可了解函数的作用。开源 Mars 中没有包含加密相关的函数,因此我们仍然需要阅读反编译的代码,但即使在这些情况下,我们也得到了开源 Mars 中已知的各种函数和结构的帮助。

将反编译的代码与源代码进行匹配

在包含源文件路径的微信内部日志消息中,我们注意到三个顶级目录,我们在下面突出显示:

  • /home/android/devopsAgent/workspace/p-e118ef4209d745e1b9ea0b1daa0137ab/src/mars/

  • /home/android/devopsAgent/workspace/p-e118ef4209d745e1b9ea0b1daa0137ab/src/mars-wechat/

  • /home/android/devopsAgent/workspace/p-e118ef4209d745e1b9ea0b1daa0137ab/src/mars-private/

mars 目录下的源文件在Mars 开源仓库中都可以找到,其他两个顶级目录下的源文件在开源仓库中是找不到的。下面是反编译后的一小段代码libwechatnetwork.so:

XLogger::XLogger((XLogger *)&local_2c8,5,"mars::stn",

"/home/android/devopsAgent/workspace/p-e118ef4209d745e1b9ea0b1daa0137ab/src/mars/mars/stn/src/longlink.cc"
                ,"Send",0xb2,false,(FuncDef0 *)0x0);
    XLogger::Assert((XLogger *)&local_2c8,"tracker_.get()");
    XLogger::~XLogger((XLogger *)&local_2c8);

从相似性来看,这段代码很有可能是从开源存储库中的 longlink.cc 文件中定义的 Send() 函数中的这一行编译而来的:

xassert2(tracker_.get());

重复这个观察,每当我们的反编译器无法确定函数的名称时,我们就可以使用编译代码中的日志消息来确定其名称。此外,如果源文件来自开源 Mars,我们也可以阅读其源代码。

火星的三个部分

在Mars wiki上的几篇文章中,腾讯开发者给出了开发 Mars 的以下动机:

  • 需要一个跨平台的网络库,以减少Android和iOS上两个独立的网络库的开发和维护成本。

  • 需要自定义TCP握手过程的参数,以便更快地建立连接。

据开发人员介绍,Mars 及其 STN 模块可与AFNetworking和OkHttp等在其他移动应用中广泛使用的网络库相媲美。

微信开发团队发布的一篇技术文章中,写到了 Mars 开源的过程。文章中介绍,他们要将微信专用的代码(私有的)和通用的代码(开源的)分开,最终分成了三个部分:

  • mars-open:开源,独立的存储库。

  • mars-private:潜在开源,依赖于mars-open。

  • mars-wechat:微信业务逻辑代码,依赖于mars-open和mars-private。

如果我们将“mars”顶级目录中的“mars-open”视为“mars”,则这三个名称与我们之前找到的顶级目录相匹配。利用这些知识,在阅读反编译的微信代码时,我们可以轻松判断它是否是微信特有的。从我们对代码的阅读来看,mars-open 包含基本和通用的结构和功能,例如缓冲区结构、配置存储、线程管理,最重要的是负责网络传输的名为“STN”的模块。(我们无法确定 STN 代表什么。)另一方面,mars-wechat 包含 MMTLS 实现,而 mars-private 与我们研究范围内的功能没有密切关系。

从技术角度讲,开源 Mars编译后只生成一个名为“libmarsstn.so”的目标文件。然而,在微信中,有多个共享目标文件引用了开源 Mars 中的代码,其中包括:

  • libwechatxlog.so

  • libwechatbase.so

  • libwechataccessory.so

  • libwechathttp.so

  • libandromeda.so

  • libwechatmm.so

  • libwechatnetwork.so

我们的研究重点是微信的传输协议和加密,主要在 libwechatmm.so 和 libwechatnetwork.so 中实现。此外,我们还检查了 libMMProtocalJni.so,它不属于 Mars,但包含用于加密计算的函数。其余共享对象文件我们没有检查。

匹配 Mars 版本

尽管我们能够找到微信部分功能的开源代码,但在研究初期,我们无法确定用于构建微信的 mars-open 源代码的具体版本。后来,我们在 中找到了一些版本字符串libwechatnetwork.so。对于微信 8.0.21,搜索字符串“MARS_”得到以下结果:

MARS_BRANCHHEAD
MARS_COMMITIDd92f1a94604402cf03939dc1e5d3af475692b551
MARS_PRIVATE_BRANCHHEAD
MARS_PRIVATE_COMMITID193e2fb710d2bb42448358c98471cd773bbd0b16
MARS_URL
MARS_PATHHEAD
MARS_REVISIONd92f1a9
MARS_BUILD_TIME2022-03-28 21:52:49
MARS_BUILD_JOBrb/2022-MAR-p-e118ef4209d745e1b9ea0b1daa0137ab-22.3_1040

具体的MARS_COMMITID(d92f1a…)在开源的Mars仓库中是存在的,该版本的源码也和反编译后的代码相符。

确定具体的源代码版本对 Ghidra 的反编译帮助很大,由于微信使用的核心数据结构很多都来自 Mars,通过导入已知的数据结构,我们可以观察到非开源代码访问结构字段,从而推断出其用途。

限制

本研究仅关注客户端行为,因此受到隐私研究中其他常见限制的影响,这些限制只能进行客户端分析。客户端传输到微信服务器的大部分数据可能是应用程序功能所必需的。例如,微信服务器当然可以看到聊天消息,因为微信可以根据其内容对其进行审查。我们无法总是衡量腾讯如何处理他们收集的数据,但我们可以推断出什么是可能的。先前的研究对数据共享做出了某些有限的推断,例如非我国大陆用户发送的消息用于训练针对我国大陆用户的审查算法。在本报告中,我们重点关注非我国大陆用户的微信版本。

由于法律和道德约束,我们的调查也受到了限制。由于严格的电话号码和相关政府身份证要求,获取我国电话号码进行调查变得越来越困难。因此,我们没有对我国电话号码进行测试,这导致微信的行为有所不同。此外,由于没有我国大陆账户,与某些功能和小程序的互动类型受到限制。例如,我们没有在应用程序上进行金融交易。

我们的主要分析仅限于分析两个版本的微信 Android 版本(8.0.21 和 8.0.23)。但是,我们还再次确认了我们的工具适用于微信 8.0.49 for Android(2024 年 4 月发布),并且 MMTLS 网络格式与微信 8.0.49 for iOS 使用的格式相匹配。测试不同版本的微信、服务器与旧版本应用程序的向后兼容性,以及在具有不同 API 版本的各种 Android 操作系统上进行测试,都是未来工作的重要途径。

在微信 Android 应用程序中,我们专注于其网络组件。通常,在移动应用程序中(以及在大多数其他程序中),所有其他组件都会将通过网络进行通信的工作推迟到网络组件。我们的研究并不是对微信应用程序的完整安全和隐私审计,因为即使网络通信得到适当保护,应用程序的其他部分仍然需要安全和私密。例如,如果服务器接受任何帐户登录密码,即使密码是保密传输的,应用程序也不会安全。

研究微信和MMTLS的工具

在Github 存储库中,我们发布了可以使用 Frida 记录密钥并解密在同一时间段内捕获的网络流量的工具,以及解密的有效负载样本。此外,我们还提供了额外的文档和我们研究协议的逆向工程笔记。我们希望这些工具和文档能够进一步帮助研究人员研究微信。

发起微信网络请求

与其他任何应用程序一样,微信由各种组件组成。微信中的组件可以调用网络组件来发送或接收网络传输。在本节中,我们将高度简化地描述微信中发送网络请求的过程和组件。实际过程要复杂得多,我们将在单独的文档中更详细地解释。数据加密的具体内容将在下一节“微信网络请求加密”中讨论。

在微信源码中,每个 API 被称为不同的“场景”。例如,在注册过程中,有一个 API 会提交用户提供的所有新账户信息,称为NetSceneReg。NetSceneReg我们将其称为“场景类”,其他组件可以通过调用特定的场景类向 API 发起网络请求。在这种情况下NetSceneReg,它通常由按钮 UI 组件的单击事件调用。

调用时,Scene 类将准备请求数据。请求数据(以及响应)的结构在“RR 类”中定义。(我们将它们称为 RR 类,因为它们的名称中往往包含“ReqResp”。)通常,一个 Scene 类对应一个 RR 类。在 的情况下NetSceneReg,它对应于 RR 类MMReqRespReg2,并包含所需用户名和电话号码等字段。对于每个 API,其 RR 类还定义了一个唯一的内部 URI(通常以“/cgi-bin”开头)和一个“请求类型”编号(大约 2 到 4 位整数)。内部 URI 和请求类型编号通常在整个代码中用于识别不同的 API。Scene 类准备好数据后,将其发送到MMNativeNetTaskAdapter。

MMNativeNetTaskAdapter是一个任务队列管理器,它管理和监控每个网络连接和 API 请求的进度。当场景类调用时MMNativeNetTaskAdapter,它会将新请求(任务)放入任务队列,并调用 req2Buf() 函数。req2Buf() 将场景类准备的请求Protobuf对象序列化为字节,然后使用业务层加密对字节进行加密。

最后将业务层加密后的密文发送到 Mars 中的“STN”模块,STN 再使用MMTLS 加密对数据进行第二次加密,然后建立网络传输连接,并将 MMTLS 加密密文通过该连接发送出去。STN 中传输连接有两种类型:Shortlink和Longlink。Shortlink 是指承载 MMTLS 密文的 HTTP 连接,Shortlink 连接在一个请求-响应周期后关闭。Longlink 是指长存活的 TCP 连接,一个 Longlink 连接可以承载多个 MMTLS 加密请求和响应而无需关闭。

微信网络请求加密

微信网络请求经过两次加密,使用不同的密钥集。序列化的请求数据首先使用我们所说的业务层加密进行加密,因为本文中将内部加密称为发生在“业务层”。业务层加密有两种模式:对称模式和非对称模式。结果业务层加密密文会附加到有关业务层请求的元数据中。然后,使用MMTLS 加密对业务层请求(即请求元数据和内部密文)进行额外加密。然后,将最终得到的密文序列化为MMTLS 请求并通过网络发送。

微信的网络加密系统是脱节的,似乎仍然是至少三种不同密码系统的组合。腾讯文档中描述的加密过程与我们关于 MMTLS 加密的发现大多相符,但该文档似乎并未详细描述业务层加密,其操作在登录和注销时有所不同。登录客户端使用对称模式,而注销客户端使用非对称模式。我们还观察到微信利用 HTTP、HTTPS 和 QUIC 传输大型静态资源,如翻译字符串或传输文件。这些通信的端点主机与 MMTLS 服务器主机不同。它们的域名也表明它们属于CDN。然而,我们感兴趣的端点是那些下载动态生成的、通常是机密的资源(即服务器在每次请求时生成)的端点,或者用户向微信服务器传输通常是机密的数据的端点。这些类型的传输是使用 MMTLS 进行的。

作为最后的实现说明,微信在所有这些密码系统中都使用编译到程序中的内部 OpenSSL 绑定。特别是,libwechatmm.so 库似乎是用OpenSSL 版本 1.1.1l编译的,尽管使用 OpenSSL 绑定的其他库(即和libMMProtocalJni.so)libwechatnetwork.so未使用 OpenSSL 版本字符串编译。我们注意到 OpenSSL 内部 API 可能会造成混淆,并且经常被善意的开发人员滥用。我们在Github 存储库中可以找到有关所使用的每个 OpenSSL API 的完整说明。

在表 1 中,我们总结了每个相关的密码系统、它们的密钥如何派生、如何实现加密和认证以及哪些库包含相关的加密和认证函数。我们将在接下来的章节中讨论密码系统的细节。

微信MMTLS加密协议安全性分析

表 1:微信网络请求加密的不同密码系统概述、密钥如何派生、加密和认证如何执行以及哪些库执行它们。

1. MMTLS 有线格式

由于 MMTLS 可以通过各种传输方式进行传输,我们将MMTLS 数据包称为 MMTLS 内的通信单位。在 Longlink 上,MMTLS 数据包可以拆分为多个 TCP 数据包。在 Shortlink 上,MMTLS 数据包通常包含在 HTTP POST 请求或响应正文中。

每个 MMTLS 数据包包含一个或多个MMTLS 记录(其结构和用途与TLS 记录类似)。记录是在每个 MMTLS 数据包中携带握手数据、应用程序数据或警报/错误消息数据的消息单位。

1A. MMTLS 记录

记录可以通过不同的记录头来识别,记录头是记录内容前面的固定 3 字节序列。具体来说,我们观察到 4 种不同的记录类型,以及相应的记录头:

微信MMTLS加密协议安全性分析

握手记录包含元数据和密钥建立材料,另一方可以使用 Diffie-Hellman 派生相同的共享会话密钥。握手恢复记录包含足够的元数据,可通过重新使用先前建立的密钥材料来“恢复”先前建立的会话。数据记录可以包含加密的密文,其中携带有意义的微信请求数据。一些数据包仅包含加密的无操作心跳。警报记录表示错误或表示一方打算结束连接。在 MMTLS 中,所有非握手记录都经过加密,但使用的密钥材料因握手完成的阶段而异。

这是来自服务器的包含握手记录的带注释的 MMTLS 数据包:这是从客户端发送到服务器的数据记录 示例:

微信MMTLS加密协议安全性分析

微信MMTLS加密协议安全性分析

举个例子来说明这些记录是如何交互的,通常客户端和服务器会交换握手记录,直到 Diffie-Hellman 握手完成并且它们建立了共享密钥材料。之后,它们将交换使用共享密钥材料加密的数据记录。当任何一方想要关闭连接时,它们将发送警报记录。下一节将对每种记录类型的用法进行更多说明。

1B.MMTLS 扩展

由于 MMTLS 的有线协议在很大程度上模仿了 TLS,我们注意到它还借用了“ TLS 扩展”的有线格式,以便在握手期间交换相关的加密数据。具体来说,MMTLS 使用与 TLS 扩展相同的格式,以便客户端传达其密钥共享(即客户端的公钥)以进行 Diffie-Hellman,类似于 TLS 1.3 的key_share扩展,并传达会话数据以进行会话恢复(类似于 TLS 1.3 的pre_shared_key扩展)。此外,MMTLS 支持与 TLS 类似的加密扩展,但它们目前未在 MMTLS 中使用(即加密扩展部分始终为空)。

2.MMTLS 加密

本节描述了加密的外层,即使用什么密钥和加密函数来加密和解密“ MMTLS 有线格式”部分中的密文,以及如何派生加密密钥。

这一层的加密和解密发生在 STN 模块中,在单独生成的“com.tencent.mm:push”中进程。生成的进程最终通过网络传输和接收数据。从库中分析了所有 MMTLS 加密和 MMTLS 序列化的代码libwechatnetwork.so。特别是,我们研究了该Crypt()函数,这是用于所有加密和解密的中心函数,其名称是从调试日志代码中派生出来的。我们还将所有调用连接到HKDF_Extract () 和HKDF_Expand (),即HKDF的 OpenSSL 函数,以便了解如何派生密钥。

当“:push”进程启动时,它会在 HandshakeLoop() 中启动一个事件循环,该循环处理所有传出和传入的 MMTLS 记录。我们挂钩了此事件循环调用的所有函数,以了解每个 MMTLS 记录的处理方式。本研究的代码以及我们研究的特定版本微信的内部函数地址可在 Github存储库中找到。

微信MMTLS加密协议安全性分析

图 1:网络请求:通过长链接和短链接进行 MMTLS 加密连接。每个框都是一个 MMTLS 记录,每个箭头代表通过长链接(即单个 TCP 数据包)或短链接(即 HTTP POST 正文中)发送的“MMTLS 数据包”。双方收到 DH 密钥共享后,所有后续记录都将被加密。

2A. 握手和密钥建立

为了使业务层加密开始发送消息并建立密钥,它必须使用 MMTLS 加密隧道。由于必须首先建立 MMTLS 加密的密钥材料,因此本节中的握手发生在任何数据可以通过业务层加密发送或加密之前。本节讨论的 MMTLS 加密握手的最终目标是建立一个只有客户端和服务器知道的通用秘密值。

在微信的全新启动过程中,它会尝试通过 Shortlink 完成一次 MMTLS 握手,并通过 Longlink 完成一次 MMTLS 握手,从而产生两个 MMTLS 加密隧道,每个隧道使用不同的加密密钥集。对于 Longlink,握手完成后,相同的 Longlink (TCP) 连接将保持打开状态以传输未来的加密数据。对于 Shortlink,MMTLS 握手在第一个 HTTP 请求-响应周期中完成,然后第一个 HTTP 连接关闭。客户端和服务器存储已建立的密钥,当需要通过 Shortlink 发送数据时,这些已建立的密钥用于加密,然后通过新建立的 Shortlink 连接发送。在本节的其余部分,我们将描述握手的细节。

客户端你好

首先,客户端在SECP256R1 椭圆曲线上生成密钥对。请注意,这些椭圆曲线密钥与业务层加密部分中生成的密钥完全不同。客户端还会从本地存储中名为 的文件中读取一些恢复票证数据(psk.key如果存在)。该psk.key文件在收到第一个 ServerHello 后写入,因此,在全新安装的微信中,恢复票证将从 ClientHello 中省略。

客户端首先同时通过 Shortlink 和 Longlink 发送 ClientHello 消息(包含在握手记录中)。这两次握手中第一次成功完成的握手是初始业务层加密握手发生的握手(业务层加密的详细信息将在第 4 节中讨论)。之后,Shortlink 和 Longlink 连接都用于发送其他数据。

在初始 Shortlink 和 Longlink 握手中,每个 ClientHello 数据包都包含以下数据项:

  • ClientRandom(32字节随机数)

  • 从 psk.key 读取恢复票证数据(如果可用)

  • 客户端公钥

MMTLS ClientHello 的缩写版本如下所示。

16 f1 04 (Handshake Record header) . . .
01 04 f1 (ClientHello) . . .
08 cd 1a 18 f9 1c . . . (ClientRandom) . . .
00 0c c2 78 00 e3 . . . (Resumption Ticket from psk.key) . . .
04 0f 1a 52 7b 55 . . . (Client public key) . . .

请注意,客户端会为 Shortlink ClientHello 和 Longlink ClientHello 生成单独的密钥对。客户端发送的恢复票证在两个 ClientHello 数据包中是相同的,因为它始终从相同的 psk.key 文件中读取。在新安装的微信中,由于没有 psk.key 文件,因此会省略恢复票证。

服务器问候

客户端会收到一个 ServerHello 数据包,以响应每个 ClientHello 数据包。每个数据包包含:

  • 包含 ServerRandom 和服务器公钥的记录

  • 包含加密服务器证书、新的恢复票证和 ServerFinished 消息的记录。

MMTLS ServerHello 的缩写版本如下所示;可以在带注释的网络捕获中找到带有标签的完整数据包示例。

16 f1 04 (Handshake Record header) . . .
02 04 f1 (ServerHello) . . .
2b a6 88 7e 61 5e 27 eb . . . (ServerRandom) . . .
04 fa e3 dc 03 4a 21 d9 . . . (Server public key) . . .
16 f1 04 (Handshake Record header) . . .
b8 79 a1 60 be 6c . . . (ENCRYPTED server certificate) . . .
16 f1 04 (Handshake Record header) . . .
1a 6d c9 dd 6e f1 . . . (ENCRYPTED NEW resumption ticket) . . .
16 f1 04 (Handshake Record header) . . .
b8 79 a1 60 be 6c . . . (ENCRYPTED ServerFinished) . . .

收到服务器公钥后,客户端生成

secret = ecdh(client_private_key, server_public_key).

请注意,由于每个 MMTLS 加密隧道使用不同的客户端密钥对,因此共享密钥以及任何派生密钥和 IV 在 MMTLS 隧道之间都会有所不同。这也意味着长链接握手和短链接握手各自计算不同的共享密钥。

然后,使用共享密钥通过 HKDF 派生出几组加密参数,HKDF 是一种将短密钥值转换为长密钥值的数学安全方法。在本节中,我们将重点介绍握手参数。除了每组密钥之外,还会生成初始化向量 (IV) 。IV 是初始化AES-GCM加密算法所需的值。IV不需要保密。但是,它们必须是随机的并且不能重复使用。

握手参数使用 HKDF 生成(“握手键扩展”是程序中的常量字符串,以及本节中的其他 monotype 双引号字符串):

key_enc, key_dec, iv_enc, iv_dec = HKDF(secret, 56, “handshake key expansion”)

使用key_dec和iv_dec,客户端可以解密 ServerHello 记录的其余部分。解密后,客户端将验证服务器证书。然后,客户端还将新的恢复票证保存到文件psk.key。

此时,由于共享secret已建立,因此 MMTLS 加密握手被视为已完成。要开始加密和发送数据,客户端通过 HKDF 从共享密钥中派生其他参数集。派生哪些密钥以及用于哪些连接的详细信息在这些注释中完整指定,我们在注释中注释了微信启动时创建的密钥和连接。

2B. 数据加密

握手后,MMTLS 使用 AES-GCM 和特定密钥以及 IV(与特定 MMTLS 隧道绑定)来加密数据。IV 会根据之前使用此密钥加密的记录数递增。这一点很重要,因为重复使用具有相同密钥的 IV 会破坏 AES-GCM 提供的机密性,因为它可能导致使用已知标签进行密钥恢复攻击。ciphertext, tag = AES-GCM(input, key, iv+n)
ciphertext = ciphertext | tag

ciphertext, tag = AES-GCM(input, key, iv+n)
ciphertext = ciphertext | tag

16 字节标签附加在密文末尾。此标签是AES-GCM计算的身份验证数据;它充当MAC ,因为当正确验证时,此数据可提供身份验证和完整性。在许多情况下,如果这是正在加密的数据记录,input则包含已加密的元数据和密文,如业务层加密部分所述。

我们将在以下小节中分别讨论 Longlink 和 Shortlink 中的数据加密。

2B1. 长链

Longlink 数据包的客户端加密使用 AES-GCM 完成,key_enc和iv_enc在握手中派生。客户端解密使用key_dec和iv_dec。下面是一个示例 Longlink (TCP) 数据包,其中包含一条数据记录,其中包含来自服务器的加密心跳消息:

17 f1 04     RECORD HEADER (of type “DATA”)
00 20                 RECORD LENGTH
e6 55 7a d6 82 1d a7 f4 2b 83 d4 b7 78 56 18 f3 ENCRYPTED DATA
1b 94 27 e1 1e c3 01 a6 f6 23 6a bc 94 eb 47 39             TAG (MAC)

在长期 Longlink 连接中,IV 会随着每条加密记录而递增。如果创建新的 Longlink 连接,则会重新启动握手并生成新的密钥材料。

2B2. 短链接

Shortlink 连接只能包含单个 MMTLS 数据包请求和单个 MMTLS 数据包响应(分别通过 HTTP POST 请求和响应)。在启动时发送初始 Shortlink ClientHello 后,微信将发送带有握手恢复数据包的 ClientHello。这些记录的标头为 19 f1 04,而不是常规 ClientHello/ServerHello 握手数据包上的 16 f1 04。

下面是包含握手恢复的短链接请求包的缩写示例。

19 f1 04 (Handshake Resumption Record header) . . .
01 04 f1 (ClientHello) . . .
9b c5 3c 42 7a 5b 1a 3b . . . (ClientRandom) . . .
71 ae ce ff d8 3f 29 48 . . . (NEW Resumption Ticket) . . .
19 f1 04 (Handshake Resumption Record header) . . .
47 4c 34 03 71 9e . . . (ENCRYPTED Extensions) . . .
17 f1 04 (Data Record header) . . .
98 cd 6e a0 7c 6b . . . (ENCRYPTED EarlyData) . . .
15 f1 04 (Alert Record header) . . .
8a d1 c3 42 9a 30 . . . (ENCRYPTED Alert (ClientFinished)) . . .

请注意,根据我们对 MMTLS 协议的理解,此数据包中发送的 ClientRandom 不会被服务器使用,因为在恢复会话中无需重新运行 Diffie-Hellman。服务器使用恢复票证来识别应使用哪个先前建立的共享密钥来解密以下数据包内容。

Shortlink 数据包的加密是使用 AES-GCM 和握手参数 key_enc和iv_enc完成的。(请注意,尽管名称相同,但这里的key_enc和iv_enc与 Longlink 的 key_enc 和 iv_enc 不同,因为 Shortlink 和 Longlink 各自使用不同的椭圆曲线客户端密钥对完成自己的握手。)iv_enc会随着每条加密记录而递增。通常,通过S hortlink发送的 EarlyData 记录包含已使用业务层加密加密的密文以及相关元数据。然后,此元数据和密文将在此层进行额外加密。

微信内部将其称为 EarlyData 的原因可能是它借鉴了TLS;通常,它指的是在通过 Diffie-Hellman 建立常规会话密钥之前,使用从预共享密钥派生的密钥加密的数据。但是,在这种情况下,当使用 Shortlink 时,没有“在建立常规会话密钥之后”发送的数据,因此几乎所有 Shortlink 数据都在此 EarlyData 部分中加密和发送。

最后,ClientFinished表示客户端已完成其一方的握手。它是一条加密的警报记录,带有一条固定消息,该消息始终跟在 EarlyData 记录后面。通过逆向工程,我们发现此消息的处理程序将其称为ClientFinished。

3. 业务层请求

MMTLS 数据记录要么携带“业务层请求”,要么携带心跳消息。换句话说,如果解密 MMTLS 数据记录中的有效负载,结果通常是下面所述的消息。

这个业务层请求里面包含几个元数据参数,描述了请求的目的,包括内部URI和请求类型编号,我们在“发起微信网络请求”一节中简单描述过。

登录后,业务层请求的格式如下:

00 00 00 7b                 (total data length)
00 24                       (URI length)
/cgi-bin/micromsg-bin/... (URI)
00 12                       (hostname length)
sgshort.wechat.com          (hostname)
00 00 00 3D                 (length of rest of data)
BF B6 5F                    (request flags)
41 41 41 41                 (user ID)
42 42 42 42                 (device ID)
FC 03 48 02 00 00 00 00     (cookie)
1F 9C 4C 24 76 0E 00        (cookie)
D1 05 varint                (request_type)
0E 0E 00 02                 (4 more varints)
BD 95 80 BF 0D varint       (signature)
FE                          (flag)
80 D2 89 91
04 00 00                    (marks start of data)
08 A6 29 D1 A4 2A CA F1 ... (ciphertext)

响应的格式非常相似:

bf b6 5f                    (flags)
41 41 41 41                 (user ID)
42 42 42 42                 (device ID)
fc 03 48 02 00 00 00 00     (cookie)
1f 9c 4c 24 76 0e 00        (cookie)
fb 02 varint                (request_type)
35 35 00 02 varints
a9 ad 88 e3 08 varint       (signature)
fe
ba da e0 93
04 00 00                    (marks start of data)
b6 f8 e9 99 a1 f4 d1 20 . . . ciphertext

然后,此请求包含另一个加密的密文,该密文由我们称为业务层加密的加密方式加密。业务层加密与我们在MMTLS 加密部分中描述的系统是分开的。signature上面提到的是的输出,将在“完整性检查”部分中讨论。序列化方案的伪代码和微信加密请求标头的更多示例可以在我们的Github 存储库genSignature()中找到。

4.业务层加密

WeChat Crypto 图解(内层)https://docs.google.com/drawings/d/1WSZY_R8XBliTDrb3tSkmoZpaq3lsud0sBaWRuygGuJo/edit

本节介绍第 3 节中描述的业务层请求如何加密和解密,以及如何派生密钥。我们注意到,本节介绍的密钥集和加密过程与 MMTLS 加密部分中提到的密钥集和加密过程完全不同。通常,对于业务层加密,大部分协议逻辑都在 Java 代码中处理,并且 Java 代码调用 C++ 库进行加密和解密计算。而对于 MMTLS 加密,一切都在 C++ 库中处理,并且完全发生在不同的过程中。这两层加密之间几乎没有相互作用。

业务层加密有两种模式,分别是非对称模式和对称模式,加密过程不同。进入对称模式需要微信发起Autoauth请求。微信启动时一般会经历以下三个阶段:

  • 在用户登录账户之前,业务层加密首先使用非对称加密通过静态 Diffie-Hellman(静态 DH)派生共享密钥,然后使用共享密钥作为密钥对数据进行 AES-GCM 加密。我们称之为非对称模式。在非对称模式下,客户端会为每个请求派生一个新的共享密钥。

  • 使用非对称模式,微信可以发送一个 Autoauth 请求,服务器会返回一个 Autoauth 响应,其中包含一个session_key。

  • 客户端获取后session_key,业务层加密会用它对数据进行AES-CBC加密。由于它只使用对称加密,因此我们称之为对称模式。在对称模式下,同一个密钥session_key可以用于多个请求。

对于非对称模式,我们对 libwechatmm.so 中的 C++ 函数进行了动态和静态分析;特别是HybridEcdhEncrypt()和HybridEcdhDecrypt()函数,它们分别调用AesGcmEncryptWithCompress()/ AesGcmDecryptWithUncompress()。

对于对称模式,请求在 、 和 中的函数中处理pack()。unpack()通常genSignature(),libMMProtocalJNI.so处理pack()传出请求,并unpack()处理对这些请求的传入响应。它们还执行加密/解密。最后,genSignature()计算整个请求的校验和。在 Github 存储库中,我们上传了pack、AES-CBC加密和genSignature例程的伪代码。

业务层加密也与微信的用户认证系统紧密结合,用户需要登录后客户端才能发送Autoauth请求,未登录的客户端只使用非对称模式,已登录的客户端第一个业务层数据包通常是使用非对称模式加密的Autoauth请求,第二个及以后的业务层数据包则使用对称模式加密。

微信MMTLS加密协议安全性分析

图 2:业务层加密、注销、登录和登录:泳道图从高层次展示了业务层加密请求的样子,包括哪些秘密用于生成用于加密的密钥材料。secret🔑通过 DH(静态服务器公钥、客户端私钥)生成,🔑new_secret是 DH(服务器公钥、客户端私钥)。登录后,🔑会话从第一个响应中解密。虽然上面没有显示,但登录后, 🔑new_secret也在genSignature()中使用;此签名与请求和响应元数据一起发送。

4A.业务层加密,非对称模式

在用户登录微信账户之前,业务层加密过程使用静态服务器公钥,并生成新的客户端密钥对,以就每个微信网络请求的静态 Diffie-Hellman 共享密钥达成一致。共享密钥通过 HKDF 函数运行,任何数据都使用 AES-GCM 加密并与生成的客户端公钥一起发送,以便服务器可以计算共享密钥。

对于每个请求,客户端都会生成一个公钥和私钥对,用于ECDH。我们还注意到,客户端在应用程序中固定了一个静态服务器公钥。然后,客户端计算出一个初始密钥。

secret = ECDH(static_server_pub, client_priv)
hash = sha256(client_pub)
client_random = <32 randomly generated bytes>
derived_key = HKDF(secret)

derived_key然后使用 AES-GCM 加密数据,我们将在下一节中详细描述。

4B.业务层加密,获取session_key

如果客户端已登录(即用户在之前运行的应用上已登录微信帐号),第一个请求将是一个非常大的数据包,用于向服务器验证客户端的身份(在微信内部称为 Autoauth),其中也包含密钥材料。我们将此请求称为 Autoauth 请求。此外,客户端会拉取本地存储的密钥autoauth_key,我们没有追踪其来源,因为除了本例之外,它似乎没有被使用。用于加密此初始请求的密钥(authrequest_data)是 derived_key,计算方式与第 4A 节中相同。下面描述的加密是非对称模式加密,尽管是数据是的特例authrequest_data。

以下是序列化和加密的 Autoauth 请求的缩写版本:

08 01 12 . . . [Header metadata]
    04 46 40 96 4d 3e 3e 7e [client_publickey] . . .
    fa 5a 7d a7 78 e1 ce 10 . . . [ClientRandom encrypted w secret]
    a1 fb 0c da . . . [IV]
    9e bc 92 8a 5b 81 . . . [tag]
    db 10 d3 0f f8 e9 a6 40 . . . [ClientRandom encrypted w autoauth_key]
    75 b4 55 30 . . . [IV]
    d7 be 7e 33 a3 45 . . . [tag]
    c1 98 87 13 eb 6f f3 20 . . . [authrequest_data encrypted w derived_key]
    4c ca 86 03 . . [IV]
    3c bc 27 4f 0e 7b . . . [tag]

可以在Github 存储库中找到 Autoauth 请求和每层加密响应的完整示例。最后,我们注意到,autoauth_key除了在此特定请求中进行加密之外,上述内容似乎并未被积极使用。我们怀疑这是微信使用的旧加密协议的残留。

客户端在此处使用带有随机生成的 IV 的 AES-GCM 进行加密,并使用上述消息内容的 SHA256 哈希作为AAD。在此阶段,消息(包括 ClientRandom 消息)在加密之前始终是ZLib压缩的。

iv = <12 random bytes>
compressed = zlib_compress(plaintext)
ciphertext, tag = AESGCM_encrypt(compressed, aad = hash(previous), derived_key, iv)

上图中,previous 是请求的标头(即数据开始标记 04 00 00 之前的所有标头字节)。客户端将 12 字节 IV 和 16 字节标签附加到密文上。服务器可以使用此标签来验证密文的完整性,本质上起到MAC的作用。

4B1. 获取 session_key:Autoauth 响应

对 autoauth 的响应序列化方式与请求类似:

08 01 12 . . . [Header metadata]
04 46 40 96 4d 3e 3e 7e [new_server_pub] . . .
c1 98 87 13 eb 6f f3 20 . . . [authresponse_data encrypted w new_secret]
4c ca 86 03 . . [IV]
3c bc 27 4f 0e 7b . . . [tag]

使用新收到的服务器公钥(new_server_pub)(与应用程序中的硬编码不同)static_server_pub,客户端可以派生出一个新密钥(new_secret)。然后使用 new_secret 作为 AES-GCM 解密的密钥。客户端还可以使用给定的标签authresponse_data进行验证。authresponse_data

new_secret = ECDH(new_server_pub, client_privatekey)
authresponse_data= AESGCM_decrypt(aad = hash(authrequest_data),
new_secret, iv)

authresponse_data是一个序列化的 Protobuf,其中包含微信启动所需的大量重要数据,首先是一条有用的“ Everything is ok”状态消息。可以在Github 存储库中找到此 Protobuf 的完整示例。最重要的是,authresponse_data包含session_key,这是未来对称模式下 AES-CBC 加密使用的密钥。从现在开始,new_secret仅在中使用genSignature(),这将在下面的第 4C2 节完整性检查中讨论。

我们测量了服务器提供的 session_key 的熵,因为它将用于未来的加密。此密钥仅使用可打印的 ASCII 字符,因此熵值限制在约 100 位左右。

微信代码中涉及到三个不同的密钥:client_session、server_session、single_session。一般来说,client_session指的是client_publickey,server_session指的是使用 ECDH 即 生成的共享密钥new_secret,而single_session指的是session_key服务端提供的 。

4C.业务层加密,对称模式

客户端从服务器收到 session_key 后,未来的数据将使用对称模式加密。对称模式加密大多使用 AES-CBC 而不是 AES-GCM 进行,但一些大型文件除外,它们会使用 进行加密AesGcmEncryptWithCompress()。由于AesGcmEncryptWithCompress()请求是例外,因此我们重点介绍更常见的 AES-CBC 用法。

具体来说,对称模式使用带 PKCS-7 填充的 AES-CBC,并使用 session_key 作为对称密钥:

ciphertext = AES-CBC(PKCS7_pad(plaintext), session_key, iv = session_key)

这session_key被双重用作加密的 IV。

4C1. 完整性检查

在对称模式下,名为 的函数genSignature()会计算明文的伪完整性代码。此函数首先计算微信为登录用户分配的用户 ID ( )、的MD5 哈希值以及明文长度。然后,genSignature()对与明文连接的 MD5 哈希值使用校验和函数Adler32 。uinnew_secret

signature = adler32(md5(uin | new_secret | plaintext_len) |
            plaintext)

Adler32 的结果作为元数据连接到密文中(请参阅第 3A 节,了解如何将其包含在请求和响应标头中),并signature在微信的代码库中称为。我们注意到,虽然它被称为,但它不提供任何加密属性;详细信息可在“安全问题”部分中找到。此函数的完整伪代码也可以在Github 存储库signature中找到。

5. Protobuf 数据有效载荷

业务层加密的输入一般是序列化的 Protobuf,可选择使用 Zlib 进行压缩。登录后,发送到服务器的许多 Protobuf 都包含以下标头数据:

"1": {
    "1": "u0000",
    "2": "1111111111", # User ID (assigned by WeChat)
    "3": "AAAAAAAAAAAAAAAu0000", # Device ID (assigned by WeChat)
    "4": "671094583", # Client Version
    "5": "android-34", # Android Version
    "6": "0"
    },

Protobuf 结构定义在每个 API 对应的 RR 类中,正如我们之前在“发起微信网络请求”部分提到的。

6. 综合起来

在下图中,我们演示了打开微信应用程序的最常见情况的网络流程。我们注意到,为了防止图表进一步复杂化,未显示 HKDF 派生;例如,当🔑mmtls使用“ ”时,HKDF 用于从“ 🔑mmtls”派生密钥,并使用派生密钥进行加密。有关如何派生密钥以及使用哪些派生密钥加密哪些数据的具体信息,可参见这些说明。

微信MMTLS加密协议安全性分析

图 3:泳道图展示了最常见情况(用户登录、打开微信应用程序)的加密设置和网络流。

我们注意到其他配置也是可能的。例如,我们观察到,如果 Longlink MMTLS 握手首先完成,业务层“登录”请求和响应可以通过 Longlink 连接进行,而不是通过多个短链接连接进行。此外,如果用户已注销,业务层请求只需使用🔑密钥加密(类似于短链接 2请求)

安全问题

在本节中,我们概述了我们在构建MMTLS 加密和业务层加密层时发现的潜在安全问题和隐私漏洞。还可能存在其他问题。

MMTLS 加密问题

下面我们详细介绍了我们在微信的MMTLS加密中发现的问题。

确定性 IV

MMTLS 加密过程每次连接生成一个 IV。然后,它们会为该连接中加密的每个后续记录增加 IV。通常,NIST建议不要在 AES-GCM 中使用完全确定性的 IV 派生,因为很容易意外重复使用 IV。在 AES-GCM 的情况下,重复使用 (key, IV) 元组是灾难性的,因为它允许从 AES-GCM 身份验证标签中恢复密钥。由于这些标签附加到 AES-GCM 密文以进行身份验证,因此可以从使用相同密钥和 IV 对加密的少至 2 个密文中恢复明文。

此外,Bellare 和 Tackmann还表明,使用确定性 IV 可以让强大的对手暴力破解特定的 (密钥,IV) 组合。如果加密系统部署到非常大的(即互联网大小)所选 (密钥,IV) 组合池中,则这种类型的攻击适用于强大的对手。由于微信拥有超过 10 亿用户,因此这种数量级使这种攻击处于可行性范围内。

缺乏前向保密

现代通信协议通常都要求具有前向保密性,以降低会话密钥的重要性。一般来说,TLS 本身在设计上就是前向保密性的,但“恢复”会话的第一个数据包除外。第一个数据包使用“预共享密钥”或上次握手期间建立的 PSK 进行加密。

MMTLS 在设计上大量使用 PSK。由于 Shortlink 传输格式仅支持单次往返通信(通过单个 HTTP POST 请求和响应),因此通过传输格式发送的任何加密数据都使用预共享密钥加密。由于泄露共享的 `PSK_ACCESS` 密钥将使第三方能够解密通过多个 MMTLS 连接发送的任何 EarlyData,因此使用预共享密钥加密的数据不是前向机密。通过 MMTLS 加密的绝大多数记录都是通过 Shortlink 传输发送的,这意味着微信发送的大多数网络数据在连接之间不是前向机密。此外,在打开应用程序时,微信会创建一个长寿命的 Longlink 连接。这个长寿命的 Longlink 连接在微信应用程序的持续时间内都是打开的,任何需要发送的加密数据都通过同一连接发送。由于大多数微信请求要么使用(A)会话恢复 PSK 或(B)长寿命 Longlink 连接的应用程序数据密钥加密,因此微信的网络流量通常不会在网络请求之间保留前向保密性。

业务层加密问题

业务层加密结构,尤其是对称模式 AES-CBC 结构本身存在许多严重问题。由于微信发出的请求是双重加密的,这些问题只影响内部业务层加密,因此我们没有找到立即利用它们的方法。然而,在仅使用业务层加密的旧版微信中,这些问题是可以被利用的。

元数据泄露

业务层加密不会对用户ID、请求URI等元数据进行加密,具体细节在“业务层请求”一节中提到。这个问题也被微信开发者们自己承认,也是开发MMTLS加密的动机之一。

可伪造的 genSignature 完整性检查

虽然代码的用途genSignature并不完全清楚,但如果要将其用于身份验证(因为ecdh_key包含在 MD5 中)或完整性,则两方面都会失败。可以使用任何已知的 来计算有效的伪造,plaintext而无需知道ecdh_key。如果客户端为某些已知的明文消息 生成以下内容plaintext:

sig = adler32(md5(uin | ecdh_key | plaintext_len) | plaintext)

我们可以执行以下操作来伪造长度为evil_sig的签名:evil_plaintextplaintext_len

evil_sig = sig - adler32(plaintext) + adler32(evil_plaintext)

当消息较短时,adler32可以通过求解方程组来实现校验和的减法和加法。减法和加法校验和的代码,从而伪造此完整性检查,可以在我们的 Github 存储库中找到。adler32adler.py

可能的 AES-CBC 填充预言机

由于 AES-CBC 与PKCS7填充一起使用,因此单独使用这种加密可能会受到AES-CBC 填充 oracle 的影响,从而导致加密明文被恢复。今年早些时候,我们发现腾讯公司开发的另一种自定义加密方案容易受到这种攻击。

分组密码模式下密钥、IV 重复使用

将密钥重新用作 AES-CBC 的 IV,以及在给定会话(即用户打开应用程序的时间长度)内对所有加密重复使用同一密钥,会给加密明文带来一些隐私问题。例如,由于密钥和 IV 提供了所有随机性,因此重复使用两者意味着如果两个明文相同,它们将加密为相同的密文。此外,由于使用了CBC 模式,两个具有相同 N 个块长度前缀的明文将加密为相同的前 N 个密文块。

加密密钥问题

服务器选择客户端使用的加密密钥是非常不合常规的。事实上,我们注意到服务器生成的加密密钥(“会话密钥”)仅使用可打印的 ASCII 字符。因此,即使密钥长度为 128 位,该密钥的熵也最多为 106 位。

无前向保密

如上一节所述,前向保密是现代网络通信加密的标准属性。当用户登录时,在此加密层上与微信的所有通信都使用完全相同的密钥进行。直到用户关闭并重新启动微信,客户端才会收到新密钥。

其他版本微信

为了验证我们的发现,我们还在 Android 版微信 8.0.49(2024 年 4 月发布)上测试了我们的解密代码,发现 MMTLS 网络格式与 iOS 版微信 8.0.49 使用的格式相匹配。

微信网络加密的早期版本

为了了解微信复杂的加密系统是如何联系在一起的,我们还简要地对未使用 MMTLS 的旧版微信进行了逆向工程。未使用 MMTLS 的最新版本微信是 2016 年发布的 v6.3.16。我们对这次逆向工程的完整记录可以在这里找到。

注销时,请求主要使用业务层加密密码系统,使用 RSA 公钥加密,而不是静态 Diffie-Hellman 加上通过 AES-GCM 的对称加密。我们观察到对内部 URIcgi-bin/micromsg-bin/encryptcheckresupdate和 的请求cgi-bin/micromsg-bin/getkvidkeystrategyrsa。

还使用了另一种加密模式,即带静态密钥的 DES。此模式用于发送崩溃日志和内存堆栈;对 URI 的 POST 请求/cgi-bin/mmsupport-bin/stackreport使用 DES 加密。

我们无法登录此版本进行动态分析,但从静态分析中,我们确定登录时的加密行为与业务层加密相同(即使用session_key服务器提供的 AES-CBC 加密)。

讨论

为什么业务层加密很重要?

既然业务层加密被包裹在 MMTLS 中,那么它是否安全又有什么关系呢?首先,从我们对微信早期版本的研究来看,业务层加密是微信网络请求的唯一加密层,直到 2016 年。其次,从业务层加密暴露未加密的内部请求 URI 的事实来看,微信的可能架构之一是托管不同的内部服务器来处理不同类型的网络请求(对应不同的“requestType”值和不同的 cgi-bin 请求 URL)。例如,在前端微信服务器(处理 MMTLS 解密)终止 MMTLS 后,转发到相应内部微信服务器的内部微信请求不会被重新加密,因此仅使用业务层加密进行加密。微信内联网中的网络窃听者或网络分路器可能会攻击这些转发请求上的业务层加密。然而,这种情况纯粹是推测性的。腾讯对我们披露的回应涉及业务层加密中的问题,并暗示他们正在慢慢从问题更大的 AES-CBC 迁移到 AES-GCM,因此腾讯也对此感到担忧。

为什么不使用 TLS?

根据公开文件和我们自己的研究结果,MMTLS(加密的“外层”)主要基于 TLS 1.3。事实上,该文档表明 MMTLS 的架构师对非对称加密有着相当的了解。

该文件包含不使用 TLS 的理由。它解释说,微信使用网络请求的方式需要类似0-RTT会话恢复之类的东西,因为大多数微信数据传输只需要一个请求-响应周期(即 Shortlink)。MMTLS 只需要一次往返握手来建立底层 TCP 连接,然后才能发送任何应用程序数据;根据该文件,为 TLS 1.2 握手引入另一次往返是行不通的。

幸好,TLS1.3 提出了 0-RTT(无额外网络延迟)的协议握手方式,此外协议本身也通过版本号、CipherSuite、Extension 等机制提供了可扩展性。不过 TLS1.3 目前还处于草案阶段,距离正式实施可能还很遥远。TLS1.3 也是面向所有 App 的通用协议,考虑到微信的特点,还有很大的优化空间。因此,最终我们选择基于 TLS1.3 草案标准,设计和实现自己的安全传输协议 MMTLS。[原文]

然而,即使在 2016 年撰写本文时,TLS 1.2 也确实提供了会话恢复选项。此外,由于微信同时控制服务器和客户端,因此部署当时正在测试的成熟的 TLS 1.3 实现似乎并不不合理,即使 IETF 草案不完整。

尽管 MMTLS 的设计者们付出了最大的努力,但总体而言,微信使用的安全协议似乎比 TLS 1.3 性能和安全性都更低。总的来说,设计一个安全且性能良好的传输协议并非易事。

握手时进行额外往返的问题一直是应用程序开发人员面临的一个长期问题。TCP 和 TLS 握手都需要一次往返,这意味着发送的每个新数据包都需要两次往返。如今,TLS-over-QUIC 结合了传输层和加密层握手,只需要一次握手。QUIC 兼具两全其美的优势,既提供了强大的前向秘密加密,又将安全通信所需的往返次数减少了一半。我们建议微信迁移到标准 QUIC 实现。

最后,除了网络性能之外,还有客户端性能的问题。由于微信的加密方案对每个请求执行两层加密,因此客户端加密数据的工作量是使用单一标准化密码系统的两倍。

国产密码技术在我国的应用趋势

这一发现为我们 之前的许多 研究提供了帮助,这些研究揭示了本土加密技术在我国应用中的流行程度。总体而言,避免使用 TLS 并偏爱专有和非标准加密技术背离了加密最佳实践。虽然 2011 年可能有许多正当理由不信任 TLS(例如EFF和Access Now对证书颁发机构生态系统的担忧),但自那时起 TLS 生态系统已基本稳定,并且更具可审计性和透明度。与 MMTLS 一样,我们过去研究过的所有专有协议都包含相对于 TLS 的弱点,在某些情况下甚至可能被网络对手轻易解密。随着全球互联网逐渐转向使用 QUIC 或 TLS 等技术保护传输中的数据,这是一种日益增长且令人担忧的趋势,仅出现在国安全领域。

反DNS劫持机制

和腾讯编写自己的加密系统类似,我们发现在 Mars 中他们也编写了一个专有的域名查找系统。这个系统是 STN 的一部分,能够支持通过 HTTP 从域名到 IP 地址的查找。这个功能在 Mars 中被称为“NewDNS”。根据我们的动态分析,这个功能在微信中经常使用。乍一看,NewDNS 重复了 DNS(域名系统)已经提供的功能,而 DNS 已经内置在几乎所有联网设备中。

微信并不是国唯一一款使用此类系统的应用程序。国的主要云计算提供商(如阿里云和腾讯云)都提供自己的 DNS over HTTP 服务。在 VirusTotal 上搜索尝试联系腾讯云的 DNS over HTTP 服务端点 (119.29.29.98)的应用程序,得到了3,865 个唯一结果。

采用这种系统的一个可能原因是,国的 ISP 经常实施DNS 劫持来插入广告并重定向网络流量以进行广告欺诈。这个问题非常严重,以至于六家国互联网巨头于 2015 年发表联合声明,敦促 ISP 改进。根据新闻文章,美团(一家在线购物网站)约 1-2% 的流量受到 DNS 劫持的影响。近年来,国 ISP 的广告欺诈似乎仍然是一个普遍存在的 问题。

与 MMTLS 加密系统类似,腾讯的 NewDNS 域名查询系统旨在满足国网络环境的需求。多年来,DNS 本身已被证明存在多个安全和隐私问题。与 TLS 相比,我们发现微信的 MMTLS 存在更多缺陷。然而,与 DNS 本身相比,NewDNS 是否存在更多或更少的问题仍是一个悬而未决的问题。我们将这个问题留待将来研究。

Mars STN 在微信之外的使用

基于以下观察,我们推测 Mars(mars-open)在微信之外也有广泛的应用:

  • Mars GitHub 存储库中存在许多问题。

  • 有大量 技术文章概述了如何使用 Mars 构建即时通讯系统。

  • 目前已经有基于Mars的白标即时通讯系统产品。

微信之外对 Mars 的采用令人担忧,因为 Mars 默认不提供任何传输加密。正如我们在“Mars 的三个部分”部分中提到的,微信中使用的 MMTLS 加密是 mars-wechat 的一部分,而 mars-wechat并非开源的。Mars 开发人员也不打算添加对 TLS 的支持,并希望其他使用 Mars 的开发人员在上层实现自己的加密。更糟糕的是,在 Mars 中实现 TLS似乎需要进行相当多的架构更改。尽管腾讯保留 MMTLS 专有权并不公平,但 MMTLS 仍然是 Mars 设计的主要加密系统,将 MMTLS 保留为专有意味着其他使用 Mars 的开发人员要么投入大量资源将不同的加密系统与 Mars 集成,要么将所有内容都保留为未加密状态。

Mars 的文档也比较缺乏。官方wiki上只有几篇关于如何与 Mars 集成的旧文章。使用 Mars 的开发人员通常求助于在 GitHub 上提问。缺乏文档意味着开发人员更容易犯错,最终降低安全性。

该领域还有待进一步研究,分析使用腾讯Mars库的应用程序的安全性。

“Tinker”,一个动态代码加载模块

在本节中,我们暂将从 Google Play Store 下载的 APK 称为“微信 APK”,将从微信官方网站下载的 APK 称为“微信 APK”。微信和微信之间的区别似乎很模糊。微信 APK 和微信 APK 包含部分不同的代码,我们将在本节后面讨论。然而,当将这两个 APK 安装到英语语言环境的 Android 模拟器时,它们的应用名称都显示为“微信”。它们的应用程序 ID(Android 系统和 Google Play Store 用于识别应用程序)也都是“com.tencent.mm”。我们还能够使用这两个 APK 登录我们的美国号码账户。

与微信 APK 不同,我们发现微信 APK 包含 Tinker,即“热修复解决方案库”。Tinker 允许开发人员通过使用一种称为“动态代码加载”的技术来更新应用程序本身,而无需调用 Android 的系统 APK 安装程序。在之前的一份报告中,我们发现 TikTok 和抖音之间也有类似的区别,我们发现抖音具有类似的动态代码加载功能,而 TikTok 中没有此功能。此功能引发了三个担忧:

  1. 如果下载和加载动态代码的过程没有充分验证下载的代码(例如,它使用正确的公钥进行加密签名,它不是过期的,并且它是要下载的代码而不是其他经过加密签名的最新代码),攻击者可能能够利用此过程在设备上运行恶意代码(例如,通过注入任意代码、执行降级攻击或执行侧级攻击)。早在 2016 年,我们就在其他 国 应用中发现了这种情况。

  2. 即使代码下载和加载机制没有任何弱点,动态代码加载功能仍允许应用程序在不通知用户的情况下加载代码,从而绕过用户决定在其设备上运行哪些程序的同意。例如,开发人员可能会推出不需要的更新,而用户没有选择继续使用旧版本的选择。此外,开发人员可能会选择性地向用户提供危害其安全或隐私的更新。2016 年,一位国安全分析师指责阿里巴巴向支付宝推送动态加载的代码,以偷偷在他的设备上拍照和录音。

  3. 动态加载代码会使应用商店审核人员无法审核应用执行的所有相关行为。因此,Google Play 开发者计划政策不允许应用使用动态代码加载。

在分析微信 APK 时,我们发现,虽然它保留了 Tinker 的一些组件。似乎处理应用更新下载的组件仍然存在,但 Tinker 处理加载和执行下载的应用更新的核心部分已被“no-op”函数替换,这些函数不执行任何操作。我们没有分析其他第三方应用商店提供的微信二进制文件。

需要进一步研究分析Tinker的应用更新过程的安全性,其他来源的微信APK是否包含动态代码加载功能,以及微信APK和微信APK之间是否存在其他差异。

建议

在本节中,我们根据调查结果向相关受众提出建议。

对于应用程序开发者

与使用经过严格审查的标准加密套件相比,实施专有加密的成本更高、性能更低、安全性更低。鉴于应用程序发送的数据具有敏感性,我们鼓励应用程序开发人员使用久经考验的加密套件和协议,避免自行加密。经过严格的公众和学术审查,SSL/TLS 近三十年来经历了各种改进。TLS 配置现在比以往任何时候都更容易,而基于 QUIC 的 TLS 的出现大大提高了性能。

致腾讯及微信开发者

以下是我们在披露中向微信和腾讯发送的建议副本。完整的披露函件可在附录中找到。

在2016 年的这篇文章中,微信开发人员指出他们希望升级加密,但为 TLS 1.2 握手增加另一个往返将显著降低微信网络性能,因为该应用程序依赖于许多短时间的通信。当时,TLS 1.3 还不是 RFC(尽管 TLS 1.2 有会话恢复扩展),因此他们选择“自行开发”,并将 TLS 1.3 的会话恢复模型纳入 MMTLS。

握手时进行额外往返这一问题一直是全球应用程序开发人员面临的长期问题。TCP 和 TLS 握手各需要一次往返,这意味着发送的每个新数据包都需要两次往返。如今,TLS-over-QUIC 结合了传输层和加密层握手,只需要一次握手。QUIC 就是为此目的而开发的,它可以提供强大的前向秘密加密,同时将安全通信所需的往返次数减半。我们还注意到,微信似乎已经在使用 QUIC 下载一些大文件。我们建议微信完全迁移到标准 TLS 或 QUIC+TLS 实现。

除了网络性能之外,客户端性能也是问题所在。由于微信的加密方案对每个请求执行两层加密,因此与微信使用单一标准化密码系统相比,客户端加密数据的工作量增加了一倍。

对于操作系统

在网络上,客户端浏览器安全 警告和使用 HTTPS 作为搜索引擎排名因素促成了 TLS 的广泛采用。我们可以将其与移动生态系统的操作系统和应用商店进行粗略的类比。

是否有任何平台或操作系统级别的权限模型可以指示标准加密网络通信的常规使用情况?正如我们在之前研究中文 IME 键盘专有加密技术的工作中所提到的,操作系统开发人员可以考虑设备权限模型,该模型可以显示应用程序是否使用较低级别的系统调用进行网络访问。

对于有隐私问题的高风险用户

许多微信用户是出于需要而非选择而使用它。对于出于需要而使用微信的隐私问题用户,我们之前报告中的建议仍然适用:

尽可能避免使用被划定为“微信”服务的功能。我们注意到,隐私政策中划定的许多核心“微信”服务(如搜索、频道、小程序)比核心“微信”服务执行更多的跟踪。

如果可能的话,优先选择网页或应用程序,而不是小程序或其他此类嵌入式功能。

使用更严格的设备权限并定期更新您的软件和操作系统以获得安全功能。

此外,由于官网下载的微信存在动态代码加载的风险,我们建议用户尽量从 Google Play Store 下载微信。对于已经从官网安装微信的用户,卸载并重新安装 Google Play Store 版本也可以降低风险。

对于安全和隐私研究人员

由于微信拥有超过 10 亿用户,我们推测全球 MMTLS 用户的数量级与全球 TLS 用户的数量级相似。尽管如此,与 TLS 相比,几乎没有第三方对 MMTLS 进行分析或审查。在这种影响力规模下,MMTLS 值得与 TLS 进行类似的审查。我们恳请未来的安全和隐私研究人员在此基础上继续研究 MMTLS 协议,因为从我们的通信中可以看出,腾讯坚持继续使用和开发用于微信连接的 MMTLS。

致谢

我们非常感谢 Jedidiah Crandall、Jakub Dalek、Prateek Mittal 和 Jonathan Mayer 对本报告的指导和反馈。本项目的研究由 Ron Deibert 监督。

附录

在本附录中,我们详细说明了向腾讯披露的我们的调查结果及其回应。

2024 年 4 月 24 日——我们的披露

敬启者:

公民实验室是位于加拿大多伦多的多伦多大学蒙克全球事务与公共政策学院的一个学术研究小组。

我们分析了 Android 和 iOS 版微信 v8.0.23,这是我们正在进行的分析热门移动和桌面应用程序的安全和隐私问题工作的一部分。我们发现,微信的专有网络加密协议 MMTLS 与现代网络加密协议(如 TLS 或 QUIC+TLS)相比存在弱点。例如,该协议不是前向保密的,可能容易受到重放攻击。我们计划发布 MMTLS 网络加密协议的文档,并强烈建议负责超过 10 亿用户网络安全的微信改用强大且性能良好的加密协议,如 TLS 或 QUIC+TLS。

欲了解更多详细信息,请参阅附件。

公开披露时间表

公民实验室致力于研究透明度,并将在其网站上发布其在研究活动中发现的安全漏洞的详细信息(除非特殊情况):https://citizenlab.ca/。

公民实验室将在本通讯发布之日起 45 个日历日内公布我们的分析细节。

如果您对我们的调查结果有任何疑问,请告诉我们。您可以通过以下电子邮件地址联系我们:[email protected]

真挚地,

公民实验室

2024 年 5 月 17 日——腾讯的回应

感谢您的报告。自2024年4月25日收到您的报告以来,我们进行了认真的评估。微信的安全协议核心是外层mmtls加密,目前确保外层mmtls加密是安全的。另一方面,内层的加密问题处理如下:核心数据流量已切换到AES-GCM加密,其他流量正在逐步从AES-CBC切换到AES-GCM。如果您还有其他问题,请告诉我们。谢谢。

原文地址(点击阅读原文):

https://citizenlab.ca/2024/10/should-we-chat-too-security-analysis-of-wechats-mmtls-encryption-protocol/

原文始发于微信公众号(Ots安全):微信MMTLS加密协议安全性分析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月29日18:20:33
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   微信MMTLS加密协议安全性分析https://cn-sec.com/archives/3328885.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息