2 * Copyright 2016 Andrei Pangin
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
22 typedef HMODULE (WINAPI *GetModuleHandle_t)(LPCTSTR lpModuleName);
23 typedef FARPROC (WINAPI *GetProcAddress_t)(HMODULE hModule, LPCSTR lpProcName);
24 typedef int (__stdcall *JVM_EnqueueOperation_t)(char* cmd, char* arg0, char* arg1, char* arg2, char* pipename);
27 GetModuleHandle_t GetModuleHandleA;
28 GetProcAddress_t GetProcAddress;
31 char pipeName[MAX_PATH];
32 char args[4][MAX_PATH];
36 #pragma check_stack(off)
38 // This code is executed in remote JVM process; be careful with memory it accesses
39 static DWORD WINAPI remote_thread_entry(LPVOID param) {
40 CallData* data = (CallData*)param;
42 HMODULE libJvm = data->GetModuleHandleA(data->strJvm);
47 JVM_EnqueueOperation_t JVM_EnqueueOperation = (JVM_EnqueueOperation_t)data->GetProcAddress(libJvm, data->strEnqueue + 1);
48 if (JVM_EnqueueOperation == NULL) {
49 // Try alternative name: _JVM_EnqueueOperation@20
50 data->strEnqueue[21] = '@';
51 data->strEnqueue[22] = '2';
52 data->strEnqueue[23] = '0';
53 data->strEnqueue[24] = 0;
55 JVM_EnqueueOperation = (JVM_EnqueueOperation_t)data->GetProcAddress(libJvm, data->strEnqueue);
56 if (JVM_EnqueueOperation == NULL) {
61 return (DWORD)JVM_EnqueueOperation(data->args[0], data->args[1], data->args[2], data->args[3], data->pipeName);
64 static VOID WINAPI remote_thread_entry_end() {
70 // Allocate executable memory in remote process
71 static LPTHREAD_START_ROUTINE allocate_code(HANDLE hProcess) {
72 SIZE_T codeSize = (SIZE_T)remote_thread_entry_end - (SIZE_T)remote_thread_entry;
73 LPVOID code = VirtualAllocEx(hProcess, NULL, codeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
75 WriteProcessMemory(hProcess, code, remote_thread_entry, codeSize, NULL);
77 return (LPTHREAD_START_ROUTINE)code;
80 // Allocate memory for CallData in remote process
81 static LPVOID allocate_data(HANDLE hProcess, char* pipeName, int argc, char** argv) {
83 data.GetModuleHandleA = GetModuleHandleA;
84 data.GetProcAddress = GetProcAddress;
85 strcpy(data.strJvm, "jvm");
86 strcpy(data.strEnqueue, "_JVM_EnqueueOperation");
87 strcpy(data.pipeName, pipeName);
90 for (i = 0; i < 4; i++) {
91 strcpy(data.args[i], i < argc ? argv[i] : "");
94 LPVOID remoteData = VirtualAllocEx(hProcess, NULL, sizeof(CallData), MEM_COMMIT, PAGE_READWRITE);
95 if (remoteData != NULL) {
96 WriteProcessMemory(hProcess, remoteData, &data, sizeof(data), NULL);
101 static void print_error(const char* msg, DWORD code) {
102 printf("%s (error code = %d)\n", msg, code);
105 // If the process is owned by another user, request SeDebugPrivilege to open it.
106 // Debug privileges are typically granted to Administrators.
107 static int enable_debug_privileges() {
109 if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, FALSE, &hToken)) {
110 if (!ImpersonateSelf(SecurityImpersonation) ||
111 !OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, FALSE, &hToken)) {
117 if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {
122 tp.PrivilegeCount = 1;
123 tp.Privileges[0].Luid = luid;
124 tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
126 BOOL success = AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
128 return success ? 1 : 0;
131 // Fail if attaching 64-bit jattach to 32-bit JVM or vice versa
132 static int check_bitness(HANDLE hProcess) {
134 BOOL targetWow64 = FALSE;
135 if (IsWow64Process(hProcess, &targetWow64) && targetWow64) {
136 printf("Cannot attach 64-bit process to 32-bit JVM\n");
140 BOOL thisWow64 = FALSE;
141 BOOL targetWow64 = FALSE;
142 if (IsWow64Process(GetCurrentProcess(), &thisWow64) && IsWow64Process(hProcess, &targetWow64)) {
143 if (thisWow64 != targetWow64) {
144 printf("Cannot attach 32-bit process to 64-bit JVM\n");
152 // The idea of Dynamic Attach on Windows is to inject a thread into remote JVM
153 // that calls JVM_EnqueueOperation() function exported by HotSpot DLL
154 static int inject_thread(int pid, char* pipeName, int argc, char** argv) {
155 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid);
156 if (hProcess == NULL && GetLastError() == ERROR_ACCESS_DENIED) {
157 if (!enable_debug_privileges()) {
158 print_error("Not enough privileges", GetLastError());
161 hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid);
163 if (hProcess == NULL) {
164 print_error("Could not open process", GetLastError());
168 if (!check_bitness(hProcess)) {
169 CloseHandle(hProcess);
173 LPTHREAD_START_ROUTINE code = allocate_code(hProcess);
174 LPVOID data = code != NULL ? allocate_data(hProcess, pipeName, argc, argv) : NULL;
176 print_error("Could not allocate memory in target process", GetLastError());
177 CloseHandle(hProcess);
182 HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, code, data, 0, NULL);
183 if (hThread == NULL) {
184 print_error("Could not create remote thread", GetLastError());
187 printf("Connected to remote process\n");
188 WaitForSingleObject(hThread, INFINITE);
190 GetExitCodeThread(hThread, &exitCode);
192 print_error("Attach is not supported by the target process", exitCode);
195 CloseHandle(hThread);
198 VirtualFreeEx(hProcess, code, 0, MEM_RELEASE);
199 VirtualFreeEx(hProcess, data, 0, MEM_RELEASE);
200 CloseHandle(hProcess);
205 // JVM response is read from the pipe and mirrored to stdout
206 static int read_response(HANDLE hPipe) {
207 ConnectNamedPipe(hPipe, NULL);
211 if (!ReadFile(hPipe, buf, sizeof(buf) - 1, &bytesRead, NULL)) {
212 print_error("Error reading response", GetLastError());
216 // First line of response is the command result code
218 int result = atoi(buf);
221 fwrite(buf, 1, bytesRead, stdout);
222 } while (ReadFile(hPipe, buf, sizeof(buf), &bytesRead, NULL));
227 int jattach(int pid, int argc, char** argv) {
228 // When attaching as an Administrator, make sure the target process can connect to our pipe,
229 // i.e. allow read-write access to everyone. For the complete format description, see
230 // https://docs.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-string-format
231 SECURITY_ATTRIBUTES sec = {sizeof(SECURITY_ATTRIBUTES), NULL, FALSE};
232 ConvertStringSecurityDescriptorToSecurityDescriptor("D:(A;;GRGW;;;WD)", SDDL_REVISION_1,
233 &sec.lpSecurityDescriptor, NULL);
235 char pipeName[MAX_PATH];
236 sprintf(pipeName, "\\\\.\\pipe\\javatool%d", GetTickCount());
237 HANDLE hPipe = CreateNamedPipe(pipeName, PIPE_ACCESS_INBOUND, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
238 1, 4096, 8192, NMPWAIT_USE_DEFAULT_WAIT, &sec);
240 print_error("Could not create pipe", GetLastError());
241 LocalFree(sec.lpSecurityDescriptor);
245 LocalFree(sec.lpSecurityDescriptor);
247 if (!inject_thread(pid, pipeName, argc, argv)) {
252 printf("Response code = ");
255 int result = read_response(hPipe);
262 #ifdef JATTACH_VERSION
264 int main(int argc, char** argv) {
266 printf("jattach " JATTACH_VERSION " built on " __DATE__ "\n"
267 "Copyright 2021 Andrei Pangin\n"
269 "Usage: jattach <pid> <cmd> [args ...]\n"
272 " load threaddump dumpheap setflag properties\n"
273 " jcmd inspectheap datadump printflag agentProperties\n"
278 int pid = atoi(argv[1]);
280 fprintf(stderr, "%s is not a valid process ID\n", argv[1]);
284 return jattach(pid, argc - 2, argv + 2);
287 #endif // JATTACH_VERSION