[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