remove verbose mode
[sven/jattach.git] / src / jattach_windows.c
1 /*
2  * Copyright 2016 Andrei Pangin
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <Windows.h>
20
21 typedef HMODULE (WINAPI *GetModuleHandle_t)(LPCTSTR lpModuleName);
22 typedef FARPROC (WINAPI *GetProcAddress_t)(HMODULE hModule, LPCSTR lpProcName);
23 typedef int (__stdcall *JVM_EnqueueOperation_t)(char* cmd, char* arg0, char* arg1, char* arg2, char* pipename);
24
25 typedef struct {
26     GetModuleHandle_t GetModuleHandleA;
27     GetProcAddress_t GetProcAddress;
28     char strJvm[32];
29     char strEnqueue[32];
30     char pipeName[MAX_PATH];
31     char args[4][MAX_PATH];
32 } CallData;
33
34
35 #pragma check_stack(off)
36
37 // This code is executed in remote JVM process; be careful with memory it accesses
38 DWORD WINAPI remote_thread_entry(LPVOID param) {
39     CallData* data = (CallData*)param;
40
41     HMODULE libJvm = data->GetModuleHandleA(data->strJvm);
42     if (libJvm != NULL) {
43         JVM_EnqueueOperation_t JVM_EnqueueOperation = (JVM_EnqueueOperation_t)data->GetProcAddress(libJvm, data->strEnqueue);
44         if (JVM_EnqueueOperation != NULL) {
45             return (DWORD)JVM_EnqueueOperation(data->args[0], data->args[1], data->args[2], data->args[3], data->pipeName);
46         }
47     }
48
49     return 0xffff;
50 }
51
52 #pragma check_stack
53
54
55 // Allocate executable memory in remote process
56 static LPTHREAD_START_ROUTINE allocate_code(HANDLE hProcess) {
57     SIZE_T codeSize = 1024;
58     LPVOID code = VirtualAllocEx(hProcess, NULL, codeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
59     if (code != NULL) {
60         WriteProcessMemory(hProcess, code, remote_thread_entry, codeSize, NULL);
61     }
62     return (LPTHREAD_START_ROUTINE)code;
63 }
64
65 // Allocate memory for CallData in remote process
66 static LPVOID allocate_data(HANDLE hProcess, char* pipeName, int argc, char** argv) {
67     CallData data;
68     data.GetModuleHandleA = GetModuleHandleA;
69     data.GetProcAddress = GetProcAddress;
70     strcpy(data.strJvm, "jvm");
71     strcpy(data.strEnqueue, "JVM_EnqueueOperation");
72     strcpy(data.pipeName, pipeName);
73
74     int i;
75     for (i = 0; i < 4; i++) {
76         strcpy(data.args[i], i < argc ? argv[i] : "");
77     }
78
79     LPVOID remoteData = VirtualAllocEx(hProcess, NULL, sizeof(CallData), MEM_COMMIT, PAGE_READWRITE);
80     if (remoteData != NULL) {
81         WriteProcessMemory(hProcess, remoteData, &data, sizeof(data), NULL);
82     }
83     return remoteData;
84 }
85
86 static void print_error(const char* msg, DWORD code) {
87     printf("%s (error code = %d)\n", msg, code);
88 }
89
90 // If the process is owned by another user, request SeDebugPrivilege to open it.
91 // Debug privileges are typically granted to Administrators.
92 static int enable_debug_privileges() {
93     HANDLE hToken;
94     if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, FALSE, &hToken)) {
95         if (!ImpersonateSelf(SecurityImpersonation) ||
96             !OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, FALSE, &hToken)) {
97             return 0;
98         }
99     }
100
101     LUID luid;
102     if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {
103         return 0;
104     }
105
106     TOKEN_PRIVILEGES tp;
107     tp.PrivilegeCount = 1;
108     tp.Privileges[0].Luid = luid;
109     tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
110
111     BOOL success = AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
112     CloseHandle(hToken);
113     return success ? 1 : 0;
114 }
115
116 // The idea of Dynamic Attach on Windows is to inject a thread into remote JVM
117 // that calls JVM_EnqueueOperation() function exported by HotSpot DLL
118 static int inject_thread(int pid, char* pipeName, int argc, char** argv) {
119     HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid);
120     if (hProcess == NULL && GetLastError() == ERROR_ACCESS_DENIED) {
121         if (!enable_debug_privileges()) {
122             print_error("Not enough privileges", GetLastError());
123             return 0;
124         }
125         hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid);
126     }
127     if (hProcess == NULL) {
128         print_error("Could not open process", GetLastError());
129         return 0;
130     }
131
132     LPTHREAD_START_ROUTINE code = allocate_code(hProcess);
133     LPVOID data = code != NULL ? allocate_data(hProcess, pipeName, argc, argv) : NULL;
134     if (data == NULL) {
135         print_error("Could not allocate memory in target process", GetLastError());
136         CloseHandle(hProcess);
137         return 0;
138     }
139
140     int success = 1;
141     HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, code, data, 0, NULL);
142     if (hThread == NULL) {
143         print_error("Could not create remote thread", GetLastError());
144         success = 0;
145     } else {
146         printf("Connected to remote process\n");
147         WaitForSingleObject(hThread, INFINITE);
148         DWORD exitCode;
149         GetExitCodeThread(hThread, &exitCode);
150         if (exitCode != 0) {
151             print_error("Attach is not supported by the target process", exitCode);
152             success = 0;
153         }
154         CloseHandle(hThread);
155     }
156
157     VirtualFreeEx(hProcess, code, 0, MEM_RELEASE);
158     VirtualFreeEx(hProcess, data, 0, MEM_RELEASE);
159     CloseHandle(hProcess);
160
161     return success;
162 }
163
164 // JVM response is read from the pipe and mirrored to stdout
165 static int read_response(HANDLE hPipe) {
166     ConnectNamedPipe(hPipe, NULL);
167
168     char buf[8192];
169     DWORD bytesRead;
170     if (!ReadFile(hPipe, buf, sizeof(buf) - 1, &bytesRead, NULL)) {
171         print_error("Error reading response", GetLastError());
172         return 1;
173     }
174
175     // First line of response is the command result code
176     buf[bytesRead] = 0;
177     int result = atoi(buf);
178
179     do {
180         fwrite(buf, 1, bytesRead, stdout);
181     } while (ReadFile(hPipe, buf, sizeof(buf), &bytesRead, NULL));
182
183     return result;
184 }
185
186 int main(int argc, char** argv) {
187     if (argc < 3) {
188         printf("jattach " JATTACH_VERSION " built on " __DATE__ "\n"
189                "Copyright 2018 Andrei Pangin\n"
190                "\n"
191                "Usage: jattach <pid> <cmd> [args ...]\n");
192         return 1;
193     }
194
195     int pid = atoi(argv[1]);
196
197     char pipeName[MAX_PATH];
198     sprintf(pipeName, "\\\\.\\pipe\\javatool%d", GetTickCount());
199     HANDLE hPipe = CreateNamedPipe(pipeName, PIPE_ACCESS_INBOUND, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
200         1, 4096, 8192, NMPWAIT_USE_DEFAULT_WAIT, NULL);
201     if (hPipe == NULL) {
202         print_error("Could not create pipe", GetLastError());
203         return 1;
204     }
205
206     if (!inject_thread(pid, pipeName, argc - 2, argv + 2)) {
207         CloseHandle(hPipe);
208         return 1;
209     }
210
211     printf("Response code = ");
212     fflush(stdout);
213
214     int result = read_response(hPipe);
215     printf("\n");
216     CloseHandle(hPipe);
217
218     return result;
219 }