参加Pwn2Own 2021 Austin竞赛:Icarus登顶之路
引言
2021年,我终于抽出时间研究使用了多年的消费级路由器。最初这只是个周末项目,想看看与平常工作不同的东西。此外,这也是个玩新工具、学新东西的好机会。
我下载了Ghidra,抓取固件更新,开始逆向运行在NETGEAR DGND3700v2设备上的各种MIPS二进制文件。很快我就被发现的东西吓到了,周末写了篇《Longue vue 🔭》,很有意思(也许下次再讲这个故事?)。安全性简直是个笑话,第二天我就把路由器扔了,订了个新的。简直不敢相信这东西在我的网络里待了好几年。呃😞。
总之,我最终收到了全新的TP-Link路由器,也开始研究它。很高兴看到代码质量好多了,下班后我慢慢啃代码。最终,在2021年5月,宣布了Pwn2Own 2021 Austin比赛,目标是路由器、打印机和手机。太兴奋了。参加这种比赛一直在我的待办清单上,但我一直说服自己没能力参加😅。
但这次不同。我决定投入时间专注一个目标,看看会发生什么。没什么损失。而且,几个朋友也有兴趣和动力来破解代码,所以我们就这样做了。在这篇博客中,我将带你了解准备和参加mofoffensive团队比赛的旅程。
目录
- 引言
- 目标选择
- 获取目标shell
- 枚举攻击面
- 追逐幽灵
- NetUSB的诡计
- 在QEMU中启动NetUSB
- Zenith登场
- 参加比赛
- 总结
目标选择
此时,@pwning_me、@chillbro4201和我在Discord上积极聊天。我们的最终目标是参加比赛,看了比赛规则后,阻力最小的路径似乎是瞄准路由器。我们对路由器更有经验,硬件容易且便宜获取,所以感觉是正确选择。
至少,我们当时认为这是阻力最小的路径。参加比赛后,也许打印机至少一样软,但奖金更高。但不管怎样,我们不是为了钱,所以专注于路由器类别并坚持了下来。
在5个候选设备中,我们决定专注于消费设备,因为我们假设它们更软。此外,我有点TP-Link的经验,组里有人熟悉NETGEAR路由器。所以我们就选了这两个目标,然后上了亚马逊订硬件开始。很兴奋。
TP-Link AC1750智能Wi-Fi路由器到了我家,我开始动手。但从哪里开始?好吧,这种情况下最好的办法是获取设备的root shell。怎么获取不重要,只要有一个就能找出有趣的攻击面。
如引言所述,在之前的几个月玩自己的TP-Link路由器时,我发现了post auth漏洞,可以执行shell命令。虽然从攻击者角度看没用,但对获取设备shell和引导研究有用。不幸的是,目标不脆弱,所以我需要另找办法。
哦,还有。有趣的事实:我最初订错了路由器。原来TP-Link卖两条产品线,看起来很像:A7和C7。我买了前者,但比赛需要后者,哎呀🤦🏽♂️。特别感谢Cody告诉我😅!
获取目标shell
逆向web服务器几天后,找低挂果实没找到,我意识到需要另找办法获取设备shell。
谷歌了一下,我找到了同胞写的文章:Pwn2own Tokyo 2020: Defeating the TP-Link AC1750 by @0xMitsurugi and @swapg。文章描述了他们在2020年Pwn2Own Tokyo如何攻破路由器,但也描述了如何获取设备shell,太好了🙏🏽。问题是我真的没有任何硬件经验。一点都没有。
但幸运的是,我有很酷的朋友。我ping了@bsmtiam,他推荐订FT232 USB线,我就订了。硬件很快到了,我去他家。他拆开路由器,放在工作台上开始工作。
试了几次后,他成功焊了UART。我们把FT232 USB线接到路由器板子上,插到我的笔记本:
用Python和minicom库,我们终于能进入交互式root shell💥:
太棒了。为庆祝这小胜利,我们去当地酒吧吃了汉堡和啤酒🍻。好日子。
枚举攻击面
是时候找出该专注哪些区域了。我读了很多东西,因为这路由器多年来在Pwn2Own被多次瞄准。我觉得尝试新领域可能好,降低重复进入比赛的机会,也最大化找到东西的机会。在想重复之前,我需要个bug。
我开始做一些非常基础的攻击面枚举:运行进程、iptable规则、监听套接字、crontable等。没什么花哨的。
|
|
第一眼,以下进程看起来有趣:
- uhttpd HTTP服务器,
- 第三方dnsmasq服务,可能未修补上游bug(不太可能?),
- tdpServer,2021年被爆过,是sync-server漏洞的载体。
追逐幽灵
因为我熟悉家里路由器的uhttpd HTTP服务器工作原理,我觉得至少花几天看看目标路由器上运行的。HTTP服务器能运行和调用Lua扩展,我觉得bug可能在那里:命令注入等。但有趣的是,所有现有公共Lua工具都分析不了那些扩展,既沮丧又困惑。长话短说,路由器上用的Lua运行时似乎被修改了,操作码表看起来打乱了。结果,编译的扩展会破坏所有公共工具,因为操作码不匹配。傻。我最终设法反编译了一些扩展,找到一个bug,但从攻击者角度看可能没用。是时候继续了,因为我觉得那里潜力不够大。
我烧时间的另一件事是浏览TP-Link为这路由器发布的GPL代码存档:ArcherC7V5.tar.bz2。因为许可,TP-Link必须(?)“维护”包含设备上使用的GPL代码的存档。我觉得这可能是弄清dnsmasq是否正确修补了近年发布漏洞的好方法。看起来有些漏洞没修补,但反汇编显示不同😔。死胡同。
NetUSB的诡计
上面netstat输出中有两行奇怪,引起了我的注意:
|
|
为什么这些套接字没有关联进程名🤔?好吧,原来谷歌和查看后,这些套接字是由……等等……内核模块打开的。听起来很疯狂,我也是第一次见。不过有点兴奋。
这NetUSB.ko内核模块其实是KCodes公司写的软件,做USB over IP。另一件疯狂的事是我记得在NETGEAR路由器上见过同样模块。怪。谷歌后,也不奇怪看到过去发现和利用了多个漏洞,TP-Link确实不是唯一装这模块的路由器。
虽然我不觉得可能在那里找到有趣的东西,但还是投入时间查看和感受。静态逆向几天后,看起来确实比最初想的复杂得多,所以我决定再坚持久点。
啃了一段时间后,事情开始合理:我逆向了一些重要结构,能跟踪不受信任输入更深代码。枚举了很多解析和使用攻击者输入的地方后,我找到了这一个点,我能在算术中溢出整数,喂给分配函数:
|
|
我最初以为这会导致疯狂溢出类bug,因为代码会尝试读大量字节到这缓冲区,但我还是继续做了PoC。那时我意识到我错了。仔细看,SoftwareBus_fillBuf函数实际定义如下:
|
|
KTCP_get基本上是ks_recv的包装,意思攻击者能强制函数返回而不读整个BufferLen字节数。这意味着我能强制分配小缓冲区,用任意多数据溢出它。如果你有兴趣了解如何首先触发这代码路径,请查看zenith-poc.py中的握手工作方式,或读@maxpl0it的CVE-2021-45608 | NetUSB RCE Flaw in Millions of End User Routers。以下代码能触发上述漏洞:
|
|
另一个有趣细节是分配函数是mallocPageBuf,我不知道。看实现后,它最终调用_get_free_pages,是Linux内核的一部分。_get_free_pages分配2**n页数,用所谓Binary Buddy Allocator实现。我不熟悉这种分配器,最终有点着迷。想了解更多,可读Chapter 6: Physical Page Allocation。
哇好吧,所以也许我能用这bug做点有用的事。还是远射,但基于我的理解,bug会给我完全控制内容,我能用任意多数据溢出页。唯一不能完全控制的是传给分配的大小。唯一限制是,由于整数溢出,我只能用以下区间大小触发mallocPageBuf调用:[0, 8]。mallocPageBuf对齐传递大小到下一个2的幂,计算顺序(n在2**n中)调用_get_free_pages。
另一件对我有利的事是内核没有KASLR,我也注意到内核尽最大努力保持运行,即使遇到访问违规或什么。它不会在第一次打嗝时崩溃重启,而是尝试运行直到不能再运行。甜。
我最终也发现驱动通过网络泄漏内核地址。上面片段中,kc_printf用诊断/调试字符串调用。看代码,我意识到字符串实际通过网络发送到不同端口。我觉得这对同步和泄漏驱动做的分配也有帮助。
|
|
挺搞笑对吧?
在QEMU中启动NetUSB
虽然我有设备root shell,但无法调试内核或驱动代码。这使思考利用这漏洞非常难。此外,我是完全Linux菜鸟,缺乏内省不行。我有什么选择?
好吧,如我之前提到,TP-Link维护GPL存档,有他们用的Linux版本信息,应用的补丁,以及假设构建内核所需的一切。我觉得他们非常nice,应该给我好起点,能在QEMU下调试这驱动。我知道这不会给最精确模拟环境,但同时,会大大改善当前情况。我能挂钩GDB,检查分配器状态,希望有进展。
结果这比我想的难得多。我首先尝试通过GPL存档构建内核。表面上,一切都在,简单make应该就行。但不行。花了我几周实际编译(正确依赖,这里那里修补位,……),但我最终做到了。我必须试一堆工具链版本,修复随机文件,导致Linux发行版错误,等。老实说,我大多忘了所有细节,但记得痛苦。如果你有兴趣,我压缩了这VM的文件系统,可在这里找到:wheezy-openwrt-ath.tar.xz。
我以为痛苦结束,但事实不是。完全不是。构建的内核不会在QEMU中启动,会在启动时挂起。我尝试理解发生了什么,但看起来与模拟硬件相关,我老实说力不从心。我决定从不同角度看问题。相反,我从aurel32网站下载了启动良好的Linux MIPS QEMU镜像,决定尝试合并两个内核配置,直到最终得到可启动镜像,配置尽可能接近设备上运行的内核。相同内核版本,分配器,相同驱动,等。至少足够相似,能加载NetUSB.ko驱动。
再次,因为我是完全Linux菜鸟,我失败真正看到那里的复杂性。所以我开始这旅程,我必须编译轻松100+内核,直到能在QEMU中加载和执行NetUSB.ko驱动。我失败看到的主要挑战是,在Linux领域,配置标志能改变内部结构大小。这意味着如果你尝试在内核B上运行驱动A,驱动A可能误认结构大小C,当实际大小D。那正是发生的。在这QEMU镜像中启动驱动导致一堆随机崩溃,我最初无法真正解释。所以我跟了多个兔子洞,直到意识到我的内核配置只是与驱动期望不一致。例如,下面定义的net_device显示其定义随内核配置选项开或关变化:CONFIG_WIRELESS_EXT、CONFIG_VLAN_8021Q、CONFIG_NET_DSA、CONFIG_SYSFS、CONFIG_RPS、CONFIG_RFS_ACCEL,等。但不止。这结构用的任何类型能做同样事,意味着看结构的主要定义不够。
|
|