USDT第三方支付

菜宝钱包(caibao.it)是使用TRC-20协议的Usdt第三方支付平台,Usdt收款平台、Usdt自动充提平台、usdt跑分平台。免费提供入金通道、Usdt钱包支付接口、Usdt自动充值接口、Usdt无需实名寄售回收。菜宝Usdt钱包一键生成Usdt钱包、一键调用API接口、一键无实名出售Usdt。

0x00 前言

这篇文章主要先容了一种多年来我一直实验行使的破绽行使技巧,这种技巧终于在Windows 10的最新版本中取得了乐成。行使这种技巧,可以让我们获得对虚拟内存的接见,在遇到虚拟内存时获得反馈并延伸至无限期的接见。在这篇文章中,首先将先容一些靠山知识,来说明为什么这种技巧可以乐成行使,随后将先容发现这种行使方式的历程,以及可以使用的破绽类型概述。

0x01 靠山

我们什么时候需要这种破绽行使技巧?Mateusz Jurczyk和Gynvael Coldwind在具有开创性的Bochspwn研究中找到了一个很好的例子,可以说明适用的平安破绽类型。经由他们的研究,发现了一种自动发现Windows内核中内存double-fetch的方式。

若是人人还没有读过这篇论文,我可以做简要的说明,这里所说的double-fetch可以理解为一个检查时间vs使用时间(TOCTOU)破绽。代码从内存中读取一个值(例如缓冲区长度),首先验证该值是否在所需局限之内,然后在需要用到这个值时,再次从内存中读取该值。一旦攻击者在第一次读取(检查)和第二次读取(使用)之间修改内存中的值,就可以绕过前面的检查历程,可能会引发平安问题,例如特权提升或信息泄露。下面展示的是一个简朴的例子,说明double-fetch的发生历程。

DWORD* lpInputPtr = // controlled user-mode address
UCHAR  LocalBuffer[256];
 
if (*lpInputPtr > sizeof(LocalBuffer)) { ①
  return STATUS_INVALID_PARAMETER;
}
RtlCopyMemory(LocalBuffer, lpInputPtr, *lpInputPtr);②

这段代码将缓冲区从受控的用户模式地址复制到牢固巨细的栈缓冲区中。缓冲区以DWORD巨细值开头,这个值示意缓冲区的总巨细。若是lpInputBuffer指向的巨细值在“第一次读取与缓冲区巨细举行对照”①和“第二次读取复制到缓冲区”②之间发生了改变,就可能会导致内存损坏。举例来说,若是第一次读取时值为100,第二次读取时为400,这样的情形下代码会通过巨细检查,由于100小于256,但现实上会将修改后的400复制到缓冲区中,从而破坏了栈。

一旦发现了这样的破绽,Mateusz和Gynvael就最先研究若何行使。在本文的第四章中详细先容了若何实现破绽行使。像这一类的破绽行使都是存在一定概率的,破绽行使历程通常需要读取和写入的两个线程相互竞争,只有在两次读取之间发生修改的情形下才气够乐成。

为了进一步扩大TOCTOU的窗口期,有许多手艺都滥用了Windows上虚拟内存的行为。Windows上的历程通常可以接见最大8TiB巨细的大型虚拟内存区域。这个巨细可能要比系统中的物理内存大得多,特别是考虑到限制是凭证历程而不是凭证系统的。因此,为了保持云云大的内存地址空间的错觉,内核需要按需对内存举行分页。

在分配内存后,CPU的页表被设置为指示内存区域的存在,但此时被标记为无效。此时,已经分配了虚拟内存区域,但没有物理内存的支持。当历程实验接见该内存区域时,CPU将会天生一个异常,通常称为页错误(Page-Fault),由内核举行处置。

内核可以查找导致页错误的已接见内存地址,然后实验修复该地址。修复页错误的方式取决于内存接见的类型。一个简朴的例子是,若是内存已分配但尚未使用,内核将获得一个物理内存页,将其初始化为零,然后调整页表,以将新的物理内存页映射到故障地址。一旦修复了页错误,据可以凭证接见内存的指令重新启动有故障的线程,而且内存接见应该就可以乐成举行,就好像它始终存在一样。

更庞大的情形是需要判断页面是否是内存映射文件的一部门。在这种情形下,内核需要请求从磁盘读取页面数据,然后才气知足页错误触发的条件。这可能要破费很长的时间,至少对于传统的机械磁盘来说是这样,由于可能需要在守候页面读取时挂起故障的线程。一旦读取页面后,就可以修复内存,然后恢回复始线程,凭证错误指令重新启动线程。

最终的效果是,与CPU自己的速率(即处置页错误)相比,可能要破费大量时间。然则,滥用这些虚拟内存行为只会扩大TOCTOU的窗口期,并没有设施提供一个用于交流内存中值的准确时间。那么这样一来,破绽行使手艺仍然存在局限性。例如,在某些情形下,在具有单个CPU内核的盘算机上举行破绽行使的速率异常慢,由于这一历程依赖于并发线程的读写。

一个理想的破绽行使原语是可以随便增大行使窗口期,从而轻松获得竞争的胜利。连系以前对这类破绽行使的知识和履历,我以为理想的原语应该要知足以下条件:

