/********************************************************************** * Windows Intel Processor Trace (PT) Driver * Filename: IntelPtControlApp.cpp * Implement the entire PT driver's Control Application * Last revision: 12/01/2016 * * Copyright© 2016 Andrea Allievi, Richard Johnson * Microsoft Ltd & TALOS Research and Intelligence Group * All right reserved **********************************************************************/ #include "stdafx.h" #include "IntelPtControlApp.h" #include "Psapi.h" #include #include "pt_dump.h" #include "UndocNt.h" #include "log.h" const LPTSTR g_ptDevName = L"\\\\.\\WindowsIntelPtDev"; #pragma comment (lib, "ntdll.lib") // Entry point without command line arguments int NoCmdlineStartup() { BOOL bRetVal = FALSE; INTEL_PT_CAPABILITIES ptCap = { 0 }; HANDLE hPtDev = NULL; // Handle to the PT device TCHAR procPath[MAX_PATH] = { 0 }; // The target process full path PT_USER_REQ ptStartStruct = { 0 }; // The Intel PT starting structure DWORD dwBytesIo = 0; // Number of I/O bytes DWORD dwCpusCount = 1; // Number of CPUs in which to run the code DWORD dwLastErr = 0; // Last Win32 Error KAFFINITY cpuAffinity = 0; // The processor Affinity mask BOOLEAN bDoKernelTrace = FALSE; // TRUE if I would like to do kernel tracing PT_CPU_BUFFER_DESC * pCpuDescArray; // The CPU PT buffer descriptor array LPTSTR lpOutBasePath = NULL; // The dump files base directory BOOLEAN bManuallyAllocBuff = FALSE; // TRUE if I would like to manually allocate the buffer (used for test purposes) BOOLEAN bDeleteFiles = FALSE; // TRUE if some errors that require the file deletion PROCESS_INFORMATION pi = { 0 }; SYSTEM_INFO sysInfo = { 0 }; // Allocate memory for the file names lpOutBasePath = new TCHAR[MAX_PATH]; RtlZeroMemory(lpOutBasePath, MAX_PATH * sizeof(TCHAR)); GetNativeSystemInfo(&sysInfo); hPtDev = CreateFile(g_ptDevName, FILE_ALL_ACCESS, 0, NULL, OPEN_EXISTING, 0, NULL); dwLastErr = GetLastError(); if (hPtDev == INVALID_HANDLE_VALUE) { // Do not use the driver to check the processor Support bRetVal = CheckIntelPtSupport(&ptCap); wprintf(L"Intel Processor Tracing support for this CPU: "); if (bRetVal) cl_wprintf(GREEN, L"YES\r\n"); else cl_wprintf(RED, L"NO\r\n"); wprintf(L"Unable to open the Intel PT device object!\r\n"); return 0; } else g_appData.hPtDev = hPtDev; // First check if the processor is Intel PT compatible (bypass HyperV if needed) bRetVal = DeviceIoControl(hPtDev, IOCTL_PTDRV_CHECKSUPPORT, NULL, 0, (LPVOID)&ptCap, sizeof(ptCap), &dwBytesIo, NULL); if (!bRetVal) // We have failed, rely on the classical method as last resort bRetVal = CheckIntelPtSupport(&ptCap); wprintf(L"Intel Processor Tracing support for this CPU: "); if (bRetVal) cl_wprintf(GREEN, L"YES\r\n"); else cl_wprintf(RED, L"NO\r\n"); // Create the Exit Event g_appData.hExitEvt = CreateEvent(NULL, TRUE, FALSE, NULL); #pragma region 1. Generate Output dunp files base path string SYSTEMTIME curTime = { 0 }; GetModuleFileName(GetModuleHandle(NULL), lpOutBasePath, MAX_PATH); GetLocalTime(&curTime); LPTSTR slashPtr = wcsrchr(lpOutBasePath, L'\\'); if (slashPtr) slashPtr[1] = 0; swprintf_s(lpOutBasePath, MAX_PATH, L"%s%.2i%.2i-%.2i%.2i%.4i_Dumps", lpOutBasePath, curTime.wHour, curTime.wMinute, curTime.wMonth, curTime.wDay, curTime.wYear); CreateDirectory(lpOutBasePath, NULL); #pragma endregion #pragma region 2. Ask the user and check the CPU affinity TCHAR answer[10] = { 0 }; wprintf(L"Would you like to do the Kernel Tests? [Y/N] "); wscanf_s(L"%2s", answer, 10); if ((answer[0] | 0x20) == L'y') { g_appData.dwMainThrId = GetCurrentThreadId(); bDoKernelTrace = TRUE; } wprintf(L"Insert here the target %s to trace: ", (bDoKernelTrace ? L"kernel driver" : L"process")); wscanf_s(L"%s", procPath, MAX_PATH); if (sysInfo.dwNumberOfProcessors > 1) { // Ask how many processor to use wprintf(L"On how many processors would you like to run the process? [1/%i] ", sysInfo.dwNumberOfProcessors); wscanf_s(L"%i", &dwCpusCount); if (dwCpusCount > sysInfo.dwNumberOfProcessors) { wprintf(L"Invalid value, assuming all the processors as valid.\r\n"); cpuAffinity = sysInfo.dwActiveProcessorMask; dwCpusCount = sysInfo.dwNumberOfProcessors; } else cpuAffinity = ((DWORD_PTR)(-1i64) >> ((sizeof(DWORD_PTR) * 8) - dwCpusCount)); if (FALSE) // If you would like to test the different affinities: cpuAffinity = 0xd; _ASSERT((sysInfo.dwActiveProcessorMask | cpuAffinity) == sysInfo.dwActiveProcessorMask); } else { cpuAffinity = 0x1; dwCpusCount = 1; } #pragma endregion #pragma region 3. Create the CPU buffer data structures and trace files wprintf(L"Creating trace files (binary and readable)... "); bRetVal = (BOOL)InitPerCpuData(cpuAffinity, lpOutBasePath); if (bRetVal) cl_wprintf(GREEN, L"Success!\r\n"); else { RemoveDirectory(lpOutBasePath); // We are great, we would like to try to write to the TEMP directory dwBytesIo = GetTempPath(MAX_PATH, lpOutBasePath); LPTSTR slashPtr = wcsrchr(lpOutBasePath, L'\\'); if (lpOutBasePath[dwBytesIo - 1] == '\\') lpOutBasePath[--dwBytesIo] = 0; swprintf_s(lpOutBasePath, MAX_PATH, L"%s\\IntelPt_Dumps_%.2i%.2i-%.2i%.2i%.4i", lpOutBasePath, curTime.wHour, curTime.wMinute, curTime.wMonth, curTime.wDay, curTime.wYear); CreateDirectory(lpOutBasePath, NULL); bRetVal = (BOOL)InitPerCpuData(cpuAffinity, lpOutBasePath); if (bRetVal) { cl_wprintf(GREEN, L"Success! "); wprintf(L"(in TEMP directory)\r\n"); } } if (!bRetVal) { RemoveDirectory(lpOutBasePath); cl_wprintf(RED, L"Error!\r\n"); wprintf(L"Unable to create the output dump file.\r\n"); CloseHandle(hPtDev); return -1; } pCpuDescArray = g_appData.pCpuDescArray; #pragma endregion #pragma region 4. Spawn of the new process and PMI threads if (!bDoKernelTrace) { wprintf(L"Creating target process... "); bRetVal = SpawnSuspendedProcess(procPath, NULL, &pi); if (bRetVal) cl_wprintf(GREEN, L"OK\r\n"); else { wprintf(L"Error!\r\n"); FreePerCpuData(TRUE); CloseHandle(hPtDev); wprintf(L"Press any key to exit..."); getwchar(); return -1; } g_appData.hTargetProc = pi.hProcess; } else { // Set this process as the remote one pi.hProcess = GetCurrentProcess(); pi.hThread = GetCurrentThread(); pi.dwProcessId = GetCurrentProcessId(); } if (!bDoKernelTrace) { bRetVal = SetProcessAffinityMask(pi.hProcess, cpuAffinity); _ASSERT(bRetVal); bRetVal = SetThreadAffinityMask(pi.hThread, cpuAffinity); _ASSERT(bRetVal); wprintf(L" Affinity mask set to 0x%08X.\r\n", (UINT32)cpuAffinity); } else bRetVal = (BOOL)SetThreadAffinityMask(pi.hThread, cpuAffinity); _ASSERT(bRetVal); if (!bRetVal) { cl_wprintf(YELLOW, L"Warning!\r\n"); wprintf(L" Unable Set the processor affinity for the spawned process.\r\n"); } // Create the PMI threads (1 per target CPU) for (int i = 0; i < (int)dwCpusCount; i++) { PT_PMI_USER_CALLBACK pmiDesc = { 0 }; HANDLE hNewThr = NULL; DWORD newThrId = 0; hNewThr = CreateThread(NULL, 0, PmiThreadProc, (LPVOID)i, CREATE_SUSPENDED, &newThrId); // Register this thread and its callback pmiDesc.dwThrId = newThrId; pmiDesc.kCpuAffinity = (1i64 << i); pmiDesc.lpAddress = PmiCallback; bRetVal = DeviceIoControl(hPtDev, IOCTL_PTDRV_REGISTER_PMI_ROUTINE, (LPVOID)&pmiDesc, sizeof(PT_PMI_USER_CALLBACK), NULL, 0, &dwBytesIo, NULL); if (bRetVal) { pCpuDescArray[i].dwPmiThrId = newThrId; pCpuDescArray[i].hPmiThread = hNewThr; ResumeThread(hNewThr); } } #pragma endregion #pragma region 5. Set IP filtering (if any) and TRACE options HMODULE hRemoteMod = NULL; // The remote module base address MODULEINFO remoteModInfo = { 0 }; // The remote module information if (g_appData.bTraceByIp) { // Now grab the remote image base address and size if (!bDoKernelTrace) { bRetVal = EnumProcessModules(pi.hProcess, &hRemoteMod, sizeof(HMODULE), &dwBytesIo); bRetVal = GetModuleInformation(pi.hProcess, hRemoteMod, &remoteModInfo, sizeof(MODULEINFO)); dwLastErr = GetLastError(); } else { // Grab the target module base address SYSTEM_ALL_MODULES * pSysAllModules = NULL; NTSTATUS ntStatus = 0; CHAR modNameAnsi[0x80] = { 0 }; sprintf_s(modNameAnsi, COUNTOF(modNameAnsi), "%S", procPath); ntStatus = ZwQuerySystemInformation(11, pSysAllModules, 0, &dwBytesIo); if (ntStatus == STATUS_INFO_LENGTH_MISMATCH) { pSysAllModules = (SYSTEM_ALL_MODULES*)VirtualAlloc(NULL, dwBytesIo + 64, MEM_COMMIT, PAGE_READWRITE); RtlZeroMemory(pSysAllModules, dwBytesIo); ntStatus = ZwQuerySystemInformation(11, pSysAllModules, dwBytesIo, &dwBytesIo); if (ntStatus == 0) { // Search for the SimplePt for (unsigned i = 0; i < pSysAllModules->dwNumOfModules; i++) { SYSTEM_MODULE_INFORMATION curMod = pSysAllModules->modules[i]; LPSTR lpTargetModName = curMod.ImageName + curMod.ModuleNameOffset; if (_stricmp(modNameAnsi, lpTargetModName) == 0) { // Target module found wprintf(L"Found \"%S\" kernel driver in memory.\r\n", lpTargetModName); remoteModInfo.lpBaseOfDll = curMod.Base; remoteModInfo.SizeOfImage = curMod.Size; break; } } } } if (pSysAllModules) VirtualFree((LPVOID)pSysAllModules, 0, MEM_RELEASE); ptStartStruct.bTraceKernel = TRUE; ptStartStruct.bTraceUser = FALSE; } g_appData.bTraceOnlyKernel = bDoKernelTrace; #ifdef _DEBUG if (!remoteModInfo.lpBaseOfDll && _wcsicmp(procPath, L"AaLl86TestDriver.sys") == 0) { wprintf(L"Would you like to perform the Tracing test from Kernel-mode? [Y/N] "); wscanf_s(L"%2s", answer, 10); // Do the special Kernel-mode test of AaLl86 if ((answer[0] | 0x20) == L'y') { DoKernelTrace(hPtDev, PT_USER_REQ(), procPath); goto CloseTrace; } } #endif if (!remoteModInfo.lpBaseOfDll) { cl_wprintf(RED, L"Error! "); wprintf(L"I was not able to find the target %s base address and size.\r\n", (bDoKernelTrace ? L"kernel module" : L"process' main module")); FreePerCpuData(); CloseHandle(hPtDev); return -1; } cl_wprintf(PINK, L"\r\n Using IP filtering mode!\r\n"); wprintf(L"%s base address: 0x%llX, size 0x%08X.\r\n\r\n", (bDoKernelTrace ? L"Target kernel driver" : L"New Process main module"), (QWORD)remoteModInfo.lpBaseOfDll, remoteModInfo.SizeOfImage); // Set the PT_USER_REQUEST structure ptStartStruct.IpFiltering.dwNumOfRanges = 1; ptStartStruct.IpFiltering.Ranges[0].lpStartVa = (LPVOID)((ULONG_PTR)remoteModInfo.lpBaseOfDll); ptStartStruct.IpFiltering.Ranges[0].lpEndVa = (LPVOID)((ULONG_PTR)remoteModInfo.lpBaseOfDll + remoteModInfo.SizeOfImage); ptStartStruct.IpFiltering.Ranges[0].bStopTrace = FALSE; } // END Tracing by IP block // Write some information in the output text file: WriteCpuTextDumpsHeader(procPath, (ULONG_PTR)remoteModInfo.lpBaseOfDll, remoteModInfo.SizeOfImage, bDoKernelTrace); ptStartStruct.bTraceUser = !bDoKernelTrace; ptStartStruct.bTraceKernel = bDoKernelTrace; // For now do not set the frequencies.... ptStartStruct.dwOptsMask = PT_TRACE_BRANCH_PCKS_MASK | PT_ENABLE_RET_COMPRESSION_MASK | PT_ENABLE_TOPA_MASK; ptStartStruct.kCpuAffinity = cpuAffinity; ptStartStruct.dwTraceSize = g_appData.dwTraceBuffSize; #pragma endregion #pragma region 6. Optional - Allocate each PT CPU buffer (we can even skip this process, the START_TRACE IOCTL can do it for us) LPVOID * lpBuffArray = new LPVOID[dwCpusCount]; RtlZeroMemory(lpBuffArray, sizeof(LPVOID)* dwCpusCount); if (bManuallyAllocBuff) { // 2 Things to keep in mind here: // 1. The IOCTL_PTDRV_ALLOC_BUFFERS checks PT_ENABLE_TOPA bit for the buffer allocations // 2. We do not need to send the entire PT_USER_REQ structure but only CPU mask, Size and a BOOL value (that contains the bit for the TOPA) DeviceIoControl(hPtDev, IOCTL_PTDRV_FREE_BUFFERS, (LPVOID)&ptStartStruct, FIELD_OFFSET(PT_USER_REQ, dwProcessId), NULL, 0, &dwBytesIo, NULL); bRetVal = DeviceIoControl(hPtDev, IOCTL_PTDRV_ALLOC_BUFFERS, (LPVOID)&ptStartStruct, FIELD_OFFSET(PT_USER_REQ, dwProcessId), lpBuffArray, sizeof(LPVOID) * dwCpusCount, &dwBytesIo, NULL); dwLastErr = GetLastError(); if (bRetVal) { // Save our buffers for (int i = 0; i < (int)dwCpusCount; i++) g_appData.pCpuDescArray[i].lpPtBuff = (LPBYTE)lpBuffArray[i]; } else { cl_wprintf(RED, L"Error! "); wprintf(L"Unable to allocate the PT buffers!\r\n"); FreePerCpuData(); CloseHandle(hPtDev); return 0; } } #pragma endregion #pragma region 8. Start the tracing and wait the process to exit // Start the device Tracing if (!bDoKernelTrace) { wprintf(L"Starting the Tracing and resuming the process... "); ptStartStruct.dwProcessId = pi.dwProcessId; ptStartStruct.kCpuAffinity = cpuAffinity; bRetVal = DeviceIoControl(hPtDev, IOCTL_PTDRV_START_TRACE, (LPVOID)&ptStartStruct, sizeof(PT_USER_REQ), lpBuffArray, sizeof(LPVOID) * dwCpusCount, &dwBytesIo, NULL); dwLastErr = GetLastError(); if (bRetVal) { cl_wprintf(GREEN, L"OK\r\n"); g_appData.currentTrace = ptStartStruct; // Copy the returned Buffer array for (int i = 0; i < (int)g_appData.dwNumOfActiveCpus; i++) { g_appData.pCpuDescArray[i].lpPtBuff = (LPBYTE)lpBuffArray[i]; g_appData.pCpuDescArray[i].dwBuffSize = ptStartStruct.dwTraceSize; } // Resume the target process wprintf(L"\r\n"); Sleep(100); ResumeThread(pi.hThread); wprintf(L"Waiting for the traced process to exit...\r\n"); WaitForSingleObject(pi.hProcess, INFINITE); wprintf(L"\r\n"); } else { TerminateProcess(pi.hProcess, -1); cl_wprintf(RED, L"Error!\r\n"); bDeleteFiles = TRUE; } } else { DoKernelTrace(hPtDev, ptStartStruct, procPath); } // Set the event and wait for all PMI thread to exit CloseTrace: SetEvent(g_appData.hExitEvt); for (int i = 0; i < (int)dwCpusCount; i++) { WaitForSingleObject(pCpuDescArray[i].hPmiThread, INFINITE); CloseHandle(pCpuDescArray[i].hPmiThread); pCpuDescArray[i].hPmiThread = NULL; pCpuDescArray[i].dwPmiThrId = 0; } #pragma endregion #pragma region 9. Optional - Get the results of our tracing (like the number of written packets) PT_TRACE_DETAILS ptDetails = { 0 }; QWORD qwTotalNumOfPtPcks = 0; // The TOTAL number of acquired packets wprintf(L"\r\n\r\n"); cl_wprintf(DARKYELLOW, L"*** PT Trace results ***\r\n"); wprintf(L"Number of traced CPUs: %i - Affinity mask: 0x%08X.\r\n", dwCpusCount, (DWORD)cpuAffinity); for (int i = 0; i < sizeof(cpuAffinity) * 8; i++) { if (!(cpuAffinity & (1i64 << i))) continue; wprintf(L"CPU %i\r\n", i); RtlZeroMemory(&ptDetails, sizeof(ptDetails)); bRetVal = DeviceIoControl(hPtDev, IOCTL_PTDR_GET_TRACE_DETAILS, (LPVOID)&i, sizeof(int), (LPVOID)&ptDetails, sizeof(ptDetails), &dwBytesIo, NULL); if (bRetVal) { wprintf(L" Number of traced IP ranges: %i\r\n", ptDetails.IpFiltering.dwNumOfRanges); wprintf(L" Number of acquired packets: %I64i\r\n", ptDetails.qwTotalNumberOfPackets); qwTotalNumOfPtPcks += ptDetails.qwTotalNumberOfPackets; } else cl_wprintf(RED, L" Error!\r\n"); } wprintf(L"\r\nGlobal number of PT packets acquired: %I64i.\r\n", qwTotalNumOfPtPcks); wprintf(L"All the dumps have been saved in \"%s\".\r\n", lpOutBasePath); #pragma endregion #pragma region 10. Free the resources and close each files // Stop the Tracing (and clear the buffer if not manually allocated) bRetVal = DeviceIoControl(hPtDev, IOCTL_PTDRV_CLEAR_TRACE, (LPVOID)&cpuAffinity, sizeof(cpuAffinity), NULL, 0, &dwBytesIo, NULL); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); FreePerCpuData(bDeleteFiles); if (bManuallyAllocBuff) bRetVal = DeviceIoControl(g_appData.hPtDev, IOCTL_PTDRV_FREE_BUFFERS, (LPVOID)&cpuAffinity, sizeof(cpuAffinity), NULL, 0, &dwBytesIo, NULL); CloseHandle(hPtDev); #pragma endregion return 0; } extern "C" void MovToRdi(QWORD value); // Check if the current CPU has support for Intel PT BOOL CheckIntelPtSupport(INTEL_PT_CAPABILITIES * lpPtCap) { INTEL_PT_CAPABILITIES ptCap = { 0 }; int cpuid_ctx[4] = { 0 }; // EAX, EBX, ECX, EDX // Instrumentation for the Hypervisor QWORD rdiVal = 0; RtlCopyMemory(&rdiVal, " AaLl86 ", sizeof(__int64)); MovToRdi(rdiVal); // Processor support for Intel Processor Trace is indicated by CPUID.(EAX=07H,ECX=0H):EBX[bit 25] = 1. __cpuidex(cpuid_ctx, 0x07, 0); if (!(cpuid_ctx[1] & (1 << 25))) return FALSE; // Now enumerate the Intel Processor Trace capabilities RtlZeroMemory(cpuid_ctx, sizeof(cpuid_ctx)); MovToRdi(rdiVal); __cpuidex(cpuid_ctx, 0x14, 0); // If the maximum valid sub-leaf index is 0 exit immediately if (cpuid_ctx[0] == 0) return FALSE; ptCap.bCr3Filtering = (cpuid_ctx[1] & (1 << 0)) != 0; // EBX ptCap.bConfPsbAndCycSupported = (cpuid_ctx[1] & (1 << 1)) != 0; ptCap.bIpFiltering = (cpuid_ctx[1] & (1 << 2)) != 0; ptCap.bMtcSupport = (cpuid_ctx[1] & (1 << 3)) != 0; ptCap.bTopaOutput = (cpuid_ctx[2] & (1 << 0)) != 0; // ECX ptCap.bTopaMultipleEntries = (cpuid_ctx[2] & (1 << 1)) != 0; ptCap.bSingleRangeSupport = (cpuid_ctx[2] & (1 << 2)) != 0; ptCap.bTransportOutputSupport = (cpuid_ctx[2] & (1 << 3)) != 0; ptCap.bIpPcksAreLip = (cpuid_ctx[2] & (1 << 31)) != 0; // Enmeration part 2: RtlZeroMemory(cpuid_ctx, sizeof(cpuid_ctx)); MovToRdi(rdiVal); __cpuidex(cpuid_ctx, 0x14, 1); ptCap.numOfAddrRanges = (BYTE)(cpuid_ctx[0] & 0x7); ptCap.mtcPeriodBmp = (SHORT)((cpuid_ctx[0] >> 16) & 0xFFFF); ptCap.cycThresholdBmp = (SHORT)(cpuid_ctx[1] & 0xFFFF); ptCap.psbFreqBmp = (SHORT)((cpuid_ctx[1] >> 16) & 0xFFFF); if (lpPtCap) *lpPtCap = ptCap; return TRUE; } // Close and flush the per-CPU files and data structures bool FreePerCpuData(BOOL bDeleteFiles) { PT_CPU_BUFFER_DESC * pCpuDesc = NULL; // Current CPU Descriptor BOOLEAN bBuffValid = FALSE; DWORD dwBytesIo = 0; if (g_appData.pCpuDescArray == NULL) return false; for (int i = 0; i < (int)g_appData.dwNumOfActiveCpus; i++) { pCpuDesc = &g_appData.pCpuDescArray[i]; if (pCpuDesc->hBinFile) { if (bDeleteFiles) SetFileInformationByHandle(pCpuDesc->hBinFile, FileDispositionInfo, (LPVOID)&bDeleteFiles, sizeof(BOOL)); CloseHandle(pCpuDesc->hBinFile); pCpuDesc->hBinFile = NULL; } if (pCpuDesc->hTextFile) { if (bDeleteFiles) SetFileInformationByHandle(pCpuDesc->hTextFile, FileDispositionInfo, (LPVOID)&bDeleteFiles, sizeof(BOOL)); CloseHandle(pCpuDesc->hTextFile); pCpuDesc->hTextFile = NULL; } if (pCpuDesc->lpPtBuff) bBuffValid = TRUE; } // The actual PT buffer deallocation is done in the main routine (by the PT driver) delete[] g_appData.pCpuDescArray; g_appData.pCpuDescArray = NULL; g_appData.dwNumOfActiveCpus = 0; g_appData.kActiveCpuAffinity = 0; return true; } // Initialize and open the per-CPU files and data structures bool InitPerCpuData(ULONG_PTR kCpuAffinity, LPTSTR lpBasePath) { PT_CPU_BUFFER_DESC * pCpuArray = NULL; // The new PER-CPU array HANDLE hNewFile = NULL; // The handle of the new file TCHAR newFileName[MAX_PATH] = { 0 }; DWORD dwPathLen = 0; DWORD dwNumOfCpus = 0, // Total number of CPUs dwCurCpuCount = 0; // Current CPU counter (different from ID) FreePerCpuData(); for (int i = 0; i < sizeof(kCpuAffinity) * 8; i++) if (kCpuAffinity & (1i64 << i)) dwNumOfCpus++; pCpuArray = new PT_CPU_BUFFER_DESC[dwNumOfCpus]; RtlZeroMemory(pCpuArray, sizeof(PT_CPU_BUFFER_DESC) * dwNumOfCpus); g_appData.dwNumOfActiveCpus = dwNumOfCpus; g_appData.kActiveCpuAffinity = kCpuAffinity; g_appData.pCpuDescArray = pCpuArray; dwPathLen = (DWORD)wcslen(lpBasePath); for (int i = 0; sizeof(kCpuAffinity) * 8; i++) { PT_CPU_BUFFER_DESC * pCurCpuDesc = &pCpuArray[dwCurCpuCount]; if (!(kCpuAffinity & (1i64 << i))) continue; if (dwCurCpuCount >= dwNumOfCpus) break; newFileName[0] = 0; swprintf_s(newFileName, MAX_PATH, L"%s\\cpu%.2i_bin.bin", lpBasePath, i); // Create the binary file hNewFile = CreateFile(newFileName, FILE_GENERIC_WRITE | DELETE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL); // Create the text file if (hNewFile != INVALID_HANDLE_VALUE) { pCurCpuDesc->hBinFile = hNewFile; newFileName[0] = 0; swprintf_s(newFileName, MAX_PATH, L"%s\\cpu%.2i_text.log", lpBasePath, i); hNewFile = CreateFile(newFileName, FILE_GENERIC_WRITE | DELETE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL); } if (hNewFile != INVALID_HANDLE_VALUE) pCurCpuDesc->hTextFile = hNewFile; else { FreePerCpuData(TRUE); return false; } dwCurCpuCount++; } return true; } // Write the human readable dump file header bool WriteCpuTextDumpsHeader(LPTSTR lpImgName, ULONG_PTR qwBase, DWORD dwSize, BOOLEAN bKernelTrace) { DWORD dwCurCpuCount = 0; // Current CPU counter (different from ID) DWORD dwNumOfCpus = 0; // Total number of CPUs KAFFINITY kCpuAffinity = 0; // Current CPU affinity mask CHAR fullLine[0x200] = { 0 }; // A full line of log dump DWORD dwBytesIo = 0; CVersionInfo verInfo; // Version information about myself LPTSTR verString = NULL; // My version info string if (!g_appData.pCpuDescArray) return false; // Grab some basic data dwNumOfCpus = g_appData.dwNumOfActiveCpus; kCpuAffinity = g_appData.kActiveCpuAffinity; if (lpImgName && wcsrchr(lpImgName, L'\\')) lpImgName = wcsrchr(lpImgName, L'\\') + 1; for (int i = 0; i < sizeof(g_appData.kActiveCpuAffinity) * 8; i++) { PT_CPU_BUFFER_DESC * pCurCpuBuff = &g_appData.pCpuDescArray[dwCurCpuCount]; HANDLE hTextFile = NULL; if (!(kCpuAffinity & (1i64 << i))) continue; if (dwCurCpuCount > dwNumOfCpus) break; hTextFile = pCurCpuBuff->hTextFile; if (!hTextFile) { dwCurCpuCount++; continue; } verString = verInfo.GetFileVersionString(); sprintf_s(fullLine, COUNTOF(fullLine), "Intel PT Trace file. Version %S.\r\nCPU ID : %i\r\n", verString, i); WriteFile(hTextFile, fullLine, (DWORD)strlen(fullLine), &dwBytesIo, NULL); if (qwBase && dwSize) { if (!bKernelTrace) sprintf_s(fullLine, COUNTOF(fullLine), "Executable name: %S\r\n", lpImgName); else sprintf_s(fullLine, COUNTOF(fullLine), "Kernel driver name: %S\r\n", lpImgName); WriteFile(hTextFile, fullLine, (DWORD)strlen(fullLine), &dwBytesIo, NULL); sprintf_s(fullLine, COUNTOF(fullLine), "Base address: 0x%016llX - Size 0x%08X\r\n", (QWORD)qwBase, dwSize); WriteFile(hTextFile, fullLine, (DWORD)strlen(fullLine), &dwBytesIo, NULL); } sprintf_s(fullLine, COUNTOF(fullLine), "\r\n"); WriteFile(hTextFile, fullLine, (DWORD)strlen(fullLine), &dwBytesIo, NULL); WriteFile(hTextFile, "Begin Trace Dump:\r\n", (DWORD)strlen("Begin Trace Dump:\r\n"), &dwBytesIo, NULL); dwCurCpuCount++; } return true; } // Spawn a suspended process and oblige the loader to load the remote image in memory BOOL SpawnSuspendedProcess(LPTSTR lpAppName, LPTSTR lpCmdLine, PROCESS_INFORMATION * pOutProcInfo) { BYTE remote_opcodes[] = { 0x90, 0x90, 0xc3, 0x90, 0x90 }; // NOP - RET opcodes PROCESS_INFORMATION pi = { 0 }; // Process information STARTUPINFO si = { 0 }; // The process Startup options ULONG_PTR ulBytesIo = 0; // Number of I/O bytes LPVOID lpRemBuff = NULL; // Remote memory buffer HANDLE hRemoteThr = NULL; // The remote thread stub BOOL bRetVal = FALSE; // Win32 return value DWORD dwThrId = 0; // Remote thread ID si.cb = sizeof(STARTUPINFO); bRetVal = CreateProcess(lpAppName, lpCmdLine, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi); // To get the remote image base address I need to instruct the Windows loader to load the // Target image file in memory, and to compile the PEB lpRemBuff = VirtualAllocEx(pi.hProcess, NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (lpRemBuff) bRetVal = WriteProcessMemory(pi.hProcess, lpRemBuff, (LPCVOID)remote_opcodes, sizeof(remote_opcodes), (SIZE_T*)&ulBytesIo); else bRetVal = FALSE; if (bRetVal) hRemoteThr = CreateRemoteThread(pi.hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpRemBuff, NULL, 0, &dwThrId); if (hRemoteThr) { WaitForSingleObject(hRemoteThr, INFINITE); if (lpRemBuff) VirtualFreeEx(pi.hProcess, lpRemBuff, 0, MEM_RELEASE); // Get rid of it: CloseHandle(hRemoteThr); if (pOutProcInfo) *pOutProcInfo = pi; return TRUE; } else { TerminateProcess(pi.hProcess, -1); CloseHandle(pi.hThread); CloseHandle(pi.hProcess); return FALSE; } } // The PMI interrupt Thread DWORD WINAPI PmiThreadProc(LPVOID lpParameter) { LPTSTR lpEventName = L"Global\\" INTEL_PT_PMI_EVENT_NAME; HANDLE hKernelEvt = NULL; DWORD dwLastErr = 0; // Last Win32 error DWORD dwBytesIo = 0, // Number of I/O bytes dwEvtNum = 0; // The event number that has satisfied the wait BOOLEAN bRetVal = FALSE; DWORD dwCpuNumber = (DWORD)lpParameter; HANDLE hWaitEvts[2] = { 0 }; PT_PMI_USER_CALLBACK pmiDesc = { 0 }; PT_CPU_BUFFER_DESC * pCurCpuBuff = &g_appData.pCpuDescArray[dwCpuNumber]; hKernelEvt = OpenEvent(SYNCHRONIZE, FALSE, lpEventName); dwLastErr = GetLastError(); if (!hKernelEvt) return -1; hWaitEvts[0] = hKernelEvt; hWaitEvts[1] = g_appData.hExitEvt; while (TRUE) { // Perform an ALERTABLE wait dwEvtNum = WaitForMultipleObjectsEx(2, hWaitEvts, FALSE, INFINITE, TRUE); // WAIT_IO_COMPLETION means APC has been queued if (dwEvtNum - WAIT_OBJECT_0 == 1) { // We are exiting, pause the Tracing DeviceIoControl(g_appData.hPtDev, IOCTL_PTDRV_PAUSE_TRACE, (LPVOID)&g_appData.kActiveCpuAffinity, sizeof(KAFFINITY), NULL, 0, &dwBytesIo, NULL); break; } // Continue to wait on the PMI Event, and raise the appropriate Callbacks } // Deregister my callback pmiDesc.dwThrId = GetCurrentThreadId(); pmiDesc.lpAddress = PmiCallback; DeviceIoControl(g_appData.hPtDev, IOCTL_PTDRV_FREE_PMI_ROUTINE, (LPVOID)&pmiDesc, sizeof(PT_PMI_USER_CALLBACK), NULL, 0, &dwBytesIo, NULL); // Sleep a bit Sleep(500); // and write the rest of the log if (pCurCpuBuff->lpPtBuff && pCurCpuBuff->hBinFile) { BYTE zeroArray[16] = { 0 }; DWORD dwEndOffset = 0; for (DWORD i = 0; i < pCurCpuBuff->dwBuffSize - sizeof(zeroArray); i += sizeof(zeroArray)) if (RtlCompareMemory(pCurCpuBuff->lpPtBuff + i, zeroArray, sizeof(zeroArray)) == sizeof(zeroArray)) { dwEndOffset = i; break; } if (!dwEndOffset) dwEndOffset = g_appData.pCpuDescArray[dwCpuNumber].dwBuffSize; bRetVal = WriteFile(pCurCpuBuff->hBinFile, pCurCpuBuff->lpPtBuff, dwEndOffset, &dwBytesIo, NULL); if (pCurCpuBuff->hTextFile) { // Dump the text trace file immediately bRetVal = pt_dumpW((LPBYTE)pCurCpuBuff->lpPtBuff, (DWORD)dwEndOffset, pCurCpuBuff->hTextFile, pCurCpuBuff->qwDelta, g_appData.bTraceOnlyKernel); pCurCpuBuff->qwDelta += (QWORD)dwEndOffset; } } return 0; } // The PMI callback VOID PmiCallback(DWORD dwCpuId, PVOID lpBuffer, QWORD qwBufferSize) { HANDLE hTraceBinFile = NULL; // The trace BINARY file HANDLE hTraceTextFile = NULL; // The trace Text file DWORD dwDescNum = 0; // The descriptor number DWORD dwBytesIo = 0; // Number of I/O bytes BOOL bRetVal = FALSE; // Returned Win32 value DWORD dwLastErr = 0; // Last Win32 error KAFFINITY thisCpuAffinity = (1i64 << dwCpuId); // Check if there is the main thread, open if so if (g_appData.dwMainThrId && !g_appData.hMainThr) g_appData.hMainThr = OpenThread(SYNCHRONIZE | THREAD_SUSPEND_RESUME, FALSE, g_appData.dwMainThrId); // Convert the CPU ID in descriptor number for (int i = 0; i < sizeof(KAFFINITY) * 8; i++) { if ((1i64 << i) & g_appData.kActiveCpuAffinity) { if (i == dwCpuId) break; dwDescNum++; } } // Grab the parameters hTraceBinFile = g_appData.pCpuDescArray[dwDescNum].hBinFile; hTraceTextFile = g_appData.pCpuDescArray[dwDescNum].hTextFile; QWORD & qwDelta = g_appData.pCpuDescArray[dwDescNum].qwDelta; // Suspend the main thread if any if (g_appData.hMainThr) SuspendThread(g_appData.hMainThr); if (hTraceBinFile) { bRetVal = WriteFile(hTraceBinFile, lpBuffer, (DWORD)qwBufferSize, &dwBytesIo, NULL); if (!bRetVal) { cl_wprintf(RED, L"Warning! "); wprintf(L"Unable to write in the log file. Results could be erroneous.\r\n"); } } if (hTraceTextFile) { // Dump the text trace file immediately bRetVal = pt_dumpW((LPBYTE)lpBuffer, (DWORD)qwBufferSize, hTraceTextFile, qwDelta, g_appData.bTraceOnlyKernel); qwDelta += (QWORD)qwBufferSize; } RtlZeroMemory((LPBYTE)lpBuffer, (DWORD)qwBufferSize); // Resume the tracing and the execution of the target process bRetVal = DeviceIoControl(g_appData.hPtDev, IOCTL_PTDRV_RESUME_TRACE, (LPVOID)&thisCpuAffinity, sizeof(KAFFINITY), NULL, 0, &dwBytesIo, NULL); if (!g_appData.currentTrace.bTraceKernel) ZwResumeProcess(g_appData.hTargetProc); if (g_appData.hMainThr) ResumeThread(g_appData.hMainThr); } // Try some Kernel tracing activity :-) bool DoKernelTrace(HANDLE hPtDev, PT_USER_REQ ptUserReq, LPTSTR lpDrvName) { BOOL bRetVal = FALSE; HANDLE hTestDev = NULL; DWORD dwLastErr = 0, dwBytesIo = 0; KERNEL_MODULE kernelMod = { 0 }; LPVOID lpPtBuff = NULL; TCHAR answer[0x20] = { 0 }; LPVOID * lpBuffArray = NULL; DWORD dwNumOfCpus = g_appData.dwNumOfActiveCpus; // Specific AaLl86 Driver data: const LPTSTR DosDevName = L"\\\\.\\IntelPtTest"; LPTSTR lpKernelModName = L"ci.dll"; bool bAaLl86Test = (_wcsicmp(lpDrvName, L"AaLl86TestDriver.sys") == 0); #ifdef _DEBUG if (bAaLl86Test && ptUserReq.dwTraceSize == 0) { // Here theoretically I have to open the target driver module and insert the special BAD_OPCODE // BUT I am too lazy. wprintf(L"Testing Kernel-mode Tracing from a Kernel module... "); // Send the special IOCTLs dwBytesIo = (DWORD)((wcslen(lpDrvName) + 1) * sizeof(WCHAR)); bRetVal = DeviceIoControl(hPtDev, IOCTL_PTDR_DO_KERNELDRV_TEST, (LPVOID)lpDrvName, dwBytesIo, NULL, 0, &dwBytesIo, NULL); dwLastErr = GetLastError(); if (bRetVal) { cl_wprintf(GREEN, L"OK\r\n"); wprintf(L"The dump file has been saved in the \"C:\" volume.\r\n"); return true; } else { cl_wprintf(RED, L"Error!\r\n"); return false; } } #endif if (bAaLl86Test) { // Open the target kernel device object wprintf(L"Simple Kernel Driver Test - Opening the device... "); hTestDev = CreateFile(DosDevName, FILE_ALL_ACCESS, 0, NULL, OPEN_EXISTING, 0, NULL); dwLastErr = GetLastError(); if (hTestDev != INVALID_HANDLE_VALUE) cl_wprintf(GREEN, L"OK\r\n"); else { cl_wprintf(RED, L"Error!\r\n"); return false; } } // Allocate the buffer array lpBuffArray = new LPVOID[dwNumOfCpus]; RtlZeroMemory(lpBuffArray, dwNumOfCpus * sizeof(LPVOID)); // Start the device Tracing wprintf(L"Starting the Kernel-mode Tracing... "); bRetVal = DeviceIoControl(hPtDev, IOCTL_PTDRV_START_TRACE, (LPVOID)&ptUserReq, sizeof(PT_USER_REQ), lpBuffArray, sizeof(LPVOID) * dwNumOfCpus, &dwBytesIo, NULL); dwLastErr = GetLastError(); if (bRetVal) { cl_wprintf(GREEN, L"OK\r\n"); g_appData.currentTrace = ptUserReq; // Copy the returned Buffer array for (int i = 0; i < (int)dwNumOfCpus; i++) { g_appData.pCpuDescArray[i].lpPtBuff = (LPBYTE)lpBuffArray[i]; g_appData.pCpuDescArray[i].dwBuffSize = ptUserReq.dwTraceSize; } } else { cl_wprintf(RED, L"Error!\r\n"); return false; } Sleep(100); if (bAaLl86Test) { wprintf(L"Doing some test malicious activity (this could crash your system)... "); // Test the Search Module IOCTL bRetVal = DeviceIoControl(hTestDev, IOCTL_PTBUG_SEARCHKERNELMODULE, (LPVOID)lpKernelModName, (DWORD)(wcslen(lpKernelModName) + 1) * sizeof(WCHAR), (LPVOID)&kernelMod, sizeof(KERNEL_MODULE), &dwBytesIo, NULL); dwLastErr = GetLastError(); if (bRetVal) { // READ some memory from the CI.DLL module LPBYTE lpBuff = new BYTE[0x1000]; bRetVal = ReadFile(hTestDev, (LPVOID)lpBuff, 0x1000, &dwBytesIo, NULL); if (bRetVal && lpBuff[0] == 'M' && lpBuff[1] == 'Z') { DWORD dwValue = 0x4000C; DWORD dwOffset = 0x00194b4; // CI!g_CiDeveloperMode symbol bRetVal = SetFilePointer(hTestDev, dwOffset, NULL, FILE_BEGIN); bRetVal = WriteFile(hTestDev, (LPCVOID)&dwValue, sizeof(DWORD), &dwBytesIo, NULL); } } if (bRetVal) cl_wprintf(GREEN, L"OK\r\n"); else cl_wprintf(RED, L"Error!\r\n"); } else { wprintf(L"\r\n\r\nPress any key when you would like to stop the tracing...\r\n"); rewind(stdin); getwchar(); } CloseHandle(hTestDev); return (bRetVal != FALSE); }