模糊矩阵加密的冒险

2021-06-15 03:50:15

大家好!我的名字是丹尼斯和我和第39; m一个securityResearcher。六个月前,我开始努力为重要的矩阵项目进行专用的安全研究。在一些初步关注突触的初步关注时,我决定仔细看看Libolm。在这个条目中,我喜欢topresent概述了那项工作,以及一些早期的水果出来。

TL;博士:我们发现了一些悄悄地利用Libolm'原来审计的一些错误,因为正确地改变了我们的模糊能力,我们' D喜欢你所有的东西!错误不容易利用(如果根本),并且已经修复了。

为了给出一些背景,Libolm是一个加密库,实现了双重ratchetalgorithm先驱的bysignal,它是矩阵后面的加密工作。在矩阵土地中称为OLM,但Libolm还实现了Megolm,其中许多参与者之间有效的加密组聊天的一个变体。

由于Libolm目前用于支持端到endEnclyption的所有矩阵客户端,因此它是特别多汁的目标。目前的州'矩阵加密垄断的统一性有点不幸 - 幸运的是,在地平线上有一些令人兴奋的新发展,例如锈病的vodozempacemation。但是现在,我们陷入困境。

要开始,我决定做一些模糊。 Libolm已经有一个模糊套装AFL,但这是在前的写作。在过去几年中,最先进的杰出卓越的速度迅速,所以设置缺少了许多熟悉的特征和技巧。作为示例,模糊设置被配置为使用现在的古代AFL-GCC覆盖方式,这可以比仅基于MoreModern LLVM的覆盖率慢一倍。

我也注意到这种模糊用非硬化二进制文件(而不是像Asan这样的东西),所以很多内存错误都可以' vere没有注意到。也没有从以前的模糊运行中提供的集团,那么一些代码没有被用具覆盖。

