#include "Process.h" #include "../Misc/NameResolve.h" #include "../Misc/DynImport.h" #include namespace blackbone { #define SystemHandleInformation (SYSTEM_INFORMATION_CLASS)16 #define ObjectNameInformation (OBJECT_INFORMATION_CLASS)1 Process::Process() : _core() , _modules( *this ) , _memory( this ) , _threads( _core ) , _hooks( _memory ) , _localHooks( *this ) , _remote( *this ) , _mmap( *this ) , _nativeLdr( *this ) { // Ensure InitOnce is called InitializeOnce(); } Process::~Process(void) { } /// /// Attach to existing process /// /// Process ID /// Access mask /// Status code NTSTATUS Process::Attach( DWORD pid, DWORD access /*= DEFAULT_ACCESS_P*/ ) { Detach(); return _core.Open( pid, access ); } /// /// Attach to existing process /// /// Process handle /// Status code NTSTATUS Process::Attach( HANDLE hProc ) { Detach(); return _core.Open( hProc ); } /// /// Attach to existing process /// /// Process name /// Access mask /// Status code NTSTATUS Process::Attach( const wchar_t* name, DWORD access /*= DEFAULT_ACCESS_P*/ ) { auto pids = EnumByName( name ); return pids.empty() ? STATUS_NOT_FOUND : Attach( pids.front(), access ); } /// /// Create new process and attach to it /// /// Executable path /// Leave process in suspended state. To resume process one should resume its main thread /// If 'suspended' is true, this flag will enforce process initialization via second thread /// Process command line /// Startup directory /// Additional startup params /// Status code NTSTATUS Process::CreateAndAttach( const std::wstring& path, bool suspended /*= false*/, bool forceInit /*= true*/, const std::wstring& cmdLine /*= L""*/, const wchar_t* currentDir /*= nullptr*/, STARTUPINFOW* pStartup /*= nullptr*/ ) { Detach(); STARTUPINFOW si = { }; PROCESS_INFORMATION pi = { }; if (!pStartup) pStartup = &si; if (!CreateProcessW( path.c_str(), const_cast(cmdLine.c_str()), nullptr, nullptr, FALSE, CREATE_SUSPENDED, nullptr, currentDir, pStartup, &pi )) { return LastNtStatus(); } // Get handle ownership auto status = _core.Open( pi.hProcess ); if (NT_SUCCESS( status )) { // Check if process must be left in suspended mode if (suspended) { // Create new thread to make sure LdrInitializeProcess gets called if (forceInit) EnsureInit(); } else ResumeThread( pi.hThread ); } // Close unneeded handles CloseHandle( pi.hThread ); return status; } /// /// Detach form current process, if any /// /// Status code NTSTATUS Process::Detach() { // Reset data _memory.reset(); _modules.reset(); _remote.reset(); _mmap.reset(); _hooks.reset(); _core.Close(); return STATUS_SUCCESS; } /// /// Ensure LdrInitializeProcess gets called /// /// Status code NTSTATUS Process::EnsureInit() { auto pProc = _modules.GetNtdllExport( "NtYieldExecution", mt_default, Sections ); if (pProc) return _remote.ExecDirect( pProc->procAddress, 0 ); return STATUS_NOT_FOUND; } /// /// Suspend process /// /// Status code NTSTATUS Process::Suspend() { return SAFE_NATIVE_CALL( NtSuspendProcess, _core._hProcess ); } /// /// Resume process /// /// Status code NTSTATUS Process::Resume() { return SAFE_NATIVE_CALL( NtResumeProcess, _core._hProcess ); } /// /// Checks if process still exists /// /// bool Process::valid() { DWORD dwExitCode = 0; if (!_core.handle() || !GetExitCodeProcess( _core.handle(), &dwExitCode )) return false; return (dwExitCode == STILL_ACTIVE); } /// /// Terminate process /// /// Exit code /// Stratus code NTSTATUS Process::Terminate( uint32_t code /*= 0*/ ) { TerminateProcess( _core.handle(), code ); return LastNtStatus(); } /// /// Enumerate all open handles /// /// Found handles or status code call_result_t> Process::EnumHandles() { ULONG bufSize = 0x10000; uint8_t* buffer = (uint8_t*)VirtualAlloc( NULL, bufSize, MEM_COMMIT, PAGE_READWRITE ); ULONG returnLength = 0; std::vector handles; // Query handle list NTSTATUS status = SAFE_NATIVE_CALL( NtQuerySystemInformation, SystemHandleInformation, buffer, bufSize, &returnLength ); while (status == STATUS_INFO_LENGTH_MISMATCH) { bufSize *= 2; VirtualFree( buffer, 0, MEM_RELEASE ); buffer = (uint8_t*)VirtualAlloc( NULL, bufSize, MEM_COMMIT, PAGE_READWRITE ); status = SAFE_NATIVE_CALL( NtQuerySystemInformation, SystemHandleInformation, buffer, bufSize, &returnLength ); } if (!NT_SUCCESS( status )) { VirtualFree( buffer, 0, MEM_RELEASE ); return status; } SYSTEM_HANDLE_INFORMATION_T* handleInfo = (SYSTEM_HANDLE_INFORMATION_T*)buffer; for (ULONG i = 0; i < handleInfo->HandleCount; i++) { HandleInfo info; Handle hLocal; OBJECT_TYPE_INFORMATION_T* pTypeInfo = nullptr; PVOID pNameInfo = nullptr; UNICODE_STRING objectName = { 0 }; // Filter process if (handleInfo->Handles[i].ProcessId != _core._pid) continue; // Get local handle copy status = SAFE_NATIVE_CALL( NtDuplicateObject, _core._hProcess, reinterpret_cast(handleInfo->Handles[i].Handle), GetCurrentProcess(), &hLocal, 0, 0, DUPLICATE_SAME_ACCESS ); if (!NT_SUCCESS( status )) continue; // // Get type information // pTypeInfo = (OBJECT_TYPE_INFORMATION_T*)malloc( 0x1000 ); status = SAFE_NATIVE_CALL( NtQueryObject, hLocal, ObjectTypeInformation, pTypeInfo, 0x1000, nullptr ); if (!NT_SUCCESS( status )) { continue; } // // Obtain object name // pNameInfo = malloc( 0x1000 ); status = SAFE_NATIVE_CALL( NtQueryObject, hLocal, ObjectNameInformation, pNameInfo, 0x1000, &returnLength); if (!NT_SUCCESS( status )) { pNameInfo = realloc( pNameInfo, returnLength ); status = SAFE_NATIVE_CALL( NtQueryObject, hLocal, ObjectNameInformation, pNameInfo, returnLength, nullptr ); if (!NT_SUCCESS( status )) { free( pTypeInfo ); free( pNameInfo ); continue; } } objectName = *(PUNICODE_STRING)pNameInfo; // // Fill handle info structure // info.handle = reinterpret_cast(handleInfo->Handles[i].Handle); info.access = handleInfo->Handles[i].GrantedAccess; info.flags = handleInfo->Handles[i].Flags; info.pObject = handleInfo->Handles[i].Object; if (pTypeInfo->Name.Length) info.typeName = (wchar_t*)pTypeInfo->Name.Buffer; if (objectName.Length) info.name = objectName.Buffer; // // Type-specific info // if (_wcsicmp( info.typeName.c_str(), L"Section" ) == 0) { SECTION_BASIC_INFORMATION_T secInfo = { 0 }; status = SAFE_NATIVE_CALL( NtQuerySection, hLocal, SectionBasicInformation, &secInfo, (ULONG)sizeof( secInfo ), nullptr ); if (NT_SUCCESS( status )) { info.section.reset( new SectionInfo() ); info.section->size = secInfo.Size.QuadPart; info.section->attrib = secInfo.Attributes; } } handles.emplace_back( info ); free( pTypeInfo ); free( pNameInfo ); } VirtualFree( buffer, 0, MEM_RELEASE ); return call_result_t>( handles, status ); } /// /// Search for process by executable name /// /// Process name. If empty - function will retrieve all existing processes /// Found processses std::vector Process::EnumByName( const std::wstring& name ) { std::vector found; auto hProcSnap = SnapHandle( CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ) ); if (!hProcSnap) return found; PROCESSENTRY32W tEntry = { 0 }; tEntry.dwSize = sizeof( PROCESSENTRY32W ); // Iterate threads for (BOOL success = Process32FirstW( hProcSnap, &tEntry ); success != FALSE; success = Process32NextW( hProcSnap, &tEntry )) { if (name.empty() || _wcsicmp( tEntry.szExeFile, name.c_str() ) == 0) found.emplace_back( tEntry.th32ProcessID ); } return found; } /// /// Search for process by executable name or by process ID /// /// Target process ID. rocess name. If empty - function will retrieve all existing processes /// Process executable name. If empty - function will retrieve all existing processes /// Found processses /// If set to true, function will retrieve info ablout process threads /// Status code call_result_t> Process::EnumByNameOrPID( uint32_t pid, const std::wstring& name, bool includeThreads /*= false*/ ) { ULONG bufSize = 0x100; uint8_t tmpbuf[0x100]; uint8_t* buffer = tmpbuf; ULONG returnLength = 0; std::vector found; // Query process info NTSTATUS status = SAFE_NATIVE_CALL( NtQuerySystemInformation, (SYSTEM_INFORMATION_CLASS)57, buffer, bufSize, &returnLength ); if (!NT_SUCCESS( status )) { bufSize = returnLength; buffer = (uint8_t*)VirtualAlloc( NULL, bufSize, MEM_COMMIT, PAGE_READWRITE ); status = SAFE_NATIVE_CALL( NtQuerySystemInformation, (SYSTEM_INFORMATION_CLASS)57, buffer, bufSize, &returnLength ); if (!NT_SUCCESS( status )) { VirtualFree( buffer, 0, MEM_RELEASE ); return status; } } // Parse info for (auto pInfo = reinterpret_cast<_SYSTEM_PROCESS_INFORMATION_T*>(buffer);;) { // Skip idle process, compare name or compare pid if (pInfo->UniqueProcessId != 0 && ((name.empty() && pid == 0) || _wcsicmp( name.c_str(), (wchar_t*)pInfo->ImageName.Buffer ) == 0 || pid == pInfo->UniqueProcessId)) { ProcessInfo info; info.pid = static_cast(pInfo->UniqueProcessId); if (pInfo->ImageName.Buffer) info.imageName = reinterpret_cast(pInfo->ImageName.Buffer); // Get threads info if (includeThreads) { int64_t minTime = 0xFFFFFFFFFFFFFFFF; ULONG mainIdx = 0; for (ULONG i = 0; i < pInfo->NumberOfThreads; i++) { ThreadInfo tinfo; auto& thd = pInfo->Threads[i]; tinfo.tid = static_cast(thd.ThreadInfo.ClientId.UniqueThread); tinfo.startAddress = static_cast(thd.ThreadInfo.StartAddress); // Check for main thread if (thd.ThreadInfo.CreateTime.QuadPart < minTime) { minTime = thd.ThreadInfo.CreateTime.QuadPart; mainIdx = i; } info.threads.emplace_back( tinfo ); info.threads[mainIdx].mainThread = true; } } found.emplace_back( info ); } if (pInfo->NextEntryOffset) pInfo = reinterpret_cast<_SYSTEM_PROCESS_INFORMATION_T*>((uint8_t*)pInfo + pInfo->NextEntryOffset); else break; } // Sort results std::sort( found.begin(), found.end() ); VirtualFree( buffer, 0, MEM_RELEASE ); return call_result_t>( found, status ); } }