Skip to content

Commit f7f1624

Browse files
dschonaveen521kk
andcommitted
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 <[email protected]> Signed-off-by: Johannes Schindelin <[email protected]>
1 parent 7e67f56 commit f7f1624

File tree

3 files changed

+330
-0
lines changed

3 files changed

+330
-0
lines changed

winsup/configure.ac

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ if test "x$with_cross_bootstrap" != "xyes"; then
106106
test -n "$MINGW_CXX" || AC_MSG_ERROR([no acceptable MinGW g++ found in \$PATH])
107107
AC_CHECK_PROGS(MINGW_CC, ${target_cpu}-w64-mingw32-gcc)
108108
test -n "$MINGW_CC" || AC_MSG_ERROR([no acceptable MinGW gcc found in \$PATH])
109+
110+
AC_CHECK_PROGS(MINGW32_CC, i686-w64-mingw32-gcc)
111+
test -n "$MINGW32_CC" || AC_MSG_ERROR([no acceptable mingw32 gcc found in \$PATH])
112+
AC_CHECK_PROGS(MINGW64_CC, x86_64-w64-mingw32-gcc)
113+
test -n "$MINGW64_CC" || AC_MSG_ERROR([no acceptable mingw64 gcc found in \$PATH])
109114
fi
110115
AM_CONDITIONAL(CROSS_BOOTSTRAP, [test "x$with_cross_bootstrap" != "xyes"])
111116

winsup/utils/mingw/Makefile.am

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,21 @@ bin_PROGRAMS = \
2626
ldh \
2727
strace
2828

29+
libexec_PROGRAMS = getprocaddr32 getprocaddr64
30+
31+
# Must *not* use -O2 here, as it screws up the stack backtrace
32+
getprocaddr32.o: %32.o: %.c
33+
$(MINGW32_CC) -c -o $@ $<
34+
35+
getprocaddr32.exe: %.exe: %.o
36+
$(MINGW32_CC) -o $@ $^ -static -ldbghelp
37+
38+
getprocaddr64.o: %64.o: %.c
39+
$(MINGW64_CC) -c -o $@ $<
40+
41+
getprocaddr64.exe: %.exe: %.o
42+
$(MINGW64_CC) -o $@ $^ -static -ldbghelp
43+
2944
cygcheck_SOURCES = \
3045
bloda.cc \
3146
cygcheck.cc \

