[v1] kernel32/tests: Test suspended process states
Jonathan Doron
jond at wizery.com
Thu Sep 14 03:27:37 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..40e70d8 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 ProcessHandle, PMEMORY_BASIC_INFORMATION mbi, PIMAGE_NT_HEADERS NtHeader)
+{
+ IMAGE_DOS_HEADER DosHeader;
+
+ // Get the MZ Header
+ if (!ReadProcessMemory(ProcessHandle, mbi->BaseAddress, &DosHeader, sizeof(DosHeader), NULL))
+ return FALSE;
+
+ // Validate the MZ Header
+ if ((DosHeader.e_magic != IMAGE_DOS_SIGNATURE) ||
+ ((ULONG)DosHeader.e_lfanew > mbi->RegionSize) ||
+ (DosHeader.e_lfanew < sizeof(DosHeader))) {
+ return FALSE;
+ }
+
+ if (!ReadProcessMemory(ProcessHandle, (PVOID)((ULONG_PTR)mbi->BaseAddress + DosHeader.e_lfanew),
+ (PVOID)NtHeader, sizeof(*NtHeader), NULL)) {
+ return FALSE;
+ }
+
+ if (NtHeader->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 PipeWriteBuf;
+ ULONG PipeReadBuf;
+ ULONG BytesReturned;
+ CHAR PipeName[MAX_PATH];
+} REMOTE_NAMED_PIPE_PARAMS, *PREMOTE_NAMED_PIPE_PARAMS;
+
+#ifdef _WIN64
+typedef struct _REMOTE_ROP_CHAIN {
+ ULONG_PTR ExitProcessPtr;
+ ULONG_PTR HomeRCX;
+ ULONG_PTR HomeRDX;
+ ULONG_PTR HomeR8;
+ ULONG_PTR HomeR9;
+ ULONG_PTR PipeReadBufSize;
+ ULONG_PTR BytesReturned;
+ ULONG_PTR Timeout;
+} REMOTE_ROP_CHAIN, *PREMOTE_ROP_CHAIN;
+#else
+typedef struct _REMOTE_ROP_CHAIN {
+ ULONG_PTR ExitProcessPtr;
+ ULONG_PTR PipeName;
+ ULONG_PTR PipeWriteBuf;
+ ULONG_PTR PipeWriteBufSize;
+ ULONG_PTR PipeReadBuf;
+ ULONG_PTR PipeReadBufSize;
+ ULONG_PTR BytesReturned;
+ ULONG_PTR Timeout;
+ ULONG_PTR ExitCode;
+} REMOTE_ROP_CHAIN, *PREMOTE_ROP_CHAIN;
+#endif
+
+static void test_SuspendProcessState(void)
+{
+ CHAR ExePath[MAX_PATH];
+ STARTUPINFOA si = {0};
+ PROCESS_INFORMATION pi = {0};
+ HMODULE Kernel32Base;
+ PVOID ExeBase, Address, RemotePipeParams, ExitProcessPtr,
+ CallNamedPipeAPtr;
+ IMAGE_NT_HEADERS NtHeader;
+ MEMORY_BASIC_INFORMATION mbi;
+ IMAGE_IMPORT_DESCRIPTOR iid;
+ ULONG_PTR OrigIatEntryValue, IatEntryValue;
+ REMOTE_NAMED_PIPE_PARAMS PipeParams;
+ REMOTE_ROP_CHAIN RopChain;
+ CONTEXT Ctx = {0};
+ HANDLE ServerPipeHandle = INVALID_HANDLE_VALUE;
+ BOOL PipeConnected;
+ ULONG PipeMagic, Numb;
+ BOOL ret;
+
+ Kernel32Base = GetModuleHandleA("kernel32.dll");
+ ok(Kernel32Base != NULL, "GetModuleHandleA Kernel32\n");
+
+ ExitProcessPtr = GetProcAddress(Kernel32Base, "ExitProcess");
+ ok(ExitProcessPtr != NULL, "GetProcAddress ExitProcess failed\n");
+
+ CallNamedPipeAPtr = GetProcAddress(Kernel32Base, "CallNamedPipeA");
+ ok(CallNamedPipeAPtr != NULL, "GetProcAddress CallNamedPipeA failed\n");
+
+ ok(GetModuleFileNameA(NULL, ExePath, sizeof(ExePath)), "Failed to get exe module path (%d)\n", GetLastError());
+
+ si.cb = sizeof(si);
+ ret = CreateProcessA(ExePath, 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
+ ExeBase = 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, &NtHeader) &&
+ !(NtHeader.FileHeader.Characteristics & IMAGE_FILE_DLL)) {
+ ExeBase = mbi.BaseAddress;
+ break;
+ }
+ }
+
+ // Make sure we found the EXE in the new process
+ ok(ExeBase != NULL, "Could not find EXE in remote process\n");
+
+ // Check the EXE has import table
+ ok(NtHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress, "Import table VA is zero\n");
+ ok(NtHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size, "Import table Size is zero\n");
+
+ // Read the first IID
+ ret = ReadProcessMemory(pi.hProcess,
+ (PVOID)((ULONG_PTR)ExeBase + NtHeader.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)ExeBase + iid.FirstThunk),
+ &IatEntryValue, sizeof(IatEntryValue), NULL);
+ ok(ret, "Failed to read IAT entry from FirstThunk [1] (%d)\n", GetLastError());
+
+ ok(IatEntryValue, "IAT entry in FirstThunk is NULL [1]\n");
+
+ // Read a single IAT entry from the OriginalFirstThunk
+ ret = ReadProcessMemory(pi.hProcess,
+ (PVOID)((ULONG_PTR)ExeBase + iid.OriginalFirstThunk),
+ &OrigIatEntryValue, sizeof(OrigIatEntryValue), NULL);
+ ok(ret, "Failed to read IAT entry from OriginalFirstThunk [1] (%d)\n", GetLastError());
+
+ ok(OrigIatEntryValue, "IAT entry in OriginalFirstThunk is NULL [1]\n");
+
+ // The IAT should be UNRESOLVED
+ ok(IatEntryValue == OrigIatEntryValue, "IAT entry resolved prematurely\n");
+
+ ServerPipeHandle = CreateNamedPipeA(PIPE_NAME, PIPE_ACCESS_DUPLEX | FILE_FLAG_WRITE_THROUGH,
+ PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, 1, 0x20000, 0x20000,
+ 0, NULL);
+ ok(ServerPipeHandle != 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());
+
+ RemotePipeParams = VirtualAllocEx(pi.hProcess, NULL, sizeof(RemotePipeParams), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
+ ok(RemotePipeParams != NULL, "Failed allocating memory in remote process (%d)\n", GetLastError());
+
+ PipeParams.PipeWriteBuf = (ULONG)PIPE_WRITE_MAGIC;
+ PipeParams.PipeReadBuf = 0;
+ PipeParams.BytesReturned = 0;
+ strcpy(PipeParams.PipeName, PIPE_NAME);
+
+ ret = WriteProcessMemory(pi.hProcess, RemotePipeParams,
+ &PipeParams, sizeof(PipeParams), NULL);
+ ok(ret, "Failed to write to remote process memory (%d)\n", GetLastError());
+
+#ifdef _WIN64
+ RopChain.ExitProcessPtr = (ULONG_PTR)ExitProcessPtr;
+ Ctx.Rcx = (ULONG_PTR)RemotePipeParams + FIELD_OFFSET(REMOTE_NAMED_PIPE_PARAMS, PipeName);
+ Ctx.Rdx = (ULONG_PTR)RemotePipeParams + FIELD_OFFSET(REMOTE_NAMED_PIPE_PARAMS, PipeWriteBuf);
+ Ctx.R8 = sizeof(PipeParams.PipeWriteBuf);
+ Ctx.R9 = (ULONG_PTR)RemotePipeParams + FIELD_OFFSET(REMOTE_NAMED_PIPE_PARAMS, PipeReadBuf);
+ RopChain.PipeReadBufSize = sizeof(PipeParams.PipeReadBuf);
+ RopChain.BytesReturned = (ULONG_PTR)RemotePipeParams + FIELD_OFFSET(REMOTE_NAMED_PIPE_PARAMS, BytesReturned);
+ RopChain.Timeout = NMPWAIT_WAIT_FOREVER;
+
+ Ctx.Rip = (ULONG64)CallNamedPipeAPtr;
+ Ctx.Rsp -= sizeof(RopChain);
+ ret = WriteProcessMemory(pi.hProcess, (PVOID)(ULONG_PTR)Ctx.Rsp, &RopChain,
+ sizeof(RopChain), NULL);
+ ok(ret, "Failed to write to remote process thread stack (%d)\n", GetLastError());
+#else
+ RopChain.ExitProcessPtr = (ULONG_PTR)ExitProcessPtr;
+ RopChain.PipeName = (ULONG_PTR)RemotePipeParams + FIELD_OFFSET(REMOTE_NAMED_PIPE_PARAMS, PipeName);
+ RopChain.PipeWriteBuf = (ULONG_PTR)RemotePipeParams + FIELD_OFFSET(REMOTE_NAMED_PIPE_PARAMS, PipeWriteBuf);
+ RopChain.PipeWriteBufSize = sizeof(PipeParams.PipeWriteBuf);
+ RopChain.PipeReadBuf = (ULONG_PTR)RemotePipeParams + FIELD_OFFSET(REMOTE_NAMED_PIPE_PARAMS, PipeReadBuf);
+ RopChain.PipeReadBufSize = sizeof(PipeParams.PipeReadBuf);
+ RopChain.BytesReturned = (ULONG_PTR)RemotePipeParams + FIELD_OFFSET(REMOTE_NAMED_PIPE_PARAMS, BytesReturned);
+ RopChain.Timeout = NMPWAIT_WAIT_FOREVER;
+ RopChain.ExitCode = 0;
+
+ Ctx.Eip = (ULONG)CallNamedPipeAPtr;
+ Ctx.Esp -= sizeof(RopChain);
+ ret = WriteProcessMemory(pi.hProcess, (PVOID)(ULONG_PTR)Ctx.Esp, &RopChain,
+ sizeof(RopChain), 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);
+
+ PipeConnected = ConnectNamedPipe(ServerPipeHandle, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
+ ok(PipeConnected, "Pipe did not connect\n");
+
+ ret = ReadFile(ServerPipeHandle, &PipeMagic, sizeof(PipeMagic), &Numb, NULL);
+ ok(ret, "Failed to read buffer from pipe (%d)\n", GetLastError());
+
+ ok(PipeMagic == 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.
+ IatEntryValue = OrigIatEntryValue = 0;
+
+ // Read a single IAT entry from the FirstThunk
+ ret = ReadProcessMemory(pi.hProcess,
+ (PVOID)((ULONG_PTR)ExeBase + iid.FirstThunk),
+ &IatEntryValue, sizeof(IatEntryValue), 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)ExeBase + iid.OriginalFirstThunk),
+ &OrigIatEntryValue, sizeof(OrigIatEntryValue), NULL);
+ ok(ret, "Failed to read IAT entry from OriginalFirstThunk [2] (%d)\n", GetLastError());
+
+ // The IAT should be RESOLVED
+ ok(IatEntryValue != OrigIatEntryValue, "EXE IAT is not resolved\n");
+
+ ret = WriteFile(ServerPipeHandle, &PipeMagic, sizeof(PipeMagic), &Numb, NULL);
+ ok(ret, "Failed to write the magic back to the pipe (%d)\n", GetLastError());
+
+ if (ServerPipeHandle != INVALID_HANDLE_VALUE)
+ CloseHandle(ServerPipeHandle);
+
+ 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