From a63ea7e338cabc315b58b2356a004c8772ac0f6c Mon Sep 17 00:00:00 2001
From: Jeremy Drake <github@jdrake.com>
Date: Tue, 22 Jun 2021 22:06:26 -0700
Subject: [PATCH 44/N] getprocaddr: refactor/cleanup

Look in kernel32 and kernelbase for CtrlRoutine before breaking out the
big guns and trying to find it in the call stack.  Fixes #51.

The change to look at the return code of the thread caused the program
to act as though ExitProcess failed to inject.  Deal with this by
passing the thread exit code as an OUT param, and only looking at it
when calling the CtrlRoutine.

While I was there, I noticed a potential race between CreateEvent and
the ctrl_handler function that uses it, so cleaned that up.  Also the
event doesn't need to be named, so don't name it to avoid potential
collisions with other processes.

Fixed a couple of printf type warnings.  As this is mingw, it should be
safe to use %lu for DWORDs.
---
 winsup/utils/getprocaddr.c | 202 +++++++++++++++++++++----------------
 1 file changed, 116 insertions(+), 86 deletions(-)

diff --git a/winsup/utils/getprocaddr.c b/winsup/utils/getprocaddr.c
index d5ecd35..25814c7 100644
--- a/winsup/utils/getprocaddr.c
+++ b/winsup/utils/getprocaddr.c
@@ -27,13 +27,14 @@ static HANDLE CtrlEvent;
 static int
 inject_remote_thread_into_process (HANDLE process,
                                    LPTHREAD_START_ROUTINE address,
-                                   uintptr_t exit_code)
+                                   uintptr_t exit_code,
+                                   DWORD *thread_return)
 {
   int res = -1;
 
   if (!address)
     return res;
-  DWORD thread_id, code;
+  DWORD thread_id;
   HANDLE thread = CreateRemoteThread (process, NULL, 1024 * 1024, address,
                                       (PVOID)exit_code, 0, &thread_id);
   if (thread)
@@ -53,9 +54,8 @@ inject_remote_thread_into_process (HANDLE process,
       CtrlEvent set by SetConsoleCtrlHandler is handled correctly, in all
       other cases it returns something non-zero(not sure what it that).
       */
-      GetExitCodeThread (thread, &code);
-      if (code != 0)
-        res = code;
+      if (thread_return != NULL)
+        GetExitCodeThread (thread, thread_return);
 
       CloseHandle (thread);
     }
@@ -80,6 +80,7 @@ ctrl_handler (DWORD ctrl_type)
   HANDLE process;
   PSYMBOL_INFOW info;
   DWORD64 displacement;
+  DWORD thread_return = 0;
 
   count = CaptureStackBackTrace (1l /* skip this function */,
                                  1l /* return only one trace item */, &address,
@@ -134,15 +135,18 @@ ctrl_handler (DWORD ctrl_type)
           return 1;
         }
       /* Inject the remote thread only when asked to */
-      int t = inject_remote_thread_into_process (h, address, exit_code);
-      if (t != 0)
+      if (inject_remote_thread_into_process (h, address, exit_code,
+                                             &thread_return) < 0)
         {
           fprintf (stderr,
-                   "Error while injecting remote thread %d for pid(%d)\n", t,
-                   pid);
+                   "Error while injecting remote thread for pid(%lu)\n", pid);
           exit (1); /*We should exit immediately or else there will a 10s hang
                        waiting for the event to happen.*/
         }
+      if (thread_return)
+        fprintf (stderr,
+                 "Injected remote thread for pid(%lu) returned %lu\n", pid,
+                 thread_return);
     }
   SymCleanup (process);
   if (!SetEvent (CtrlEvent))
@@ -150,77 +154,16 @@ ctrl_handler (DWORD ctrl_type)
       fprintf (stderr, "SetEvent failed (%ld)\n", GetLastError ());
       return 1;
     }
-  exit (0);
+  exit (thread_return != 0);
 }
 