(1)适用于Windows 10 20H2默认安装版本操作系统;

(2)在读取或写入内存时可以发出一个明确的信号;

(3)在从用户模式和内核模式接见内存时都可以行使;

(4)允许无限期延迟内存的接见;

(5)接见的内存中的数据是随便的;

(6)可以从一系列特权级别中设置原语;

(7)可以在统一破绽行使历程中多次捕捉。

上述这些是我们完全理想情形下的条件,我们不能保证可以知足上述所有的条件。若是我们仅仅实现了其中的一部门,破绽的可行使局限就会受到一些限制。接下来,我们对先前研究成果加以概述,这可能会让我们对若何继续寻找原语有所启发。

0x02 先前研究成果

在我与Mateusz举行交流并起劲举行后续研究的历程中,似乎没有找到确立在Bochspwn论文基础上的其他TOCTOU行使思绪。至少,在Windows环境下确实云云。但我在其他平台,特别是Linux上,已经发现了新的破绽行使手艺。这两种手艺都依赖于我先前形貌的虚拟内存的行为。

在Linux中发现的第一种手艺是行使Userfault文件形貌符(userfaultfd)在历程中发生页错误时获取通知。启用userfaultfd后,历程中的辅助线程可以读取通知,并在用户模式下处置页错误。处置错误的方式可能是在适当的位置映射内存或更改页面珍爱。这里的关键是,有故障的线程会被挂起,直到由另一个线程处置页错误为止。因此,若是内核函数接见了内存,则请求会被捕捉,直到完成为止。这样一来,就可以无限期地延迟存储器接见,同时也能够获得用于接见的准时信号的原语。使用userfaultfd还可以将错误区分为读错误和写错误,由于可以对内存页举行写珍爱。

使用userfaultdd可以举行历程内接见,例如从内核举行接见,然则若是接见内存的代码位于另一个历程中,就没有真正的用处。为了解决这一问题,我们可以使用FUSE文件系统,就像Jann Horn此前在Project Zero博客文章中所演示的。FUSE文件系统完全以用户模式实现,然则对文件的任何请求都市通过Linux内核的虚拟文件系统API举行。由于文件接见似乎是由内核文件系统实现的,因此可以使用mmap将文件映射到内存中。当FUSE支持的内存区域上发生页错误时,会向用户模式文件系统守护程序发出请求,这可能会无限期地延迟读取或写入请求。

0x03 远程文件系统

据我所知,在Windows上不存在与Linux的userfaultd相类似的功效。但有一个功效引起了我的注重,那就是内存写入监视。然则,它似乎仅允许应用程序查询自上次检查之后是否写入了内存,而且不允许捕捉内存写入。

若是我们无法将页错误捕捉到虚拟内存中,那么应该若何在FUSE等用户模式文件系统上映射文件呢?遗憾的是,在Windows 10中没有内置的FUSE驱动程序(可能是尚未包罗),但这不意味着没有机制可以在用户模式下实现一个文件系统。此前已经有研究成果实验在Windows上制作真正的FUSE,例如WinFsp项目,但我以为将它们在真实系统上安装的概率会很小。

我首先想到的是实验行使多个UNC Provider(MUP)客户端。当我们通过UNC路径接见文件时(例如\\server\share\file.bin),会由内核中的MUP驱动程序处置,并将其通报给已注册的客户端驱动程序之一。就内核而言,打开的文件是一个通例文件(包罗一些忠告),这通常意味着该文件可以映射到内存中。然则,对该文件内容的任何请求都不会直接处置,而是由服务器通过网络协议处置。

理想情形下,我们应该可以实现自己的服务器,处置对文件映射的读取或写入请求,这将使我们能够检测或延迟该请求,以便我们可以行使任何的TOCTOU。我们剖析了Microsoft MUP驱动程序,确认其是否支持Windows 10版本,以及默认情形下是否启用了该驱动程序。

远程文件系统支持版本如下:

· SMB所有版本默认支持(可能需禁用SMBv1);

· WebDAV所有版本默认支持(服务器SKU除外);

· NFS所有版本支持,非默认启用,需调整设置;

· P9仅Windows 10 1903支持,非默认启用,需WSL支持;

· 远程桌面客户端所有版本默认支持。

只管MUP是为远程文件系统而设计的,但并不要求文件系统服务器必须是远程的。SMB、WebDAV、NFS是基于IP的协议,可以重定向到localhost。P9使用内陆Unix套接字,无论若何都无法远程举行。终端服务客户端通过RDP协议将文件接见请求发送回客户端系统。对于所有这些协议,我们可以用差别的方式来实现服务器,并查看我们是否可以检测并延迟对文件映射的读写操作。

我决议只关注SMB和WebDAV这两个,由于只有它们是默认启用的,而且很少被使用。理论上,在安装远程桌面客户端时,通常默认不会启用RDP服务器。同样,设置RDP会话也很庞大,可能需要有用的身份验证凭证,因此我决议首先剖析它。

3.1 服务器新闻