winsup/utils/mingw/getprocaddr.c

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
/* getprocaddr.c
2+
3+
This program is a helper for getting the pointers for the
4+
functions in kernel32 module, and optionally injects a remote
5+
thread that runs those functions given a pid and exit code.
6+
7+
We use dbghelp.dll to get the pointer to kernel32!CtrlRoutine
8+
because it isn't exported. For that, we try to generate console
9+
event (Ctrl+Break) ourselves, to find the pointer, and it is
10+
printed if asked to, or a remote thread is injected to run the
11+
given function.
12+
13+
This software is a copyrighted work licensed under the terms of the
14+
Cygwin license. Please consult the file "CYGWIN_LICENSE" for
15+
details. */
16+
17+
#include <stdio.h>
18+
#include <windows.h>
19+
20+
/* Include dbghelp.h after windows.h */
21+
#include <dbghelp.h>
22+
23+
static DWORD pid;
24+
static uintptr_t exit_code;
25+
static HANDLE CtrlEvent;
26+
27+
static int
28+
inject_remote_thread_into_process (HANDLE process,
29+
LPTHREAD_START_ROUTINE address,
30+
uintptr_t exit_code,
31+
DWORD *thread_return)
32+
{
33+
int res = -1;
34+
35+
if (!address)
36+
return res;
37+
DWORD thread_id;
38+
HANDLE thread = CreateRemoteThread (process, NULL, 1024 * 1024, address,
39+
(PVOID)exit_code, 0, &thread_id);
40+
if (thread)
41+
{
42+
/*
43+
* Wait up to 10 seconds (arbitrary constant) for the thread to finish;
44+
* Maybe we should wait forever? I have seen Cmd does so, but well...
45+
*/
46+
if (WaitForSingleObject (thread, 10000) == WAIT_OBJECT_0)
47+
res = 0;
48+
/*
49+
According to the docs at MSDN for GetExitCodeThread, it will
50+
get the return value from the function, here CtrlRoutine. So, this
51+
checks if the Ctrl Event is handled correctly by the process.
52+
53+
By some testing I could see CtrlRoutine returns 0 in case where
54+
CtrlEvent set by SetConsoleCtrlHandler is handled correctly, in all
55+
other cases it returns something non-zero(not sure what it that).
56+
*/
57+
if (thread_return != NULL)
58+
GetExitCodeThread (thread, thread_return);
59+
60+
CloseHandle (thread);
61+
}
62+
63+
return res;
64+
}
65+
66+
/* Here, we send a CtrlEvent to the current process for the
67+
* sole purpose of capturing the address of the CtrlRoutine
68+
* function, by looking the stack trace.
69+
*
70+
* This hack is needed because we cannot use GetProcAddress()
71+
* as we do for ExitProcess(), because CtrlRoutine is not
72+
* exported (although the .pdb files ensure that we can see
73+
* it in a debugger).
74+
*/
75+
static WINAPI BOOL
76+
ctrl_handler (DWORD ctrl_type)
77+
{
78+
unsigned short count;
79+
void *address;
80+
HANDLE process;
81+
PSYMBOL_INFOW info;
82+
DWORD64 displacement;
83+
DWORD thread_return = 0;
84+
85+
count = CaptureStackBackTrace (1l /* skip this function */,
86+
1l /* return only one trace item */, &address,
87+
NULL);
88+
if (count != 1)
89+
{
90+
fprintf (stderr, "Could not capture backtrace\n");
91+
return FALSE;
92+
}
93+
94+
process = GetCurrentProcess ();
95+
if (!SymInitialize (process, NULL, TRUE))
96+
{
97+
fprintf (stderr, "Could not initialize symbols\n");
98+
return FALSE;
99+
}
100+
101+
info = (PSYMBOL_INFOW)malloc (sizeof (*info)
102+
+ MAX_SYM_NAME * sizeof (wchar_t));
103+
if (!info)
104+
{
105+
fprintf (stderr, "Could not allocate symbol info structure\n");
106+
return FALSE;
107+
}
108+
info->SizeOfStruct = sizeof (*info);
109+
info->MaxNameLen = MAX_SYM_NAME;
110+
111+
if (!SymFromAddrW (process, (DWORD64) (intptr_t)address, &displacement,
112+
info))
113+
{
114+
fprintf (stderr, "Could not get symbol info\n");
115+
SymCleanup (process);
116+
return FALSE;
117+
}
118+
119+
if (pid == 0)
120+
{
121+
printf ("%p\n", (void *)(intptr_t)info->Address);
122+
}
123+
else
124+
{
125+
LPTHREAD_START_ROUTINE address =
126+
(LPTHREAD_START_ROUTINE) (intptr_t)info->Address;
127+
HANDLE h = OpenProcess (PROCESS_CREATE_THREAD |
128+
PROCESS_QUERY_INFORMATION |
129+
PROCESS_VM_OPERATION |
130+
PROCESS_VM_WRITE |
131+
PROCESS_VM_READ, FALSE, pid);
132+
if (h == NULL)
133+
{
134+
fprintf (stderr, "OpenProcess failed: %ld\n", GetLastError ());
135+
return 1;
136+
}
137+
/* Inject the remote thread only when asked to */
138+
if (inject_remote_thread_into_process (h, address, exit_code,
139+
&thread_return) < 0)
140+
{
141+
fprintf (stderr,
142+
"Error while injecting remote thread for pid(%lu)\n", pid);
143+
exit (1); /*We should exit immediately or else there will a 10s hang
144+
waiting for the event to happen.*/
145+
}
146+
if (thread_return)
147+
fprintf (stderr,
148+
"Injected remote thread for pid(%lu) returned %lu\n", pid,
149+
thread_return);
150+
}
151+
SymCleanup (process);
152+
if (!SetEvent (CtrlEvent))
153+
{
154+
fprintf (stderr, "SetEvent failed (%ld)\n", GetLastError ());
155+
return 1;
156+
}
157+
exit (thread_return != 0);
158+
}
159+
160+
/* The easy route for finding the address of CtrlRoutine
161+
* would be use GetProcAddress() but this isn't viable
162+
* here because that symbol isn't exported.
163+
*/
164+
static int
165+
find_ctrl_routine_the_hard_way ()
166+
{
167+
/*
168+
* Avoid terminating all processes attached to the current console;
169+
* This would happen if we used the same console as the caller, though,
170+
* because we are sending a CtrlEvent on purpose (which _is_ sent to
171+
* all processes connected to the same console, and the other processes
172+
* are most likely unprepared for that CTRL_BREAK_EVENT and would be
173+
* terminated as a consequence, _including the caller_).
174+
*
175+
* In case we get only one result from GetConsoleProcessList(), we don't
176+
* need to create and allocate a new console, and it could avoid a console
177+
* window popping up.
178+
*/
179+
DWORD proc_lists;
180+
if (GetConsoleProcessList (&proc_lists, 5) > 1)
181+
{
182+
if (!FreeConsole () && GetLastError () != ERROR_INVALID_PARAMETER)
183+
{
184+
fprintf (stderr, "Could not detach from current Console: %ld\n",
185+
GetLastError ());
186+
return 1;
187+
}
188+
if (!AllocConsole ())
189+
{
190+
fprintf (stderr, "Could not allocate a new Console\n");
191+
return 1;
192+
}
193+
}
194+
195+
CtrlEvent = CreateEvent (NULL, // default security attributes
196+
TRUE, // manual-reset event
197+
FALSE, // initial state is nonsignaled
198+
NULL // object name
199+
);
200+
201+
if (CtrlEvent == NULL)
202+
{
203+
fprintf (stderr, "CreateEvent failed (%ld)\n", GetLastError ());
204+
return 1;
205+
}
206+
207+
208+
if (!SetConsoleCtrlHandler (ctrl_handler, TRUE))
209+
{
210+
fprintf (stderr, "Could not register Ctrl handler\n");
211+
return 1;
212+
}
213+
214+
if (!GenerateConsoleCtrlEvent (CTRL_BREAK_EVENT, 0))
215+
{
216+
fprintf (stderr, "Could not simulate Ctrl+Break\n");
217+
return 1;
218+
}
219+
220+
if (WaitForSingleObject (CtrlEvent, 10000 /* 10 seconds*/) != WAIT_OBJECT_0)
221+
{
222+
fprintf (stderr, "WaitForSingleObject failed (%ld)\n", GetLastError ());
223+
return 1;
224+
}
225+
return 0;
226+
}
227+
228+
static void *
229+
get_proc_addr (const char * module_name, const char * function_name)
230+
{
231+
HMODULE module = GetModuleHandle (module_name);
232+
if (!module)
233+
return NULL;
234+
return (void *)GetProcAddress (module, function_name);
235+
}
236+
237+
int
238+
main (int argc, char **argv)
239+
{
240+
char *end;
241+
void *address;
242+
BOOL is_ctrl_routine;
243+
DWORD thread_return = 0;
244+
245+
if (argc == 4)
246+
{
247+
exit_code = atoi (argv[2]);
248+
pid = strtoul (argv[3], NULL, 0);
249+
}
250+
else if (argc == 2)
251+
{
252+
pid = 0;
253+
}
254+
else
255+
{
256+
fprintf (stderr, "Need a function name, exit code and pid\n"
257+
"Or needs a function name.\n");
258+
return 1;
259+
}
260+
261+
is_ctrl_routine = strcmp (argv[1], "CtrlRoutine") == 0;
262+
address = get_proc_addr ("kernel32", argv[1]);
263+
if (is_ctrl_routine && !address)
264+
{
265+
/* CtrlRoutine is undocumented, and has been seen in both
266+
* kernel32 and kernelbase
267+
*/
268+
address = get_proc_addr ("kernelbase", argv[1]);
269+
if (!address)
270+
return find_ctrl_routine_the_hard_way ();
271+
}
272+
273+
if (!address)
274+
{
275+
fprintf (stderr, "Could not get proc address\n");
276+
return 1;
277+
}
278+
279+
if (pid == 0)
280+
{
281+
printf ("%p\n", address);
282+
fflush (stdout);
283+
return 0;
284+
}
285+
HANDLE h = OpenProcess (PROCESS_CREATE_THREAD |
286+
PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION |
287+
PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, pid);
288+
if (h == NULL)
289+
{
290+
fprintf (stderr, "OpenProcess failed: %ld\n", GetLastError ());
291+
return 1;
292+
}
293+
/* Inject the remote thread */
294+
if (inject_remote_thread_into_process (h, (LPTHREAD_START_ROUTINE)address,
295+
exit_code, &thread_return) < 0)
296+
{
297+
fprintf (stderr, "Could not inject thread into process %lu\n", pid);
298+
return 1;
299+
}
300+
301+
if (is_ctrl_routine && thread_return)
302+
{
303+
fprintf (stderr,
304+
"Injected remote thread for pid %lu returned %lu\n", pid,
305+
thread_return);
306+
return 1;
307+
}
308+
309+
return 0;
310+
}

0 commit comments

Comments
 (0)