2

I have adapted a 32-bit memory scanner in C# to 64-bit. Before scanning a program (read: live program), it is supposed to suspend the process. I am using NtSuspendProcess at this point to attempt to achieve this end. The funny thing is, I have this program (do not have access to the source) which, once a file is opened, refuses to suspend. Figuring it was a bug in my scanner, I tried suspending the program, once again when a file is opened, using Process Explorer; it flashes "Suspended" very briefly; I looked under Suspend Count for the threads associated with the process, and it increments just fine when I tell Process Explorer to suspend it...but when I tell Process Explorer to resume the process, it increments the Suspend Count (you read that right, it INCREMENTS it).

So yes, with my memory scanner and Process Explorer, when this program has no files open, it suspends and resumes normally. When the program has a file open, it fails to suspend, and increments the Suspend Count on attempts to resume.

I suspect a number of things here. Somehow, the message to suspend is being duplicated, and being released when resume is called. This explains the Suspend Count incrementing when it should be decrementing. Which might mean the original Suspend message isn't being consumed properly.

How do I even go about debugging this problem from this point? Where do I start?

Below are some code snippets from my memory scanner:

const uint PROCESS_SUSPEND_RESUME = 0x0800;
const uint PROCESS_QUERY_INFORMATION = 0x0400;
const uint MEM_COMMIT = 0x00001000;
const uint PAGE_READWRITE = 0x04;
const uint PROCESS_WM_READ = 0x0010;

[DllImport("ntdll.dll", EntryPoint = "NtSuspendProcess", SetLastError = true, ExactSpelling = false)]
private static extern UIntPtr NtSuspendProcess(UIntPtr processHandle);

[DllImport("ntdll.dll", EntryPoint = "NtResumeProcess", SetLastError = true, ExactSpelling = false)]
private static extern UIntPtr NtResumeProcess(UIntPtr processHandle);

[DllImport("kernel32.dll")]
public static extern UIntPtr OpenProcess(UIntPtr dwDesiredAccess, bool bInheritHandle, UIntPtr dwProcessId);

[DllImport("kernel32.dll")]
public static extern bool ReadProcessMemory(UIntPtr hProcess, UIntPtr lpBaseAddress, byte[] lpBuffer, UIntPtr dwSize, out UIntPtr lpNumberOfBytesRead);

[DllImport("kernel32.dll")]
static extern void GetSystemInfo(out SYSTEM_INFO lpSystemInfo);

[DllImport("kernel32.dll", SetLastError = true)]
static extern UIntPtr VirtualQueryEx(UIntPtr hProcess, UIntPtr lpAddress, out MEMORY_BASIC_INFORMATION64 lpBuffer, UIntPtr dwLength);

[DllImport("kernel32.dll")]
static extern bool CloseHandle(UIntPtr hObject);

