Introduction
The makers of process hacker know the Windows kernel inside and out! Fortunately for them, they are also fluent in C++: the language that the Windows kernel was designed on. For the rest of us, we struggle to make C# perform similar functions. One of those functions is the ability to release memory from the windows standby memory. That is what we will do here.
Credits
Obviously, the procedures used here are fundamentally taken from Process Hacker's source code - Thanks!
Next, the hardest part is getting the Kernel privileges. While I've significantly tweaked the code, my thanks must also go to Nick Lowe - Thanks!
Note: I have trimmed almost all code that is not related to the task itself. If you want to know more about either project, please visit their websites.
Step One: Setting Kernel Privileges
Microsoft has designed the API to take a structure that contains an array of structures. There are no good Marshaling procedures for this - they either assume the array is length=1
or require that the size of the array be predetermined at compilation. For this purpose, I designed a class that holds the exact structure that Microsoft requires in unmanaged memory via an System.IntPtr
. I had to resort to one unsafe procedure at this point ... but I'm sure that can be bypassed with more work in the future.
[return: MarshalAs(UnmanagedType.Bool)]
[SuppressUnmanagedCodeSecurity, DllImport("advapi32.dll", SetLastError = true)]
internal static extern bool AdjustTokenPrivileges(AccessTokenHandle accessTokenHandle,
[MarshalAs(UnmanagedType.Bool)] bool disableAllPrivileges, IntPtr NewPriviledges,
Int32 bufferLength, ref IntPtr PriorPriviledges, out Int32 returnLength);
Combining my class with Nick Lowe's privilege code, we can setup our kernel privileges with:
AccessTokenHandle
myProcessToken = new AccessTokenHandle(System.Diagnostics.Process.GetCurrentProcess(),
ProcessPrivileges.TokenAccessRights.AdjustPrivileges | TokenAccessRights.Query);
myProcessToken.EnablePrivilege(Privilege.Debug, Privilege.ProfileSingleProcess);
Step Two: Sending the Command to the Kernel
This time, Microsoft simply wants an int
array. These Marshal easily, so all we need is:
[DllImport("ntdll.dll")]
public static extern NtStatus NtSetSystemInformation
(SYSTEM_INFORMATION_CLASS InfoClass, int[] Info, uint Length);
NativeMethods.NtStatus
result = 0;
int[]
arr = new int[] { (int)Commands.MemoryPurgeStandbyList };
result = NativeMethods.NtSetSystemInformation
(NativeMethods.SYSTEM_INFORMATION_CLASS.SystemMemoryListInformation,
arr, (uint)(sizeof(int) * arr.Length));
if (result > 0)
throw new System.ComponentModel.Win32Exception();