Skip to content

Memory Operations

Ido Veltzman edited this page Jan 30, 2024 · 9 revisions

Function Patching

Patching a function inside a usermode module, can either use built in patches for AMSI and ETW or your own.

Function Signature

NTSTATUS MemoryUtils::PatchModule(PatchedModule* ModuleInformation)

ModuleInformation [PatchedModule*] -- Pointer to a PatchedModule structure that contains information about the module to be patched.

Usage Example

# Patching AMSI
NidhoggClient.exe patch <PID> amsi

# Patching ETW
NidhoggClient.exe patch <PID> etw

# Patch arbitrary module
NidhoggClient.exe patch <PID> <Module Name> <Function> <Patch comma separated>

How It Works

The function begins by copying the module name and function name from the ModuleInformation structure to local variables.

Next, it looks up the target process using the process ID from the ModuleInformation structure.

The function then attaches to the target process and retrieves the base address of the module to be patched.

If the module base address is successfully retrieved, the function gets the address of the function to be patched within the module.

Finally, the function patches the module by writing the patch data from the ModuleInformation structure to the function address in the target process, and then detaches from the target process.

If any of the steps fail, the function returns an error status. Otherwise, it returns the status of the patch operation.


Module Hiding

Hide usermode module from PEB of a process and from the VAD tree.

Function Signature

NTSTATUS MemoryUtils::HideModule(HiddenModuleInformation* ModuleInformation)

ModuleInformation [HiddenModuleInformation*] -- Pointer to a HiddenModuleInformation structure that contains the process ID and the name of the module to be hidden.

Usage Example

NidhoggClient.exe module hide <PID> <PATH>

How It Works

The function begins by allocating memory for the module name and copying the module name from the ModuleInformation structure.

Next, it looks up the target process using the process ID from the ModuleInformation structure.

The function then attaches to the target process and retrieves the PEB of the target process.

If the PEB is not found, the function detaches from the target process, dereferences the target process, and returns STATUS_ABANDONED.

The function then waits for the loader data in the PEB to be available. If the loader data is not available after 10 attempts, the function detaches from the target process, dereferences the target process, and returns STATUS_ABANDONED_WAIT_0.

The function then iterates over the InLoadOrderModuleList in the loader data. For each module in the list, it checks if the module name matches the module name to be hidden. If a match is found, it removes the module from the InLoadOrderModuleList, InInitializationOrderLinks, InMemoryOrderLinks, and HashLinks, and sets the status to STATUS_SUCCESS.

The function then detaches from the target process.

If the module was successfully found and removed, the function hides the module in the VAD tree of the target process.

Finally, the function dereferences the target process and returns the status of the operation.


Shellcode Injection

Injecting a shellcode to usermode process via either APC or remote thread.

Function Signature

NTSTATUS MemoryUtils::InjectShellcodeAPC(ShellcodeInformation* ShellcodeInfo)

ShellcodeInfo [ShellcodeInformation*] -- Pointer to a ShellcodeInformation structure that contains all the information regarding the injected shellcode.
NTSTATUS MemoryUtils::InjectShellcodeThread(ShellcodeInformation* ShellcodeInfo)

ShellcodeInfo [ShellcodeInformation*] -- Pointer to a ShellcodeInformation structure that contains all the information regarding the injected shellcode.

Usage Example

# Inject shellcode with remote thread
NidhoggClient.exe shinject thread <PID> <PATH>

# Inject shellcode with APC
NidhoggClient.exe shinject apc <PID> <PATH>

How It Works

APC Injection

The function begins by looking up the target process using the process ID from the ShellcodeInfo structure.

Next, it finds an APC suitable thread (alertable thread, non GUI) in the target process.

The function then opens the target process and allocates memory for the shellcode.

If the memory is successfully allocated, the function writes the shellcode to the allocated memory in the target process.

The function then creates two APCs: a preparation APC and the shellcode APC. The preparation APC is used to prepare the target thread for the shellcode APC.

The function then inserts the shellcode APC and the preparation APC into the APC queue of the target thread.

If the target thread is terminating, the function sets the status to STATUS_THREAD_IS_TERMINATING.

Finally, the function cleans up by freeing any allocated memory and closing any opened handles, and then returns the status of the operation.

Thread Injection

The function begins by looking up the target process using the process ID from the ShellcodeInfo structure.

Next, it opens the target process and allocates memory for the shellcode.

If the memory is successfully allocated, the function writes the shellcode to the allocated memory in the target process.

The function then changes the previous mode to KernelMode and creates a new thread in the target process using NtCreateThreadEx, after the function is executed the previous mode is returned to the original value. The start routine of the new thread is the address of the injected shellcode.

