pwnlib.gdb
— 配合 GDB 一起工作¶
在漏洞利用的编写中, 会非常频繁使用到 GDB 来调试目标二进制程序 Pwntools通过一些帮助例程来实现这一点 这些例程旨在使您的 Exploit 调试/迭代周期更快。
有用的函数¶
attach()
- 附加到一个已存在的进程debug()
- 在调试器下启动一个新进程, 并且停在第一条指令debug_shellcode()
- 通过提供的 shellcode 来构建一个二进制程序, 并且在调试器中启动它
调试小技巧¶
The attach()
and debug()
functions will likely be your bread and
butter for debugging.
Both allow you to provide a script to pass to GDB when it is started, so that it can automatically set your breakpoints.
附加至进程¶
To attach to an existing process, just use attach()
. It is surprisingly
versatile, and can attach to a process
for simple
binaries, or will automatically find the correct process to attach to for a
forking server, if given a remote
object.
产生新进程¶
Attaching to processes with attach()
is useful, but the state the process
is in may vary. If you need to attach to a process very early, and debug it from
the very first instruction (or even the start of main
), you instead should use
debug()
.
When you use debug()
, the return value is a tube
object
that you interact with exactly like normal.
Tips and Troubleshooting¶
NOPTRACE
magic argument¶
It’s quite cumbersom to comment and un-comment lines containing attach.
You can cause these lines to be a no-op by running your script with the
NOPTRACE
argument appended, or with PWNLIB_NOPTRACE=1
in the environment.
$ python exploit.py NOPTRACE
[+] Starting local process '/bin/bash': Done
[!] Skipping debug attach since context.noptrace==True
...
Kernel Yama ptrace_scope¶
The Linux kernel v3.4 introduced a security mechanism called ptrace_scope
,
which is intended to prevent processes from debugging eachother unless there is
a direct parent-child relationship.
This causes some issues with the normal Pwntools workflow, since the process heirarchy looks like this:
python ---> target
`--> gdb
Note that python
is the parent of target
, not gdb
.
In order to avoid this being a problem, Pwntools uses the function
prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY)
. This disables Yama
for any processes launched by Pwntools via process
or via
ssh.process()
.
Older versions of Pwntools did not perform the prctl
step, and
required that the Yama security feature was disabled systemwide, which
requires root
access.
Member Documentation¶
-
pwnlib.gdb.
attach
(target, gdbscript = None, exe = None, arch = None, ssh = None) → None[源代码]¶ Start GDB in a new terminal and attach to target.
参数: - target – The target to attach to.
- gdbscript (
str
orfile
) – 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.
返回: 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:int
- PID of a process
str
- Process name. The youngest process is selected.
tuple
- Host, port pair of a listening
gdbserver
process
- Process to connect to
sock
- Connected socket. The executable on the other end of the connection is attached to.
Can be any socket type, including
listen
orremote
. ssh_channel
- Remote process spawned via
ssh.process()
. This will use the GDB installed on the remote machine. If a password is required to connect, thesshpass
program must be installed.
Examples
# Attach directly to pid 1234 gdb.attach(1234)
# Attach to the youngest "bash" process gdb.attach('bash')
# 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')
# 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')
# Connect to the SSH server shell = ssh('bandit0', 'bandit.labs.overthewire.org', password='bandit0', 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()
-
pwnlib.gdb.
binary
() → str[源代码]¶ 返回: str – Path to the appropriate gdb
binary to use.Example
>>> gdb.binary() '/usr/bin/gdb'
-
pwnlib.gdb.
corefile
(process)[源代码]¶ Drops a core file for the process.
参数: process – Process to dump 返回: Core
– The generated core file
-
pwnlib.gdb.
debug
(args) → tube[源代码]¶ Launch a GDB server with the specified command line, and launches GDB to attach to it.
参数: - args (list) – Arguments to the process, similar to
process
. - gdbscript (str) – GDB script to run.
- exe (str) – Path to the executable on disk
- env (dict) – Environment to start the binary in
- ssh (
ssh
) – Remote ssh session to use to launch the process. - sysroot (str) – Foreign-architecture sysroot, used for QEMU-emulated binaries and Android targets.
返回: process
orssh_channel
– A tube connected to the target processNotes
The debugger is attached automatically, and you can debug everything from the very beginning. This requires that both
gdb
andgdbserver
are installed on your machine.When GDB opens via
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
sincelibc.so
has not even been loaded yet.There are several ways to handle this:
- 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
.
- Set a breakpoint on the executable’s entry point (generally,
- 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 internalmalloc
, and eventaully inlibc
’s malloc.
- This has the side-effect of setting breakpoints for every function
which matches the name. For
- Use pending breakpoints via
- 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.
- Wait for libraries to be loaded with
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") # Interact with the process io.interactive()
# 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
debug()
to spawn new processes on remote machines as well, by using thessh=
keyword to pass in yourssh
instance.# Connect to the SSH server shell = ssh('passcode', 'pwnable.kr', 2222, password='guest') # 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()
- args (list) – Arguments to the process, similar to
-
pwnlib.gdb.
debug_assembly
(asm, gdbscript=None, vma=None) → tube[源代码]¶ Creates an ELF file, and launches it under a debugger.
This is identical to debug_shellcode, except that any defined symbols are available in GDB, and it saves you the explicit call to asm().
- Arguments:
- asm(str): Assembly code to debug
gdbscript(str): Script to run in GDB
vma(int): Base address to load the shellcode at
**kwargs: Override any
pwnlib.context.context
values. - Returns:
process
Example:
assembly = shellcraft.echo("Hello world!
- “)
- io = gdb.debug_assembly(assembly) io.recvline() # ‘Hello world!’
-
pwnlib.gdb.
debug_shellcode
(*a, **kw)[源代码]¶ Creates an ELF file, and launches it under a debugger.
- Arguments:
- data(str): Assembled shellcode bytes
gdbscript(str): Script to run in GDB
vma(int): Base address to load the shellcode at
**kwargs: Override any
pwnlib.context.context
values. - Returns:
process
Example:
assembly = shellcraft.echo("Hello world!
- “)
- shellcode = asm(assembly) io = gdb.debug_shellcode(shellcode) io.recvline() # ‘Hello world!’
-
pwnlib.gdb.
find_module_addresses
(binary, ssh=None, ulimit=False)[源代码]¶ Cheat to find modules by using GDB.
We can’t use
/proc/$pid/map
since some servers forbid it. This breaksinfo proc
in GDB, butinfo sharedlibrary
still works. Additionally,info sharedlibrary
works on FreeBSD, which may not have procfs enabled or accessible.The output looks like this:
info proc mapping process 13961 warning: unable to open /proc file '/proc/13961/maps' info sharedlibrary From To Syms Read Shared Object Library 0xf7fdc820 0xf7ff505f Yes (*) /lib/ld-linux.so.2 0xf7fbb650 0xf7fc79f8 Yes /lib32/libpthread.so.0 0xf7e26f10 0xf7f5b51c Yes (*) /lib32/libc.so.6 (*): Shared library is missing debugging information.
Note that the raw addresses provided by
info sharedlibrary
are actually the address of the.text
segment, not the image base address.This routine automates the entire process of:
- Downloading the binaries from the remote server
- Scraping GDB for the information
- Loading each library into an ELF
- Fixing up the base address vs. the
.text
segment address
参数: - binary (str) – Path to the binary on the remote server
- ssh (pwnlib.tubes.tube) – SSH connection through which to load the libraries.
If left as
None
, will use apwnlib.tubes.process.process
. - ulimit (bool) – Set to
True
to run “ulimit -s unlimited” before GDB.
返回: A list of pwnlib.elf.ELF objects, with correct base addresses.
Example:
>>> with context.local(log_level=9999): ... shell = ssh(host='bandit.labs.overthewire.org',user='bandit0',password='bandit0', port=2220) ... bash_libs = gdb.find_module_addresses('/bin/bash', shell) >>> os.path.basename(bash_libs[0].path) 'libc.so.6' >>> hex(bash_libs[0].symbols['system']) '0x7ffff7634660'