我决定一个接一个地解决这些,添加ASAN和MSAN作为FirstStep。我借此机会切换到AFL ++以来是一个替代品,包含许多改进,值得明显的覆盖方式,这些模式要快得多(例如LLVM-PCGuard)orguarantEED,无法碰撞(LTO)1. AFL ++还优化变形(通过使用来自aflfast的调度算法和突变运算符选择(通过mopt)。所有这些都在发现错误时更有效率。

在此之后,我改变了现有的线束来使用AFL' s持久模式(降低过程创建开销,从而增加了模糊性能)。这种变化,结合开关到更新的覆盖方式,从我的机器上增加了模糊exec / s到〜5.5k,所以这是诺兰人的收益!

在这项准备工作之后,我生成了一个小型初始语料库,并用不同参数运行的小型模糊。几乎立即,我开始了撞击的撞击。幸运的是,经过一些调查,这些概述了图书馆中的错误虫,而是在模糊束缚中是一个双重的!只有在输入的大小0时才会触发。它也只有AFL ++而不是香草AFL,可能是由于输入的逻辑差异,这必须是未来没有人注意到的原因。我用修补程序迅速命名并恢复。

我让模糊跑了一段时间。由于ASAN引入了一个Persification头发,因此我只使用二进制文件的ASAN变体运行单个AFL实例。这是可以的,因为所有的模糊实例都实际上会同步他们的发现,这意味着每个实例都会看到增加覆盖的每个输入。当我回来检查,还有另一个碰撞等待。这次蒙古德输入是不断生成的输入,所以它看起来很有妥协 - 并且只有Asan实例正在崩溃。 A-HA!

运行在线束的ASAN变体上的违规输入显示它是浪花无效读取堆缓冲区末尾的一个字节。读取的是Base64解码器:

❮./build/fuzzers/fuzz_group_decrypt_asan""腌入机组组会话.txt<输入====================================== ============================= 1838065 ==错误:addresssanitizer:堆缓冲区溢出在地址0xf4a00795上PC 0x56560660 bp 0xffff9df8 sp 0xffff9de8 read大小1在0xf4a00795线程t0#0 0x5656065f在olm :: decode_base64(未签名的char const *,unsigned int,unsigned char *)src / base64.cpp:124#1 0x565607b5 in _olm_decode_base64 src / base64.cpp:165#2 0x565d5a9e在olm_group_decrypt_max_plaintext_length SRC / inbound_group_session.c:304在主模糊器/ fuzz_group_decrypt.cpp#3 0x56558e75:46#4 0xf7509a0c在__libc_start_main(/usr/lib32/libc.so.6+0x1ea0c)#5 0x5655a0f4在_start(/家/ DKASAK / CODE / OLM / BUID / fuzzers / fuzz_group_decrypt_asan + 0x50f4)0xf4a00795位于5字节区域的右侧的0个字节[0xf4a00790,0xf4a00795)在此处分配:#0 0xf7a985c5 __interceptor_malloc / build / gcc / src / gcc / libsanitizer / asan / asan_malloc_linux.cpp:145#1 0x56558Ce3在主模糊物/ fuzz_group_decrypt.cpp: 32#2 0xf7509a0c在__libc_start_main中(/usr/lib32/libc.so.6+0x1ea0c)summary:addresssanitizer:heap-beaffer-odflow src / base64.cpp:124在olm :: decode_base64中(未签名的char const *,unsigned int,无符号字符*)暗影字节左右的车地址:0x3e9400a0:发发发发发发发发发发发发发发发发0x3e9400b0:发发发发发发发发发发发发发发发发0x3e9400c0:发FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FAF FA FA FA FA FA FA FA FA FA FA FAFA FA => 0x3E9400F0:FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FAF FA FA FA FA FA FA FA FA FA FA FA FA FA FAF FA FAF FA FAF FA FAF FA FAF FA FAF FA FAF FA FAF FA FA发发发发发发发发发发0x3e940120:发发发发发发发发发发发发发发发发0x3e940130:发发发发发发发发发发发发发发发发0x3e940140:发发FA FA FA FA FA FA FA FA FA FAFAWAD BYTE传奇(一个影子字节代表8个应用程序字节):可寻址:00 PARII盟友可寻址:01 02 03 04 05 06 07堆左重新释放:FA释放堆区域:FD堆栈左重新滤芯:F1堆栈中间redzone:F2堆栈右重新滤芯:F3堆栈返回后:F8堆栈范围后:F8全局redzone:F9全局init订单:F6由用户中毒:F7集装箱溢出:FC阵列cookie:AC Intra Object Redzone:BB Asan内部:FE Letth Alloca Redzone:CA右类redzone:CB阴影间隙:CC == 1838065 ==中止

在堆栈跟踪之后,我很快就查明了错误的根:解码器的逻辑被细冲探险,无条件地在Base64输入中访问剩余的字节2,这可能并非实际上存在。当输入是1(mod 4)时发生长度,这可能永远不会发生在有效的base64有效载荷中,但当然我们不能假设所有输入都是必需的有效有效载荷。具体地,如果有效载荷不是0(mod 4)的长度,则代码假设它是至少2(mod 4)或更多的长度,并且读取第二字节。然后将这种虚假的字节包含在内的输出值。

我检查了代码试图找到一种方法来泄漏更多单个字节,但这是不可能的。事实证明,由于字节ISENDODED的方式,甚至没有将完整的字节信息编码为输出 - 由于字节ISENSODED的方式,但在输出值中仅最终有6位有用信息。

仍然,即使是单个泄漏的位也太多了在加密上下文中。可以做一些堆的黑客,以便在那里放置感兴趣的东西,然后泄露给我们?

我接下来追踪漏洞函数OLM :: Decode_Base64的所有呼叫站点。他们中的大多数都是对这个问题的免疫,因为他们被呼叫到另一个函数,OLM :: Decode_Base64_Length,基本64有效载荷是法律长度的挑选。这只留下了我只有几个易受攻击的呼叫网站,所以我检查了他们的Base64输入Comefrom的位置。承诺,其中两个收到了其他对话术客的输入,但它们要么无法将信息泄回到Theattacker,或者在确保输入的输入之后,它们已经硬结了要处理的字节数。剩余函数OLM_PK_DECRYPT的输出永远不会在外部发送任何地方,因此还没有泄漏数据到攻击者。

总之,即使这个无效的阅读是一个有效的错误,我也无法加以工作的漏斗。

但等一下!有些东西仍在困扰我的olm_pk_decrypt.it' s一个相当复杂的函数,从AhomeServer接收多个字符串输入,它本身是由任何线束测试的ISN' t。此外,我首先开始看它的原因是它缺少OLM :: Decode_Base64_Length检查。也许它需要仔细看看?

并肯定,有些不对劲。由于OLM_PK_DECRYPT接收来自Homeserver的三base64输入:要解密的密文,一个短射出的键和MAC。所有三个最终将被解码为OLM :: Decode_Base64。然而,只有一个长度的检查,以确保进入密码的​​密文适合其输出缓冲区。如果Theserver返回超过预期的公钥,会发生什么?

从代码段可以看出,公钥的解码版本获取Writtento EPhemeral.Public_Key,它是在堆栈上分配的数组。如果inInput比预期的长,则这将成为堆栈缓冲区溢出。

OLM_PK_DECRYPT的目的是解密先前在Homeserver上存储的矩阵设备的秘密。加密点是为了防止他们以来的学习这些秘密,因为它们' re应该只知道自己的设备。此机制的一个用例是允许您的其中一个是在Homeserver上存储加密的端到端加密密钥。然后,您的其他设备可以从HomeServer中检索那些键,从而使您可以在每个设备上查看所有私有谈话。

我决定去结束于结束的测试来确认错误是易于使用我的测试手机到我的主页的最新元素Android的令人触发的错误,用Mitmproxy坐在之间。这使我允许一台小型麻省备注脚本拦截从Homeserver获取e2Eencryption键并修改响应的HTTP调用,以便keys长于预期的键。

从Mitmproxy导入CTX导入JSON,HTTP DEF响应(flow:http .httpflow) - >无:if(" / _矩阵/客户/ rustable / room_keys / keys"在flow .request .pretty_url和flow .request .method ==" get"):response_body = flow .response .content .decode(" utf-8")response_json = json .loads(response_body)的房间= response_json ["房间" ] room_id = list(房间.keys())[0]会话=房间[room_id] ["会话" ]会话=列表(会话.keys())[0] session_data = sessions [session] [" session_data" ] ephemeral = session_data ["星期六; ] CTX .log .info(f"取代短信钥匙' {ephemeral}'' {ephemeral * 10}'")session_data ["短期" ] =短暂* 10修改_Body = JSON .dumps(Response_JSON).encode(" UTF-8")流。响应.content = modified_body

然后,该较长值最终通过元素Android到Libolm' s olm_pk_decrypt,它触发缓冲区溢出。使用所有此,我删除了设备上的本地加密密钥备份,并要求从服务器恢复它:

f libc:堆栈损坏检测到(-fstack-protector)f libc:致命信号6(sigabrt),tid 24517中的代码-6(si_tkill)(defaultdispatch),pID 24459(im.vector.app)f调试:*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***调试:构建指纹:' xiaomi / tissot / tissot_sprout:9 / pkq1.180917.001 / v10.0.24.0.pdhmixm:用户/释放键和#39; f调试:' 0' f调试:abi :' arm64' f调试:pid:24459,tid:24517,名称:defaultdispatch>>>>>>> im.vector.app<<<˚FDEBUG:信号6(SIGABRT),码-6(SI_TKILL),故障地址--------˚FDEBUG:中止消息:'堆栈损坏检测(-fstack保护器)'˚FDEBUG:X0 X1 0000000000000000 X2 0000000000005fc5 X3 0000000000000006 DEBUG 0000000000000008F:X4 X5 0000000000000000 0000000000000000 0000000000000000 5233 X7 0000000000000030F DEBUG:X8 0000000000000083 X9 7d545b4513138652 X10 X11 0000000000000000 DEBUG fffffffc7ffffbdfF:X12 X13 0000000000000001 X14 0000000060b0f2a9 X15 0022ed916fede200 0000d925cd93f18fF DEBUG:X16 00000079e74​​1b2b0 X17 X18 00000079e733c9d8 0000000000000000 X19 0000000000005f8bF DEBUG:X20 X21 0000000000005fc5 X22 0000007940e3c400 X23 000000000000026b DEBUG 00000000000001d0F:X24 X25 000000000000002f X26 000000793d9653f0 X27 0000007948303368 DEBUG 0000007945dd5588F:X28 00000000000001d0 X29 0000007945dd37d0F DEBUG:SP 0000007945dd3790 LR 00000079e732e00c PC 00000079e732e034

当然,从任何远程控制的堆栈bufferoverflow源最大的恐惧是代码执行。这甚至可能在加密Library中倍增,我们有额外的担忧攻击者能够泄漏深受保护的对话。

在这种情况下,矩阵的联邦架构可能有点有些缓渠,因为用户更有可能知道和信任Homeserver所有者,但我们不想依靠这种信任。

幸运的是,自己的错误不足以成功执行代码onnnative二进制文件。默认情况下,为包含Canaries的所有支持的目标(也称为堆栈保护器或堆栈cookie)编译libolm,这是攻击者未知的魔术值,放在当前功能之前' s框架堆栈。从函数返回时检查此值 - 如果改变其值,则该过程中止自身以防止进一步损坏。这是从中止消息中的'堆栈腐败检测到(-fstack-protector)'上面的消息。除了Canaries外,还存在其他系统级别的保护,以使利用诸如此更难的错误,例如ASLR。

因此,为了实现远程代码执行,攻击者需要FindAditional漏洞,这将允许他从系统中删除堆栈CanaryAnd的键存储位置的地址。

通过WASM,由于其非常不同的和执行模型,分析更加复杂。在WASM中,由于它缺少对堆具公民队员的支持,非托管堆栈通常会更加多样化。这意味着StackBuffer溢出不能仅覆盖overflow发生的函数的帧,还可以覆盖所有父帧。

另一方面,由于键入的呼叫和更强大的控制流程积分技术,它'攻击者更难使代码做某事是(恶意地)有用。值得注意的是,返回地址直播在UnmanageMemory之外,并无法访问攻击者。因此,影响代码执行的主要方法是通过操纵Call_Indirect指令呼叫的方式。

因此,对WASM二进制文件的这种错误的影响是向读者的Anexercise留下的。如果你' ref感兴趣,2020 usenix纸张evitiledre是新的:webassemblyis的二进制安全性很大的起点。

一旦确定了问题,斑块相实地存在,问题迅速解决。第一种包括修复的血浆是3.2.3,在2021-05-25释放。

我们达到了决心受影响的所有矩阵客户。第一次修复此问题的TheElement客户端版本如下:

对于移动客户端,在发布此帖子时,这些版本已在其重症应用程序存储中使用。如果你已经,请升级。

即使模糊设置现在处于更好的形状(或者宁愿是,因为我仍然有一些PRS合并上游),那么它仍然可以替代进一步改善它。

现在,毫无疑问的代码库部分不是模糊不清的。这一范围的原因是显而易见的,就像任何现有的线束都不调用的代码中的某些部分,以更加微妙的是,加密操作形成一个朴素的模糊操作的近乎难以克服的自然网络。最后,一些人现有的绑定接受其他参数作为命令行参数,这意味着我们必须重新运行相同的线束,以达到不同的帖子分析值,以便达到代码的完全覆盖范围。这是次优。

写出启动语料库生成器。这些应该为每个线束产生可信的,validInput。例如,对于解密线束,Weshould生成各种加密消息:空,短,长,文本,二进制等。

修改线束,以便从弹出的输入确定其额外的参数。这将允许模糊物改变这些本身,这将在循环中重新生动人类的重要性,并使忘记分析变得更加困难。

FUZZE一段时间,直到覆盖率停止增加。 Corpora Goodce拯救,以便未来的模糊尝试可以从早期的点恢复,以便不浪费这项工作。

使用AFL-COV来调查代码的哪些部分并不覆盖orat所有。这应该告诉我们需要进一步的更改。

写智能,自定义突变者。这些将允许Fuzzer到Takea有效输入,并轻松地产生另一个有效输入,而不是仅具有高概率。

它非常令人兴奋的是我们'重新能够对Matrixthese的全日制安全研究(感谢元素'资金),并前进我们' ll发布任何交感器的可见性和全部矩阵中的教育。我们也想提醒大家,我们运行一份官方安全性Disclose策略FormatRix.org和我们' D欢迎其他研究人员加入我们的名利堂!(希望我们将来会获得更多的奖励计划。 )

在模糊的上下文中,碰撞是不同的执行路径,不同的执行路径出现在模糊的情况下,因为它是相同的一个到来的totechnical限制。基本上,通过跟踪所谓的&#34来追踪覆盖范围。边缘" (或"元组")。边缘是(a,b)的成对,其中a和b代表基本块。每个确de都是表示不同的执行"跳跃"但有时,程序中的基本块数量的数量增长,两个不同的执行路径扫描最终被编码为相同的边缘。在AFL ++中的LTO模式执行了一些Magico,这保证不会发生。 ↩

通过剩余的字节,我的意思是非一组的字节,这是4的4.这些只能在Base64有效载荷和它们的结束时出现。在填充Base64中使用填充后缀的RETHE。 ↩

经典模糊着名具有硬度计的魔法值和校验和,并且密码学完全是其中的。这一事实进一步复杂于Double Ratchet算法isvery状态并且取决于在LockStep中的两种棘轮演变的两个棘轮。即使是解密线束提供有效加密消息的语料库,该突变也不会管理,该突变不会管理将产生将无法解密的这些消息的损坏版本,但它将〜永远不会管理不同albencrypted消息。 ↩