Linux 守护进程|应急响应

  • A+
所属分类:安全闲碎


最近被安排做一些应急响应的工作,所以学习了一下Linux进程相关的知识,越学越多,那就记下来吧!

在Linux中:

  • 打开terminal,也就是终端程序,之后可以获得一个shell

  • 通过ssh连接到linux的ssh-server 服务器,也可以获得一个shell

通常我们都是通过以上两种方式来获得一个shell,之后运行程序的,此时我需要纠正一个概念,我们通常都说获得一个shell,本质上来说,我们获取了一个session(会话,以下session都是会话)

Linux 守护进程|应急响应

拿两种常见情况进行举例

案例1

我们输入

ping www.baidu.com

Linux 守护进程|应急响应

大家都知道,此时我们启动了一个程序 ping ,并且创建了一个进程,我们再开一个终端ssh连接这个服务器看一下

Linux 守护进程|应急响应

可以看到,我们起了一个PID为1779的进程,进程在不断向我们打印ping的结果,那么本质上来讲是什么样的呢?

我们使用 ps ajfx 来看一下

Linux 守护进程|应急响应

  1. pid,pgid,sid均为890的 sshd 守护进程生成一个SID为1494的session,同时创建了一个pid为1494的子进程“sshd: helper [priv]” ,并且创建了一个进程组,此进程就是进程组的leader,进程组的PGID等于此进程的pid 1494,这个进程就是该session的leader

  2. “sshd: helper [priv]”创建了一个PID为1518子进程 “sshd: [email protected]/2” ,其实就是开了一个虚拟终端 pts

  3. 虚拟终端pts生成了一个SID为1519的session,创建了一个pid为1519的子进程 “bash”,并且创建了一个新的进程组,新进程组的PGID等于新的子进程的PID为1519,这个子进程为进程组的leader,也是这个session的leader。

  4. bash创建了一个pid为1779的子进程 “ping www.baidu.com”,同时创建一个新的进程组,PGID为1779,并且这是一个前台进程

案例2

我们输入

ping www.baidu.com &

Linux 守护进程|应急响应

可以看到,ping百度 这个操作的“交互”已经放到后台了,但是依旧像终端输出,我们可以正常输入命令ls,pwd等,执行返回也都正常

ps ajfx

Linux 守护进程|应急响应

同样的过程就不重复了,不一样的地方在于

Linux 守护进程|应急响应

Linux 守护进程|应急响应

这里是 ps 命令的 STAT 列,具体字符含义如下

  • D 不能中断的进程(通常为IO)

  • R 正在运行中的进程

  • S 已经中断的进程,通常情况下,系统中大部分进程都是这个状态

  • T 已经停止或者暂停的进程,如果我们正在运行一个命令,比如说sleep 10,如果我们按一下ctrl -z 让他暂停,那我们用ps查看就会显示T这个状态

  • W 这个好像是说,从内核2.6xx 以后,表示为没有足够的内存页分配

  • X 已经死掉的进程(这个好像从来不会出现)

  • Z 僵尸进程,杀不掉,打不死的垃圾进程,占系统一小点资源,不过没有关系。如果太多,就有问题了。一般不会出现。

下面一些是BSD风格的参数

  • < 高优先级进程

  • N 低优先级进程

  • L 在内存中被锁了内存分页

  • s 主进程

  • l 多线程进程

  • + 代表在前台运行的进程

可以看出

  • 执行 ping www.baidu.com 的时候ping是前台运行的进程, bash是后台运行的进程

  • 执行 ping www.baidu.com & 的时候ping是后台运行的进程, bash是前台运行的进程


如果上面涉及的所有概念你都能清晰的理解,那么下面的内容你也可以看一看,毕竟来都来了...

进程组

进程的概念大家都能理解的话,进程组就很好说了,其实就是一堆进程捆一起了,之后形成一个组就叫进程组了

这么做肯定是有意义的,不然Linux也不会这么搞,主要还是为了方便管理。

公司为了方便管理,给人分组,方便分配工作;社会为了方便管理,给人区分成年人,未成年人,老人;我们又因为爱好,信念等被分成了各种各样的小组...

系统把同一个job(作业)的进程分成一个组,既然有组织肯定得有组长,组的ID(PGID)就采用组长的PID

这里有一个问题,如果组长进程死亡了,小组还存在吗?如果存在组长归谁?

如果组长进程死亡了,小组只要还剩下进程就会存在,此时组长不会变,PGID也不会变;就像纪念一样...

实验一下:

#include <unistd.h>
#include <stdio.h>

int main()
{
setbuf(stdout, NULL);
pid_t pid;
pid = fork();
if(pid == 0){
printf("child pid: %dn", getpid());
while(1){
sleep(1);
printf("childn");
}
} else {
printf("father pid %dn", getpid());
while(1){
sleep(1);
printf("fathern");
}
}

}