SMB险些与Windows一样古老,早在1987年就在Lan Manager 1.0中引入。最新的SMB 3.1版本与最早的版本仅有一些相似之处,但新版本不再使用TCP/IP毗邻的NetBIOS。从血统上来看,SMB是所有网络文件系统中最理想的一个集成,而且MUP API是针对SMB的需求而设计的。

我决议对通过SMB映射文件的行为举行简朴测试。这异常简朴,由于我们可以通过localhost接见统一台盘算机上的SMB。首先我在内陆磁盘上建立了一个1GB的文件,缘故原由在于,若是SMB支持缓存文件数据,则不可能一次性读取云云之大的文件。然后,我启动了WireShark,并监测回环接口以捕捉SMB流量,如下所示。

然后,我快速编写了一个PowerShell剧本,该剧本会将文件映射到内存中,然后使用几个差别的偏移量从内存中读取几个字节。

Use-NtObject($f = Get-NtFile "\\localhost\c$\root\file.bin" -Win32Path) {
    Use-NtObject($s = New-NtSection -File $f -Protection ReadWrite) {
        Use-NtObject($m = Add-NtSection -Section $s -Protection ReadWrite) {
            $m.ReadBytes(0, 4)
            $m.ReadBytes(256*1024*1024, 4)
            $m.ReadBytes(512*1024*1024, 4)
            $m.ReadBytes(768*1024*1024, 4)
        }
    }
}

其中,会从偏移量0、256MiB、512MiB和768MiB读取4个字节。返回到WireShark,我使用过滤器 *** b2.cmd == 8筛选了输出中的SMBv2读取请求,可以考察到以下四个数据包。

Read Request Len:32768 Off:0 File: root\file.bin
Read Request Len:32768 Off:268435456 File: root\file.bin
Read Request Len:32768 Off:536870912 File: root\file.bin
Read Request Len:32768 Off:805306368 File: root\file.bin

只管长度始终为32KiB,并不是我们期望的4KiB,但这与我们在剧本中接见的确切内存偏移量相对应。请注重,这不是我们期望的通例Windows内存分配单元64KiB。在我们的测试中,除了请求的32KiB之外,我们再也没有看到其他任何内容。

我们测试过的所有字节都与32KiB对齐,那么,若是我们从地址512MiB减去2的地址接见了4个字节,在这种字节未对齐的情形下会怎么样呢?通过修改剧本并添加以下内容,可以确认其行为:

$m.ReadBytes(512*1024*1024 - 2, 4)

在WireShark中,我们看到了以下读取请求。

Read Request Len:32768 Off:536838144 File: root\file.bin
Read Request Len:32768 Off:536870912 File: root\file.bin

接见仍然在32KiB界限,然则当请求跨越了两个块时,内核会从文件中获取前面的32KiB数据,随后再获取后面的32KiB数据。人人可能以为这有一定原理,但事实证实,这只是测试历程中泛起的有时情形。

上图展示了用于处置映射文件读取的结构。在读取地址后,内核将从最近的4KiB页界限(而不是32KiB界限)请求32KiB。然则,凭证支持的大页面巨细,在最上面还存在一个辅助的结构。若是读取的是大页面末尾32KiB内的随便位置,则读取的偏移量始终是最后32KiB的值。

例如,在我的系统上,大页面巨细(使用GetLargePageMinimum API查询)为2MiB。因此,若是从偏移量512MiB最先,位于512MiB和514-32KiB之间,内核将找到最接近的4KiB界限的偏移量并读取32KiB。在514-32KiB到514MiB之间,读取历程请求的始终是514-32KiB的偏移量,从而使得32KiB不会跨越大页面界限。

这样就允许在4KiB界限举行读取,但读取的数据量仍然为32KiB。这意味着,一旦接见一个4KiB页面,内核将填充当前页面和随后的7个页面。那么,有什么方式可以只填充单个内陆页面吗?凭证Mateusz的谈论,我举行了测试。若是SMB服务器返回的字节数少于读取请求的字节数,则只会填充读取所笼罩的页面,并不会发生失败。通过返回这些较短的读取效果,我们可以将精度减小到内陆页面巨细,除了最后的32KiB之外。若是读取请求小于内陆页面巨细,那么其他的页面就会被清零。

那么,对于写入来说呢?我们再次修改剧本,将ReadBytes改为WriteBytes,例如:

$m.WriteBytes(256*1024*1024, @(0xAA, 0xBB, 0xCC, 0xDD))

我们可以在WireShark中看到对该文件的以下写入请求,类似于以下内容:

Write Request Len:4096 Off:268435456 File: root\file.bin

然则,若是我们举行更深入的研究,就会发现写入仅在文件关闭后才会发生,并不是在对WriteBytes挪用的响应历程中。这是有缘故原由的,没有任何方式可以检测在什么时候发生了写入操作并迫使页面刷新到文件系统。纵然有方式可以在每次写入时都刷新到网络服务器,那也会对性能发生伟大的影响。

然则,所有内容都不会丢失,因此在平安写入内存之前,必须使用文件中的内容举行填充。若是我们在写入操作之前举行考察,就会看到对32KiB区域的响应读取操作,该请求包罗与读取同步的写入位置。我们可以通过响应的读取来检测写入,然则无法在协议的层面将读取与写入区离开。

