def sh_prepare(variables, export = False): r"""Outputs a posix compliant shell command that will put the data specified by the dictionary into the environment. It is assumed that the keys in the dictionary are valid variable names that does not need any escaping. Arguments: variables(dict): The variables to set. export(bool): Should the variables be exported or only stored in the shell environment? output(str): A valid posix shell command that will set the given variables. It is assumed that `var` is a valid name for a variable in the shell. Examples: >>> sh_prepare({'X': 'foobar'}) b'X=foobar' >>> r = sh_prepare({'X': 'foobar', 'Y': 'cookies'}) >>> r == b'X=foobar;Y=cookies' or r == b'Y=cookies;X=foobar' or r True >>> sh_prepare({'X': 'foo bar'}) b"X='foo bar'" >>> sh_prepare({'X': "foo'bar"}) b"X='foo'\\''bar'" >>> sh_prepare({'X': "foo\\\\bar"}) b"X='foo\\\\bar'" >>> sh_prepare({'X': "foo\\\\'bar"}) b"X='foo\\\\'\\''bar'" >>> sh_prepare({'X': "foo\\x01'bar"}) b"X='foo\\x01'\\''bar'" >>> sh_prepare({'X': "foo\\x01'bar"}, export = True) b"export X='foo\\x01'\\''bar'" >>> sh_prepare({'X': "foo\\x01'bar\\n"}) b"X='foo\\x01'\\''bar\\n'" >>> sh_prepare({'X': "foo\\x01'bar\\n"}) b"X='foo\\x01'\\''bar\\n'" >>> sh_prepare({'X': "foo\\x01'bar\\n"}, export = True) b"export X='foo\\x01'\\''bar\\n'" """ out = [] export = b'export ' if export else b'' _, variables = normalize_argv_env([], variables, log) for k, v in variables: out.append(b'%s%s=%s' % (export, k, sh_string(v))) return b';'.join(out)
def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=False, **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. api(bool): Enable access to GDB Python API. Returns: :class:`.process` or :class:`.ssh_channel`: A tube connected to the target process. When ``api=True``, ``gdb`` member of the returned object contains a :class:`Gdb` instance. 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(b"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(b"echo hello") >>> io.recvline() b'hello\n' Interact with the process >>> io.interactive() # doctest: +SKIP >>> io.close() Using GDB Python API: .. doctest :skipif: six.PY2 Debug a new process >>> io = gdb.debug(['echo', 'foo'], api=True) Stop at 'write' >>> bp = io.gdb.Breakpoint('write', temporary=True) >>> io.gdb.continue_and_wait() Dump 'count' >>> count = io.gdb.parse_and_eval('$rdx') >>> long = io.gdb.lookup_type('long') >>> int(count.cast(long)) 4 Resume the program >>> io.gdb.continue_nowait() >>> io.recvline() b'foo\n' Using SSH: 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 and start a process on the server >>> shell = ssh('travis', 'example.pwnme', password='******') >>> io = gdb.debug(['whoami'], ... ssh = shell, ... gdbscript = ''' ... break main ... continue ... ''') Send a command to Bash >>> io.sendline(b"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 api and runner is not tubes.process.process: raise ValueError( 'GDB Python API is supported only for local processes') args, env = misc.normalize_argv_env(args, env, log) if env: env = {bytes(k): bytes(v) for k, v in env} 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) if context.os == 'baremetal': qemu_args = [qemu_user, '-S', '-gdb', 'tcp::' + str(qemu_port)] else: 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 tmp = attach((host, port), exe=exe, gdbscript=gdbscript, ssh=ssh, sysroot=sysroot, api=api) if api: _, gdb = tmp gdbserver.gdb = gdb # 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=2) return gdbserver
def _validate(self, cwd, executable, argv, env): """ Perform extended validation on the executable path, argv, and envp. Mostly to make Python happy, but also to prevent common pitfalls. """ orig_cwd = cwd cwd = cwd or os.path.curdir argv, env = normalize_argv_env(argv, env, self, 4) if env: env = {bytes(k): bytes(v) for k, v in env} if argv: argv = list(map(bytes, argv)) # # Validate executable # # - Must be an absolute or relative path to the target executable # - If not, attempt to resolve the name in $PATH # if not executable: if not argv: self.error("Must specify argv or executable") executable = argv[0] if not isinstance(executable, str): executable = executable.decode('utf-8') path = env and env.get(b'PATH') if path: path = path.decode() else: path = os.environ.get('PATH') # Do not change absolute paths to binaries if executable.startswith(os.path.sep): pass # If there's no path component, it's in $PATH or relative to the # target directory. # # For example, 'sh' elif os.path.sep not in executable and which(executable, path=path): executable = which(executable, path=path) # Either there is a path component, or the binary is not in $PATH # For example, 'foo/bar' or 'bar' with cwd=='foo' elif os.path.sep not in executable: tmp = executable executable = os.path.join(cwd, executable) self.warn_once( "Could not find executable %r in $PATH, using %r instead" % (tmp, executable)) # There is a path component and user specified a working directory, # it must be relative to that directory. For example, 'bar/baz' with # cwd='foo' or './baz' with cwd='foo/bar' elif orig_cwd: executable = os.path.join(orig_cwd, executable) if not os.path.exists(executable): self.error("%r does not exist" % executable) if not os.path.isfile(executable): self.error("%r is not a file" % executable) if not os.access(executable, os.X_OK): self.error("%r is not marked as executable (+x)" % executable) return executable, argv, env