专栏首页FreeBufGrub2被曝登陆验证绕过0Day,影响众多Linux版本(CVE-2015-8370)

Grub2被曝登陆验证绕过0Day,影响众多Linux版本(CVE-2015-8370)

描述

近日,研究人员发现了一个Grub2的漏洞,版本1.98(2009年发布)到2.02(2015年发布)均受影响。本地用户能够通过这个漏洞绕过任何形式的认证(明文密码或者哈希密码),使得攻击者进而可以获得电脑的控制权限。而大部分的linux系统都将Grub2作为开机引导程序,包括一些嵌入式系统。因此将有不计其数的设备受到此漏洞的威胁。

如下图所示,我们成功的在Debian 7.5 QEMU下利用这个漏洞获得了Grub rescue shell。

快速判断你的系统是否有该漏洞?

想要快速判断你的系统是否有这个漏洞,只需在grub出现输入用户名的界面时,连续按28次Backspace(退格键),如果系统重启或者返回rescue shell ,那么你的grub就会受到该漏洞影响。

影响

成功利用此漏洞的攻击者可以得到Grub rescue shell,Grub rescue是一个权限非常高的shell,通过这个漏洞可以做到以下事情:

1.权限提升:攻击者在没有有效的用户名密码的情况下即可获得grub控制台的所有权限。

2.信息泄露:攻击者可以通过加载一个定制的内核和initramfs(比如从USB加载),从而获得一个更便利的环境,拷贝窃取整个硬盘的数据或者往系统里安装一个rootkit。

3.拒绝服务:攻击者能够摧毁任何数据包括grub自身,即使硬盘是加密的数据也可以被覆盖,从而造成DOS(系统无法使用)。

细节

这个漏洞从1.98(2009)版本就存在grub的代码中了,b391bdb2f2c5ccf29da66cecdbfb7566656a704d是这个漏洞的提交编号,问题就存在于grub_password_get()函数中。

其中有两个函数都存在整数下溢问题grub_username_get()和grub_password_get()。它们分别存在于grub-core/normal/auth.c和lib/crypto.c文件中。这两个函数除了对printf()的调用在其他地方都是一样的,就像下面所示的grub_username_get()那样。本文中的POC是通过利用grub_username_get()中的漏洞获取Grub rescue shell的。

以下是grub_username_get()函数中的漏洞细节代码:

grub_username_get()函数代码

这个问题是由于自减变量cur_len没有做范围校验导致的。

利用(POC)

这段代码中,对cur_len变量的使用存在两个问题off-by-twoOut of bounds overwrite(代码中后面两个注释)。前面那个错误注释点,在用于存储用户名的缓冲区中会有两个字节的超长,但是这里没有办法利用,被覆盖的内存是用于填充的。

