0day_dev/pwntools-tutorial/logging.md
2020-08-07 21:19:09 -07:00

3.4 KiB

Table of Contents

Logging

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

Functions

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.

Command Line

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.

Context

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'

log_console

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

log_file

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'

Tubes

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'

Scoped

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()