-int
-main (int argc, char **argv)
+/* The easy route for finding the address of CtrlRoutine
+ * would be use GetProcAddress() but this isn't viable
+ * here because that symbol isn't exported.
+ */
+static int
+find_ctrl_routine_the_hard_way ()
 {
-  char *end;
-
-  if (argc == 4)
-    {
-      exit_code = atoi (argv[2]);
-      pid = strtoul (argv[3], NULL, 0);
-    }
-  else if (argc == 2)
-    {
-      pid = 0;
-    }
-  else
-    {
-      fprintf (stderr, "Need a function name, exit code and pid\n"
-               "Or needs a function name.\n");
-      return 1;
-    }
-
-  /* The easy route for finding the address of CtrlRoutine
-   * would be use GetProcAddress() but this isn't viable
-   * because here because that symbol isn't exported.
-   */
-
-  if (strcmp (argv[1], "CtrlRoutine"))
-    {
-      HINSTANCE kernel32 = GetModuleHandle ("kernel32");
-      if (!kernel32)
-        return 1;
-      void *address = (void *)GetProcAddress (kernel32, argv[1]);
-
-      if (!address)
-        {
-          fprintf (stderr, "Could not find address in stack\n");
-          return 1;
-        }
-
-      if (pid == 0)
-        {
-          printf ("%p\n", address);
-          fflush (stdout);
-          return 0;
-        }
-      HANDLE h = OpenProcess (PROCESS_CREATE_THREAD |
-                              PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION |
-                              PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, pid);
-      if (h == NULL)
-        {
-          fprintf (stderr, "OpenProcess failed: %ld\n", GetLastError ());
-          return 1;
-        }
-      /* Inject the remote thread */
-      if (inject_remote_thread_into_process (h, (LPTHREAD_START_ROUTINE)address,
-                                             exit_code) < 0)
-        {
-          fprintf (stderr, "Could not inject thread into process %d\n", pid);
-          return 1;
-        }
-      return 0;
-    }
-  if (argc > 4)
-    {
-      fprintf (stderr, "Unhandled option: %s\n", argv[4]);
-      return 1;
-    }
-
   /*
    * Avoid terminating all processes attached to the current console;
    * This would happen if we used the same console as the caller, though,
@@ -249,32 +192,119 @@ main (int argc, char **argv)
         }
     }
 
+  CtrlEvent = CreateEvent (NULL,  // default security attributes
+                           TRUE,  // manual-reset event
+                           FALSE, // initial state is nonsignaled
+                           NULL   // object name
+  );
+
+  if (CtrlEvent == NULL)
+    {
+      fprintf (stderr, "CreateEvent failed (%ld)\n", GetLastError ());
+      return 1;
+    }
+
+
   if (!SetConsoleCtrlHandler (ctrl_handler, TRUE))
     {
       fprintf (stderr, "Could not register Ctrl handler\n");
       return 1;
     }
+
   if (!GenerateConsoleCtrlEvent (CTRL_BREAK_EVENT, 0))
     {
       fprintf (stderr, "Could not simulate Ctrl+Break\n");
       return 1;
     }
 
-  CtrlEvent = CreateEvent (NULL,              // default security attributes
-                           TRUE,              // manual-reset event
-                           FALSE,             // initial state is nonsignaled
-                           TEXT ("CtrlEvent") // object name
-  );
-  if (CtrlEvent == NULL)
+  if (WaitForSingleObject (CtrlEvent, 10000 /* 10 seconds*/) != WAIT_OBJECT_0)
     {
-      fprintf (stderr, "CreateEvent failed (%ld)\n", GetLastError ());
+      fprintf (stderr, "WaitForSingleObject failed (%ld)\n", GetLastError ());
       return 1;
     }
+  return 0;
+}
 
-  if (WaitForSingleObject (CtrlEvent, 10000 /* 10 seconds*/) != WAIT_OBJECT_0)
+static void *
+get_proc_addr (const char * module_name, const char * function_name)
+{
+      HMODULE module = GetModuleHandle (module_name);
+      if (!module)
+        return NULL;
+      return (void *)GetProcAddress (module, function_name);
+}
+
+int
+main (int argc, char **argv)
+{
+  char *end;
+  void *address;
+  BOOL is_ctrl_routine;
+  DWORD thread_return = 0;
+
+  if (argc == 4)
     {
-      fprintf (stderr, "WaitForSingleObject failed (%ld)\n", GetLastError ());
+      exit_code = atoi (argv[2]);
+      pid = strtoul (argv[3], NULL, 0);
+    }
+  else if (argc == 2)
+    {
+      pid = 0;
+    }
+  else
+    {
+      fprintf (stderr, "Need a function name, exit code and pid\n"
+               "Or needs a function name.\n");
+      return 1;
+    }
+
+  is_ctrl_routine = strcmp (argv[1], "CtrlRoutine") == 0;
+  address = get_proc_addr ("kernel32", argv[1]);
+  if (is_ctrl_routine && !address)
+    {
+      /* CtrlRoutine is undocumented, and has been seen in both
+       * kernel32 and kernelbase
+       */
+      address = get_proc_addr ("kernelbase", argv[1]);
+      if (!address)
+        return find_ctrl_routine_the_hard_way ();
+    }
+
+  if (!address)
+    {
+      fprintf (stderr, "Could not get proc address\n");
+      return 1;
+    }
+
+  if (pid == 0)
+    {
+      printf ("%p\n", address);
+      fflush (stdout);
+      return 0;
+    }
+  HANDLE h = OpenProcess (PROCESS_CREATE_THREAD |
+                          PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION |
+                          PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, pid);
+  if (h == NULL)
+    {
+      fprintf (stderr, "OpenProcess failed: %ld\n", GetLastError ());
+      return 1;
+    }
+  /* Inject the remote thread */
+  if (inject_remote_thread_into_process (h, (LPTHREAD_START_ROUTINE)address,
+                                         exit_code, &thread_return) < 0)
+    {
+      fprintf (stderr, "Could not inject thread into process %lu\n", pid);
       return 1;
     }
+
+  if (is_ctrl_routine && thread_return)
+    {
+      fprintf (stderr,
+               "Injected remote thread for pid %lu returned %lu\n", pid,
+               thread_return);
+      return 1;
+    }
+
   return 0;
 }
-- 
2.32.0.windows.1

