共计 3882 个字符,预计需要花费 10 分钟才能阅读完成。
之前文章提到服务器上一个过程启动后不到三分钟就挂掉,到底是什么起因挂掉了,这个问题能够写篇文章了。过程死了,无非就两种可能:他杀,自杀。自杀又包含第三方杀害和零碎判死刑。
先来看他杀。
1. 他杀
咱们以 Java 为例,Java 程序在 main 办法运行完就会退出,这种属于他杀。或者像上面这样
System.exit();
这样也属于他杀。
比方上面的代码
public class SelfKill {public static void main(String[] args) throws InterruptedException {while(true){Thread.sleep(5000);
System.exit(4);
}
}
}
运行如下:
[koudai@koudai-pc classes]$ java baicai.other.SelfKill
[koudai@koudai-pc classes]$ echo $?4
咱们能用 shutdownHook 来在临死前记日志。
public class SelfKill {public static void main(String[] args) throws InterruptedException {Runtime.getRuntime().addShutdownHook(new Thread(() -> {System.out.println("敞开利用,开释资源");
}));
while(true){Thread.sleep(2000);
System.exit(4);
}
}
}
执行后果
[koudai@koudai-pc classes]$ java baicai.other.SelfKill
敞开利用,开释资源
能够看到,无论是被动他杀,还是被他杀,shutdownHook 都能放弃现场。那如果是不反对 shutdownHook 的语言呢,或者程序里没有做 Hook,那咱们是不晓得的。另外,不只是他杀 shutdownHook 能触发,它杀 shutdownHook 也会被触发。
<u> 总结下:对于被动他杀,如果咱们有应用了 shutdownHook,是能记录下他杀工夫和现场的。如果是被他杀(歹意后门调用 System.exit),并且咱们没有做 Hook,那对他杀现场是不知情的。代码里肯定要有欠缺的 shutdownHook。</u>
被动他杀是咱们的被动行为,那怎么防止被动他杀呢?方才说了,被动他杀个别是歹意调用 System.exit 导致,一种是开发者退出的后门,一种是脚本小子退出的后门。System.exit 导致 JVM 间接退出,且没有日志能够查问到是哪个类里的代码导致,因而通常状况下须要屏蔽。System.exit 是能够被禁止的,办法就是自定义 SecurityManager:
public class SelfSecurityManager extends SecurityManager{
@Override
public void checkPermission(Permission perm) {if (perm instanceof java.lang.RuntimePermission) {String name = perm.getName();
if (name != null && name.contains("setSecurityManager")) {throw new SecurityException("System.setSecurityManager denied!");
}
}
}
@Override
public void checkPermission(Permission perm, Object context) {//}
@Override
public void checkExit(int status) {super.checkExit(status);
throw new ExitException(status);// 自定义异样
}
}
System.setSecurityManager(new SelfSecurityManager());//main 办法中
须要留神的是,你能够自定义 SecurityManager,脚本小子也能够自定义。因而光在 Java 类中自定义 SecurityManager 是不够的,你须要在 JVM 启动参数上定义更精密的 policy 文件,以及爱护本人的 SecurityManager 不被重置。尤其须要留神不要被反射绕过。
2. 自杀
后面说了,不只是他杀 shutdownHook 能触发,以下场景都会触发 ShutdownHook :
- 代码执行完结,JVM 失常退出
- 利用代码中调用
System#exit
办法 - 利用中产生 OOM 谬误,导致 JVM 敞开
- 终端中应用
Ctrl+C
(非后盾运行) - 被动敞开利用
咱们模仿 Ctrl+ C 试试。如下所示
[koudai@koudai-pc classes]$ java baicai.other.SelfKill^C 敞开利用,开释资源[koudai@koudai-pc classes]$
能够看到 ctrl+ C 是能被捕捉到的。那么 kill 指令能被捕捉吗
[koudai@koudai-pc classes]$ kill 14675[koudai@koudai-pc classes]$ java baicai.other.SelfKill 敞开利用,开释资源
能够看到一般的 kill 是能被捕捉的,那么 kill - 9 呢
[koudai@koudai-pc classes]$ ps -ef|grep Kill
koudai 14948 8231 0 00:57 pts/1 00:00:00 java baicai.other.SelfKill
koudai 15235 14640 0 00:58 pts/2 00:00:00 grep --colour=auto Kill
[koudai@koudai-pc classes]$ kill -9 14948
[koudai@koudai-pc classes]$ java baicai.other.SelfKill
已杀死
能够看到 kill - 9 是无奈被过程本身所捕捉的。
到这里就完结了吗?
真正的问题来了,就算我有 shutdownHook 能记下临终遗嘱,然而最要害的是我无奈晓得是谁杀死了我。尤其是剖析某些木马的场景下
如果是零碎杀死我的,那个别就是 OOM,这种状况也还好。
Linux 内核有个机制叫 OOM killer(Out Of Memory killer),该机制会监控那些占用内存过大,尤其是霎时占用内存很快的过程,而后避免内存耗尽而主动把该过程杀掉。内核检测到零碎内存不足、筛选并杀掉某个过程的过程能够参考内核源代码 linux/mm/oom_kill.c,当零碎内存不足的时候,out_of_memory()被触发,而后调用 select_bad_process()抉择一个”bad”过程杀掉。
这种 OOM,是有日志记录的,能够用上面的办法查看
grep "Out of memory" /var/log/messagessudo
dmesg|grep "Out of memory"
零碎杀的,自认倒霉。
最麻烦的是被第三方杀的,比方木马,各种监控脚本,各种 sh 脚本,我咋样才晓得是哪个过程杀的呢?这就须要用到 systemtap 了。
3.systemtap 应用
systemtap 是一个 用于简化 linux 零碎运行状态信息收集的开源工具。它立足于性能诊断和 bug 调试,对内核及用户态程序提供了动静追踪性能,用户能够自定探测事件来跟踪程序的运行状况,如函数的调用门路、CPU 占用和磁盘 IO 等一系列能够探测的状况。有了 systemtap,能够在程序不批改代码,甚至不必重启就能剖析出程序的运行状况。
systemtap 的核心思想是定义一个事件(event),以及给出解决该事件的句柄(Handler)。当一个特定的事件产生时,内核运行该解决句柄,就像疾速调用一个子函数一样,解决完之后复原到内核原始状态。
先来装置它
yum install systemtap systemtap-runtime
stap-prep
stap -e 'probe begin{printf("Hello, World"); exit();}' #测试验证
因为咱们并不需要高级性能,所以暂不装置内核符号文件。接下来,咱们写一个 stap 脚本
vim sigmon.stp
# 内容如下
probe begin
{
printf("%-8s %-16s %-5s %-16s %6s %-16s\n",
"SPID", "SNAME", "RPID", "RNAME", "SIGNUM", "SIGNAME")
}
probe signal.send
{if (sig_name == @1 && sig_pid == target())
printf("%-8d %-16s %-5d %-16s %-6d %-16s\n",
pid(), execname(), sig_pid, pid_name, sig, sig_name)
}
当初咱们须要监控某个过程,就这么调用
(base) [root@VM-0-7-centos ~]# stap -x 28262 sigmon.stp SIGKILLSPID SNAME RPID RNAME SIGNUM SIGNAME 2362819 bash 28262 rsyslogd 9 SIGKILL
这样咱们就晓得 28262 这个过程是被 2362819 这个过程,也就是 bash 所杀死的。
当然,如果不在事先监控,预先咱们是拿不到日志信息的。
如果咱们即不做 ShutdownHook,也不应用 systemtap 进行监控,仅仅靠操作系统自带日志,那咱们是无奈保留死亡现场,也很难晓得谁是过程 killed 背地的凶手了