Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow new file format #849

Merged
merged 5 commits into from Jun 26, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
232 changes: 145 additions & 87 deletions gef.py
Expand Up @@ -82,7 +82,7 @@
from io import StringIO, TextIOWrapper
from types import ModuleType
from typing import (Any, ByteString, Callable, Dict, Generator, Iterable,
Iterator, List, NoReturn, Optional, Sequence, Tuple, Type,
Iterator, List, NoReturn, Optional, Sequence, Set, Tuple, Type,
Union)
from urllib.request import urlopen

Expand Down Expand Up @@ -158,9 +158,10 @@ def update_gef(argv: List[str]) -> int:
GEF_EXTRAS_DEFAULT_BRANCH = "main"

gef : "Gef"
__registered_commands__ : List[Type["GenericCommand"]] = []
__registered_functions__ : List[Type["GenericFunction"]] = []
__registered_architectures__ : Dict[Union["Elf.Abi", str], Type["Architecture"]] = {}
__registered_commands__ : List[Type["GenericCommand"]] = []
__registered_functions__ : List[Type["GenericFunction"]] = []
__registered_architectures__ : Dict[Union["Elf.Abi", str], Type["Architecture"]] = {}
__registered_file_formats__ : Set[ Type["FileFormat"] ] = set()


def reset_all_caches() -> None:
Expand Down Expand Up @@ -289,44 +290,52 @@ def wrapper(*args: Any, **kwargs: Any) -> Any:
# Helpers
#

def p8(x: int, s: bool = False) -> bytes:
def p8(x: int, s: bool = False, e: Optional["Endianness"] = None) -> bytes:
"""Pack one byte respecting the current architecture endianness."""
return struct.pack(f"{gef.arch.endianness}B", x) if not s else struct.pack(f"{gef.arch.endianness}b", x)
endian = e or gef.arch.endianness
return struct.pack(f"{endian}B", x) if not s else struct.pack(f"{endian:s}b", x)


def p16(x: int, s: bool = False) -> bytes:
def p16(x: int, s: bool = False, e: Optional["Endianness"] = None) -> bytes:
"""Pack one word respecting the current architecture endianness."""
return struct.pack(f"{gef.arch.endianness}H", x) if not s else struct.pack(f"{gef.arch.endianness}h", x)
endian = e or gef.arch.endianness
return struct.pack(f"{endian}H", x) if not s else struct.pack(f"{endian:s}h", x)


def p32(x: int, s: bool = False) -> bytes:
def p32(x: int, s: bool = False, e: Optional["Endianness"] = None) -> bytes:
"""Pack one dword respecting the current architecture endianness."""
return struct.pack(f"{gef.arch.endianness}I", x) if not s else struct.pack(f"{gef.arch.endianness}i", x)
endian = e or gef.arch.endianness
return struct.pack(f"{endian}I", x) if not s else struct.pack(f"{endian:s}i", x)


def p64(x: int, s: bool = False) -> bytes:
def p64(x: int, s: bool = False, e: Optional["Endianness"] = None) -> bytes:
"""Pack one qword respecting the current architecture endianness."""
return struct.pack(f"{gef.arch.endianness}Q", x) if not s else struct.pack(f"{gef.arch.endianness}q", x)
endian = e or gef.arch.endianness
return struct.pack(f"{endian}Q", x) if not s else struct.pack(f"{endian:s}q", x)


def u8(x: bytes, s: bool = False) -> int:
def u8(x: bytes, s: bool = False, e: Optional["Endianness"] = None) -> int:
"""Unpack one byte respecting the current architecture endianness."""
return struct.unpack(f"{gef.arch.endianness}B", x)[0] if not s else struct.unpack(f"{gef.arch.endianness}b", x)[0]
endian = e or gef.arch.endianness
return struct.unpack(f"{endian}B", x)[0] if not s else struct.unpack(f"{endian:s}b", x)[0]


def u16(x: bytes, s: bool = False) -> int:
def u16(x: bytes, s: bool = False, e: Optional["Endianness"] = None) -> int:
"""Unpack one word respecting the current architecture endianness."""
return struct.unpack(f"{gef.arch.endianness}H", x)[0] if not s else struct.unpack(f"{gef.arch.endianness}h", x)[0]
endian = e or gef.arch.endianness
return struct.unpack(f"{endian}H", x)[0] if not s else struct.unpack(f"{endian:s}h", x)[0]


def u32(x: bytes, s: bool = False) -> int:
def u32(x: bytes, s: bool = False, e: Optional["Endianness"] = None) -> int:
"""Unpack one dword respecting the current architecture endianness."""
return struct.unpack(f"{gef.arch.endianness}I", x)[0] if not s else struct.unpack(f"{gef.arch.endianness}i", x)[0]
endian = e or gef.arch.endianness
return struct.unpack(f"{endian}I", x)[0] if not s else struct.unpack(f"{endian:s}i", x)[0]


def u64(x: bytes, s: bool = False) -> int:
def u64(x: bytes, s: bool = False, e: Optional["Endianness"] = None) -> int:
"""Unpack one qword respecting the current architecture endianness."""
return struct.unpack(f"{gef.arch.endianness}Q", x)[0] if not s else struct.unpack(f"{gef.arch.endianness}q", x)[0]
endian = e or gef.arch.endianness
return struct.unpack(f"{endian}Q", x)[0] if not s else struct.unpack(f"{endian:s}q", x)[0]


def is_ascii_string(address: int) -> bool:
Expand Down Expand Up @@ -679,7 +688,39 @@ def __int__(self) -> int:
return self.value


class Elf:
class FileFormatSection:
misc: Any


class FileFormat:
name: str
path: pathlib.Path
entry_point: int
checksec: Dict[str, bool]
sections: List[FileFormatSection]

def __init__(self, path: Union[str, pathlib.Path]) -> None:
raise NotImplemented

def __init_subclass__(cls: Type["FileFormat"], **kwargs):
global __registered_file_formats__
super().__init_subclass__(**kwargs)
required_attributes = ("name", "entry_point", "is_valid", "checksec",)
for attr in required_attributes:
if not hasattr(cls, attr):
raise NotImplementedError(f"File format '{cls.__name__}' is invalid: missing attribute '{attr}'")
__registered_file_formats__.add(cls)
return

@classmethod
def is_valid(cls, path: pathlib.Path) -> bool:
raise NotImplemented

def __str__(self) -> str:
return f"{self.name}('{self.path.absolute()}', entry @ {self.entry_point:#x})"


class Elf(FileFormat):
"""Basic ELF parsing.
Ref:
- http://www.skyfree.org/linux/references/ELF_Format.pdf
Expand Down Expand Up @@ -747,18 +788,25 @@ class OsAbi(enum.Enum):
path: pathlib.Path
phdrs : List["Phdr"]
shdrs : List["Shdr"]
name: str = "ELF"

def __init__(self, path: Union[str, pathlib.Path] = "", minimalist: bool = False) -> None:
"""Instantiate an ELF object. The default behavior is to create the object by parsing the ELF file.
But in some cases (QEMU-stub), we may just want a simple minimal object with default values."""
if minimalist:
return
__checksec : Dict[str, bool]

self.path = pathlib.Path(path).expanduser() if isinstance(path, str) else path
def __init__(self, path: Union[str, pathlib.Path]) -> None:
"""Instantiate an ELF object. A valid ELF must be provided, or an exception will be thrown."""

if isinstance(path, str):
self.path = pathlib.Path(path).expanduser()
elif isinstance(path, pathlib.Path):
self.path = path
else:
raise TypeError

if not os.access(self.path, os.R_OK):
if not self.path.exists():
raise FileNotFoundError(f"'{self.path}' not found/readable, most gef features will not work")

self.__checksec = {}

with self.path.open("rb") as self.fd:
# off 0x0
self.e_magic, e_class, e_endianness, self.e_eiversion = self.read_and_unpack(">IBBB")
Expand Down Expand Up @@ -823,6 +871,45 @@ def __repr__(self) -> str:
def entry_point(self) -> int:
return self.e_entry

@classmethod
def is_valid(cls, path: pathlib.Path) -> bool:
return u32(path.open("rb").read(4), e = Endianness.BIG_ENDIAN) == Elf.ELF_MAGIC

@property
def checksec(self) -> Dict[str, bool]:
"""Check the security property of the ELF binary. The following properties are:
- Canary
- NX
- PIE
- Fortify
- Partial/Full RelRO.
Return a dict() with the different keys mentioned above, and the boolean
associated whether the protection was found."""
if not self.__checksec:
def __check_security_property(opt: str, filename: str, pattern: str) -> bool:
cmd = [readelf,]
cmd += opt.split()
cmd += [filename,]
lines = gef_execute_external(cmd, as_list=True)
for line in lines:
if re.search(pattern, line):
return True
return False

abspath = str(self.path.absolute())
readelf = gef.session.constants["readelf"]
self.__checksec["Canary"] = __check_security_property("-rs", abspath, r"__stack_chk_fail") is True
has_gnu_stack = __check_security_property("-W -l", abspath, r"GNU_STACK") is True
if has_gnu_stack:
self.__checksec["NX"] = __check_security_property("-W -l", abspath, r"GNU_STACK.*RWE") is False
else:
self.__checksec["NX"] = False
self.__checksec["PIE"] = __check_security_property("-h", abspath, r":.*EXEC") is False
self.__checksec["Fortify"] = __check_security_property("-s", abspath, r"_chk@GLIBC") is True
self.__checksec["Partial RelRO"] = __check_security_property("-l", abspath, r"GNU_RELRO") is True
self.__checksec["Full RelRO"] = self.__checksec["Partial RelRO"] and __check_security_property("-d", abspath, r"BIND_NOW") is True
return self.__checksec

@classproperty
@deprecated("use `Elf.Abi.X86_64`")
def X86_64(cls) -> int: return Elf.Abi.X86_64.value # pylint: disable=no-self-argument
Expand Down Expand Up @@ -2009,40 +2096,9 @@ def gef_execute_gdb_script(commands: str) -> None:
return


@lru_cache(32)
@deprecated("Use Elf(fname).checksec()")
def checksec(filename: str) -> Dict[str, bool]:
"""Check the security property of the ELF binary. The following properties are:
- Canary
- NX
- PIE
- Fortify
- Partial/Full RelRO.
Return a dict() with the different keys mentioned above, and the boolean
associated whether the protection was found."""
readelf = gef.session.constants["readelf"]

def __check_security_property(opt: str, filename: str, pattern: str) -> bool:
cmd = [readelf,]
cmd += opt.split()
cmd += [filename,]
lines = gef_execute_external(cmd, as_list=True)
for line in lines:
if re.search(pattern, line):
return True
return False

results = collections.OrderedDict()
results["Canary"] = __check_security_property("-rs", filename, r"__stack_chk_fail") is True
has_gnu_stack = __check_security_property("-W -l", filename, r"GNU_STACK") is True
if has_gnu_stack:
results["NX"] = __check_security_property("-W -l", filename, r"GNU_STACK.*RWE") is False
else:
results["NX"] = False
results["PIE"] = __check_security_property("-h", filename, r":.*EXEC") is False
results["Fortify"] = __check_security_property("-s", filename, r"_chk@GLIBC") is True
results["Partial RelRO"] = __check_security_property("-l", filename, r"GNU_RELRO") is True
results["Full RelRO"] = results["Partial RelRO"] and __check_security_property("-d", filename, r"BIND_NOW") is True
return results
return Elf(filename).checksec


@lru_cache()
Expand Down Expand Up @@ -2080,7 +2136,7 @@ def get_entry_point() -> Optional[int]:


def is_pie(fpath: str) -> bool:
return checksec(fpath)["PIE"]
return Elf(fpath).checksec["PIE"]


@deprecated("Prefer `gef.arch.endianness == Endianness.BIG_ENDIAN`")
Expand Down Expand Up @@ -3392,29 +3448,21 @@ def hook_stop_handler(_: "gdb.StopEvent") -> None:
def new_objfile_handler(evt: "gdb.Event") -> None:
"""GDB event handler for new object file cases."""
reset_all_caches()
if evt:
filename: str = evt.new_objfile.filename
if filename.startswith("target:"):
filename = filename[7:]
warn(f"Omitting remote file '{filename}'")
return

try:
elf = Elf(filename)
if not gef.binary:
gef.binary = elf
reset_architecture()
load_libc_args()
else:
gef.session.modules.append(elf)
except FileNotFoundError:
pass
else:
elf = Elf(gdb.current_progspace().filename)
try:
target = pathlib.Path( evt.new_objfile.filename if evt else gdb.current_progspace().filename)
FileFormatClasses = list(filter(lambda fmtcls: fmtcls.is_valid(target), __registered_file_formats__))
GuessedFileFormatClass : Type[FileFormat] = FileFormatClasses.pop() if len(FileFormatClasses) else Elf
binary = GuessedFileFormatClass(target)
if not gef.binary:
gef.binary = elf
gef.binary = binary
reset_architecture()
load_libc_args()
else:
gef.session.modules.append(binary)
except FileNotFoundError as fne:
warn(f"Failed to find objfile or not a valid file format: {str(fne)}")
except RuntimeError as re:
warn(f"Not a valid file format: {str(re)}")
return


Expand Down Expand Up @@ -10400,7 +10448,7 @@ def __init__(self) -> None:
self.pie_breakpoints: Dict[int, PieVirtualBreakpoint] = {}
self.pie_counter: int = 1
self.aliases: List[GefAlias] = []
self.modules: List[Elf] = []
self.modules: List[FileFormat] = []
self.constants = {} # a dict for runtime constants (like 3rd party file paths)
for constant in ("python3", "readelf", "file", "ps"):
self.constants[constant] = which(constant)
Expand Down Expand Up @@ -10728,8 +10776,18 @@ def version(self) -> Optional[Tuple[int, int]]:
class Gef:
"""The GEF root class, which serves as a entrypoint for all the debugging session attributes (architecture,
memory, settings, etc.)."""
binary: Optional[FileFormat]
arch: Architecture
config : GefSettingsManager
ui: GefUiManager
libc: GefLibcManager
memory : GefMemoryManager
heap : GefHeapManager
session : GefSessionManager
gdb: GefCommand

def __init__(self) -> None:
self.binary: Optional[Elf] = None
self.binary: Optional[FileFormat] = None
self.arch: Architecture = GenericArchitecture() # see PR #516, will be reset by `new_objfile_handler`
self.config = GefSettingsManager()
self.ui = GefUiManager()
Expand Down Expand Up @@ -10797,9 +10855,6 @@ def reset_caches(self) -> None:
[PYTHONBIN, "-c", "import os, sys;print(os.linesep.join(sys.path).strip())"]).decode("utf-8").split()
sys.path.extend(SITE_PACKAGES_DIRS)

# setup prompt
gdb.prompt_hook = __gef_prompt__

# setup config
gdb_initial_settings = (
"set confirm off",
Expand Down Expand Up @@ -10827,6 +10882,9 @@ def reset_caches(self) -> None:
# load config
gef.gdb.load_extra_plugins()

# setup gdb prompt
gdb.prompt_hook = __gef_prompt__

# gdb events configuration
gef_on_continue_hook(continue_handler)
gef_on_stop_hook(hook_stop_handler)
Expand Down