diff options
| -rw-r--r-- | README.md | 7 | ||||
| -rw-r--r-- | hypodermic/process.py | 90 | ||||
| -rw-r--r-- | hypodermic/shellcode.py | 85 |
3 files changed, 173 insertions, 9 deletions
@@ -33,9 +33,10 @@ 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 to make use of its existing -GOT/PLT setup functionality. This involves injecting auxiliary vectors onto the -stack in an attempt to trick the RTLD. +instead maps the Linux runtime linker into memory. This is difficult, as it +means mapping it the way the kernel would. This involves injecting auxiliary +vectors onto the stack in an attempt to trick it into loading the desired +libraries. ## Important Resources diff --git a/hypodermic/process.py b/hypodermic/process.py index c2c7409..e26978b 100644 --- a/hypodermic/process.py +++ b/hypodermic/process.py @@ -361,10 +361,95 @@ class Process(object): fd = self.get_register("eax") self.set_register("eax", old_eax) + # FIXME: fd is parsed as a ctypes.c_ulonglong... if fd < 0: raise OSError("Couldn't open {}".format(path)) return fd + def close(self, fd: int): + """Attempts to close a file descriptor within the inferior. + + Args: + fd (int): The file descriptor to close. + """ + if self.arch == "x64": + self.run_code(close_shellcode(fd)) + else: + self.run_code(close_shellcode(fd, arch="i386")) + + def mmap(self, addr=0, size=0, prot=0, flags=0, fd=-1, off=0) -> int: + """Introduce a new mapping to the process' address space. + + Args: + addr (:obj:`int`, optional): The address, or 0 if + unimportant. + size (:obj:`int`, optional): The desired size of the + mapping. + prot (:obj:`int`, optional): The protections for the + mapping. + flags (:obj:`int`, optional): Any other flags for the + mapping. + fd (:obj:`int`, optional): A file descriptor to map. + off (:obj:`int`, optional): An offset in the file + descriptor. + + Raises: + OSError: If the mapping cannot be made. + + Returns: + The address the mapping was made at. + """ + if self.arch == "x64": + old_rax = self.get_register("rax") + self.run_code(mmap_shellcode(addr, size, prot, flags, fd, off, path), + preserve=["rax"]) + res = self.get_register("rax") + self.set_register("rax", old_rax) + else: + old_eax = self.get_register("eax") + self.run_code(mmap_shellcode(addr, size, prot, flags, fd, off, path, + arch="i386"), preserve=["eax"]) + res = self.get_register("eax") + self.set_register("eax", old_eax) + + # FIXME: fd is parsed as a ctypes.c_ulonglong... + if res == -1: + raise OSError("Couldn't complete mapping.") + return res + + def munmap(self, addr=0, size=0): + """Removes a mapping from the process' address space. + + Args: + addr (:obj:`int`, optional): The address, or 0 if + unimportant. + size (:obj:`int`, optional): The desired size of the + mapping. + Raises: + OSError: If the mapping cannot be made. + + Returns: + The address the mapping was made at. + """ + if self.arch == "x64": + self.run_code(munmap_shellcode(addr, size)) + else: + self.run_code(munmap_shellcode(addr, size, arch="i386")) + + def page_start(self, addr: int) -> int: + return addr & ~(self.page_size - 1) + + def page_offset(self, addr: int) -> int: + return addr & (self.page_size - 1) + + def page_align(self, addr: int) -> int: + return (addr + self.page_size - 1) & ~(self.page_size - 1) + + # FIXME: Not tested on i386. + @property + def page_size(self) -> int: + return 4096 + @property def arch(self) -> str: """Returns the architecture of the host processor. @@ -381,11 +466,6 @@ class Process(object): """ return "x64" if self._isamd64 else "x86" - # FIXME: Not tested on i386. - @property - def page_size(self) -> int: - return 4096 - @property def maps(self) -> list: """Obtain the process' memory map. diff --git a/hypodermic/shellcode.py b/hypodermic/shellcode.py index 75ab4ad..ceaf7a7 100644 --- a/hypodermic/shellcode.py +++ b/hypodermic/shellcode.py @@ -74,8 +74,91 @@ def open_shellcode(path: str, flags=0, arch="amd64") -> bytes: " movl $0x05, %eax;" \ " call $. + 5;" \ " popl %ebx;" \ - " subl $. - 4 - __path, %ebx;" + " subl $. - 4 - __path, %ebx;" \ " movl ${}, %ecx;" \ " movl $0x00, %edx;" \ " int $0x80;".format(path, flags) return assemble(asm, arch) + + +def close_shellcode(fd: int, arch="amd64") -> bytes: + """Generates shellcode to close a file descriptor. + + Args: + fd (int): The file descriptor to close. + arch (:obj:`str`, optional): The target architecture. + Defaults to "amd64". + + Returns: + The assembled shellcode, as a `bytes` object. + """ + if arch == "amd64": + asm = " movq $0x03, %rax;" \ + " movq ${}, %rdi;" \ + " syscall;".format(fd) + else: + asm = " movq $0x06, %eax;" \ + " movq ${}, %ebx;" \ + " int $0x80;;".format(fd) + return assemble(asm, arch) + + +# FIXME: Syscall number may be incorrect for i386. +def mmap_shellcode(addr=0, size=0, prot=0, flags=0, fd=-1, off=0, arch="amd64"): + """Generates shellcode to map a region of memory. + + Args: + addr (:obj:`int`, optional): The address, or 0 if unimportant. + size (:obj:`int`, optional): The desired size of the mapping. + prot (:obj:`int`, optional): The protections for the mapping. + flags (:obj:`int`, optional): Any other flags for the mapping. + fd (:obj:`int`, optional): A file descriptor to map. + off (:obj:`int`, optional): An offset in the file descriptor. + arch (:obj:`str`, optional): The target architecture. + + Returns: + The assembled shellcode, as a `bytes` object. + """ + if arch == "amd64": + asm = " movq $0x09, %rax;" \ + " movq ${}, %rdi;" \ + " movq ${}, %rsi;" \ + " movq ${}, %rdx;" \ + " movq ${}, %r10;" \ + " movq ${}, %r8;" \ + " movq ${}, %r9;" \ + " syscall;".format(addr, size, prot, flags, fd, off) + else: + asm = " movl $0x5a, %eax;" \ + " movl ${}, %ebx;" \ + " movl ${}, %ecx;" \ + " movl ${}, %edx;" \ + " movl ${}, %esi;" \ + " movl ${}, %edi;" \ + " movl ${}, %ebp;" \ + " int $0x80;".format(addr, size, prot, flags, fd, off) + return assemble(asm, arch) + + +def munmap_shellcode(addr=0, size=0, arch="amd64"): + """Generates shellcode to map a region of memory. + + Args: + addr (:obj:`int`, optional): The address of the mapping. + size (:obj:`int`, optional): The size of the mapping. + arch (:obj:`str`, optional): The target architecture. + + Returns: + The assembled shellcode, as a `bytes` object. + """ + if arch == "amd64": + asm = " movq $0x0b, %rax;" \ + " movq ${}, %rdi;" \ + " movq ${}, %rsi;" \ + " syscall;".format(addr, size) + else: + asm = " movl $0x5b, %eax;" \ + " movl ${}, %ebx;" \ + " movl ${}, %ecx;" \ + " int $0x80;".format(addr, size) + return assemble(asm, arch) |