diff options
| -rw-r--r-- | hypodermic/main.py | 20 | ||||
| -rw-r--r-- | hypodermic/process.py | 79 | ||||
| -rw-r--r-- | hypodermic/shellcode.py | 41 | ||||
| -rw-r--r-- | setup.py | 2 |
4 files changed, 122 insertions, 20 deletions
diff --git a/hypodermic/main.py b/hypodermic/main.py index 7ce9be6..cb17d3b 100644 --- a/hypodermic/main.py +++ b/hypodermic/main.py @@ -108,26 +108,8 @@ def main(): if args.create: alert("Creating process at path '{}'...".format(args.create)) p = Process(path=args.create) - - shellcode = b"\x48\xc7\xc0\x01\x00\x00\x00\x48\xc7\xc7\x01\x00" + \ - b"\x00\x00\x48\xc7\xc2\x29\x00\x00\x00\x48\x8d\x35" + \ - b"\x00\x00\x00\x00\x48\x81\xc6\x0d\x00\x00\x00\x0f" + \ - b"\x05\x90\x90\x90\xcc\x61\x6d\x64\x36\x34\x20\x4c" + \ - b"\x69\x6e\x75\x78\x20\x73\x79\x73\x5f\x77\x72\x69" + \ - b"\x74\x65\x20\x73\x68\x65\x6c\x6c\x63\x6f\x64\x65" + \ - b"\x20\x62\x79\x20\x4a\x61\x6b\x6f\x62\x0a" - - old_rip = p.get_register("rip") - alert("%rip at {}".format(hex(old_rip))) - old_code = p.read_bytes(old_rip, len(shellcode)) - p.write_bytes(old_rip, shellcode) - while p.read_bytes(p.get_register("rip"), 1) != b'\xcc': - p.single_step() - alert("Hit breakpoint!") - p.write_bytes(old_rip, old_code) - p.set_register("rip", old_rip) - alert("%rip reset to {}".format(hex(p.get_register("rip")))) p.continue_until_haulted() else: alert("Attaching to process with pid {}...".format(args.attach)) p = Process(pid=args.attach) + p.continue_until_haulted() diff --git a/hypodermic/process.py b/hypodermic/process.py index c8a2823..1e2c1d4 100644 --- a/hypodermic/process.py +++ b/hypodermic/process.py @@ -22,6 +22,7 @@ import os.path import re from hypodermic.memory import Region, maps +from hypodermic.shellcode import assemble _AMD64_INDICES = { "r15": 0, @@ -53,6 +54,23 @@ _AMD64_INDICES = { "gs": 26 } +_AMD64_REGS = [ + "rax", + "rbx", + "rcx", + "rdx", + "rsi", + "rdi", + "r8", + "r9", + "r10", + "r11", + "r12", + "r13", + "r14", + "r15", +] + _I386_INDICES = { "ebx": 0, "ecx": 1, @@ -73,6 +91,15 @@ _I386_INDICES = { "xss": 16 } +_I386_REGS = [ + "eax", + "ebx", + "ecx", + "edx", + "esi", + "edi", +] + class Process(object): """Process attached via ptrace. @@ -259,6 +286,58 @@ class Process(object): return self._setreg(self.pid, regs.get(reg), ctypes.c_ulonglong(val)) return self._setreg(self.pid, regs.get(reg), ctypes.c_ulong(val)) + def _run_code_32(self, code: bytes, preserve: list): + reg_order = [reg for reg in _I386_REGS if reg not in preserve] + push = assemble("".join("pushl %{};".format(reg) for reg in reg_order), "i386") + pop = assemble("".join("popl %{};".format(reg) for reg in reversed(reg_order)), "i386") + bp = assemble("nop; nop; int3;", "i386") + payload = push + code + pop + bp + + old_eip = self.get_register("eip") + old_code = self.read_bytes(old_eip, len(payload)) + self.write_bytes(old_eip, payload) + while self.read_bytes(self.get_register("eip"), 1) != b"\xcc": + self.single_step() + self.write_bytes(old_eip, old_code) + self.set_register("eip", old_eip) + + def _run_code_64(self, code: bytes, preserve: list): + reg_order = [reg for reg in _AMD64_REGS if reg not in preserve] + push = assemble("".join("pushq %{};".format(reg) for reg in reg_order)) + pop = assemble("".join("popq %{};".format(reg) for reg in reversed(reg_order))) + bp = assemble("nop; nop; int3;") + payload = push + code + pop + bp + + old_rip = self.get_register("rip") + old_code = self.read_bytes(old_rip, len(payload)) + self.write_bytes(old_rip, payload) + while self.read_bytes(self.get_register("rip"), 1) != b"\xcc": + self.single_step() + self.write_bytes(old_rip, old_code) + self.set_register("rip", old_rip) + + def run_code(self, code: bytes, preserve=[]) -> tuple: + """Executes code on the inferior. + + Args: + code (:obj:`bytes`): The code to execute. + preserve (:obj:`list`, optional): Registers that should be + allowed to be clobbered. + + Returns: + A pair of lists, the first containing the values of + preserved registers before the code was executed, and the + second containing the values of preserved registers after + the code was executed. + """ + before = [self.get_register(reg) for reg in preserve] + if self.arch == "x64": + self._run_code_64(code, preserve) + else: + self._run_code_32(code, preserve) + after = [self.get_register(reg) for reg in preserve] + return before, after + @property def arch(self) -> str: """Returns the architecture of the host processor. diff --git a/hypodermic/shellcode.py b/hypodermic/shellcode.py new file mode 100644 index 0000000..0a35e0a --- /dev/null +++ b/hypodermic/shellcode.py @@ -0,0 +1,41 @@ +# Copyright (C) 2017 Jakob Kreuze, All Rights Reserved. +# +# This file is part of Hypodermic. +# +# Hypodermic is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# Hypodermic is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Hypodermic. If not, see <http://www.gnu.org/licenses/>. + +"""Module for generating payloads.""" + +from keystone import * + + +def assemble(code: str, arch="amd64", syntax="att") -> bytes: + """Assembles the given assembly code. + + Args: + code (str): The code to assemble + arch (:obj:`str`, optional): The target architecture. + Defaults to "amd64" + syntax (:obj:`str`, optional): The assembly syntax to use. + Defaults to "att" + + Returns: + A `bytes` object containing the resultant machine code. + """ + wordlen = KS_MODE_64 if arch == "amd64" else KS_MODE_32 + ks = Ks(KS_ARCH_X86, wordlen) + if syntax == "att": + ks.syntax = keystone.KS_OPT_SYNTAX_ATT + encoded, _ = ks.asm(code) + return bytes(encoded) @@ -30,7 +30,7 @@ setup( packages=["hypodermic"], include_package_data=True, ext_modules=[lib], - install_requires=["pyelftools"], + install_requires=["pyelftools", "keystone-engine"], extras_require={}, tests_require=[], entry_points={"console_scripts": ["hypodermic = hypodermic.main:main"]}, |