所有这些测试解释,若是我们可以控制服务器,就可以检测到对映射文件的内存接见。那么,我们可以延迟接见吗?这里使用Tal Aloni的SMBLibrary在.NET 5中编写了一个简朴的SMB服务器。我使用自界说文件系统处置程序实现了服务器,并向读取路径添加了一些代码,当文件偏移量大于512MiB时,该路径会延迟10秒。

if (Position >= (512 * 1024 * 1024)) {
    Console.WriteLine("====> Delaying at Position {0:X}", Position);
    Thread.Sleep(10000);
    Console.WriteLine("====> Continuing.");
}

读取操作返回的数据可以是随便的,我们只需要在读取中填充适当的字节缓冲区即可。为了测试接见时间,我将内存读取请求包装在Measure-Command挪用中,以对内存接见举行计时。

Measure-Command { $m.ReadBytes(512*1024*1024 - 4, 4) }
Measure-Command { $m.ReadBytes(512*1024*1024 - 4, 4) }
Measure-Command { $m.ReadBytes(512*1024*1024, 4) }
Measure-Command { $m.ReadBytes(512*1024*1024, 4) }

为了对照接见时间,在512MiB界限以下的4个字节处、512MiB界限处划分发出读取请求。通过发出这两个请求,我们应该可以看到每次读取的效果是否差别。效果如下:

, Below 512MiB (Request 1)
Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 1
Milliseconds      : 25
...
, Below 512MiB (Request 2)
Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 1
...
, Above 512MiB (Request 1)
Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 10
Milliseconds      : 358
...
, Above 512MiB (Request 2)
Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 1
...

低于512MiB的第一次接见约莫需要1秒钟,这是由于仍然需要向服务器发出请求,而且服务器是使用.NET编写的,因此运行新代码的启动时间可能会很慢。第二个请求破费的时间远远少于1秒,由于此时内存已经内陆缓存,以是不需要任何请求。

对于512MiB以上的接见,第一个请求约莫需要10秒,这与增添的延迟相关。第二个请求不到1秒钟,由于该页面现在已经内陆缓存。这正是我们所期望的,而且证实我们可以至少延迟10秒钟。现实上,可以在强制重置毗邻之前将请求延迟至少60秒。这基于SMB客户端的会话超时。可以在PowerShell中使用以下下令查询SMB客户端超时:

PS> (Get-SmbClientConfiguration).SessionTimeout
60

测试历程中,需要注重几点。首先,客户端或Windows缓存管理器似乎能对远程文件举行一些缓存。若是在打开文件时请求特定的接见权限,例如GENERIC_READ | GENERIC_WRITE用于所需的接见,然后启用缓存。这意味着,若是读取请求先于在内陆缓存,就不会发送到服务器。然则,若是我们为所需的接见指定了MAXIMUM_ALLOWED,似乎就不会举行缓存。其次,有时文件的某些部门会被预先缓存,例如文件的第一个和最后一个32KiB。我还没弄清楚这是什么缘故原由,奇怪的是,内陆代码似乎比.NET代码发生这种情形的概率更高,也许是由于Windows Defender可能正在监测内存,另有可能是Superfetch。通常情形下,只要将内存接见定位在一个大文件中心的某个位置,应该就是平安的。

我们在运行示例代码后,可能会注重到一个问题,在内陆运行示例服务器失败,并显示以下错误:

System.Net.Sockets.SocketException (10013): An attempt was made to access a socket in a way forbidden by its access permissions.

默认情形下,Windows 10启用了SMB服务器。这将接受TCP端口并独占这一端口,因此普通用户无法绑定到这些端口。可以禁用内陆SMB服务器,但这需要管理员权限。只管云云,若是我们必须与远程服务器举行通讯,照样需要验证SMB服务器方式是否能起作用。

我还试图研究了一些技巧,希望能使内置SMB服务器正常事情。例如,我实验设置机遇锁来捕捉文件读取。我行使这个技巧来实现LUAFV驱动程序中的TOCTOU破绽行使。遗憾的是,SMB服务器会检测到这个文件已经处于锁定状态,并守候机遇锁中止,然后才允许接见该文件。这样一来,这种方式就不可行了。

,

Usdt第三方支付接口

菜宝钱包(caibao.it)是使用TRC-20协议的Usdt第三方支付平台,Usdt收款平台、Usdt自动充提平台、usdt跑分平台。免费提供入金通道、Usdt钱包支付接口、Usdt自动充值接口、Usdt无需实名寄售回收。菜宝Usdt钱包一键生成Usdt钱包、一键调用API接口、一键无实名出售Usdt。

,

为了举行测试,可以禁用LanmanServer服务及响应驱动程序。若是要在随便系统上禁用它,险些可以确定需要毗邻到远程服务器。我还公开了示例服务器的代码,只管这只是一个演示程序,但完全可以复现。它允许读取内陆页面巨细的粒度(假设为4KiB)。服务器代码理论上在Linux上可以使用,但从NuGet的SMBLibrary 1.4.3版本最先,存在一个Bug,该Bug会导致服务器失足。GitHub上有一个修复程序,但在撰写本文时,还没有公布更新后的补丁包。

