def process_init(self): context.arch = self.elf.get_machine_arch() context.log_level = self.log if self.mode == 'ida' or self.mode == 'run' or self.mode == 'gdb': if not os.path.exists(self.config['elf']): log.error("ELF file does not exist.") exit(1) if self.mode == 'gdb': self.execute = gdb.debug(['./' + self.config['elf']], self.gdbscript) else: self.execute = process(['./' + self.config['elf']]) if self.mode == 'ida': wait_for_debugger(self.execute.pid) elif self.mode == 'remote': self.server = autopwn.core.classes.Server( self.config['server'], self.config['server_class']) try: self.execute = self.server.connect() except exception.PwnlibException: print("Remote server error.") else: print("Parameter Error.") self.execute = autopwn.ctf.less_tube.add_features(self.execute) return self.execute
def attach(target, gdbscript='', exe=None, gdb_args=None, ssh=None, sysroot=None, r2cmd=None): r""" Start GDB in a new terminal and attach to `target`. Arguments: target: The target to attach to. gdbscript(:obj:`str` or :obj:`file`): GDB script to run after attaching. exe(str): The path of the target binary. arch(str): Architechture of the target binary. If `exe` known GDB will detect the architechture automatically (if it is supported). gdb_args(list): List of additional arguments to pass to GDB. sysroot(str): Foreign-architecture sysroot, used for QEMU-emulated binaries and Android targets. Returns: PID of the GDB process (or the window which it is running in). Notes: The ``target`` argument is very robust, and can be any of the following: :obj:`int` PID of a process :obj:`str` Process name. The youngest process is selected. :obj:`tuple` Host, port pair of a listening ``gdbserver`` :class:`.process` Process to connect to :class:`.sock` Connected socket. The executable on the other end of the connection is attached to. Can be any socket type, including :class:`.listen` or :class:`.remote`. :class:`.ssh_channel` Remote process spawned via :meth:`.ssh.process`. This will use the GDB installed on the remote machine. If a password is required to connect, the ``sshpass`` program must be installed. Examples: Attach to a process by PID >>> pid = gdb.attach(1234) # doctest: +SKIP Attach to the youngest process by name >>> pid = gdb.attach('bash') # doctest: +SKIP Attach a debugger to a :class:`.process` tube and automate interaction >>> io = process('bash') >>> pid = gdb.attach(io, gdbscript=''' ... call puts("Hello from process debugger!") ... detach ... quit ... ''') >>> io.recvline() b'Hello from process debugger!\n' >>> io.sendline('echo Hello from bash && exit') >>> io.recvall() b'Hello from bash\n' Attach to the remote process from a :class:`.remote` or :class:`.listen` tube, as long as it is running on the same machine. >>> server = process(['socat', 'tcp-listen:12345,reuseaddr,fork', 'exec:/bin/bash,nofork']) >>> sleep(1) # Wait for socat to start >>> io = remote('127.0.0.1', 12345) >>> sleep(1) # Wait for process to fork >>> pid = gdb.attach(io, gdbscript=''' ... call puts("Hello from remote debugger!") ... detach ... quit ... ''') >>> io.recvline() b'Hello from remote debugger!\n' >>> io.sendline('echo Hello from bash && exit') >>> io.recvall() b'Hello from bash\n' Attach to processes running on a remote machine via an SSH :class:`.ssh` process >>> shell = ssh('travis', 'example.pwnme', password='******') >>> io = shell.process(['cat']) >>> pid = gdb.attach(io, gdbscript=''' ... call sleep(5) ... call puts("Hello from ssh debugger!") ... detach ... quit ... ''') >>> io.recvline(timeout=5) # doctest: +SKIP b'Hello from ssh debugger!\n' >>> io.sendline('This will be echoed back') >>> io.recvline() b'This will be echoed back\n' >>> io.close() """ if context.noptrace: log.warn_once("Skipping debug attach since context.noptrace==True") return # if gdbscript is a file object, then read it; we probably need to run some # more gdb script anyway if hasattr(gdbscript, 'read'): with gdbscript: gdbscript = gdbscript.read() # enable gdb.attach(p, 'continue') if gdbscript and not gdbscript.endswith('\n'): gdbscript += '\n' # Use a sane default sysroot for Android if not sysroot and context.os == 'android': sysroot = 'remote:/' # gdb script to run before `gdbscript` pre = '' if not context.native: pre += 'set endian %s\n' % context.endian pre += 'set architecture %s\n' % get_gdb_arch() if sysroot: pre += 'set sysroot %s\n' % sysroot if context.os == 'android': pre += 'set gnutarget ' + _bfdname() + '\n' if exe: pre += 'file %s\n' % exe # let's see if we can find a pid to attach to pid = None if isinstance(target, six.integer_types): # target is a pid, easy peasy pid = target elif isinstance(target, str): # pidof picks the youngest process pidof = proc.pidof if context.os == 'android': pidof = adb.pidof pids = list(pidof(target)) if not pids: log.error('No such process: %s' % target) pid = pids[0] log.info('Attaching to youngest process "%s" (PID = %d)' % (target, pid)) elif isinstance(target, tubes.ssh.ssh_channel): if not target.pid: log.error("PID unknown for channel") shell = target.parent tmpfile = shell.mktemp() if six.PY3: tmpfile = tmpfile.decode() gdbscript = 'shell rm %s\n%s' % (tmpfile, gdbscript) shell.upload_data(gdbscript or '', tmpfile) cmd = [ 'ssh', '-C', '-t', '-p', str(shell.port), '-l', shell.user, shell.host ] if shell.password: if not misc.which('sshpass'): log.error("sshpass must be installed to debug ssh processes") cmd = ['sshpass', '-p', shell.password] + cmd if shell.keyfile: cmd += ['-i', shell.keyfile] exefile = target.executable cmd += ['gdb -q %s %s -x "%s"' % (exefile, target.pid, tmpfile)] misc.run_in_new_terminal(' '.join(cmd)) return elif isinstance(target, tubes.sock.sock): pids = proc.pidof(target) if not pids: log.error('Could not find remote process (%s:%d) on this machine' % target.sock.getpeername()) pid = pids[0] # Specifically check for socat, since it has an intermediary process # if you do not specify "nofork" to the EXEC: argument # python(2640)───socat(2642)───socat(2643)───bash(2644) if proc.exe(pid).endswith('/socat') and time.sleep( 0.1) and proc.children(pid): pid = proc.children(pid)[0] # We may attach to the remote process after the fork but before it performs an exec. # If an exe is provided, wait until the process is actually running the expected exe # before we attach the debugger. t = Timeout() with t.countdown(2): while exe and os.realpath( proc.exe(pid)) != os.realpath(exe) and t.timeout: time.sleep(0.1) elif isinstance(target, tubes.process.process): pid = proc.pidof(target)[0] exe = exe or target.executable elif isinstance(target, tuple) and len(target) == 2: host, port = target if context.os != 'android': pre += 'target remote %s:%d\n' % (host, port) else: # Android debugging is done over gdbserver, which can't follow # new inferiors (tldr; follow-fork-mode child) unless it is run # in extended-remote mode. pre += 'target extended-remote %s:%d\n' % (host, port) pre += 'set detach-on-fork off\n' def findexe(): for spid in proc.pidof(target): sexe = proc.exe(spid) name = os.path.basename(sexe) # XXX: parse cmdline if name.startswith('qemu-') or name.startswith('gdbserver'): exe = proc.cmdline(spid)[-1] return os.path.join(proc.cwd(spid), exe) exe = exe or findexe() elif isinstance(target, elf.corefile.Corefile): pre += 'target core %s\n' % target.path else: log.error("don't know how to attach to target: %r" % target) # if we have a pid but no exe, just look it up in /proc/ if pid and not exe: exe_fn = proc.exe if context.os == 'android': exe_fn = adb.proc_exe exe = exe_fn(pid) if not pid and not exe and not ssh: log.error('could not find target process') cmd = binary() cmd = "" if gdb_args: cmd += ' ' cmd += ' '.join(gdb_args) if context.gdbinit: cmd += ' -nh ' # ignore ~/.gdbinit cmd += ' -x %s ' % context.gdbinit # load custom gdbinit cmd += ' -q ' if exe and context.native: if not ssh and not os.path.isfile(exe): log.error('No such file: %s' % exe) cmd += ' "%s"' % exe if pid and not context.os == 'android': cmd += ' %d' % pid if context.os == 'android' and pid: runner = _get_runner() which = _get_which() gdb_cmd = _gdbserver_args(pid=pid, which=which, env=env) gdbserver = runner(gdb_cmd) port = _gdbserver_port(gdbserver, None) host = context.adb_host pre += 'target extended-remote %s:%i\n' % (context.adb_host, port) # gdbserver on Android sets 'detach-on-fork on' which breaks things # when you're trying to debug anything that forks. pre += 'set detach-on-fork off\n' gdbscript = pre + (gdbscript or '') if gdbscript: tmp = tempfile.NamedTemporaryFile(prefix='pwn', suffix='.gdb', delete=False, mode='w+') log.debug('Wrote gdb script to %r\n%s' % (tmp.name, gdbscript)) gdbscript = 'shell rm %s\n%s' % (tmp.name, gdbscript) tmp.write(gdbscript) tmp.close() cmd += ' -x %s' % (tmp.name) #cmd = "r2 -d {}".format(pid) if r2cmd != None: cmd = "r2 -c '{}' -d {}".format(r2cmd, pid) else: cmd = "r2 -d {}".format(pid) print("Command: {}".format(cmd)) log.info('running in new terminal: %s' % cmd) gdb_pid = misc.run_in_new_terminal(cmd) if pid and context.native: proc.wait_for_debugger(pid, gdb_pid) return gdb_pid
def attach(target, gdbscript='', exe=None, gdb_args=None, ssh=None, sysroot=None): r""" Start GDB in a new terminal and attach to `target`. Arguments: target: The target to attach to. gdbscript(:obj:`str` or :obj:`file`): GDB script to run after attaching. exe(str): The path of the target binary. arch(str): Architechture of the target binary. If `exe` known GDB will detect the architechture automatically (if it is supported). gdb_args(list): List of additional arguments to pass to GDB. sysroot(str): Foreign-architecture sysroot, used for QEMU-emulated binaries and Android targets. Returns: PID of the GDB process (or the window which it is running in). Notes: The ``target`` argument is very robust, and can be any of the following: :obj:`int` PID of a process :obj:`str` Process name. The youngest process is selected. :obj:`tuple` Host, port pair of a listening ``gdbserver`` :class:`.process` Process to connect to :class:`.sock` Connected socket. The executable on the other end of the connection is attached to. Can be any socket type, including :class:`.listen` or :class:`.remote`. :class:`.ssh_channel` Remote process spawned via :meth:`.ssh.process`. This will use the GDB installed on the remote machine. If a password is required to connect, the ``sshpass`` program must be installed. Examples: >>> # Attach directly to pid 1234 >>> gdb.attach(1234) # doctest: +SKIP >>> # Attach to the youngest "bash" process >>> gdb.attach('bash') # doctest: +SKIP >>> # Start a process >>> bash = process('bash') >>> # Attach the debugger >>> pid = gdb.attach(bash, ''' ... set follow-fork-mode child ... break execve ... continue ... ''') >>> # Interact with the process >>> bash.sendline("/bin/echo hello") >>> bash.recvline() b'hello\n' >>> bash.close() >>> # Start a forking server >>> server = process(['socat', 'tcp-listen:12345,fork,reuseaddr', 'exec:/bin/bash,nofork']) >>> sleep(1) >>> # Connect to the server >>> io = remote('127.0.0.1', 12345) >>> # Connect the debugger to the server-spawned process >>> pid = gdb.attach(io, ''' ... break exit ... continue ... ''', exe = '/bin/bash') >>> # Talk to the spawned 'bash' >>> io.sendline("echo hello") >>> io.recvline() b'hello\n' >>> io.sendline("exit") >>> io.close() >>> # Connect to the SSH server >>> shell = ssh('travis', 'example.pwnme', password='******') >>> # Start a process on the server >>> cat = shell.process(['cat']) >>> # Attach a debugger to it >>> gdb.attach(cat, ''' ... break exit ... continue ... ''') >>> cat.sendline("hello") >>> cat.recvline() b'hello\n' >>> # Cause `cat` to exit >>> cat.close() """ if context.noptrace: log.warn_once("Skipping debug attach since context.noptrace==True") return # if gdbscript is a file object, then read it; we probably need to run some # more gdb script anyway if hasattr(gdbscript, 'read'): with gdbscript: gdbscript = gdbscript.read() # enable gdb.attach(p, 'continue') if gdbscript and not gdbscript.endswith('\n'): gdbscript += '\n' # Use a sane default sysroot for Android if not sysroot and context.os == 'android': sysroot = 'remote:/' # gdb script to run before `gdbscript` pre = '' if not context.native: pre += 'set endian %s\n' % context.endian pre += 'set architecture %s\n' % get_gdb_arch() if sysroot: pre += 'set sysroot %s\n' % sysroot if context.os == 'android': pre += 'set gnutarget ' + _bfdname() + '\n' # let's see if we can find a pid to attach to pid = None if isinstance(target, six.integer_types): # target is a pid, easy peasy pid = target elif isinstance(target, str): # pidof picks the youngest process pidof = proc.pidof if context.os == 'android': pidof = adb.pidof pids = list(pidof(target)) if not pids: log.error('No such process: %s' % target) pid = pids[0] log.info('Attaching to youngest process "%s" (PID = %d)' % (target, pid)) elif isinstance(target, tubes.ssh.ssh_channel): if not target.pid: log.error("PID unknown for channel") shell = target.parent tmpfile = shell.mktemp() if six.PY3: tmpfile = tmpfile.decode() gdbscript = 'shell rm %s\n%s' % (tmpfile, gdbscript) shell.upload_data(gdbscript or '', tmpfile) cmd = [ 'ssh', '-C', '-t', '-p', str(shell.port), '-l', shell.user, shell.host ] if shell.password: if not misc.which('sshpass'): log.error("sshpass must be installed to debug ssh processes") cmd = ['sshpass', '-p', shell.password] + cmd if shell.keyfile: cmd += ['-i', shell.keyfile] exefile = target.executable cmd += ['gdb -q %s %s -x "%s"' % (exefile, target.pid, tmpfile)] misc.run_in_new_terminal(' '.join(cmd)) return elif isinstance(target, tubes.sock.sock): pids = proc.pidof(target) if not pids: log.error('could not find remote process (%s:%d) on this machine' % target.sock.getpeername()) waiting = 100 if exe: while waiting: waiting -= 1 for pid in pids: if proc.exe(pid) == exe: waiting = False break else: time.sleep(0.01) elif isinstance(target, tubes.process.process): pid = proc.pidof(target)[0] exe = exe or target.executable elif isinstance(target, tuple) and len(target) == 2: host, port = target if context.os != 'android': pre += 'target remote %s:%d\n' % (host, port) else: # Android debugging is done over gdbserver, which can't follow # new inferiors (tldr; follow-fork-mode child) unless it is run # in extended-remote mode. pre += 'target extended-remote %s:%d\n' % (host, port) pre += 'set detach-on-fork off\n' def findexe(): for spid in proc.pidof(target): sexe = proc.exe(spid) name = os.path.basename(sexe) # XXX: parse cmdline if name.startswith('qemu-') or name.startswith('gdbserver'): exe = proc.cmdline(spid)[-1] return os.path.join(proc.cwd(spid), exe) exe = exe or findexe() elif isinstance(target, elf.corefile.Corefile): pre += 'target core %s\n' % target.path else: log.error("don't know how to attach to target: %r" % target) # if we have a pid but no exe, just look it up in /proc/ if pid and not exe: exe_fn = proc.exe if context.os == 'android': exe_fn = adb.proc_exe exe = exe_fn(pid) if not pid and not exe and not ssh: log.error('could not find target process') cmd = binary() if gdb_args: cmd += ' ' cmd += ' '.join(gdb_args) if context.gdbinit: cmd += ' -nh ' # ignore ~/.gdbinit cmd += ' -x %s ' % context.gdbinit # load custom gdbinit cmd += ' -q ' if exe and context.native: if not ssh and not os.path.isfile(exe): log.error('No such file: %s' % exe) cmd += ' "%s"' % exe if pid and not context.os == 'android': cmd += ' %d' % pid if context.os == 'android' and pid: runner = _get_runner() which = _get_which() gdb_cmd = _gdbserver_args(pid=pid, which=which, env=env) gdbserver = runner(gdb_cmd) port = _gdbserver_port(gdbserver, None) host = context.adb_host pre += 'target extended-remote %s:%i\n' % (context.adb_host, port) # gdbserver on Android sets 'detach-on-fork on' which breaks things # when you're trying to debug anything that forks. pre += 'set detach-on-fork off\n' gdbscript = pre + (gdbscript or '') if gdbscript: tmp = tempfile.NamedTemporaryFile(prefix='pwn', suffix='.gdb', delete=False, mode='w+') log.debug('Wrote gdb script to %r\n%s' % (tmp.name, gdbscript)) gdbscript = 'shell rm %s\n%s' % (tmp.name, gdbscript) tmp.write(gdbscript) tmp.close() cmd += ' -x %s' % (tmp.name) log.info('running in new terminal: %s' % cmd) gdb_pid = misc.run_in_new_terminal(cmd) if pid and context.native: proc.wait_for_debugger(pid, gdb_pid) return gdb_pid
def attach(target, gdbscript='', exe=None, gdb_args=None, ssh=None, sysroot=None, api=False): r""" Start GDB in a new terminal and attach to `target`. Arguments: target: The target to attach to. gdbscript(:obj:`str` or :obj:`file`): GDB script to run after attaching. exe(str): The path of the target binary. arch(str): Architechture of the target binary. If `exe` known GDB will detect the architechture automatically (if it is supported). gdb_args(list): List of additional arguments to pass to GDB. sysroot(str): Foreign-architecture sysroot, used for QEMU-emulated binaries and Android targets. api(bool): Enable access to GDB Python API. Returns: PID of the GDB process (or the window which it is running in). When ``api=True``, a (PID, :class:`Gdb`) tuple. Notes: The ``target`` argument is very robust, and can be any of the following: :obj:`int` PID of a process :obj:`str` Process name. The youngest process is selected. :obj:`tuple` Host, port pair of a listening ``gdbserver`` :class:`.process` Process to connect to :class:`.sock` Connected socket. The executable on the other end of the connection is attached to. Can be any socket type, including :class:`.listen` or :class:`.remote`. :class:`.ssh_channel` Remote process spawned via :meth:`.ssh.process`. This will use the GDB installed on the remote machine. If a password is required to connect, the ``sshpass`` program must be installed. Examples: Attach to a process by PID >>> pid = gdb.attach(1234) # doctest: +SKIP Attach to the youngest process by name >>> pid = gdb.attach('bash') # doctest: +SKIP Attach a debugger to a :class:`.process` tube and automate interaction >>> io = process('bash') >>> pid = gdb.attach(io, gdbscript=''' ... call puts("Hello from process debugger!") ... detach ... quit ... ''') >>> io.recvline() b'Hello from process debugger!\n' >>> io.sendline(b'echo Hello from bash && exit') >>> io.recvall() b'Hello from bash\n' Using GDB Python API: .. doctest :skipif: six.PY2 >>> io = process('bash') Attach a debugger >>> pid, io_gdb = gdb.attach(io, api=True) Force the program to write something it normally wouldn't >>> io_gdb.execute('call puts("Hello from process debugger!")') Resume the program >>> io_gdb.continue_nowait() Observe the forced line >>> io.recvline() b'Hello from process debugger!\n' Interact with the program in a regular way >>> io.sendline(b'echo Hello from bash && exit') Observe the results >>> io.recvall() b'Hello from bash\n' Attach to the remote process from a :class:`.remote` or :class:`.listen` tube, as long as it is running on the same machine. >>> server = process(['socat', 'tcp-listen:12345,reuseaddr,fork', 'exec:/bin/bash,nofork']) >>> sleep(1) # Wait for socat to start >>> io = remote('127.0.0.1', 12345) >>> sleep(1) # Wait for process to fork >>> pid = gdb.attach(io, gdbscript=''' ... call puts("Hello from remote debugger!") ... detach ... quit ... ''') >>> io.recvline() b'Hello from remote debugger!\n' >>> io.sendline(b'echo Hello from bash && exit') >>> io.recvall() b'Hello from bash\n' Attach to processes running on a remote machine via an SSH :class:`.ssh` process >>> shell = ssh('travis', 'example.pwnme', password='******') >>> io = shell.process(['cat']) >>> pid = gdb.attach(io, gdbscript=''' ... call sleep(5) ... call puts("Hello from ssh debugger!") ... detach ... quit ... ''') >>> io.recvline(timeout=5) # doctest: +SKIP b'Hello from ssh debugger!\n' >>> io.sendline(b'This will be echoed back') >>> io.recvline() b'This will be echoed back\n' >>> io.close() """ if context.noptrace: log.warn_once("Skipping debug attach since context.noptrace==True") return # if gdbscript is a file object, then read it; we probably need to run some # more gdb script anyway if hasattr(gdbscript, 'read'): with gdbscript: gdbscript = gdbscript.read() # enable gdb.attach(p, 'continue') if gdbscript and not gdbscript.endswith('\n'): gdbscript += '\n' # Use a sane default sysroot for Android if not sysroot and context.os == 'android': sysroot = 'remote:/' # gdb script to run before `gdbscript` pre = '' if not context.native: pre += 'set endian %s\n' % context.endian pre += 'set architecture %s\n' % get_gdb_arch() if sysroot: pre += 'set sysroot %s\n' % sysroot if context.os == 'android': pre += 'set gnutarget ' + _bfdname() + '\n' if exe and context.os != 'baremetal': pre += 'file "%s"\n' % exe # let's see if we can find a pid to attach to pid = None if isinstance(target, six.integer_types): # target is a pid, easy peasy pid = target elif isinstance(target, str): # pidof picks the youngest process pidof = proc.pidof if context.os == 'android': pidof = adb.pidof pids = list(pidof(target)) if not pids: log.error('No such process: %s', target) pid = pids[0] log.info('Attaching to youngest process "%s" (PID = %d)' % (target, pid)) elif isinstance(target, tubes.ssh.ssh_channel): if not target.pid: log.error("PID unknown for channel") shell = target.parent tmpfile = shell.mktemp() gdbscript = b'shell rm %s\n%s' % ( tmpfile, packing._need_bytes(gdbscript, 2, 0x80)) shell.upload_data(gdbscript or b'', tmpfile) cmd = [ 'ssh', '-C', '-t', '-p', str(shell.port), '-l', shell.user, shell.host ] if shell.password: if not misc.which('sshpass'): log.error("sshpass must be installed to debug ssh processes") cmd = ['sshpass', '-p', shell.password] + cmd if shell.keyfile: cmd += ['-i', shell.keyfile] cmd += ['gdb', '-q', target.executable, str(target.pid), '-x', tmpfile] misc.run_in_new_terminal(cmd) return elif isinstance(target, tubes.sock.sock): pids = proc.pidof(target) if not pids: log.error('Could not find remote process (%s:%d) on this machine' % target.sock.getpeername()) pid = pids[0] # Specifically check for socat, since it has an intermediary process # if you do not specify "nofork" to the EXEC: argument # python(2640)───socat(2642)───socat(2643)───bash(2644) if proc.exe(pid).endswith('/socat') and time.sleep( 0.1) and proc.children(pid): pid = proc.children(pid)[0] # We may attach to the remote process after the fork but before it performs an exec. # If an exe is provided, wait until the process is actually running the expected exe # before we attach the debugger. t = Timeout() with t.countdown(2): while exe and os.path.realpath( proc.exe(pid)) != os.path.realpath(exe) and t.timeout: time.sleep(0.1) elif isinstance(target, tubes.process.process): pid = proc.pidof(target)[0] exe = exe or target.executable elif isinstance(target, tuple) and len(target) == 2: host, port = target if context.os != 'android': pre += 'target remote %s:%d\n' % (host, port) else: # Android debugging is done over gdbserver, which can't follow # new inferiors (tldr; follow-fork-mode child) unless it is run # in extended-remote mode. pre += 'target extended-remote %s:%d\n' % (host, port) pre += 'set detach-on-fork off\n' def findexe(): for spid in proc.pidof(target): sexe = proc.exe(spid) name = os.path.basename(sexe) # XXX: parse cmdline if name.startswith('qemu-') or name.startswith('gdbserver'): exe = proc.cmdline(spid)[-1] return os.path.join(proc.cwd(spid), exe) exe = exe or findexe() elif isinstance(target, elf.corefile.Corefile): pre += 'target core "%s"\n' % target.path else: log.error("don't know how to attach to target: %r", target) # if we have a pid but no exe, just look it up in /proc/ if pid and not exe: exe_fn = proc.exe if context.os == 'android': exe_fn = adb.proc_exe exe = exe_fn(pid) if not pid and not exe and not ssh: log.error('could not find target process') gdb_binary = binary() cmd = [gdb_binary] if gdb_args: cmd += gdb_args if context.gdbinit: cmd += ['-nh'] # ignore ~/.gdbinit cmd += ['-x', context.gdbinit] # load custom gdbinit cmd += ['-q'] if exe and context.native: if not ssh and not os.path.isfile(exe): log.error('No such file: %s', exe) cmd += [exe] if pid and not context.os == 'android': cmd += [str(pid)] if context.os == 'android' and pid: runner = _get_runner() which = _get_which() gdb_cmd = _gdbserver_args(pid=pid, which=which) gdbserver = runner(gdb_cmd) port = _gdbserver_port(gdbserver, None) host = context.adb_host pre += 'target extended-remote %s:%i\n' % (context.adb_host, port) # gdbserver on Android sets 'detach-on-fork on' which breaks things # when you're trying to debug anything that forks. pre += 'set detach-on-fork off\n' if api: # create a UNIX socket for talking to GDB socket_dir = tempfile.mkdtemp() socket_path = os.path.join(socket_dir, 'socket') bridge = os.path.join(os.path.dirname(__file__), 'gdb_api_bridge.py') # inject the socket path and the GDB Python API bridge pre = 'python socket_path = ' + repr(socket_path) + '\n' + \ 'source ' + bridge + '\n' + \ pre gdbscript = pre + (gdbscript or '') if gdbscript: tmp = tempfile.NamedTemporaryFile(prefix='pwn', suffix='.gdb', delete=False, mode='w+') log.debug('Wrote gdb script to %r\n%s', tmp.name, gdbscript) gdbscript = 'shell rm %s\n%s' % (tmp.name, gdbscript) tmp.write(gdbscript) tmp.close() cmd += ['-x', tmp.name] log.info('running in new terminal: %s', cmd) if api: # prevent gdb_faketerminal.py from messing up api doctests def preexec_fn(): os.environ['GDB_FAKETERMINAL'] = '0' else: preexec_fn = None gdb_pid = misc.run_in_new_terminal(cmd, preexec_fn=preexec_fn) if pid and context.native: proc.wait_for_debugger(pid, gdb_pid) if not api: return gdb_pid # connect to the GDB Python API bridge from rpyc import BgServingThread from rpyc.utils.factory import unix_connect if six.PY2: retriable = socket.error else: retriable = ConnectionRefusedError, FileNotFoundError t = Timeout() with t.countdown(10): while t.timeout: try: conn = unix_connect(socket_path) break except retriable: time.sleep(0.1) else: # Check to see if RPyC is installed at all in GDB rpyc_check = [ gdb_binary, '--nx', '-batch', '-ex', 'python import rpyc; import sys; sys.exit(123)' ] if 123 != tubes.process.process(rpyc_check).poll(block=True): log.error('Failed to connect to GDB: rpyc is not installed') # Check to see if the socket ever got created if not os.path.exists(socket_path): log.error( 'Failed to connect to GDB: Unix socket %s was never created', socket_path) # Check to see if the remote RPyC client is a compatible version version_check = [ gdb_binary, '--nx', '-batch', '-ex', 'python import platform; print(platform.python_version())' ] gdb_python_version = tubes.process.process( version_check).recvall().strip() python_version = str(platform.python_version()) if gdb_python_version != python_version: log.error( 'Failed to connect to GDB: Version mismatch (%s vs %s)', gdb_python_version, python_version) # Don't know what happened log.error('Failed to connect to GDB: Unknown error') # now that connection is up, remove the socket from the filesystem os.unlink(socket_path) os.rmdir(socket_dir) # create a thread for receiving breakpoint notifications BgServingThread(conn, callback=lambda: None) return gdb_pid, Gdb(conn)
def attach(target, gdbscript=None, exe=None, need_ptrace_scope=True, gdb_args=None, ssh=None): """attach(target, gdbscript = None, exe = None, arch = None, ssh = None) -> None Start GDB in a new terminal and attach to `target`. Arguments: target: The target to attach to. gdbscript(:obj:`str` or :obj:`file`): GDB script to run after attaching. exe(str): The path of the target binary. arch(str): Architechture of the target binary. If `exe` known GDB will detect the architechture automatically (if it is supported). gdb_args(list): List of additional arguments to pass to GDB. Returns: PID of the GDB process (or the window which it is running in). Notes: The ``target`` argument is very robust, and can be any of the following: :obj:`int` PID of a process :obj:`str` Process name. The youngest process is selected. :obj:`tuple` Host, port pair of a listening ``gdbserver`` :class:`.process` Process to connect to :class:`.sock` Connected socket. The executable on the other end of the connection is attached to. Can be any socket type, including :class:`.listen` or :class:`.remote`. :class:`.ssh_channel` Remote process spawned via :meth:`.ssh.process`. This will use the GDB installed on the remote machine. If a password is required to connect, the ``sshpass`` program must be installed. .. code-block:: python # Attach directly to pid 1234 gdb.attach(1234) .. code-block:: python # Attach to the youngest "bash" process gdb.attach('bash') .. code-block:: python # Start a process bash = process('bash') # Attach the debugger gdb.attach(bash, ''' set follow-fork-mode child break execve continue ''') # Interact with the process bash.sendline('whoami') .. code-block:: python # Start a forking server server = process(['socat', 'tcp-listen:1234,fork,reuseaddr', 'exec:/bin/sh']) # Connect to the server io = remote('localhost', 1234) # Connect the debugger to the server-spawned process gdb.attach(io, ''' break exit continue ''') # Talk to the spawned 'sh' io.sendline('exit') .. code-block:: python # Connect to the SSH server shell = ssh('bandit0', 'bandit.labs.overthewire.org', password='******', port=2220) # Start a process on the server cat = shell.process(['cat']) # Attach a debugger to it gdb.attach(cat, ''' break exit continue ''') # Cause `cat` to exit cat.close() """ if context.noptrace: log.warn_once("Skipping debug attach since context.noptrace==True") return # if gdbscript is a file object, then read it; we probably need to run some # more gdb script anyway if isinstance(gdbscript, file): with gdbscript: gdbscript = gdbscript.read() # enable gdb.attach(p, 'continue') if gdbscript and not gdbscript.endswith('\n'): gdbscript += '\n' # gdb script to run before `gdbscript` pre = '' if not context.native: pre += 'set endian %s\n' % context.endian pre += 'set architecture %s\n' % get_gdb_arch() if context.os == 'android': pre += 'set gnutarget ' + _bfdname() + '\n' # let's see if we can find a pid to attach to pid = None if isinstance(target, (int, long)): # target is a pid, easy peasy pid = target elif isinstance(target, str): # pidof picks the youngest process pidof = proc.pidof if context.os == 'android': pidof = adb.pidof pids = pidof(target) if not pids: log.error('No such process: %s' % target) pid = pids[0] log.info('Attaching to youngest process "%s" (PID = %d)' % (target, pid)) elif isinstance(target, tubes.ssh.ssh_channel): if not target.pid: log.error("PID unknown for channel") shell = target.parent tmpfile = shell.mktemp() gdbscript = 'shell rm %s\n%s' % (tmpfile, gdbscript) shell.upload_data(gdbscript or '', tmpfile) cmd = [ 'ssh', '-C', '-t', '-p', str(shell.port), '-l', shell.user, shell.host ] if shell.password: if not misc.which('sshpass'): log.error("sshpass must be installed to debug ssh processes") cmd = ['sshpass', '-p', shell.password] + cmd if shell.keyfile: cmd += ['-i', shell.keyfile] cmd += [ 'gdb -q %r %s -x "%s"' % (target.executable, target.pid, tmpfile) ] misc.run_in_new_terminal(' '.join(cmd)) return elif isinstance(target, tubes.sock.sock): pids = proc.pidof(target) if not pids: log.error('could not find remote process (%s:%d) on this machine' % target.sock.getpeername()) pid = pids[0] elif isinstance(target, tubes.process.process): pid = proc.pidof(target)[0] elif isinstance(target, tuple) and len(target) == 2: host, port = target pre += 'target remote %s:%d\n' % (host, port) def findexe(): for spid in proc.pidof(target): sexe = proc.exe(spid) name = os.path.basename(sexe) # XXX: parse cmdline if name.startswith('qemu-') or name.startswith('gdbserver'): exe = proc.cmdline(spid)[-1] return os.path.join(proc.cwd(spid), exe) exe = exe or findexe() elif isinstance(target, elf.corefile.Corefile): pre += 'target core %s\n' % target.path else: log.error("don't know how to attach to target: %r" % target) # if we have a pid but no exe, just look it up in /proc/ if pid and not exe: exe_fn = proc.exe if context.os == 'android': exe_fn = adb.proc_exe exe = exe_fn(pid) if not pid and not exe: log.error('could not find target process') cmd = binary() if gdb_args: cmd += ' ' cmd += ' '.join(gdb_args) if context.gdbinit: cmd += ' -nh ' # ignore ~/.gdbinit cmd += ' -x %s ' % context.gdbinit # load custom gdbinit cmd += ' -q ' if exe and context.native: if ssh: ssh.download_file(exe) exe = os.path.basename(exe) if not os.path.isfile(exe): log.error('No such file: %s' % exe) cmd += ' "%s"' % exe if pid and not context.os == 'android': cmd += ' %d' % pid if context.os == 'android' and pid: runner = _get_runner() which = _get_which() gdb_cmd = _gdbserver_args(pid=pid, which=which) gdbserver = runner(gdb_cmd) port = _gdbserver_port(gdbserver, None) host = context.adb_host pre += 'target remote %s:%i' % (context.adb_host, port) gdbscript = pre + (gdbscript or '') if gdbscript: tmp = tempfile.NamedTemporaryFile(prefix='pwn', suffix='.gdb', delete=False) log.debug('Wrote gdb script to %r\n%s' % (tmp.name, gdbscript)) gdbscript = 'shell rm %s\n%s' % (tmp.name, gdbscript) tmp.write(gdbscript) tmp.close() cmd += ' -x "%s"' % (tmp.name) log.info('running in new terminal: %s' % cmd) gdb_pid = misc.run_in_new_terminal(cmd) if pid and context.native: proc.wait_for_debugger(pid) return gdb_pid
def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_args = None, ssh = None): """attach(target, gdbscript = None, exe = None, arch = None, ssh = None) -> None Start GDB in a new terminal and attach to `target`. Arguments: target: The target to attach to. gdbscript(:obj:`str` or :obj:`file`): GDB script to run after attaching. exe(str): The path of the target binary. arch(str): Architechture of the target binary. If `exe` known GDB will detect the architechture automatically (if it is supported). gdb_args(list): List of additional arguments to pass to GDB. Returns: PID of the GDB process (or the window which it is running in). Notes: The ``target`` argument is very robust, and can be any of the following: :obj:`int` PID of a process :obj:`str` Process name. The youngest process is selected. :obj:`tuple` Host, port pair of a listening ``gdbserver`` :class:`.process` Process to connect to :class:`.sock` Connected socket. The executable on the other end of the connection is attached to. Can be any socket type, including :class:`.listen` or :class:`.remote`. :class:`.ssh_channel` Remote process spawned via :meth:`.ssh.process`. This will use the GDB installed on the remote machine. If a password is required to connect, the ``sshpass`` program must be installed. .. code-block:: python # Attach directly to pid 1234 gdb.attach(1234) .. code-block:: python # Attach to the youngest "bash" process gdb.attach('bash') .. code-block:: python # Start a process bash = process('bash') # Attach the debugger gdb.attach(bash, ''' set follow-fork-mode child break execve continue ''') # Interact with the process bash.sendline('whoami') .. code-block:: python # Start a forking server server = process(['socat', 'tcp-listen:1234,fork,reuseaddr', 'exec:/bin/sh']) # Connect to the server io = remote('localhost', 1234) # Connect the debugger to the server-spawned process gdb.attach(io, ''' break exit continue ''') # Talk to the spawned 'sh' io.sendline('exit') .. code-block:: python # Connect to the SSH server shell = ssh('bandit0', 'bandit.labs.overthewire.org', password='******', port=2220) # Start a process on the server cat = shell.process(['cat']) # Attach a debugger to it gdb.attach(cat, ''' break exit continue ''') # Cause `cat` to exit cat.close() """ if context.noptrace: log.warn_once("Skipping debug attach since context.noptrace==True") return # if gdbscript is a file object, then read it; we probably need to run some # more gdb script anyway if isinstance(gdbscript, file): with gdbscript: gdbscript = gdbscript.read() # enable gdb.attach(p, 'continue') if gdbscript and not gdbscript.endswith('\n'): gdbscript += '\n' # gdb script to run before `gdbscript` pre = '' if not context.native: pre += 'set endian %s\n' % context.endian pre += 'set architecture %s\n' % get_gdb_arch() if context.os == 'android': pre += 'set gnutarget ' + _bfdname() + '\n' # let's see if we can find a pid to attach to pid = None if isinstance(target, (int, long)): # target is a pid, easy peasy pid = target elif isinstance(target, str): # pidof picks the youngest process pidof = proc.pidof if context.os == 'android': pidof = adb.pidof pids = pidof(target) if not pids: log.error('No such process: %s' % target) pid = pids[0] log.info('Attaching to youngest process "%s" (PID = %d)' % (target, pid)) elif isinstance(target, tubes.ssh.ssh_channel): if not target.pid: log.error("PID unknown for channel") shell = target.parent tmpfile = shell.mktemp() gdbscript = 'shell rm %s\n%s' % (tmpfile, gdbscript) shell.upload_data(gdbscript or '', tmpfile) cmd = ['ssh', '-C', '-t', '-p', str(shell.port), '-l', shell.user, shell.host] if shell.password: if not misc.which('sshpass'): log.error("sshpass must be installed to debug ssh processes") cmd = ['sshpass', '-p', shell.password] + cmd if shell.keyfile: cmd += ['-i', shell.keyfile] cmd += ['gdb -q %r %s -x "%s"' % (target.executable, target.pid, tmpfile)] misc.run_in_new_terminal(' '.join(cmd)) return elif isinstance(target, tubes.sock.sock): pids = proc.pidof(target) if not pids: log.error('could not find remote process (%s:%d) on this machine' % target.sock.getpeername()) pid = pids[0] elif isinstance(target, tubes.process.process): pid = proc.pidof(target)[0] elif isinstance(target, tuple) and len(target) == 2: host, port = target pre += 'target remote %s:%d\n' % (host, port) def findexe(): for spid in proc.pidof(target): sexe = proc.exe(spid) name = os.path.basename(sexe) # XXX: parse cmdline if name.startswith('qemu-') or name.startswith('gdbserver'): exe = proc.cmdline(spid)[-1] return os.path.join(proc.cwd(spid), exe) exe = exe or findexe() elif isinstance(target, elf.corefile.Corefile): pre += 'target core %s\n' % target.path else: log.error("don't know how to attach to target: %r" % target) # if we have a pid but no exe, just look it up in /proc/ if pid and not exe: exe_fn = proc.exe if context.os == 'android': exe_fn = adb.proc_exe exe = exe_fn(pid) if not pid and not exe: log.error('could not find target process') cmd = binary() if gdb_args: cmd += ' ' cmd += ' '.join(gdb_args) cmd += ' -q ' if exe and context.native: if ssh: ssh.download_file(exe) exe = os.path.basename(exe) if not os.path.isfile(exe): log.error('No such file: %s' % exe) cmd += ' "%s"' % exe if pid and not context.os == 'android': cmd += ' %d' % pid if context.os == 'android' and pid: runner = _get_runner() which = _get_which() gdb_cmd = _gdbserver_args(pid=pid, which=which) gdbserver = runner(gdb_cmd) port = _gdbserver_port(gdbserver, None) host = context.adb_host pre += 'target remote %s:%i' % (context.adb_host, port) gdbscript = pre + (gdbscript or '') if gdbscript: tmp = tempfile.NamedTemporaryFile(prefix = 'pwn', suffix = '.gdb', delete = False) log.debug('Wrote gdb script to %r\n%s' % (tmp.name, gdbscript)) gdbscript = 'shell rm %s\n%s' % (tmp.name, gdbscript) tmp.write(gdbscript) tmp.close() cmd += ' -x "%s"' % (tmp.name) log.info('running in new terminal: %s' % cmd) gdb_pid = misc.run_in_new_terminal(cmd) if pid and context.native: proc.wait_for_debugger(pid) return gdb_pid