后面那个错误注释点(// Out of bounds overwrite 这里)比较有意思,因为这句代码允许我们用0x00去覆盖用于存储用户名缓存的内存。这是因为grub_memset()函数会尝试将用户名缓冲区未使用的字节设置成0x00。为了达到这个目的,这段代码会计算出第一个未使用的字节地址和需要被填充为0x00的缓冲区的大小。这两个计算的结果会作为参数传递给grub_memset()函数:

grub_memset (buf + cur_len, 0, buf_size - cur_len);

举个例子,当在用户名中输入“root”时,cur_len的值为5,grub_memset()函数会将用户名缓冲区的第5到1024字节(用户名和密码的缓冲区长度为1024字节)清空(设置为0x00)。这样写代码其健壮性是很好的。例如,如果输入的用户名被存储在干净的1024-byte数组中,就可以直接将整个1024-byte内存与有效的用户名进行比较,而不是去比较两个字符串。这样能够防御一些short of side-channels攻击,比如timing attacks(比如第一次输入username为aaaaaaaa,然后接着又输入bbbb,这样编码就可以避免出现第二次结果为bbbb[0x00]aaa的情况)。

最简单快速的验证这个内存覆盖越界的方法就是不停的按backspace (退格键)让cur_len变量下溢,达到一个非常大的值,这个值马上会被用来计算待清空空间的起始地址。

memset destination address = buf + cur_len

通过这个点,由于用户名缓冲区地址的值超出了32位变量能够存储的范围,第二次缓冲区溢出被触发。因此,我们要通过精心构造第一次下溢与第二次缓冲区溢出来计算grub_memset()函数将要使用的目的地址:

cur_len--; // Integer Underflow grub_memset (buf + cur_len, 0, buf_size - cur_len); // Integer Overflow

下面的这个例子可以帮助你们去理解我们是如何利用这个漏洞的。假设用户名缓冲区的起始地址为0x7f674,然后攻击者按一次退格键(下溢值为0xFFFFFFFF),那么memset就是下面这样的:

grub_memset (0x7f673, 0, 1025);

第一个参数:(buf+cur_len) = (0x7f674+0xFFFFFFFF)=(0x7f674-1) = 0x7f673;第二个参数:用来覆盖内存的常量,这里是0;第三个参数是要覆盖的大小:(buf_size-cur_len)=(1024-(-1))=1025。结果就是,整个用户名的缓冲区空间(1024)外加前面的一个字节全都被0x00覆盖。

按下的退格键的次数,就是用户名缓冲区之前填充的0x00的数量。

现在,我们已经能够覆盖用户名缓冲区的任意数量的字节。接下来需要去找0x00覆盖到并且可以用来实现恶意代码执行的内存地址。 在栈帧中进行寻找,可以很快发现grub_memset()函数的返回地址能够被覆盖到。下面这张图可以清楚的展示出栈的内存布局:

Grub2: 重定向控制流

如图中所示,grub_memset()函数的返回地址与用户名缓冲区之间的距离为16字节。换句话说,如果我们按17次退格键,我们就能够覆盖到返回地址的最高字节。所以,函数返回地址0x07eb53e8会被替换掉,最终会跳转到0x00eb53e8。当grub_memset()执行结束时,控制流会重定向到0x00eb53e8,导致系统重启。同样的,按退格键18,19,20次,都会导致系统重启。

到这里,我们能够重定向控制流了。

我们跳过了对0x00eb53e8,0x000053e8、0x000000e8地址处的代码分析,因为跳到这几个地址中,只能导致系统重启,没有办法控制执行流。

虽然成功的构造攻击跳转到0x0看起来非常困难,但是我们下面将会展示我们最终是如何做到的。

跳转到0x0之后系统能否继续存活?

0x0地址是处理器的IVT(中断向量表)的入口。这里包含了大量的段偏移表的指针。

IVT中断最低地址处的代码

在启动的早期阶段,处理器和执行框架都还不具备所有的功能。下面是能否成功利用的一些关键因素,这在每个系统中可能有所不同:

1.处理器处于“保护模式”,Grub2在最开始的阶段会开启这个模式 2.未启用虚拟内存 3.没有内存保护,内存是可读/可写/可执行的,并且没有NX/DEP保护 4.处理器执行32位指令集,即使在64位架构下 5.处理器会自动处理动态自修改的代码:如果写入影响到一条预取指令,指令队列将会无效。 6.没用栈保护机制(SSP) 7.没有开启地址空间布局随机化(ASLR)

因此,跳转到0x0地址并不会造成系统自身崩溃,但是我们需要控制执行流让代码走到包含 Grub2 Rescue Shell功能的目标函数grub_rescue_run()中。

要跳转到0x0,需要控制哪些东西?

当用户按下[Enter]或者[Esc]时,grub_username_get()函数的主循环将会结束。此时,%ebx寄存器中会保存最后一个按键的值(Enter的ascii码为0xd,Esc的ascii码为0x8)。%esi寄存器会保存cur_len变量的值。

如上图所示,指令指针(EIP)指向0x0地址,%esi寄存器的值为-28(利用程序连按了28次退格键),然后按下[Enter](%ebx=0xd)。

IVT逆向

如果处理器的状态像前面所述的那样(会自动处理动态自修改的代码),IVT中的代码就有像memcpy()一样的功能,会从%esi寄存器指向的地址中拷贝代码到0x0(IVT自身)。因此,IVT是自修改的代码,并且我们能够选择我们想拷贝的代码区块。

下面的步骤展示了代码真正的执行顺序,此时%esi寄存器的值为-28(0xffffffe4):

在第三次循环中,往0x0007处插入了retw指令,此时%esp指向的地址为0xe00c(栈顶返回地址)。

因此,当retw指令执行后,执行流跳到0xe00c。这个地址属于grub_rescue_run()函数:

通过这步利用,GRUB2进到了grub rescue函数中,我们获得了一个高权限的shell。

幸运的是,内存虽然被轻微的修改,但是还是能够使用GRUB的所有功能。IVT的中断向量虽然被修改了,但由于处理器现在处于保护模式,IVT不会再被使用。

近一步深入

虽然我们进到了GRUB2 rescue函数中,但却并没有真正的通过认证。如果要进入“normal”模式(这个模式提供了grub菜单和完整的编辑功能),GRUB会要求你输入正确的用户名密码。我们可以直接输入GRUB2命令,甚至引入一个新的模块来添加一个新的GRUB功能,最终通过往系统里部署恶意软件启动完整的bash shell来获取一个更便利环境。要运行linux的bash,我们可以使用GRUB2的命令,比如linux, initrd或者insmod。

虽然使用GRUB2命令运行linux内核来部署恶意软件是完全可行的,但是我们发现了一个更简单的解决方案,往GRUB2的RAM中写入代码补丁来绕过认证,然后再回到“normal”模式。这个方法的思路是修改用户认证的校验条件,其相关代码在 grub-core/normal/auth.c 文件中的is_authenticated()函数中。

完成这个修改使用了GRUB2 rescue的命令write_word。这样,返回GRUB2的“normal”模式的所有条件都已经达成了。 句话说,我们不需要用户名密码就可以进入GRUB2的“编辑模式”了。

APT攻击如何使用这个0day?

物理接触是APT攻击的一个“高级”特性。APT攻击的一个主要目标就是窃取敏感信息。下面是一个非常简单的例子展示一个APT如何影响系统,持续性的获取用户数据的。以下是目标系统配置的概述:

1.BIOS/UEFI 使用密码进行了保护; 2.GRUB2编辑模式使用了密码进行保护; 3.扩展启动方式被禁用:CDROM,DVD,USB,Netboot,PXE… 4.用户数据被加密。

正如前面提到的,我们的目标是窃取用户数据。由于数据被加密了,我们的策略是先感染系统然后等待用户解密数据(通过登录系统),然后我们直接获取明文信息。

准备环境部署恶意软件

通过我们刚刚对GRUB2漏洞利用的分析与展示,我们可以很容易的修改linux入口去加载一个linux内核来获取root权限的shell。这是一个很老但却仍然有效的欺骗方法,只需要添加init=/bin/bash到linux入口处,我们就能够获取root权限的linux shell,这个环境能够让我们更方便的部署恶意软件。

由于/bin/bash 是第一个启动的进程,syslog监控还没有运行,日志不会记录。因此,这一入侵将不会被常见的linux监控检测到。

部署恶意软件来获得持续性的控制

为了展示通过利用这个Grub2 0day漏洞能够做多少事情,我们开发了一个简单的POC。这个POC可以篡改火狐的链接库,能够创建一个新线程并启动一个反弹shell连接到控制服务器的53号端口上。当然这只是一个简单的例子,在实际场景中的恶意软件获取信息会隐秘很多。

将修改后的链接库上传到virustotal中检测,55款杀毒引擎没有一个能检测出这是一个恶意软件。火狐是一款web浏览器,向HTTP和DNS端口发送请求,所以,我们修改的链接库使用这些端口看起来并不是什么可疑行为。

为了感染系统我们将被修改的libplc4.so放到USB中然后替换掉源文件。我们必须将USB设备挂载到系统上并且赋予可写的权限,正如下图所示:

感染系统

当任意用户运行火狐时,一个反弹shell就会发起连接。此时,所有的用户数据都已经被解密了,这允许我们窃取用户的所有信息。下图展示了用户Bob(被攻击用户)正在使用火狐浏览网页,而Alice(攻击者)则完全获得了Bob的数据。

想要更持续性的控制系统,可以修改一个简单的内核放到未加密的/boot分区中,来提权部署一个更为持久的恶意软件,这样我们就可以为所欲为了。

修复方案

这个漏洞很容易修复,只要防止cur_len溢出就行。目前主流厂商都已经意识到了这个漏洞,因此我们也顺便写了个“紧急补丁”放到GRUB2的git中:

补丁地址:http://hmarco.org/bugs/patches/0001-Fix-CVE-2015-8370-Grub2-user-pass-vulnerability.patch

GRUB 2.02修复命令:

$ git clone git://git.savannah.gnu.org/grub.git grub.git $ cd grub.git $ wget http://hmarco.org/bugs/patches/0001-Fix-CVE-2015-8370-Grub2-user-pass-vulnerability.patch $ git apply 0001-Fix-CVE-2015-8370-Grub2-user-pass-vulnerability.patch

总结

经过我们的深入分析,最终成功利用这个漏洞。但正如文中所说,成功的利用需要很多条件:BIOS版本、GRUB版本、RAM容量、内存布局能否修改,且每个系统都需要深入的分析去构造特殊的利用。

最后提一下这里我们没有利用的地方,大家可以去发散一下:grub_memset()函数可以被滥用以便设置内存的0x00区块而不跳转到0x0,另外用户名和密码缓冲区也可以被用来存储payloads。

另外,更复杂的攻击方法(需要更大的下溢或者payload),可以被用在键盘仿真设备上,例如Teensy device。我们可以记录键盘按下的攻击序列,然后在目标系统上重现。

比较幸运的是,本文所展示的GRUB2漏洞利用方法不是通用的,但却仍有可能存在其他更多变种的利用在你的环境中生效。

* 参考来源:hmarco.org,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)

