ELF(ExecutableandLinkableFormat,可执行和可链接格式)作为嵌入式系统(如Linux、BSD系统和Solaris系统)中基本的文件格式,被广泛的使用着。按照ELF文件标准,使用ELF格式的文件分为可重定位文件,可执行文件、目标共享文件、核心转储文件。ELF文件用于定义不同文件类型的对象文件内容和格式,可移植性很强。ELF文件有许多技巧,比如我们在*nix Meterpreter实现中使用的那些技巧,但这些技巧需要使用我们的特殊工具链或配置有-static-pie标志的GCC 8编译器构建每个可执行文件。如果碰到特殊情况该怎么办?比如内核不需要磁盘上的文件来加载和运行代码。
手工执行技巧
对于可执行格式来说,最常用的技巧可能是反射加载。反射加载是一种重要的后期开发(post-exploitation)技术,用于避免在锁定环境中检测和执行更复杂的工具,加载过程通常需要三个步骤:
1.获取代码执行(例如,漏洞利用或网络钓鱼);
2.从某个位置获取自己的代码;
3.强制操作系统在正擦进程中加载异常运行代码;
反射加载就是在最后一步中实现的,由于环境被锁定的越来越严实,通过正常流程启动的组件就成了最明显的目标。传统的杀毒软件程序都会扫描磁盘上的内容,且代码签名会在新进程启动时检查完整性,另外行为监控程序会不断检查,以确保这些进程中没有任何异常。这意味着如果攻击者无法运行任何程序,那他们就无法做任何事情,按着这个逻辑理解系统也始终是安全的。
但实践证明,情况并不是如此。特别是,使用的Windows已经对该安全主题进行了大量研究,原因有二:一是PE格式已被广泛使用,PE(Portable Executable)格式,是微软Win32环境可移植可执行文件(如exe、dll、vxd、sys和vdm等)的标准文件格式;二是因为因为它在核心操作系统中内置了有用的反射构建块。许多攻击者利用PE文件格式不仅可以加载恶意代码,而且还可以将这些代码注入其他正在运行的进程。
尽管缺乏有趣的反射注入API,如Windows中的CreateRemoteThread,CreateRemoteThread是一个Windows API函数,它能够创建一个在其它进程地址空间中运行的线程(也称:创建远程线程)。但在过去几年中,我们看到了一些有趣的研究,这些研究通过非传统的方式,让Linux特性的安全特性围绕开发人员来展开。其中,研究人员用到了linux-inject等工具,可以从辅助二进制或脚本语言使用一些新的系统调用,执行最新的技术。最近,我遇到了linux-inject,它是一个注入程序,可以注入一个.so文件到一个运行中的应用程序进程中。类似于LD_PRELOAD环境变量所实现的功能,但它可以在程序运行过程中进行动态注入,而LD_PRELOAD是定义在程序运行前优先加载的动态链接库。事实上,linux-inject并不取代任何功能。
我们把这些与Linux安全特性相关的研究方法分为了五大类:
1.写入临时文件:这与典型的代码没有太大区别,但它不会在磁盘中留下操作痕迹;
2.注入ptrace:这需要一些复杂的控制,可以实现大范围的进程跳跃;
3.自修改可执行文件:dd(直接读写磁盘)是这方面的经典工具,它需要一些精确性和自定义shellcode;
4.脚本语言中的FFI集成:Python和Ruby都是合适的,当前的技术只加载shellcode;
5.创建非文件系统临时文件:这将使用2014年添加的系统调用,因此它的可用性非常广;严格的运行环境
临时文件
发现/ tmp和/ dev /shm安装noexec(该树中没有文件可以执行)越来越常见,特别是在移动和嵌入式系统上。说到嵌入式系统,那些嵌入式系统通常也有只读的持久文件存储,所以有人查看磁盘的可能性非常大。
自修改可执行文件和ptrace
访问ptrace和/ proc / <PID>中的大多数有趣的内省都是由kernel.yama.ptrace_scope sysctl变量控制的。对于不用于开发的盒子,至少应该将其设置为2,以限制非特权用户的访问。这在许多移动和嵌入式系统上是默认的,目前的桌面或服务器发行版默认值至少为1,这降低了它在跨进程中疯狂跳跃的便利性。此外,该方法是特定于Linux的,所以不适合在BSD中修改shell。
FFI集成
Fiddler是一款用于网页数据分析,抓取的工具(使用的是Ruby语言),而ctypes是Python的一个外部库,提供和C语言兼容的数据类型,可以很方便地调用DLL中输出的C接口函数,把它们两个结合起来使用,特别灵活,并且很多小的操作都可以像解释的C一样运行。但是,它们没有汇编程序,所以任何使用寄存器或引导到不同可执行文件的细节都需要使用你的shellcode来完成,这意味着你永远也不知道要在给定的系统上安装哪个版本。
非文件系统(Non-filesystem)临时文件
该方法也仅仅针对Linux,主要工作就是对新的系统进行调用,该方法可以绕过我目前已知的任何noexec标志(通过内核4.19.10进行测试)。第一个系统调用是memfd_create(2)。这里向大家介绍一个linux系统的底层调用函数memfd_create(2),它在内核3.17中引入,会创建一个匿名文件并返回一个文件描述符指向它,该文件表现和常规文件类同, 可以进行修改,截断,内存映射等等。但不同的是,它存在于RAM当中,这就是可以被攻击者所利用的,因为它具有默认权限的新临时文件系统,并在其中创建一个文件,该文件不会显示在除/ proc之外的任何已安装文件系统中。在内核3.19中添加的第二个系统调用是execveat(2),它可以获取一个文件描述符并将其传递给内核执行,它的缺点是使用了find /proc/*/fd -lname '/memfd:*',攻击者可以很容易地找到创建的文件,因为所有memfd_create(2)文件都表示为具有常量前缀的符号链接。特别是这个功能在普通软件中很少被使用,而我在Linux boxen上找到的唯一合法示例是在2016年被添加到PulseAudio中的。Boxen 是 GitHub 内部开发和使用的电脑环境部署套件,用于帮助新员工快速部署开发环境,只需运行一行命令,即可将 GitHub.com 的开发环境部署到新电脑中。
运行时链接器(runtime linker)的使用
不过要想在没有execve的情况下运行Linux可执行文件,还存在很大的瓶颈,因为要运行其他程序,所有用到的技术都必须使用标准的execve(2)或者在上述的最后一种情况下使用相关的execveat(2)调用。因此自定义的SELinux配置文件或系统调用审计的存在很容易突破这些限制。SELinux(Security-Enhanced Linux) 是美国国家安全局(NSA)对于强制访问控制的实现,是 Linux历史上最杰出的新安全子系统。NSA是在Linux社区的帮助下开发了一种访问控制体系,在这种访问控制体系的限制下,进程只能访问那些在他的任务中所需要文件。除此之外,还有另一种叫做userland exec或ul_exec的技术;但是,这类似于内核在execve期间初始化进程并将控制权交给运行时链接器(runtime linker):ld.so(8)。这是该领域中最早的技术之一,由grugq首创,但由于与上面的技术相比有些复杂,所以从未得到过多的研究。x86_64经过更新和重写,已经实现了自己的标准库,这使得扩展非常困难,并且无法使用现代的堆栈销毁(stack-smashing)保护进行编译。
如今的Linux世界已与15年前这种技术首次发布时的情况截然不同。在目前的Linux运行环境中,默认情况下,有40多位地址空间和与位置无关的可执行文件,将execve进程链接在一起更为简单。当然,我们不能对堆栈地址进行硬编码并让它在90%的时间内工作,不过程序也不再依赖于它的常量,所以我们几乎可以把它放在任何位置,并且减少对内存地址的消耗。与2004年相比,使用Linux环境的用户现在也更普遍,并且安全性更好,因此在某些情况下,完成这些工作是值得的。
运行过程
根据执行文件的方法和运行环境,模拟execve的情况仍然需要满足两个要求,不过这两个要求并非都要同时满足。首先,我们需要页面对齐的内存分配,以及在填充内存后标记可执行内存的能力。因为它是JIT编译器,内置库加载器dlopen(3)以及一些DRM实现所必需的,所以很难从系统中完全删除。但是,SELinux可以限制可执行内存的分配,而一些像Android这样的自包含平台使用这些限制可以快速的影响未经批准的浏览器或DRM库。接下来,我们要求能够任意跳入所述内存。例如,在C或shellcode中,它确实需要脚本语言中的完整FFI接口,并删除非XS Perl实现。
可以肯定的是,grugq详细介绍的过程正以微妙而有趣的方式发生着变化,不过即使这样,整个步骤也与以前相同。现代GNU/Linux用户环境的一个安全特性是它们不太关心特定的内存地址,这使我们能够更灵活的实现其他类型的安全特性。还有更多的运行迹象表明,内核在辅助向量中传递给运行时。虽然现在我们更希望找到原始的内核,但是大多数程序在大多数体系结构中使用简单的内核也可以很好地工作。
运行工具介绍
由于开源和安全方面的因素,原理许多很好用的工具都不适合本文所讲的Linux可执行文件的运行。除了execve仿真之外,其他加载方法对这两个ul_exec实现没有什么兴趣。
我们的Linux Meterpreter实现目前缺少对流行的execute命令的-m选项的支持,这个命令在Windows上以良性程序的名义在内存中完整运行一个进程。使用这个和上面的两个回退技术将为我们提供从内存运行上传文件所需的功能,并带来一些小技巧,例如映射额外的良性文件或在将控制权交给上传的可执行文件之前更改进程名称,完全复制-m。一个显而易见的好处是,这也将使构建和分发插件更容易,因为它们不再需要在构建时生成内存映像。
为了实现这一点,我正在创建一个与我们的Linux Meterpreter一起协同使用的共享库——Mettle。它不依赖于任何Meterpreter代码,但默认情况下,它将使用其工具链进行构建。它也是免费的,可以打包成你想要的任何后期开发机制。如果你有任何问题或建议,请务必查看此网页。
在此,我们可以通过系统调用跟踪程序strace(1)看到一个实际使用此库的示例工具。为了避免影响与正常跟踪相关的输出,我们只使用%process跟踪表达式来查看与进程生命周期相关的调用,如fork,execve和exit。在x86_64上,%process表达式还获取了arch_prctl系统调用,该调用只在x86_64上使用,且仅用于设置线程本地存储。 execve来自strace启动可执行文件,第一对arch_prctl调用来自库初始化,然后什么也不做,直到目标库启动它自己的一对arch_prctl调用并打印出我们的消息。
$ strace -e trace=%process ./noexec $(which cat) haxmas.txt execve("./noexec", ["./noexec", "/usr/bin/cat", "haxmas.txt"], 0x7ffdcbdf0bc0 /* 23 vars */) = 0 arch_prctl(0x3001 /* ARCH_??? */, 0x7fffa750dd20) = -1 EINVAL (Invalid argument) arch_prctl(ARCH_SET_FS, 0x7f17ca7db540) = 0 arch_prctl(0x3001 /* ARCH_??? */, 0x7fffa750dd20) = -1 EINVAL (Invalid argument) arch_prctl(ARCH_SET_FS, 0x7f17ca7f3540) = 0 Merry, HaXmas! exit_group(0) = ? +++ exited with 0 +++
创建完Mettle这个新库,我们希望可以为其提供一种长期的、隐秘的方式,可以在受攻击的Linux设备上可靠的加载程序。如果你有任何问题或建议,请务必查看此网页。