From ebe7dd1682f79edae434b3440516f694b43bee79 Mon Sep 17 00:00:00 2001 From: jakob Date: Sun, 22 Oct 2017 15:01:36 -0400 Subject: Initial calling to internal _dl_open --- README.md | 10 +++--- hypodermic/process.py | 85 ++++++++++++++++++++++++++++++++----------------- hypodermic/shellcode.py | 40 ++++++++--------------- 3 files changed, 73 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 9c48ceb..4a4588e 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,9 @@ redirected. There have been a few attempts at this in the past, such as This is oftentimes unsuccessful, being very dependent upon how glibc was compiled. -The point of Hypodermic is to find a means of injecting a dynamic library into +The purpose of Hypodermic is to find a means of injecting a dynamic library into any Linux executable, even ones that are statically-linked, and transferring -this method over to [PINCE][2] when it is stable enough. The current goal is the +this code into [PINCE][2] when it becomes stable enough. The current goal is the ability to inject an internal cheat into Counter-Strike: Global Offensive, such as [AimTux][3]. This will signal that the method has reached a point of viability. @@ -33,9 +33,9 @@ routines. This did not work, as the process of loading an ELF library into memory is far more complicated than calling mmap(2) on the file. The second iteration also involves injecting code into the inferior process, but -instead maps the Linux runtime linker into memory, if it is not already there, -and utilizes the internal _dl_open routine. This is difficult, as it means -mapping it the way the kernel would to ensure proper initialization of the RTLD. +it instead maps the Linux runtime linker into memory, if it is not already +there, and utilizes the internal `_dl_open` routine. This is difficult, as it +requires mapping it the way the kernel would to ensure proper initialization. ## Important Resources diff --git a/hypodermic/process.py b/hypodermic/process.py index 35b8c1d..4740062 100644 --- a/hypodermic/process.py +++ b/hypodermic/process.py @@ -456,20 +456,71 @@ class Process(object): if not self.rtld: raise OSError("Process does not have a usable RTLD") + self.path_caller_check() if self.arch == "x64": + ret = self.get_register("rip") old_rax = self.get_register("rax") - self.run_code(dlopen_shellcode(self.rtld_dl_open_addr, path), - preserve=["rax"]) + shellcode = dlopen_shellcode(self.rtld_dlsym("_dl_open"), ret, path) + self.run_code(shellcode, preserve=["rax"]) addr = self.get_register("rax") self.set_register("rax", old_rax) else: + ret = self.get_register("eip") old_eax = self.get_register("eax") - self.run_code(dlopen_shellcode(self.rtld_dl_open_addr, path, - arch="i386"), preserve=["eax"]) + shellcode = dlopen_shellcode(self.rtld_dlsym("_dl_open"), ret, path, + arch="i386") + self.run_code(shellcode, preserve=["eax"]) addr = self.get_register("eax") self.set_register("eax", old_eax) return addr + def patch_caller_check(self): + """Patches out a caller check security measure in the RTLD. + + Note: + This is requried before making any invocations to `dlopen`. + + Raises: + OSError: If the process either has no RTLD, or the caller + check cannot be found. + """ + if not self.rtld: + raise OSError("Process does not have a usable RTLD") + + if self.arch == "x64": + caller_check = self.rtld_dlsym("_dl_check_caller") + shellcode = assemble("movq $0x00, %rax; ret;") + self.write_bytes(caller_check, shellcode) + else: + caller_check = self.rtld_dlsym("_dl_check_caller") + shellcode = assemble("movl $0x00, %eax; ret;") + self.write_bytes(caller_check, shellcode) + + def rtld_dlsym(self, sym: str) -> int: + """Obtain the absolute address of an RTLD symbol in main memory. + + Raises: + OSError: If either the process has no instance of the RTLD, + or the RTLD lacks sufficient symbols. + + Returns: + An integer containing the address. + """ + if self.rtld is None: + raise OSError("Process has no RTLD instance") + + with open(self.rtld.path, "rb") as rtld: + elf = ELFFile(rtld) + symtab = elf.get_section_by_name(".symtab") + + if not isinstance(symtab, SymbolTableSection): + raise OSError("RTLD has no usable symbol table") + + res = symtab.get_symbol_by_name(sym) + if len(res) < 1: + raise OSError("RTLD has no {} symbol".format(sym)) + return self.rtld.start + res[0].entry.st_value + def page_start(self, addr: int) -> int: return addr & ~(self.page_size - 1) @@ -500,32 +551,6 @@ class Process(object): """ return "x64" if self._isamd64 else "x86" - @property - def rtld_dl_open_addr(self) -> int: - """Obtain the absolute address of _dl_open in main memory. - - Raises: - OSError: If either the process has no instance of the RTLD, - or the RTLD lacks sufficient symbols. - - Returns: - An integer containing the address. - """ - if self.rtld is None: - raise OSError("Process has no RTLD instance") - - with open(self.rtld.path, "rb") as rtld: - elf = ELFFile(rtld) - symtab = elf.get_section_by_name(".symtab") - - if not isinstance(symtab, SymbolTableSection): - raise OSError("RTLD has no usable symbol table") - - res = symtab.get_symbol_by_name("_dl_open") - if len(res) < 1: - raise OSError("RTLD has no _dl_open symbol") - return self.rtld.start + res[0].entry.st_value - @property def maps(self) -> list: """Obtain the process' memory map. diff --git a/hypodermic/shellcode.py b/hypodermic/shellcode.py index 114a4bc..38aa841 100644 --- a/hypodermic/shellcode.py +++ b/hypodermic/shellcode.py @@ -164,12 +164,12 @@ def munmap_shellcode(addr=0, size=0, arch="amd64"): return assemble(asm, arch) -# FIXME: Relative addressing is untested in i386. -def dlopen_shellcode(addr: int, path: str, arch="amd64"): +# TODO: i386 not implemented. +def dlopen_shellcode(dlopen: int, ret: int, path: str, arch="amd64"): """Generates shellcode to invoke _dl_open in the RTLD. Args: - addr (int): The absolute address of _dl_open. + dlopen (int): The absolute address of _dl_open. path (str): The path of the library to open. Returns: @@ -180,29 +180,15 @@ def dlopen_shellcode(addr: int, path: str, arch="amd64"): "__path:" \ " .asciz \"{}\";" \ "__path_end:" \ - " leaq (%rip), %rdi;" \ - " subq $. - __path, %rdi;" \ - " movq $0x80000101, %rsi;" \ - " movq $0x00, %rdx;" \ - " movq $0x00, %rcx;" \ - " movq $0x00, %r8;" \ - " movq $0x00, %r9;" \ - " pushq $0x00;" \ - " callq ${};".format(path, addr) + " leaq (%rip), %rdi;" \ + " subq $. - __path, %rdi;" \ + " movq $0x80000101, %rsi;" \ + " movq ${}, %rdx;" \ + " movq $0xfffffffffffffffe, %rcx;" \ + " movq $0x00, %r8;" \ + " movq $0x00, %r9;" \ + " movq $0x00, %r10;" \ + " callq ${};".format(path, ret, dlopen) else: - asm = " jmp __path_end;" \ - "__path:" \ - " .asciz \"{}\";" \ - "__path_end:" \ - " call $. + 5;" \ - " popl %ebx;" \ - " subl $. - 4 - __path, %ebx;" \ - " pushl %ebx;" \ - " pushl $0x80000101;" \ - " pushl $0x00;" \ - " pushl $0x00;" \ - " pushl $0x00;" \ - " pushl $0x00;" \ - " pushl $0x00;" \ - " calll ${};".format(path, addr) + asm = "" return assemble(asm, arch) -- cgit v1.3