File: intro.rst

package info (click to toggle)
pwntools 4.15.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 18,508 kB
  • sloc: python: 59,870; ansic: 48,351; asm: 45,047; sh: 396; makefile: 256
file content (228 lines) | stat: -rw-r--r-- 6,380 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
.. testsetup:: *

   from pwn import *

Getting Started
========================

To get your feet wet with pwntools, let's first go through a few examples.

When writing exploits, pwntools generally follows the "kitchen sink" approach.

    >>> from pwn import *

This imports a lot of functionality into the global namespace.  You can now
assemble, disassemble, pack, unpack, and many other things with a single function.

A full list of everything that is imported is available on :doc:`globals`.


Tutorials
---------

A series of tutorials for Pwntools exists online, at
https://github.com/Gallopsled/pwntools-tutorial#readme


Making Connections
------------------

You need to talk to the challenge binary in order to pwn it, right?
pwntools makes this stupid simple with its :mod:`pwnlib.tubes` module.

This exposes a standard interface to talk to processes, sockets, serial ports,
and all manner of things, along with some nifty helpers for common tasks.
For example, remote connections via :mod:`pwnlib.tubes.remote`.

    >>> conn = remote('ftp.ubuntu.com',21)
    >>> conn.recvline() # doctest: +ELLIPSIS
    b'220 ...'
    >>> conn.send(b'USER anonymous\r\n')
    >>> conn.recvuntil(b' ', drop=True)
    b'331'
    >>> conn.recvline()
    b'Please specify the password.\r\n'
    >>> conn.close()

It's also easy to spin up a listener

    >>> l = listen()
    >>> r = remote('localhost', l.lport)
    >>> c = l.wait_for_connection()
    >>> r.send(b'hello')
    >>> c.recv()
    b'hello'

Interacting with processes is easy thanks to :mod:`pwnlib.tubes.process`.

::

    >>> sh = process('/bin/sh')
    >>> sh.sendline(b'sleep 3; echo hello world;')
    >>> sh.recvline(timeout=1)
    b''
    >>> sh.recvline(timeout=5)
    b'hello world\n'
    >>> sh.close()

Not only can you interact with processes programmatically, but you can
actually **interact** with processes.

    >>> sh.interactive() # doctest: +SKIP
    $ whoami
    user

There's even an SSH module for when you've got to SSH into a box to perform
a local/setuid exploit with :mod:`pwnlib.tubes.ssh`.  You can quickly spawn
processes and grab the output, or spawn a process and interact with it like
a ``process`` tube.

::

    >>> shell = ssh('bandit0', 'bandit.labs.overthewire.org', password='bandit0', port=2220)
    >>> shell['whoami']
    b'bandit0'
    >>> shell.download_file('/etc/motd')
    >>> sh = shell.run('sh')
    >>> sh.sendline(b'sleep 3; echo hello world;') # doctest: +SKIP
    >>> sh.recvline(timeout=1)
    b''
    >>> sh.recvline(timeout=5)
    b'hello world\n'
    >>> shell.close()

Packing Integers
------------------

A common task for exploit-writing is converting between integers as Python
sees them, and their representation as a sequence of bytes.
Usually folks resort to the built-in ``struct`` module.

pwntools makes this easier with :mod:`pwnlib.util.packing`.  No more remembering
unpacking codes, and littering your code with helper routines.

    >>> import struct
    >>> p32(0xdeadbeef) == struct.pack('I', 0xdeadbeef)
    True
    >>> leet = unhex('37130000')
    >>> u32(b'abcd') == struct.unpack('I', b'abcd')[0]
    True

The packing/unpacking operations are defined for many common bit-widths.

    >>> u8(b'A') == 0x41
    True

Setting the Target Architecture and OS
--------------------------------------

The target architecture can generally be specified as an argument to the routine that requires it.

    >>> asm('nop')
    b'\x90'
    >>> asm('nop', arch='arm')
    b'\x00\xf0 \xe3'

However, it can also be set once in the global ``context``.  The operating system, word size, and endianness can also be set here.

    >>> context.arch      = 'i386'
    >>> context.os        = 'linux'
    >>> context.endian    = 'little'
    >>> context.word_size = 32

Additionally, you can use a shorthand to set all of the values at once.

    >>> asm('nop')
    b'\x90'
    >>> context(arch='arm', os='linux', endian='big', word_size=32)
    >>> asm('nop')
    b'\xe3 \xf0\x00'

.. doctest::
   :hide:

    >>> context.clear()

Setting Logging Verbosity
-------------------------

You can control the verbosity of the standard pwntools logging via ``context``.

For example, setting

    >>> context.log_level = 'debug'

Will cause all of the data sent and received by a ``tube`` to be printed to the screen.

.. doctest::
   :hide:

    >>> context.clear()

Assembly and Disassembly
------------------------

Never again will you need to run some already-assembled pile of shellcode
from the internet!  The :mod:`pwnlib.asm` module is full of awesome.

    >>> enhex(asm('mov eax, 0'))
    'b800000000'

But if you do, it's easy to suss out!

    >>> print(disasm(unhex('6a0258cd80ebf9')))
       0:   6a 02                   push   0x2
       2:   58                      pop    eax
       3:   cd 80                   int    0x80
       5:   eb f9                   jmp    0x0

However, you shouldn't even need to write your own shellcode most of the
time!  pwntools comes with the :mod:`pwnlib.shellcraft` module, which is
loaded with useful time-saving shellcodes.

Let's say that we want to `setreuid(getuid(), getuid())` followed by `dup`ing
file descriptor 4 to `stdin`, `stdout`, and `stderr`, and then pop a shell!

    >>> enhex(asm(shellcraft.setreuid() + shellcraft.dupsh(4))) # doctest: +ELLIPSIS
    '6a3158cd80...'


Misc Tools
----------------------

Never write another hexdump, thanks to :mod:`pwnlib.util.fiddling`.


Find offsets in your buffer that cause a crash, thanks to :mod:`pwnlib.cyclic`.

    >>> cyclic(20)
    b'aaaabaaacaaadaaaeaaa'
    >>> # Assume EIP = 0x62616166 (b'faab' which is pack(0x62616166))  at crash time
    >>> cyclic_find(b'faab')
    120

ELF Manipulation
----------------

Stop hard-coding things!  Look them up at runtime with :mod:`pwnlib.elf`.

    >>> e = ELF('/bin/cat')
    >>> print(hex(e.address)) #doctest: +SKIP
    0x400000
    >>> print(hex(e.symbols['write'])) #doctest: +SKIP
    0x401680
    >>> print(hex(e.got['write'])) #doctest: +SKIP
    0x60b070
    >>> print(hex(e.plt['write'])) #doctest: +SKIP
    0x401680

You can even patch and save the files.

    >>> e = ELF('/bin/cat')
    >>> e.read(e.address, 4)
    b'\x7fELF'
    >>> e.asm(e.address, 'ret')
    >>> e.save('/tmp/quiet-cat')
    >>> disasm(open('/tmp/quiet-cat','rb').read(1))
    '   0:   c3                      ret'