summaryrefslogtreecommitdiff
path: root/hypodermic
diff options
context:
space:
mode:
Diffstat (limited to 'hypodermic')
-rw-r--r--hypodermic/main.py20
-rw-r--r--hypodermic/process.py79
-rw-r--r--hypodermic/shellcode.py41
3 files changed, 121 insertions, 19 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)