[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