From 42f581e3e19430c1458be1a86d6c1b74eb148f0e Mon Sep 17 00:00:00 2001 From: Jakob Date: Sat, 2 Sep 2017 18:28:20 -0400 Subject: Began to implement runtime manipulation utilities. --- wrapper/ptrace.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) (limited to 'wrapper') diff --git a/wrapper/ptrace.c b/wrapper/ptrace.c index 706b13e..7e5dfa8 100644 --- a/wrapper/ptrace.c +++ b/wrapper/ptrace.c @@ -84,3 +84,75 @@ int cont(int pid) { return 0; } + + +/* user_regs_struct is copied from sys/user.h so that we can debug a + 32-bit executable on a 64-bit platform. */ +struct amd64_user_regs_struct { + __extension__ unsigned long long r15; + __extension__ unsigned long long r14; + __extension__ unsigned long long r13; + __extension__ unsigned long long r12; + __extension__ unsigned long long rbp; + __extension__ unsigned long long rbx; + __extension__ unsigned long long r11; + __extension__ unsigned long long r10; + __extension__ unsigned long long r9; + __extension__ unsigned long long r8; + __extension__ unsigned long long rax; + __extension__ unsigned long long rcx; + __extension__ unsigned long long rdx; + __extension__ unsigned long long rsi; + __extension__ unsigned long long rdi; + __extension__ unsigned long long orig_rax; + __extension__ unsigned long long rip; + __extension__ unsigned long long cs; + __extension__ unsigned long long eflags; + __extension__ unsigned long long rsp; + __extension__ unsigned long long ss; + __extension__ unsigned long long fs_base; + __extension__ unsigned long long gs_base; + __extension__ unsigned long long ds; + __extension__ unsigned long long es; + __extension__ unsigned long long fs; + __extension__ unsigned long long gs; +}; + + +struct i386_user_regs_struct { + unsigned long ebx; + unsigned long ecx; + unsigned long edx; + unsigned long esi; + unsigned long edi; + unsigned long ebp; + unsigned long eax; + unsigned long xds; + unsigned long xes; + unsigned long xfs; + unsigned long xgs; + unsigned long orig_eax; + unsigned long eip; + unsigned long xcs; + unsigned long eflags; + unsigned long esp; + unsigned long xss; +}; + + +unsigned long long getreg64(int pid, int idx) { + struct amd64_user_regs_struct regs; + + ptrace(PTRACE_GETREGS, pid, NULL, ®s); + + return ((unsigned long long *) ®s)[idx]; +} + + +unsigned long getreg32(int pid, int idx) { + struct i386_user_regs_struct regs; + + ptrace(PTRACE_GETREGS, pid, NULL, ®s); + + return ((unsigned long *) ®s)[idx]; +} -- cgit v1.3 From 6f0a28b09a670371b22658a295403ab2573ed993 Mon Sep 17 00:00:00 2001 From: Jakob Date: Sun, 3 Sep 2017 14:35:48 -0400 Subject: Fixed up how the user regs struct is accessed. --- hypodermic/process.py | 34 ++++++++++++++------- wrapper/ptrace.c | 83 ++++++++++++++------------------------------------- 2 files changed, 46 insertions(+), 71 deletions(-) (limited to 'wrapper') diff --git a/hypodermic/process.py b/hypodermic/process.py index 9a321e0..8307e08 100644 --- a/hypodermic/process.py +++ b/hypodermic/process.py @@ -136,10 +136,9 @@ class Process(object): self._attach = self._so.attach self._detach = self._so.detach self._cont = self._so.cont - self._getreg32 = self._so.getreg32 - self._getreg32.restype = ctypes.c_ulong - self._getreg64 = self._so.getreg64 - self._getreg64.restype = ctypes.c_ulonglong + self._isamd64 = self._so.is_amd64 + self._getreg = self._so.getreg + self._getreg.restype = ctypes.c_ulonglong def detach(self): """Explicitly detaches from the process. @@ -210,25 +209,40 @@ class Process(object): def get_register(self, reg: str) -> int: """Returns the value of the given register. + Note: + Registers are tied to the host processor, not the target + processor. For example, a 32-bit ELF will still have 64-bit + registers on 64-bit Linux. + Args: reg (str): The register to inspect. (e.g. "rax") Returns: An integer representing the value of the register. + """ - regs = AMD64_INDICES if self.arch == "x64" else I386_INDICES + regs = AMD64_INDICES if self._isamd64 else I386_INDICES if reg not in regs: raise ValueError("{} is not a valid register".format(reg)) - if self.arch == "x64": - return self._getreg64(self.pid, regs.get(reg)) - return self._getreg32(self.pid, regs.get(reg)) + return self._getreg(self.pid, regs.get(reg)) @property def arch(self) -> str: - with open("/proc/{}/exe".format(self.pid), "rb") as elf: - return ELFFile(elf).get_machine_arch() + """Returns the architecture of the host processor. + + Note: + The architecture of the host platform is not necessarily + the architecture of the target executable. However, this + value will accurately represent how registers should be + addressed. + + Returns: + A string representing the host processor. As of now, only + "x64" and "x86" are supported. + """ + return "x64" if self._isamd64 else "x86" @property def maps(self) -> list: diff --git a/wrapper/ptrace.c b/wrapper/ptrace.c index 7e5dfa8..8517519 100644 --- a/wrapper/ptrace.c +++ b/wrapper/ptrace.c @@ -19,9 +19,12 @@ #include #include +#include +#include #include #include +#include #include @@ -86,73 +89,31 @@ int cont(int pid) { } -/* user_regs_struct is copied from sys/user.h so that we can debug a - 32-bit executable on a 64-bit platform. */ -struct amd64_user_regs_struct { - __extension__ unsigned long long r15; - __extension__ unsigned long long r14; - __extension__ unsigned long long r13; - __extension__ unsigned long long r12; - __extension__ unsigned long long rbp; - __extension__ unsigned long long rbx; - __extension__ unsigned long long r11; - __extension__ unsigned long long r10; - __extension__ unsigned long long r9; - __extension__ unsigned long long r8; - __extension__ unsigned long long rax; - __extension__ unsigned long long rcx; - __extension__ unsigned long long rdx; - __extension__ unsigned long long rsi; - __extension__ unsigned long long rdi; - __extension__ unsigned long long orig_rax; - __extension__ unsigned long long rip; - __extension__ unsigned long long cs; - __extension__ unsigned long long eflags; - __extension__ unsigned long long rsp; - __extension__ unsigned long long ss; - __extension__ unsigned long long fs_base; - __extension__ unsigned long long gs_base; - __extension__ unsigned long long ds; - __extension__ unsigned long long es; - __extension__ unsigned long long fs; - __extension__ unsigned long long gs; -}; - - -struct i386_user_regs_struct { - unsigned long ebx; - unsigned long ecx; - unsigned long edx; - unsigned long esi; - unsigned long edi; - unsigned long ebp; - unsigned long eax; - unsigned long xds; - unsigned long xes; - unsigned long xfs; - unsigned long xgs; - unsigned long orig_eax; - unsigned long eip; - unsigned long xcs; - unsigned long eflags; - unsigned long esp; - unsigned long xss; -}; - - -unsigned long long getreg64(int pid, int idx) { - struct amd64_user_regs_struct regs; +/* TODO: As of now, Hypodermis is strongly tied to the Intel x86 + family of processors. This should really be expanded. */ +int is_amd64(void) { + struct utsname ub; - ptrace(PTRACE_GETREGS, pid, NULL, ®s); + uname(&ub); - return ((unsigned long long *) ®s)[idx]; + return !strcmp(ub.machine, "x86_64"); } -unsigned long getreg32(int pid, int idx) { - struct i386_user_regs_struct regs; +#ifdef __x86_64__ +unsigned long long getreg(int pid, int idx) { + struct user_regs_struct regs; + + ptrace(PTRACE_GETREGS, pid, NULL, ®s); + + return ((unsigned long long *) ®s)[idx]; +} +#else +unsigned long getreg(int pid, int idx) { + struct user_regs_struct regs; ptrace(PTRACE_GETREGS, pid, NULL, ®s); - return ((unsigned long *) ®s)[idx]; + return ((unsigned long *) ®s)[idx]; } +#endif -- cgit v1.3 From 8927b31b8ef6334a5efd28ea779db5a58ac5d445 Mon Sep 17 00:00:00 2001 From: Jakob Date: Sun, 3 Sep 2017 16:19:33 -0400 Subject: Implemented basic machine code injection. --- hypodermic/main.py | 20 +++++++++++++++++++ hypodermic/process.py | 53 ++++++++++++++++++++++++++++++++++++++++----------- wrapper/ptrace.c | 32 +++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 11 deletions(-) (limited to 'wrapper') diff --git a/hypodermic/main.py b/hypodermic/main.py index 74c4486..7ce9be6 100644 --- a/hypodermic/main.py +++ b/hypodermic/main.py @@ -108,6 +108,26 @@ 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) diff --git a/hypodermic/process.py b/hypodermic/process.py index 8307e08..c8a2823 100644 --- a/hypodermic/process.py +++ b/hypodermic/process.py @@ -21,11 +21,9 @@ import ctypes import os.path import re -from elftools.elf.elffile import ELFFile - from hypodermic.memory import Region, maps -AMD64_INDICES = { +_AMD64_INDICES = { "r15": 0, "r14": 1, "r13": 2, @@ -55,7 +53,7 @@ AMD64_INDICES = { "gs": 26 } -I386_INDICES = { +_I386_INDICES = { "ebx": 0, "ecx": 1, "edx": 2, @@ -136,7 +134,9 @@ class Process(object): self._attach = self._so.attach self._detach = self._so.detach self._cont = self._so.cont + self._step = self._so.step self._isamd64 = self._so.is_amd64 + self._setreg = self._so.setreg self._getreg = self._so.getreg self._getreg.restype = ctypes.c_ulonglong @@ -158,6 +158,15 @@ class Process(object): if self._cont(ctypes.c_int(self.pid)): raise OSError("Could not continue") + def single_step(self): + """Execute a single instruction. + + Raises: + OSError: If the process cannot be put into single step mode. + """ + if self._step(ctypes.c_int(self.pid)): + raise OSError("Could not continue") + def write_bytes(self, address: int, src: bytes) -> int: """Writes data into process memory. @@ -210,24 +219,46 @@ class Process(object): """Returns the value of the given register. Note: - Registers are tied to the host processor, not the target - processor. For example, a 32-bit ELF will still have 64-bit - registers on 64-bit Linux. + Registers names are tied to the host processor, not the + target processor. For example, a 32-bit ELF will still have + 64-bit registers on 64-bit Linux. It would be wise to query + the `arch` property of the Process object. Args: reg (str): The register to inspect. (e.g. "rax") Returns: An integer representing the value of the register. - """ - regs = AMD64_INDICES if self._isamd64 else I386_INDICES + regs = _AMD64_INDICES if self._isamd64 else _I386_INDICES if reg not in regs: raise ValueError("{} is not a valid register".format(reg)) return self._getreg(self.pid, regs.get(reg)) + def set_register(self, reg: str, val: int): + """Sets the value of the given register. + + Note: + Registers names are tied to the host processor, not the + target processor. For example, a 32-bit ELF will still have + 64-bit registers on 64-bit Linux. It would be wise to query + the `arch` property of the Process object. + + Args: + reg (str): The register to modify. (e.g. "rax") + val (int): The new value for the register. + """ + regs = _AMD64_INDICES if self._isamd64 else _I386_INDICES + + if reg not in regs: + raise ValueError("{} is not a valid register".format(reg)) + + if self._isamd64: + return self._setreg(self.pid, regs.get(reg), ctypes.c_ulonglong(val)) + return self._setreg(self.pid, regs.get(reg), ctypes.c_ulong(val)) + @property def arch(self) -> str: """Returns the architecture of the host processor. @@ -235,8 +266,8 @@ class Process(object): Note: The architecture of the host platform is not necessarily the architecture of the target executable. However, this - value will accurately represent how registers should be - addressed. + value will accurately represent which registers are + available. Returns: A string representing the host processor. As of now, only diff --git a/wrapper/ptrace.c b/wrapper/ptrace.c index 8517519..706fd4e 100644 --- a/wrapper/ptrace.c +++ b/wrapper/ptrace.c @@ -81,6 +81,18 @@ int cont(int pid) { return -1; } + waitpid(pid, &s, WNOHANG); + return 0; +} + + +int step(int pid) { + int s; + + if ((ptrace(PTRACE_SINGLESTEP, pid, NULL, NULL)) < 0) { + return -1; + } + while (!WIFSTOPPED(s)) { waitpid(pid, &s, WNOHANG); } @@ -108,6 +120,16 @@ unsigned long long getreg(int pid, int idx) { return ((unsigned long long *) ®s)[idx]; } + +void setreg(int pid, int idx, unsigned long long value) { + struct user_regs_struct regs; + + ptrace(PTRACE_GETREGS, pid, NULL, ®s); + + ((unsigned long long *) ®s)[idx] = value; + + ptrace(PTRACE_SETREGS, pid, NULL, ®s); +} #else unsigned long getreg(int pid, int idx) { struct user_regs_struct regs; @@ -116,4 +138,14 @@ unsigned long getreg(int pid, int idx) { return ((unsigned long *) ®s)[idx]; } + +void setreg(int pid, int idx, unsigned long value) { + struct user_regs_struct regs; + + ptrace(PTRACE_GETREGS, pid, NULL, ®s); + + ((unsigned long *) ®s)[idx] = value; + + ptrace(PTRACE_SETREGS, pid, NULL, ®s); +} #endif -- cgit v1.3