def __on_enoexec(self, exception): """We received an 'exec format' error (ENOEXEC) This implies that the user tried to execute e.g. an ARM binary on a non-ARM system, and does not have binfmt helpers installed for QEMU. """ # Get the ELF binary for the target executable with context.quiet: # XXX: Cyclic imports :( from pwnlib.elf import ELF binary = ELF(self.executable) # If we're on macOS, this will never work. Bail now. # if platform.mac_ver()[0]: # self.error("Cannot run ELF binaries on macOS") # Determine what architecture the binary is, and find the # appropriate qemu binary to run it. qemu_path = qemu.user_path(arch=binary.arch) if not qemu_path: raise exception qemu_path = which(qemu_path) if qemu_path: self._qemu = qemu_path args = [qemu_path] if self.argv: args += ['-0', self.argv[0]] args += ['--'] return [args, qemu_path] # If we get here, we couldn't run the binary directly, and # we don't have a qemu which can run it. self.exception(exception)
def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, **kwargs): r""" Launch a GDB server with the specified command line, and launches GDB to attach to it. Arguments: args(list): Arguments to the process, similar to :class:`.process`. gdbscript(str): GDB script to run. exe(str): Path to the executable on disk env(dict): Environment to start the binary in ssh(:class:`.ssh`): Remote ssh session to use to launch the process. sysroot(str): Foreign-architecture sysroot, used for QEMU-emulated binaries and Android targets. Returns: :class:`.process` or :class:`.ssh_channel`: A tube connected to the target process Notes: The debugger is attached automatically, and you can debug everything from the very beginning. This requires that both ``gdb`` and ``gdbserver`` are installed on your machine. When GDB opens via :func:`debug`, it will initially be stopped on the very first instruction of the dynamic linker (``ld.so``) for dynamically-linked binaries. Only the target binary and the linker will be loaded in memory, so you cannot set breakpoints on shared library routines like ``malloc`` since ``libc.so`` has not even been loaded yet. There are several ways to handle this: 1. Set a breakpoint on the executable's entry point (generally, ``_start``) - This is only invoked after all of the required shared libraries are loaded. - You can generally get the address via the GDB command ``info file``. 2. Use pending breakpoints via ``set breakpoint pending on`` - This has the side-effect of setting breakpoints for **every** function which matches the name. For ``malloc``, this will generally set a breakpoint in the executable's PLT, in the linker's internal ``malloc``, and eventaully in ``libc``'s malloc. 3. Wait for libraries to be loaded with ``set stop-on-solib-event 1`` - There is no way to stop on any specific library being loaded, and sometimes multiple libraries are loaded and only a single breakpoint is issued. - Generally, you just add a few ``continue`` commands until things are set up the way you want it to be. Examples: >>> # Create a new process, and stop it at 'main' >>> io = gdb.debug('bash', ''' ... break main ... continue ... ''') >>> # Send a command to Bash >>> io.sendline("echo hello") >>> io.recvline() b'hello\n' >>> # Interact with the process >>> io.interactive() # doctest: +SKIP >>> io.close() >>> # Create a new process, and stop it at '_start' >>> io = gdb.debug('bash', ''' ... # Wait until we hit the main executable's entry point ... break _start ... continue ... ... # Now set breakpoint on shared library routines ... break malloc ... break free ... continue ... ''') >>> # Send a command to Bash >>> io.sendline("echo hello") >>> io.recvline() b'hello\n' >>> # Interact with the process >>> io.interactive() # doctest: +SKIP >>> io.close() You can use :func:`debug` to spawn new processes on remote machines as well, by using the ``ssh=`` keyword to pass in your :class:`.ssh` instance. >>> # Connect to the SSH server >>> # Start a process on the server >>> shell = ssh('travis', 'example.pwnme', password='******') >>> io = gdb.debug(['bash'], ... ssh = shell, ... gdbscript = ''' ... break main ... continue ... ''') >>> # Send a command to Bash >>> io.sendline("echo hello") >>> # Interact with the process >>> io.interactive() # doctest: +SKIP >>> io.close() """ if isinstance( args, six.integer_types + (tubes.process.process, tubes.ssh.ssh_channel)): log.error("Use gdb.attach() to debug a running process") if isinstance(args, (bytes, six.text_type)): args = [args] orig_args = args runner = _get_runner(ssh) which = _get_which(ssh) gdbscript = gdbscript or '' if context.noptrace: log.warn_once("Skipping debugger since context.noptrace==True") return runner(args, executable=exe, env=env) if ssh or context.native or (context.os == 'android'): args = _gdbserver_args(args=args, which=which, env=env) else: qemu_port = random.randint(1024, 65535) qemu_user = qemu.user_path() sysroot = sysroot or qemu.ld_prefix(env=env) if not qemu_user: log.error( "Cannot debug %s binaries without appropriate QEMU binaries" % context.arch) qemu_args = [qemu_user, '-g', str(qemu_port)] if sysroot: qemu_args += ['-L', sysroot] args = qemu_args + args # Use a sane default sysroot for Android if not sysroot and context.os == 'android': sysroot = 'remote:/' # Make sure gdbserver/qemu is installed if not which(args[0]): log.error("%s is not installed" % args[0]) if not ssh: exe = exe or which(orig_args[0]) if not (exe and os.path.exists(exe)): log.error("%s does not exist" % exe) # Start gdbserver/qemu # (Note: We override ASLR here for the gdbserver process itself.) gdbserver = runner(args, env=env, aslr=1, **kwargs) # Set the .executable on the process object. gdbserver.executable = exe # Find what port we need to connect to if context.native or (context.os == 'android'): port = _gdbserver_port(gdbserver, ssh) else: port = qemu_port host = '127.0.0.1' if not ssh and context.os == 'android': host = context.adb_host attach((host, port), exe=exe, gdbscript=gdbscript, ssh=ssh, sysroot=sysroot) # gdbserver outputs a message when a client connects garbage = gdbserver.recvline(timeout=1) # Some versions of gdbserver output an additional message garbage2 = gdbserver.recvline_startswith(b"Remote debugging from host ", timeout=1) return gdbserver
def debug(args, gdbscript=None, exe=None, ssh=None, env=None, **kwargs): """debug(args) -> tube Launch a GDB server with the specified command line, and launches GDB to attach to it. Arguments: args(list): Arguments to the process, similar to :class:`.process`. gdbscript(str): GDB script to run. exe(str): Path to the executable on disk env(dict): Environment to start the binary in ssh(:class:`.ssh`): Remote ssh session to use to launch the process. Returns: :class:`.process` or :class:`.ssh_channel`: A tube connected to the target process Notes: The debugger is attached automatically, and you can debug everything from the very beginning. This requires that both ``gdb`` and ``gdbserver`` are installed on your machine. When GDB opens via :func:`debug`, it will initially be stopped on the very first instruction of the dynamic linker (``ld.so``) for dynamically-linked binaries. Only the target binary and the linker will be loaded in memory, so you cannot set breakpoints on shared library routines like ``malloc`` since ``libc.so`` has not even been loaded yet. There are several ways to handle this: 1. Set a breakpoint on the executable's entry point (generally, ``_start``) - This is only invoked after all of the required shared libraries are loaded. - You can generally get the address via the GDB command ``info file``. 2. Use pending breakpoints via ``set breakpoint pending on`` - This has the side-effect of setting breakpoints for **every** function which matches the name. For ``malloc``, this will generally set a breakpoint in the executable's PLT, in the linker's internal ``malloc``, and eventaully in ``libc``'s malloc. 3. Wait for libraries to be loaded with ``set stop-on-solib-event 1`` - There is no way to stop on any specific library being loaded, and sometimes multiple libraries are loaded and only a single breakpoint is issued. - Generally, you just add a few ``continue`` commands until things are set up the way you want it to be. Examples: .. code-block:: python # Create a new process, and stop it at 'main' io = gdb.debug('bash', ''' break main continue ''') # Send a command to Bash io.sendline("echo hello") # Interact with the process io.interactive() .. code-block:: python # Create a new process, and stop it at 'main' io = gdb.debug('bash', ''' # Wait until we hit the main executable's entry point break _start continue # Now set breakpoint on shared library routines break malloc break free continue ''') # Send a command to Bash io.sendline("echo hello") # Interact with the process io.interactive() You can use :func:`debug` to spawn new processes on remote machines as well, by using the ``ssh=`` keyword to pass in your :class:`.ssh` instance. .. code-block:: python # Connect to the SSH server shell = ssh('passcode', 'pwnable.kr', 2222, password='******') # Start a process on the server io = gdb.debug(['bash'], ssh=shell, gdbscript=''' break main continue ''') # Send a command to Bash io.sendline("echo hello") # Interact with the process io.interactive() """ if isinstance(args, (int, tubes.process.process, tubes.ssh.ssh_channel)): log.error("Use gdb.attach() to debug a running process") if env is None: env = os.environ if isinstance(args, (str, unicode)): args = [args] orig_args = args runner = _get_runner(ssh) which = _get_which(ssh) sysroot = None gdbscript = gdbscript or '' if context.noptrace: log.warn_once("Skipping debugger since context.noptrace==True") return runner(args, executable=exe, env=env) if ssh or context.native or (context.os == 'android'): args = _gdbserver_args(args=args, which=which) else: qemu_port = random.randint(1024, 65535) qemu_user = qemu.user_path() sysroot = qemu.ld_prefix(env) if not qemu_user: log.error( "Cannot debug %s binaries without appropriate QEMU binaries" % context.arch) args = [qemu_user, '-g', str(qemu_port)] + args # Make sure gdbserver/qemu is installed if not which(args[0]): log.error("%s is not installed" % args[0]) exe = exe or which(orig_args[0]) if not exe: log.error("%s does not exist" % orig_args[0]) else: gdbscript = 'file %s\n%s' % (exe, gdbscript) # Start gdbserver/qemu # (Note: We override ASLR here for the gdbserver process itself.) gdbserver = runner(args, env=env, aslr=1, **kwargs) # Set the .executable on the process object. gdbserver.executable = which(orig_args[0]) # Find what port we need to connect to if context.native or (context.os == 'android'): port = _gdbserver_port(gdbserver, ssh) else: port = qemu_port host = '127.0.0.1' if not ssh and context.os == 'android': host = context.adb_host attach((host, port), exe=exe, gdbscript=gdbscript, need_ptrace_scope=False, ssh=ssh, sysroot=sysroot) # gdbserver outputs a message when a client connects garbage = gdbserver.recvline(timeout=1) if "Remote debugging from host" not in garbage: gdbserver.unrecv(garbage) return gdbserver