From 8b232076240dc7e6f8abdfca3d1634ae065c691b Mon Sep 17 00:00:00 2001 From: Jakob Date: Fri, 1 Sep 2017 16:40:01 -0400 Subject: Process.rtld now returns the base region of the RTLD. --- README.md | 38 ++++++++------- hypodermic/main.py | 6 +-- hypodermic/process.py | 126 ++++++++++++++++++++++++++++++++++++++++++++++++ hypodermic/ptrace.py | 129 -------------------------------------------------- 4 files changed, 150 insertions(+), 149 deletions(-) create mode 100644 hypodermic/process.py delete mode 100644 hypodermic/ptrace.py diff --git a/README.md b/README.md index a14d08b..94582fb 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,14 @@ 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 goal of Hypodermic is to find a means of injecting a dynamic library into -any Linux executable, even ones that are statically-linked, and tranferring this -method over to [PINCE][2] when it is stable enough. +The point 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 +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. -Hypodermic is free software, licensed under the [GNU General Public License.][3] +Hypodermic is free software, licensed under the [GNU General Public License.][4] ## Current Attempts @@ -37,20 +40,21 @@ stack in an attempt to trick the RTLD. ## Important Resources -* [Understanding Linux ELF RTLD internals][4] -* [Runtime Process Infection][5] -* [ELF Program Header][6] -* [Dynamic Loader Operation][7] -* [About ELF Auxiliary Vectors][8] -* [Code Injection into Running Linux Application][9] +* [Understanding Linux ELF RTLD internals][5] +* [Runtime Process Infection][6] +* [ELF Program Header][7] +* [Dynamic Loader Operation][8] +* [About ELF Auxiliary Vectors][9] +* [Code Injection into Running Linux Application][10] [1]: https://github.com/gaffe23/linux-inject [2]: https://github.com/korcankaraokcu/PINCE -[3]: https://www.gnu.org/licenses/gpl.html -[4]: http://s.eresi-project.org/inc/articles/elf-rtld.txt -[5]: http://phrack.org/issues/59/8.html -[6]: http://www.sco.com/developers/gabi/latest/ch5.pheader.html -[7]: https://sourceware.org/glibc/wiki/DynamicLoader -[8]: http://articles.manugarg.com/aboutelfauxiliaryvectors -[9]: https://www.codeproject.com/Articles/33340/Code-Injection-into-Running-Linux-Application +[3]: https://github.com/AimTuxOfficial/AimTux/ +[4]: https://www.gnu.org/licenses/gpl.html +[5]: http://s.eresi-project.org/inc/articles/elf-rtld.txt +[6]: http://phrack.org/issues/59/8.html +[7]: http://www.sco.com/developers/gabi/latest/ch5.pheader.html +[8]: https://sourceware.org/glibc/wiki/DynamicLoader +[9]: http://articles.manugarg.com/aboutelfauxiliaryvectors +[10]: https://www.codeproject.com/Articles/33340/Code-Injection-into-Running-Linux-Application diff --git a/hypodermic/main.py b/hypodermic/main.py index 635e3a7..74c4486 100644 --- a/hypodermic/main.py +++ b/hypodermic/main.py @@ -21,7 +21,7 @@ import argparse import sys import textwrap -from hypodermic.ptrace import Process +from hypodermic.process import Process class CustomHelp(argparse.HelpFormatter): @@ -106,8 +106,8 @@ def main(): sys.exit(1) if args.create: - alert("Creating process at path {}".format(args.create)) + alert("Creating process at path '{}'...".format(args.create)) p = Process(path=args.create) else: - alert("Attaching to process with pid {}".format(args.attach)) + alert("Attaching to process with pid {}...".format(args.attach)) p = Process(pid=args.attach) diff --git a/hypodermic/process.py b/hypodermic/process.py new file mode 100644 index 0000000..6fc1519 --- /dev/null +++ b/hypodermic/process.py @@ -0,0 +1,126 @@ +# 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 . + +"""ctypes wrapper for ptrace.""" + +import ctypes +import os.path +import re + +from hypodermic.memory import Region, maps + + +class Process(object): + """Process attached via ptrace. + + Note: + The process is implicitly detached from upon destruction of this + object, if appropriate. + + Args: + pid (:obj:`int`, optional): The pid of the process to attach to. + Defaults to 0, which means that the argument will not be + used. + path (:obj:`str`, optional): The path of the binary to run. + Defaults to "", which will as the target if a pid is not + specified, either. + + Raises: + TypeError: If the pid argument is not an int, or if the path + argument is not a string. + OSError: If the pid cannot be attached to, if the process could + not be created for the given binary, or if any wrapper + libraries could not be loaded. + """ + + def __init__(self, pid=0, path=""): + if not isinstance(pid, int): + raise TypeError("pid argument must be an int") + elif not isinstance(path, str): + raise TypeError("path argument must be a string") + self._load_ffi_methods() + + if pid != 0: + self._is_parent = False + if self._attach(ctypes.c_int(pid)): + raise OSError("Could not attach to pid {}".format(pid)) + else: + self._is_parent = True + self.pid = self._new_proc(ctypes.c_char_p(path.encode())) + if self.pid < 0: + raise OSError("Could not create process {}".format(path)) + + def __del__(self): + if hasattr(self, "_is_parent") and not self._is_parent: + self.detach() + + def _load_ffi_methods(self): + # setuptools/cython hack. + script_path = os.path.abspath(os.path.dirname(__file__)) + + for filename in os.listdir(os.path.join(script_path, "..")): + if filename.startswith("libhypodermicw"): + lib_path = os.path.join(script_path, "..", filename) + break + else: + raise OSError("Could not find wrapper library.") + + self._so = ctypes.cdll.LoadLibrary(lib_path) + self._new_proc = self._so.new_proc + self._attach = self._so.attach + self._detach = self._so.detach + self._cont = self._so.cont + + def detach(self): + """Explicitly detaches from the process. + + Raises: + OSError: If the process cannot be detached from. + """ + if not self._is_parent and self._detach(ctypes.c_int(self.pid)): + raise OSError("Could not detach from pid {}".format(self.pid)) + + def cont(self): + """Continues until the program is haulted. + + Raises: + OSError: If the process cannot be continued. + """ + if self._cont(ctypes.c_int(self.pid)): + raise OSError("Could not continue") + + @property + def maps(self) -> list: + """Obtain the process' memory map. + + Returns: + A list of Region objects. + """ + return maps(self.pid) + + @property + def rtld(self) -> Region: + """Obtain the base region of memory for the process' RTLD, if it + exists. + + Returns: + The Region object belonging to the RTLD, or None if no + RTLD was found. + """ + for region in self.maps: + if re.search(r"ld.+\.so", region.path) and region.off == 0: + return region diff --git a/hypodermic/ptrace.py b/hypodermic/ptrace.py deleted file mode 100644 index 714b567..0000000 --- a/hypodermic/ptrace.py +++ /dev/null @@ -1,129 +0,0 @@ -# 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 . - -"""ctypes wrapper for ptrace.""" - -import ctypes -import os.path -import re - -from hypodermic.memory import Region, maps - - -class Process(object): - """Process attached via ptrace. - - Note: - The process is implicitly detached from upon destruction of this - object, if appropriate. - - Args: - pid (:obj:`int`, optional): The pid of the process to attach to. - Defaults to 0, which means that the argument will not be - used. - path (:obj:`str`, optional): The path of the binary to run. - Defaults to "", which will as the target if a pid is not - specified, either. - - Raises: - TypeError: If the pid argument is not an int, or if the path - argument is not a string. - OSError: If the pid cannot be attached to, if the process could - not be created for the given binary, or if any wrapper - libraries could not be loaded. - """ - - def __init__(self, pid=0, path=""): - if not isinstance(pid, int): - raise TypeError("pid argument must be an int") - elif not isinstance(path, str): - raise TypeError("path argument must be a string") - self._load_ffi_methods() - - if pid != 0: - self._is_parent = False - if self._attach(ctypes.c_int(pid)): - raise OSError("Could not attach to pid {}".format(pid)) - else: - self._is_parent = True - self.pid = self._new_proc(ctypes.c_char_p(path.encode())) - if self.pid < 0: - raise OSError("Could not create process {}".format(path)) - - def __del__(self): - if hasattr(self, "_is_parent") and not self._is_parent: - self.detach() - - def _load_ffi_methods(self): - # setuptools/cython hack. - script_path = os.path.abspath(os.path.dirname(__file__)) - - for filename in os.listdir(os.path.join(script_path, "..")): - if filename.startswith("libhypodermicw"): - lib_path = os.path.join(script_path, "..", filename) - break - else: - raise OSError("Could not find wrapper library.") - - self._so = ctypes.cdll.LoadLibrary(lib_path) - self._new_proc = self._so.new_proc - self._attach = self._so.attach - self._detach = self._so.detach - self._cont = self._so.cont - - def detach(self): - """Explicitly detaches from the process. - - Raises: - OSError: If the process cannot be detached from. - """ - if not self._is_parent and self._detach(ctypes.c_int(self.pid)): - raise OSError("Could not detach from pid {}".format(self.pid)) - - def cont(self): - """Continues until the program is haulted. - - Raises: - OSError: If the process cannot be continued. - """ - if self._cont(ctypes.c_int(self.pid)): - raise OSError("Could not continue") - - @property - def maps(self) -> list: - """Obtain the process' memory map. - - Returns: - A list of Region objects. - """ - return maps(self.pid) - - # FIXME: This approach does not work outside of seeing if the - # process has an RTLD page. The reality is that the RTLD is - # broken up into independent several pages. - @property - def rtld(self) -> Region: - """Obtain the region of memory for the process' RTLD, if it - exists. - - Returns: - The Region object belonging to the RTLD, or None if no - RTLD was found. - """ - for region in self.maps: - if re.search(r"ld.+\.so", region.path): - return region -- cgit v1.3