[v2] kernel32/tests: Test suspended process states

Jonathan Doron jond at wizery.com
Thu Sep 14 03:49:36 CDT 2017


Test the exe module imports state when a process is getting
suspended, and also test it after the process has resumed
into a new context set by SetThreadContext.

This test mimics two type of code injections:
1. Remote injection to a suspended process by modifying it's
   import table. (see applications using Detours library).
2. SafeDisc 1.X basic implementation of code injection to
   decrypt the child ICD process. (refer to bug: 9925)

Signed-off-by: Jonathan Doron <jond at wizery.com>
---
 dlls/kernel32/tests/process.c | 252 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 252 insertions(+)

diff --git a/dlls/kernel32/tests/process.c b/dlls/kernel32/tests/process.c
index c480a27..a650755 100644
--- a/dlls/kernel32/tests/process.c
+++ b/dlls/kernel32/tests/process.c
@@ -2941,6 +2941,257 @@ static void test_DetachConsoleHandles(void)
 #endif
 }
 
+static BOOL ReadRemoteNtHeader(HANDLE process_handle, PMEMORY_BASIC_INFORMATION mbi, PIMAGE_NT_HEADERS nt_header)
+{
+    IMAGE_DOS_HEADER dos_header;
+
+    // Get the MZ Header
+    if (!ReadProcessMemory(process_handle, mbi->BaseAddress, &dos_header, sizeof(dos_header), NULL))
+        return FALSE;
+
+    // Validate the MZ Header
+    if ((dos_header.e_magic != IMAGE_DOS_SIGNATURE) ||
+        ((ULONG)dos_header.e_lfanew > mbi->RegionSize) ||
+        (dos_header.e_lfanew < sizeof(dos_header))) {
+        return FALSE;
+    }
+
+    if (!ReadProcessMemory(process_handle, (PVOID)((ULONG_PTR)mbi->BaseAddress + dos_header.e_lfanew),
+                           (PVOID)nt_header, sizeof(*nt_header), NULL)) {
+        return FALSE;
+    }
+
+    if (nt_header->Signature != IMAGE_NT_SIGNATURE)
+        return FALSE;
+
+    return TRUE;
+}
+
+#define PIPE_NAME   "\\\\.\\pipe\\TestPipe"
+
+#define PIPE_WRITE_MAGIC    0x454E4957
+typedef struct _REMOTE_NAMED_PIPE_PARAMS {
+    ULONG pipe_write_buf;
+    ULONG pipe_read_buf;
+    ULONG bytes_returned;
+    CHAR pipe_name[MAX_PATH];
+} REMOTE_NAMED_PIPE_PARAMS, *PREMOTE_NAMED_PIPE_PARAMS;
+
+#ifdef _WIN64
+typedef struct _REMOTE_ROP_CHAIN {
+    ULONG_PTR exit_process_ptr;
+    ULONG_PTR home_rcx;
+    ULONG_PTR home_rdx;
+    ULONG_PTR home_r8;
+    ULONG_PTR home_r9;
+    ULONG_PTR pipe_read_buf_size;
+    ULONG_PTR bytes_returned;
+    ULONG_PTR timeout;
+} REMOTE_ROP_CHAIN, *PREMOTE_ROP_CHAIN;
+#else
+typedef struct _REMOTE_ROP_CHAIN {
+    ULONG_PTR exit_process_ptr;
+    ULONG_PTR pipe_name;
+    ULONG_PTR pipe_write_buf;
+    ULONG_PTR pipe_write_buf_size;
+    ULONG_PTR pipe_read_buf;
+    ULONG_PTR pipe_read_buf_size;
+    ULONG_PTR bytes_returned;
+    ULONG_PTR timeout;
+    ULONG_PTR exit_code;
+} REMOTE_ROP_CHAIN, *PREMOTE_ROP_CHAIN;
+#endif
+
+static void test_SuspendProcessState(void)
+{
+    CHAR exe_path[MAX_PATH];
+    STARTUPINFOA si = {0};
+    PROCESS_INFORMATION pi = {0};
+    HMODULE kernel32_base;
+    PVOID exe_base, address, remote_pipe_params, exit_process_ptr,
+          call_named_pipe_a;
+    IMAGE_NT_HEADERS nt_header;
+    MEMORY_BASIC_INFORMATION mbi;
+    IMAGE_IMPORT_DESCRIPTOR iid;
+    ULONG_PTR orig_iat_entry_value, iat_entry_value;
+    REMOTE_NAMED_PIPE_PARAMS pipe_params;
+    REMOTE_ROP_CHAIN rop_chain;
+    CONTEXT ctx = {0};
+    HANDLE server_pipe_handle = INVALID_HANDLE_VALUE;
+    BOOL pipe_connected;
+    ULONG pipe_magic, numb;
+    BOOL ret;
+
+    kernel32_base = GetModuleHandleA("kernel32.dll");
+    ok(kernel32_base != NULL, "GetModuleHandleA Kernel32\n");
+
+    exit_process_ptr = GetProcAddress(kernel32_base, "ExitProcess");
+    ok(exit_process_ptr != NULL, "GetProcAddress ExitProcess failed\n");
+
+    call_named_pipe_a = GetProcAddress(kernel32_base, "CallNamedPipeA");
+    ok(call_named_pipe_a != NULL, "GetProcAddress CallNamedPipeA failed\n");
+
+    ok(GetModuleFileNameA(NULL, exe_path, sizeof(exe_path)), "Failed to get exe module path (%d)\n", GetLastError());
+
+    si.cb = sizeof(si);
+    ret = CreateProcessA(exe_path, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
+    ok(ret, "Failed to create process (%d)\n", GetLastError());
+
+    /* Find the EXE base in the new process */
+    exe_base = NULL;
+    for (address = NULL ;
+         VirtualQueryEx(pi.hProcess, address, &mbi, sizeof(mbi)) ;
+         address = (PVOID)((ULONG_PTR)mbi.BaseAddress + mbi.RegionSize)) {
+        if ((mbi.Type == SEC_IMAGE) &&
+            ReadRemoteNtHeader(pi.hProcess, &mbi, &nt_header) &&
+            !(nt_header.FileHeader.Characteristics & IMAGE_FILE_DLL)) {
+            exe_base = mbi.BaseAddress;
+            break;
+        }
+    }
+
+    /* Make sure we found the EXE in the new process */
+    ok(exe_base != NULL, "Could not find EXE in remote process\n");
+
+    /* Check the EXE has import table */
+    ok(nt_header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress, "Import table VA is zero\n");
+    ok(nt_header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size, "Import table Size is zero\n");
+
+    /* Read the first IID */
+    ret = ReadProcessMemory(pi.hProcess,
+                           (PVOID)((ULONG_PTR)exe_base + nt_header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress),
+                           &iid, sizeof(iid), NULL);
+    ok(ret, "Failed to read process EXE IID (%d)\n", GetLastError());
+
+    /* Validate the IID is present and not a bound import, and that we have
+       an OriginalFirstThunk to compare with */
+    ok(iid.Name, "Exe first IID does not have a Name\n");
+    ok(iid.FirstThunk, "Exe first IID does not have a FirstThunk\n");
+    ok(!iid.TimeDateStamp, "Exe first IID is a bound import (UNSUPPORTED for current test)\n");
+    ok(iid.OriginalFirstThunk, "Exe first IID does not have an OriginalFirstThunk (UNSUPPORTED for current test)\n");
+
+    /* Read a single IAT entry from the FirstThunk */
+    ret = ReadProcessMemory(pi.hProcess,
+                            (PVOID)((ULONG_PTR)exe_base + iid.FirstThunk),
+                            &iat_entry_value, sizeof(iat_entry_value), NULL);
+    ok(ret, "Failed to read IAT entry from FirstThunk [1] (%d)\n", GetLastError());
+
+    ok(iat_entry_value, "IAT entry in FirstThunk is NULL [1]\n");
+
+    /* Read a single IAT entry from the OriginalFirstThunk */
+    ret = ReadProcessMemory(pi.hProcess,
+                            (PVOID)((ULONG_PTR)exe_base + iid.OriginalFirstThunk),
+                            &orig_iat_entry_value, sizeof(orig_iat_entry_value), NULL);
+    ok(ret, "Failed to read IAT entry from OriginalFirstThunk [1] (%d)\n", GetLastError());
+
+    ok(orig_iat_entry_value, "IAT entry in OriginalFirstThunk is NULL [1]\n");
+
+    /* The IAT should be UNRESOLVED */
+    ok(iat_entry_value == orig_iat_entry_value, "IAT entry resolved prematurely\n");
+
+    server_pipe_handle = CreateNamedPipeA(PIPE_NAME, PIPE_ACCESS_DUPLEX | FILE_FLAG_WRITE_THROUGH,
+                                        PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, 1, 0x20000, 0x20000,
+                                        0, NULL);
+    ok(server_pipe_handle != INVALID_HANDLE_VALUE, "Failed to create communication pipe (%d)\n", GetLastError());
+
+    /* Setup the remote process enviornment */
+    ctx.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
+    ret = GetThreadContext(pi.hThread, &ctx);
+    ok(ret, "Failed retrieving remote thread context (%d)\n", GetLastError());
+
+    remote_pipe_params = VirtualAllocEx(pi.hProcess, NULL, sizeof(pipe_params), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
+    ok(remote_pipe_params != NULL, "Failed allocating memory in remote process (%d)\n", GetLastError());
+
+    pipe_params.pipe_write_buf = (ULONG)PIPE_WRITE_MAGIC;
+    pipe_params.pipe_read_buf = 0;
+    pipe_params.bytes_returned = 0;
+    strcpy(pipe_params.pipe_name, PIPE_NAME);
+
+    ret = WriteProcessMemory(pi.hProcess, remote_pipe_params,
+                            &pipe_params, sizeof(pipe_params), NULL);
+    ok(ret, "Failed to write to remote process memory (%d)\n", GetLastError());
+
+#ifdef _WIN64
+    rop_chain.exit_process_ptr = (ULONG_PTR)exit_process_ptr;
+    ctx.Rcx = (ULONG_PTR)remote_pipe_params + FIELD_OFFSET(REMOTE_NAMED_PIPE_PARAMS, pipe_name);
+    ctx.Rdx = (ULONG_PTR)remote_pipe_params + FIELD_OFFSET(REMOTE_NAMED_PIPE_PARAMS, pipe_write_buf);
+    ctx.R8 = sizeof(pipe_params.pipe_write_buf);
+    ctx.R9 = (ULONG_PTR)remote_pipe_params + FIELD_OFFSET(REMOTE_NAMED_PIPE_PARAMS, pipe_read_buf);
+    rop_chain.pipe_read_buf_size = sizeof(pipe_params.pipe_read_buf);
+    rop_chain.bytes_returned = (ULONG_PTR)remote_pipe_params + FIELD_OFFSET(REMOTE_NAMED_PIPE_PARAMS, bytes_returned);
+    rop_chain.timeout = NMPWAIT_WAIT_FOREVER;
+
+    ctx.Rip = (ULONG64)call_named_pipe_a;
+    ctx.Rsp -= sizeof(rop_chain);
+    ret = WriteProcessMemory(pi.hProcess, (PVOID)(ULONG_PTR)ctx.Rsp, &rop_chain,
+                            sizeof(rop_chain), NULL);
+    ok(ret, "Failed to write to remote process thread stack (%d)\n", GetLastError());
+#else
+    rop_chain.exit_process_ptr = (ULONG_PTR)exit_process_ptr;
+    rop_chain.pipe_name = (ULONG_PTR)remote_pipe_params + FIELD_OFFSET(REMOTE_NAMED_PIPE_PARAMS, pipe_name);
+    rop_chain.pipe_write_buf = (ULONG_PTR)remote_pipe_params + FIELD_OFFSET(REMOTE_NAMED_PIPE_PARAMS, pipe_write_buf);
+    rop_chain.pipe_write_buf_size = sizeof(pipe_params.pipe_write_buf);
+    rop_chain.pipe_read_buf = (ULONG_PTR)remote_pipe_params + FIELD_OFFSET(REMOTE_NAMED_PIPE_PARAMS, pipe_read_buf);
+    rop_chain.pipe_read_buf_size = sizeof(pipe_params.pipe_read_buf);
+    rop_chain.bytes_returned = (ULONG_PTR)remote_pipe_params + FIELD_OFFSET(REMOTE_NAMED_PIPE_PARAMS, bytes_returned);
+    rop_chain.timeout = NMPWAIT_WAIT_FOREVER;
+    rop_chain.exit_code = 0;
+
+    ctx.Eip = (ULONG)call_named_pipe_a;
+    ctx.Esp -= sizeof(rop_chain);
+    ret = WriteProcessMemory(pi.hProcess, (PVOID)(ULONG_PTR)ctx.Esp, &rop_chain,
+                            sizeof(rop_chain), NULL);
+    ok(ret, "Failed to write to remote process thread stack (%d)\n", GetLastError());
+#endif
+    ret = SetThreadContext(pi.hThread, &ctx);
+    ok(ret, "Failed to set remote thread context (%d)\n", GetLastError());
+
+    ResumeThread(pi.hThread);
+
+    pipe_connected = ConnectNamedPipe(server_pipe_handle, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
+    ok(pipe_connected, "Pipe did not connect\n");
+
+    ret = ReadFile(server_pipe_handle, &pipe_magic, sizeof(pipe_magic), &numb, NULL);
+    ok(ret, "Failed to read buffer from pipe (%d)\n", GetLastError());
+
+    ok(pipe_magic == PIPE_WRITE_MAGIC, "Did not get the correct magic from the remote process\n");
+
+    /* Validate the Imports, at this point the thread in the new process should have
+       initialized the EXE module imports and call each dll DllMain notifying it on
+       the new thread in the process. */
+    iat_entry_value = orig_iat_entry_value = 0;
+
+    /* Read a single IAT entry from the FirstThunk */
+    ret = ReadProcessMemory(pi.hProcess,
+                            (PVOID)((ULONG_PTR)exe_base + iid.FirstThunk),
+                            &iat_entry_value, sizeof(iat_entry_value), NULL);
+    ok(ret, "Failed to read IAT entry from FirstThunk [2] (%d)\n", GetLastError());
+
+    /* Read a single IAT entry from the OriginalFirstThunk */
+    ret = ReadProcessMemory(pi.hProcess,
+                            (PVOID)((ULONG_PTR)exe_base + iid.OriginalFirstThunk),
+                            &orig_iat_entry_value, sizeof(orig_iat_entry_value), NULL);
+    ok(ret, "Failed to read IAT entry from OriginalFirstThunk [2] (%d)\n", GetLastError());
+
+    /* The IAT should be RESOLVED */
+    ok(iat_entry_value != orig_iat_entry_value, "EXE IAT is not resolved\n");
+
+    ret = WriteFile(server_pipe_handle, &pipe_magic, sizeof(pipe_magic), &numb, NULL);
+    ok(ret, "Failed to write the magic back to the pipe (%d)\n", GetLastError());
+
+    if (server_pipe_handle != INVALID_HANDLE_VALUE)
+        CloseHandle(server_pipe_handle);
+
+    if (pi.hThread)
+        CloseHandle(pi.hThread);
+
+    if (pi.hProcess) {
+        TerminateProcess(pi.hProcess, 0);
+        WaitForSingleObject(pi.hProcess, INFINITE);
+        CloseHandle(pi.hProcess);
+    }
+}
+
 static void test_DetachStdHandles(void)
 {
 #ifndef _WIN64
@@ -3433,6 +3684,7 @@ START_TEST(process)
     test_GetLogicalProcessorInformationEx();
     test_largepages();
     test_ProcThreadAttributeList();
+    test_SuspendProcessState();
 
     /* things that can be tested:
      *  lookup:         check the way program to be executed is searched
-- 
2.9.4




More information about the wine-patches mailing list