本文分享自微信公众号 - FreeBuf(freebuf),作者:SecDarker

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2015-12-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • NTLMRecon:一款针对Web应用NTLM认证信息的枚举工具

    NTLMRecon是一款针对Web应用NTLM认证信息的枚举工具,如果目标Web节点启用了NTLM认证功能,那么广大研究人员就可以使用NTLMRecon来枚举目...

    FB客服
  • 挖洞经验 | HackerOne平台ImageMagick漏洞导致服务器内存信息泄露

    大家好,今天我要分享的是关于HackerOne平台GIF图像处理的ImageMagick漏洞(CVE-2017–15277),漏洞很简单,最终也获得了Hacke...

    FB客服
  • JIS-CTF靶机+Kioptrix靶机渗透

    最近一直在down各种CTF靶机玩,本次分享的2个靶机因较基础,故合并成一篇文章发表,本文章仅为初学者练手学习使用,大神们勿喷,感谢各位大佬!

    FB客服
  • Ubuntu | 双系统的windows启动项找不到修复

    双系统windows10 、ubuntu 16.04 ,重装ubuntu后,开机找不到windows的启动项,在网上找到了解决方案:

    努力在北京混出人样
  • 让Redis突破内存大小的限制

    Redis虽然可以实现持久化存储,也是基于数据内存模型的基础之上,单机内存大小限制着Redis存储的数据量,有没有一种替代方案呢?本文介绍一款笔者使用的采用Ne...

    歪脖贰点零
  • 其实你不一定懂csv文件格式

    最近业务中涉及到了csv文件的读写,本以为是非常简单的一件事情。结果却踩了几个坑。想象一下下面这段写csv文件的代码有什么问题?

    horstxu
  • 为什么大家都怕学C++?

    现在很多人都觉得C++学起来相当的费劲,特别是对刚入门的,看到最后直接就没法看下去了,抽象的逻辑太多了,越看越迷糊,最后也就选择了放弃。笔者看到很多大学开设的第...

    程序员互动联盟
  • 11.2 VR扫描:“HADO”开发商meleap已完成逾700万美元B轮融资;IMAX又关一家VR中心

    meleap是一家总部位于东京的公司,其不仅开发了AR Techno-Sports(科技体育运动)技术,还向23个国家的52个竞技场出售了其团队竞技体育产品“H...

    VRPinea
  • 关于计算机工作方向的几点想法

       都快毕业一年了,呆在现在的公司很闲,没做过多少实际的项目, 最近在做系统软件集成方面的东西,涉及到编程的东西很少,有做别的想法。

    ccf19881030
  • 地图开发知识之-投影坐标

    由于地球是一个赤道略宽两极略扁的不规则的梨形球体,表面是一个不可展平的曲面,而地图通常是二维平面,因此在地图制图时首先要考虑把曲面转化成平面。然而,从几何意义上...

    欧阳大哥2013

扫码关注云+社区

领取腾讯云代金券