diff --git a/dlls/kernel32/tests/process.c b/dlls/kernel32/tests/process.c index 7e8ef16178f..ca51a417b7c 100644 --- a/dlls/kernel32/tests/process.c +++ b/dlls/kernel32/tests/process.c @@ -2944,6 +2944,239 @@ static void test_DetachConsoleHandles(void) #endif } +#if defined(__i386__) || defined(__x86_64__) +static BOOL read_nt_header(HANDLE process_handle, MEMORY_BASIC_INFORMATION *mbi, + IMAGE_NT_HEADERS *nt_header) +{ + IMAGE_DOS_HEADER dos_header; + + if (!ReadProcessMemory(process_handle, mbi->BaseAddress, &dos_header, sizeof(dos_header), NULL)) + return FALSE; + + 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, (char *)mbi->BaseAddress + dos_header.e_lfanew, + nt_header, sizeof(*nt_header), NULL)) + return FALSE; + + return (nt_header->Signature == IMAGE_NT_SIGNATURE); +} + +static void test_SuspendProcessState(void) +{ + struct pipe_params + { + ULONG pipe_write_buf; + ULONG pipe_read_buf; + ULONG bytes_returned; + CHAR pipe_name[MAX_PATH]; + }; + +#ifdef __x86_64__ + struct remote_rop_chain + { + void *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; + }; +#else + struct remote_rop_chain + { + void *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; + void *unreached_ret; + ULONG_PTR exit_code; + }; +#endif + + static const char pipe_name[] = "\\\\.\\pipe\\TestPipe"; + static const ULONG pipe_write_magic = 0x454e4957; + STARTUPINFOA si = {0}; + PROCESS_INFORMATION pi = {0}; + 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; + struct pipe_params pipe_params; + struct remote_rop_chain rop_chain; + CONTEXT ctx; + HANDLE server_pipe_handle; + BOOL pipe_connected; + ULONG pipe_magic, numb; + BOOL ret; + + exit_process_ptr = GetProcAddress(hkernel32, "ExitProcess"); + ok(exit_process_ptr != NULL, "GetProcAddress ExitProcess failed\n"); + + call_named_pipe_a = GetProcAddress(hkernel32, "CallNamedPipeA"); + ok(call_named_pipe_a != NULL, "GetProcAddress CallNamedPipeA failed\n"); + + si.cb = sizeof(si); + ret = CreateProcessA(NULL, selfname, 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 = (char *)mbi.BaseAddress + mbi.RegionSize) { + if ((mbi.Type == SEC_IMAGE) && + read_nt_header(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, + (char *)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, (char *)exe_base + iid.FirstThunk, + &iat_entry_value, sizeof(iat_entry_value), NULL); + ok(ret, "Failed to read IAT entry from FirstThunk (%d)\n", GetLastError()); + ok(iat_entry_value, "IAT entry in FirstThunk is NULL\n"); + + /* Read a single IAT entry from the OriginalFirstThunk */ + ret = ReadProcessMemory(pi.hProcess, (char *)exe_base + iid.OriginalFirstThunk, + &orig_iat_entry_value, sizeof(orig_iat_entry_value), NULL); + ok(ret, "Failed to read IAT entry from OriginalFirstThunk (%d)\n", GetLastError()); + ok(orig_iat_entry_value, "IAT entry in OriginalFirstThunk is NULL\n"); + + /* The IAT should be UNRESOLVED */ + todo_wine + 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_FULL; + 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, PAGE_READWRITE); + ok(remote_pipe_params != NULL, "Failed allocating memory in remote process (%d)\n", GetLastError()); + + pipe_params.pipe_write_buf = 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 __x86_64__ + rop_chain.exit_process_ptr = exit_process_ptr; + ctx.Rcx = (ULONG_PTR)remote_pipe_params + offsetof(struct pipe_params, pipe_name); + ctx.Rdx = (ULONG_PTR)remote_pipe_params + offsetof(struct pipe_params, pipe_write_buf); + ctx.R8 = sizeof(pipe_params.pipe_write_buf); + ctx.R9 = (ULONG_PTR)remote_pipe_params + offsetof(struct 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 + offsetof(struct pipe_params, bytes_returned); + rop_chain.timeout = 10000; + + ctx.Rip = (ULONG_PTR)call_named_pipe_a; + ctx.Rsp -= sizeof(rop_chain); + ret = WriteProcessMemory(pi.hProcess, (void *)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 = exit_process_ptr; + rop_chain.pipe_name = (ULONG_PTR)remote_pipe_params + offsetof(struct pipe_params, pipe_name); + rop_chain.pipe_write_buf = (ULONG_PTR)remote_pipe_params + offsetof(struct 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 + offsetof(struct 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 + offsetof(struct pipe_params, bytes_returned); + rop_chain.timeout = 10000; + rop_chain.exit_code = 0; + + ctx.Eip = (ULONG_PTR)call_named_pipe_a; + ctx.Esp -= sizeof(rop_chain); + ret = WriteProcessMemory(pi.hProcess, (void *)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) || (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, (char *)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, (char *)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()); + + CloseHandle(server_pipe_handle); + WaitForSingleObject(pi.hProcess, 10000); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); +} +#else +static void test_SuspendProcessState(void) +{ +} +#endif + static void test_DetachStdHandles(void) { #ifndef _WIN64 @@ -3436,6 +3669,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