If the thread is successfully created, the function closes the thread handle.

If the shellcode injection fails at any point, the function frees the allocated memory and closes any opened handles.

Finally, the function dereferences the target process and returns the status of the operation.


DLL Injection

Injecting a DLL to usermode process via either APC or remote thread.

Function Signature

NTSTATUS MemoryUtils::InjectDllAPC(DllInformation* DllInfo)

DllInfo [DllInformation*] -- Pointer to a DllInformation structure that contains all the information regarding the injected DLL.
NTSTATUS MemoryUtils::InjectDllThread(DllInformation* DllInfo)

DllInfo [DllInformation*] -- Pointer to a DllInformation structure that contains all the information regarding the injected DLL.

Usage Example

# Inject DLL with remote thread
NidhoggClient.exe dllinject thread <PID> <PATH>

$ Inject DLL with APC
NidhoggClient.exe dllinject apc <PID> <PATH>

How It Works

APC Injection

The APC injection works like the shellcode APC injection just with a template shellcode that loads then DLL into the process.

Thread Injection

The function begins by looking up the target process using the process ID from the DllInfo structure.

Next, it attaches to the target process and retrieves the base address of kernel32.dll within the process.

If the base address is successfully retrieved, the function gets the address of the LoadLibraryA function within kernel32.dll.

The function then opens the target process and allocates memory for the DLL path.

If the memory is successfully allocated, the function writes the DLL path to the allocated memory in the target process.

The function then changes the previous mode to KernelMode and creates a new thread in the target process using NtCreateThreadEx, after the function is executed the previous mode is returned to the original value. The start routine of the new thread is the address of the LoadLibraryA function, and the parameter is the address of the DLL path in the target process.

If the thread is successfully created, the function closes the thread handle.

If the DLL injection fails at any point, the function frees the allocated memory, closes any opened handles, and dereferences the target process.

Finally, the function returns the status of the operation.


Driver Hiding

Hiding the driver by removing it from the PsLoadedModuleList.

Function Signature

NTSTATUS MemoryUtils::HideDriver(HiddenDriverInformation* DriverInformation)

DriverInformation [HiddenDriverInformation*] -- Pointer to a HiddenDriverInformation structure that contains the driver's information.

Usage Example

# Hide a driver
NidhoggClient.exe driver hide <PATH>

# Unhide a driver
NidhoggClient.exe driver unhide <PATH>

How It Works

The function begins by acquiring an exclusive lock on the PsLoadedModuleResource to prevent other threads from modifying it while it is being read.

Next, it iterates over the PsLoadedModuleList which contains all the loaded modules in the system. For each module in the list, it checks if the module name matches the driver name to be hidden.

If a match is found, it copies the original entry to a HiddenDriverItem structure to make sure it can be restored again. It then adds the HiddenDriverItem to the list of hidden drivers.

The function then removes the module from the PsLoadedModuleList and sets the status to STATUS_SUCCESS.

If the driver is not found in the PsLoadedModuleList or if adding the HiddenDriverItem to the list of hidden drivers fails, the function sets the status to STATUS_NOT_FOUND or STATUS_ABANDONED respectively.

Finally, the function releases the lock on the PsLoadedModuleResource and returns the status of the operation.


Credential Dumping

Dumping credentials by memory scanning Lsasrv and extracting the 3DES key and encrypted credentials (password hash, username and domain). The credentials are saved in a cache and retrieved right after the dump.

Function Signature

NTSTATUS MemoryUtils::DumpCredentials(ULONG* AllocationSize)

AllocationSize [ULONG*] -- Pointer to a ULONG that will hold the size to allocate for the credentials buffer.

Usage Example

NidhoggClient.exe dump_creds

How It Works

The function begins by checking if the last index of the lsass credentials is not zero, if so it returns STATUS_ABANDONED.

Next, it finds the process ID of lsass.exe and looks up the process by the process ID.

The function then attaches the current thread to the address space of the lsass process.

It locates the base address of the lsasrv.dll module and the address of the LsaIAuditSamEvent function within the module.

The function then finds the location of the LsaEnumerateLogonSession and LsaInitializeProtectedMemory functions, and the start of the LsaEnumerateLogonSession function.

It retrieves the 3DES key used by lsass for encrypting credentials.

The function then finds the address of the LogonSessionList and counts the number of credentials in the list.

It allocates memory for the credentials and then iterates over the list of credentials, copying the username, domain, and encrypted hash for each credential.

Finally, the function detaches from the lsass process and returns the status of the operation. If the operation was successful, it also sets the count and last index of the lsass credentials and the allocation size for the credentials buffer.