Metadata-Version: 2.1
Name: pwnscripts
Version: 0.6.0
Summary: Simple pwntools QoL scripts
Home-page: https://github.com/152334H/pwnscripts
Author: 152334H
Author-email: 54623771+152334H@users.noreply.github.com
License: GPL3
Platform: UNKNOWN
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Natural Language :: English
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3.8
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Debuggers
Classifier: Topic :: Utilities
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: pwntools

# pwnscripts (0.6.0)
[![Tests](https://github.com/152334H/pwnscripts/workflows/Python%20package/badge.svg)](https://github.com/152334H/pwnscripts/actions)
[![PyPI package](https://badge.fury.io/py/pwnscripts.svg)](https://pypi.org/project/pwnscripts/)
[![Python](https://img.shields.io/pypi/pyversions/pwnscripts)](https://www.python.org/downloads/)

Very simple script(s) to hasten binary exploit creation. To use, `pip install pwnscripts` OR run
```bash
git clone https://github.com/152334H/pwnscripts
cd pwnscripts
pip install -e .
```
and replace `from pwn import *` with `from pwnscripts import *`, e.g.

```python
from pwnscripts import *
context.binary = './my_challenge'
...
```

Additionally, the `libc_database()` extension of pwnscripts requires the [libc-database](https://github.com/niklasb/libc-database).

You might want to look at some of the examples in `user_tests_and_examples.py`.

## Features

Pwnscripts has a number of different features.

### Libc
Things like [LibcSearcher](https://github.com/lieanu/LibcSearcher) have always felt incomplete.

Pwnscripts provides two libc classes: `libc()` and `libc_database()`. The easiest way to start with both is with `context`:
```python
context.libc_database = '/path/to/libc-database'  # https://github.com/niklasb/libc-database
context.libc = '/path/to/pwnscripts/examples/libc.so.6'
```
Anything you can run with `./libc-database/[executable]` is available as a `libc_database()` method: 
```python
>>> context.libc_database.dump('libc6_2.27-3ubuntu1_amd64')
b'offset___libc_start_main_ret = 0x21b97\noffset_system = 0x000000000004f440\noffset_dup2 = 0x00000000001109a0\noffset_read = 0x0000000000110070\noffset_write = 0x0000000000110140\noffset_str_bin_sh = 0x1b3e9a\n'
>>> output = context.libc_database.add()
>>> print(output.decode())
Adding local libc /path/to/pwnscripts/examples/libc.so.6 (id local-18292bd12d37bfaf58e8dded9db7f1f5da1192cb  /path/to/pwnscripts/examples/libc.so.6)
  -> Writing libc /path/to/pwnscripts/examples/libc.so.6 to db/local-18292bd12d37bfaf58e8dded9db7f1f5da1192cb.so
  -> Writing symbols to db/local-18292bd12d37bfaf58e8dded9db7f1f5da1192cb.symbols
  -> Writing version info
```
`libc_database()` also has a few additional methods; you can look at the [tests](https://github.com/152334H/pwnscripts/blob/master/test_automated.py) and [examples](https://github.com/152334H/pwnscripts/blob/master/user_tests_and_examples.py) and documentation to see.

---

The `libc()` object is a subclass of pwntools' `pwnlib.elf.elf.ELF()`. It starts off with a base address of `0`, but you can change that to match a remote executable by providing it with leaked addresses:
```python
>>> context.libc.symbols['scanf'] = 0x7fffa3b8b040 # Provide a leaked address to libc
>>> context.libc.address  # This is automagically updated after assignment
0x7fffa3b10000
>>> context.libc.symbols['str_bin_sh']  # Symbols from libc-database are stored in context.libc
0x7fffa3cc3e9a
```

`pwnscripts` is smart about `context.binary`: if `context.libc` is set, `context.binary.process()` will run with that libc version:
```bash
$ gcc -x c - <<< 'int main(){printf("%p\n", printf);}'
$ python3.8
>>> from pwnscripts import *
>>> context.log_level = 'warn'
>>> context.libc_database = 'libc-database'
>>> context.binary = './a.out'
>>> context.libc = 'libc6_2.31-0ubuntu9_amd64'
>>> context.binary.process().recvline()
b'0x7fa0f3c3fe10\n'   # printf 0000000000064e10
>>> context.libc = 'libc6_2.24-11+deb9u4_amd64'
>>> context.binary.process().recvline()
b'0x7fb99b69e190\n'   # printf 000000000004f190
```

`libc()` provides `one_gadget` integration in the form of an interactive selection:
```python
>>> context.libc.select_gadget()
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rsp & 0xf == 0
  rcx == NULL

0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
 [?] choose the gadget to use: 
       1) 0x4f2c5
       2) 0x4f322
       3) 0x10a38c
     Choice 
```
You're free to shut up the interactive menu by giving `.select_gadget()` an argument:
```python
>>> context.libc.select_gadget(1)
0x4f322
```
More features exist, but this is already too long.

### Format string exploitation
`printf()` challenges are repetitive. Under the `fsb` module, `pwnscripts` makes an attempt to further abstract the process of `printf()` exploitation.

Consider this simple program:
```c
//usr/bin/gcc -pie -fstack-protector-all -z,relro,-z,now "$0" -o test.o; exit
int main(){
  char s[200];
  fgets(s, 199, stdin);
  printf(s);   // leaker
  gets(s+200); // overflow
}
```
Let's further assume that you've decided to exploit this by returning to libc with the buffer overflow present. `printf()` can leak the runtime values of the [stack canary](https://ctf101.org/binary-exploitation/stack-canaries/) && [libc page](#Libc), but only after figuring out the specific stack offset (i.e. figuring out _m_ for `%m$p`) for both of those values.

Similar to the `FmtStr()` class in pwntools, we'll start by setting up a python function to abstract away the i/o associated with this challenge in particular:
```python
>>> @context.quiet
... def printf(line: str) -> bytes:
...   r = context.binary.process()
...   r.send(line)
...   return r.recvline() # return the output of printf()
```
With this function, We can automate the process of offset identification with `fsb.leak_offset`:
```python
>>> context.binary = './test.o'
>>> context.log_level = 'debug' # demonstration
>>> canary_offset = fsb.find_offset.canary(printf)
[DEBUG] cache is at ~/.cache/.pwntools-cache-3.8/fsb-cache/07e93d243fc1a7d88432cfb25bdc8bbb7b65fcabd6bb96ccea9c1ad027f2039f-default
[DEBUG] pwnscripts: extracted 0x7c
[DEBUG] pwnscripts: extracted 0x4141414141414141
... (omitted log) ...
[DEBUG] pwnscripts: extracted 0xcd088451e013aa00
[*] pwnscripts.fsb.find_offset for 'canary': 31
```
With the canary found, we can move on to leaking libc. Since `__libc_start_main_ret` is located immediately after the canary in the stack, the `printf()` cache maintained by `fsb.find_offset` will speed things up immensely:
```python
>>> context.libc_database = '../libc-database'       # replace with yours
>>> libc = libc('/lib/x86_64-linux-gnu/libc.so.6') # ibid
>>> libc_offset = fsb.find_offset.libc(printf,
... offset=libc.symbols['__libc_start_main_ret']&0xfff)  # Specify that we're looking for a value matching __l_s_m_r
[DEBUG] cache is at /home/throwaway/.cache/.pwntools-cache-3.8/fsb-cache/07e93d243fc1a7d88432cfb25bdc8bbb7b65fcabd6bb96ccea9c1ad027f2039f-default
(cached) [DEBUG] pwnscripts: extracted 0x7c
(cached) [DEBUG] pwnscripts: extracted 0x4141414141414141
... (omitted cache log) ...
(cached) [DEBUG] pwnscripts: extracted 0x3ad0d999e654b800
[DEBUG] pwnscripts: extracted 0x0
[DEBUG] pwnscripts: extracted 0x7f17857870b3
[*] pwnscripts.fsb.find_offset for 'libc': 33
```
More examples can be found [here](test_automated.py) and [here](user_tests_and_examples.py).

---

Apart from offset bruteforcing, `pwnscripts.fsb` also contains a `.leak` submodule to make leaking values with `%s` more programmatic.

The simple idea is that you get a payload to leak printf values:
```python
offset = fsb.find_offset.buffer(...) # == 6
payload = fsb.leak.deref_payload(offset, [0x400123, 0x600123])
print(payload)  # b'^^%10$s||%11$s$$#\x01@\x00#\x01`\x00'
```
And after sending the payload, extract the values with a helper function:
```python
r = remote(...)
r.sendline(payload)
print(fsb.leak.deref_extractor(r.recvline()))  # [b'\x80N\x03p\x94\x7f', b' \xeb\x04p\x94\x7f']
```
### Minor features
Pwnscripts also comes with a few minor extensions and functions:
* `util`: utility functions absent from pwntools. Some of the more useful things:
  * `is_addr` is an object you can use to check for specific address types. e.g.
    ```python
    >>> context.arch = 'amd64'
    >>> is_addr.PIE(0x55f83ba1034d)
    True
    >>> is_addr.stack(0xba081240a911)
    False
    >>> is_addr.libc(0x7fba912bd93d)
    True
    ```
    These functions are heuristic-based: they don't guarantee correctness, but tend to hit the mark nonetheless.
  * `unpack_*`: Better unpacking functions. Some examples:
    ```python
    >>> unpack_many_hex(b'jfawoa0x1234aokfw 0x123')
    [0x1234a, 0x123]
    >>> unpack_bytes(b'\x12\x34\x56\x78\x90\xab\xcd\xef', 6)
    0xab9078563412
    ```
* `rop.py`: an extension of pwntools' `pwnlib.rop.rop.ROP`. Core feature is to simplify ROP building outside of SIGROP:
  ```python
  >>> context.arch = 'amd64'
  >>> r = ROP('./binary')
  >>> r.system_call.execve(['/bin/sh',0,0])
  >>> print(r.dump())
    0x0000:         0x44a309 pop rdx; pop rsi; ret
    0x0008:              0x0 [arg3] rdx = 0
    0x0010:              0x0 [arg2] rsi = 0
    0x0018:         0x41e4af pop rax; ret
    0x0020:             0x3b [arg0] rax = SYS_execve
    0x0028:         0x401696 pop rdi; ret
    0x0030:             0x40 [arg1] rdi = AppendedArgument(['/bin/sh'], 0x0)
    0x0038:         0x4022b4 SYS_execve
    0x0040:   b'/bin/sh\x00'
    ```
* As was implicit in prior sections, `context` has been expanded with a number of extra attributes:
   * `.libc` and `.libc_database`, which are useful for everything mentioned [above](#libc)
   * `.is_local`, to check if the most recently opened pwntools `tube` is a remote/local process
* other unlisted features in development

Proper examples for `pwnscripts` are available in `examples/` and `user_tests_and_examples.py`.
## I tried using it; it doesn't work!

File in an [issue](https://github.com/152334H/pwnscripts/issues), if you can. With a single-digit userbase, it's hard to guess what might go wrong, but potentially:
 * pwnscripts is broken
 * Python is outdated (try python3.8+)
 * libc-database is not properly installed/initialised (did you run ./get?)
 * The binary provided is neither i386 or amd64; other architectures are mostly ignored (out of necessity)
 * The challenge is amd64, but `context.arch` wasn't set to `amd64`

     * Set `context.binary` appropriately, or set `context.arch` manually if no binary is given
 * Other unknown reasons. Try making a pull-request if you're interested.

## Updates

See [`CHANGELOG.md`](CHANGELOG.md).

Although version numbers follow the [Semantic Versioning](https://semver.org/) format, backwards compatibility is never assured; historical `pwnscripts` behaviour will be broken where appropriate.

Gradual updates expected as I continue to do pwn.

