You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
pwndbg/pwndbg/elf.py

288 lines
7.6 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This file declares types and methods useful for enumerating
all of the address spaces and permissions of an ELF file in memory.
This is necessary for when access to /proc is restricted, or when
working on a BSD system which simply does not have /proc.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import ctypes
import os
import re
import subprocess
import sys
import tempfile
import gdb
import pwndbg.auxv
import pwndbg.elftypes
import pwndbg.events
import pwndbg.info
import pwndbg.memoize
import pwndbg.memory
import pwndbg.proc
import pwndbg.stack
# ELF constants
PF_X, PF_W, PF_R = 1,2,4
ET_EXEC, ET_DYN = 2,3
module = sys.modules[__name__]
@pwndbg.events.start
@pwndbg.events.new_objfile
def update():
if pwndbg.arch.ptrsize == 4:
Ehdr = pwndbg.elftypes.Elf32_Ehdr
Phdr = pwndbg.elftypes.Elf32_Phdr
else:
Ehdr = pwndbg.elftypes.Elf64_Ehdr
Phdr = pwndbg.elftypes.Elf64_Phdr
module.__dict__.update(locals())
update()
def read(typ, address, blob=None):
size = ctypes.sizeof(typ)
if not blob:
data = pwndbg.memory.read(address, size)
else:
data = blob[address:address+size]
obj = typ.from_buffer_copy(data)
obj.address = address
obj.type = typ
return obj
@pwndbg.proc.OnlyWhenRunning
@pwndbg.memoize.reset_on_start
def exe():
"""
Return a loaded ELF header object pointing to the Ehdr of the
main executable.
"""
e = entry()
if e:
return load(e)
@pwndbg.proc.OnlyWhenRunning
@pwndbg.memoize.reset_on_start
def entry():
"""
Return the address of the entry point for the main executable.
"""
entry = pwndbg.auxv.get().AT_ENTRY
if entry:
return entry
# Looking for this line:
# Entry point: 0x400090
for line in pwndbg.info.files().splitlines():
if "Entry point" in line:
entry_point = int(line.split()[-1], 16)
# PIE entry points are sometimes reported as an
# offset from the module base.
if entry_point < 0x10000:
break
return entry_point
# Try common names
for name in ['_start', 'start', '__start', 'main']:
try:
return pwndbg.symbol.address(name)
except gdb.error:
pass
# Can't find it, give up.
return 0
def load(pointer):
return get_ehdr(pointer)[1]
ehdr_type_loaded = 0
@pwndbg.memoize.reset_on_start
def reset_ehdr_type_loaded():
global ehdr_type_loaded
ehdr_type_loaded = 0
def get_ehdr(pointer):
"""Returns an ehdr object for the ELF pointer points into.
"""
# Align down to a page boundary, and scan until we find
# the ELF header.
base = pwndbg.memory.page_align(pointer)
try:
data = pwndbg.memory.read(base, 4)
# Do not search more than 4MB of memory
for i in range(1024):
if data == b'\x7FELF':
break
base -= pwndbg.memory.PAGE_SIZE
data = pwndbg.memory.read(base, 4)
else:
print("ERROR: Could not find ELF base!")
return None, None
except gdb.MemoryError:
return None, None
# Determine whether it's 32- or 64-bit
ei_class = pwndbg.memory.byte(base+4)
# Find out where the section headers start
Elfhdr = read(Ehdr, base)
return ei_class, Elfhdr
def get_phdrs(pointer):
"""
Returns a tuple containing (phnum, phentsize, gdb.Value),
where the gdb.Value object is an ELF Program Header with
the architecture-appropriate structure type.
"""
ei_class, Elfhdr = get_ehdr(pointer)
if Elfhdr is None:
return (0, 0, None)
phnum = Elfhdr.e_phnum
phoff = Elfhdr.e_phoff
phentsize = Elfhdr.e_phentsize
x = (phnum, phentsize, read(Phdr, Elfhdr.address + phoff))
return x
def iter_phdrs(ehdr):
if not ehdr:
raise StopIteration
phnum, phentsize, phdr = get_phdrs(ehdr.address)
if not phdr:
raise StopIteration
first_phdr = phdr.address
PhdrType = phdr.type
for i in range(0, phnum):
p_phdr = int(first_phdr + (i*phentsize))
p_phdr = read(PhdrType, p_phdr)
yield p_phdr
def map(pointer, objfile=''):
"""
Given a pointer into an ELF module, return a list of all loaded
sections in the ELF.
Returns:
A sorted list of pwndbg.memory.Page objects
Example:
>>> pwndbg.elf.load(pwndbg.regs.pc)
[Page('400000-4ef000 r-xp 0'),
Page('6ef000-6f0000 r--p ef000'),
Page('6f0000-6ff000 rw-p f0000')]
>>> pwndbg.elf.load(0x7ffff77a2000)
[Page('7ffff75e7000-7ffff77a2000 r-xp 0x1bb000 0'),
Page('7ffff77a2000-7ffff79a2000 ---p 0x200000 1bb000'),
Page('7ffff79a2000-7ffff79a6000 r--p 0x4000 1bb000'),
Page('7ffff79a6000-7ffff79ad000 rw-p 0x7000 1bf000')]
"""
ei_class, ehdr = get_ehdr(pointer)
return map_inner(ei_class, ehdr, objfile)
def map_inner(ei_class, ehdr, objfile):
if not ehdr:
return []
base = int(ehdr.address)
# For each Program Header which would load data into our
# address space, create a representation of each individual
# page and its permissions.
#
# Entries are processed in-order so that later entries
# which change page permissions (e.g. PT_GNU_RELRO) will
# override their small subset of address space.
pages = []
for phdr in iter_phdrs(ehdr):
memsz = int(phdr.p_memsz)
if not memsz:
continue
vaddr = int(phdr.p_vaddr)
offset = int(phdr.p_offset)
flags = int(phdr.p_flags)
ptype = int(phdr.p_type)
memsz += pwndbg.memory.page_offset(vaddr)
memsz = pwndbg.memory.page_size_align(memsz)
vaddr = pwndbg.memory.page_align(vaddr)
offset = pwndbg.memory.page_align(offset)
# For each page described by this program header
for page_addr in range(vaddr, vaddr+memsz, pwndbg.memory.PAGE_SIZE):
if page_addr in pages:
page = pages[pages.index(page_addr)]
# Don't ever remove the execute flag.
# Sometimes we'll load a read-only area into .text
# and the loader doesn't actually *remove* the executable flag.
if page.flags & PF_X: flags |= PF_X
page.flags = flags
else:
page = pwndbg.memory.Page(page_addr, pwndbg.memory.PAGE_SIZE, flags, offset + (page_addr-vaddr))
pages.append(page)
# Adjust against the base address that we discovered
# for binaries that are relocatable / type DYN.
if ET_DYN == int(ehdr.e_type):
for page in pages:
page.vaddr += base
# Merge contiguous sections of memory together
pages.sort()
prev = pages[0]
for page in list(pages[1:]):
if (prev.flags & PF_W) == (page.flags & PF_W) and prev.vaddr+prev.memsz == page.vaddr:
prev.memsz += page.memsz
pages.remove(page)
else:
prev = page
# Fill in any gaps with no-access pages.
# This is what the linker does, and what all the '---p' pages are.
gaps = []
for i in range(len(pages)-1):
a, b = pages[i:i+2]
a_end = (a.vaddr + a.memsz)
b_begin = b.vaddr
if a_end != b_begin:
gaps.append(pwndbg.memory.Page(a_end, b_begin-a_end, 0, b.offset))
pages.extend(gaps)
for page in pages:
page.objfile = objfile
return tuple(sorted(pages))