那么,这种SMB客户端滥用的方式是否能知足我们之前的条件呢。事实上,在7条中已经知足了5条。

(1)适用于Windows 10 20H2默认安装版本操作系统(相符);

(2)在读取或写入内存时可以发出一个明确的信号(相符);

(3)在从用户模式和内核模式接见内存时都可以行使(相符);

(4)允许无限期延迟内存的接见(相符);

(5)接见的内存中的数据是随便的(相符);

(6)可以从一系列特权级别中设置原语;

(7)可以在统一破绽行使历程中多次捕捉。

使用SMB客户端确实相符我们的大多数条件。我同时验证了内核模式和用户模式,二者都可以捕捉内存。最大的问题在于,难以从沙箱化应用程序中使用。这是由于在默认情形下,MUP限制来自受限制的低IL历程的远程文件系统接见,而且AppContainer沙箱需要特定的功效,而这些功效不太可能授予大多数应用程序。并不是说完全不适用沙箱应用程序,但确实很难做到。

只管我们的技巧没有真正地无限期延迟内存读取,但就我们的场景来说,60秒的时间对于大多数破绽行使来说已经足够。同样,一旦激活了捕捉,我们就无法强制内存管理器从服务器请求统一页面。我实验过使用内存缓存标志和直接IO,但至少对于SMB上的文件似乎没有任何作用。不外,可以在映射文件时指定自己的基址,以便通过作废映射原始文件并映射到新副本以将文件中的差别偏移量映射到相同的虚拟地址。这样一来,就可以多次使用相同的地址。

3.2 WebDAV

既然SMB无法在内陆轻松使用,那么WebDAV呢?默认情形下,Windows 10不会启用TCP 80端口,因此我们可以启动自己的Web服务器举行通讯。另外,与Linux差别的一点是,不需要管理员权限来绑定到编号为1024以下的TCP端口。纵然没有这两个优势,WebDAV客户端也支持制订服务器TCP端口的语法。例如,若是使用路径 \\localhost@8080\share ,就可以通过8080端口确立WebDAV HTTP毗邻。

然则,WebDAV客户端是否公开了合适的读取和写入原语,让我们可以捕捉内存接见呢?我使用NWebDav库编写了一个简朴的WebDAV服务器,以用于内陆文件。运行剧本,在8080端口上指定WebDAV服务器打开1GiB文件,立刻就泛起了报错:

Get-NtFile : (0xC0000904) - The file size exceeds the limit allowed and cannot be saved.

仅仅打开文件的历程就泛起了错误,错误代码为STATUS_FILE_TOO_LARGE。我们可以在多个Microsoft知识库文章中找到其缘故原由。WebDAV共享中接见任何文件的默认巨细限制为50MB(十进制兆字节),由于以前若是让Windows系统下载随便巨细的文件可能会导致拒绝服务。

这样的巨细限制,导致WebDAV不再适用于我们的攻击场景。若是将文件巨细调整为50MB以下,则WebDAV客户端会在从文件打开挪用返回之前,就将整个文件拉取到内陆磁盘。然后,将该文件作为内陆文件映射到内存中。WebDAV服务器永远不会收到同步读取/写入内存映射的GET或PUT请求,因此没有检测或捕捉特定内存请求的机制。

0x04 文件系统笼罩API

我们可以滥用SMB客户端,然则默认安装情形下不适用于内陆。因此,我决议找寻另一种方式。在研究Windows筛选器驱动程序时,我注重到有一些驱动程序提供了一种在现有文件系统上笼罩另一个文件系统的机制。借助查阅MSDN并查看API文档,最终找到了3个对照适合的,详细如下:

· 投影文件系统(Projected File System),支持Windows 10 1809,默认不启用;

· Windows Overlay(WOF),支持所有版本,默认启用;

· 云文件API,支持Windows 10 1709,默认启用(非桌面服务器SKU除外)。

现在,最值得关注的就是投影文件系统。这是由Windows开发的,旨在为GIT提供虚拟文件系统。它允许将占位符文件“投影”到磁盘目录中,而且凭证需要将这些文件的内容重新回复成完整文件。理论上看,这听起来异常理想,只要能够琐屑地填充文件的内容,我们就可以在吸收PRJ_GET_FILE_DATA_CB回调时添加延迟。

然则,基于微软ProjectedFileSystem示例代码的基础实现会始终在文件打开时代回复整个文件,类似于WebDAV。也许我错过了某个地方,可以设置为流式传输内容,而不是一口气填充它,然则我现在没能找到。除了这个因素之外,默认情形下系统中也不会安装投影文件系统,这样一来它就没有太大的现实作用。

Windows Overlay(WOF)现实上不允许我们实现自己的文件系统语义。相反,它允许我们从辅助Windows映像文件(WIM)笼罩文件,或将其压缩到统一个卷上。这确实无法为我们提供所需的控制功效,我们也许可以行使它,但照样需要支出许多起劲才行。

