| 1 | /************************************************************************** |
|---|
| 2 | * Copyright (C) 2004-2007 by Michael Medin <michael@medin.name> * |
|---|
| 3 | * * |
|---|
| 4 | * This code is part of NSClient++ - http://trac.nakednuns.org/nscp * |
|---|
| 5 | * * |
|---|
| 6 | * This program is free software; you can redistribute it and/or modify * |
|---|
| 7 | * it under the terms of the GNU General Public License as published by * |
|---|
| 8 | * the Free Software Foundation; either version 2 of the License, or * |
|---|
| 9 | * (at your option) any later version. * |
|---|
| 10 | * * |
|---|
| 11 | * This program is distributed in the hope that it will be useful, * |
|---|
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * |
|---|
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * |
|---|
| 14 | * GNU General Public License for more details. * |
|---|
| 15 | * * |
|---|
| 16 | * You should have received a copy of the GNU General Public License * |
|---|
| 17 | * along with this program; if not, write to the * |
|---|
| 18 | * Free Software Foundation, Inc., * |
|---|
| 19 | * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * |
|---|
| 20 | ***************************************************************************/ |
|---|
| 21 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers |
|---|
| 22 | #include <windows.h> |
|---|
| 23 | #include <tchar.h> |
|---|
| 24 | #include <iostream> |
|---|
| 25 | |
|---|
| 26 | #include "EnumProcess.h" |
|---|
| 27 | |
|---|
| 28 | ////////////////////////////////////////////////////////////////////// |
|---|
| 29 | // Construction/Destruction |
|---|
| 30 | ////////////////////////////////////////////////////////////////////// |
|---|
| 31 | |
|---|
| 32 | |
|---|
| 33 | CEnumProcess::CEnumProcess() : PSAPI(NULL), VDMDBG(NULL), FVDMEnumTaskWOWEx(NULL) |
|---|
| 34 | { |
|---|
| 35 | PSAPI = ::LoadLibrary(_TEXT("PSAPI")); |
|---|
| 36 | if (PSAPI) |
|---|
| 37 | { |
|---|
| 38 | // Find PSAPI functions |
|---|
| 39 | FEnumProcesses = (PFEnumProcesses)::GetProcAddress(PSAPI, "EnumProcesses"); |
|---|
| 40 | FEnumProcessModules = (PFEnumProcessModules)::GetProcAddress(PSAPI, "EnumProcessModules"); |
|---|
| 41 | #ifdef UNICODE |
|---|
| 42 | FGetModuleFileNameEx = (PFGetModuleFileNameEx)::GetProcAddress(PSAPI, "GetModuleFileNameExW"); |
|---|
| 43 | #else |
|---|
| 44 | FGetModuleFileNameEx = (PFGetModuleFileNameEx)::GetProcAddress(PSAPI, "GetModuleFileNameExA"); |
|---|
| 45 | #endif |
|---|
| 46 | } |
|---|
| 47 | |
|---|
| 48 | VDMDBG = ::LoadLibrary(_TEXT("VDMDBG")); |
|---|
| 49 | if (VDMDBG) |
|---|
| 50 | { |
|---|
| 51 | // Find VDMdbg functions |
|---|
| 52 | FVDMEnumTaskWOWEx = (PFVDMEnumTaskWOWEx)::GetProcAddress(VDMDBG, "VDMEnumTaskWOWEx"); |
|---|
| 53 | } |
|---|
| 54 | } |
|---|
| 55 | |
|---|
| 56 | CEnumProcess::~CEnumProcess() |
|---|
| 57 | { |
|---|
| 58 | if (PSAPI) FreeLibrary(PSAPI); |
|---|
| 59 | if (VDMDBG) FreeLibrary(VDMDBG); |
|---|
| 60 | } |
|---|
| 61 | |
|---|
| 62 | struct find_16bit_container { |
|---|
| 63 | std::list<CEnumProcess::CProcessEntry> *target; |
|---|
| 64 | DWORD pid; |
|---|
| 65 | }; |
|---|
| 66 | BOOL CALLBACK Enum16Proc( DWORD dwThreadId, WORD hMod16, WORD hTask16, PSZ pszModName, PSZ pszFileName, LPARAM lpUserDefined ) |
|---|
| 67 | { |
|---|
| 68 | find_16bit_container *container = reinterpret_cast<find_16bit_container*>(lpUserDefined); |
|---|
| 69 | CEnumProcess::CProcessEntry pEntry; |
|---|
| 70 | pEntry.dwPID = container->pid; |
|---|
| 71 | pEntry.command_line = pszFileName; |
|---|
| 72 | std::string::size_type pos = pEntry.command_line.find_last_of("\\"); |
|---|
| 73 | if (pos != std::string::npos) |
|---|
| 74 | pEntry.filename = pEntry.command_line.substr(++pos); |
|---|
| 75 | else |
|---|
| 76 | pEntry.filename = pEntry.command_line; |
|---|
| 77 | container->target->push_back(pEntry); |
|---|
| 78 | return FALSE; |
|---|
| 79 | } |
|---|
| 80 | |
|---|
| 81 | |
|---|
| 82 | void CEnumProcess::enable_token_privilege(LPTSTR privilege) |
|---|
| 83 | { |
|---|
| 84 | HANDLE hToken; |
|---|
| 85 | TOKEN_PRIVILEGES token_privileges; |
|---|
| 86 | DWORD dwSize; |
|---|
| 87 | ZeroMemory (&token_privileges, sizeof(token_privileges)); |
|---|
| 88 | token_privileges.PrivilegeCount = 1; |
|---|
| 89 | if ( !OpenProcessToken (GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken)) |
|---|
| 90 | throw process_enumeration_exception("Failed to open process token: " + error::lookup::last_error()); |
|---|
| 91 | if (!LookupPrivilegeValue ( NULL, privilege, &token_privileges.Privileges[0].Luid)) { |
|---|
| 92 | CloseHandle (hToken); |
|---|
| 93 | throw process_enumeration_exception("Failed to lookup privilege: " + error::lookup::last_error()); |
|---|
| 94 | } |
|---|
| 95 | token_privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; |
|---|
| 96 | if (!AdjustTokenPrivileges ( hToken, FALSE, &token_privileges, 0, NULL, &dwSize)) { |
|---|
| 97 | CloseHandle (hToken); |
|---|
| 98 | throw process_enumeration_exception("Failed to adjust token privilege: " + error::lookup::last_error()); |
|---|
| 99 | } |
|---|
| 100 | CloseHandle (hToken); |
|---|
| 101 | } |
|---|
| 102 | |
|---|
| 103 | void CEnumProcess::disable_token_privilege(LPTSTR privilege) |
|---|
| 104 | { |
|---|
| 105 | HANDLE hToken; |
|---|
| 106 | TOKEN_PRIVILEGES token_privileges; |
|---|
| 107 | DWORD dwSize; |
|---|
| 108 | ZeroMemory (&token_privileges, sizeof (token_privileges)); |
|---|
| 109 | token_privileges.PrivilegeCount = 1; |
|---|
| 110 | if ( !OpenProcessToken (GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken)) |
|---|
| 111 | throw process_enumeration_exception("Failed to open process token: " + error::lookup::last_error()); |
|---|
| 112 | if (!LookupPrivilegeValue ( NULL, privilege, &token_privileges.Privileges[0].Luid)) { |
|---|
| 113 | CloseHandle (hToken); |
|---|
| 114 | throw process_enumeration_exception("Failed to lookup privilege: " + error::lookup::last_error()); |
|---|
| 115 | } |
|---|
| 116 | token_privileges.Privileges[0].Attributes = SE_PRIVILEGE_REMOVED; |
|---|
| 117 | if (!AdjustTokenPrivileges ( hToken, FALSE, &token_privileges, 0, NULL, &dwSize)) { |
|---|
| 118 | CloseHandle (hToken); |
|---|
| 119 | throw process_enumeration_exception("Failed to adjust token privilege: " + error::lookup::last_error()); |
|---|
| 120 | } |
|---|
| 121 | CloseHandle (hToken); |
|---|
| 122 | } |
|---|
| 123 | |
|---|
| 124 | CEnumProcess::process_list CEnumProcess::enumerate_processes(bool expand_command_line, bool find_16bit, CEnumProcess::error_reporter *error_interface, unsigned int buffer_size) { |
|---|
| 125 | try { |
|---|
| 126 | enable_token_privilege(SE_DEBUG_NAME); |
|---|
| 127 | } catch (process_enumeration_exception &e) { |
|---|
| 128 | if (error_interface!=NULL) |
|---|
| 129 | error_interface->report_warning(e.reason()); |
|---|
| 130 | } |
|---|
| 131 | |
|---|
| 132 | std::list<CProcessEntry> ret; |
|---|
| 133 | DWORD *dwPIDs = new DWORD[buffer_size+1]; |
|---|
| 134 | DWORD cbNeeded = 0; |
|---|
| 135 | BOOL OK = FEnumProcesses(dwPIDs, buffer_size*sizeof(DWORD), &cbNeeded); |
|---|
| 136 | if (cbNeeded >= DEFAULT_BUFFER_SIZE*sizeof(DWORD)) { |
|---|
| 137 | delete [] dwPIDs; |
|---|
| 138 | if (error_interface!=NULL) |
|---|
| 139 | error_interface->report_debug("Need larger buffer: " + strEx::s::xtos(buffer_size)); |
|---|
| 140 | return enumerate_processes(expand_command_line, find_16bit, error_interface, buffer_size * 10); |
|---|
| 141 | } |
|---|
| 142 | if (!OK) { |
|---|
| 143 | delete [] dwPIDs; |
|---|
| 144 | throw process_enumeration_exception("Failed to enumerate process: " + error::lookup::last_error()); |
|---|
| 145 | } |
|---|
| 146 | unsigned int process_count = cbNeeded/sizeof(DWORD); |
|---|
| 147 | for (unsigned int i = 0;i <process_count; ++i) { |
|---|
| 148 | if (dwPIDs[i] == 0) |
|---|
| 149 | continue; |
|---|
| 150 | CProcessEntry entry; |
|---|
| 151 | entry.hung = false; |
|---|
| 152 | try { |
|---|
| 153 | // if (error_interface!=NULL) |
|---|
| 154 | // error_interface->report_debug_enter(_T("describe_pid")); |
|---|
| 155 | try { |
|---|
| 156 | entry = describe_pid(dwPIDs[i], expand_command_line); |
|---|
| 157 | } catch (process_enumeration_exception &e) { |
|---|
| 158 | if (error_interface!=NULL) |
|---|
| 159 | error_interface->report_debug(e.reason()); |
|---|
| 160 | if (expand_command_line) { |
|---|
| 161 | try { |
|---|
| 162 | entry = describe_pid(dwPIDs[i], false); |
|---|
| 163 | } catch (process_enumeration_exception &e) { |
|---|
| 164 | if (error_interface!=NULL) |
|---|
| 165 | error_interface->report_debug(e.reason()); |
|---|
| 166 | } |
|---|
| 167 | } |
|---|
| 168 | } |
|---|
| 169 | // if (error_interface!=NULL) |
|---|
| 170 | // error_interface->report_debug_exit(_T("describe_pid")); |
|---|
| 171 | if (VDMDBG!=NULL&&find_16bit) { |
|---|
| 172 | if (error_interface!=NULL) |
|---|
| 173 | error_interface->report_debug("Looking for 16bit apps"); |
|---|
| 174 | if(stricmp(entry.filename.substr(0,9).c_str(), "NTVDM.EXE") == 0) { |
|---|
| 175 | find_16bit_container container; |
|---|
| 176 | container.target = &ret; |
|---|
| 177 | container.pid = entry.dwPID; |
|---|
| 178 | FVDMEnumTaskWOWEx(entry.dwPID, (TASKENUMPROCEX)&Enum16Proc, (LPARAM) &container); |
|---|
| 179 | } |
|---|
| 180 | } |
|---|
| 181 | ret.push_back(entry); |
|---|
| 182 | } catch (process_enumeration_exception &e) { |
|---|
| 183 | if (error_interface!=NULL) |
|---|
| 184 | error_interface->report_error("Unhandeled exception describing PID: " + strEx::s::xtos(dwPIDs[i]) + ": " + e.reason()); |
|---|
| 185 | } catch (...) { |
|---|
| 186 | if (error_interface!=NULL) |
|---|
| 187 | error_interface->report_error("Unknown exception describing PID: " + strEx::s::xtos(dwPIDs[i])); |
|---|
| 188 | } |
|---|
| 189 | } |
|---|
| 190 | |
|---|
| 191 | std::vector<DWORD> hung_pids = find_crashed_pids(error_interface); |
|---|
| 192 | for (process_list::iterator entry = ret.begin(); entry != ret.end(); ++entry) { |
|---|
| 193 | if (std::find(hung_pids.begin(), hung_pids.end(), entry->dwPID) != hung_pids.end()) |
|---|
| 194 | (*entry).hung = true; |
|---|
| 195 | else |
|---|
| 196 | (*entry).hung = false; |
|---|
| 197 | } |
|---|
| 198 | |
|---|
| 199 | delete [] dwPIDs; |
|---|
| 200 | return ret; |
|---|
| 201 | } |
|---|
| 202 | |
|---|
| 203 | struct enum_data { |
|---|
| 204 | CEnumProcess::error_reporter * error_interface; |
|---|
| 205 | std::vector<DWORD> crashed_pids; |
|---|
| 206 | |
|---|
| 207 | }; |
|---|
| 208 | |
|---|
| 209 | BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam ) { |
|---|
| 210 | enum_data *data = reinterpret_cast<enum_data*>(lParam); |
|---|
| 211 | DWORD pid; |
|---|
| 212 | GetWindowThreadProcessId(hwnd, &pid); |
|---|
| 213 | if (GetWindow(hwnd, GW_OWNER) != NULL) |
|---|
| 214 | return TRUE; |
|---|
| 215 | PDWORD result; |
|---|
| 216 | if (!SendMessageTimeout(hwnd, WM_NULL, 0, 0, SMTO_ABORTIFHUNG, 500, reinterpret_cast<PDWORD_PTR>(&result))) { |
|---|
| 217 | if (data->error_interface!=NULL) |
|---|
| 218 | data->error_interface->report_debug("pid: " + strEx::s::xtos(pid) + " was hung"); |
|---|
| 219 | data->crashed_pids.push_back(pid); |
|---|
| 220 | } |
|---|
| 221 | |
|---|
| 222 | // TCHAR *buffer = new TCHAR[1024]; |
|---|
| 223 | // int len = GetWindowText(hwnd, buffer, 1023); |
|---|
| 224 | // buffer[30] = 0; |
|---|
| 225 | // if (data->error_interface!=NULL) |
|---|
| 226 | // data->error_interface->report_debug(_T("pid: ") + res + strEx::itos(pid) + _T(" - ") + strEx::itos(len) + _T(" - ") + buffer); |
|---|
| 227 | // //std::wcout << _T("pid: ") << pid << _T(" - ") << len << _T(" : ") << buffer << std::endl; |
|---|
| 228 | // delete [] buffer; |
|---|
| 229 | return TRUE; |
|---|
| 230 | } |
|---|
| 231 | |
|---|
| 232 | std::vector<DWORD> CEnumProcess::find_crashed_pids(CEnumProcess::error_reporter * error_interface) { |
|---|
| 233 | enum_data data; |
|---|
| 234 | data.error_interface = error_interface; |
|---|
| 235 | if(!EnumWindows(&EnumWindowsProc, reinterpret_cast<LPARAM>(&data))) { |
|---|
| 236 | if (error_interface) |
|---|
| 237 | error_interface->report_error("Failed to enumerate windows: " + utf8::cvt<std::string>(error::lookup::last_error())); |
|---|
| 238 | } |
|---|
| 239 | return data.crashed_pids; |
|---|
| 240 | } |
|---|
| 241 | |
|---|
| 242 | CEnumProcess::CProcessEntry CEnumProcess::describe_pid(DWORD pid, bool expand_command_line) { |
|---|
| 243 | CProcessEntry entry; |
|---|
| 244 | entry.dwPID = pid; |
|---|
| 245 | // Open process to get filename |
|---|
| 246 | DWORD openArgs = PROCESS_QUERY_INFORMATION|PROCESS_VM_READ; |
|---|
| 247 | // if (expand_command_line) |
|---|
| 248 | // openArgs |= PROCESS_VM_OPERATION; |
|---|
| 249 | HANDLE hProc = OpenProcess(openArgs, FALSE, pid); |
|---|
| 250 | if (!hProc) |
|---|
| 251 | throw process_enumeration_exception(GetLastError(), "Failed to open process: " + strEx::s::xtos(pid) + ": "); |
|---|
| 252 | if (expand_command_line) |
|---|
| 253 | entry.command_line = utf8::cvt<std::string>(GetCommandLine(hProc)); |
|---|
| 254 | HMODULE hMod; |
|---|
| 255 | DWORD size; |
|---|
| 256 | // Get the first module (the process itself) |
|---|
| 257 | if( FEnumProcessModules(hProc, &hMod, sizeof(hMod), &size) ) { |
|---|
| 258 | TCHAR buffer[MAX_FILENAME+1]; |
|---|
| 259 | if( !FGetModuleFileNameEx( hProc, hMod, reinterpret_cast<LPTSTR>(&buffer), MAX_FILENAME) ) { |
|---|
| 260 | CloseHandle(hProc); |
|---|
| 261 | throw process_enumeration_exception("Failed to find name for: " + strEx::s::xtos(pid) + ": " + error::lookup::last_error()); |
|---|
| 262 | } else { |
|---|
| 263 | std::wstring path = buffer; |
|---|
| 264 | std::wstring::size_type pos = path.find_last_of(_T("\\")); |
|---|
| 265 | if (pos != std::wstring::npos) { |
|---|
| 266 | path = path.substr(++pos); |
|---|
| 267 | } |
|---|
| 268 | entry.filename = utf8::cvt<std::string>(path); |
|---|
| 269 | } |
|---|
| 270 | } |
|---|
| 271 | |
|---|
| 272 | CloseHandle(hProc); |
|---|
| 273 | return entry; |
|---|
| 274 | } |
|---|
| 275 | |
|---|
| 276 | typedef struct _PROCESS_BASIC_INFORMATION { |
|---|
| 277 | LONG ExitStatus; |
|---|
| 278 | LPVOID PebBaseAddress; |
|---|
| 279 | ULONG_PTR AffinityMask; |
|---|
| 280 | LONG BasePriority; |
|---|
| 281 | ULONG_PTR UniqueProcessId; |
|---|
| 282 | ULONG_PTR ParentProcessId; |
|---|
| 283 | } PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION; |
|---|
| 284 | |
|---|
| 285 | typedef struct _UNICODE_STRING { |
|---|
| 286 | USHORT Length; |
|---|
| 287 | USHORT MaximumLength; |
|---|
| 288 | PWSTR Buffer; |
|---|
| 289 | } UNICODE_STRING, *PUNICODE_STRING; |
|---|
| 290 | |
|---|
| 291 | LPVOID GetPebAddress(HANDLE ProcessHandle) { |
|---|
| 292 | PFNtQueryInformationProcess NtQueryInformationProcess = (PFNtQueryInformationProcess)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess"); |
|---|
| 293 | if (NtQueryInformationProcess == NULL) |
|---|
| 294 | throw CEnumProcess::process_enumeration_exception("Failed to load NtQueryInformationProcess"); |
|---|
| 295 | PROCESS_BASIC_INFORMATION pbi; |
|---|
| 296 | NtQueryInformationProcess(ProcessHandle, 0, &pbi, sizeof(pbi), NULL); |
|---|
| 297 | return pbi.PebBaseAddress; |
|---|
| 298 | } |
|---|
| 299 | |
|---|
| 300 | |
|---|
| 301 | typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); |
|---|
| 302 | LPFN_ISWOW64PROCESS fnIsWow64Process; |
|---|
| 303 | |
|---|
| 304 | bool IsWow64(HANDLE hProcess, bool def = false) { |
|---|
| 305 | BOOL bIsWow64 = FALSE; |
|---|
| 306 | fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress(GetModuleHandle(TEXT("kernel32")),"IsWow64Process"); |
|---|
| 307 | if(NULL != fnIsWow64Process) { |
|---|
| 308 | if (!fnIsWow64Process(hProcess,&bIsWow64)) |
|---|
| 309 | return def; |
|---|
| 310 | } |
|---|
| 311 | return bIsWow64?true:false; |
|---|
| 312 | } |
|---|
| 313 | |
|---|
| 314 | std::wstring CEnumProcess::GetCommandLine(HANDLE hProcess) { |
|---|
| 315 | |
|---|
| 316 | UNICODE_STRING commandLine; |
|---|
| 317 | #ifdef _WIN64 |
|---|
| 318 | LPVOID pebAddress = GetPebAddress(hProcess); |
|---|
| 319 | LPVOID rtlUserProcParamsAddress; |
|---|
| 320 | if (!ReadProcessMemory(hProcess, (PCHAR)pebAddress + 0x20, &rtlUserProcParamsAddress, sizeof(LPVOID), NULL)) |
|---|
| 321 | throw process_enumeration_exception("Could not read the address of ProcessParameters: " + error::lookup::last_error()); |
|---|
| 322 | if (!ReadProcessMemory(hProcess, (PCHAR)rtlUserProcParamsAddress + 0x70, &commandLine, sizeof(commandLine), NULL)) |
|---|
| 323 | throw process_enumeration_exception("Could not read commandline: " + error::lookup::last_error()); |
|---|
| 324 | #else |
|---|
| 325 | bool osIsWin64 = IsWow64(GetCurrentProcess()); |
|---|
| 326 | if (!IsWow64(hProcess, !osIsWin64)) |
|---|
| 327 | return _T(""); |
|---|
| 328 | LPVOID pebAddress = GetPebAddress(hProcess); |
|---|
| 329 | LPVOID rtlUserProcParamsAddress; |
|---|
| 330 | if (!ReadProcessMemory(hProcess, (PCHAR)pebAddress + 0x10, &rtlUserProcParamsAddress, sizeof(LPVOID), NULL)) |
|---|
| 331 | throw process_enumeration_exception("Could not read the address of ProcessParameters: " + error::lookup::last_error()); |
|---|
| 332 | if (!ReadProcessMemory(hProcess, (PCHAR)rtlUserProcParamsAddress + 0x40, &commandLine, sizeof(commandLine), NULL)) |
|---|
| 333 | throw process_enumeration_exception("Could not read commandline: " + error::lookup::last_error()); |
|---|
| 334 | #endif |
|---|
| 335 | |
|---|
| 336 | /* allocate memory to hold the command line */ |
|---|
| 337 | wchar_t *commandLineContents = new wchar_t[commandLine.Length+2]; |
|---|
| 338 | memset(commandLineContents, 0, commandLine.Length); |
|---|
| 339 | |
|---|
| 340 | /* read the command line */ |
|---|
| 341 | if (!ReadProcessMemory(hProcess, commandLine.Buffer, commandLineContents, commandLine.Length, NULL)) { |
|---|
| 342 | delete [] commandLineContents; |
|---|
| 343 | throw process_enumeration_exception("Could not read commandline string: " + error::lookup::last_error()); |
|---|
| 344 | } |
|---|
| 345 | |
|---|
| 346 | commandLineContents[(commandLine.Length/sizeof(WCHAR))] = '\0'; |
|---|
| 347 | std::wstring ret = commandLineContents; |
|---|
| 348 | delete [] commandLineContents; |
|---|
| 349 | |
|---|
| 350 | return ret; |
|---|
| 351 | } |
|---|
| 352 | |
|---|