NTDS.dit (Microsoft Active Directory) forensics module for Powershell.

Find it on Github here.

Current bug: Unencrypted NT hashes are incorrect!

This Powershell module is intended to aid in the process of examining and extracting information from the Active Directory database known as NTDS.dit.

Special thanks to Csaba Barta (, without whom this module would not be possible.

Also thanks to @moyix,,

Also thanks to Brendan Dolan-Gavitt, author of creddump

Q: So why did I reinvent the wheel?

A: Because:

  1. Python shouldn't have all the fun. I wanted to port the ideas into idiomatic Powershell.
  2. I wanted to gain in-depth knowledge of the subject for myself, not just run someone else's scripts.
  3. I have further plans with this data and it will be very handy to have it in Powershell format.

Currently exported Cmdlets:


A couple of screenshots:

alt tag

alt tag

alt tag

The Basics of DLL Injection Part I

Hello... sorry for not posting in a while.  I've been going through some recent career changes and haven't really come across any IT-related inspiration lately.  I thought I'd post a simple, bare-bones technique of injecting a DLL into a running process on a Windows computer.  It's really simple.

  1. Verify that the PID supplied is valid.
  2. Get a handle to the remote process.
  3. Allocate memory in the remote process.
  4. Write the DLL path into the remote process's memory.
  5. Call CreateRemoteThread to induce LoadLibrary in the remote process. 

As long as the process does not already have the DLL loaded, the remote process will automatically call DllMain (the injected DLL's entry point,) which can contain all sorts of fun code.  For instance, now that you have injected a DLL into the remote process, you are well on your way to performing something like API hooking, for example. You could modify the Import Address Table of the remote process... but I'm getting ahead of myself.  First things first.  Without further ado:

// InjectDLL.cpp
// Joseph Ryan Ries - 2015
// Injects a DLL into another process on the system.


// Returns true if the supplied file path exists and is a file.
// Returns false if the supplied path does not exist, or is a directory.
BOOL FileExists(LPCTSTR FilePath)
	DWORD FileAttributes = GetFileAttributes(FilePath);

	return (FileAttributes != INVALID_FILE_ATTRIBUTES && !(FileAttributes & FILE_ATTRIBUTE_DIRECTORY));

// Entry point. Returns 0 at the end if everything was successful.
// Returns 1 if something failed. Outputs messages to console.
int wmain(int argc, wchar_t *argv[])
	if (argc != 3)
		wprintf_s(L"\nUsage: %s C:\\Temp\\MyDLL.dll 1337\n", argv[0]);
		wprintf_s(L"\nUse the full path to the DLL you want to inject and supply the\n");
		wprintf_s(L"process ID (PID) of the process you want to inject it in to.\n");

	if (!FileExists(argv[1]))
		wprintf_s(L"\nERROR: Unable to find file %s.\n", argv[1]);

	wchar_t DLLPath[MAX_PATH] = { 0 };

	wcscpy_s(DLLPath, argv[1]);

	DWORD Pid = _wtoi(argv[2]);

	if (Pid == 0)
		wprintf_s(L"\nERROR: Unable to interpret the supplied process ID.\n");

	wprintf_s(L"Searching for PID %d.\n", Pid);

	DWORD CurrentProcesses[2048] = { 0 };
	DWORD ProcessListSize = 0;
	DWORD ProcessCount = 0;

	if (EnumProcesses(CurrentProcesses, sizeof(CurrentProcesses), &ProcessListSize) == 0)
		wprintf_s(L"\nERROR: Unable to enumerate currently running processes!\nLastError: 0x%x.\n", GetLastError());

	ProcessCount = ProcessListSize / sizeof(DWORD);

	wprintf_s(L"%d processes currently on the system.\n", ProcessCount - 1);

	BOOL ProcessFound = FALSE;

	for (unsigned int ProcessCounter = 0; ProcessCounter < ProcessCount; ProcessCounter++)
		if (CurrentProcesses[ProcessCounter] == 0)
		if (CurrentProcesses[ProcessCounter] == Pid)
			ProcessFound = TRUE;

	if (ProcessFound == FALSE)
		wprintf_s(L"\nERROR: A process with PID %d could not be found.\n", Pid);

	HANDLE ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);

	if (ProcessHandle == NULL)
		wprintf_s(L"\nERROR: Unable to open handle to process %d. LastError 0x%x.\n", Pid, GetLastError());

	wprintf_s(L"Process with PID %d successfully opened.\n", Pid);

	void* RemoteDLLPathMemory = NULL;		// The DLL entry address in the remote process

	// VirtualAlloc will probably not give us less than 4k. Whatever. We only need ~520 bytes for MAX_PATH * 2.
	RemoteDLLPathMemory = VirtualAllocEx(ProcessHandle, NULL, (MAX_PATH * sizeof(wchar_t)), MEM_COMMIT, PAGE_READWRITE);

	if (RemoteDLLPathMemory == NULL)
		wprintf_s(L"\nERROR: Unable to allocate memory in remote process %d. LastError: 0x%x.\n", Pid, GetLastError());

	wprintf_s(L"Memory allocated in process %d.\n", Pid);

	SIZE_T BytesWritten = 0;

	if (WriteProcessMemory(ProcessHandle, RemoteDLLPathMemory, (void*)DLLPath, sizeof(DLLPath), &BytesWritten) == 0)
		wprintf_s(L"\nERROR: Unable to write DLL path into remote process memory. LastError: 0x%x.\n", GetLastError());

	wprintf_s(L"%I64d bytes written into the memory of process %d.\n", BytesWritten, Pid);
	HANDLE ThreadHandle = CreateRemoteThread(
		(LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW"),

	if (ThreadHandle == NULL)
		wprintf_s(L"\nERROR: Unable to start remote thread in process %d. LastError: 0x%x.\n", Pid, GetLastError());

	wprintf_s(L"Successfully loaded %s into process %d.\n", DLLPath, Pid);

	wprintf_s(L"Note: DllMain will only run if the DLL has not already been loaded by the process.\n");

Ignore the very last line (</psapi.h></windows.h></stdio.h>), the code editor wigged out.

UNC Hardening

A couple months ago, Microsoft published a couple of Windows patches to address some vulnerabilities found in the way that Windows machines access UNC paths over the network.



Guidance on Deployment of MS15-011 and MS15-014 by AskPFE Platforms

This is essentially another man-in-the-middle style SMB hijack, and these types of attacks have been well-known for a long time, maybe second only behind pass the hash stuff.  One of the countermeasures that we admins have had for years to help combat these sorts of SMB proxy attacks, is SMB signing:

Of course I'd recommend enabling this everywhere - on both domain controllers and domain members - but that's no longer quite enough.  Security researchers found a way of bypassing or disabling SMB signing, which is what prompted Microsoft to release those two security patches I mentioned above.  One of those hotfixes comes with a new Group Policy configuration setting, called UNC Hardening.

You can find this new setting in Computer Configuration > Policies > Administrative Templates > Network > Network Provider:

So keep in mind that just applying the patch alone doesn't award you any of the benefits of Hardened UNC Paths.  There is additional GPO configuration you must do to enable it.

In the GPO, an admin would specify the types of UNCs that he or she wanted to harden, so that when a client connects to a UNC that matches a certain pattern, that client applies additional security policies to that connection.

Wildcards are supported, but you must supply either a server name or share name, so no, you cannot do \\* or \\*\*.

To get the two most important UNC paths in an Active Directory domain, you'd configure the GPO thusly:

\\*\NETLOGON  RequireMutualAuthentication=1, RequireIntegrity=1
\\*\SYSVOL    RequireMutualAuthentication=1, RequireIntegrity=1

This additional layer of security costs very little, relative to the benefit of ensuring all your Windows clients will only connect to genuine, mutually authenticated domain controllers to get their Group Policies and logon scripts.  Especially if you have mobile clients on the go that connect from coffee shops and hotels!

Maintain Your Directory Services Restore Mode (DSRM) Password

On your domain controllers, there is an account called the Directory Services Restore Mode account.  This account is quite special - it's not an Active Directory account.  It's a local account, that is isolated to that one domain controller, which means each DC has its own DSRM account with its own unique password.  (You use ntdsutil.exe to change this password.) This comes as a surprise to some people, as they might have thought that Active Directory domain controllers don't have any local accounts.  Well, they have this one.  As the name implies, you would only find yourself using the DSRM account if things have really gone off the rails.  In a disaster recovery scenario, basically.  But when that day comes, you really don't want to have forgotten what the password to that DSRM account is.

What if you have 50 domain controllers in your AD forest?  That means you have to keep up with 50 different DSRM passwords.  And what if your company has a security policy that requires you to change those passwords on a regular basis?

Alright, it's time to automate this.  We have better things to do than sit around changing passwords by hand all day.

So first off, create a new domain user in AD.  Disable the account so that it cannot be logged on to, and name the account something like "DSRM".  Set the account's password to something strong that you will remember and/or have recorded.

Next, create a new Group Policy Object, and link it to the Domain Controllers OU.  You want this policy to apply to all your domain controllers.  Edit the GPO, drill down to Computer Configuration > Preferences > Control Panel Settings > Scheduled Tasks, and create a new Scheduled Task to be run on all your domain controllers at a regular interval (like, say, once a week.)

This scheduled task will run as the "SYSTEM" account.  For the action you want to run this command:

C:\Windows\System32\ntdsutil.exe "SET DSRM PASSWORD" "SYNC FROM DOMAIN ACCOUNT Dsrm" Q Q

This command will synchronize the local DSRM password on the domain controller to match the password of the "Dsrm" user account in Active Directory.

This means when it comes time to change the DSRM password, you only need to change it once, and that scheduled task will automatically disseminate it to all your DCs, no matter how many DCs you have.