最后一个就是云文件API。OneDrive提供了内陆在线文件系统,可以用来实现任何文件系统笼罩。其事情原理与投影文件系统异常相似,包罗文件占位符和按需对文件举行回复的观点。文件的内容不需要来源于任何在线服务(例如OneDrive),它们都可以在内陆获取。至关重要的是,在我们举行了一些基本的测试之后,发现它支持凭证正在读取的内容流式传输文件的内容,而且可以延迟文件数据请求,此时读取线程将被壅闭,直到知足读取条件为止。在设置基础同步根目录时,可以界说CF_HYDRATION_POLICY_PRIMARY计谋为CF_HYDRATION_POLICY_PARTIAL值以启用这一功效。这样一来,云文件API只能合并被接见的文件部门。

这似乎异常完善,但我在使用PowerShell文件映射剧本举行测试时,发现没有凭证预期举行事情,始终会要求我的云文件提供者提供完整的文件。我们检查云筛选器驱动程序,发现在收到映射占位符文件的请求时,IRP_MJ_ACQUIRE_FOR_SECTION_SYNCHRONIZATION处置程序会在完成之前对文件举行完整地回复。若是文件没有完整回复,那么对NtCreateSection的挪用将永远不会返回,这就会阻止文件映射到内存中。

进一步对过滤器举行研究的历程中,我意识到,可以将SMB客户端环回与云筛选器API连系起来。我们已经知道,SMB客户端并没有真正映射文件,纵然对于内陆也是云云,现实上是在通过SMB协议按需读取文件。而且我们也知道,只要文件没有映射到内存中,云筛选器API就能够按需对文件的一部门举行流传输。因此,我们采用了下图的部署:

要使用这个原语,我们首先使用CfRegisterSyncRoot API来设置计谋,注册同步根目录,从而设置我们的云服务商。然后,可以使用CfCreatePlaceholders在目录中建立1GiB的占位符。此时,在磁盘上还没有任何文件内容。若是现在我们通过SMB环回客户端打开并映射占位符文件,那么这个文件不会立刻被还原。

对映射的任何内存举行接见,都将导致SMB客户端请求一个32KiB块,该请求将通报给我们的用户模式云服务商,我们可以凭证需要举行检测或延迟。毫无疑问,文件的内容也可以是随便的。凭证测试,似乎不能像实现自界说SMB服务器时那样,将读取粒度强制削减到内陆页面巨细,然则仍然可以在大页面的巨细限制内,以内陆页面巨细界限举行请求。这样可能能够修改文件巨细,以诱骗SMB服务器举行较短的读取。在这里提供了云服务商的示例实现。

0x05 用例

现在,我们已经获得了一种行使方式,可以让我们捕捉和延迟虚拟内存的读写操作。最大的问题在于,这是否能改善对double-fetch等破绽的行使历程?其谜底取决于现实的破绽。首先要提醒的是,我在这里所说的“页面”,是指引起对SMB服务器的请求的内存单元(例如32KiB),而不是内陆页面巨细(例如4KiB)。

我们以本文开头给出的示例为例。这个破绽从统一个内存地址lpInputPtr读取两次其中的值。第一次是用于对照,第二次是用于复制。这样的破绽行使所具有的局限性在于,内存捕捉只有一次机遇。若是在读取巨细以举行对照的历程中被捕捉,我们就可以实现无限的延迟。然则,一旦提供了请求的内存页面,而且恢复了失足的线程,就不会再在第二次读取时再次捕捉,仅仅会从内存中读取,就犹如没有事情发生过一样。

这时不禁要问,在检测到第一次读取时,是否可以重新映射内存页面?遗憾的是,这样不行。在恢复线程后,它将在错误指令处重新启动,并再次执行读取,因此会发生以下的情形:

从图中可以看出,最终陷入了无限循环,由于重新映射了一个新页面,这正好出发了另一个页错误。若是不执行步骤③,那么操作可以完成,而且在恢复线程、读取当前内存以举行对照和第二次读取之间存在一个时间窗口期。然则,在这个示例中,窗口期可能是几条指令的执行时间,因此使用我们改善后的技巧也许不会比原来的破绽行使方式更优。换而言之,我们的优势仅仅在于得知了什么时间发生读取,这样就可以更准确地瞄准这个窗口期。

这个例子只是最不理想的情形,在两次读取之间也可能会有更多的时间。例如,Bochspwn的研究中就给出了另一个示例:

PDWORD BufferSize = // controlled user-mode address
PUCHAR BufferPtr  = // controlled user-mode address
PUCHAR LocalBuffer;
 
LocalBuffer = ExAllocatePool(PagedPool, *BufferSize);①
if (LocalBuffer != NULL) {
  RtlCopyMemory(LocalBuffer, BufferPtr, *BufferSize);②
} else {
  // bail out
}

这里存在相同的double-fetch行为,然则差别之处在于将值通报给了另一个函数(在本例中为ExAllocatePool,该函数分配内核内存)。这种情形中,①和②之间可能会有相当长的时间延迟,详细要取决于当前的内存设置或请求的分配量。那么,我们有什么设施可以赢得竞争?

