功击
因为市面上没有现成的工具才能执行这些功击,所以我们自己制做了工具,并把它命名为EvilAbigail。Evilmaid功击可以针对任何操作系统。这次的研究我们针对的是使用LUKS全盘加密的Linux系统。
通常来说,当Linux系统使用了全盘加密后,会由一小块分区还是没有加密,这个区域就是拿来揭秘和引导加密c盘的。这个分区会挂载在/boot,但是包含内核和初始RAMc盘(initrd)。其实功击内核或则bootloader也是可行的,并且我们还是针对initrd进行了功击。
initrd是指一个临时文件系统,它在启动阶段被Linux内核调用。initrd主要用于当root文件系统被挂载之前,进行打算工作。initrd中包含了是揭密和挂载root文件系统所须要的目录和可执行程序的最小集合。一旦initrd任务完成,它还会执行pivot_root,因而将initrd根文件系统卸载掉,并挂载真正的根文件系统。
通常来说linux软件工程师培训,initrd是一个通过gzip压缩的cpio镜像。我们测试的基于Debian的操作系统是这样,但基于RedHat的操作系统(Fedora,RHEL,CentOS)如今使用的是dracut,包含一个未压缩的cpio镜像。基于Debian的initrds会用ashshell脚本执行启动,而dracut则会用systemd和它所关联的配置方式。
为了执行我们的功击,我们选择使用一个基于LD_PRELOAD的bootkit,并且似乎也可以注入恶意的内核或可执行文件中。我们使用LD_PRELOAD的主要目标是对刚才揭秘完成的root文件系统中的第一个可执行文件注入一个共享对象。第一个可执行文件一般是/sbin/init,PID通常会是1。进行功击最简单的方式就是更改init脚本,导入这个环境变量,这样执行pivot_root的时侯环境变量就设置好了。由于当文件系统修改的时侯还得在合适的时侯(揭秘以后)把共享对象复制到新系统中。把以下这两行装入initrd的init脚本中,插在切换文件系统之前:
cp/hack.so/${rootmnt}/hack.so
exportLD_PRELOAD=/hack.so
之所以这样可行是由于真正的root文件系统是在临时root文件系统下揭秘挂载的,这先于pivot,但是rootmnt变量是用挂载点位置填充的。并且,在这之前须要把目标文件系统重新挂载成读写,由于默认是只读的。在我们的反例中我们对init脚本进行了更改,更改了脚本剖析内核命令行的地方,因而无论提供的参数是哪些,root文件系统都是读写方法挂载。另一种方式是在注入的命令中添加mount-oremount,rw/${rootmnt}。
不过基于dracut的initrds中不存在注入点,由于init的可执行文件是个二补码文件而非shell脚本。这就给我们带来了三个问题,只有克服了这三个问题我们才才能注入到pid1进程中。
三个问题
第一个问题是有关复制我们的二补码文件到揭秘的root文件系统中。这个问题是三个问题中最好解决的一个。我们可以加两个ExecPre指令到负责pivote文件系统的systemd服务文件中。这基本就相当于后面提及的插入脚本的方式。第一个命令会以读写方法重新挂载root文件系统,第二个执行复制操作。
第二个问题有关LD_PRELOAD。由于我们不是在更改shell脚本,我们不能把环境变量传递给这个进程(由于它是由内核调用的),因而加载我们的共享对象就有点棘手了。最简单的办法就是,先把init二补码文件联通到另一个位置,之后在它原先的位置插入我们自己的shell脚本,最后再执行原先的二补码文件。我们只须要两行代码,第一行导入LD_PRELOAD,第二行执行原先的systemd二补码文件。请注意,这样注入的是initrd中的pid1进程,而不是最终root文件系统的pid1。
第三个问题就是,在调用switch-root命令之前,systemd会用clearenv()函数清理所有环境变量。由于这个函数是标准库的一部份,我们就可以重画这个函数,让被注入的进程会调用我们的函数而不是原先的函数。我们不关心真正消除环境变量,我们写的clearenv()函数会消除所有环境变量,之后把我们的LD_PRELOAD变量注入到环境中。因为clearenv()只会被调用一次,我们的更改不会造成任何副作用。
解决了以上这三个问题然后,我们的共享对新都会被复制到加密的root文件系统中,我们的LD_PRELOAD会被注入到目标文件系统的pid1进程中。接出来我们就可以获取用于揭秘的用户密码。
对于Debian的initrds,可执行文件会要求用户输入密码,之后揭秘、挂载root文件系统。我们可以把我们的脚本注入到pipeline进程中因而获取密码。
至于systemd,它会通过Unixdomainsockets使用一种更复杂的进程间通讯。我们选择功击文件系统的挂载,而非这个进程。这又是一个库函数,它在动态加载库中,从systemd里调用,所以我们可以hook这个函数。揭秘硬碟的函数称作crypt_activate_by_passphrase。这个函数会把密码作为char链表。通过hook这个函数,我们可以获取到密码。我们要包裹这个原先的函数,所以我们用dlsym打开真正的函数,但是调用它。不过在此之前我们会保存密码便于之后拿回。
为了“保存”密码,我们简单地把密码加到我们以后要复制到root文件系统的共享对象中。之所以选择这些技巧是由于我们早已晓得这个文件存在,但是会被复制过去。采用这些技巧都会降低我们接触到的c盘文件的数目。为了获取密码,我们以后会读取我们自己文件末尾(通过LD_PRELOAD变量定位),之后把它设置为我们的回调shell中PASSWORD环境变量的值,因而(以meterpreter为例)通过‘getenvPASSWORD’命令可以获取用于揭秘c盘的密码。因为我们所有的目标主机都默认安装了python,所以我们就使用了pythonmeterpreter回调shell。
解决方案
解决这些问题有好多方式。并且虽然使用了这种解决方式,假如功击者才能化学接触计算机,而且有足够的时间重刷BIOS/UEFI,那也是防不住的。
第一种方式是把bootloader、内核和initrd置于内置的U盘上,之后从U盘上启动因而取代/boot分区。但对用户来说这个方式很糟糕,由于她们离开电脑的时侯要记得拔除U盘,假如没有卸载的话还要安全卸载/boot分区。更新的时侯也很麻烦,要插上U盘才会更新initrd/内核。
另一种方案则是彻底关掉从内置媒体启动系统。这样就不存在手动化功击的可能性了,而且个别情况下,如对于包含shell的Debianinitrds,还是可以从initrd人工挂载和更改initrd的。这可以通过手动按键式设备完成,这样就绕开了难以通过内置媒体启动系统的限制。
另外,还可以开启BIOS启动密码红旗linux桌面版,这样没有密码的人就没法启动计算机了。
不过若果功击者有足够的时间把硬碟拆下,之后用她们自己的电脑启动硬碟的话,后两种方案也不管用了。
最后,最安全的方案就是将SecureBoot拓展到initrd。SecureBoot可以验证bootloader和内核,假如才能验证经过签名的initrd的话,要不留痕迹地在/boot分区更改任何东西就会很困难。并且假如功击者可以通过刷BIOS/UEFI关掉secureboot的话,这些方案也没用了。
避免这类功击最好的方式就是不要让你的设备落入功击者们的手中加密linux系统加密linux系统,我们的PoC功击可以在2分钟内攻入所有的目标主机,但在现实世界中,功击者也可以作出功击特定目标的payload,这样的payload只用几秒就可以功击设备。