Applocker and PowerShell: how do they tightly work together?

  • Goal: Make sure to understand how Applocker and PowerShell work together.
  • Background

Let’s suppose you’re familiar with Applocker.
(that’s not a requirement to read this post 🙂 )
Anyway, please keep in mind the following that appears in the official online documentation:

Source: https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/applocker/applocker-architecture-and-components

A script is run

Before a script file is run, the script host (for example. for .ps1 files the script host is PowerShell) invokes AppLocker to verify the script. AppLocker invokes the Application Identity component in user-mode with the file name or file handle to calculate the file properties. The script file then is evaluated against the AppLocker policy to verify that it is allowed to run. In each case, the actions taken by AppLocker are written to the event log.

Source: https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/applocker/security-considerations-for-applocker
Source:https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/applocker/understand-applocker-policy-design-decisions

AppLocker can only control VBScript, JScript, .bat files, .cmd files, and Windows PowerShell scripts. It does not control all interpreted code that runs within a host process, for example, Perl scripts and macros. Interpreted code is a form of executable code that runs within a host process. For example, Windows batch files (*.bat) run within the context of the Windows Command Host (cmd.exe). To control interpreted code by using AppLocker, the host process must call AppLocker before it runs the interpreted code, and then enforce the decision returned by AppLocker. Not all host processes call into AppLocker and, therefore, AppLocker cannot control every kind of interpreted code, such as Microsoft Office macros.

Source:https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/applocker/using-event-viewer-with-applocker

