Skip to content

Commit f9133b1

Browse files
authored
Add STDERR argument, and write docs for args module (#760)
Add STDERR argument, and write docs for args module Fixes #759
1 parent 8aa21bd commit f9133b1

File tree

8 files changed

+131
-29
lines changed

8 files changed

+131
-29
lines changed

docs/source/args.rst

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.. testsetup:: *
2+
3+
from pwn import *
4+
5+
:mod:`pwnlib.args` --- Magic Command-Line Arguments
6+
=====================================================
7+
8+
.. automodule:: pwnlib.args
9+
:members:

docs/source/conf.py

+10
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@
5555
pwnlib.term.text.when = 'never'
5656
pwnlib.log.install_default_handler()
5757
pwnlib.log.rootlogger.setLevel(1)
58+
59+
# Sphinx modifies sys.stdout, and context.log_terminal has
60+
# a reference to the original instance. We need to update
61+
# it for logging to be captured.
62+
class stdout(object):
63+
def __getattr__(self, name):
64+
return getattr(sys.stdout, name)
65+
def __setattr__(self, name, value):
66+
return setattr(sys.stdout, name, value)
67+
pwnlib.context.ContextType.defaults['log_console'] = stdout()
5868
'''
5969

6070
autodoc_member_order = 'alphabetical'

docs/source/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Each of the ``pwntools`` modules is documented here.
4343
:glob:
4444

4545
adb
46+
args
4647
asm
4748
atexception
4849
atexit

pwnlib/args.py

+79-20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,43 @@
11
#!/usr/bin/env python2
22
"""
3+
Pwntools exposes several magic command-line arguments and environment
4+
variables when operating in `from pwn import *` mode.
5+
6+
The arguments extracted from the command-line and removed from ``sys.argv``.
7+
8+
Arguments can be set by appending them to the command-line, or setting
9+
them in the environment prefixed by ``PWNLIB_``.
10+
11+
The easiest example is to enable more verbose debugging. Just set ``DEBUG``.
12+
13+
.. code-block:: bash
14+
15+
$ PWNLIB_DEBUG=1 python exploit.py
16+
$ python exploit.py DEBUG
17+
18+
These arguments are automatically extracted, regardless of their name, and
19+
exposed via ``pwnlib.args.args``, which is exposed as the global variable
20+
``args``. Arguments which ``pwntools`` reserves internally are not exposed
21+
this way.
22+
23+
.. code-block:: bash
24+
25+
$ python -c 'from pwn import *; print args' A=1 B=Hello HOST=1.2.3.4 DEBUG
26+
defaultdict(<type 'str'>, {'A': '1', 'HOST': '1.2.3.4', 'B': 'Hello'})
27+
28+
This is very useful for conditional code, for example determining whether to
29+
run an exploit locally or to connect to a remote server. Arguments which are
30+
not specified evaluate to an empty string.
31+
32+
.. code-block:: python
33+
34+
if args['REMOTE']:
35+
io = remote('exploitme.com', 4141)
36+
else:
37+
io = process('./pwnable')
38+
39+
The full list of supported "magic arguments" and their effects are listed
40+
below.
341
"""
442
import collections
543
import logging
@@ -42,46 +80,67 @@ def asbool(s):
4280
else:
4381
raise ValueError('must be integer or boolean: %r' % s)
4482

45-
def set_log_level(x):
83+
def LOG_LEVEL(x):
84+
"""Sets the logging verbosity used via ``context.log_level``,
85+
e.g. ``LOG_LEVEL=debug``.
86+
"""
4687
with context.local(log_level=x):
4788
context.defaults['log_level']=context.log_level
4889

49-
def set_log_file(x):
90+
def LOG_FILE(x):
91+
"""Sets a log file to be used via ``context.log_file``, e.g.
92+
``LOG_FILE=./log.txt``"""
5093
context.log_file=x
5194

52-
def set_log_level_error(x):
53-
set_log_level('error')
95+
def SILENT(x):
96+
"""Sets the logging verbosity to ``error`` which silences most
97+
output."""
98+
LOG_FILE('error')
5499

55-
def set_log_level_debug(x):
56-
set_log_level('debug')
100+
def DEBUG(x):
101+
"""Sets the logging verbosity to ``debug`` which displays much
102+
more information, including logging each byte sent by tubes."""
103+
LOG_FILE('debug')
57104

58-
def set_noterm(v):
105+
def NOTERM(v):
106+
"""Disables pretty terminal settings and animations."""
59107
if asbool(v):
60108
global term_mode
61109
term_mode = False
62110

63-
def set_timeout(v):
111+
def TIMEOUT(v):
112+
"""Sets a timeout for tube operations (in seconds) via
113+
``context.timeout``, e.g. ``TIMEOUT=30``"""
64114
context.defaults['timeout'] = int(v)
65115

66-
def set_randomize(v):
116+
def RANDOMIZE(v):
117+
"""Enables randomization of various pieces via ``context.randomize``"""
67118
context.defaults['randomize'] = asbool(v)
68119

69-
def set_aslr(v):
120+
def NOASLR(v):
121+
"""Disables ASLR via ``context.aslr``"""
70122
context.defaults['aslr'] = not asbool(v)
71123

72-
def set_noptrace(v):
124+
def NOPTRACE(v):
125+
"""Disables facilities which require ``ptrace`` such as ``gdb.attach()``
126+
statements, via ``context.noptrace``."""
73127
context.defaults['noptrace'] = asbool(v)
74128

129+
def STDERR(v):
130+
"""Sends logging to ``stderr`` by default, instead of ``stdout``"""
131+
context.log_console = sys.stderr
132+
75133
hooks = {
76-
'LOG_LEVEL': set_log_level,
77-
'LOG_FILE': set_log_file,
78-
'DEBUG': set_log_level_debug,
79-
'NOTERM': set_noterm,
80-
'SILENT': set_log_level_error,
81-
'RANDOMIZE': set_randomize,
82-
'TIMEOUT': set_timeout,
83-
'NOASLR': set_aslr,
84-
'NOPTRACE': set_noptrace,
134+
'LOG_LEVEL': LOG_LEVEL,
135+
'LOG_FILE': LOG_FILE,
136+
'DEBUG': DEBUG,
137+
'NOTERM': NOTERM,
138+
'SILENT': SILENT,
139+
'RANDOMIZE': RANDOMIZE,
140+
'TIMEOUT': TIMEOUT,
141+
'NOASLR': NOASLR,
142+
'NOPTRACE': NOPTRACE,
143+
'STDERR': STDERR,
85144
}
86145

87146
def initialize():

pwnlib/commandline/common.py

-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
import pwnlib
66
from pwnlib.context import context
77

8-
pwnlib.log.console.stream = sys.stderr
9-
108
choices = map(str, [16,32,64])
119
choices += list(context.oses)
1210
choices += list(context.architectures)

pwnlib/commandline/main.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import sys
2+
13
from . import asm
24
from . import checksec
35
from . import common
@@ -15,6 +17,7 @@
1517
from . import unhex
1618
from . import update
1719
from .common import parser
20+
from ..context import context
1821

1922
commands = {
2023
'asm': asm.main,
@@ -36,7 +39,8 @@
3639

3740
def main():
3841
args = parser.parse_args()
39-
commands[args.command](args)
42+
with context.local(log_console = sys.stderr):
43+
commands[args.command](args)
4044

4145
if __name__ == '__main__':
4246
main()

pwnlib/context/__init__.py

+19
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ class ContextType(object):
334334
'kernel': None,
335335
'log_level': logging.INFO,
336336
'log_file': _devnull(),
337+
'log_console': sys.stdout,
337338
'randomize': False,
338339
'newline': '\n',
339340
'noptrace': False,
@@ -922,6 +923,24 @@ def log_file(self, value):
922923
value.flush()
923924
return value
924925

926+
@_validator
927+
def log_console(self, stream):
928+
"""
929+
Sets the default logging console target.
930+
931+
Examples:
932+
933+
>>> context.log_level = 'warn'
934+
>>> log.warn("Hello")
935+
[!] Hello
936+
>>> context.log_console=open('/dev/null', 'w')
937+
>>> log.warn("Hello")
938+
>>> context.clear()
939+
"""
940+
if isinstance(stream, str):
941+
stream = open(stream, 'wt')
942+
return stream
943+
925944
@property
926945
def mask(self):
927946
return (1 << self.bits) - 1

pwnlib/log.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@
113113
from .term import text
114114

115115
# list of prefixes to use for the different message types. note that the `text`
116-
# module won't add any escape codes if `sys.stderr.isatty()` is `False`
116+
# module won't add any escape codes if `pwnlib.context.log_console.isatty()` is `False`
117117
_msgtype_prefixes = {
118118
'status' : [text.magenta, 'x'],
119119
'success' : [text.bold_green, '+'],
@@ -490,10 +490,14 @@ class Handler(logging.StreamHandler):
490490
logger will not be emitted but rather an animated progress line will be
491491
created.
492492
493-
This handler outputs to ``sys.stderr``.
494-
495493
An instance of this handler is added to the ``'pwnlib'`` logger.
496494
"""
495+
@property
496+
def stream(self):
497+
return context.log_console
498+
@stream.setter
499+
def stream(self, value):
500+
pass
497501
def emit(self, record):
498502
"""
499503
Emit a log record or create/update an animated progress logger
@@ -652,7 +656,7 @@ def handle(self, *a, **kw):
652656
# logger.addHandler(myCoolPitchingHandler)
653657
#
654658
rootlogger = getLogger('pwnlib')
655-
console = Handler(sys.stdout)
659+
console = Handler()
656660
formatter = Formatter()
657661
console.setFormatter(formatter)
658662

@@ -663,8 +667,6 @@ def install_default_handler():
663667
the ``pwnlib`` root logger. This function is automatically called from when
664668
importing :mod:`pwn`.
665669
'''
666-
console.stream = sys.stdout
667-
668670
logger = logging.getLogger('pwnlib')
669671

670672
if console not in logger.handlers:

0 commit comments

Comments
 (0)