private void Button_Extract_Click(object sender, EventArgs e)
{
    Process process = Process.GetProcessById(int.Parse(DataGridView_Processes.SelectedRows[0].Cells["Process ID"].Value.ToString()));
        UIntPtr processSuspendResumeHandle = OpenProcess(new UIntPtr(PROCESS_SUSPEND_RESUME), false, new UIntPtr((uint)process.Id));
        
        //process.Suspend();
        
        UIntPtr suspendreturnvalue = NtSuspendProcess(processSuspendResumeHandle);
        System.Diagnostics.Debug.WriteLine("Return Value: " + suspendreturnvalue.ToString());

        UIntPtr processHandle = OpenProcess(new UIntPtr(PROCESS_QUERY_INFORMATION | PROCESS_WM_READ), false, new UIntPtr((uint)process.Id));

        //int error = Marshal.GetLastWin32Error();
        //System.Diagnostics.Debug.WriteLine("Last Win32 Error: " + error);

    SYSTEM_INFO sys_info = new SYSTEM_INFO();

    GetSystemInfo(out sys_info);

    UIntPtr proc_min_address = sys_info.minimumApplicationAddress;
    UIntPtr proc_max_address = sys_info.maximumApplicationAddress;

    ulong proc_min_address_l = (ulong)proc_min_address;
    ulong proc_max_address_l = (ulong)proc_max_address;

    //Skip to end

    CloseHandle(processHandle);
    NtResumeProcess(processSuspendResumeHandle);
    CloseHandle(processSuspendResumeHandle);
    MessageBox.Show("Extraction Complete.");
}
  • 3
    Checking the return value of these winapi functions is crucial, you don't have friendly exceptions anymore to remind you that they failed. – Hans Passant Dec 03 '21 at 18:20
  • I added code to get the last Win32 error, and its return value appears to be 0, which would indicate success (of the suspending of a process). Yet this is not so. – user3799003 Dec 12 '21 at 08:21
  • Check the _return value_ of the function, not `GetLastWin32Error` — you are dealing with the internal user-mode kernel API, not the Win32 layer above it. Check this out as well: https://ntopcode.wordpress.com/tag/ntsuspendprocess/ – Ondrej Tucny Dec 12 '21 at 11:35
  • Hmm. Assuming I have the method declaration correct, I am still getting a return value of 0. But I have stumbled across something interesting. Up until now, I've been using procexp64.exe (I'm on 64-bit Windows 10); however, if I use procexp.exe, the program suspends correctly. Perhaps I should have added that this program is a 32-bit program; I didn't think it relevant at the time. Why would the 32-bit version of Process Explorer suspend a 32-bit program correctly, but the 64-bit version not? – user3799003 Dec 14 '21 at 09:00
  • need how minimum have this exe for look. *once a file is opened* - and what this mean ? – RbMm Dec 22 '21 at 00:30
  • The program I am attempting to suspend has the ability to open files of a certain format. When a file is open in this program, it fails to suspend. When no files are open in this program, it suspends just fine. – user3799003 Dec 22 '21 at 23:39
  • *open files of a certain format.* - no any usefull info here. need have this program and look it in action – RbMm Dec 24 '21 at 15:00

1 Answers1

0

Do the following:

  1. Make sure you are running as an admin
  2. Check the return code of OpenProcess it should return a nonzero, if not continue to step 3.
  3. Check the return code of GetLastError, search its value here https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499- (this only contains error 0-400 so check the sidebar, there's a link to the other pages that contain other error codes)
  4. Check the returned value of NtSuspendProcess and search it on the link above.
  5. Since it looks like you're accessing the memory, you're probably using this app for game cheats.... so check OpenProcess, NtOpenProcess, NtSuspendProcess, and NtResumeProcess using an assembler such as Cheat Engine or x64dbg.
  6. Go to the address of the functions I highlighted. Check if the first 5 bytes have a JMP instruction. If it has then that means the function has been patched by either an antivirus or an anticheat.
  7. If it's patched, you might wanna disable the antivirus and if its still patched then you're against an anticheat. Switch to C++, use direct syscalls using syswhispers2, use Qt for C++ its a much better alternative to C# Winforms, it has a drag-and-drop form builder too.
  8. If there is nothing suspicious, the functions didn't look patched. The GetLastError calls return normal status. Then you might be against a rootkit anticheat. In that case, you'll have to write your own driver to handle the open process, read memory, and write memory using C++ and WDK. You'll also need to find a mapper for your unsigned driver (KDMapper, LPMapper, etc) since it's not possible to load unsigned driver in the OS without disabling Driver Signature Enforcement feature of Windows, which when disabled, can be detected by an anti cheat.

EDIT: I found this article about NtSuspendProcess failing when opening a process with PROCESS_SUSPEND_RESUME access mask. Try using PROCESS_ALL_ACCESS access mask instead. https://social.msdn.microsoft.com/Forums/vstudio/en-US/e119f8e8-98bc-466e-a9bb-88bcbde6524c/why-does-ntsuspendprocess-fail-with-processsuspendresume-but-succeeds-with-processallaccess?forum=windowssdk