Event ID: Level: Event message: Description:
8007 Error *File name* was not allowed to run. Access to *file name* is restricted by the administrator. Applied only when the **Enforce rules ** enforcement mode is set either directly or indirectly through Group Policy inheritance. The script or .msi file cannot run.
  • Some Requirements:
  • Have a computer that meets the Applocker requirement to enforce an Applocker group policy (=have an Enterprise edition of Windows)
  • Have Windows PowerShell up-to-date
  • Create the default rules for EXE,MSI,SCRIPTS,Packages and delete the rule named “(Default rule) All scripts” that has a single ‘*’ in the Path and applies for the local built-in administrators group.
  • Enforce the rules
  • Start the AppId service
  • Refresh the local group policy
  • Make sure Applocker “allow mode” (a.k.a whitelist mode) is enforced and that PowerShell runs in Constrained Language mode (even for the administrator)
  • Have the ExecutionPolicy set to “Remote Signed”
    • The (simple) case of a .bat file:

    Let’s create a .bat file that contains nothing malicious and copy it in a location where it’s allowed to run and in another location where it’s not.
    Let’s assess now how the above documentation applies!

    # LangMode? 
    $ExecutionContext.SessionState.LanguageMode
    
    # Appplocker service running?
    Get-Service -Name AppIdsvc | fl St*
    
    # Create a batch file in allowed location
    @'
    @echo off
    echo Hello world
    '@ | Out-File C:\windows\test.bat -Encoding ascii
    
    # Copy the same file in a denied location
    Copy-Item -Path C:\Windows\test.bat -Destination C:\
    
    # How Applocker would evaluate its RUN decisions
    Get-AppLockerPolicy -Effective | 
    Test-AppLockerPolicy -Path C:\test.bat -User Everyone
    Get-AppLockerPolicy -Effective | 
    Test-AppLockerPolicy -Path C:\Windows\test.bat -User Everyone
    
    # Run these 2 scripts
    C:\test.bat
    C:\Windows\test.bat
    
    # What appears in the log
    Get-WinEvent "Microsoft-Windows-AppLocker/MSI and Script" `
    -MaxEvents 100 | Where Message -match "TEST.BAT" | 
    Select -First 2
    

    We can see that the above documentation strictly applies. The .bat file in a location that is not explicitly mentioned in the rules is denied from running by default.
    The code in the .bat file isn’t executed, I get a “This program is blocked by group policy. For more information, contact your system administrator.” message instead and a related 8007 event is generated.

    Let’s have a quick look at what appears in the eventlog.
    The file %OSDRIVE%\TEST.BAT isn’t allowed to run because there’s no rule that would allow it to be executed. A DeniedbyDefault RUN decision from Applocker has a RuleId set to {00000000-0000-0000-0000-000000000000}.

    The file %WINDIR%\TEST.BAT was allowed to run because the rule named “(Default Rule) All scripts located in the Windows folder” allows it explicitly.

    We have seen so far that the events written to the eventlog, whether the file is really executed or not and the PolicyDecision(s) reported by the Test-AppLockerPolicy cmdlet are fully aligned and coherent with the official documentation.

    • The (advanced) case of a .ps1 file:

    Let’s create a .ps1 file that contains nothing malicious and copy it in a location where it’s allowed to run and in another location where it’s not.
    Let’s assess now how the above documentation applies!

    # LangMode? 
    $ExecutionContext.SessionState.LanguageMode
    
    # Appplocker service running?
    Get-Service -Name AppIdsvc | fl St*
    
    # Create a PowerShell script file in allowed location
    'echo "Hello World from .PS1 file!"' | 
    Out-File C:\Windows\test.ps1 -Encoding ascii
    
    # Copy the same file in a denied location
    Copy-Item -Path C:\Windows\test.ps1 -Destination C:\
    
    # How Applocker would evaluate its RUN decisions
    Get-AppLockerPolicy -Effective | 
    Test-AppLockerPolicy -Path C:\test.ps1 -User Everyone
    Get-AppLockerPolicy -Effective | 
    Test-AppLockerPolicy -Path C:\Windows\test.ps1 -User Everyone
    
    # Run these 2 scripts
    C:\test.ps1
    C:\Windows\test.ps1
    
    # What appears in the log
    Get-WinEvent "Microsoft-Windows-AppLocker/MSI and Script" `
    -MaxEvents 100 | Where Message -match "TEST.PS1" | 
    Select -First 4
    

    We can see in the above picture that:

  • PowerShell host is running in constrained language mode (it’s evaluated when the process is started)
  • The content of the script isn’t malicious and doesn’t contain anything that the Constrained language mode would block.
  • The PolicyDecision(s) reported that c:\test.ps1 should be denied from running by default and C:\windows\test.p1 should run because the rule named “(Default Rule) All scripts located in the Windows folder” allows it explicitly.
  • C:\test.ps1 is executed. WAIT WHAT??? That’s unexpected, isn’t it? Why?
  • C:\windows\test.ps1 is executed. That was expected.
  • There are 4 events generated for the 2 executions.
  • Although C:\test.ps1 is executed, there’s an event Id 8007 telling that it has not! This could create a false sense of insecurity. 🙄

    We have seen so far that the events written to the eventlog, whether the file is really executed or not and the PolicyDecision(s) reported by the Test-AppLockerPolicy cmdlet are not fully aligned and coherent with the official documentation.

    Before jumping to conclusions and interpreting the above behavior, let me please introduce what happens if the PowerShell host is started in “Full Language” mode.

    Please ignore for the moment the last execution in the above picture. I’ll try to explain it later on.
    We can see in the above picture that:

  • PowerShell host is running in Full language mode (The language mode to be used is evaluated when the process is started)
  • The content of the script isn’t malicious and any language element would be allowed in this language mode.
  • The PolicyDecision(s) reported that c:\test.ps1 should be denied by default and shouldn’t run because there isn’t any rule that allows it.
  • C:\test.ps1 is not executed. That’s expected. I get a message saying: “File C:\test.ps1 cannot be loaded because its operation is blocked by software restriction policies, such as those created by using Group Policy”
  • There’s a single message in the eventlog saying “%OSDRIVE%\TEST.PS1 was prevented from running.”. At the execution time of the script, the Applocker rules have been evaluated and it appears that there isn’t any rule that would explicitly allow this script to run.
      • How could the above behaviors be explained? Is it normal?

    What’s more dangerous? Full Language mode or Constrained Language mode?

    The answer is Full Language mode. You should avoid it because if an attacker executes a malicious piece of code in this mode, he’ll have full access to any language element and therefore to any Windows API.

    Constrained language mode was introduced and designed to restrict access to sensitive language elements that can be used to invoke arbitrary Windows APIs.

    In Full Language mode, the only layer of protection that would block malicious code are the Applocker rules. Therefore, you’ve the Applocker PolicyDecision(s) that (first) apply. The security applies at the filesytem level. Let’s say Applocker operates how it was initially designed to operate: in a “File Mode”.

    If you’ve Applocker rules in “Allow mode”, any PowerShell host started will use the Constrained Language mode. This is where a 2nd security layer kicks in. The protection is somehow delegated to this language mode because it enforces so many restrictions on sensitive language elements that (almost?) all malicious code is using. This 2nd security layer applies at the file content level. Let’s say it’s a “File Content Mode”.

    Remember the last line I told you to ignore? I’ll explain it:
    To be able to get a PowerShell host running in full language mode, I restored all the default Applocker rules and especially the one that uses a wildcard for administrators, ran gpupdate.exe, deleted this rule and ran gpupdate.exe again. Inside a PowerShell host running in full language mode when I called powershell.exe the language mode to be applied was reevaluated and it’s the constrained language mode that was chosen. And we know now that file system based rules were ignored.

    At each script execution, the PowerShell host queries Applocker rules.
    These rules are bypassed if the script is executed in the constrained language mode.

      • How restrictive is the Constrained Language mode?

    Here’s an example of a piece of code using a method restricted by the Constrained Language mode:

    # LangMode? 
    $ExecutionContext.SessionState.LanguageMode
    
    # Create a PowerShell script file in allowed location
    '[console]::WriteLine("Hello World from .PS1 file!!!")' | 
    Out-File C:\Windows\test.ps1 -Encoding ascii
    
    # Copy the same file in a denied location
    Copy-Item -Path C:\Windows\test.ps1 -Destination C:\
    
    # How Applocker would evaluate its RUN decisions
    Get-AppLockerPolicy -Effective | 
    Test-AppLockerPolicy -Path C:\test.ps1 -User Everyone
    Get-AppLockerPolicy -Effective | 
    Test-AppLockerPolicy -Path C:\Windows\test.ps1 -User Everyone
    
    # Run these 2 scripts
    C:\test.ps1
    C:\Windows\test.ps1
    
    # What appears in the log
    Get-WinEvent "Microsoft-Windows-AppLocker/MSI and Script" `
    -MaxEvents 100 | Where Message -match "TEST.PS1" | 
    Select -First 4
    

    Why is there a difference between C:\test.ps1 and C:\windows\test.ps1?

    When C:\test.ps1 is executed, no Applocker rule that would allow it to run is found.
    The contrained language mode kicks in, the file is executed. The contrained language mode does its job line by line and restricts what’s not permitted.

    When C:\Windows\test.ps1 is executed. There’s an Applocker rule that allows it.
    The full language mode is selected, the file is executed. In full language mode nothing is restricted.

    If the test.ps1 script contains both allowed and sensitive language elements, here’s what happens:

    # LangMode? 
    $ExecutionContext.SessionState.LanguageMode
    
    # Create a PowerShell script file in allowed location
    @'
    echo "Hello World from .PS1 file!"
    [console]::WriteLine("Only works in FullLanguage mode")
    echo "Reaching this line in the code?"
    '@ | 
    Out-File C:\test.ps1 -Encoding ascii
    
    # Run the script
    C:\test.ps1
    

    The code that doesn’t call sensitive language element succeeds whatever its location in the script.

    The 2nd line with the sensitive language element fails and I get a message saying that “Cannot invoke method. Method invocation is supported only on core types in this language mode.”

    Let’s imagine that this piece of code is malicious. It would have failed to achieve its purpose and deliver its payload.

      • How is the language mode evaluated?

    When PowerShell.exe is started, it first tries to execute a random script (PS1) created in the %TEMP% folder.

    Then it tries to load a random module (PSM1) created in the %TEMP% folder.

    The content of these 2 files looks like this

      • What did we learn so far?

    Constrained language mode is designed to operate at the file content level while Applocker is at the file system level.

    Constrained language mode does a good job at restricting sensitive language elements and will prevent attackers to access all the Windows API. It reduces the attack surface and makes PowerShell a less attractive tool to attackers. Constrained language mode is not your only friend. There’s also the Antimalware Scan Interface (a.k.a. AMSI) but that’s another subject.

    What did we learn from a forensics perspective?

    You cannot rely on the “Microsoft-Windows-AppLocker/MSI and Script” event log to know what run if you’ve Applocker ‘allow mode’/’whitelist mode’/’lockdown mode’ turned on that makes PowerShell run in Constrained language mode.

    What’s the solution in this case?

    You need to have PowerShell logging turned on.

      • What can be seen if PowerShell logging is enabled?

    A C:\transcripts folder was created, logging enabled in the local computer policy and the computer local policy refreshed with gpupdate.exe.

    Here’s the minimum logging settings:

    When the ScriptBlockLogging is enabled, the following events in the Microsoft-Windows-PowerShell/Operational log can be seen:


    Unfortunately, it doesn’t tell you what succeeded and failed to run. It just shows what was run. Note also there’s no indication about the language mode.

    With the (Over-the-shoulder) Transcription enabled, we can both see what succeeded and failed to run:

    • Conclusion

    I hope that the official documentation of Applocker will be modified to clarify how Applocker really works with PowerShell scripts. The documentation should also reflect the changes introduced by the constrained language mode.

    I hope that the Applocker eventlog stops saying it has blocked a file and starts saying instead that it’s operating at the file content level and delegated the security to the constrained language mode.

    I hope that the transcription log and the scriptblock logging events include an additional property telling us what language mode was used to execute the code.

    Have fun! PowerShell Rock! 😎

    Advertisements

    3 thoughts on “Applocker and PowerShell: how do they tightly work together?

    1. Pingback: Dew Drop – March 8, 2019 (#2915) | Morning Dew

    2. Superb! Just what we were looking for. Unfortunately Microsoft does not provide any such documentation which would have been useful for many.

    Leave a Reply

    Fill in your details below or click an icon to log in:

    WordPress.com Logo

    You are commenting using your WordPress.com account. Log Out /  Change )

    Google photo

    You are commenting using your Google account. Log Out /  Change )

    Twitter picture

    You are commenting using your Twitter account. Log Out /  Change )

    Facebook photo

    You are commenting using your Facebook account. Log Out /  Change )

    Connecting to %s

    This site uses Akismet to reduce spam. Learn how your comment data is processed.