首先,这一定不能100%乐成。然则我们可以行使一种行为来实验稍微同步一下读取和写入线程。我们回忆之前,为了写入未剖析的页面,必须首先从服务器读取页面的内容。因此,为了保持一致性,任何线程写入未剖析的页面都必须发生页错误,而且与另一个刚从该页面读取的线程都守候相同的锁,如下图所示:

通过同步读取和写入的线程,我们可以有一定的机遇在破绽行使时代发生写入操作。但这仍然是一个概率问题,取决于调剂程序。例如,写入线程可能在读取线程之前被叫醒,这样会导致指针取到的是最终值。另有可能,读取线程会在写入线程设计运行之前完成运行,从而导致值不会更改。这里,可能另有一些跟调剂程序有关的技巧,例如使用多个读取或写入线程,或者选择适当的优先级,从而行使这些优先级来保证读写顺序。也迎接人人对若何提高可靠性提出更好的想法。

我们可能还会想到一种方式,就是未对齐的接见,将值分成两个单独的页面。从微体系结构的角度来看,读取的内容很可能会分为两部门,先读取一页,然后读取另一页。然则,我们回首页错误的事情原理,它会天生一个异常,从而导致处置程序在内核中执行。此时,当内核处置页错误时,指令已经完成的所有事情都市被镌汰。在恢复线程后,它将重新启动失足的指令,该指令将重新发出适当的微操作,以从未对齐的地址举行读取。除非编译器为未对齐的接见天生了两次加载(在某些体系结构上可能会发生这种情形),否则将无法重新启动整个历程中的内存接见指令。

似乎我们实验了许多方式,都只能略微提高破绽行使技巧的可用性。事实上,破绽行使的类型与海里的鱼一样多。例如,我们可以将原始示例修改如下:

PDWORD lpInputPtr = // controlled user-mode address
UCHAR  LocalBuffer[256];
 
if (lpInputPtr[0] > sizeof(LocalBuffer) || lpInputPtr[1] != 2) {
  return STATUS_INVALID_PARAMETER;
}
RtlCopyMemory(LocalBuffer, lpInputPtr, *lpInputPtr);

现在,检查历程会确保缓冲区足够大,而且缓冲区的第二个DWORD没有设置为2。第二个字段可能代表缓冲区类型,而且类型2对这个请求无效。若是检查这段代码的编译器输出,会发现它与内陆代码存在2-3条指令的差异。从概率上来说,这似乎并没有从根本上提高赢得TOCTOU竞争的概率。然则,借助我们的破绽行使技巧,现在就可以构建一个具有确定性的破绽行使。

上图展示了若何实现这种具有确定性的破绽行使。只管缓冲区在虚拟内存中仍然是延续的,然则我们可以将Size字段放置在与输入缓冲区其余部门差别的页面上。第一页(N-1)应该已经被故障转移到内存中,而且包罗小于LocalBuffer巨细的Size字段。我们就可以让巨细①的读取正常完成。

接下来,代码将读取第N页上的Type字段②。该页面当前不在内存中,因此在接见该页面时会发生页错误③。这要求内核从文件中读取内容,我们可以检测和延迟。当检测到读取后,只要修改Size字段,使其包罗一个大于LocalBuffer巨细的值④。最终,我们完成读取,这会在Type字段读取指令重新启动线程⑤。这段代码可以继续,而且会由于读取过大的Size字段导致内存损坏。

这里的要点在于,若是在两次读取(double-fetch)之间,代码接触了我们控制的任何用户模式内存,而非double-fetch的内存,应该就有可能将其转换为具有确定性的破绽行使。在这种情形下,目的系统有几个CPU、内核中的调剂算法是什么、double-fetch之间有若干条指令等因素都无关紧要,破绽行使都可以正常事情。

在double-fetch的后续文章中,提供了一些破绽行使的相关数据。到现在为止,凭证上述示例,若是选择了准确的时间窗口期,在几秒之后乐成的概率可以到达100%。对于统一个破绽的某些类型,我们可以获得100%的可靠性,然则除了可靠性之外,与该文章相比,其他方面没有太多的改善。

到现在为止,所有的示例都只展示了竞争条件的行使。然则在文章中还提到了第二类破绽,也就是二进制竞争,这种方式很难被行使,而且从未获得过100%的乐成。我们来看看文章中的示例,看看我们的破绽行使技巧是否更好一些。

PVOID* UserPointer = // controlled user-mode address
__try {
   ProbeForWrite(*UserPointer, sizeof(STRUCTURE), 1);①
   RtlCopyMemory(*UserPointer, LocalPointer, sizeof(STRUCTURE));②
} __except {
   return GetExceptionCode();
}

从表面上看,这与前面的示例并没有太大的差别,然则在这种情形下,目的指针被更改,而不是巨细。用于检查指针的ProbeForWrite内核API位于用户模式地址,而且内存是可写的。这是验证用户提供的指针未指向内核内存的常用方式。

若是在①和②之间指针值从用户模式地址改为了内核模式地址,则该示例可以笼罩内核内存。由于仅存在两个有用的指针值,即用户模式地址或内核模式地址,因此具有概率性的破绽行使会更难一些。若是我们暴力破解指针值,纵然在两次读取之间将其更改为内核指针,也有可能最终两次都读取的是用户模式指针。

