383 lines
12 KiB
Diff
383 lines
12 KiB
Diff
From b57f22d8180e9ae283dafce96436f0c1281dbcc7 Mon Sep 17 00:00:00 2001
|
|
From: Johannes Schindelin <johannes.schindelin@gmx.de>
|
|
Date: Mon, 16 Apr 2018 14:59:39 +0200
|
|
Subject: [PATCH 24/N] Add a helper to obtain a function's address in
|
|
kernel32.dll
|
|
|
|
In particular, we are interested in the address of the CtrlRoutine
|
|
and the ExitProcess functions. Since kernel32.dll is loaded first thing,
|
|
the addresses will be the same for all processes (matching the
|
|
CPU architecture, of course).
|
|
|
|
This will help us with emulating SIGINT properly (by not sending signals
|
|
to *all* processes attached to the same Console, as
|
|
GenerateConsoleCtrlEvent() would do).
|
|
|
|
Co-authored-by: Naveen M K <naveen@syrusdark.website>
|
|
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
|
|
---
|
|
winsup/configure.ac | 5 +
|
|
winsup/utils/mingw/Makefile.am | 15 ++
|
|
winsup/utils/mingw/getprocaddr.c | 310 +++++++++++++++++++++++++++++++
|
|
3 files changed, 330 insertions(+)
|
|
create mode 100644 winsup/utils/mingw/getprocaddr.c
|
|
|
|
diff --git a/winsup/configure.ac b/winsup/configure.ac
|
|
index 9b9b59d..b9e3977 100644
|
|
--- a/winsup/configure.ac
|
|
+++ b/winsup/configure.ac
|
|
@@ -106,6 +106,11 @@ if test "x$with_cross_bootstrap" != "xyes"; then
|
|
test -n "$MINGW_CXX" || AC_MSG_ERROR([no acceptable MinGW g++ found in \$PATH])
|
|
AC_CHECK_PROGS(MINGW_CC, ${target_cpu}-w64-mingw32-gcc)
|
|
test -n "$MINGW_CC" || AC_MSG_ERROR([no acceptable MinGW gcc found in \$PATH])
|
|
+
|
|
+ AC_CHECK_PROGS(MINGW32_CC, i686-w64-mingw32-gcc)
|
|
+ test -n "$MINGW32_CC" || AC_MSG_ERROR([no acceptable mingw32 gcc found in \$PATH])
|
|
+ AC_CHECK_PROGS(MINGW64_CC, x86_64-w64-mingw32-gcc)
|
|
+ test -n "$MINGW64_CC" || AC_MSG_ERROR([no acceptable mingw64 gcc found in \$PATH])
|
|
fi
|
|
AM_CONDITIONAL(CROSS_BOOTSTRAP, [test "x$with_cross_bootstrap" != "xyes"])
|
|
|
|
diff --git a/winsup/utils/mingw/Makefile.am b/winsup/utils/mingw/Makefile.am
|
|
index 7f7317a..07b9f92 100644
|
|
--- a/winsup/utils/mingw/Makefile.am
|
|
+++ b/winsup/utils/mingw/Makefile.am
|
|
@@ -26,6 +26,21 @@ bin_PROGRAMS = \
|
|
ldh \
|
|
strace
|
|
|
|
+libexec_PROGRAMS = getprocaddr32 getprocaddr64
|
|
+
|
|
+# Must *not* use -O2 here, as it screws up the stack backtrace
|
|
+getprocaddr32.o: %32.o: %.c
|
|
+ $(MINGW32_CC) -c -o $@ $<
|
|
+
|
|
+getprocaddr32.exe: %.exe: %.o
|
|
+ $(MINGW32_CC) -o $@ $^ -static -ldbghelp
|
|
+
|
|
+getprocaddr64.o: %64.o: %.c
|
|
+ $(MINGW64_CC) -c -o $@ $<
|
|
+
|
|
+getprocaddr64.exe: %.exe: %.o
|
|
+ $(MINGW64_CC) -o $@ $^ -static -ldbghelp
|
|
+
|
|
cygcheck_SOURCES = \
|
|
bloda.cc \
|
|
cygcheck.cc \
|
|
diff --git a/winsup/utils/mingw/getprocaddr.c b/winsup/utils/mingw/getprocaddr.c
|
|
new file mode 100644
|
|
index 0000000..25814c7
|
|
--- /dev/null
|
|
+++ b/winsup/utils/mingw/getprocaddr.c
|
|
@@ -0,0 +1,310 @@
|
|
+/* getprocaddr.c
|
|
+
|
|
+This program is a helper for getting the pointers for the
|
|
+functions in kernel32 module, and optionally injects a remote
|
|
+thread that runs those functions given a pid and exit code.
|
|
+
|
|
+We use dbghelp.dll to get the pointer to kernel32!CtrlRoutine
|
|
+because it isn't exported. For that, we try to generate console
|
|
+event (Ctrl+Break) ourselves, to find the pointer, and it is
|
|
+printed if asked to, or a remote thread is injected to run the
|
|
+given function.
|
|
+
|
|
+This software is a copyrighted work licensed under the terms of the
|
|
+Cygwin license. Please consult the file "CYGWIN_LICENSE" for
|
|
+details. */
|
|
+
|
|
+#include <stdio.h>
|
|
+#include <windows.h>
|
|
+
|
|
+/* Include dbghelp.h after windows.h */
|
|
+#include <dbghelp.h>
|
|
+
|
|
+static DWORD pid;
|
|
+static uintptr_t exit_code;
|
|
+static HANDLE CtrlEvent;
|
|
+
|
|
+static int
|
|
+inject_remote_thread_into_process (HANDLE process,
|
|
+ LPTHREAD_START_ROUTINE address,
|
|
+ uintptr_t exit_code,
|
|
+ DWORD *thread_return)
|
|
+{
|
|
+ int res = -1;
|
|
+
|
|
+ if (!address)
|
|
+ return res;
|
|
+ DWORD thread_id;
|
|
+ HANDLE thread = CreateRemoteThread (process, NULL, 1024 * 1024, address,
|
|
+ (PVOID)exit_code, 0, &thread_id);
|
|
+ if (thread)
|
|
+ {
|
|
+ /*
|
|
+ * Wait up to 10 seconds (arbitrary constant) for the thread to finish;
|
|
+ * Maybe we should wait forever? I have seen Cmd does so, but well...
|
|
+ */
|
|
+ if (WaitForSingleObject (thread, 10000) == WAIT_OBJECT_0)
|
|
+ res = 0;
|
|
+ /*
|
|
+ According to the docs at MSDN for GetExitCodeThread, it will
|
|
+ get the return value from the function, here CtrlRoutine. So, this
|
|
+ checks if the Ctrl Event is handled correctly by the process.
|
|
+
|
|
+ By some testing I could see CtrlRoutine returns 0 in case where
|
|
+ CtrlEvent set by SetConsoleCtrlHandler is handled correctly, in all
|
|
+ other cases it returns something non-zero(not sure what it that).
|
|
+ */
|
|
+ if (thread_return != NULL)
|
|
+ GetExitCodeThread (thread, thread_return);
|
|
+
|
|
+ CloseHandle (thread);
|
|
+ }
|
|
+
|
|
+ return res;
|
|
+}
|
|
+
|
|
+/* Here, we send a CtrlEvent to the current process for the
|
|
+ * sole purpose of capturing the address of the CtrlRoutine
|
|
+ * function, by looking the stack trace.
|
|
+ *
|
|
+ * This hack is needed because we cannot use GetProcAddress()
|
|
+ * as we do for ExitProcess(), because CtrlRoutine is not
|
|
+ * exported (although the .pdb files ensure that we can see
|
|
+ * it in a debugger).
|
|
+ */
|
|
+static WINAPI BOOL
|
|
+ctrl_handler (DWORD ctrl_type)
|
|
+{
|
|
+ unsigned short count;
|
|
+ void *address;
|
|
+ HANDLE process;
|
|
+ PSYMBOL_INFOW info;
|
|
+ DWORD64 displacement;
|
|
+ DWORD thread_return = 0;
|
|
+
|
|
+ count = CaptureStackBackTrace (1l /* skip this function */,
|
|
+ 1l /* return only one trace item */, &address,
|
|
+ NULL);
|
|
+ if (count != 1)
|
|
+ {
|
|
+ fprintf (stderr, "Could not capture backtrace\n");
|
|
+ return FALSE;
|
|
+ }
|
|
+
|
|
+ process = GetCurrentProcess ();
|
|
+ if (!SymInitialize (process, NULL, TRUE))
|
|
+ {
|
|
+ fprintf (stderr, "Could not initialize symbols\n");
|
|
+ return FALSE;
|
|
+ }
|
|
+
|
|
+ info = (PSYMBOL_INFOW)malloc (sizeof (*info)
|
|
+ + MAX_SYM_NAME * sizeof (wchar_t));
|
|
+ if (!info)
|
|
+ {
|
|
+ fprintf (stderr, "Could not allocate symbol info structure\n");
|
|
+ return FALSE;
|
|
+ }
|
|
+ info->SizeOfStruct = sizeof (*info);
|
|
+ info->MaxNameLen = MAX_SYM_NAME;
|
|
+
|
|
+ if (!SymFromAddrW (process, (DWORD64) (intptr_t)address, &displacement,
|
|
+ info))
|
|
+ {
|
|
+ fprintf (stderr, "Could not get symbol info\n");
|
|
+ SymCleanup (process);
|
|
+ return FALSE;
|
|
+ }
|
|
+
|
|
+ if (pid == 0)
|
|
+ {
|
|
+ printf ("%p\n", (void *)(intptr_t)info->Address);
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ LPTHREAD_START_ROUTINE address =
|
|
+ (LPTHREAD_START_ROUTINE) (intptr_t)info->Address;
|
|
+ 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 only when asked to */
|
|
+ if (inject_remote_thread_into_process (h, address, exit_code,
|
|
+ &thread_return) < 0)
|
|
+ {
|
|
+ fprintf (stderr,
|
|
+ "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))
|
|
+ {
|
|
+ fprintf (stderr, "SetEvent failed (%ld)\n", GetLastError ());
|
|
+ return 1;
|
|
+ }
|
|
+ exit (thread_return != 0);
|
|
+}
|
|
+
|
|
+/* 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 ()
|
|
+{
|
|
+ /*
|
|
+ * Avoid terminating all processes attached to the current console;
|
|
+ * This would happen if we used the same console as the caller, though,
|
|
+ * because we are sending a CtrlEvent on purpose (which _is_ sent to
|
|
+ * all processes connected to the same console, and the other processes
|
|
+ * are most likely unprepared for that CTRL_BREAK_EVENT and would be
|
|
+ * terminated as a consequence, _including the caller_).
|
|
+ *
|
|
+ * In case we get only one result from GetConsoleProcessList(), we don't
|
|
+ * need to create and allocate a new console, and it could avoid a console
|
|
+ * window popping up.
|
|
+ */
|
|
+ DWORD proc_lists;
|
|
+ if (GetConsoleProcessList (&proc_lists, 5) > 1)
|
|
+ {
|
|
+ if (!FreeConsole () && GetLastError () != ERROR_INVALID_PARAMETER)
|
|
+ {
|
|
+ fprintf (stderr, "Could not detach from current Console: %ld\n",
|
|
+ GetLastError ());
|
|
+ return 1;
|
|
+ }
|
|
+ if (!AllocConsole ())
|
|
+ {
|
|
+ fprintf (stderr, "Could not allocate a new Console\n");
|
|
+ return 1;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ 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;
|
|
+ }
|
|
+
|
|
+ if (WaitForSingleObject (CtrlEvent, 10000 /* 10 seconds*/) != WAIT_OBJECT_0)
|
|
+ {
|
|
+ fprintf (stderr, "WaitForSingleObject failed (%ld)\n", GetLastError ());
|
|
+ return 1;
|
|
+ }
|
|
+ return 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)
|
|
+ {
|
|
+ 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;
|
|
+}
|