Linux 守护进程|应急响应

Linux 守护进程|应急响应

从ps的结果可以看到,我们的程序创建了两个进程,两个进程属于同一个进程组,PGID为29938

现在我们kill 掉进程组leader 29938

kill -9 29938

Linux 守护进程|应急响应

Linux 守护进程|应急响应

当我们kill掉进程leader之后,立马father就不打印了,但是child依旧在打印,这说明父进程被杀死,子进程还活着,接下来看看子进程活得怎么样

Linux 守护进程|应急响应

好家伙,父进程被杀死后,子进程直接把PPID设置为1,但是进程组PGID依旧没变,还是29938 ,session的id SID也没有发生变化,还是29756

此时这个子进程被称为孤儿进程

这里我们就需要注意了,一个木马或者后门如果主进程还存在子进程,仅仅 kill -9 pid 杀死主进程可能是没用的,因为不会杀死子进程

问题来了,如果我想把这些木马病毒进程都干掉,怎么操作?

我见过各种骚操作,有的是写脚本,有的是手动挨个杀,用killall、pkill等等,这种回复一看就是没遇到那种进程pid,进程名称一直变化的

其实非常简单,我们只需要把这个进程组给杀死就好了

kill -9 -PGID

没有看错,其实就是在 PGID前面加个减号

实验开始:

Linux 守护进程|应急响应

可以看到,父子进程都起来了,pid分别为29949和29950

这个时候我们杀掉这个进程组

kill -9 -29949

Linux 守护进程|应急响应

Linux 守护进程|应急响应

Linux 守护进程|应急响应

可以看到,这个进程组已经没有了,渣都不剩!

这里一定要注意,你杀的是一个进程组,一定要注意,进程组里是否有正常业务进程,别杀错了

Session

其实文章开头我们已经简单提到过了,我们一般讨论的都是shell session,我们打开一个新的终端就会创建一个session,每个session都是由一个或者多个进程组组成的,每个进程组称为 job,这里job不是任务,而叫作业

从描述中可以看出,session管理的范围要比进程组大,打开一个终端,你执行100条命令,只要没有新的session生成(调用 setsid()函数可以生成新的session ),那么这些命令可以通过session进行统一管理,当然最常见的管理方式还是全部杀死,但是这个杀伤力太大了,所以一般不使用,主要还是了解session的概念,从web安全过来对于session这种机制应该很容易理解。

session中的第一个进程(一般是bash)的PID就是session的SID

现在大招来了,如何干掉整个session呢?

pkill -s SID

实验开始

Linux 守护进程|应急响应

可以看到,fk的SID为29756

pkill -e -s 29756

Linux 守护进程|应急响应

可以看到,杀掉了这个SID下的三个进程,分别为 29756, 29957, 29958

-e 参数是现实杀掉了谁,多人性化

Linux 守护进程|应急响应

可以看到,杀掉了bash进程后,ssh链接就断开了

守护进程(daemon)

守护进程这个词经常听到,名字还挺温暖,遗憾的是总是在处理linux挖矿病毒的案例中听到,简直破坏美感

守护进程的一个特点就是进程不受任何终端控制

不受任何终端控制这个定义似乎有些模糊,所以我试图去找到一些限定条件,大部分人是这样说的:

  • 随系统启动而启动

  • 父进程是init,也就是ppid为1

  • 在后台运行

  • 进程名字通常以字母 d 结束

  • ps显示中终端名设置为问号(?),终端前台进程组ID设置为-1

  • 工作目录为 (根)

这其中很明显不完全准确,但是也都是基于实际情况分析出来的,所以我一直在纠结后台进程、nohup起的后台进程和守护进程是什么关系,直到遇到了这篇文章,我觉得才是说的比较透彻的

https://www.cnblogs.com/lvyahui/p/7389554.html

我直接摘过来:

  • 没有控制终端,终端名设置为?号:也就意味着没有 stdin 0 、stdout 1、stderr 2

  • 父进程不是用户创建的进程,init进程或者systemd(pid=1)以及用户人为启动的用户层进程一般以pid=1的进程为父进程,而以kthreadd内核进程创建的守护进程以kthreadd为父进程

  • 守护进程一般是会话首进程、组长进程。

  • 工作目录为/(根),主要是为了防止占用磁盘导致无法卸载磁盘

守护进程在后台默默提供着服务,但是不接受任何终端的管控,没有标准输入、标准输出、标准错误,比较典型的有mysqld, sshd等,当然我们也是可以创建一个守护进程的,步骤如下:

