Pwntools has a rich internal debugging system, available for your own debugging as well as figuring out what's happening behind-the-scenes in Pwntools
The logging functionality is exposed when you import from pwn import *
.
This exposes these routines:
error
warn
info
debug
For example:
>>> warn('Warning!')
[!] Warning!
>>> info('Info!')
[*] Info!
>>> debug('Debug!')
Note that the last line is not shown by default, since the default log-level is "info".
You can use these in your exploit scripts instead of print
which lets
you dial in the exact amount of debugging information you see.
You can control which log messages are visible in a variety of ways, all which are explained below.
The easiest way to turn on the maximum amount of logging verbosity is to
run your script with the magic argument DEBUG
, e.g.
$ python exploit.py DEBUG
This is useful for seeing the exact bytes being sent / received, and things that are happening internal to pwntools to make your exploit work.
You can also set the logging verbosity via context.log_level
, in the same way
that you set e.g. the target architecture.
This controls all logging statements in the same way as on the command-line.
>>> context.log_level = 'debug'
By default, all logs go to STDOUT. If you want to change this to a different file,
e.g. STDERR, you can do this with the log_console
setting.
>>> context.log_console = sys.stderr
Sometimes you want your logs to go to a specific file, e.g. log.txt
, to look at later.
You can add a log file by setting context.log_file
.
>>> context.log_file = './log.txt'
Each tube can have its logging verbosity controlled individually, when it is created.
Simply pass level='...'
to the construction of the object.
>>> io = process('sh', level='debug')
[x] Starting local process '/usr/bin/sh' argv=[b'sh']
[+] Starting local process '/usr/bin/sh' argv=[b'sh'] : pid 34475
>>> io.sendline('echo hello')
[DEBUG] Sent 0xb bytes:
b'echo hello\n'
>>> io.recvline()
[DEBUG] Received 0x6 bytes:
b'hello\n'
b'hello\n'
This works for all of the tubes (process
, remote
, etc), and also works for
tube-like things (e.g. gdb.attach
and gdb.debug
) as well as many other
routines.
For example, if you want to see exactly how some shellcode is assembled:
>>> asm('nop', log_level='debug')
[DEBUG] cpp -C -nostdinc -undef -P -I/home/user/pwntools/pwnlib/data/includes /dev/stdin
[DEBUG] Assembling
.section .shellcode,"awx"
.global _start
.global __start
_start:
__start:
.intel_syntax noprefix
nop
[DEBUG] /usr/bin/x86_64-linux-gnu-as -32 -o /tmp/user/pwn-asm-0yy12n6i/step2 /tmp/user/pwn-asm-0yy12n6i/step1
[DEBUG] /usr/bin/x86_64-linux-gnu-objcopy -j .shellcode -Obinary /tmp/user/pwn-asm-0yy12n6i/step3 /tmp/user/pwn-asm-0yy12n6i/step4
b'\x90'
Sometimes you want ALL the logs to be enabled, but only for part of an exploit script.
You can manually toggle context.log_level
, or you can use a scoped helper.
io = process(...)
with context.local(log_level='debug'):
# Things inside the 'with' block are logged verbosely
io.recvall()