幸运的是,由于挪用了ProbeForWrite,若是可以捕捉用户内存接见,则如下图所示:

首先读取UserPointer①并将效果指针值通报给ProbeForWrite。ProbeForWrite API检查指针是否位于用户模式地址空间中,然后探查内存的每个页面,直到长度参数的巨细②。若是页面无效或不可写,则会天生异常,并由示例的__except块捕捉。这一历程为我们提供了破绽行使的机遇,我们可以在要探测的用户模式页面上行使这个技巧,导致ProbeForWrite天生我们可以捕捉的页错误③。然则,由于所探查的地址与存储指针的地址差别,我们可以在捕捉请求的同时将其修改为包罗内核模式的地址④。这样一来,我们就可以确定赢得竞争。

固然,我一直关注内核double-fetch,由于它最初让我去寻找这样的行为。在许多情形下,这种方式都可以用于用户模式应用程序的破绽行使。最显著的一种场景就是服务与特权较低的应用程序共享内存。其中的一个示例就是DfMarshal COM封送处置程序中存在的double-fetch。COM封送处置程序在历程之间共享一个内存段,因此可以使用这里的破绽行使技巧。但这个技巧并不是必须的,由于易受攻击代码的逻辑可以让我们建立一个无限循环来获得double-fetch窗口期。但若是这种方式行不通,在代码处于可以切换句柄的位置时,我们就可以行使这个技巧来检测和延迟。

另一个更巧妙的用途是历程特权从特权较低的历程读取内存。这可能要用到显式使用的API,例如ReadProcessMemory,也可能是间接的,例如使用NtQueryInformationProcess查询历程的下令行就可以读出我们控制的内存位置。

使用这个破绽行使技巧时需要注重的是,它可以用来增添窗口期以赢得时间的竞争。在这种情形下,它与我之前关于OpLock的研究类似,然则用于内存接见。现实上,对内存的接见可能是易受攻击的代码附带的,它纷歧定是内存double-fetch,也纷歧定是TOCTOU破绽。举例来说,我们可能赢得带有符号毗邻的两个文件路径之间的竞争。只要可以让存在破绽的代码探测我们控制的用户模式地址,就可以将其作为准时信号,并扩大破绽行使的局限。

0x06 总结

在这里,我已经形貌了基于SMB和云文件API的一种破绽行使技巧,它可以辅助证实对某些类型的应用程序和内核破绽的行使。也许另有其他方式能够借助我未曾使用过的API到达类似的效果,然则就现在而言,这是我能想到的最好方式。它可以让我们捕捉来自用户模式内存的读取,检测接见发生的时间,并将读取延迟至少60秒。在这里,提供了SMB和云文件API破绽行使技巧的代码示例。

在得出结论前,我需要再强调一下这个破绽行使技巧的局限性。

(1)不能在沙箱中使用,只能以普通用户权限举行。

(2)从文件映射的任何页面仅有一次机遇。若是任何其他事物(例如反病毒软件)试图读取该页面或从文件中读取,都有可能会提早触发捕捉。

(3)无法检测到读取的确切位置,仅限于4KiB的粒度。对于通过云文件API举行的内陆接见,始终会填充接下来的7页和32KiB读取的一部门。若是接见自界说SMB服务器,则读取巨细可以减小为4KiB。为防止某些破绽的行使,这些破绽仅需要在较大结构内的一小部门区域内举行准确捕捉。

(4)只能间接检测写入,不能专门捕捉写入。

从现实角度开栏,这里先容的技巧并不能显著提高Bochspwn论文中形貌的传统double-fetch的乐成率。现实上,对于大多数这样的破绽,我们可能还想要使用一种概率更高的方式。然则,该技巧适用于其他种别的破绽,在这些种别中,都可以获得一个确定的准时信号。

这种破绽行使技巧仅有一次机遇,这也导致针对简朴的double-fetch代码路径举行破绽行使的历程中可能无法获得对照大的收益。同样,更庞大的代码在接见存在破绽的代码之前可能会多次读取和写入内存地址,这可能会使触发信号变得加倍难题。

本文翻译自:https://googleprojectzero.blogspot.com/2021/01/windows-exploitation-tricks-trapping.html Allbet Gaming声明:该文看法仅代表作者自己,与阳光在线无关。转载请注明:usdt钱包(www.caibao.it):Windows破绽行使技巧:限制虚拟内存接见
发布评论

分享到:

太平洋在线(9cx.net):香港限制社交距离措施将延伸一周
1 条回复
  1. 皇冠即时比分
    皇冠即时比分
    (2021-04-26 00:00:05) 1#

    Usdt自动充值接口菜宝钱包(www.caibao.it)是使用TRC-20协议的Usdt第三方支付平台,Usdt收款平台、Usdt自动充提平台、usdt跑分平台。免费提供入金通道、Usdt钱包支付接口、Usdt自动充值接口、Usdt无需实名寄售回收。菜宝Usdt钱包一键生成Usdt钱包、一键调用API接口、一键无实名出售Usdt。小花花送给你

发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。