直接摘抄吧:

  1. 执行一个fork(),之后父进程退出,子进程继续执行。(结果就是daemon成为了init进程的子进程。)之所以要做这一步是因为下面两个原因:

    • 假设daemon是从命令行启动的,父进程的终止会被shell发现,shell在发现之后会显示出另一个shell提示符并让子进程继续在后台运行。

    • 子进程被确保不会称为一个进程组组长进程,因为它从其父进程那里继承了进程组ID并且拥有了自己的唯一的进程ID,而这个进程ID与继承而来的进程组ID是不同的,这样才能够成功地执行下面一个步骤。

  2. 子进程调用setsid()开启一个新回话并释放它与控制终端之间的所有关联关系。结果就是使子进程: (a)成为新会话的首进程,(b)成为一个新进程组的组长进程,(c)没有控制终端。

  3. 如果daemon从来没有打开过终端设备,那么就无需担心daemon会重新请求一个控制终端了。如果daemon后面可能会打开一个终端设备,那么必须要采取措施来确保这个设备不会成为控制终端。这可以通过下面两种方式实现:

    • 在所有可能应用到一个终端设备上的open()调用中指定O_NOCTTY标记。

    • 或者更简单地说,在setsid()调用之后执行第二个fork(),然后再次让父进程退出并让孙子进程继续执行。这样就确保了子进程不会为会话组长,因此根据System V中获取终端的规则,进程永远不会重新请求一个控制终端。(多一个fork()调用不会带来任何坏处。

  4. 清除进程的umask以确保当daemon创建文件和目录时拥有所需的权限。

  5. 修改进程的当前工作目录,通常会改为根目录(/)。这样做是有必要的,因为daemon通常会一直运行直至系统关闭为止。如果daemon的当前工作目录为不包含/的文件系统,那么就无法卸载该文件系统。或者daemon可以将工作目录改为完成任务时所在的目录或在配置文件中定义一个目录,只要包含这个目录的文件系统永远不会被卸载即可。

  6. 关闭daemon从其父进程继承而来的所有打开着的文件描述符。(daemon可能需要保持继承而来的文件描述的打开状态,因此这一步是可选的或者可变更的。)之所以这样做的原因有很多。由于daemon失去了控制终端并且是在后台运行的,因此让daemon保持文件描述符0(标准输入)、1(标准输出)和2(标准错误)的打开状态毫无意义,因为它们指向的就是控制终端。此外,无法卸载长时间运行的daemon打开的文件所在的文件系统。因此,通常的做法是关闭所有无用的打开着的文件描述符,因为文件描述符是一种有限的资源。

  7. 在关闭了文件描述符0、1和2之后,daemon通常会打开/dev/null并使用dup2()(或类似的函数)使所有这些描述符指向这个设备。之所以要这样做是因为下面两个原因:

    • 它确保了当daemon调用了在这些描述符上执行I/O的库函数时不会出乎意料地失败。

    • 它防止了daemon后面使用描述符1或2打开一个文件的情况,因为库函数会将这些描述符当做标准输出和标准错误来写入数据(进而破坏了原有的数据)。

说了这么多,还是那一个实际的守护进程出来看一下吧,以sshd为例

Linux 守护进程|应急响应

因为守护进程PPID为1,而且是在单独的进程组、单独的session中,所以PID=PGID=SID,同时终端处值为 ? , 终端前台进程组ID设置为-1

杀死守护进程没啥特别的,该杀杀,当然前提是权限要够


看到这里已经可以了,基本上知识点都接触到了,下面是我在关于进程相关知识学习过程中思考的一些问题,不解决不舒服那种,无聊的可以看一看

dies und das

1. ping www.baidu.com & 这种后台进程是不是守护进程

不是

Linux 守护进程|应急响应

Linux 守护进程|应急响应

存在标准输出和标准错误

2. nohup ping www.baidu.com &

不是

Linux 守护进程|应急响应

Linux 守护进程|应急响应

还是存在标准输出,只不过是重定向到 nohup.out中了

3. ping www.baidu.com > /dev/null 2>&1 & 更像是守护进程了吗

更像了,但还不是

Linux 守护进程|应急响应

这种形式确实是不在存在标准输出,标准输出,标准错误,但是PPID还不是1

4. 不就是PPID=1吗?上代码

#include <unistd.h>
#include <stdio.h>

int main()
{
setbuf(stdout, NULL);
pid_t pid;
pid = fork();
if(pid == 0){
system("ping www.baidu.com > /dev/null 2>&1 &");
} else {
exit(0);
}
}

Linux 守护进程|应急响应

Linux 守护进程|应急响应

  • 无标准输入、无标准输出、无标准错误

  • ppid=1

现在更像是守护进程了,但是PID,PGID,SID还是不相等,终端处值不为 ? , 终端前台进程组ID也不是-1,目录也不是根目录,换句话说还是受到终端的控制。

具体创建一个守护进程的代码网上有的是,自己搜索吧,既有直接使用daemon()函数生成的,也有一步一步按照上面描述去生成的,推荐先看看后者。

5. 我们ssh断开链接后session还在吗?

我使用两个终端连接同一个服务器的ssh

Linux 守护进程|应急响应

可以看到,现在有两个SID,我们使用 1682 这个session来进行执行ping www.baidu.com之后ctrl+c 中断,exit退出连接

Linux 守护进程|应急响应

我们使用1731的shell来查看

Linux 守护进程|应急响应

SID为1682的session不存在了,ping 的命令也被我们中断了

现在我们还是使用两个终端连接ssh

Linux 守护进程|应急响应

我们使用 1788的shell来执行

ping www.baidu.com & 之后exit退出ssh连接

Linux 守护进程|应急响应

Linux 守护进程|应急响应

从这里可以看到,虽然我们把ssh连接退出了,但是后台进行依旧在这个session上执行,还属于这个会话,所以如果session存在还在执行的后台进程,即使关闭终端或者断开ssh等远程连接,session还是会存在的

6. nohup 命令意义难道仅仅就是将标准输出,标准错误重定向到 nohup.out 吗?

如果仅仅是输出重定向,我们可以直接使用 > ,为什么会有nohup命令呢?没有点啥重要作用也对不起这个名字呀!

其实呢,产生这个疑问的主要就是因为问题5我们仅仅从表面现象就得出了结论,而没有进行本质上的剖析,所以如果只看到问题5的哥们儿可能要被误导了...

当一个终端关闭或者ssh等远程连接退出的时候,系统会向session管理的所有进程发送一个SIGHUP信号,这个信号就是挂断的意思,效果就是进程中断,理论上问题5中 ping www.baidu.com 这个后台进程也应该能够收到,但是,在session要断开这种情况是否给属于session的后台进程发送SIGHUP信号是受系统一个配置参数控制的——huponexit一般情况下,这个参数的缺省是off,在这种配置下,关闭终端后台进程不会收到SIGHUP信号。

shopt | grep huponexit

Linux 守护进程|应急响应

可以看到,在当前系统中,该参数为off,所以才会出现终端关闭或者ssh等远程连接断开的时候,后台进程能够继续以这个session运行

此时再说 nohup 应该就很清晰了,nohup其实就是忽略SIGHUP信号,这样保证我们的程序在后台平稳执行

7. tmux 后台执行的效果更好,tmux的底层原理是什么呢?

还是使用两个终端来进行

Linux 守护进程|应急响应

Linux 守护进程|应急响应

ctrl b+d 
tmux ls

Linux 守护进程|应急响应

我们使用另一个终端观察一下:

Linux 守护进程|应急响应

可以看到,其实tmux创建了一个守护进程,进程PID=1348,之后通过守护进程创建 bash,之后通过bash执行ping,创建ping www.baidu.com

为了更加严谨证实这个观点,我们再创建一个tmux任务

Linux 守护进程|应急响应

Linux 守护进程|应急响应

现在是ping百度和新浪同时跑着,再观察一下

Linux 守护进程|应急响应

中间STAT为Zs的进程是因为我忘了截图,就退出了重新来导致的,不用关注

可以看到的是,对于每一个任务,tmux都会创建一个新的session、进程组、进程,这样实现多个进程之间互不影响

至此,关于Linux的进程相关知识应该将明白了,如果想从更加底层去分析,就去学习学习C和汇编吧!


参考文章

https://www.cnblogs.com/lvyahui/p/7389554.html

https://wudaijun.com/2016/08/linux-job-control/

https://zhuanlan.zhihu.com/p/80439267

http://www.ruanyifeng.com/blog/2016/02/linux-daemon.html

https://blog.csdn.net/weicao1990/article/details/78639549

http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html

https://segmentfault.com/a/1190000022770900

https://segmentfault.com/q/1010000000310278

https://blog.csdn.net/hust_sheng/article/details/50766752

https://segmentfault.com/a/1190000022097240

https://ytlee.cn/2020/05/the-difference-between-daemon-and-background-process/

https://www.cnblogs.com/lvyahui/p/7389554.html

https://www.jianshu.com/p/eed75164334d

https://www.lujun9972.win/blog/2019/08/26/%E5%A6%82%E4%BD%95kill%E6%95%B4%E4%B8%80%E4%B8%AA%E8%BF%9B%E7%A8%8B%E7%BB%84%E6%88%96%E4%BC%9A%E8%AF%9D/index.html



有态度,不苟同


Linux 守护进程|应急响应

本文始发于微信公众号(漫流砂):Linux 守护进程|应急响应

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: