原文地址 :https://medium.com/@maxi./finding-and-exploiting-cve-2018-7445-f3103f163cc1
- CVE-2018-7445是SMB服务二进制文件中的堆栈缓冲区溢出,存在于6.41.3 / 6.42rc27之前的所有RouterOS版本和体系结构中。
- 它被发现使用哑声模糊辅助使用Cisco Talos的Mutiny Fuzzer工具并在大约一年前报告/修复。
- 易受攻击的二进制文件没有使用stack canaries。
- 利用ROP将堆标记为可执行文件并跳转到堆中的固定位置。堆基不是随机的。
- 愚蠢的模糊测试仍然在2018年发现了有趣的目标中的错误(虽然我确信2019年肯定不会留下任何错误!)
- 该帖子描述了从目标选择到识别漏洞然后产生有效漏洞的完整过程。
介绍
在过去几年中,MikroTik RouterOS设备中发现和报告的公共漏洞数量激增。从影响CIA Vault 7中包含的内置Web服务器的远程缓冲区溢出泄漏到可能安全的Kirils Solovjovs和Tenable的Jacob Baines报告的大量其他漏洞导致完全远程攻击。
MikroTik最近被Zerodium维护的漏洞利用收购计划列入了符合条件的路由器品牌名单,其中包括以10万美元购买优先RCE 的一个月优惠。这可能反映出对MikroTik产品及其安全态势越来越感兴趣。
这篇博文旨在为正在进行的MikroTik RouterOS漏洞研究做出一点贡献。我将概述我们与我的同事Juan(感谢Juan!)在Core Security一起寻找和利用CVE-2018-7445的步骤,这是MikroTik的RouterOS SMB服务中的远程缓冲区溢出,可以从未经验证的攻击者。
漏洞很容易找到,利用很简单,因此我们的想法是提供详细的演练,希望(对于对内存损坏感兴趣的其他初学者)有用。我将尝试从“嘿!让我们来看看这个MikroTik的事情“实际上在网络服务中发现漏洞并为其编写漏洞。
最初的建议可以在这里找到。
强制性免责声明:我不再隶属于Core Security,因此本文的内容不反映其观点或以任何方式代表公司。
建立
在6.41.3 / 6.42rc27之前运行RouterOS的所有体系结构和设备中都存在此漏洞,因此第一步是使易受攻击的系统运行。
MikroTik通过维护所有以前发布的版本的存档使这变得非常容易。也可以下载RouterOS 的Cloud Hosted Router版本,该版本可作为具有完整RouterOS功能的虚拟机提供。这允许使用流行的虚拟机管理程序在x86-64架构中运行RouterOS,而无需实际的硬件设备。
让我们从这里获得6.40.5版本的Cloud Hosted Router,并在VirtualBox上创建虚拟机。
默认管理员凭据由admin作为用户名和空密码组成。
RouterOS管理控制台
RouterOS控制台是受限制的环境,不允许用户在预定义的配置选项集之外执行任何命令。
要复制漏洞发现,需要启用SMB服务。这可以通过ip smb set enabled = yes命令来实现。
启用SMB并检查设备的IP地址
请注意,情况默认下未启用该服务的事实使得活动利用的可能性要小得多。此外,您可能不应该将您的SMB服务暴露给公共网络,但是,内部网络中始终存在可能有权访问此服务的那些讨厌的用户。
受限制的控制台不适合执行正确的调试,因此在查找漏洞之前,拥有完整的shell访问权限非常有用。Kirils Solovjovs发表了关于越狱RouterOS的广泛研究,包括发布可用于越狱6.40.5的工具。重复这里的基本细节是没有意义的,所以前往Kirils的研究中心或更新近的Jacob Baines的新版本的帖子,其中6.40.5的入口点已被修补。
越狱的RouterOS 6.40.5就像克隆https://github.com/0ki/mikrotik-tools存储库并运行指向我们的VM 的交互式exploit-backup / exploit_full.sh漏洞一样简单。
越狱工具由0ki定位6.40.5
最后,从https://github.com/rapid7/embedded-tools/raw/master/binaries/gdbserver/gdbserver.i686 下载预编译的GDB版本,并使用FTP将其上传到系统。
通过Telnet连接到设备将允许我们附加到正在运行的进程并正确调试它们。
我们现在准备开始寻找网络服务中的漏洞。
目标选择
在RouterOS中运行了很多服务。快速回顾显示常见服务,如HTTP,FTP,SSH和Telnet,以及一些其他特定于RouterOS的服务,例如在端口2000上运行的bandwidh测试服务器。
雅各布贝恩斯指出,通过使用Winbox协议可以达到90多种实现网络服务的不同二进制文件(参见他的优秀博客文章中的真实攻击曲面)。
当我们开始使用RouterOS并且没有花时间对使用Winbox的二进制文件进行反向工程时,我们并没有意识到所有可达到的功能,所以我们只是继续查看在网络上明确监听的几个二进制文件。
RouterOS中的大多数(全部?)服务似乎都是从头开始实现的,因此有数千行自定义低级代码等待审计。
我们的目标是实现未经身份验证的远程代码执行,初看起来,FTP或Telnet等常见服务的二进制文件在没有提供凭据的情况下无法提供太多可访问的功能。这使我们转向其他默认情况下可能未启用的服务,但需要实现丰富的功能集。默认情况下未启用这些服务这一事实意味着他们可能会被其他攻击者忽略,这些攻击者希望最大限度地提高影响RouterOS默认安装的漏洞的ROI,因此更有价值。
通过遵循这一基本原理并检查可用服务,我们决定查看SMB实施。
发现漏洞
我们知道我们想在SMB服务中发现漏洞。我们有虚拟机设置,服务运行,我们对设备有完全的shell访问权限,我们可以调试任何进程。我们如何找到漏洞?
一种选择是反汇编二进制文件并寻找不安全的编码模式。我们将识别有趣的操作,如strcpy,memcpy等,并查看是否有正确的大小检查。然后我们将看看是否可以通过用户控制的输入访问这些代码路径。我们可以将它与动态分析相结合,并使用我们的能力通过GDB连接到正在运行的进程,以便在运行时,内存位置等处检查寄存器。但是,这可能非常耗时,如果您没有经验,很容易感到沮丧进行逆向工程,特别是如果它是一个大型二进制文件。
另一个选择是模糊网络服务。此方法包括将数据发送到远程服务并检查它是否导致意外行为或崩溃。此数据将包含格式错误的邮件,无效的大小,非常长的字符串等。
进行模糊测试过程有不同的方法。两种最流行的策略是基于生成和基于变异的模糊测试。基于生成的模糊测试需要协议知识来构建符合协议规定格式的测试用例,并且(很可能)会导致更全面的覆盖。更多的覆盖意味着更多的机会击中易受攻击的代码路径,因此更多的错误。另一方面,基于突变的模糊测试假定没有关于协议被模糊化的先验知识,并且以可能较差的代码覆盖和在需要计算校验和以确保数据完整性的协议中的额外困难为代价而花费更少的努力。
我们决定用一个愚蠢的模糊器试试运气,并选择几个月前Cisco Talos团队发布的Mutiny Fuzzer工具。Mutiny采用合法网络流量的样本,并通过突变模糊器重放它。特别是,Mutiny使用Radamsa来改变流量。
Radamsa变异的例子
执行这种模糊测试的好处是可以非常快速地开始运行,并且正如我们将看到的,如果我们有一系列强调各种功能的测试用例,可能会提供很好的结果。
综上所述,模糊网络服务的步骤是:
- 捕获合法流量
- 从生成的PCAP文件中创建Mutiny模糊器模板
- 运行Mutiny来改变流量并将其重播到服务
- 观察正在运行的服务会发生什么
Mutiny确实提供了一个监控脚本,可用于监控服务并识别奇怪的行为。这可以通过实现monitorTarget函数来完成,如https://github.com/Cisco-Talos/mutiny-fuzzer/blob/master/mutiny_classes/monitor.py 中所述。样本检查可能是ping远程服务或连接到远程服务以评估其可用性,监视进程,日志或其他可能表示奇怪行为的信号。
在这种情况下,SMB服务在崩溃后需要一段时间才能重新启动并记录堆栈跟踪消息,因此我们认为不值得编写任何监控操作的脚本。相反,我们刚刚使用Wireshark捕获整个模糊过程中的流量,并依赖于Mutiny的默认行为,当请求因连接拒绝错误而失败时退出,这意味着服务已关闭。这是相当简陋的,并留下了很大的改进空间,但这对我们的测试来说已经足够了。
在我们启动模糊测试过程之前启用完整日志记录非常重要。这可能对跟踪可能发生的任何崩溃非常有用,因为完整堆栈跟踪将包含在/rw/logs/backtrace.log中的日志中。这可以通过RouterOS的Web界面进行配置。
启用所有日志以写入磁盘
另一件被证明有用的事情是在交互式控制台中运行二进制文件以实时获得调试输出。这可以通过终止正在运行的进程并从完整终端重新启动它来实现。将打印错误和已处理请求的一般状态。
在处理请求时获取调试输出
现在我们对所涉及的步骤进行了高级概述,让我们回顾一下并实际模糊SMB服务。
首先,我们克隆https://github.com/Cisco-Talos/mutiny-fuzzer.git 并按照设置说明进行操作。
我们计划的下一步包括生成一些网络流量。为此,请打开Wireshark并尝试使用smbclient访问路由器上的资源。
Smbclient将向端口445 / TCP 发送协商协议请求,并收到我们不关心的响应。这可以在Wireshark捕获中观察到。
我们希望使用此请求作为产生(希望!)有意义的突变的起点。停止Wireshark捕获并保存请求数据包,方法是转到文件 - >导出指定数据包并选择请求数据包。输出格式应为PCAP。
一旦我们有包含模糊请求的PCAP,我们就会使用mutiny_prep.py交互式脚本准备Mutiny的 .fuzzer文件。
最好检查生成的文件,以确定转换过程中可能出现的任何奇怪现象。
在这里,我们可以将Mutiny配置为仅模糊消息的一部分。如果我们想要将我们的工作重点放在个别领域,这将是有用的。在这种情况下,我们将模糊整个消息。值得一提的是,Mutiny还可以处理多消息交换。
如果我们用作初始模板的测试用例包含不会导致程序采用不同路径的部分,那么我们对此数据所做的所有修改都不会增加代码覆盖率,从而导致浪费时间和低效的模糊测试。
在不详细讨论SMB协议的情况下,我们可以观察到该请求包含大约十二个请求方言的列表。每个方言对应于一组特定的支持命令。如果我们对一组特定的命令进行模糊测试,这可能会很有趣,但是现在我们并不关心这一点。
提供一个或两个方言的较短列表将导致Radamsa创建更有意义的突变并发送更多种类的SMB请求类型。我们的理由是,可能改变一种方言或另一种方言不会使应用程序在单个消息对话中采用截然不同的路径,因此我们这样做并编辑模板如下所示:
>>> open(“req”).read()
‘\x81\x00\x00 \x00\x00 \x00\x00 \x00\x00 \x00\x00 \x00\x00 \x00\x00 \x00\x00 \x00\x00 \x00\x00 \x00\x00 \x00\x00’
有了模板,我们就可以开始模糊测试了。记得用Wireshark捕获完整的会话。Mutiny还可以记录发送的每个数据包,但是我们发现当服务器在崩溃后停止响应时,更容易查看Wireshark。
使用devel帐户打开与路由器的telnet连接并运行pkill smb; / nova / bin / smb启动新的SMB进程并观察其输出。
以下命令将指示Mutiny在数据包之间休眠半秒并记录所有请求: ./mutiny.py -s 0.5 - logAll negotiate_protocol_request-0.fuzzer HOST
详细输出将显示正在发送的不同大小的数据包以及每个数据包的数字标识符。该值可用于重复完全相同的突变序列,并提供重现崩溃的方法。这很重要,因为即使我们发现崩溃,以前的请求也可能已经损坏了某些内容,或者以崩溃发生所需的方式更改了应用程序状态。如果我们无法在崩溃之前重新创建状态,即使我们确定哪个特定请求最终导致崩溃,我们也可能空手而归。
如果模糊测试会话中断并且我们不想重播先前的突变,则可以使用-r参数指示Mutiny开始从该迭代开始发送突变(例如:-r1500-将发送突变1500,1501, 1502,依此类推)。
如果我们在模糊器运行时观察Wireshark,我们将看到并非所有数据包都符合预期的格式,这对我们来说是件好事。当应用程序无法以正确的方式处理意外数据时,通常会出现漏洞。
我们运行SMB二进制文件的终端还将包含有用的数据,以确认我们实际上正在向服务提供格式错误的请求。
现在我们让模糊器运行。我们可以使用不同的延迟值来查看服务器是否可以快速处理请求,但是每秒两个请求对于此概念验证是可行的。
几分钟后,Mutiny在尝试连接服务后无法完成运行。
如果我们看一下运行二进制文件的终端,我们将收到一条Segmentation fault消息
如前所述,backtrace.log文件包含寄存器转储以及有关导致崩溃的更多信息。
最后,通过检查Wireshark,我们可以看到发送到服务器的最后一个数据包被描述为“对非法NetBIOS名称的会话请求”。
了解崩溃
首先,我们将确保我们可以随意重现崩溃。复制Mutiny发送的最后一个数据包或从Wireshark中提取消息在此处是等效的。我们对NetBIOS下面的层不感兴趣,因为我们将创建一个小脚本来通过TCP发送数据包。
从导出的文件中提取原始字节。
>>> open(“req”)。read()
'\ x81 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00'
创建一个简单的python脚本,将有效负载发送到远程服务。请注意,为了清楚起见,我已使用等效的十六进制表示替换了空格。
在产生新的SMB进程(pkill smb && / nova / bin / smb)后运行该脚本几次,看看会发生什么。我们现在有一种可靠的方法来通过单个请求重现崩溃。
在这种情况下,我们正在处理Wireshark具有解析器的协议,因此我们可以使用该信息来了解协议级别的崩溃原因。显然,发送NetBIOS会话请求(消息类型0x81)会在SMB二进制文件中执行易受攻击的代码路径。
让我们从路由器中提取二进制文件,这样我们就可以在反汇编程序中打开它。将/ nova / bin / smb复制到/ flash / rw / pckg,以便可以通过FTP访问并下载。
这也是能够使用GDB调试流程的好时机。我喜欢使用PEDA来增强GDB的显示并添加一些有用的命令。
我们打开两个到目标路由器的连接。在一个我们做pkill smb && / nova / bin / smb以获得实时输出,另一方面我们启动附加到新生成的进程的gdbserver。
最后,我们在测试机器中打开GDB,并使用目标远程IP:PORT连接到调试服务器。
通过执行文件smb来指示GDB我们所附加的二进制文件也很有用。下次连接到调试服务器时,它将尝试解析已加载库的符号。
在调试会话中按c,以便像往常一样继续执行。
运行概念证明将导致服务因SIGSEGV而停止。在这里,我们看到在执行复制操作时取消引用了NULL指针。
现在,我必须承认我做静态分析非常糟糕,特别是涉及C ++程序时。在克服这种限制的蹩脚尝试中,我将尽可能多地依赖于动态分析,在这种特殊情况下,我将依赖Wireshark解剖器提供的信息,该信息可以提供有关协议字段的更多信息。
可以看出,我们发送的NetBIOS会话服务数据包的第一个字节将消息类型设置为会话请求(0x81)。
第二个字节包含标志,在我们的概念验证中将所有位设置为零。
接下来的两个字节表示消息长度,设置为32。
最后,剩余的32个字节称为非法NetBIOS名称。
我们可以假设在某个时刻正在读取此大小,并且由于它是我们发送的唯一消息长度信息,因此可能与漏洞有关。为了测试这个假设,我们将在诸如read和recv之类的常用函数上放置断点,以识别应用程序从套接字读取数据包的位置。
运行脚本后,程序在read()中断。
我们使用ni导航到下一个指令,并在执行读取系统调用后立即停止。
read的定义如下:
ssize_t read(int fd,void * buf,size_t count);
EAX保持读取的字节数,似乎是0x24(36)。这对应于我们之前分析的标头:消息类型为1个字节 - 标志为1个字节 - 消息长度为2个字节 - NetBIOS名称为32个字节。
ECX包含存储数据读取的缓冲区的地址。我们可以使用vmmap $ ecx或vmmap 0x8075068来验证这对应于堆区域。
最后,EDX声明调用读操作从套接字读取最多0x10000字节。
从这里开始,我们可以继续逐步执行并添加观察点,以查看我们的数据会发生什么。
由于Wireshark没有在NetBIOS名称中识别与分析协议相关的任何内容,因此我们将更改有效负载以包含更多可区分的字符,例如“A”,以便在我们的调试会话中更容易识别该有效负载。这也是一个好主意,看看是否可能触发额外的复制操作,否则将停止在第一个NULL字节。
我们有两个字节可以播放不同的大小,所以在向前移动之前尝试发送不同的消息长度并查看崩溃是否仍然发生是很有趣的。
我们已经尝试了32个字节,所以让我们变得疯狂并做64,250,1000,4000,16000,65005。
64(payload=“\ x81 \ x00 \ x00 \ x40”+“A”* 0x40)
与原始的概念证明相同。寄存器看起来一样。
250(payload=“\ x81 \ x00 \ x00 \ xfa”+“A”* 0xfa)
这是一个非常有趣的变化。我们看到大多数寄存器设置为0x41414141,这是我们的输入,我们看到堆栈中充满了大量的“A”,甚至EIP似乎已经被破坏了。
1000(payload=“\ x81 \ x00 \ x03 \ xe8”+“A”* 0x3e8)
与之前的有效负载相同
4000(payload=“\ x81 \ x00 \ x0f \ xa0”+“A”* 0xfa0)
与之前的有效负载相同
16000(payload=“\ x81 \ x00 \ x3e \ x80”+“A”* 0x3e80)
虽然我们发现堆栈也已损坏,但这会在不同的指令处崩溃。
65500(payload=“\ x81 \ x00 \ xff \ xdc”+“A”* 0xffdc)
与之前的有效负载相同
所以...我们看到程序在执行不同的指令时崩溃了。但是,我们可以观察到的常见问题是,当从单个NetBIOS会话请求消息解析NetBIOS名称时,堆栈在某些时候已经损坏,并且当我们发送250字节消息时,大多数寄存器包括我们的有效负载的一部分。这使得分析特别有趣,因为我们有直接的EIP覆盖和控制堆栈。
请注意,我们无法确保所有崩溃都归因于此时完全相同的错误。也许发送更大的缓冲区会让我们走上一条不同的道路,最终会更容易被利用,所以你必须自己回答这个问题。
中间还有一些看似随机数的“。”(0x2e)字符。我们将看到他们以后会发生什么。
在崩溃之前,程序会打印一条显示“New connection:”的消息。这对于获得一些态势感知非常有用,无需向缓冲区添加观察点并跟踪数十个读取操作(您可以使用rwatch * addr在GDB中添加读取观察点,并且只要程序访问该内存地址,就会停止执行)。
我们在Binary Ninja中打开/ nova / bin / smb二进制文件并搜索字符串
在0x80709fb只出现一次。检查交叉引用显示单个用法,这可能是我们想要的。
如果我们转到sub_806b11c的开头,我们会注意到需要满足几个条件才能让我们到达打印字符串的块。
第一个条件是与0x81的字节比较,这是我们发送的消息类型。
将断点放在0x806b12e并执行后,我们可以检查寄存器值并更好地了解发生的情况。我们可以观察到例如我们在请求中发送的大小需要高于0x43才能进入感兴趣的块。
根据我们之前的测试,我们知道从这个块中调用的函数之一需要是破坏堆栈的函数。我们继续使用n代替s来遍历GDB中的每条指令,以避免进入函数。每次运行函数后,我们都会看一下堆栈。
我们遇到的第一个函数是0x805015e。
运行后我们看到堆栈似乎没问题,所以这可能不是负责溢出的函数。
稍后我们有一些指令,我们有下一个候选函数,函数在0x8054607。我们再一次让它运行并观察堆栈并记录上下文。
Aaaaand我们找到了罪魁祸首。看看EBP并观察堆栈框架是否已损坏。继续调试,直到该函数即将返回。这里从包含我们数据的堆栈中弹出各种寄存器。
这里不受欢迎的想法:你真的不需要了解这个功能在利用漏洞时所做的一切。我们已经有了EIP控制,并且大多数堆栈看起来或多或少都是未损坏的输入数据。
花一些时间来查看0x5054607处的功能,并使用GDB的帮助产生以下伪代码:
int parse_names(char *dst, char *src) {
int len;
int i;
int offset;
// take the length of the first string
len = *src;
offset = 0;
while (len) {
// copy the bytes of the string into the destination buffer
for (i = offset; (i - offset) < len; ++i) {
dst[i] = src[i+1];
}
// take the length of the next string
len = src[i+1];
// if it exists, then add a separator
if (len) {
dst[i] = ".";
}
// start over with the next string
offset = i + 1;
}
// nul-terminate the string
dst[offset] = 0;
return offset;
}
实质上,该函数接收两个堆栈分配的缓冲区,其中源缓冲区预期格式为SIZE1-BUF1,SIZE2-BUF2,SIZE3-BUF3等。“。”用作条目分隔符。
读取源缓冲区的第一个字节并将其用作复制操作的大小。然后,该函数将该字节数复制到目标缓冲区中。完成后,读取源缓冲区的下一个字节并将其用作新大小。当要复制的大小等于零时,此循环结束。未进行验证以确保数据适合目标缓冲区,从而导致堆栈溢出。
写exp
如何处理利用取决于目标设备和架构的细节。在这里,我们只对Cloud Hosted Router x86二进制文件感兴趣。
值得一提的是,可能有几种不同的方法来实现对此漏洞的可靠利用,因此我们将审查我们使用的那个,这可能不是最优雅或最有效的方法。
Tobias Klein的checksec脚本是一个很好的资源,可以检查我们需要采取哪些缓解措施。可以从PEDA调用此脚本。
stack canaries的缺乏可能是最缺失的最相关的缓解,使得基于堆栈的缓冲区溢出很容易被利用。如果程序是用堆栈金丝雀编译的,那么我们以前的测试会产生非常不同的结果。堆栈canaries将随机值放在分配缓冲区的每个函数帧中的重要数据之前,并在函数返回之前检查这些值。如果发生溢出,将终止执行并且不再进一步利用。
PIE禁用意味着我们可以依赖程序代码的固定位置,禁用RELRO意味着我们可以覆盖全局偏移表中的条目。
总而言之,我们将只处理NX,它限制从可写区域(如堆栈或堆)执行。
在系统级实施的另一项缓解措施是ASLR。这是一个32位系统,因此部分覆盖或甚至暴力强制可被视为ASLR的可行旁路。在这种情况下,这是没有必要的。
检查RouterOS中任何程序的内存映射表明堆栈基本确实是随机的,但堆不是。这可以验证运行cat / proc / self / maps几次并比较结果。
构建我们的漏洞的第一步是获得精确的偏移以获得对EIP的控制。为了做到这一点,我们可以使用PEDA生成一个独特的模式,命令模式创建256并将其插入到我们的漏洞利用框架中。请注意,标头之后的第一个字节将是易受攻击的函数解析的大小,因此我们指定0xFF以不变地读取256个字节,并避免将“。”字符放置在有效负载的中间。
发生崩溃时,可以使用伴随的模式偏移VALUE命令来确定覆盖EIP的确切位置。
更改有效负载并验证EIP是否可以设置为任意值。
我们没有观察到烦人的“。”字符,这很好。
既然我们控制了EIP和堆栈的其余部分,我们就可以使用借用的代码块技术,这种技术更好地称为返回导向编程或ROP(有些人似乎对那些使用后者的人非常恼火,所以很可能更好地提及所有替代方案)。
主要思想是我们将链接以RET指令结尾的各种代码片段来执行或多或少的任意代码。如果有足够的这些小工具,我们应该能够运行我们想要的任何东西。但在这种特殊情况下,我们只想将堆区域标记为可执行文件。最终目标是在堆中存储某些东西(已经包含从客户端读取的消息)并利用静态基址跳转到那里。
这里的相关功能是mprotect,如下所示:
int mprotect(void * addr,size_t len,int prot);
地址将是0x8072000,这是堆的基础。这需要页面对齐才能工作。
Len可以是我们想要的任何东西,但让我们改变整个0x14000字节的保护。
最后,prot指的是执行的按位或所需的保护措施。7指的是PROT_READ | PROT_WRITE | PROT_EXEC,本质上是我们的目标RWX。
有迹象表明,可以尝试自动创建链的各种工具,如ROPGadget和Ropper。我们将使用ropper但手动构建ROP链以显示它是如何完成的。
根据Linux系统调用约定,EAX将包含系统调用号,对于mprotect为0x7d。EBX将包含地址参数,ECX的大小和EDX所需的保护。
让我们开始将EBX设置为0x8072000。我们寻找包含POP EBX指令的小工具,并尽可能减少副作用。
我们选择较小的小工具并开始构建我们的链。这看起来如下。执行将被重定向到0x804c39d,它将首先执行POP EBX指令,将EBX设置为所需的值0x8072000。接下来,将执行POP EBP,因此我们需要提供一些虚拟值,以便从堆栈中弹出一些东西。最后,执行RET指令,弹出堆栈中的下一个并跳转到那里。这需要成为我们链中的下一步。
所有值都打包为little-endian无符号整数。
我们使用相同的过程在ECX中设置所需的大小。重要的是要理解顺序很重要,因为如果我们不小心,我们可能会在不知不觉中覆盖我们已经设置的寄存器。此外,有时小工具看起来不如POP DESIRED_REG好; RET和我们将不得不处理需要额外调整的潜在副作用。
在这里,我们将选择更加良性的0x080664f5。这个小工具改变了EAX的值,但我们目前不依赖于EAX中的任何特定设置,因此它非常有用。我们将此附加到我们的ROP链。
我们重复这个过程,这次将EDX设置为7,这是RWX保护级别。
这次我们在0x08066f24处选择小工具,这不会弄乱我们之前设置的寄存器。
最后,我们需要将EAX设置为系统调用号0x7d。我们搜索包含POP EAX的小工具,但找不到任何不会改变我们当前设置的内容。
我们可以尝试以不同的方式重新排序我们的小工具,但我们只会搜索另一个执行XCHG EAX,EBP并使用无处不在的POP EBP链接它的小工具; RET。
从这里我们取0x804f94a和0x804c39e并追加它们
现在可以根据需要配置寄存器以执行mprotect系统调用。为此,我们需要调用INT 0x80,它通知内核我们要执行系统调用。
但是,当我们查找包含此指令的小工具时,我们找不到任何内容。这可能会使事情变得更加困难。
幸运的是,还有另一个地方我们可以找到这种小工具。所有用户空间应用程序都有一个小型共享库,由内核映射到其地址空间,称为vDSO(虚拟动态共享对象)。这是出于性能原因而存在的,并且是内核将某些功能导出到用户空间并避免对经常调用的函数进行上下文切换的方法。如果我们看一下手册页,我们会看到一些有趣的东西:
这意味着vDSO中有一个可能知道如何执行系统调用的函数。我们可以检查这个函数在GDB中的作用。
从上面的屏幕截图中可以看出,__ kernel_vsyscall包含一个有用的小工具。我们执行该过程几次,并意识到此映射不受ASLR的影响,这允许我们使用此小工具。EBX,ECX和EBP的值现在并不重要,因为它们将在执行系统调用后设置。
我们更新漏洞利用代码以发送我们构建的链并将GDB附加到正在运行的SMB二进制文件
EIP会将执行重定向到我们的第一个小工具,因此最好将断点放在0x804c39d,这是链的起点。
使用stepi观察寄存器如何设置为所需的值。在INT 0x80之后,我们可以列出映射区域,如果一切正常,堆将被标记为RWX。
剩下的部分包括将堆中的任意代码存储在已知位置,这样我们就可以跳转到那里获得一个shell,但是我们怎么能这样做呢?
当我们在read()中放置一个断点时,我们发现请求数据存储在堆中的某个地方。此外,我们有各种协商协议请求请求的样本,因此可以确定如果消息类型字节设置为0x00,那么我们将到达程序中的某个路径,其中有效负载将被处理并存储在堆。
为了测试这个假设,让我们再次在read()中放置一个断点,并更改PoC有效负载,以发送一个带有512“A”作为内容的良性协商协议请求消息。提醒一下,格式是:
消息类型(1字节) - 标志(1字节) - 消息长度(2字节) - 消息
此时消息类型将设置为NETBIOS_SESSION_MESSAGE(0x00)。我们没有使用另一个会话请求消息(0x81)来避免意外触发漏洞并且必须处理易受攻击的函数所在的“。”字符。
逐步执行读取功能,直到从网络读取0x204字节(512“A”s + 4字节标头)。如前所述,ECX包含缓冲区的地址。
检查指定地址处的内存内容会显示我们的有效负载。
按c键允许执行正常继续并发送新请求以检查前一个请求是否被覆盖,或者它是否刚刚留在堆中。
当再次到达断点时,我们尝试打印读缓冲区的内容,不幸的是我们意识到它已被清零。
但是,如果应用程序制作了未归零的副本,则之前的请求仍可能在其他位置挥之不去。它是可以通过使用搜索与PEDA当前地址空间的发现或searchmem命令。我们的消息由512“A”组成,因此我们试图找到一个连续的“A”块。这些命令采用由空格分隔的可选参数,以将搜索限制在特定区域。我们只对堆中可能存在的结果感兴趣。
这意味着正在复制请求的内容并将其保留在未清除的某个缓冲区中。我们需要进行一些测试,以便能够信任此位置来存储我们的有效负载。特别是,如果我们将脚本更改为发送512“B”而不是“A”,我们将看到0x8085074将在处理请求后最终包含“B”。我们需要数据在其他请求中持续存在,因此这并不好。
但是,如果我们首先发送512“A”然后再说256“B”,那么很明显前半部分被覆盖但后半部分仍然包含前一个请求的字节。奇怪的0x00000e89是来自堆控制结构的块元数据,与我们的场景无关。
知道数据将至少在两个请求中保持不变,我们可以制定以下计划:
使用我们要执行的代码发送协商协议请求。第一部分将是几百个NOP指令,因为当我们发出第二个请求时,这些字节将被覆盖,并带有触发漏洞的相应会话请求消息。
发送破坏堆栈的会话请求消息,将ROP保护为mprotect,将堆标记为可执行文件并跳转到存储#1负载的硬编码位置,滥用堆基不随机化的事实。
我们决定为第二个请求留下512个字节,因此我们将跳转到0x8085074 + 512 = 0x8085270的硬编码位置。此地址需要附加到我们的ROP链中。之前的小工具将执行其最终的RET指令,将从堆栈中弹出0x8085270,然后执行程序。
shellcode的第一个版本将只包含INT3指令,因此调试器在执行时会中断。INT3的操作码是CC。
该脚本也被修改为打开两个连接,每个连接一个。
456c-43e2-1.png)
附加到新的SMB流程并运行漏洞利用程序
我们现在正在执行任意代码。让我们使用msfvenom生成反向shell有效负载。
我们修改第一阶段以存储此有效负载并再次运行该漏洞。
这次我们在指定的端口打开一个netcat监听器,以便我们可以接收连接。
结论
使用基于突变的方法对网络服务进行模糊化可以很少的努力完成,并且可以产生很好的结果。
如果您正在寻找那些谈论神秘专有协议的应用程序中的漏洞,或者只是懒得构建一个全面的模板,那么就给愚蠢的模糊测试。您可以在应用忍者逆向工程技能的同时以最小的努力使模糊器运行,以了解协议并构建更好的东西。
RouterOS驱动的设备现在无处不在,缺乏现代(对现代的任意定义)漏洞利用缓解有点令人担忧。启用完整的ASLR会使漏洞利用编写器的生命变得更加困难,并且如果二进制文件是使用堆栈canaries支持编译的,那么在没有info-leak漏洞的情况下,大多数堆栈溢出将变得无法开发。
值得一提的是MikroTik的响应和补丁时间都很棒。起初,更改日志没有暗示存在安全漏洞:
What's new in 6.41.3 (2018-Mar-08 11:55):
*) smb - improved NetBIOS name handling and stability;
但是,它们现在看起来更严重了。它们在其有关安全漏洞的更改日志中包含更详细的评论,并且似乎有一个博客,他们也发布有关这些类型问题的官方公告。
精明的读者可能已经注意到,如果你再现了这篇文章中概述的步骤,你甚至可能在RouterOS SMB中找到了一些额外的0天。
玩得开心!
其他资源
Core Security的原始咨询(https://www.coresecurity.com/advisories/mikrotik-routeros-smb-buffer-overflow)
chimay-red的详细分析(WWW漏洞中的漏洞7)https://blog.seekintoo.com/chimay-red.html
MIPS利用BigNerd95在这篇文章中描述的bug https://github.com/BigNerd95/Chimay-Blue
Jacob Baines发布了https://medium.com/@jbaines
对去年着名的Winbox漏洞的分析很简单https://n0p.me/winbox-bug-dissection/
0ki MikroTik工具https://github.com/0ki/mikrotik-tools
0ki的研究中心,有数十个视频/幻灯片格式的演示文稿https://kirils.org/