前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Grub2被曝登陆验证绕过0Day,影响众多Linux版本(CVE-2015-8370)

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

作者头像
FB客服
发布2018-02-07 12:02:48
1.6K0
发布2018-02-07 12:02:48
举报
文章被收录于专栏:FreeBuf

描述

近日,研究人员发现了一个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)

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2015-12-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FreeBuf 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云服务器
云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档