diff --git a/UI/UI.cpp b/UI/UI.cpp new file mode 100644 index 0000000..d950c9d --- /dev/null +++ b/UI/UI.cpp @@ -0,0 +1,307 @@ +#include "pch.h" +#include +#include +#include +#include + +#include +#include + +#include "..\driver\public.h" +#include "findPFNDatabase.h" + +class statistics { +public: + statistics() + { + scannedPages = ignoredPagesNX = scannedProcesses = modifiedPages = 0; + } + + unsigned int scannedPages; + unsigned int ignoredPagesNX; + unsigned int scannedProcesses; + unsigned int modifiedPages; +}; + +class modifiedPage { +public: + modifiedPage(DWORD newProcessID, wchar_t* newModuleName, void* newPageBase, BYTE newSectionName[8], unsigned long long newSectionOffset); + + unsigned long processID; + std::wstring moduleName; + + unsigned long long pageBase; + + std::wstring sectionName; + unsigned long long sectionOffset; +}; + +modifiedPage::modifiedPage(DWORD newProcessID, wchar_t* newModuleName, void* newPageBase, BYTE newSectionName[8], unsigned long long newSectionOffset) + : processID(newProcessID), moduleName(newModuleName), pageBase((unsigned long long)newPageBase), sectionName(L""), sectionOffset(newSectionOffset) +{ + wchar_t sectionNameCleaned[9]; + memset(sectionNameCleaned, 0, 9 * sizeof(wchar_t)); + wsprintf(sectionNameCleaned, L"%.8s", newSectionName); + sectionName.append(sectionNameCleaned); +} + +BOOL EnableDebugPrivilege(BOOL bEnable) +{ + HANDLE hToken = nullptr; + LUID luid; + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) return FALSE; + if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) return FALSE; + + TOKEN_PRIVILEGES tokenPriv; + tokenPriv.PrivilegeCount = 1; + tokenPriv.Privileges[0].Luid = luid; + tokenPriv.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0; + + if (!AdjustTokenPrivileges(hToken, FALSE, &tokenPriv, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) return FALSE; + + return TRUE; +} + +int scanProcess(HANDLE driverHnd, DWORD targetPID, HANDLE toScanHandle, std::vector *resultsOut, statistics* stats) +{ + DWORD cbNeeded; + int s = EnumProcessModules(toScanHandle, NULL, 0, &cbNeeded); + if (s == 0) + { + printf("Couldn't call EnumProcessModules to get buffer size, gle %d\n", GetLastError()); + return -1; + } + + HMODULE* moduleList = (HMODULE*)malloc(cbNeeded); + memset(moduleList, 0, cbNeeded); + s = EnumProcessModules(toScanHandle, moduleList, cbNeeded, &cbNeeded); + if (s == 0) + { + // This'll happen sometimes if there's a module loaded between our calls. + // TODO: we can retry in this case. + printf("Couldn't call EnumProcessModules to get modules, gle %d.\n", GetLastError()); + return -1; + } + + for (HMODULE* thisModPtr = &moduleList[0]; thisModPtr < &moduleList[cbNeeded / sizeof(HMODULE)]; thisModPtr++) + { + HMODULE thisModule = *thisModPtr; + TCHAR szModName[MAX_PATH]; + memset(szModName, 0, MAX_PATH * sizeof(TCHAR)); + if (GetModuleFileNameEx(toScanHandle, thisModule, szModName, sizeof(szModName) / sizeof(TCHAR)) == 0) + { + printf("GetModuleFileNameEx failed, GLE %d\n", GetLastError()); + continue; + } + IMAGE_DOS_HEADER mz; + SIZE_T bytesRead; + if (!ReadProcessMemory(toScanHandle, thisModule, &mz, sizeof(IMAGE_DOS_HEADER), &bytesRead)) + { + printf("Can't read module MZ header\n"); + return -1; + } + if (mz.e_magic != IMAGE_DOS_SIGNATURE) + { + printf("MZ header not found\n"); + continue; + } + IMAGE_NT_HEADERS pe; + unsigned long long peAddress = (((unsigned long long)thisModule) + mz.e_lfanew); + if (!ReadProcessMemory(toScanHandle, (void*)peAddress, &pe, sizeof(IMAGE_NT_HEADERS), &bytesRead)) + { + printf("Can't read module PE header\n"); + return -1; + } + + if (pe.Signature != IMAGE_NT_SIGNATURE) + { + printf("PE header not found\n"); + continue; + } + IMAGE_SECTION_HEADER* sect; + unsigned long long firstSectionAddress = peAddress + FIELD_OFFSET(IMAGE_NT_HEADERS, OptionalHeader) + sizeof(IMAGE_OPTIONAL_HEADER); + sect = (IMAGE_SECTION_HEADER*)malloc(sizeof(IMAGE_SECTION_HEADER) * pe.FileHeader.NumberOfSections); + if (!ReadProcessMemory(toScanHandle, (LPCVOID)(firstSectionAddress), sect, sizeof(IMAGE_SECTION_HEADER) * pe.FileHeader.NumberOfSections, &bytesRead)) + { + printf("Can't read first section of module\n"); + return -1; + } + + for (unsigned long sectionIndex = 0; sectionIndex < pe.FileHeader.NumberOfSections; sectionIndex++) + { + IMAGE_SECTION_HEADER* thisSection = §[sectionIndex]; + unsigned long long relocatedSectionBase = thisSection->VirtualAddress + (unsigned long long)thisModule; + + // We are interested only in executable sections. + // TODO: check that discardable pages are zero'ed out? + // TODO: check that non-executable pages haven't been made executable? + if ((thisSection->Characteristics & IMAGE_SCN_MEM_EXECUTE) == 0) + { + stats->ignoredPagesNX += (thisSection->SizeOfRawData / 0x1000); + // printf("%ls!%s (at %p) is not executable, skipping\n", szModName, thisSection->Name, relocatedSectionBase); + continue; + } + //printf("scanning %ls!%s (at %p), size 0x%08lx\n", szModName, thisSection->Name, relocatedSectionBase, thisSection->SizeOfRawData); + + int dirtyPages = 0; + int errorPages = 0; + getPageInfoRequest req; + req.pageToCheck = relocatedSectionBase; + // Work out how many pages we will check + req.numberOfPagesToCheck = thisSection->Misc.VirtualSize / 0x1000; + if (thisSection->Misc.VirtualSize % 0x1000 != 0) + req.numberOfPagesToCheck++; + req.targetPID = targetPID; + + getPageInfoResponse* resp = (getPageInfoResponse*)malloc(sizeof(getPageInfoRequest) * req.numberOfPagesToCheck); + memset(resp, 0x00, sizeof(getPageInfoResponse) * req.numberOfPagesToCheck); + + DWORD bytesRet; + s = DeviceIoControl(driverHnd, IOCTL_DRIVER_QUERY_VA, &req, sizeof(req), resp, sizeof(getPageInfoResponse) * req.numberOfPagesToCheck, &bytesRet, NULL); + if (s == 0) + { + errorPages++; + printf("DeviceIoControl failed, GLE %d\n", GetLastError()); + return -1; + } + + stats->scannedPages += req.numberOfPagesToCheck; + + for (unsigned int n = 0; n < req.numberOfPagesToCheck; n++) + { + unsigned long long pageAddress = relocatedSectionBase + (n * 0x1000); + + if (!resp[n].isValid) + { + printf("Page at 0x%016llx (%ls!%s) not valid (maybe it's paged out?) 0x%08lx\n", pageAddress, szModName, thisSection->Name, thisSection->Characteristics); + errorPages++; + continue; + } + + if (resp[n].isDirty) + { + dirtyPages++; + resultsOut->push_back(modifiedPage(targetPID, (wchar_t*)szModName, (void*)pageAddress, thisSection->Name, (pageAddress - relocatedSectionBase))); + stats->modifiedPages++; + } + } + +// if (dirtyPages == 0) +// printf("Module %ls: OK\n", szModName); +// else +// printf("Module %ls: detected %d dirty pages!\n", szModName, dirtyPages); + } + } + + return 0; +} + +int setPFNDatabase(HANDLE driverHnd, unsigned long long PFNDatabaseStart) +{ + setPFNDatabaseRequest req; + req.offsetToMmPfnDatabaseInNtDllFromExAllocatePoolWithTag = PFNDatabaseStart; + + DWORD bytesRet; + int s = DeviceIoControl(driverHnd, IOCTL_DRIVER_SET_PFN_DATABASE, &req, sizeof(req), NULL, 0, &bytesRet, NULL); + if (s == 0) + { + printf("Failed to set PFN database to 0x%016llx: GLE %d\n", PFNDatabaseStart, GetLastError()); + return -1; + } + + return 0; +} + +int main() +{ + EnableDebugPrivilege(TRUE); + + HANDLE driverHnd = CreateFile(L"\\\\.\\cowspot", GENERIC_ALL, 0, NULL, OPEN_EXISTING, 0, NULL); + if (driverHnd == INVALID_HANDLE_VALUE) + { + printf("Couldn't open driver device '%ls', gle %d\n", DOS_DEVICE_NAME, GetLastError()); + return -1; + } + + if (setPFNDatabase(driverHnd, findPFNDatabase()) != 0) + return -1; + + HANDLE snapshotHnd = CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0); + if (snapshotHnd == INVALID_HANDLE_VALUE) + { + printf("CreateToolhelp32Snapshot failed, GLE %d\n", GetLastError()); + return -1; + } + + PROCESSENTRY32 proc; + memset(&proc, 0, sizeof(PROCESSENTRY32)); + proc.dwSize = sizeof(PROCESSENTRY32); + + if (!Process32First(snapshotHnd, &proc)) + { + printf("Process32First failed, GLE %d\n", GetLastError()); + return -1; + } + + statistics stat; + std::vector results; + + unsigned long start = GetTickCount(); + + while (Process32Next(snapshotHnd, &proc)) + { + HANDLE toScanHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, proc.th32ProcessID); + if (toScanHandle == NULL) + { + printf("Couldn't open target process with PID %d ('%ls'), gle %d\n", proc.th32ProcessID, proc.szExeFile, GetLastError()); + continue; + } + + stat.scannedProcesses++; + if (scanProcess(driverHnd, proc.th32ProcessID, toScanHandle, &results, &stat) != 0) + printf("Failed to scan process '%ls'\n", proc.szExeFile); + // else + // printf("Scanned process '%ls'\n", proc.szExeFile); + CloseHandle(toScanHandle); + } + + CloseHandle(snapshotHnd); + + unsigned long end = GetTickCount(); + + printf("Scan took %dms\n", (end - start)); + + // Print some stats and the results. + printf("Scanned %d pages, ignored %d NX pages (total %d). Found %d modified pages.\n", stat.scannedPages, stat.ignoredPagesNX, stat.ignoredPagesNX + stat.scannedPages, stat.modifiedPages); + for (unsigned int n = 0; n < results.size(); n++) + { + modifiedPage thisModifiedPage = results[n]; + printf("PID %04d module '%ls', section %S, offset 0x%08llux\n", thisModifiedPage.processID, thisModifiedPage.moduleName.c_str(), thisModifiedPage.sectionName.c_str(), thisModifiedPage.sectionOffset); + /* + HANDLE toScanHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, thisModifiedPage.processID); + if (toScanHandle == NULL) + { + printf("Couldn't open target process with PID %d ('%ls'), gle %d\n", proc.th32ProcessID, proc.szExeFile, GetLastError()); + continue; + } + SIZE_T bytesRead; + unsigned char* pageContents[0x2000]; + memset(pageContents, 0, 0x2000); + if (!ReadProcessMemory(toScanHandle, (LPCVOID)thisModifiedPage.pageBase, pageContents, 0x2000, &bytesRead)) + { + printf("ReadProcessMemory failed\n"); + continue; + } + + for (unsigned int n = 0; n < 0x2001; n++) + { + printf("0x%02hhx ", (unsigned)pageContents[n]); + if (n % 0x10 == 0) + printf("\n0x%08lx: ", n); + } + CloseHandle(toScanHandle);*/ + } + + return 0; +} diff --git a/UI/UI.vcxproj b/UI/UI.vcxproj new file mode 100644 index 0000000..25abdf1 --- /dev/null +++ b/UI/UI.vcxproj @@ -0,0 +1,182 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {BBBF57E1-3EC9-4C5C-8305-AD434ED61842} + Win32Proj + UI + 10.0.17763.0 + + + + Application + true + v141 + Unicode + false + + + Application + false + v141 + true + Unicode + false + + + Application + true + v141 + Unicode + false + + + Application + false + v141 + true + Unicode + false + + + + + + + + + + + + + + + + + + + + + false + $(VSInstallDir)\DIA SDK\Include;$(IncludePath) + + + true + $(VSInstallDir)\DIA SDK\Include;$(IncludePath) + + + true + $(VSInstallDir)\DIA SDK\Include;$(IncludePath) + + + false + $(VSInstallDir)\DIA SDK\Include;$(IncludePath) + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + true + + + Console + true + true + true + + + + + Use + Level3 + Disabled + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + true + + + Console + true + + + + + Use + Level3 + Disabled + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + true + + + Console + true + + + + + Use + Level3 + MaxSpeed + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + true + + + Console + true + true + true + + + + + + + + + + Create + Create + Create + Create + + + + + + + \ No newline at end of file diff --git a/UI/findPFNDatabase.cpp b/UI/findPFNDatabase.cpp new file mode 100644 index 0000000..8eab203 --- /dev/null +++ b/UI/findPFNDatabase.cpp @@ -0,0 +1,106 @@ +#include "pch.h" +#include +#include +#include + +#include + +unsigned long findSymbol(IDiaSymbol* g_pGlobalSymbol, const wchar_t* symbolName); + +unsigned long long findPFNDatabase() +{ + IDiaDataSource *g_pDiaDataSource; + IDiaSession *g_pDiaSession; + IDiaSymbol *g_pGlobalSymbol; + + // Assemble the path to ntoskrnl.exe. It'll be in System32. + wchar_t systemDir[MAX_PATH]; + GetSystemDirectory(systemDir, MAX_PATH); + std::wstring exeFilename(L""); + exeFilename.append(systemDir); + exeFilename.append(L"\\ntoskrnl.exe"); + + // Assemble the symbol path. We use the current directory as a cache path. + wchar_t curPath[MAX_PATH]; + GetCurrentDirectory(MAX_PATH, curPath); + std::wstring symPath(L""); + symPath.append(L"symsrv*symsrv.dll*"); + symPath.append(curPath); + symPath.append(L"*http://msdl.microsoft.com/download/symbols"); + + HRESULT hr = CoInitialize(NULL); + + hr = CoCreateInstance(__uuidof(DiaSource), NULL, CLSCTX_INPROC_SERVER, __uuidof(IDiaDataSource), (void **)&g_pDiaDataSource); + + if (FAILED(hr)) + { + printf("CoCreateInstance failed for UUID of IDiaDataSource - HRESULT is %08X\n", hr); + if (hr == REGDB_E_CLASSNOTREG) + printf("This means the DIA class is not registered. You may need to register it via regsvr32.\n"); + return false; + } + + printf("Loading PDBs..\n"); + hr = g_pDiaDataSource->loadDataForExe(exeFilename.c_str(), symPath.c_str(), NULL); + if (FAILED(hr)) + { + printf("loadDataForExe failed for file '%ls' - HRESULT is %08X\n", exeFilename.c_str(), hr); + if (hr == E_PDB_NOT_FOUND) + printf("This is E_PDB_NOT_FOUND. Check that you have internet connectivity, and the correct symbol server configured.\n"); + return false; + } + printf("Loading PDBs complete.\n"); + + hr = (g_pDiaDataSource)->openSession(&g_pDiaSession); + + if (FAILED(hr)) + { + printf("openSession failed - HRESULT is %08X\n", hr); + return false; + } + + g_pDiaSession->put_loadAddress(0x0); + + hr = (g_pDiaSession)->get_globalScope(&g_pGlobalSymbol); + + if (hr != S_OK) + { + printf("get_globalScope failed\n"); + return false; + } + + // Now we can resolve the symbols we want. + unsigned long long MmPFNDatabase = findSymbol(g_pGlobalSymbol, L"MmPfnDatabase"); + unsigned long long ExAllocatePoolWithTag = findSymbol(g_pGlobalSymbol, L"ExAllocatePoolWithTag"); + + if (MmPFNDatabase == 0) + printf("Unable to resolve MmPFNDatabase"); + if (ExAllocatePoolWithTag == 0) + printf("Unable to resolve ExAllocatePoolWithTag"); + if (MmPFNDatabase == 0 || ExAllocatePoolWithTag == 0) + return false; + + return ExAllocatePoolWithTag - MmPFNDatabase; +} + +unsigned long findSymbol(IDiaSymbol* g_pGlobalSymbol, const wchar_t* symbolName) +{ + IDiaEnumSymbols *pEnumSymbols; + + if (FAILED(g_pGlobalSymbol->findChildren(SymTagPublicSymbol, symbolName, nsNone, &pEnumSymbols))) + return false; + + IDiaSymbol *pCompiland; + unsigned long celt; + + if (FAILED(pEnumSymbols->Next(1, &pCompiland, &celt)) || (celt != 1)) + return false; + + unsigned long symRVA; + pCompiland->get_relativeVirtualAddress(&symRVA); + + pCompiland->Release(); + pEnumSymbols->Release(); + + return symRVA; +} \ No newline at end of file diff --git a/UI/findPFNDatabase.h b/UI/findPFNDatabase.h new file mode 100644 index 0000000..37ffba0 --- /dev/null +++ b/UI/findPFNDatabase.h @@ -0,0 +1 @@ +unsigned long long findPFNDatabase(); \ No newline at end of file diff --git a/UI/pch.cpp b/UI/pch.cpp new file mode 100644 index 0000000..1d9f38c --- /dev/null +++ b/UI/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/UI/pch.h b/UI/pch.h new file mode 100644 index 0000000..54af8b6 Binary files /dev/null and b/UI/pch.h differ diff --git a/cowspot.sln b/cowspot.sln index 6b706b4..092a6a9 100644 --- a/cowspot.sln +++ b/cowspot.sln @@ -3,6 +3,10 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.28010.2046 MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "driver", "driver\driver.vcxproj", "{65A559E4-6946-4252-BFBE-C3B5D1B8108C}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UI", "UI\UI.vcxproj", "{BBBF57E1-3EC9-4C5C-8305-AD434ED61842}" +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "inject", "inject\inject.vcxproj", "{35928C18-5D5D-4BC6-88CE-E5BBE00C0446}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "inject_simple", "inject_simple\inject_simple.vcxproj", "{5C90611E-0874-4618-9C3D-B1385C83FBDF}" @@ -17,6 +21,22 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {65A559E4-6946-4252-BFBE-C3B5D1B8108C}.Debug|x64.ActiveCfg = Debug|x64 + {65A559E4-6946-4252-BFBE-C3B5D1B8108C}.Debug|x64.Build.0 = Debug|x64 + {65A559E4-6946-4252-BFBE-C3B5D1B8108C}.Debug|x64.Deploy.0 = Debug|x64 + {65A559E4-6946-4252-BFBE-C3B5D1B8108C}.Debug|x86.ActiveCfg = Debug|x64 + {65A559E4-6946-4252-BFBE-C3B5D1B8108C}.Release|x64.ActiveCfg = Release|x64 + {65A559E4-6946-4252-BFBE-C3B5D1B8108C}.Release|x64.Build.0 = Release|x64 + {65A559E4-6946-4252-BFBE-C3B5D1B8108C}.Release|x64.Deploy.0 = Release|x64 + {65A559E4-6946-4252-BFBE-C3B5D1B8108C}.Release|x86.ActiveCfg = Release|x64 + {BBBF57E1-3EC9-4C5C-8305-AD434ED61842}.Debug|x64.ActiveCfg = Debug|x64 + {BBBF57E1-3EC9-4C5C-8305-AD434ED61842}.Debug|x64.Build.0 = Debug|x64 + {BBBF57E1-3EC9-4C5C-8305-AD434ED61842}.Debug|x86.ActiveCfg = Debug|Win32 + {BBBF57E1-3EC9-4C5C-8305-AD434ED61842}.Debug|x86.Build.0 = Debug|Win32 + {BBBF57E1-3EC9-4C5C-8305-AD434ED61842}.Release|x64.ActiveCfg = Release|x64 + {BBBF57E1-3EC9-4C5C-8305-AD434ED61842}.Release|x64.Build.0 = Release|x64 + {BBBF57E1-3EC9-4C5C-8305-AD434ED61842}.Release|x86.ActiveCfg = Release|Win32 + {BBBF57E1-3EC9-4C5C-8305-AD434ED61842}.Release|x86.Build.0 = Release|Win32 {35928C18-5D5D-4BC6-88CE-E5BBE00C0446}.Debug|x64.ActiveCfg = Debug|x64 {35928C18-5D5D-4BC6-88CE-E5BBE00C0446}.Debug|x64.Build.0 = Debug|x64 {35928C18-5D5D-4BC6-88CE-E5BBE00C0446}.Debug|x86.ActiveCfg = Debug|Win32 diff --git a/documentation/moduleStompingBlogPost-part-3.docx b/documentation/moduleStompingBlogPost-part-3.docx new file mode 100644 index 0000000..9919853 Binary files /dev/null and b/documentation/moduleStompingBlogPost-part-3.docx differ diff --git a/driver/driver.c b/driver/driver.c new file mode 100644 index 0000000..1198ea5 --- /dev/null +++ b/driver/driver.c @@ -0,0 +1,395 @@ +#include +#include +//#include +#include "driver.h" +#include "public.h" + +_Use_decl_annotations_ DRIVER_INITIALIZE DriverEntry; +_Use_decl_annotations_ DRIVER_UNLOAD DriverUnload; +_Dispatch_type_(IRP_MJ_CREATE) DRIVER_DISPATCH irp_mj_create; +_Dispatch_type_(IRP_MJ_CLOSE) DRIVER_DISPATCH irp_mj_close; +_Dispatch_type_(IRP_MJ_DEVICE_CONTROL) DRIVER_DISPATCH irp_mj_device_control; + +NTSTATUS queryVA(getPageInfoRequest* params, getPageInfoResponse* response); +NTSTATUS queryVAFromIRP(PIRP Irp); +NTSTATUS setPFNDatabase(setPFNDatabaseRequest* req); +NTSTATUS setPFNDatabaseFromIRP(PIRP Irp); +int isTableEntryValid(unsigned long long entry); +unsigned long long getChildTableFromTableEntry(unsigned long long entry); +__drv_requiresIRQL(APC_LEVEL) NTSTATUS readMemoryFromPhysical(unsigned long long address, char* errMsg, void* tableOut); + +privateInfo prv; + +// TODO: Get PFN structure info via PDBs instead of hardcoding it here. +struct PFN +{ + // 0x00 + unsigned long long padding1; + // 0x08 + unsigned long long PTEAddress; + // 0x10 + unsigned long long OriginalPte; + // 0x18 + unsigned long long u2; + // 0x20 - u3 + unsigned short referenceCount; + unsigned char e1; + unsigned char e3; + unsigned long e4; // or e2 + // 0x28 + unsigned long long u4; +}; + +_Use_decl_annotations_ NTSTATUS DriverEntry(_In_ struct _DRIVER_OBJECT *DriverObject, _In_ PUNICODE_STRING RegistryPath) +{ + NTSTATUS s; + UNICODE_STRING deviceName; + UNICODE_STRING DOSDeviceName; + + UNREFERENCED_PARAMETER(RegistryPath); + + DriverObject->DriverUnload = DriverUnload; + DriverObject->MajorFunction[IRP_MJ_CREATE] = irp_mj_create; + DriverObject->MajorFunction[IRP_MJ_CLOSE] = irp_mj_close; + DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = irp_mj_device_control; + + // Initialise our 'private' data, shared throughout the driver + memset(&prv, 0, sizeof(privateInfo)); + + // Create our device and the DOS symlink to it, as usual + RtlInitUnicodeString(&deviceName, DEVICE_NAME); + s = IoCreateDevice(DriverObject, 0, &deviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &prv.deviceObject); + if (!NT_SUCCESS(s)) + { + DbgPrint("Failed IoCreateDevice: 0x%08lx\n", s); + return s; + } + RtlInitUnicodeString(&DOSDeviceName, DOS_DEVICE_NAME); + + s = IoCreateSymbolicLink(&DOSDeviceName, &deviceName); + if (!NT_SUCCESS(s)) + { + IoDeleteDevice(prv.deviceObject); + DbgPrint("Failed IoCreateSymbolicLink: 0x%08lx\n", s); + return s; + } + + return STATUS_SUCCESS; +} + +VOID DriverUnload(_In_ struct _DRIVER_OBJECT *DriverObject) +{ + UNICODE_STRING DOSDeviceName; + + UNREFERENCED_PARAMETER(DriverObject); + + RtlInitUnicodeString(&DOSDeviceName, DOS_DEVICE_NAME); + IoDeleteSymbolicLink(&DOSDeviceName); + IoDeleteDevice(prv.deviceObject); +} + +_Use_decl_annotations_ NTSTATUS irp_mj_create(PDEVICE_OBJECT DeviceObject, PIRP Irp) +{ + UNREFERENCED_PARAMETER(DeviceObject); + + Irp->IoStatus.Status = STATUS_SUCCESS; + Irp->IoStatus.Information = 0; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ NTSTATUS irp_mj_close(PDEVICE_OBJECT DeviceObject, PIRP Irp) +{ + UNREFERENCED_PARAMETER(DeviceObject); + + // FIXME: Make sure all pending requests on this handle are complete + Irp->IoStatus.Status = STATUS_SUCCESS; + Irp->IoStatus.Information = 0; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + return STATUS_SUCCESS; +} + +NTSTATUS setPFNDatabaseFromIRP(PIRP Irp) +{ + PIO_STACK_LOCATION irpStack; + setPFNDatabaseRequest inputBuffer; + int bytesReturned; + NTSTATUS s; + + bytesReturned = 0; + + irpStack = IoGetCurrentIrpStackLocation(Irp); + if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(setPFNDatabaseRequest)) + { + s = STATUS_BUFFER_TOO_SMALL; + goto out; + } + + memcpy(&inputBuffer, Irp->AssociatedIrp.SystemBuffer, sizeof(setPFNDatabaseRequest)); + + s = setPFNDatabase(&inputBuffer); + +out: + Irp->IoStatus.Status = s; + Irp->IoStatus.Information = 0; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + return s; +} + +_Use_decl_annotations_ NTSTATUS irp_mj_device_control(PDEVICE_OBJECT DeviceObject, PIRP Irp) +{ + PIO_STACK_LOCATION irpStack; + unsigned long functionCode; + + UNREFERENCED_PARAMETER(DeviceObject); + + irpStack = IoGetCurrentIrpStackLocation(Irp); + functionCode = irpStack->Parameters.DeviceIoControl.IoControlCode; + + switch (functionCode) + { + case IOCTL_DRIVER_QUERY_VA: + return queryVAFromIRP(Irp); + case IOCTL_DRIVER_SET_PFN_DATABASE: + return setPFNDatabaseFromIRP(Irp); + default: + DbgPrint("IRP_MJ_DEVICE_CONTROL: Unrecognised function code 0x%08lx\n", functionCode); + } + + Irp->IoStatus.Status = STATUS_ILLEGAL_FUNCTION; + Irp->IoStatus.Information = 0; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + return STATUS_SUCCESS; +} + +NTSTATUS setPFNDatabase(setPFNDatabaseRequest* req) +{ + PVOID MmPfnDatabaseUnsafe; + unsigned long long MmPfnDatabase; + MM_COPY_ADDRESS src; + SIZE_T bytesRead; + NTSTATUS s; + int didExcept; + + unsigned long numberOfPFNs = 0x2000; // FIXME + + // Since we don't know the base address of ntdll (and don't want to call any undocumented stuff to get it), we accept an offset to MmPfnDatabase + // from an exported entry (ExAllocatePoolWithTag). Since this comes from userspace, we still need to santise it as best we can. We can't make it + // foolproof but we can do some basic checks. Since we only ever read the PFN database via MmCopyMemory, it should be safe for userspace to give + // us a bad address, anyway. + MmPfnDatabaseUnsafe = (PVOID)( ((unsigned long long)ExAllocatePoolWithTag) - req->offsetToMmPfnDatabaseInNtDllFromExAllocatePoolWithTag ); + + // We now have a pointer to MmPfnDatabase, which is itself a pointer to the first PFN. We should try to read it, and find the PFN DB base. + src.VirtualAddress = MmPfnDatabaseUnsafe; +#pragma warning( push ) +#pragma warning( disable : 6001 ) // VS things 'MmPfnDatabase' can be uninitialized in this call. It cannot. + s = MmCopyMemory(&MmPfnDatabase, src, sizeof(PVOID), MM_COPY_MEMORY_VIRTUAL, &bytesRead); +#pragma warning( pop ) + if (!NT_SUCCESS(s)) + { + DbgPrint("Cannot read MmPfnDatabase pointer %p as provided by userspace\n", MmPfnDatabaseUnsafe); + return s; + } + if (bytesRead != sizeof(PVOID)) + { + DbgPrint("Short read of read MmPfnDatabase pointer %p as provided by userspace (read %llu of %llu bytes)\n", MmPfnDatabaseUnsafe, bytesRead, sizeof(PVOID)); + return STATUS_ACCESS_VIOLATION; + } + + // Now we have the PFN database pointer, and we can do some basic checks on it. + // It should be aligned on a 4K boundary (I think?). This is totally from observation + // and may be incorrect. + if ((MmPfnDatabase & 0x0000000000000fff) != 0) + { + DbgPrint("Dereferenced MmPfnDatabase pointer is not correctly aligned?\n"); + return STATUS_BAD_DATA; + } + + // This should not be in a user-space buffer + __try + { + ProbeForRead((PVOID)MmPfnDatabase, sizeof(struct PFN) * numberOfPFNs, 1); + didExcept = FALSE; + } +#pragma warning( push ) +#pragma warning( disable : 6320 ) // "warning C6320: Exception-filter expression is the constant EXCEPTION_EXECUTE_HANDLER. This might mask exceptions that were not intended to be handled." + __except (EXCEPTION_EXECUTE_HANDLER) +#pragma warning( pop ) + { + didExcept = TRUE; + } + if (!didExcept) + { + DbgPrint("Dereferenced MmPfnDatabase pointer is in userspace\n"); + return STATUS_BAD_DATA; + } + + // TODO: more checks. We're giving userspace the ability to give kernel space a pointer here + // so we should be as careful as we possibly can be. + + // OK, all our checks passed! + prv.PFNDatabase = MmPfnDatabase; + DbgPrint("MmPfnDatabase is 0x%016llx\n", prv.PFNDatabase); + return STATUS_SUCCESS; +} + +NTSTATUS queryVAFromIRP(PIRP Irp) +{ + PIO_STACK_LOCATION irpStack; + getPageInfoRequest inputBuffer; + getPageInfoResponse* outputBuffer; + int bytesReturned; + NTSTATUS s; + + bytesReturned = 0; + + irpStack = IoGetCurrentIrpStackLocation(Irp); + if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(getPageInfoRequest)) + { + s = STATUS_BUFFER_TOO_SMALL; + goto out; + } + memcpy(&inputBuffer, Irp->AssociatedIrp.SystemBuffer, sizeof(getPageInfoRequest)); + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(getPageInfoResponse) * inputBuffer.numberOfPagesToCheck) + { + s = STATUS_BUFFER_TOO_SMALL; + goto out; + } + + outputBuffer = (getPageInfoResponse*)Irp->AssociatedIrp.SystemBuffer; + memset(outputBuffer, 0, sizeof(getPageInfoResponse) * inputBuffer.numberOfPagesToCheck); + + s = queryVA(&inputBuffer, outputBuffer); + + if (NT_SUCCESS(s)) + bytesReturned = sizeof(getPageInfoResponse) * inputBuffer.numberOfPagesToCheck; + +out: + Irp->IoStatus.Status = s; + Irp->IoStatus.Information = bytesReturned; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + return s; +} + +NTSTATUS queryVA(getPageInfoRequest* params, getPageInfoResponse* response) +{ + PMDL mdl; + PPFN_NUMBER pfnArray; + unsigned int pfnArrayCount; + unsigned int pfnIdx; + struct PFN* MmPfnDatabase = (struct PFN*)prv.PFNDatabase; + struct PFN ourPFN; + unsigned long long pte; + KAPC_STATE state; + PEPROCESS eprocess; + NTSTATUS s; + MM_COPY_ADDRESS srcAddress; + PHYSICAL_ADDRESS phys; + SIZE_T numRead; + + UNREFERENCED_PARAMETER(response); + + if (params->numberOfPagesToCheck == 0) + { + DbgPrint("Asked to scan 0 pages\n"); + return STATUS_INVALID_PARAMETER; + } + + mdl = IoAllocateMdl((PVOID)params->pageToCheck, 0x1000 * params->numberOfPagesToCheck, FALSE, FALSE, NULL); + if (!mdl) + { + return STATUS_NO_MEMORY; + } + + s = PsLookupProcessByProcessId((HANDLE)params->targetPID, &eprocess); + if (!NT_SUCCESS(s)) + { + IoFreeMdl(mdl); + DbgPrint("PsLookupProcesByProcessId failed for PID 0x%04lx: 0x%08lx\n", params->targetPID, s); + return s; + } + + KeStackAttachProcess(eprocess, &state); + + __try + { + MmProbeAndLockPages(mdl, UserMode, IoReadAccess); + } +#pragma warning( push ) +#pragma warning( disable : 6320 ) // "warning C6320: Exception-filter expression is the constant EXCEPTION_EXECUTE_HANDLER. This might mask exceptions that were not intended to be handled." + __except (EXCEPTION_EXECUTE_HANDLER) +#pragma warning( pop ) + { + s = STATUS_BAD_DATA; + goto out; + } + pfnArray = MmGetMdlPfnArray(mdl); + pfnArrayCount = ADDRESS_AND_SIZE_TO_SPAN_PAGES(MmGetMdlVirtualAddress(mdl), MmGetMdlByteCount(mdl)); + // Read the PTE from the PFN database using MmCopyMemory, in case we have the pfn database base address wrong. + // MmCopyMemory won't let me read us the memory by VA - not 100% sure why but I suspect because it is checked + // against the PFN table and no mapping is found (?) - so we just translate to physical address and read that + // instead. + for (pfnIdx = 0; pfnIdx < pfnArrayCount; pfnIdx++) + { + phys = MmGetPhysicalAddress(&MmPfnDatabase[pfnArray[pfnIdx]]); + srcAddress.PhysicalAddress.QuadPart = phys.QuadPart; +#pragma warning( push ) +#pragma warning( disable : 6001 ) // VS things 'ourPFN' can be uninitialized in this call. It cannot. + s = MmCopyMemory(&ourPFN, srcAddress, sizeof(struct PFN), MM_COPY_MEMORY_PHYSICAL, &numRead); +#pragma warning( pop ) + if (!NT_SUCCESS(s) || numRead != sizeof(struct PFN)) + { + DbgPrint("Failed to read PFN from PFN database at %p (%p[0x%16llx]): NTSTATUS 0x%08lx, transferred %llu of %llu bytes\n", srcAddress.VirtualAddress, MmPfnDatabase, pfnArray[pfnIdx], s, numRead, sizeof(struct PFN)); + if (NT_SUCCESS(s)) + s = STATUS_PARTIAL_COPY; + goto out; + } + pte = ourPFN.PTEAddress; + + // DbgPrint("VA 0x%016llx PFN %p\n", params->pageToCheck, &MmPfnDatabase[pfn[0]]); + + response[pfnIdx].isValid = TRUE; // TODO + response[pfnIdx].isDirty = (ourPFN.e1 >> 4) & 0x01; + } + s = STATUS_SUCCESS; + +out: + MmUnlockPages(mdl); + IoFreeMdl(mdl); + KeUnstackDetachProcess(&state); + + return s; +} + +__drv_requiresIRQL(APC_LEVEL) +NTSTATUS readMemoryFromPhysical(unsigned long long address, char* errMsg, void* tableOut) +{ + NTSTATUS s; + MM_COPY_ADDRESS srcAddress; + SIZE_T numRead; + SIZE_T bytesToRead = 0x200 * sizeof(unsigned long long); + + srcAddress.PhysicalAddress.QuadPart = address; + s = MmCopyMemory(tableOut, srcAddress, bytesToRead, MM_COPY_MEMORY_PHYSICAL, &numRead); + + if (!NT_SUCCESS(s) || numRead != bytesToRead) + { + DbgPrint("Failed to MmCopyMemory table '%s' from physical location 0x%08llx: 0x%08lx (read 0x%08llx of 0x%08llx bytes)\n", errMsg, srcAddress.PhysicalAddress.QuadPart, s, numRead, bytesToRead); + return STATUS_UNSUCCESSFUL; + } + + return STATUS_SUCCESS; +} + +unsigned long long getChildTableFromTableEntry(unsigned long long entry) +{ + // TODO/FIXME: We should honour the size of the child table pointer here, which is set as + // M-12 (M being set in the sillicon I think). Bit 63 is XD, and 62-52 is ignored, but 51 + // through M is reserved by the sillicon so we should ignore it.. + return ((unsigned long long)((entry & ~(0xfff0'0000'0000'0FFF)) )); +} + +int isTableEntryValid(unsigned long long entry) +{ + return (entry & 0x01) != 0; +} \ No newline at end of file diff --git a/driver/driver.h b/driver/driver.h new file mode 100644 index 0000000..d47c6ad --- /dev/null +++ b/driver/driver.h @@ -0,0 +1,10 @@ +#pragma once + +struct privateInfo +{ + PDEVICE_OBJECT deviceObject; + unsigned long long PFNDatabase; +}; typedef struct privateInfo privateInfo; + +extern privateInfo prv; + diff --git a/driver/driver.inf b/driver/driver.inf new file mode 100644 index 0000000..30c0249 --- /dev/null +++ b/driver/driver.inf @@ -0,0 +1,32 @@ +; +; driver.inf +; + +[Version] +Signature="$WINDOWS NT$" +Class=System +ClassGuid={4d36e97d-e325-11ce-bfc1-08002be10318} +Provider=%ManufacturerName% +DriverVer=1 +CatalogFile=driver.cat + +[DestinationDirs] +DefaultDestDir = 12 + + +[SourceDisksNames] +1 = %DiskName%,,,"" + +[SourceDisksFiles] + + +[Manufacturer] +%ManufacturerName%=Standard,NT$ARCH$ + +[Standard.NT$ARCH$] + + +[Strings] +ManufacturerName="" ;TODO: Replace with your manufacturer name +ClassName="" +DiskName="driver Source Disk" diff --git a/driver/driver.vcxproj b/driver/driver.vcxproj new file mode 100644 index 0000000..705235a --- /dev/null +++ b/driver/driver.vcxproj @@ -0,0 +1,95 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {65A559E4-6946-4252-BFBE-C3B5D1B8108C} + {dd38f7fc-d7bd-488b-9242-7d8754cde80d} + v4.5 + 12.0 + Debug + Win32 + driver + 10.0.17763.0 + + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + WDM + false + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + WDM + false + + + + + + + + + + + DbgengKernelDebugger + true + true + AllRules.ruleset + $(IncludePath) + + + DbgengKernelDebugger + true + true + AllRules.ruleset + $(IncludePath) + + + + Aux_klib.lib;%(AdditionalDependencies) + + + true + + + + + Aux_klib.lib;%(AdditionalDependencies) + + + true + _PFT_SHOULD_CHECK_RETURN;%(PreprocessorDefinitions) + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/driver/public.h b/driver/public.h new file mode 100644 index 0000000..9838a37 --- /dev/null +++ b/driver/public.h @@ -0,0 +1,27 @@ +#pragma once + +#define DOS_DEVICE_NAME L"\\DosDevices\\cowspot" +#define DEVICE_NAME L"\\Device\\cowspot" + +struct getPageInfoResponse +{ + unsigned char isValid; + unsigned char isDirty; +}; typedef struct getPageInfoResponse getPageInfoResponse; + +struct getPageInfoRequest +{ + unsigned long targetPID; + unsigned long numberOfPagesToCheck; + unsigned long long pageToCheck; +}; typedef struct getPageInfoRequest getPageInfoRequest; + +#define IOCTL_DRIVER_QUERY_VA CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) + +struct setPFNDatabaseRequest +{ + unsigned long long offsetToMmPfnDatabaseInNtDllFromExAllocatePoolWithTag; +}; typedef struct setPFNDatabaseRequest setPFNDatabaseRequest; + +#define IOCTL_DRIVER_SET_PFN_DATABASE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +