├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.rst ├── changelog.rst ├── docker ├── pwnbook │ ├── py2 │ │ └── Dockerfile │ └── py3 │ │ └── Dockerfile └── pwnypack │ ├── py2 │ └── Dockerfile │ └── py3 │ └── Dockerfile ├── docs ├── Makefile ├── conf.py ├── index.rst ├── make.bat ├── pwny.rst ├── pwnypack.rst ├── pwnypack │ ├── asm.rst │ ├── bytecode.rst │ ├── codec.rst │ ├── elf.rst │ ├── flow.rst │ ├── fmtstring.rst │ ├── marshal.rst │ ├── oracle.rst │ ├── packing.rst │ ├── php.rst │ ├── pickle.rst │ ├── py_internals.rst │ ├── rop.rst │ ├── shellcode.rst │ ├── shellcode │ │ ├── aarch64.rst │ │ ├── aarch64_linux.rst │ │ ├── arm.rst │ │ ├── arm_linux.rst │ │ ├── base.rst │ │ ├── declarative.rst │ │ ├── imperative.rst │ │ ├── linux.rst │ │ ├── translate.rst │ │ ├── x86.rst │ │ ├── x86_64.rst │ │ ├── x86_64_linux.rst │ │ └── x86_linux.rst │ ├── target.rst │ └── util.rst └── requirements.txt ├── pwny ├── __init__.py ├── bc.py └── sc.py ├── pwnypack ├── __init__.py ├── asm.py ├── bytecode.py ├── codec.py ├── elf.py ├── flow.py ├── fmtstring.py ├── ipython_ext.py ├── main.py ├── marshal.py ├── oracle.py ├── packing.py ├── php.py ├── pickle.py ├── pwnbook.py ├── py_internals.py ├── rop.py ├── shell.py ├── shellcode │ ├── __init__.py │ ├── aarch64 │ │ ├── __init__.py │ │ └── linux.py │ ├── arm │ │ ├── __init__.py │ │ ├── linux.py │ │ ├── thumb.py │ │ └── thumb_mixed.py │ ├── base.py │ ├── linux.py │ ├── mutable_data.py │ ├── ops.py │ ├── stack_data.py │ ├── translate.py │ ├── types.py │ ├── x86 │ │ ├── __init__.py │ │ ├── linux.py │ │ ├── mutable_data.py │ │ ├── null_safe.py │ │ └── stack_data.py │ └── x86_64 │ │ ├── __init__.py │ │ ├── linux.py │ │ ├── null_safe.py │ │ └── stack_data.py ├── target.py └── util.py ├── setup.cfg ├── setup.py ├── tests ├── conftest.py ├── test_asm.py ├── test_codec.py ├── test_elf.py ├── test_packing.py ├── test_php.py ├── test_pickle.py ├── test_shellcode.py ├── test_target.py └── test_util.py ├── tools ├── build_py_internals.py └── build_py_internals_docs.py └── upload.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.pyc 3 | *~ 4 | .idea 5 | dist 6 | docs/_build 7 | .eggs/ 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.6" 4 | - "2.7" 5 | - "3.3" 6 | - "3.4" 7 | - "3.5" 8 | - "3.6" 9 | addons: 10 | apt: 11 | packages: 12 | - nasm 13 | sudo: false 14 | install: 15 | - pip install -U setuptools pip 16 | - pip install -U py pytest 17 | - pip install --no-binary capstone,keystone-engine -e .[asm,disasm,rop,ssh] 18 | script: 19 | - WANT_KEYSTONE=0 python setup.py test 20 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ingmar Steen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | pwnypack 2 | ======== 3 | 4 | The official *Certified Edible Dinosaurs* CTF toolkit. *pwnypack* 5 | attempts to provide a toolset which can be used to more easily develop 6 | CTF solutions. 7 | 8 | |Build Status| 9 | 10 | Motivation 11 | ---------- 12 | 13 | After seeing the excellent 14 | `pwntools `__ by Gallopsled, I 15 | got interested in building my own CTF toolkit. *pwntools* is much more 16 | complete so you should probably use that. *pwnypack* was created mostly 17 | out of curiosity. 18 | 19 | Installation 20 | ------------ 21 | 22 | First, make sure your `setuptools` and `pip` packages are up to date: 23 | 24 | .. code:: bash 25 | 26 | $ pip install -U setuptools pip 27 | 28 | To install the latest released version of pwnypack with all optional 29 | dependencies, run: 30 | 31 | .. code:: bash 32 | 33 | $ pip install --no-binary capstone,keystone-engine pwnypack[all] 34 | 35 | To install the latest released version of pwnypack with minimal 36 | dependencies, run: 37 | 38 | .. code:: bash 39 | 40 | $ pip install pwnypack 41 | 42 | Other available install targets are: 43 | 44 | - ``--no-binary keystone-engine pwnypack[asm]`` - installs ``keystone-engine`` 45 | to support all assembler targets without any additional depenencies. 46 | 47 | - ``--no-binary capstone pwnypack[disasm]`` - installs ``capstone`` for AT&T 48 | and intel syntax disassembly, required to disassemble ARM binaries. 49 | 50 | - ``--no-binary capstone pwnypack[rop]`` - installs ``capstone`` to validate 51 | ROP gadgets. 52 | 53 | - ``pwnypack[ssh]`` - installs ``paramiko`` to enable the ``Flow`` module to 54 | connect to SSH servers. 55 | 56 | - ``pwnypack[shell]`` - installs ``ipython`` to support the enhanced pwnypack 57 | REPL environment. 58 | 59 | - ``pwnypack[pwnbook]`` - installs ``jupyter`` to support the ``pwnbook`` jupyter 60 | notebook. 61 | 62 | If you want to use the interactive shell I highly recommend installing 63 | either ``bpython`` or ``ipython`` as those packages can make your time in 64 | the shell a lot more enjoyable. 65 | 66 | Docker 67 | ------ 68 | 69 | You can also use our published docker images. 70 | 71 | To start an ipython powered pwnypack shell: 72 | 73 | .. code:: bash 74 | 75 | docker pull edibledinos/pwnypack:latest 76 | docker run --rm -it edibledinos/pwnypack:latest 77 | 78 | Or, to run pwnbook: 79 | 80 | .. code:: bash 81 | 82 | docker pull edibledinos/pwnbook:latest 83 | docker run --rm -it -p 8888:8888 edibledinos/pwnbook:latest 84 | 85 | Both images expose a volume (``/projects``). Feel free to mount something 86 | interesting there. 87 | 88 | Three tags are available: 89 | 90 | - ``py3`` (or: ``latest``) installs python 3 and pwnypack/pwnbook. 91 | - ``py2`` installs python 2 and pwnypack/pwnbook. 92 | 93 | Usage 94 | ----- 95 | 96 | To import all of *pwnypack* into your global namespace, use: 97 | 98 | .. code:: python 99 | 100 | >>> from pwny import * 101 | 102 | Or, if you're using python 2.7+ or python 3.3+, try the customized 103 | bpython or IPython shell: 104 | 105 | .. code:: bash 106 | 107 | $ pwny shell 108 | 109 | If you have bpython and/or IPython installed you can use ``--bpython``, 110 | ``--ipython`` or ``--python`` to select which interactive kernel to use. 111 | 112 | I promise that effort will be put into not exposing unnecessary stuff 113 | and thus overly polluting your global namespace. 114 | 115 | For an example, check out the `Big Prison 116 | Fence `__ 117 | example in the wiki. 118 | 119 | Common errors 120 | ------------- 121 | 122 | Capstone/keystone fails to import the dynamic library. 123 | 124 | .. code:: 125 | 126 | Traceback (most recent call last): 127 | File "", line 1, in 128 | File "/home/ingmar/.virtualenvs/pp/lib/python3.5/site-packages/capstone/__init__.py", line 230, in 129 | raise ImportError("ERROR: fail to load the dynamic library.") 130 | ImportError: ERROR: fail to load the dynamic library. 131 | 132 | The ``capstone`` and ``keystone`` packages have a bug which when used with a 133 | new verion of ``pip`` will end up installing the dynamic libraries in the 134 | wrong location on some platforms. Re-install ``capstone`` and/or ``keystone`` 135 | using: 136 | 137 | .. code:: bash 138 | 139 | $ pip install --no-binary capstone capstone 140 | $ pip install --no-binary keystone-engine keystone-engine 141 | 142 | SyntaxError when importing pwnypack. 143 | 144 | .. code:: 145 | 146 | Traceback (most recent call last): 147 | File "", line 1, in 148 | File "pwny/__init__.py", line 9, in 149 | from pwnypack.pwnbook import * 150 | File "pwnypack/pwnbook.py", line 2, in 151 | from jupyter_client import kernelspec as kernelspec 152 | File "/Users/ingmar/.virtualenvs/pwny26/lib/python2.6/site-packages/jupyter_client/__init__.py", line 4, in 153 | from .connect import * 154 | File "/Users/ingmar/.virtualenvs/pwny26/lib/python2.6/site-packages/jupyter_client/connect.py", line 23, in 155 | from traitlets.config import LoggingConfigurable 156 | File "/Users/ingmar/.virtualenvs/pwny26/lib/python2.6/site-packages/traitlets/__init__.py", line 1, in 157 | from .traitlets import * 158 | File "/Users/ingmar/.virtualenvs/pwny26/lib/python2.6/site-packages/traitlets/traitlets.py", line 1331 159 | return {n: t for (n, t) in cls.class_traits(**metadata).items() 160 | ^ 161 | SyntaxError: invalid syntax 162 | 163 | You've installed jupyter notebooks on python 2.6. Use a more modern version 164 | of python. 165 | 166 | Documentation 167 | ------------- 168 | 169 | *pwnypack*'s API documentation is hosted on 170 | `readthedocs `__. 171 | 172 | For information on the commandline apps use the built in help function: 173 | 174 | .. code:: bash 175 | 176 | $ pwny --help 177 | $ pwny shell --help 178 | 179 | Contributors 180 | ------------ 181 | 182 | *pwnypack* was created by Certified Edible Dinosaurs (dsc & doskop). If you 183 | want to contribute, feel free to fork and create a pull request on 184 | `GitHub `__. 185 | 186 | Current contributors: 187 | 188 | - blasty contributed the ARM shellcode generator. 189 | 190 | License 191 | ------- 192 | 193 | *pwnypack* is distributed under the MIT license. 194 | 195 | .. |Build Status| image:: https://travis-ci.org/edibledinos/pwnypack.svg?branch=travis-ci 196 | :target: https://travis-ci.org/edibledinos/pwnypack 197 | -------------------------------------------------------------------------------- /changelog.rst: -------------------------------------------------------------------------------- 1 | Release history 2 | ############### 3 | 4 | next release 5 | ============ 6 | 7 | * Fix serializing private PHP object properties. 8 | 9 | 0.9.0 (2017-11-17) 10 | ================== 11 | 12 | * Adding missing dev packages on docker images. 13 | * Docker images clone latest branch of pwnypack repository. 14 | * Fix magic command registration on IPython >= 5.0.0. 15 | * Support parsing (a subset of) ~/.ssh/config in flow.SSHClient. 16 | * Fix aarch64 support when using keystone engine. 17 | * Added padding oracle attack functions. 18 | * Added keystone-engine dependency for 'asm' and 'all' targets. 19 | * Make pwnbook, shell and ssh targets require python 2.7+. 20 | * Added internals for python 3.5.2 and 3.6. 21 | 22 | 0.8.0 (2016-05-17) 23 | ================== 24 | 25 | * Return empty list when trying to read non-existing .dynamic section. 26 | * Don't print newline when piping the output of a gadget. 27 | * Fix output of raw binary data on python 3. 28 | * Add pwnypack extension for ipython. 29 | * Add pwnypack jupyter notebook wrapper (pwnbook). 30 | * Moved and renamed util.pickle_call to pickle.pickle_invoke. 31 | * Added pickle_func that pickles a function and its invocation. 32 | * Added support for using GNU binutils to assemble AT&T and intel syntax. 33 | * Added support for assembling/disassembling ARM using binutils/capstone. 34 | * Use extras_require to make capstone, paramiko and jupyter optional. 35 | * Add Dockerfile for pwnypack shell and pwnbook. 36 | * Fix interact on python 3 in Flow. 37 | * Add python bytecode manipulation functions. 38 | * Added shellcode generator for X86/X86_64, ARM (+Thumb) and AArch64. 39 | * Use keystone engine as assembler engine by default. 40 | * Added xor mask finder. 41 | * Added python independent marshal and .pyc loader. 42 | * Fix internal escaping of reghex expressions. 43 | * Allow wildcards when searching for ROP gadgets using assembly statements. 44 | 45 | 0.7.2 (2016-03-11) 46 | ================== 47 | 48 | * Added support for .dynamic section parsing to ELF class. 49 | * Added checksec command line app. 50 | * Make pwnypack available as a universal wheel. 51 | 52 | 0.7.1 (2016-03-07) 53 | ================== 54 | 55 | * Add support for bpython and plain python interactive shells. 56 | * Fix missing newline after the output of several commandline apps. 57 | * Added PHP serialized data generation function and helper. 58 | * Add enurlform / deurlform / enurlquote / deurlquote functions. 59 | 60 | 0.7.0 (2015-07-12) 61 | ================== 62 | 63 | * Made IPython an optional dependency (pip install pwnypack[shell]). 64 | * Added pickle_call function to easily execute a function on unpickle. 65 | * Added format string vulnerability exploit builder. 66 | * Renamed TCPSocketChannel to TCPClientSocketChannel. 67 | * Added TCPServerSocketChannel which can listen for an incoming connection. 68 | * Added Flow.interact() method. 69 | * Added support for connecting to SSH servers from Flow. 70 | 71 | 0.6.0 (2015-04-14) 72 | ================== 73 | 74 | * Bugfixes (and travis-ci integration). 75 | * `API documentation `_ and docstrings. 76 | * Cycle-find can read from stdin. 77 | * Major refactoring of ELF class. It can now parse section headers, program 78 | headers, symbol tables and extract section, symbols. 79 | * Major refactoring of Target class. It's no longer tied to ELF (ELF is still 80 | a subclass of Target though). 81 | * A reghex compiler. 82 | * Verifying ROP gadget finder. 83 | * Disassembler functionality (based on ndisasm or capstone). 84 | * The ability to redirect stderr to stdout in flow.ProcessChannel. 85 | * The ability to create symlinks for commandline apps. 86 | * New commandline apps: 87 | * ``asm`` to assemble from commandline. 88 | * ``symbols`` to list the symbol table of an ELF file. 89 | * ``gadget`` to find ROP gadgets in an ELF file. 90 | * ``symbol-extract`` to extract a symbol from an ELF file. 91 | * ``symbol-disasm`` to disassemble a symbol in an ELF file. 92 | 93 | 0.5.2 (2015-03-22) 94 | ================== 95 | 96 | * Added command line apps and a customized IPython shell. 97 | 98 | 0.5.1 (2015-03-21) 99 | ================== 100 | 101 | * Python3 fixes for flow: 102 | * Use latin1 for echo mode as not everything will be encodable as utf-8. 103 | * Disable buffering on subprocess. 104 | 105 | 0.5.0 (2015-03-21) 106 | ================== 107 | 108 | * Initial release. 109 | -------------------------------------------------------------------------------- /docker/pwnbook/py2/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM edibledinos/pwnypack:py2 2 | 3 | MAINTAINER Ingmar Steen 4 | 5 | EXPOSE 8888 6 | WORKDIR /projects 7 | USER pwnypack 8 | CMD /virtualenv/bin/pwny pwnbook --ip 0.0.0.0 --no-browser 9 | -------------------------------------------------------------------------------- /docker/pwnbook/py3/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM edibledinos/pwnypack:py3 2 | 3 | MAINTAINER Ingmar Steen 4 | 5 | EXPOSE 8888 6 | WORKDIR /projects 7 | USER pwnypack 8 | CMD /virtualenv/bin/pwny pwnbook --ip 0.0.0.0 --no-browser 9 | -------------------------------------------------------------------------------- /docker/pwnypack/py2/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:trusty 2 | 3 | MAINTAINER Ingmar Steen 4 | 5 | RUN apt-get update && \ 6 | DEBIAN_FRONTEND=noninteractive apt-get install -qy \ 7 | git nasm build-essential python cmake \ 8 | python-dev python-pip python-virtualenv python-setuptools \ 9 | binutils-aarch64-linux-gnu binutils-arm-none-eabi \ 10 | libffi-dev libssl-dev && \ 11 | apt-get clean && \ 12 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 13 | 14 | RUN groupadd -r pwnypack && \ 15 | useradd -m -r -g pwnypack pwnypack && \ 16 | install -d -o pwnypack -g pwnypack /virtualenv /projects 17 | 18 | USER pwnypack 19 | 20 | RUN virtualenv /virtualenv && /virtualenv/bin/pip install -U pip setuptools && \ 21 | git clone -b latest https://github.com/edibledinos/pwnypack /tmp/pwnypack && \ 22 | /virtualenv/bin/pip install --no-binary capstone,keystone-engine /tmp/pwnypack/[all] && \ 23 | rm -rf /tmp/pwnypack ~/.cache 24 | 25 | RUN git clone https://github.com/edibledinos/pwnypack-examples.git /projects && \ 26 | rm -rf /projects/.git 27 | 28 | VOLUME ["/projects"] 29 | WORKDIR /projects 30 | CMD /virtualenv/bin/pwny shell 31 | -------------------------------------------------------------------------------- /docker/pwnypack/py3/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:trusty 2 | 3 | MAINTAINER Ingmar Steen 4 | 5 | RUN apt-get update && \ 6 | DEBIAN_FRONTEND=noninteractive apt-get install -qy \ 7 | git nasm build-essential python python3 cmake \ 8 | python3-dev python3-pip python3-setuptools \ 9 | binutils-aarch64-linux-gnu binutils-arm-none-eabi \ 10 | libffi-dev libssl-dev && \ 11 | apt-get clean && \ 12 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 13 | 14 | RUN /usr/bin/pip3 install virtualenv && \ 15 | groupadd -r pwnypack && \ 16 | useradd -m -r -g pwnypack pwnypack && \ 17 | install -d -o pwnypack -g pwnypack /virtualenv /projects 18 | 19 | USER pwnypack 20 | 21 | RUN virtualenv -p /usr/bin/python3 /virtualenv && /virtualenv/bin/pip install -U pip setuptools && \ 22 | git clone -b latest https://github.com/edibledinos/pwnypack /tmp/pwnypack && \ 23 | /virtualenv/bin/pip install --no-binary capstone,keystone-engine /tmp/pwnypack/[all] && \ 24 | rm -rf /tmp/pwnypack ~/.cache 25 | 26 | RUN git clone https://github.com/edibledinos/pwnypack-examples.git /projects && \ 27 | rm -rf /projects/.git 28 | 29 | VOLUME ["/projects"] 30 | WORKDIR /projects 31 | CMD /virtualenv/bin/pwny shell 32 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pwny.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pwny.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/pwny" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pwny" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to pwnypack! 2 | ==================== 3 | 4 | *pwnypack* is the official CTF toolkit of Certified Edible Dinosaurs. It aims 5 | to provide a set of command line utilities and a python library that are 6 | useful when playing hacking CTFs. 7 | 8 | The core functionality of *pwnypack* is defined in the modules of the 9 | ``pwnypack`` package. The ``pwny`` package imports all that 10 | functionality into a single namespace for convenience. 11 | 12 | Some of the functionality of the ``pwnypack`` package is also exported 13 | through a set of commandline utilities. Run :code:`pwny help` after installing 14 | *pwnypack* to get a list of available utilities. You can create convenience 15 | symlinks for all the included apps by running :code:`pwny symlink`. Each app 16 | has a help function that is accessible using the :code:`-h` parameter. 17 | 18 | For some example of how to use *pwnypack*, check the write-ups on the 19 | official `Certified Edible Dinosaurs `_ website. 20 | 21 | Package contents: 22 | 23 | .. toctree:: 24 | 25 | pwny 26 | pwnypack 27 | 28 | Indices and tables 29 | ================== 30 | 31 | * :ref:`genindex` 32 | * :ref:`modindex` 33 | 34 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 2> nul 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pwny.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pwny.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /docs/pwny.rst: -------------------------------------------------------------------------------- 1 | pwny package 2 | ============ 3 | 4 | The ``pwny`` package provides a convence metapackage that imports the entire 5 | public API of `pwnypack` into a single namespace:: 6 | 7 | >>> from pwny import * 8 | >>> enhex(asm('mov rax, 0xced', target=Target(arch=Architecture.x86_64))) 9 | u'b8ed0c0000' 10 | 11 | For details about what exactly is made available, please consult the 12 | documentation of the individual :doc:`pwnypack modules `. 13 | -------------------------------------------------------------------------------- /docs/pwnypack.rst: -------------------------------------------------------------------------------- 1 | pwnypack package 2 | ================ 3 | 4 | All the functionality of *pwnypack* is implemented in the modules of this 5 | package. 6 | 7 | .. toctree:: 8 | :glob: 9 | 10 | pwnypack/* 11 | -------------------------------------------------------------------------------- /docs/pwnypack/asm.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.asm` -- (Dis)assembler 2 | ====================================== 3 | 4 | .. automodule:: pwnypack.asm 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/pwnypack/bytecode.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.bytecode` -- Python bytecode manipulation 2 | ========================================================= 3 | 4 | .. automodule:: pwnypack.bytecode 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | .. autoclass:: AnnotatedOp 10 | :members: 11 | 12 | .. autoclass:: Block 13 | :members: 14 | -------------------------------------------------------------------------------- /docs/pwnypack/codec.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.codec` -- Data transformation 2 | ============================================= 3 | 4 | .. automodule:: pwnypack.codec 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/pwnypack/elf.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.elf` -- ELF file parsing 2 | ======================================== 3 | 4 | .. automodule:: pwnypack.elf 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/pwnypack/flow.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.flow` -- Communication 2 | ====================================== 3 | 4 | .. automodule:: pwnypack.flow 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/pwnypack/fmtstring.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.fmtstring` -- Format strings 2 | ============================================ 3 | 4 | .. automodule:: pwnypack.fmtstring 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/pwnypack/marshal.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.marshal` -- Python marshal loader 2 | ================================================= 3 | 4 | .. automodule:: pwnypack.marshal 5 | :members: 6 | :show-inheritance: 7 | -------------------------------------------------------------------------------- /docs/pwnypack/oracle.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.oracle` -- Padding oracle attacks 2 | ================================================= 3 | 4 | .. automodule:: pwnypack.oracle 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/pwnypack/packing.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.packing` -- Data (un)packing 2 | ============================================ 3 | 4 | .. automodule:: pwnypack.packing 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/pwnypack/php.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.php` -- PHP related functions 2 | ============================================= 3 | 4 | .. automodule:: pwnypack.php 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/pwnypack/pickle.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.pickle` -- Pickle tools 2 | ======================================= 3 | 4 | .. automodule:: pwnypack.pickle 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/pwnypack/py_internals.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.py_internals` -- Python internals 2 | ================================================= 3 | 4 | .. automodule:: pwnypack.py_internals 5 | 6 | .. autofunction:: get_py_internals 7 | 8 | .. autodata:: PY_260 9 | :annotation: = {...} 10 | 11 | .. autodata:: PY_270 12 | :annotation: = {...} 13 | 14 | .. autodata:: PY_300 15 | :annotation: = {...} 16 | 17 | .. autodata:: PY_310 18 | :annotation: = {...} 19 | 20 | .. autodata:: PY_320 21 | :annotation: = {...} 22 | 23 | .. autodata:: PY_330 24 | :annotation: = {...} 25 | 26 | .. autodata:: PY_340 27 | :annotation: = {...} 28 | 29 | .. autodata:: PY_350 30 | :annotation: = {...} 31 | 32 | .. autodata:: PY_352 33 | :annotation: = {...} 34 | 35 | .. autodata:: PY_360 36 | :annotation: = {...} 37 | 38 | .. autodata:: PY_INTERNALS 39 | :annotation: = {260: PY_260, 270: PY_270, 300: PY_300, 310: PY_310, 320: PY_320, 330: PY_330, 340: PY_340, 350: PY_350, 352: PY_352, 360: PY_360} 40 | -------------------------------------------------------------------------------- /docs/pwnypack/rop.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.rop` -- ROP gadgets 2 | =================================== 3 | 4 | .. automodule:: pwnypack.rop 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/pwnypack/shellcode.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.shellcode` -- Shellcode generator 2 | ================================================= 3 | 4 | This module contains functions to generate shellcode. 5 | 6 | Note: 7 | The intended audience for this documentation is the user. Implementation 8 | details are left out where possible. 9 | 10 | The idea is that you provide a shellcode generator environment with a 11 | highlevel declarative representation of the shellcode your want to 12 | assemble and the environment fills in the specifics. 13 | 14 | The generic environments target X86, X86_64, ARM, ARM Thumb, ARM Thumb 15 | Mixed and AArch64 on the Linux OS. No restrictions are made on what kind of 16 | bytes end up in the binary output. If you use buffers, the code segment will 17 | need to be writable if you use the ``Mutable`` variants. The ``Stack`` 18 | variants require an initialized stack that is large enough to hold all the 19 | allocated data and buffers. 20 | 21 | X86: 22 | 23 | - :class:`~pwnypack.shellcode.x86.linux.LinuxX86Mutable` 24 | - :class:`~pwnypack.shellcode.x86.linux.LinuxX86Stack` 25 | 26 | X86_64: 27 | 28 | - :class:`~pwnypack.shellcode.x86_64.linux.LinuxX86_64Mutable` 29 | - :class:`~pwnypack.shellcode.x86_64.linux.LinuxX86_64Stack` 30 | 31 | ARM: 32 | 33 | - :class:`~pwnypack.shellcode.arm.linux.LinuxARMMutable` 34 | - :class:`~pwnypack.shellcode.arm.linux.LinuxARMStack` 35 | 36 | ARM Thumb: 37 | 38 | - :class:`~pwnypack.shellcode.arm.linux.LinuxARMThumbMutable` 39 | - :class:`~pwnypack.shellcode.arm.linux.LinuxARMThumbStack` 40 | 41 | ARM with modeswitch to Thumb mode: 42 | 43 | - :class:`~pwnypack.shellcode.arm.linux.LinuxARMThumbMixed` 44 | - :class:`~pwnypack.shellcode.arm.linux.LinuxARMThumbStack` 45 | 46 | AArch64: 47 | 48 | - :class:`~pwnypack.shellcode.aarch64.linux.LinuxAArch64Mutable` 49 | - :class:`~pwnypack.shellcode.aarch64.linux.LinuxAArch64Stack` 50 | 51 | Specialized classes are also provided for X86 and X86_64. The 52 | *MutableNullSafe* and *StackNullSafe* variants attempt to generate binary 53 | output that does not contain NUL bytes, carriage returns and line feeds. 54 | 55 | X86: 56 | 57 | - :class:`~pwnypack.shellcode.x86.linux.LinuxX86MutableNullSafe` 58 | - :class:`~pwnypack.shellcode.x86.linux.LinuxX86StackNullSafe` 59 | 60 | X86_64: 61 | 62 | - :class:`~pwnypack.shellcode.x86_64.linux.LinuxX86_64MutableNullSafe` 63 | - :class:`~pwnypack.shellcode.x86_64.linux.LinuxX86_64StackNullSafe` 64 | 65 | Each shellcode environment defines a set of registers that are available on 66 | the architecture and a set of system calls. These are available as properties 67 | of the respective environment. 68 | 69 | The environment also provides a way to allocate strings and buffers. If you 70 | call :meth:`~pwnypack.shellcode.base.BaseEnvironment.alloc_data` with a 71 | bytestring (``str`` on python 2, ``bytes`` on python 3) it will be allocated 72 | verbatim and an :class:`~pwnypack.shellcode.types.Offset` is returned. If 73 | :meth:`~pwnypack.shellcode.base.BaseEnvironment.alloc_data` is called with 74 | a unicode string (``unicode`` on python 2, ``str`` on python 3) it will be 75 | converted to a latin1 based bytestring and terminated with a NUL byte (`\\0`). 76 | 77 | :meth:`~pwnypack.shellcode.base.BaseEnvironment.alloc_buffer` can be used to 78 | allocate an uninitialized block of memory. It will not be embedded in the 79 | shellcode. 80 | 81 | There are two ways to use these shellcode environments: 82 | 83 | - :ref:`Declaratively `. 84 | - :ref:`Imperatively `. 85 | 86 | 87 | .. toctree:: 88 | :hidden: 89 | 90 | shellcode/declarative 91 | shellcode/imperative 92 | 93 | LinuxX86 94 | LinuxX86_64 95 | LinuxARM 96 | LinuxAArch64 97 | 98 | X86 99 | X86_64 100 | ARM 101 | AArch64 102 | Linux 103 | 104 | Base 105 | Python translator 106 | -------------------------------------------------------------------------------- /docs/pwnypack/shellcode/aarch64.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.shellcode.aarch64` -- AArch64 2 | ============================================= 3 | 4 | .. autoclass:: pwnypack.shellcode.aarch64.AArch64 5 | :members: 6 | :show-inheritance: 7 | -------------------------------------------------------------------------------- /docs/pwnypack/shellcode/aarch64_linux.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.shellcode.aarch64.linux` -- Linux AArch64 2 | ========================================================= 3 | 4 | .. autoclass:: pwnypack.shellcode.aarch64.linux.LinuxAArch64Mutable 5 | :show-inheritance: 6 | :members: 7 | 8 | .. autoclass:: pwnypack.shellcode.aarch64.linux.LinuxAArch64Stack 9 | :show-inheritance: 10 | :members: 11 | 12 | .. autoclass:: pwnypack.shellcode.aarch64.linux.LinuxAArch64 13 | :show-inheritance: 14 | :members: 15 | -------------------------------------------------------------------------------- /docs/pwnypack/shellcode/arm.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.shellcode.arm` -- ARM 2 | ===================================== 3 | 4 | .. autoclass:: pwnypack.shellcode.arm.ARM 5 | :members: 6 | :show-inheritance: 7 | 8 | 9 | .. autoclass:: pwnypack.shellcode.arm.thumb.ARMThumb 10 | :members: 11 | :show-inheritance: 12 | 13 | 14 | .. autoclass:: pwnypack.shellcode.arm.thumb_mixed.ARMThumbMixed 15 | :members: 16 | :show-inheritance: 17 | -------------------------------------------------------------------------------- /docs/pwnypack/shellcode/arm_linux.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.shellcode.arm.linux` -- Linux ARM 2 | ================================================= 3 | 4 | .. autoclass:: pwnypack.shellcode.arm.linux.LinuxARMMutable 5 | :show-inheritance: 6 | :members: 7 | 8 | .. autoclass:: pwnypack.shellcode.arm.linux.LinuxARMStack 9 | :show-inheritance: 10 | :members: 11 | 12 | .. autoclass:: pwnypack.shellcode.arm.linux.LinuxARMThumbMutable 13 | :show-inheritance: 14 | :members: 15 | 16 | .. autoclass:: pwnypack.shellcode.arm.linux.LinuxARMThumbStack 17 | :show-inheritance: 18 | :members: 19 | 20 | .. autoclass:: pwnypack.shellcode.arm.linux.LinuxARMThumbMixedMutable 21 | :show-inheritance: 22 | :members: 23 | 24 | .. autoclass:: pwnypack.shellcode.arm.linux.LinuxARMThumbMixedStack 25 | :show-inheritance: 26 | :members: 27 | 28 | .. autoclass:: pwnypack.shellcode.arm.linux.LinuxARM 29 | :show-inheritance: 30 | :members: 31 | 32 | .. autoclass:: pwnypack.shellcode.arm.linux.LinuxARMThumb 33 | :show-inheritance: 34 | :members: 35 | 36 | .. autoclass:: pwnypack.shellcode.arm.linux.LinuxARMThumbMixed 37 | :show-inheritance: 38 | :members: 39 | -------------------------------------------------------------------------------- /docs/pwnypack/shellcode/base.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.shellcode.base` -- Base environment 2 | =================================================== 3 | 4 | .. automodule:: pwnypack.shellcode.base 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/pwnypack/shellcode/declarative.rst: -------------------------------------------------------------------------------- 1 | .. _declarative-shellcode: 2 | 3 | Declaratively defined shellcode 4 | =============================== 5 | 6 | When using the declarative method, you create an instance of the shellcode 7 | environment which you then order to translate a list of high level operations. 8 | 9 | There are two kinds of operations available: 10 | 11 | - :class:`~pwnypack.shellcode.ops.SyscallInvoke`: Invoke a system call. You 12 | don't generally create your own instances directly. Each environment 13 | provides access to any available system calls as members which you call 14 | instead. 15 | - :class:`~pwnypack.shellcode.ops.LoadRegister`: Load a register with a 16 | given value (which can be a literal value, the memory address of a piece 17 | of data or a buffer or the result of a system call). 18 | 19 | Examples: 20 | The following example creates an instance of the LinuxX86 environment 21 | and assembles a piece of shellcode that just calls the exit system call. 22 | 23 | >>> from pwny import * 24 | >>> env = sc.LinuxX86() 25 | >>> env.assemble([ 26 | ... env.sys_exit(0) 27 | ... ]) 28 | '1\xdb\xb8\x01\x00\x00\x00\xcd\x80' 29 | 30 | To demonstrate how registers loading works, here's an example that does 31 | the same thing but in a different way: 32 | 33 | >>> from pwny import * 34 | >>> env = sc.LinuxX86() 35 | >>> env.assemble([ 36 | ... sc.LoadRegister(env.EAX, 0), 37 | ... env.sys_exit(env.EAX) 38 | ... ]) 39 | '1\xc0\x89\xc3\xb8\x01\x00\x00\x00\xcd\x80' 40 | 41 | You can also use strings or bytes. If you use a unicode string, it will 42 | be UTF-8 encoded and zero-terminated. Bytes are allocated verbatim. 43 | 44 | >>> from pwny import * 45 | >>> env = sc.LinuxX86() 46 | >>> env.assemble([ 47 | ... env.sys_write(1, u'hello', 5), 48 | ... env.sys_exit(), 49 | ... ]) 50 | '\xe8\x00\x00\x00\x00]\x83\xc5 \xba\x05\x00\x00\x00\x89\xe9\xbb\x01\x00\x00\x00\xb8\x04\x00\x00\x00\xcd\x801\xdb\xb8\x01\x00\x00\x00\xcd\x80hello\x00' 51 | 52 | Or use lists as syscall arguments. 53 | 54 | >>> from pwny import * 55 | >>> env = sc.LinuxX86() 56 | >>> env.assemble([ 57 | ... env.sys_execve(u'/bin/sh', [u'/bin/sh', None], None) 58 | ... ]) 59 | '\xe8\x00\x00\x00\x00]\x83\xc5\x151\xd21\xc0PU\x89\xe1\x89\xeb\xb8\x0b\x00\x00\x00\xcd\x80/bin/sh\x00' 60 | 61 | Need a buffer to write something to? We've got you covered. 62 | 63 | >>> from pwny import * 64 | >>> env = sc.LinuxX86() 65 | >>> buf = env.alloc_buffer(64) 66 | >>> env.assemble([ 67 | ... env.sys_read(0, buf, buf.length), 68 | ... env.sys_write(1, buf, buf.length), 69 | ... env.sys_exit(0) 70 | ... ]) 71 | '\xba@\x00\x00\x00\x89\xe91\xdb\xb8\x03\x00\x00\x00\xcd\x80\xba@\x00\x00\x00\x89\xe9\xbb\x01\x00\x00\x00\xb8\x04\x00\x00\x00\xcd\x801\xdb\xb8\x01\x00\x00\x00\xcd\x80' 72 | -------------------------------------------------------------------------------- /docs/pwnypack/shellcode/imperative.rst: -------------------------------------------------------------------------------- 1 | .. _imperative-shellcode: 2 | 3 | Imperatively defined shellcode 4 | ============================== 5 | 6 | When using the imperatively defined shellcode, you translate a python function 7 | to a set of shellcode primitives. 8 | 9 | The set of operations you can use in your python function is limited. The 10 | properties of the environment (syscalls, registers, functions) are exposed 11 | as if they were magic globals: you cannot shadow them. From your shellcode 12 | generator you can call syscalls and other primitives of the environment, 13 | assign values to registers, use in-place addition/subtraction on registers 14 | and assign values to locals (f.e. allocated buffers or data). You can also 15 | access globals outside the shellcode generator function (f.e. pwnypack's 16 | packing functions to construct data structures). 17 | 18 | If you want to create a re-usable fragment for a commonly used subroutine, 19 | you can do so by creating a function and decorating it with the 20 | :func:`~pwnypack.shellcode.translate.fragment` decorator. If such a function 21 | is called from within a shellcode function it will be translated in the 22 | context of the current shellcode environment. Do note however that fragments 23 | are inlined in the resulting shellcode, they're not implemented as functions. 24 | 25 | You translate a function by using the environment's 26 | :meth:`~pwnypack.shellcode.base.BaseEnvironment.translate` class method. 27 | 28 | Examples: 29 | 30 | The following example creates an instance of the LinuxX86 environment 31 | and assembles a piece of shellcode that just calls the exit system call. 32 | 33 | >>> from pwny import * 34 | >>> @sc.LinuxX86Mutable.translate 35 | ... def shellcode(): 36 | ... sys_exit(0) 37 | ... 38 | >>> shellcode() 39 | '1\xdb\xb8\x01\x00\x00\x00\xcd\x80' 40 | 41 | To demonstrate how registers loading works, here's an example that does 42 | the same thing but in a different way: 43 | 44 | >>> from pwny import * 45 | >>> @sc.LinuxX86Mutable.translate 46 | ... def shellcode(): 47 | ... EAX = 0 48 | ... sys_exit(EAX) 49 | ... 50 | >>> shellcode() 51 | '1\xc0\x89\xc3\xb8\x01\x00\x00\x00\xcd\x80' 52 | 53 | You can also use strings or bytes. If you use a unicode string, it will 54 | be UTF-8 encoded and zero-terminated. Bytes are allocated verbatim. 55 | 56 | >>> from pwny import * 57 | >>> @sc.LinuxX86Mutable.translate 58 | ... def shellcode(): 59 | ... sys_write(1, u'hello', 5) 60 | ... sys_exit(0) 61 | ... 62 | >>> shellcode() 63 | '\xe8\x00\x00\x00\x00]\x83\xc5 \xba\x05\x00\x00\x00\x89\xe9\xbb\x01\x00\x00\x00\xb8\x04\x00\x00\x00\xcd\x801\xdb\xb8\x01\x00\x00\x00\xcd\x80hello\x00' 64 | 65 | Or use lists as syscall arguments. 66 | 67 | >>> from pwny import * 68 | >>> @sc.LinuxX86Mutable.translate 69 | ... def shellcode(): 70 | ... sys_execve(u'/bin/sh', [u'/bin/sh', None], None) 71 | ... 72 | >>> shellcode() 73 | '\xe8\x00\x00\x00\x00]\x83\xc5\x151\xd21\xc0PU\x89\xe1\x89\xeb\xb8\x0b\x00\x00\x00\xcd\x80/bin/sh\x00' 74 | 75 | Need a buffer to write something to? We've got you covered. 76 | 77 | >>> from pwny import * 78 | >>> @sc.LinuxX86Mutable.translate 79 | ... def shellcode(): 80 | ... buf = alloc_buffer(64) 81 | ... sys_read(0, buf, buf.length) 82 | ... sys_write(1, buf, buf.length) 83 | ... sys_exit(0) 84 | ... 85 | >>> shellcode() 86 | '\xba@\x00\x00\x00\x89\xe91\xdb\xb8\x03\x00\x00\x00\xcd\x80\xba@\x00\x00\x00\x89\xe9\xbb\x01\x00\x00\x00\xb8\x04\x00\x00\x00\xcd\x801\xdb\xb8\x01\x00\x00\x00\xcd\x80' 87 | 88 | You can also pass parameters to the shellcode function. 89 | 90 | >>> from pwny import * 91 | >>> @sc.LinuxX86Mutable.translate 92 | ... def shellcode(command): 93 | ... sys_execve(u'/bin/sh', [u'/bin/sh', command, None], None) 94 | ... 95 | >>> shellcode(u'ls -lR') 96 | '\xe8\x00\x00\x00\x00]\x83\xc5\x1a1\xd21\xc0PU\x8dE\x07P\x89\xe1\x8d]\x07\xb8\x0b\x00\x00\x00\xcd\x80ls -lR\x00/bin/sh\x00' 97 | 98 | Combining all that, here's a somewhat larger example that also 99 | demonstrates using global and local variables, register aliases and 100 | fragments to implement a connect-back shell:: 101 | 102 | from pwny import * 103 | import socket 104 | 105 | @sc.fragment 106 | def pack_sockaddr_in(addr, port): 107 | # Prepare the sockaddr_in struct: 108 | return pack( 109 | 'H2s4s8s', 110 | socket.AF_INET, 111 | P16(port, endian=Target.Endian.big), 112 | socket.inet_aton(addr), 113 | b'........', # Doesn't really have to be \0. 114 | target=target # This is a fragment, target refers to the 115 | # environment's target attribute. 116 | ) 117 | 118 | @sc.fragment 119 | def exec_to_fd(fd, executable): 120 | # Set up register aliases (for convenience): 121 | arg0 = SYSCALL_ARG_MAP[0] 122 | arg1 = SYSCALL_ARG_MAP[1] 123 | 124 | # Call dup2 to connect stdin/out/err to the fd: 125 | sys_dup2(fd, 0) 126 | arg1 += 1; sys_dup2(arg0, arg1) 127 | arg1 += 1; sys_dup2(arg0, arg1) 128 | 129 | # Execute the command: 130 | sys_execve(executable, [executable, None], None) 131 | 132 | @sc.LinuxX86Mutable.translate 133 | def shell_connect(addr, port, shell=u'/bin/sh'): 134 | # Pack the sockaddr_in struct using a fragment: 135 | sockaddr = pack_sockaddr_in(addr, port) 136 | 137 | # Set up register alias (for convenience): 138 | socket_reg = SYSCALL_ARG_MAP[4] 139 | 140 | # Prepare socket: 141 | socket_reg = sys_socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) 142 | sys_connect(socket_reg, sockaddr, len(sockaddr)) 143 | 144 | # Call the fragment that calls dup2 and execve: 145 | exec_to_fd(socket_reg, shell) 146 | -------------------------------------------------------------------------------- /docs/pwnypack/shellcode/linux.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.shellcode.linux` -- Linux OS 2 | ============================================ 3 | 4 | .. automodule:: pwnypack.shellcode.linux 5 | :members: 6 | :show-inheritance: 7 | -------------------------------------------------------------------------------- /docs/pwnypack/shellcode/translate.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.shellcode.translate` -- Python translator 2 | ========================================================= 3 | 4 | .. automodule:: pwnypack.shellcode.translate 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/pwnypack/shellcode/x86.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.shellcode.x86` -- X86 2 | ===================================== 3 | 4 | .. autoclass:: pwnypack.shellcode.x86.X86 5 | :members: 6 | :show-inheritance: 7 | -------------------------------------------------------------------------------- /docs/pwnypack/shellcode/x86_64.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.shellcode.x86_64` -- X86_64 2 | =========================================== 3 | 4 | .. autoclass:: pwnypack.shellcode.x86_64.X86_64 5 | :show-inheritance: 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/pwnypack/shellcode/x86_64_linux.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.shellcode.x86_64.linux` -- Linux X86_64 2 | ======================================================= 3 | 4 | .. autoclass:: pwnypack.shellcode.x86_64.linux.LinuxX86_64Mutable 5 | :show-inheritance: 6 | :members: 7 | 8 | .. autoclass:: pwnypack.shellcode.x86_64.linux.LinuxX86_64Stack 9 | :show-inheritance: 10 | :members: 11 | 12 | .. autoclass:: pwnypack.shellcode.x86_64.linux.LinuxX86_64MutableNullSafe 13 | :show-inheritance: 14 | :members: 15 | 16 | .. autoclass:: pwnypack.shellcode.x86_64.linux.LinuxX86_64StackNullSafe 17 | :show-inheritance: 18 | :members: 19 | 20 | .. autoclass:: pwnypack.shellcode.x86_64.linux.LinuxX86_64 21 | :show-inheritance: 22 | :members: 23 | -------------------------------------------------------------------------------- /docs/pwnypack/shellcode/x86_linux.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.shellcode.x86.linux` -- Linux X86 2 | ================================================= 3 | 4 | .. autoclass:: pwnypack.shellcode.x86.linux.LinuxX86Mutable 5 | :show-inheritance: 6 | :members: 7 | 8 | .. autoclass:: pwnypack.shellcode.x86.linux.LinuxX86Stack 9 | :show-inheritance: 10 | :members: 11 | 12 | .. autoclass:: pwnypack.shellcode.x86.linux.LinuxX86MutableNullSafe 13 | :show-inheritance: 14 | :members: 15 | 16 | .. autoclass:: pwnypack.shellcode.x86.linux.LinuxX86StackNullSafe 17 | :show-inheritance: 18 | :members: 19 | 20 | .. autoclass:: pwnypack.shellcode.x86.linux.LinuxX86 21 | :show-inheritance: 22 | :members: 23 | -------------------------------------------------------------------------------- /docs/pwnypack/target.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.target` -- Target definition 2 | ============================================ 3 | 4 | .. automodule:: pwnypack.target 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/pwnypack/util.rst: -------------------------------------------------------------------------------- 1 | :mod:`~pwnypack.util` -- Utility functions 2 | ========================================== 3 | 4 | .. automodule:: pwnypack.util 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx>=1.4.1 2 | kwonly-args 3 | -------------------------------------------------------------------------------- /pwny/__init__.py: -------------------------------------------------------------------------------- 1 | from pwnypack.target import * 2 | from pwnypack.packing import * 3 | from pwnypack.util import * 4 | from pwnypack.flow import * 5 | from pwnypack.asm import * 6 | from pwnypack.elf import * 7 | from pwnypack.codec import * 8 | from pwnypack.shell import * 9 | from pwnypack.pwnbook import * 10 | from pwnypack.rop import * 11 | from pwnypack.fmtstring import * 12 | from pwnypack.php import * 13 | from pwnypack.pickle import * 14 | from pwnypack.py_internals import * 15 | from pwnypack.marshal import * 16 | from pwnypack.oracle import * 17 | from pwny import bc 18 | from pwny import sc 19 | -------------------------------------------------------------------------------- /pwny/bc.py: -------------------------------------------------------------------------------- 1 | from pwnypack.bytecode import * 2 | -------------------------------------------------------------------------------- /pwny/sc.py: -------------------------------------------------------------------------------- 1 | from pwnypack.shellcode.ops import * 2 | from pwnypack.shellcode.translate import * 3 | from pwnypack.shellcode.x86.linux import * 4 | from pwnypack.shellcode.x86_64.linux import * 5 | from pwnypack.shellcode.arm.linux import * 6 | from pwnypack.shellcode.aarch64.linux import * 7 | -------------------------------------------------------------------------------- /pwnypack/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edibledinos/pwnypack/e0a5a8e6ef3f4f1f7e1b91ee379711f4a49cb0e6/pwnypack/__init__.py -------------------------------------------------------------------------------- /pwnypack/fmtstring.py: -------------------------------------------------------------------------------- 1 | """ 2 | The fmtstring module allows you to build format strings that can be used to 3 | exploit format string bugs (`printf(buf);`). 4 | """ 5 | 6 | 7 | import pwnypack.target 8 | import pwnypack.packing 9 | 10 | 11 | __all__ = ['fmtstring'] 12 | 13 | 14 | FMTSTRING_OPS = { 15 | 1: b'hhn', 16 | 2: b'hn', 17 | 4: b'n', 18 | } 19 | 20 | 21 | def fmtstring(offset, writes, written=0, max_width=2, target=None): 22 | """ 23 | Build a format string that writes given data to given locations. Can be 24 | used easily create format strings to exploit format string bugs. 25 | 26 | `writes` is a list of 2- or 3-item tuples. Each tuple represents a memory 27 | write starting with an absolute address, then the data to write as an 28 | integer and finally the width (1, 2, 4 or 8) of the write. 29 | 30 | :func:`fmtstring` will break up the writes and try to optimise the order 31 | to minimise the amount of dummy output generated. 32 | 33 | Args: 34 | offset(int): The parameter offset where the format string start. 35 | writes(list): A list of 2 or 3 item tuples. 36 | written(int): How many bytes have already been written before the 37 | built format string starts. 38 | max_width(int): The maximum width of the writes (1, 2 or 4). 39 | target(:class:`pwnypack.target.Target`): The target architecture. 40 | 41 | Returns: 42 | bytes: The format string that will execute the specified memory 43 | writes. 44 | 45 | Example: 46 | The following example will (on a 32bit architecture) build a format 47 | string that write 0xc0debabe to the address 0xdeadbeef and the byte 48 | 0x90 to 0xdeadbeef + 4 assuming that the input buffer is located at 49 | offset 3 on the stack. 50 | 51 | >>> from pwny import * 52 | >>> fmtstring(3, [(0xdeadbeef, 0xc0debabe), (0xdeadbeef + 4, 0x90, 1)]) 53 | """ 54 | 55 | if max_width not in (1, 2, 4): 56 | raise ValueError('max_width should be 1, 2 or 4') 57 | 58 | if target is None: 59 | target = pwnypack.target.target 60 | 61 | addrs = [] 62 | cmds = [] 63 | piece_writes = [] 64 | 65 | for write in writes: 66 | if len(write) == 2: 67 | addr, value = write 68 | width = target.bits // 8 69 | else: 70 | addr, value, width = write 71 | if width not in (1, 2, 4, 8): 72 | raise ValueError('Invalid write width') 73 | 74 | piece_width = min(max_width, width) 75 | piece_value = getattr(pwnypack.packing, 'P%d' % (8 * width))(value, target=target) 76 | piece_unpack = getattr(pwnypack.packing, 'U%d' % (piece_width * 8)) 77 | 78 | for i in range(0, width, piece_width): 79 | piece_writes.append((piece_width, addr, piece_unpack(piece_value[i:i + piece_width], target=target))) 80 | addr += piece_width 81 | 82 | written += len(piece_writes) * int(target.bits) // 8 83 | 84 | piece_writes.sort(key=lambda w_a_v: (w_a_v[2] - written) % (2 ** (max_width * 8))) 85 | 86 | for piece_width, piece_addr, piece_value in piece_writes: 87 | addrs.append(pwnypack.packing.P(piece_addr, target=target)) 88 | 89 | piece_modulo = 2 ** (piece_width * 8) 90 | 91 | padding = (piece_value - written) % piece_modulo 92 | if padding: 93 | cmds.append(b'%' + str(padding).encode('ascii') + b'c') 94 | written = piece_value 95 | 96 | cmds.append(b'%' + str(offset).encode('ascii') + b'$' + FMTSTRING_OPS[piece_width]) 97 | offset += 1 98 | 99 | return b''.join(addrs + cmds) 100 | -------------------------------------------------------------------------------- /pwnypack/ipython_ext.py: -------------------------------------------------------------------------------- 1 | import shlex 2 | import pwny 3 | import pwnypack.main 4 | from IPython.core.magic import register_line_magic 5 | 6 | 7 | __all__ = [] 8 | 9 | 10 | def wrap_main(func_name): 11 | def wrapper(line): 12 | pwnypack.main.main([func_name] + shlex.split(line)) 13 | return wrapper 14 | 15 | 16 | for f_name, f_dict in pwnypack.main.MAIN_FUNCTIONS.items(): 17 | register_line_magic(f_name)(wrap_main(f_name)) 18 | 19 | 20 | def load_ipython_extension(ipython): 21 | ipython.push(vars(pwny)) 22 | 23 | 24 | def unload_ipython_extension(ipython): 25 | ipython.drop_by_id(vars(pwny)) 26 | -------------------------------------------------------------------------------- /pwnypack/main.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | try: 3 | from collections import OrderedDict 4 | except ImportError: 5 | from ordereddict import OrderedDict 6 | import io 7 | import os 8 | import sys 9 | import argparse 10 | import six 11 | import pwnypack.target 12 | 13 | 14 | __all__ = [] 15 | 16 | 17 | MAIN_FUNCTIONS = OrderedDict() 18 | 19 | 20 | def register(name=None, symlink=True): 21 | def wrapper(f): 22 | if name is None: 23 | f_name = f.__name__ 24 | else: 25 | f_name = name 26 | if f_name in MAIN_FUNCTIONS: 27 | raise ValueError('Duplicate application %s' % f_name) 28 | MAIN_FUNCTIONS[f_name] = { 29 | 'callable': f, 30 | 'symlink': symlink, 31 | } 32 | return f 33 | return wrapper 34 | 35 | 36 | def binary_value_or_stdin(value): 37 | """ 38 | Return fsencoded value or read raw data from stdin if value is None. 39 | """ 40 | if value is None: 41 | reader = io.open(sys.stdin.fileno(), mode='rb', closefd=False) 42 | return reader.read() 43 | elif six.PY3: 44 | return os.fsencode(value) 45 | else: 46 | return value 47 | 48 | 49 | def string_value_or_stdin(value): 50 | """ 51 | Return value or read string from stdin if value is None. 52 | """ 53 | if value is None: 54 | return sys.stdin.read() 55 | else: 56 | return value 57 | 58 | 59 | def add_target_arguments(parser): 60 | parser.add_argument( 61 | '--arch', '-a', 62 | choices=[v.value for v in pwnypack.target.Target.Arch.__members__.values()], 63 | default=None, 64 | help='the target architecture', 65 | ) 66 | parser.add_argument( 67 | '--bits', '-b', 68 | type=int, 69 | choices=[v.value for v in pwnypack.target.Target.Bits.__members__.values()], 70 | default=None, 71 | help='the target word size', 72 | ) 73 | parser.add_argument( 74 | '--endian', '-e', 75 | choices=pwnypack.target.Target.Endian.__members__.keys(), 76 | default=None, 77 | help='the target endianness', 78 | ) 79 | 80 | 81 | def target_from_arguments(args): 82 | if args.endian is not None: 83 | endian = pwnypack.target.Target.Endian.__members__[args.endian] 84 | else: 85 | endian = None 86 | return pwnypack.target.Target(arch=args.arch, bits=args.bits, endian=endian) 87 | 88 | 89 | @register(symlink=False) 90 | def symlink(parser, cmd, args): 91 | """ 92 | Set up symlinks for (a subset of) the pwny apps. 93 | """ 94 | 95 | parser.add_argument( 96 | 'apps', 97 | nargs=argparse.REMAINDER, 98 | help='Which apps to create symlinks for.' 99 | ) 100 | args = parser.parse_args(args) 101 | 102 | base_dir, pwny_main = os.path.split(sys.argv[0]) 103 | 104 | for app_name, config in MAIN_FUNCTIONS.items(): 105 | if not config['symlink'] or (args.apps and app_name not in args.apps): 106 | continue 107 | dest = os.path.join(base_dir, app_name) 108 | if not os.path.exists(dest): 109 | print('Creating symlink %s' % dest) 110 | os.symlink(pwny_main, dest) 111 | else: 112 | print('Not creating symlink %s (file already exists)' % dest) 113 | 114 | 115 | def main(args=sys.argv): 116 | def usage(): 117 | global MAIN_FUNCTIONS 118 | print('Welcome to pwny!') 119 | print() 120 | print('Available apps:') 121 | longest_app_name = max(len(app) for app in MAIN_FUNCTIONS) 122 | for app, config in MAIN_FUNCTIONS.items(): 123 | f = config['callable'] 124 | fmt = ' - %%-%ds %%s' % longest_app_name 125 | print(fmt % (app, f.__doc__.strip().split('\n')[0])) 126 | print() 127 | sys.exit(1) 128 | 129 | # Import everything so all main functions are registered. 130 | import pwny 131 | 132 | global MAIN_FUNCTIONS 133 | 134 | app = os.path.basename(args[0]) 135 | app_args = args[1:] 136 | 137 | if app not in MAIN_FUNCTIONS: 138 | if len(args) < 2 or app_args[0] not in MAIN_FUNCTIONS: 139 | usage() 140 | prog = '%s %s' % (app, app_args[0]) 141 | app, app_args = app_args[0], app_args[1:] 142 | else: 143 | prog = app 144 | 145 | f = MAIN_FUNCTIONS[app]['callable'] 146 | parser = argparse.ArgumentParser( 147 | prog=prog, 148 | description=f.__doc__, 149 | ) 150 | parser.add_argument( 151 | '-f', '--format', 152 | dest='format', 153 | choices=['raw', 'hex', 'py', 'sh', 'b64'], 154 | default='raw', 155 | help='set output format' 156 | ) 157 | parser.add_argument( 158 | '-n', '--no-newline', 159 | dest='no_newline', 160 | action='store_const', 161 | const=True, 162 | help='inhibit newline after output (off when run from tty)' 163 | ) 164 | 165 | output = f(parser, app, app_args) 166 | 167 | if output is not None: 168 | if six.PY3: 169 | output_bytes = os.fsencode(output) 170 | else: 171 | output_bytes = output 172 | 173 | args = parser.parse_args(app_args) 174 | if args.no_newline or not sys.stdout.isatty(): 175 | end = '' 176 | else: 177 | end = '\n' 178 | 179 | if args.format == 'raw': 180 | if isinstance(output, six.binary_type): 181 | writer = io.open(sys.stdout.fileno(), mode='wb', closefd=False) 182 | writer.write(output) 183 | writer.write(end if not six.PY3 else os.fsencode(end)) 184 | writer.close() 185 | else: 186 | print(output, end=end) 187 | elif args.format == 'hex': 188 | print(pwny.enhex(output_bytes), end=end) 189 | elif args.format == 'py': 190 | print(repr(output), end=end) 191 | elif args.format == 'sh': 192 | r = repr(output) 193 | if r.startswith('b\''): 194 | r = r[1:] 195 | print('$' + r, end=end) 196 | elif args.format == 'b64': 197 | print(pwny.enb64(output_bytes), end=end) 198 | -------------------------------------------------------------------------------- /pwnypack/marshal.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains functions to load and unserialize data (including .pyc 3 | files) serialized using the :mod:`marshal` module on most version of python. 4 | """ 5 | 6 | import datetime 7 | from enum import IntEnum 8 | 9 | import six 10 | 11 | from pwnypack.bytecode import CodeObject 12 | from pwnypack.py_internals import PY_INTERNALS, get_py_internals 13 | from pwnypack.packing import U16, u16, U32, u32, u64, unpack 14 | from pwnypack.target import Target 15 | 16 | 17 | __all__ = ['marshal_load', 'marshal_loads', 'pyc_load', 'pyc_loads'] 18 | 19 | 20 | MAGIC_MAP = dict( 21 | (internals['magic'], internals) 22 | for version, internals in six.iteritems(PY_INTERNALS) 23 | if version is not None 24 | ) 25 | MARSHAL_TARGET = Target(Target.Arch.unknown, Target.Bits.bits_32, Target.Endian.little) 26 | NULL = object() 27 | PyLong_MARSHAL_SHIFT = 15 28 | FLAG_REF = 0x80 29 | 30 | 31 | class ObjectType(IntEnum): 32 | """ 33 | Enumeration used internally to describe / parse a marshal object type. 34 | """ 35 | 36 | null = ord('0') 37 | none = ord('N') 38 | false = ord('F') 39 | true = ord('T') 40 | stopiter = ord('S') 41 | ellipsis = ord('.') 42 | int = ord('i') 43 | int64 = ord('I') 44 | float = ord('f') 45 | binary_float = ord('g') 46 | complex = ord('x') 47 | binary_complex = ord('y') 48 | long = ord('l') 49 | string = ord('s') 50 | stringref = ord('R') 51 | interned = ord('t') 52 | ref = ord('r') 53 | tuple = ord('(') 54 | list = ord('[') 55 | dict = ord('{') 56 | code = ord('c') 57 | unicode = ord('u') 58 | unknown = ord('?') 59 | set = ord('<') 60 | frozenset = ord('>') 61 | ascii = ord('a') 62 | ascii_interned = ord('A') 63 | small_tuple = ord(')') 64 | short_ascii = ord('z') 65 | short_ascii_interned = ord('Z') 66 | 67 | 68 | def marshal_load(fp, origin=None): 69 | """ 70 | Unserialize data serialized with :func:`marshal.dump`. This function 71 | works across python versions. Marshalled code objects are returned as 72 | instances of :class:`~pwnypack.bytecode.CodeObject`. 73 | 74 | Arguments: 75 | fp(file): A file or file-like object that contains the serialized 76 | data. 77 | origin(dict): The opcode specification of the python version that 78 | generated the data. If you provide ``None``, the specs for the 79 | currently running python version will be used. 80 | 81 | Returns: 82 | The unserialized data. 83 | """ 84 | 85 | origin = get_py_internals(origin) 86 | version = origin['version'] 87 | 88 | refs = [] 89 | 90 | def ref(o, flags): 91 | if flags & FLAG_REF: 92 | refs.append(o) 93 | return o 94 | 95 | def read_byte(): 96 | return six.byte2int(fp.read(1)) 97 | 98 | def read_short(): 99 | return u16(fp.read(2), target=MARSHAL_TARGET) 100 | 101 | def read_long(): 102 | return u32(fp.read(4), target=MARSHAL_TARGET) 103 | 104 | def read_int64(): 105 | return u64(fp.read(8), target=MARSHAL_TARGET) 106 | 107 | def read_float_binary(): 108 | return unpack('d', fp.read(8), target=MARSHAL_TARGET)[0] 109 | 110 | def read_bytes(): 111 | return fp.read(read_long()) 112 | 113 | def read_bytes_short(): 114 | return fp.read(read_byte()) 115 | 116 | def read_float_text(): 117 | return float(read_bytes_short()) 118 | 119 | def read_object(): 120 | c = six.byte2int(fp.read(1)) 121 | flags = c & FLAG_REF 122 | c = ObjectType(c & ~FLAG_REF) 123 | 124 | if c is ObjectType.null: 125 | return NULL 126 | elif c is ObjectType.none: 127 | return None 128 | elif c is ObjectType.stopiter: 129 | return StopIteration 130 | elif c is ObjectType.ellipsis: 131 | return Ellipsis 132 | elif c is ObjectType.false: 133 | return False 134 | elif c is ObjectType.true: 135 | return True 136 | elif c is ObjectType.int: 137 | return ref(read_long(), flags) 138 | elif c is ObjectType.int64: 139 | return ref(read_int64(), flags) 140 | elif c is ObjectType.long: 141 | n = read_long() 142 | r = sum( 143 | read_short() << (i * PyLong_MARSHAL_SHIFT) 144 | for i in range(abs(n)) 145 | ) 146 | return ref(-r if n < 0 else r, flags) 147 | elif c is ObjectType.float: 148 | return ref(read_float_text(), flags) 149 | elif c is ObjectType.binary_float: 150 | return ref(read_float_binary(), flags) 151 | elif c is ObjectType.complex: 152 | real = read_float_text() 153 | imag = read_float_text() 154 | return ref(complex(real, imag), flags) 155 | elif c is ObjectType.binary_complex: 156 | real = read_float_binary() 157 | imag = read_float_binary() 158 | return ref(complex(real, imag), flags) 159 | elif c is ObjectType.string: 160 | return ref(read_bytes(), flags) 161 | elif c is ObjectType.unicode: 162 | return ref(read_bytes().decode('utf-8'), flags) 163 | elif c is ObjectType.interned: 164 | if version < 30: 165 | return ref(read_bytes(), FLAG_REF) 166 | else: 167 | return ref(read_bytes().decode('utf-8'), flags) 168 | elif c is ObjectType.ascii: 169 | return ref(read_bytes().decode('ascii'), flags) 170 | elif c is ObjectType.ascii_interned: 171 | return ref(read_bytes().decode('ascii'), flags) 172 | elif c is ObjectType.short_ascii: 173 | return ref(read_bytes_short().decode('ascii'), flags) 174 | elif c is ObjectType.short_ascii_interned: 175 | return ref(read_bytes_short().decode('ascii'), flags) 176 | elif c in (ObjectType.tuple, ObjectType.small_tuple, ObjectType.frozenset): 177 | ref_index = len(refs) 178 | ref(NULL, flags) 179 | r_type = frozenset if c is ObjectType.frozenset else tuple 180 | n = read_byte() if c is ObjectType.small_tuple else read_long() 181 | r = r_type(read_object() for _ in range(n)) 182 | if flags & FLAG_REF: 183 | refs[ref_index] = r 184 | return r 185 | elif c is ObjectType.list: 186 | r = ref([], flags) 187 | for _ in range(read_long()): 188 | r.append(read_object()) 189 | return r 190 | elif c is ObjectType.set: 191 | r = ref(set(), flags) 192 | for _ in range(read_long()): 193 | r.add(read_object()) 194 | return r 195 | elif c is ObjectType.dict: 196 | r = ref({}, flags) 197 | while True: 198 | k = read_object() 199 | if k is NULL: 200 | break 201 | r[k] = read_object() 202 | return r 203 | elif c in (ObjectType.stringref, ObjectType.ref): 204 | return refs[read_long()] 205 | elif c is ObjectType.code: 206 | ref_index = len(refs) 207 | ref(NULL, flags) 208 | 209 | co_argcount = read_long() 210 | if version < 30: 211 | co_kwonlyargcount = 0 212 | else: 213 | co_kwonlyargcount = read_long() 214 | co_nlocals = read_long() 215 | co_stacksize = read_long() 216 | co_flags = read_long() 217 | co_code = read_object() 218 | co_consts = read_object() 219 | co_names = read_object() 220 | co_varnames = read_object() 221 | co_freevars = read_object() 222 | co_cellvars = read_object() 223 | co_filename = read_object() 224 | co_name = read_object() 225 | co_firstlineno = read_long() 226 | co_lnotab = read_object() 227 | 228 | r = CodeObject( 229 | co_argcount, 230 | co_kwonlyargcount, 231 | co_nlocals, 232 | co_stacksize, 233 | co_flags, 234 | co_code, 235 | co_consts, 236 | co_names, 237 | co_varnames, 238 | co_filename, 239 | co_name, 240 | co_firstlineno, 241 | co_lnotab, 242 | co_freevars, 243 | co_cellvars, 244 | origin, 245 | ) 246 | if flags & FLAG_REF: 247 | refs[ref_index] = r 248 | return r 249 | else: 250 | raise ValueError('Unexpected object type %s.' % c) 251 | 252 | return read_object() 253 | 254 | 255 | def marshal_loads(data, origin=None): 256 | """ 257 | Load data serialized with :func:`marshal.dump` from a bytestring. 258 | 259 | Arguments: 260 | data(bytes): The marshalled data. 261 | origin(dict): The opcode specification of the python version that 262 | generated the data. If you provide ``None``, the specs for the 263 | currently running python version will be used. 264 | 265 | Returns: 266 | The unserialized data. 267 | """ 268 | 269 | return marshal_load(six.BytesIO(data), origin) 270 | 271 | 272 | class PycFile(object): 273 | """ 274 | This class describes a parsed .pyc file and is returned by 275 | :func:`pyc_load` and :func:`pyc_loads`. 276 | """ 277 | 278 | def __init__(self, magic, origin, timestamp, file_size, code): 279 | self.magic = magic #: The magic number of the python version that created the file. 280 | self.origin = origin #: The internals of the accompanying python version. 281 | self.timestamp = timestamp #: The timestamp of the original source file. 282 | self.file_size = file_size #: The original source file's since (or None if version < 3.3). 283 | self.code = code #: The :class:`CodeObject` instance that represents the contents of the .pyc file. 284 | 285 | 286 | def pyc_load(fp): 287 | """ 288 | Load a .pyc file from a file-like object. 289 | 290 | Arguments: 291 | fp(file): The file-like object to read. 292 | 293 | Returns: 294 | PycFile: The parsed representation of the .pyc file. 295 | """ 296 | 297 | magic_1 = U16(fp.read(2), target=MARSHAL_TARGET) 298 | magic_2 = U16(fp.read(2), target=MARSHAL_TARGET) 299 | 300 | internals = MAGIC_MAP.get(magic_1) 301 | if internals is None: 302 | raise ValueError('Invalid or unknown magic (%d).' % magic_1) 303 | 304 | if magic_2 != 2573: 305 | raise ValueError('Invalid secondary magic (%d).' % magic_2) 306 | 307 | timestamp = datetime.datetime.fromtimestamp(U32(fp.read(4), target=MARSHAL_TARGET)) 308 | 309 | if internals['version'] >= 33: 310 | file_size = U32(fp.read(4)) 311 | else: 312 | file_size = None 313 | 314 | code_object = marshal_load(fp, internals) 315 | 316 | return PycFile(magic_1, internals, timestamp, file_size, code_object) 317 | 318 | 319 | def pyc_loads(data): 320 | """ 321 | Load a .pyc file from a bytestring. 322 | 323 | Arguments: 324 | data(bytes): The content of the .pyc file. 325 | 326 | Returns: 327 | PycFile: The parsed representation of the .pyc file. 328 | """ 329 | 330 | return pyc_load(six.BytesIO(data)) 331 | -------------------------------------------------------------------------------- /pwnypack/oracle.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module provides a functions that, given an oracle function that returns 3 | ``True`` when a message is properly padded and ``False`` otherwise, will 4 | decrypt or encrypt a given message assuming that the underlying cipher 5 | operates in CBC mode. 6 | """ 7 | 8 | from __future__ import print_function, division 9 | 10 | import functools 11 | import multiprocessing 12 | import threading 13 | 14 | import os 15 | from six.moves import map, range 16 | 17 | __all__ = ['padding_oracle_decrypt', 'padding_oracle_encrypt'] 18 | 19 | 20 | def interruptable_iter(event, iterable): 21 | for value in iterable: 22 | yield value 23 | if event.is_set(): 24 | break 25 | 26 | 27 | def consult_oracle(oracle, chunk, block, is_last_byte): 28 | if not oracle(bytes(chunk + block)): 29 | return False 30 | 31 | if is_last_byte: 32 | chunk[-2] ^= 0x01 33 | if not oracle(bytes(chunk + block)): 34 | return False 35 | 36 | return True 37 | 38 | 39 | def check_padding_decrypt(event, oracle, block_len, chunk, block, plain, i, j): 40 | if event.is_set(): 41 | return None 42 | 43 | chunk, plain = chunk[:], plain[:] 44 | 45 | plain[i] = j 46 | chunk[i] ^= j 47 | 48 | if consult_oracle(oracle, chunk, block, i == block_len - 1): 49 | event.set() 50 | return plain 51 | 52 | 53 | def decrypt_block(oracle, block_len, alphabet, pool, progress, params): 54 | start, prev, block, prefix, suffix, is_last_block = params 55 | 56 | if pool is not None: 57 | event_factory = multiprocessing.Manager().Event 58 | map_func = pool.imap_unordered 59 | else: 60 | event_factory = threading.Event 61 | map_func = map 62 | 63 | plain = bytearray([0] * block_len) 64 | 65 | for i, j in enumerate(prefix): 66 | plain[i] = j 67 | if progress is not None: 68 | progress(start + i, j) 69 | 70 | for i, j in enumerate(reversed(suffix)): 71 | plain[block_len - i - 1] = j 72 | if progress is not None: 73 | progress(start + block_len - i - 1, j) 74 | 75 | in_padding = is_last_block and not suffix 76 | 77 | i = block_len - 1 - len(suffix) 78 | while i >= len(prefix): 79 | chunk = prev[:] 80 | 81 | for k in range(i, block_len): 82 | chunk[k] ^= plain[k] ^ (block_len - i) 83 | 84 | event = event_factory() 85 | f = functools.partial(check_padding_decrypt, event, oracle, block_len, chunk, block, plain, i) 86 | 87 | if in_padding: 88 | _alphabet = range(1, 17) 89 | else: 90 | _alphabet = alphabet 91 | 92 | for result in map_func(f, interruptable_iter(event, _alphabet)): 93 | if result is not None: 94 | plain = result 95 | 96 | if not event.is_set(): 97 | raise RuntimeError('Oracle is unstable') 98 | 99 | if in_padding: 100 | in_padding = False 101 | pad_value = plain[-1] 102 | for j in range(block_len - pad_value, i): 103 | plain[j] = pad_value 104 | if progress is not None: 105 | progress(start + j, pad_value) 106 | i -= pad_value 107 | else: 108 | if progress is not None: 109 | progress(start + i, plain[i]) 110 | i -= 1 111 | 112 | return plain 113 | 114 | 115 | def block_pairs(block_len, data, known_prefix, known_suffix): 116 | data_len = len(data) 117 | suffix_len = len(known_suffix) 118 | for prev, start, suffix_start in zip(range(data_len - block_len * 2, -1, -block_len), 119 | range(data_len - block_len, -1, -block_len), 120 | range(suffix_len - block_len, -data_len - 1, -block_len)): 121 | yield ( 122 | prev, 123 | data[prev:start], 124 | data[start:start + block_len], 125 | known_prefix[prev:start], 126 | known_suffix[max(suffix_start, 0):max(suffix_start + block_len, 0)], 127 | start + block_len == data_len 128 | ) 129 | 130 | 131 | def padding_oracle_decrypt(oracle, ciphertext, known_prefix=b'', known_suffix=b'', block_size=128, 132 | alphabet=None, pool=None, block_pool=None, progress=None): 133 | """ 134 | Decrypt ciphertext using an oracle function that returns ``True`` if the 135 | provided ciphertext is correctly PKCS#7 padded after decryption. The 136 | cipher needs to operate in CBC mode. 137 | 138 | Args: 139 | oracle(callable): The oracle function. Will be called repeatedly with 140 | a chunk of ciphertext. 141 | ciphertext(bytes): The data to decrypt. Should include the IV at the 142 | start. 143 | known_prefix(bytes): If the start of the plaintext is known, it can be 144 | provided to skip decrypting the known prefix. 145 | known_suffix(bytes): If the end of the plaintext is known, it can be 146 | provided to skip decrypting the known suffix. Should include 147 | padding. 148 | block_size(int): The cipher's block size in bits. 149 | alphabet(bytes): Optimize decryption if you know which characters the 150 | plaintext will consist of. 151 | pool(multiprocessing.Pool): A multiprocessing pool to use to 152 | parallelize the decryption. This pool is used to call the oracle 153 | function. Fairly heavy due to the required inter-process state 154 | synchronization. If ``None`` (the default), no multiprocessing 155 | will be used. 156 | block_pool(multiprocessing.Pool): A multiprocessing pool to use to 157 | parallelize the decryption. This pool is used to decrypt entire 158 | blocks in parallel. When decrypting ciphertext consisting of 159 | multiple blocks, it is usually more efficient than using the 160 | ``pool`` argument. If ``None`` (the default), no multiprocessing 161 | will be used. 162 | progress(callable): A callable that will be called each time a new 163 | byte is decrypted. Is called with the positition of the character 164 | in the plaintext result and the character itself. 165 | 166 | Returns: 167 | bytes: The decrypted data with its PKCS#7 padding stripped. 168 | 169 | Raises: 170 | RuntimeError: Raised if the oracle behaves unpredictable. 171 | 172 | Example: 173 | >>> from pwny import * 174 | >>> with multiprocessing.Pool(5) as pool: 175 | >>> print(padding_oracle_decrypt(oracle_function, encrypted_data, pool=pool)) 176 | b'decrypted data' 177 | """ 178 | 179 | block_len = block_size // 8 180 | assert len(ciphertext) % block_len == 0 and len(ciphertext) >= 2 * block_len 181 | 182 | known_prefix = bytearray(known_prefix) 183 | known_suffix = bytearray(known_suffix) 184 | 185 | if alphabet is None: 186 | alphabet = bytearray(range(256)) 187 | 188 | if block_pool is not None: 189 | map_func = block_pool.imap 190 | else: 191 | map_func = map 192 | 193 | plaintext = bytearray() 194 | 195 | decrypt_func = functools.partial(decrypt_block, oracle, block_len, alphabet, pool, progress) 196 | for plain in map_func(decrypt_func, block_pairs(block_len, bytearray(ciphertext), known_prefix, known_suffix)): 197 | plaintext[0:0] = plain 198 | 199 | return bytes(plaintext[:-plaintext[-1]]) 200 | 201 | 202 | def check_padding_encrypt(event, oracle, block_len, chunk, block, i, j): 203 | chunk = chunk[:] 204 | 205 | chunk[i] = j 206 | 207 | if consult_oracle(oracle, chunk, block, i == block_len - 1): 208 | event.set() 209 | return chunk 210 | 211 | 212 | def encrypt_block(oracle, block_len, block, plain, pool): 213 | if pool is not None: 214 | event_factory = multiprocessing.Manager().Event 215 | map_func = pool.imap_unordered 216 | else: 217 | event_factory = threading.Event 218 | map_func = map 219 | 220 | cipher = bytearray([0] * block_len) 221 | 222 | for i in range(block_len - 1, -1, -1): 223 | chunk = cipher[:] 224 | 225 | for k in range(i + 1, block_len): 226 | chunk[k] ^= block_len - i 227 | 228 | event = event_factory() 229 | f = functools.partial(check_padding_encrypt, event, oracle, block_len, chunk, block, i) 230 | 231 | for result in map_func(f, interruptable_iter(event, range(256))): 232 | if result is not None: 233 | cipher[i] = result[i] ^ (block_len - i) 234 | 235 | if not event.is_set(): 236 | raise RuntimeError('Oracle is unstable') 237 | 238 | for k, p in enumerate(plain): 239 | cipher[k] ^= p 240 | 241 | return cipher 242 | 243 | 244 | def padding_oracle_encrypt(oracle, plaintext, block_size=128, pool=None): 245 | """ 246 | Encrypt plaintext using an oracle function that returns ``True`` if the 247 | provided ciphertext is correctly PKCS#7 padded after decryption. The 248 | cipher needs to operate in CBC mode. 249 | 250 | Args: 251 | oracle(callable): The oracle function. Will be called repeatedly with 252 | a chunk of ciphertext. 253 | plaintext(bytes): The plaintext data to encrypt. 254 | block_size(int): The cipher's block size in bits. 255 | pool(multiprocessing.Pool): A multiprocessing pool to use to 256 | parallelize the encryption. This pool is used to call the oracle 257 | function. Fairly heavy due to the required inter-process state 258 | synchronization. If ``None`` (the default), no multiprocessing 259 | will be used. 260 | 261 | Returns: 262 | bytes: The encrypted data. 263 | 264 | Raises: 265 | RuntimeError: Raised if the oracle behaves unpredictable. 266 | """ 267 | 268 | plaintext = bytearray(plaintext) 269 | block_len = block_size // 8 270 | 271 | padding_len = block_len - (len(plaintext) % block_len) 272 | plaintext.extend([padding_len] * padding_len) 273 | 274 | ciphertext = bytearray() 275 | 276 | chunk = bytearray(os.urandom(block_len)) 277 | ciphertext[0:0] = chunk 278 | 279 | for plain_start in range(len(plaintext) - block_len, -1, -block_len): 280 | plain = plaintext[plain_start:plain_start + block_len] 281 | chunk = ciphertext[0:0] = encrypt_block(oracle, block_len, chunk, plain, pool) 282 | 283 | return bytes(ciphertext) 284 | -------------------------------------------------------------------------------- /pwnypack/packing.py: -------------------------------------------------------------------------------- 1 | import pwnypack.target 2 | import struct 3 | 4 | 5 | __all__ = [ 6 | 'pack', 7 | 'unpack', 8 | 'pack_size', 9 | 'P', 10 | 'p', 11 | 'U', 12 | 'u', 13 | ] 14 | 15 | 16 | def pack(fmt, *args, **kwargs): 17 | """pack(fmt, v1, v2, ..., endian=None, target=None) 18 | 19 | Return a string containing the values v1, v2, ... packed according to the 20 | given format. The actual packing is performed by ``struct.pack`` but the 21 | byte order will be set according to the given `endian`, `target` or 22 | byte order of the global target. 23 | 24 | Args: 25 | fmt(str): The format string. 26 | v1,v2,...: The values to pack. 27 | endian(:class:`~pwnypack.target.Target.Endian`): Override the default 28 | byte order. If ``None``, it will look at the byte order of 29 | the ``target`` argument. 30 | target(:class:`~pwnypack.target.Target`): Override the default byte 31 | order. If ``None``, it will look at the byte order of 32 | the global :data:`~pwnypack.target.target`. 33 | 34 | Returns: 35 | bytes: The provided values packed according to the format. 36 | """ 37 | 38 | endian, target = kwargs.get('endian'), kwargs.get('target') 39 | endian = endian if endian is not None else target.endian if target is not None else pwnypack.target.target.endian 40 | if fmt and fmt[0] not in '@=<>!': 41 | if endian is pwnypack.target.Target.Endian.little: 42 | fmt = '<' + fmt 43 | elif endian is pwnypack.target.Target.Endian.big: 44 | fmt = '>' + fmt 45 | else: 46 | raise NotImplementedError('Unsupported endianness: %s' % endian) 47 | return struct.pack(fmt, *args) 48 | 49 | 50 | def unpack(fmt, data, endian=None, target=None): 51 | """ 52 | Unpack the string (presumably packed by pack(fmt, ...)) according to the 53 | given format. The actual unpacking is performed by ``struct.unpack`` 54 | but the byte order will be set according to the given `endian`, `target` 55 | or byte order of the global target. 56 | 57 | Args: 58 | fmt(str): The format string. 59 | data(bytes): The data to unpack. 60 | endian(:class:`~pwnypack.target.Target.Endian`): Override the default 61 | byte order. If ``None``, it will look at the byte order of 62 | the ``target`` argument. 63 | target(:class:`~pwnypack.target.Target`): Override the default byte 64 | order. If ``None``, it will look at the byte order of 65 | the global :data:`~pwnypack.target.target`. 66 | 67 | Returns: 68 | list: The unpacked values according to the format. 69 | """ 70 | 71 | endian = endian if endian is not None else target.endian if target is not None else pwnypack.target.target.endian 72 | if fmt and fmt[0] not in '@=<>!': 73 | if endian is pwnypack.target.Target.Endian.little: 74 | fmt = '<' + fmt 75 | elif endian is pwnypack.target.Target.Endian.big: 76 | fmt = '>' + fmt 77 | else: 78 | raise NotImplementedError('Unsupported endianness: %s' % endian) 79 | return struct.unpack(fmt, data) 80 | 81 | 82 | def pack_size(fmt, endian=None, target=None): 83 | endian = endian if endian is not None else target.endian if target is not None else pwnypack.target.target.endian 84 | if fmt and fmt[0] not in '@=<>!': 85 | if endian is pwnypack.target.Target.Endian.little: 86 | fmt = '<' + fmt 87 | elif endian is pwnypack.target.Target.Endian.big: 88 | fmt = '>' + fmt 89 | else: 90 | raise NotImplementedError('Unsupported endianness: %s' % endian) 91 | return struct.calcsize(fmt) 92 | 93 | 94 | def _pack_closure(fmt): 95 | return lambda value, endian=None, target=None: pack(fmt, value, endian=endian, target=target) 96 | 97 | 98 | def _unpack_closure(fmt): 99 | return lambda data, endian=None, target=None: unpack(fmt, data, endian=endian, target=target)[0] 100 | 101 | 102 | for _w, _f in ((8, 'b'), (16, 'h'), (32, 'l'), (64, 'q')): 103 | locals().update({ 104 | 'p%d' % _w: _pack_closure(_f), 105 | 'P%d' % _w: _pack_closure(_f.upper()), 106 | 'u%d' % _w: _unpack_closure(_f), 107 | 'U%d' % _w: _unpack_closure(_f.upper()), 108 | }) 109 | 110 | locals()['p%d' % _w].__doc__ = '''Pack signed %d bit integer. Alias for ``pack('%s', ...)``.''' % (_w, _f) 111 | locals()['P%d' % _w].__doc__ = '''Pack unsigned %d bit integer. Alias for ``pack('%s', ...)``.''' % (_w, _f.upper()) 112 | locals()['u%d' % _w].__doc__ = '''Unpack signed %d bit integer. Alias for ``unpack('%s', ...)``.''' % (_w, _f) 113 | locals()['U%d' % _w].__doc__ = '''Unpack unsigned %d bit integer. Alias for ``unpack('%s', ...)``.''' % \ 114 | (_w, _f.upper()) 115 | 116 | __all__.extend([ 117 | 'p%d' % _w, 118 | 'P%d' % _w, 119 | 'u%d' % _w, 120 | 'U%d' % _w, 121 | ]) 122 | del _w, _f, _pack_closure, _unpack_closure 123 | 124 | 125 | def _get_bits(bits=None, target=None): 126 | """ 127 | Determine the number of bits to pack/unpack. 128 | """ 129 | 130 | if bits is not None: 131 | bits = int(bits) 132 | if bits in (8, 16, 32, 64): 133 | return bits 134 | else: 135 | raise ValueError('bits needs to be 8, 16, 32 or 64') 136 | else: 137 | return int((target if target is not None else pwnypack.target.target).bits) 138 | 139 | 140 | def P(value, bits=None, endian=None, target=None): 141 | """ 142 | Pack an unsigned pointer for a given target. 143 | 144 | Args: 145 | value(int): The value to pack. 146 | bits(:class:`~pwnypack.target.Target.Bits`): Override the default 147 | word size. If ``None`` it will look at the word size of 148 | ``target``. 149 | endian(:class:`~pwnypack.target.Target.Endian`): Override the default 150 | byte order. If ``None``, it will look at the byte order of 151 | the ``target`` argument. 152 | target(:class:`~pwnypack.target.Target`): Override the default byte 153 | order. If ``None``, it will look at the byte order of 154 | the global :data:`~pwnypack.target.target`. 155 | """ 156 | 157 | return globals()['P%d' % _get_bits(bits, target)](value, endian=endian, target=target) 158 | 159 | 160 | def p(value, bits=None, endian=None, target=None): 161 | """ 162 | Pack a signed pointer for a given target. 163 | 164 | Args: 165 | value(int): The value to pack. 166 | bits(:class:`pwnypack.target.Target.Bits`): Override the default 167 | word size. If ``None`` it will look at the word size of 168 | ``target``. 169 | endian(:class:`~pwnypack.target.Target.Endian`): Override the default 170 | byte order. If ``None``, it will look at the byte order of 171 | the ``target`` argument. 172 | target(:class:`~pwnypack.target.Target`): Override the default byte 173 | order. If ``None``, it will look at the byte order of 174 | the global :data:`~pwnypack.target.target`. 175 | """ 176 | 177 | return globals()['p%d' % _get_bits(bits, target)](value, endian=endian, target=target) 178 | 179 | 180 | def U(data, bits=None, endian=None, target=None): 181 | """ 182 | Unpack an unsigned pointer for a given target. 183 | 184 | Args: 185 | data(bytes): The data to unpack. 186 | bits(:class:`pwnypack.target.Target.Bits`): Override the default 187 | word size. If ``None`` it will look at the word size of 188 | ``target``. 189 | endian(:class:`~pwnypack.target.Target.Endian`): Override the default 190 | byte order. If ``None``, it will look at the byte order of 191 | the ``target`` argument. 192 | target(:class:`~pwnypack.target.Target`): Override the default byte 193 | order. If ``None``, it will look at the byte order of 194 | the global :data:`~pwnypack.target.target`. 195 | 196 | Returns: 197 | int: The pointer value. 198 | """ 199 | 200 | return globals()['U%d' % _get_bits(bits, target)](data, endian=endian, target=target) 201 | 202 | 203 | def u(data, bits=None, endian=None, target=None): 204 | """ 205 | Unpack a signed pointer for a given target. 206 | 207 | Args: 208 | data(bytes): The data to unpack. 209 | bits(:class:`pwnypack.target.Target.Bits`): Override the default 210 | word size. If ``None`` it will look at the word size of 211 | ``target``. 212 | endian(:class:`~pwnypack.target.Target.Endian`): Override the default 213 | byte order. If ``None``, it will look at the byte order of 214 | the ``target`` argument. 215 | target(:class:`~pwnypack.target.Target`): Override the default byte 216 | order. If ``None``, it will look at the byte order of 217 | the global :data:`~pwnypack.target.target`. 218 | 219 | Returns: 220 | int: The pointer value. 221 | """ 222 | 223 | return globals()['u%d' % _get_bits(bits, target)](data, endian=endian, target=target) 224 | -------------------------------------------------------------------------------- /pwnypack/php.py: -------------------------------------------------------------------------------- 1 | import six 2 | 3 | 4 | __all__ = [ 5 | 'php_serialize', 6 | 'PhpObject', 7 | ] 8 | 9 | 10 | def php_serialize(value): 11 | """ 12 | Serialize a value for use with PHP's deserialize() function. This function 13 | can serialize bytes, strings, integers, floats, booleans, None, lists, 14 | dicts and custom objects implementing __php__(). 15 | 16 | Args: 17 | value: The value to serialize. 18 | 19 | Returns: 20 | bytes: The serialized form of `value` ready to be unserialized by PHP. 21 | 22 | Example: 23 | >>> from pwny import * 24 | >>> php_serialize([b'foo', u'bar', 42, 2.5, True, None, {'a': 'b'}]) 25 | b'a:7:{i:0;s:3:"foo";i:1;s:3:"bar";i:2;i:42;i:3;d:2.5;i:4;b:1;i:5;N;i:6;a:1:{s:1:"a";s:1:"b";}}' 26 | """ 27 | 28 | def serialize_array(items): 29 | content = b''.join( 30 | php_serialize(i) + php_serialize(v) 31 | for i, v in items 32 | ) 33 | return 'a:{0}:'.format(len(value)).encode('utf8') + b'{' + content + b'}' 34 | 35 | def serialize_str(prefix, item): 36 | return prefix + b':' + str(item).encode('utf8') + b';' 37 | 38 | if isinstance(value, six.binary_type): 39 | return b's:' + str(len(value)).encode('utf8') + b':"' + value + b'";' 40 | elif isinstance(value, six.text_type): 41 | return php_serialize(value.encode('utf8')) 42 | elif isinstance(value, bool): 43 | return serialize_str(b'b', 1 if value else 0) 44 | elif isinstance(value, int): 45 | return serialize_str(b'i', value) 46 | elif isinstance(value, float): 47 | return serialize_str(b'd', value) 48 | elif value is None: 49 | return b'N;' 50 | elif isinstance(value, (list, tuple)): 51 | return serialize_array(enumerate(value)) 52 | elif isinstance(value, dict): 53 | return serialize_array(six.iteritems(value)) 54 | else: 55 | return value.__php__() 56 | 57 | 58 | class PhpObject(object): 59 | """ 60 | Helper class to represent PHP objects for serialization using 61 | :func:`php_serialize`. 62 | 63 | Instances of this class act like a dictionary of properties that should 64 | be set on the deserialized PHP instance. You can prefix the property 65 | names with ``'public '``, ``'protected '`` or ``'private '`` to ensure 66 | the correct instance variables are set. 67 | 68 | Arguments: 69 | class_name(str): The name of the PHP class to use when 70 | deserializing. 71 | properties(dict): The properties to deserialize in this instance. 72 | 73 | Example: 74 | >>> from pwny import * 75 | >>> o = PhpObject('Foo\\Bar', {'protected fg': '#000000'}) 76 | >>> php_serialize(o) 77 | b'O:7:"Foo\\Bar":1:{s:5:"\\x00*\\x00fg";s:7:"#000000";}' 78 | """ 79 | 80 | def __init__(self, class_name, properties=None): 81 | self.class_name = class_name 82 | self.items = {} 83 | for k, v in six.iteritems(properties or {}): 84 | self[k] = v 85 | 86 | def _mogrify_key(self, key): 87 | if key.startswith('protected '): 88 | return '\0*\0' + key.split(' ', 1)[1] 89 | elif key.startswith('private '): 90 | return '\0' + self.class_name + '\0' + key.split(' ', 1)[1] 91 | elif key.startswith('public '): 92 | return key.split(' ', 1)[1] 93 | else: 94 | return key 95 | 96 | def __setitem__(self, key, value): 97 | self.items[self._mogrify_key(key)] = value 98 | 99 | def __getitem__(self, key): 100 | return self.items[self._mogrify_key(key)] 101 | 102 | def __php__(self): 103 | properties = b''.join( 104 | php_serialize(k) + php_serialize(v) 105 | for k, v in six.iteritems(self.items) 106 | ) 107 | return 'O:{0}:"{1}":{2}:'.format(len(self.class_name), self.class_name, len(self.items)).encode('utf8') + \ 108 | b'{' + properties + b'}' 109 | -------------------------------------------------------------------------------- /pwnypack/pwnbook.py: -------------------------------------------------------------------------------- 1 | try: 2 | from jupyter_client import kernelspec as kernelspec 3 | import ipykernel.kernelspec as ipy_kernelspec 4 | from notebook.notebookapp import NotebookApp 5 | have_notebook = True 6 | except ImportError as e: 7 | have_notebook = False 8 | 9 | import pwnypack.main 10 | 11 | 12 | __all__ = [] 13 | 14 | 15 | if have_notebook: 16 | class KSM(kernelspec.KernelSpecManager): 17 | """ 18 | A custom jupyter KernelSpecManager which adds a pwnypack KernelSpec 19 | that is the same as the native kernel (python 2 / 3) but with the 20 | pwnypack ipython extension preloaded. 21 | """ 22 | 23 | def _get_kernel_spec_by_name(self, kernel_name, resource_dir): 24 | if kernel_name == 'pwnypack': 25 | d = ipy_kernelspec.get_kernel_dict() 26 | d['display_name'] = 'pwnypack (%s)' % d['display_name'] 27 | d['argv'].append('--ext=pwnypack.ipython_ext') 28 | return kernelspec.KernelSpec(resource_dir=resource_dir, **d) 29 | else: 30 | return super(KSM, self)._get_kernel_spec_by_name(kernel_name, resource_dir) 31 | 32 | def find_kernel_specs(self): 33 | specs = super(KSM, self).find_kernel_specs() 34 | if not 'pwnypack' in specs: 35 | specs['pwnypack'] = ipy_kernelspec.RESOURCES 36 | return specs 37 | 38 | 39 | @pwnypack.main.register() 40 | def pwnbook(_parser, cmd, args): # pragma: no cover 41 | """ 42 | Start a jupyter notebook with the pwnypack enhanced kernel pre-loaded. 43 | """ 44 | 45 | notebook = NotebookApp(kernel_spec_manager_class=KSM) 46 | notebook.initialize(args) 47 | notebook.start() 48 | -------------------------------------------------------------------------------- /pwnypack/rop.py: -------------------------------------------------------------------------------- 1 | """ 2 | The ROP module contains a function to find gadgets in ELF binaries that can 3 | be used to create ROP chains. 4 | """ 5 | 6 | from __future__ import print_function 7 | import argparse 8 | import re 9 | import six 10 | import sys 11 | import pwnypack.codec 12 | import pwnypack.elf 13 | import pwnypack.main 14 | import pwnypack.asm 15 | import pwnypack.target 16 | import pwnypack.util 17 | 18 | try: 19 | import capstone 20 | HAVE_CAPSTONE = True 21 | except ImportError: 22 | HAVE_CAPSTONE = False 23 | 24 | 25 | __all__ = [ 26 | 'find_gadget', 27 | ] 28 | 29 | 30 | if HAVE_CAPSTONE: 31 | INVALID_GROUPS = set((capstone.CS_GRP_CALL, capstone.CS_GRP_JUMP)) 32 | 33 | 34 | def find_gadget(elf, gadget, align=1, unique=True): 35 | """ 36 | Find a ROP gadget in a the executable sections of an ELF executable or 37 | library. The ROP gadget can be either a set of bytes for an exact match 38 | or a (bytes) regular expression. Once it finds gadgets, it uses the 39 | capstone engine to verify if the gadget consists of valid instructions 40 | and doesn't contain any call or jump instructions. 41 | 42 | Args: 43 | elf(:class:`~pwnypack.elf.ELF`): The ELF instance to find a gadget in. 44 | gadget(bytes or regexp): The gadget to find. 45 | align(int): Make sure the gadget starts at a multiple of this number 46 | unique(bool): If true, only unique gadgets are returned. 47 | 48 | Returns: 49 | dict: A dictionary containing a description of the found 50 | gadget. Contains the following fields: 51 | 52 | - section: The section the gadget was found in. 53 | - offset: The offset inside the segment the gadget was found at. 54 | - addr: The virtual memory address the gadget will be located at. 55 | - gadget: The machine code of the found gadget. 56 | - asm: A list of disassembled instructions. 57 | 58 | """ 59 | 60 | if not HAVE_CAPSTONE: 61 | raise NotImplementedError('pwnypack requires capstone to find ROP gadgets') 62 | 63 | if not isinstance(elf, pwnypack.elf.ELF): 64 | elf = pwnypack.elf.ELF(elf) 65 | 66 | matches = [] 67 | gadgets = [] 68 | 69 | if isinstance(gadget, six.binary_type): 70 | gadget = re.compile(re.escape(gadget)) 71 | 72 | for section in elf.section_headers: 73 | if section.type != section.Type.progbits: 74 | continue 75 | 76 | for match in gadget.finditer(section.content): 77 | match_index = match.start() 78 | if match_index % align != 0: 79 | continue 80 | 81 | match_gadget = match.group() 82 | 83 | if match_gadget in gadgets: 84 | continue 85 | 86 | match_addr = section.addr + match_index 87 | 88 | md = pwnypack.asm.prepare_capstone(syntax=pwnypack.asm.AsmSyntax.intel, target=elf) 89 | md.detail = True 90 | match_asm = [] 91 | 92 | for insn in md.disasm(match_gadget, match_addr): 93 | if insn.id == capstone.CS_OP_INVALID or set(insn.groups) & INVALID_GROUPS: 94 | # Don't try to disassemble this particular gadget again. 95 | gadgets.append(match_gadget) 96 | break 97 | match_asm.append((insn.mnemonic + ' ' + insn.op_str).strip()) 98 | else: 99 | matches.append({ 100 | 'section': section, 101 | 'offset': match_index, 102 | 'addr': match_addr, 103 | 'gadget': match_gadget, 104 | 'asm': match_asm, 105 | }) 106 | if unique: 107 | gadgets.append(match_gadget) 108 | 109 | return matches 110 | 111 | 112 | @pwnypack.main.register('gadget') 113 | def gadget_app(_parser, cmd, args): # pragma: no cover 114 | """ 115 | Find ROP gadgets in an ELF binary. 116 | """ 117 | 118 | parser = argparse.ArgumentParser( 119 | prog=_parser.prog, 120 | description=_parser.description, 121 | ) 122 | parser.add_argument('file', help='ELF file to find gadgets in') 123 | parser.add_argument('gadget', help='the assembler source or reghex expression') 124 | parser.add_argument( 125 | '--reghex', '-r', 126 | dest='mode', 127 | action='store_const', 128 | const='reghex', 129 | help='use reghex expression (hex bytes interspaced with ? for wildcard)', 130 | ) 131 | parser.add_argument( 132 | '--asm', '-a', 133 | dest='mode', 134 | action='store_const', 135 | const='asm', 136 | help='use assembler expression (separate lines with semi-colon)', 137 | ) 138 | parser.add_argument( 139 | '--all', '-l', 140 | dest='unique', 141 | action='store_const', 142 | const=False, 143 | default=True, 144 | help='also show non-unique gadgets', 145 | ) 146 | args = parser.parse_args(args) 147 | 148 | if args.mode is None: 149 | try: 150 | pwnypack.util.reghex(args.gadget) 151 | args.mode = 'reghex' 152 | except SyntaxError: 153 | args.mode = 'asm' 154 | 155 | elf = pwnypack.elf.ELF(args.file) 156 | 157 | if args.mode == 'reghex': 158 | try: 159 | gadget = pwnypack.util.reghex(args.gadget) 160 | except SyntaxError: 161 | print('Invalid reghex pattern.') 162 | sys.exit(1) 163 | else: 164 | try: 165 | gadget = pwnypack.util.reghex('*'.join([ 166 | pwnypack.codec.enhex(pwnypack.asm.asm(piece.replace(';', '\n'), target=elf)) 167 | for piece in ';'.join([line.strip() for line in args.gadget.split(';')]).split('*') 168 | ])) 169 | except SyntaxError as e: 170 | print('Could not assemble:', e.msg) 171 | sys.exit(1) 172 | 173 | matches = find_gadget( 174 | elf, 175 | gadget, 176 | unique=args.unique 177 | ) 178 | 179 | if not matches: 180 | print('No gadgets found.', file=sys.stdout) 181 | return 182 | 183 | longest_gadget = max(len(m['gadget']) for m in matches) 184 | fmt = ' 0x%%0%dx: [ %%-%ds ] %%s' % (elf.bits / 4, longest_gadget * 3 - 1) 185 | 186 | current_section = None 187 | 188 | for match in matches: 189 | if match['section'].name != current_section: 190 | if current_section is not None: 191 | print() 192 | print('Section: %s' % match['section'].name) 193 | current_section = match['section'].name 194 | 195 | hex_gadget = pwnypack.codec.enhex(match['gadget']) 196 | print(fmt % ( 197 | match['addr'], 198 | ' '.join( 199 | hex_gadget[i:i+2] 200 | for i in range(0, len(hex_gadget), 2) 201 | ), 202 | ' ; '.join(match['asm']) 203 | )) 204 | 205 | print() 206 | -------------------------------------------------------------------------------- /pwnypack/shell.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import pwnypack.main 3 | 4 | 5 | __all__ = [] 6 | 7 | 8 | BANNER = 'Welcome to the interactive pwnypack shell.\n' 9 | 10 | 11 | try: 12 | import bpython 13 | have_bpython = True 14 | except ImportError: 15 | have_bpython = False 16 | 17 | 18 | try: 19 | import IPython 20 | have_IPython = True 21 | except ImportError: 22 | have_IPython = False 23 | 24 | 25 | @pwnypack.main.register() 26 | def shell(_parser, cmd, args): # pragma: no cover 27 | """ 28 | Start an interactive python interpreter with pwny imported globally. 29 | """ 30 | 31 | parser = argparse.ArgumentParser( 32 | prog=_parser.prog, 33 | description=_parser.description, 34 | ) 35 | 36 | group = parser.add_mutually_exclusive_group() 37 | group.set_defaults(shell=have_bpython and 'bpython' or (have_IPython and 'ipython' or 'python')) 38 | if have_bpython: 39 | group.add_argument( 40 | '--bpython', 41 | action='store_const', 42 | dest='shell', 43 | const='bpython', 44 | help='Use the bpython interpreter' 45 | ) 46 | if have_IPython: 47 | group.add_argument( 48 | '--ipython', 49 | action='store_const', 50 | dest='shell', 51 | const='ipython', 52 | help='Use the IPython interpreter' 53 | ) 54 | group.add_argument( 55 | '--python', 56 | action='store_const', 57 | dest='shell', 58 | const='python', 59 | help='Use the default python interpreter' 60 | ) 61 | 62 | args = parser.parse_args(args) 63 | 64 | import pwny 65 | pwny_locals = dict( 66 | (key, getattr(pwny, key)) 67 | for key in dir(pwny) 68 | if not key.startswith('__') and not key == 'shell' 69 | ) 70 | 71 | if args.shell == 'bpython': 72 | from bpython import embed 73 | embed(pwny_locals, banner=BANNER) 74 | elif args.shell == 'ipython': 75 | from IPython import start_ipython 76 | start_ipython( 77 | argv=['--ext=pwnypack.ipython_ext'], 78 | ) 79 | else: 80 | import code 81 | code.interact(BANNER, local=pwny_locals) 82 | -------------------------------------------------------------------------------- /pwnypack/shellcode/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edibledinos/pwnypack/e0a5a8e6ef3f4f1f7e1b91ee379711f4a49cb0e6/pwnypack/shellcode/__init__.py -------------------------------------------------------------------------------- /pwnypack/shellcode/aarch64/__init__.py: -------------------------------------------------------------------------------- 1 | from pwnypack.shellcode.base import BaseEnvironment 2 | from pwnypack.shellcode.types import Register 3 | from pwnypack.target import Target 4 | 5 | 6 | __all__ = ['AArch64'] 7 | 8 | 9 | class AArch64(BaseEnvironment): 10 | """ 11 | Environment that targets a generic, unrestricted AArch64 architecture. 12 | """ 13 | 14 | target = None #: Target architecture, initialized in __init__. 15 | 16 | X0 = Register('x0') #: x0 register 17 | X1 = Register('x1') #: x1 register 18 | X2 = Register('x2') #: x2 register 19 | X3 = Register('x3') #: x3 register 20 | X4 = Register('x4') #: x4 register 21 | X5 = Register('x5') #: x5 register 22 | X6 = Register('x6') #: x6 register 23 | X7 = Register('x7') #: x7 register 24 | X8 = Register('x8') #: x8 register 25 | X9 = Register('x9') #: x9 register 26 | X10 = Register('x10') #: x10 register 27 | X11 = Register('x11') #: x11 register 28 | X12 = Register('x12') #: x12 register 29 | X13 = Register('x13') #: x13 register 30 | X14 = Register('x14') #: x14 register 31 | X15 = Register('x15') #: x15 register 32 | X16 = Register('x16') #: x16 register 33 | X17 = Register('x17') #: x17 register 34 | X18 = Register('x18') #: x18 register 35 | X19 = Register('x19') #: x19 register 36 | X20 = Register('x20') #: x20 register 37 | X21 = Register('x21') #: x21 register 38 | X22 = Register('x22') #: x22 register 39 | X23 = Register('x23') #: x23 register 40 | X24 = Register('x24') #: x24 register 41 | X25 = Register('x25') #: x25 register 42 | X26 = Register('x26') #: x26 register 43 | X27 = Register('x27') #: x27 register 44 | X28 = Register('x28') #: x28 register 45 | X29 = Register('x29') #: x29 register 46 | X30 = Register('x30') #: x30 register 47 | 48 | W0 = Register('w0') #: w0 register 49 | W1 = Register('w1') #: w1 register 50 | W2 = Register('w2') #: w2 register 51 | W3 = Register('w3') #: w3 register 52 | W4 = Register('w4') #: w4 register 53 | W5 = Register('w5') #: w5 register 54 | W6 = Register('w6') #: w6 register 55 | W7 = Register('w7') #: w7 register 56 | W8 = Register('w8') #: w8 register 57 | W9 = Register('w9') #: w9 register 58 | W10 = Register('w10') #: w10 register 59 | W11 = Register('w11') #: w11 register 60 | W12 = Register('w12') #: w12 register 61 | W13 = Register('w13') #: w13 register 62 | W14 = Register('w14') #: w14 register 63 | W15 = Register('w15') #: w15 register 64 | W16 = Register('w16') #: w16 register 65 | W17 = Register('w17') #: w17 register 66 | W18 = Register('w18') #: w18 register 67 | W19 = Register('w19') #: w19 register 68 | W20 = Register('w20') #: w20 register 69 | W21 = Register('w21') #: w21 register 70 | W22 = Register('w22') #: w22 register 71 | W23 = Register('w23') #: w23 register 72 | W24 = Register('w24') #: w24 register 73 | W25 = Register('w25') #: w25 register 74 | W26 = Register('w26') #: w26 register 75 | W27 = Register('w27') #: w27 register 76 | W28 = Register('w28') #: w28 register 77 | W29 = Register('w29') #: w29 register 78 | W30 = Register('w30') #: w30 register 79 | 80 | SP = Register('sp') #: sp (stack pointer) register 81 | XZR = Register('xzr') #: xzr register 82 | WZR = Register('wzr') #: wzr register 83 | 84 | STACK_REG = SP 85 | OFFSET_REG = X29 86 | TEMP_REG = {64: X8, 32: W8} 87 | 88 | PREAMBLE = [ 89 | '.global _start', 90 | '_start:', 91 | ] 92 | 93 | REGISTER_WIDTH_MAP = { 94 | 64: [X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, 95 | X22, X23, X24, X25, X26, X27, X28, X29, X30, SP, XZR], 96 | 32: [W0, W1, W2, W3, W4, W5, W6, W7, W8, W9, W10, W11, W12, W13, W14, W15, W16, W17, W18, W19, W20, W21, 97 | W22, W23, W24, W25, W26, W27, W28, W29, W30, WZR] 98 | } 99 | 100 | def __init__(self, endian=None, *args, **kwargs): 101 | self.target = Target(Target.Arch.arm, 64, endian) 102 | super(AArch64, self).__init__(*args, **kwargs) 103 | 104 | def reg_push(self, reg): 105 | return ['str %s, [sp, #-%d]!' % (reg, self.REGISTER_WIDTH[reg] // 8)] 106 | 107 | def reg_pop(self, reg): 108 | return ['ldr %s, [sp], #%d' % (reg, self.REGISTER_WIDTH[reg] // 8)] 109 | 110 | def reg_add_imm(self, reg, imm): 111 | if imm <= 4096: 112 | return ['add %s, %s, #%d' % (reg, reg, imm)] 113 | 114 | temp_reg = self.TEMP_REG[self.REGISTER_WIDTH[reg]] 115 | if reg is temp_reg: 116 | raise ValueError('Cannot perform large reg_add on temporary register') 117 | 118 | return ['ldr %s, =%d' % (temp_reg, imm), 119 | 'add %s, %s, %s' % (reg, reg, temp_reg)] 120 | 121 | def reg_sub_imm(self, reg, imm): 122 | if imm <= 4096: 123 | return ['sub %s, %s, #%d' % (reg, reg, imm)] 124 | 125 | temp_reg = self.TEMP_REG[self.REGISTER_WIDTH[reg]] 126 | if reg is temp_reg: 127 | raise ValueError('Cannot perform large reg_add on temporary register') 128 | 129 | return ['ldr %s, =%d' % (temp_reg, imm), 130 | 'sub %s, %s, %s' % (reg, reg, temp_reg)] 131 | 132 | def reg_add_reg(self, reg1, reg2): 133 | return ['add %s, %s, %s' % (reg1, reg1, reg2)] 134 | 135 | def reg_sub_reg(self, reg1, reg2): 136 | return ['sub %s, %s, %s' % (reg1, reg1, reg2)] 137 | 138 | def reg_load_imm(self, reg, value): 139 | if not value: 140 | return ['eor %s, %s, %s' % (reg, reg, reg)] 141 | elif value < 0xff: 142 | return ['mov %s, #%d' % (reg, value)] 143 | else: 144 | return ['ldr %s, =%d' % (reg, value)] 145 | 146 | def reg_load_reg(self, dest_reg, src_reg): 147 | return ['mov %s, %s' % (dest_reg, src_reg)] 148 | 149 | def reg_load_offset(self, reg, value): 150 | return ['add %s, %s, #%d' % (reg, self.OFFSET_REG, value)] 151 | 152 | def jump_reg(self, reg): 153 | return ['br %s' % reg] 154 | -------------------------------------------------------------------------------- /pwnypack/shellcode/aarch64/linux.py: -------------------------------------------------------------------------------- 1 | from pwnypack.shellcode.linux import Linux 2 | from pwnypack.shellcode.aarch64 import AArch64 3 | from pwnypack.shellcode.mutable_data import gnu_as_mutable_data_finalizer 4 | from pwnypack.shellcode.stack_data import stack_data_finalizer 5 | from pwnypack.shellcode.types import SyscallDef, PTR 6 | 7 | 8 | __all__ = ['LinuxAArch64Mutable', 'LinuxAArch64Stack'] 9 | 10 | 11 | class LinuxAArch64(Linux, AArch64): 12 | """ 13 | An environment that targets a generic Linux AArch64 machine. 14 | """ 15 | 16 | sys_rt_sigreturn = SyscallDef('sys_rt_sigreturn', PTR) #: 17 | 18 | SYSCALL_ARG_MAP = [AArch64.X0, AArch64.X1, AArch64.X2, AArch64.X3, AArch64.X4, AArch64.X5] 19 | SYSCALL_REG = AArch64.X8 20 | SYSCALL_RET_REG = AArch64.X0 21 | SYSCALL_INSTR = 'svc #0' 22 | 23 | SYSCALL_MAP = { 24 | Linux.sys_io_setup: 0, 25 | Linux.sys_io_destroy: 1, 26 | Linux.sys_io_submit: 2, 27 | Linux.sys_io_cancel: 3, 28 | Linux.sys_io_getevents: 4, 29 | Linux.sys_setxattr: 5, 30 | Linux.sys_lsetxattr: 6, 31 | Linux.sys_fsetxattr: 7, 32 | Linux.sys_getxattr: 8, 33 | Linux.sys_lgetxattr: 9, 34 | Linux.sys_fgetxattr: 10, 35 | Linux.sys_listxattr: 11, 36 | Linux.sys_llistxattr: 12, 37 | Linux.sys_flistxattr: 13, 38 | Linux.sys_removexattr: 14, 39 | Linux.sys_lremovexattr: 15, 40 | Linux.sys_fremovexattr: 16, 41 | Linux.sys_getcwd: 17, 42 | Linux.sys_lookup_dcookie: 18, 43 | Linux.sys_eventfd2: 19, 44 | Linux.sys_epoll_create1: 20, 45 | Linux.sys_epoll_ctl: 21, 46 | Linux.sys_epoll_pwait: 22, 47 | Linux.sys_dup: 23, 48 | Linux.sys_dup3: 24, 49 | Linux.sys_fcntl: 25, 50 | Linux.sys_inotify_init1: 26, 51 | Linux.sys_inotify_add_watch: 27, 52 | Linux.sys_inotify_rm_watch: 28, 53 | Linux.sys_ioctl: 29, 54 | Linux.sys_ioprio_set: 30, 55 | Linux.sys_ioprio_get: 31, 56 | Linux.sys_flock: 32, 57 | Linux.sys_mknodat: 33, 58 | Linux.sys_mkdirat: 34, 59 | Linux.sys_unlinkat: 35, 60 | Linux.sys_symlinkat: 36, 61 | Linux.sys_linkat: 37, 62 | Linux.sys_renameat: 38, 63 | Linux.sys_umount2: 39, 64 | Linux.sys_mount: 40, 65 | Linux.sys_pivot_root: 41, 66 | Linux.sys_nfsservctl: 42, 67 | Linux.sys_statfs: 43, 68 | Linux.sys_fstatfs: 44, 69 | Linux.sys_truncate: 45, 70 | Linux.sys_ftruncate: 46, 71 | Linux.sys_fallocate: 47, 72 | Linux.sys_faccessat: 48, 73 | Linux.sys_chdir: 49, 74 | Linux.sys_fchdir: 50, 75 | Linux.sys_chroot: 51, 76 | Linux.sys_fchmod: 52, 77 | Linux.sys_fchmodat: 53, 78 | Linux.sys_fchownat: 54, 79 | Linux.sys_fchown: 55, 80 | Linux.sys_openat: 56, 81 | Linux.sys_close: 57, 82 | Linux.sys_vhangup: 58, 83 | Linux.sys_pipe2: 59, 84 | Linux.sys_quotactl: 60, 85 | Linux.sys_getdents64: 61, 86 | Linux.sys_lseek: 62, 87 | Linux.sys_read: 63, 88 | Linux.sys_write: 64, 89 | Linux.sys_readv: 65, 90 | Linux.sys_writev: 66, 91 | Linux.sys_pread64: 67, 92 | Linux.sys_pwrite64: 68, 93 | Linux.sys_preadv: 69, 94 | Linux.sys_pwritev: 70, 95 | Linux.sys_sendfile: 71, 96 | Linux.sys_pselect6: 72, 97 | Linux.sys_ppoll: 73, 98 | Linux.sys_signalfd4: 74, 99 | Linux.sys_vmsplice: 75, 100 | Linux.sys_splice: 76, 101 | Linux.sys_tee: 77, 102 | Linux.sys_readlinkat: 78, 103 | Linux.sys_fstatat64: 79, 104 | Linux.sys_fstat: 80, 105 | Linux.sys_sync: 81, 106 | Linux.sys_fsync: 82, 107 | Linux.sys_fdatasync: 83, 108 | Linux.sys_sync_file_range2: 84, 109 | Linux.sys_timerfd_create: 85, 110 | Linux.sys_timerfd_settime: 86, 111 | Linux.sys_timerfd_gettime: 87, 112 | Linux.sys_utimensat: 88, 113 | Linux.sys_acct: 89, 114 | Linux.sys_capget: 90, 115 | Linux.sys_capset: 91, 116 | Linux.sys_personality: 92, 117 | Linux.sys_exit: 93, 118 | Linux.sys_exit_group: 94, 119 | Linux.sys_waitid: 95, 120 | Linux.sys_set_tid_address: 96, 121 | Linux.sys_unshare: 97, 122 | Linux.sys_futex: 98, 123 | Linux.sys_set_robust_list: 99, 124 | Linux.sys_get_robust_list: 100, 125 | Linux.sys_nanosleep: 101, 126 | Linux.sys_getitimer: 102, 127 | Linux.sys_setitimer: 103, 128 | Linux.sys_kexec_load: 104, 129 | Linux.sys_init_module: 105, 130 | Linux.sys_delete_module: 106, 131 | Linux.sys_timer_create: 107, 132 | Linux.sys_timer_gettime: 108, 133 | Linux.sys_timer_getoverrun: 109, 134 | Linux.sys_timer_settime: 110, 135 | Linux.sys_timer_delete: 111, 136 | Linux.sys_clock_settime: 112, 137 | Linux.sys_clock_gettime: 113, 138 | Linux.sys_clock_getres: 114, 139 | Linux.sys_clock_nanosleep: 115, 140 | Linux.sys_syslog: 116, 141 | Linux.sys_ptrace: 117, 142 | Linux.sys_sched_setparam: 118, 143 | Linux.sys_sched_setscheduler: 119, 144 | Linux.sys_sched_getscheduler: 120, 145 | Linux.sys_sched_getparam: 121, 146 | Linux.sys_sched_setaffinity: 122, 147 | Linux.sys_sched_getaffinity: 123, 148 | Linux.sys_sched_yield: 124, 149 | Linux.sys_sched_get_priority_max: 125, 150 | Linux.sys_sched_get_priority_min: 126, 151 | Linux.sys_sched_rr_get_interval: 127, 152 | Linux.sys_restart_syscall: 128, 153 | Linux.sys_kill: 129, 154 | Linux.sys_tkill: 130, 155 | Linux.sys_tgkill: 131, 156 | Linux.sys_sigaltstack: 132, 157 | Linux.sys_rt_sigsuspend: 133, 158 | Linux.sys_rt_sigaction: 134, 159 | Linux.sys_rt_sigprocmask: 135, 160 | Linux.sys_rt_sigpending: 136, 161 | Linux.sys_rt_sigtimedwait: 137, 162 | Linux.sys_rt_sigqueueinfo: 138, 163 | sys_rt_sigreturn: 139, 164 | Linux.sys_setpriority: 140, 165 | Linux.sys_getpriority: 141, 166 | Linux.sys_reboot: 142, 167 | Linux.sys_setregid: 143, 168 | Linux.sys_setgid: 144, 169 | Linux.sys_setreuid: 145, 170 | Linux.sys_setuid: 146, 171 | Linux.sys_setresuid: 147, 172 | Linux.sys_getresuid: 148, 173 | Linux.sys_setresgid: 149, 174 | Linux.sys_getresgid: 150, 175 | Linux.sys_setfsuid: 151, 176 | Linux.sys_setfsgid: 152, 177 | Linux.sys_times: 153, 178 | Linux.sys_setpgid: 154, 179 | Linux.sys_getpgid: 155, 180 | Linux.sys_getsid: 156, 181 | Linux.sys_setsid: 157, 182 | Linux.sys_getgroups: 158, 183 | Linux.sys_setgroups: 159, 184 | Linux.sys_uname: 160, 185 | Linux.sys_sethostname: 161, 186 | Linux.sys_setdomainname: 162, 187 | Linux.sys_getrlimit: 163, 188 | Linux.sys_setrlimit: 164, 189 | Linux.sys_getrusage: 165, 190 | Linux.sys_umask: 166, 191 | Linux.sys_prctl: 167, 192 | Linux.sys_getcpu: 168, 193 | Linux.sys_gettimeofday: 169, 194 | Linux.sys_settimeofday: 170, 195 | Linux.sys_adjtimex: 171, 196 | Linux.sys_getpid: 172, 197 | Linux.sys_getppid: 173, 198 | Linux.sys_getuid: 174, 199 | Linux.sys_geteuid: 175, 200 | Linux.sys_getgid: 176, 201 | Linux.sys_getegid: 177, 202 | Linux.sys_gettid: 178, 203 | Linux.sys_sysinfo: 179, 204 | Linux.sys_mq_open: 180, 205 | Linux.sys_mq_unlink: 181, 206 | Linux.sys_mq_timedsend: 182, 207 | Linux.sys_mq_timedreceive: 183, 208 | Linux.sys_mq_notify: 184, 209 | Linux.sys_mq_getsetattr: 185, 210 | Linux.sys_msgget: 186, 211 | Linux.sys_msgctl: 187, 212 | Linux.sys_msgrcv: 188, 213 | Linux.sys_msgsnd: 189, 214 | Linux.sys_semget: 190, 215 | Linux.sys_semctl: 191, 216 | Linux.sys_semtimedop: 192, 217 | Linux.sys_semop: 193, 218 | Linux.sys_shmget: 194, 219 | Linux.sys_shmctl: 195, 220 | Linux.sys_shmat: 196, 221 | Linux.sys_shmdt: 197, 222 | Linux.sys_socket: 198, 223 | Linux.sys_socketpair: 199, 224 | Linux.sys_bind: 200, 225 | Linux.sys_listen: 201, 226 | Linux.sys_accept: 202, 227 | Linux.sys_connect: 203, 228 | Linux.sys_getsockname: 204, 229 | Linux.sys_getpeername: 205, 230 | Linux.sys_sendto: 206, 231 | Linux.sys_recvfrom: 207, 232 | Linux.sys_setsockopt: 208, 233 | Linux.sys_getsockopt: 209, 234 | Linux.sys_shutdown: 210, 235 | Linux.sys_sendmsg: 211, 236 | Linux.sys_recvmsg: 212, 237 | Linux.sys_readahead: 213, 238 | Linux.sys_brk: 214, 239 | Linux.sys_munmap: 215, 240 | Linux.sys_mremap: 216, 241 | Linux.sys_add_key: 217, 242 | Linux.sys_request_key: 218, 243 | Linux.sys_keyctl: 219, 244 | Linux.sys_clone: 220, 245 | Linux.sys_execve: 221, 246 | Linux.sys_mmap2: 222, 247 | Linux.sys_fadvise64: 223, 248 | Linux.sys_swapon: 224, 249 | Linux.sys_swapoff: 225, 250 | Linux.sys_mprotect: 226, 251 | Linux.sys_msync: 227, 252 | Linux.sys_mlock: 228, 253 | Linux.sys_munlock: 229, 254 | Linux.sys_mlockall: 230, 255 | Linux.sys_munlockall: 231, 256 | Linux.sys_mincore: 232, 257 | Linux.sys_madvise: 233, 258 | Linux.sys_remap_file_pages: 234, 259 | Linux.sys_mbind: 235, 260 | Linux.sys_get_mempolicy: 236, 261 | Linux.sys_set_mempolicy: 237, 262 | Linux.sys_migrate_pages: 238, 263 | Linux.sys_move_pages: 239, 264 | Linux.sys_rt_tgsigqueueinfo: 240, 265 | Linux.sys_perf_event_open: 241, 266 | Linux.sys_accept4: 242, 267 | Linux.sys_recvmmsg: 243, 268 | Linux.sys_wait4: 260, 269 | Linux.sys_prlimit64: 261, 270 | Linux.sys_fanotify_init: 262, 271 | Linux.sys_fanotify_mark: 263, 272 | Linux.sys_name_to_handle_at: 264, 273 | Linux.sys_open_by_handle_at: 265, 274 | Linux.sys_clock_adjtime: 266, 275 | Linux.sys_syncfs: 267, 276 | Linux.sys_setns: 268, 277 | Linux.sys_sendmmsg: 269, 278 | Linux.sys_process_vm_readv: 270, 279 | Linux.sys_process_vm_writev: 271, 280 | Linux.sys_kcmp: 272, 281 | Linux.sys_finit_module: 273, 282 | Linux.sys_sched_setattr: 274, 283 | Linux.sys_sched_getattr: 275, 284 | Linux.sys_renameat2: 276, 285 | Linux.sys_seccomp: 277, 286 | Linux.sys_getrandom: 278, 287 | Linux.sys_memfd_create: 279, 288 | Linux.sys_bpf: 280, 289 | Linux.sys_execveat: 281, 290 | Linux.sys_userfaultfd: 282, 291 | Linux.sys_membarrier: 283, 292 | Linux.sys_mlock2: 284, 293 | Linux.sys_copy_file_range: 285, 294 | } 295 | 296 | 297 | class LinuxAArch64Mutable(LinuxAArch64): 298 | """ 299 | An environment that targets a 64-bit Linux ARM machine in a writable segment. 300 | """ 301 | 302 | data_finalizer = gnu_as_mutable_data_finalizer(lambda env, _: ['\tadr %s, __data' % env.OFFSET_REG], '//') 303 | 304 | 305 | class LinuxAArch64Stack(LinuxAArch64): 306 | """ 307 | An environment that targets a 64-bit Linux ARM machine that allocates the 308 | required data on the stack. 309 | """ 310 | 311 | data_finalizer = stack_data_finalizer(16) 312 | -------------------------------------------------------------------------------- /pwnypack/shellcode/arm/__init__.py: -------------------------------------------------------------------------------- 1 | from pwnypack.shellcode.base import BaseEnvironment 2 | from pwnypack.shellcode.types import Register 3 | from pwnypack.target import Target 4 | 5 | 6 | __all__ = ['ARM'] 7 | 8 | 9 | class ARM(BaseEnvironment): 10 | """ 11 | Environment that targets a generic, unrestricted ARM architecture. 12 | """ 13 | 14 | target = None #: Target architecture, initialized in __init__. 15 | 16 | R0 = Register('r0') #: r0 register 17 | R1 = Register('r1') #: r1 register 18 | R2 = Register('r2') #: r2 register 19 | R3 = Register('r3') #: r3 register 20 | R4 = Register('r4') #: r4 register 21 | R5 = Register('r5') #: r5 register 22 | R6 = Register('r6') #: r6 register 23 | R7 = Register('r7') #: r7 register 24 | R8 = Register('r8') #: r8 register 25 | R9 = Register('r9') #: r9 register 26 | R10 = Register('r10') #: r10 register 27 | R11 = Register('r11') #: r11 register 28 | R12 = Register('r12') #: r12 register 29 | SP = Register('sp') #: sp register 30 | LR = Register('lr') #: lr register 31 | PC = Register('pc') #: pc register 32 | 33 | REGISTER_WIDTH_MAP = { 34 | 32: [R0, R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, SP, LR, PC] 35 | } 36 | 37 | STACK_REG = SP 38 | OFFSET_REG = R6 39 | TEMP_REG = {32: R7} 40 | 41 | PREAMBLE = [ 42 | '.global _start', 43 | '_start:', 44 | ] 45 | 46 | def __init__(self, endian=None, *args, **kwargs): 47 | self.target = Target(Target.Arch.arm, 32, endian) 48 | super(ARM, self).__init__(*args, **kwargs) 49 | 50 | def reg_push(self, reg): 51 | return ['push {%s}' % reg] 52 | 53 | def reg_pop(self, reg): 54 | return ['pop {%s}' % reg] 55 | 56 | def reg_add_imm(self, reg, value): 57 | if value < 256: 58 | return ['add %s, #%d' % (reg, value)] 59 | 60 | temp_reg = self.TEMP_REG[self.REGISTER_WIDTH[reg]] 61 | if reg is temp_reg: 62 | raise ValueError('Cannot perform large reg_add on temporary register') 63 | 64 | return ['ldr %s, =%d' % (temp_reg, value), 65 | 'add %s, %s' % (reg, temp_reg)] 66 | 67 | def reg_sub_imm(self, reg, value): 68 | if value < 256: 69 | return ['sub %s, #%d' % (reg, value)] 70 | 71 | temp_reg = self.TEMP_REG[self.REGISTER_WIDTH[reg]] 72 | if reg is temp_reg: 73 | raise ValueError('Cannot perform large reg_add on temporary register') 74 | 75 | return ['ldr %s, =%d' % (temp_reg, value), 76 | 'sub %s, %s' % (reg, temp_reg)] 77 | 78 | def reg_add_reg(self, reg1, reg2): 79 | return ['add %s, %s' % (reg1, reg2)] 80 | 81 | def reg_sub_reg(self, reg1, reg2): 82 | return ['sub %s, %s' % (reg1, reg2)] 83 | 84 | def reg_load_imm(self, reg, value): 85 | if -256 < value < 0: 86 | return ['mvn %s, #%d' % (reg, ~value)] 87 | elif not value: 88 | return ['eor %s, %s' % (reg, reg)] 89 | elif 0 < value < 256: 90 | return ['mov %s, #%d' % (reg, value)] 91 | else: 92 | return ['ldr %s, =%d' % (reg, value)] 93 | 94 | def reg_load_reg(self, dest_reg, src_reg): 95 | return ['mov %s, %s' % (dest_reg, src_reg)] 96 | 97 | def reg_load_offset(self, reg, value): 98 | return ['add %s, %s, #%d' % (reg, self.OFFSET_REG, value)] 99 | 100 | def jump_reg(self, reg): 101 | return ['bx %s' % reg] 102 | -------------------------------------------------------------------------------- /pwnypack/shellcode/arm/thumb.py: -------------------------------------------------------------------------------- 1 | from pwnypack.shellcode.arm import ARM 2 | from pwnypack.target import Target 3 | 4 | 5 | __all__ = ['ARMThumb'] 6 | 7 | 8 | class ARMThumb(ARM): 9 | """ 10 | Environment that targets a generic, unrestricted ARM architecture using 11 | the Thumb instruction set. 12 | """ 13 | 14 | def __init__(self, endian=None, *args, **kwargs): 15 | super(ARMThumb, self).__init__(endian, *args, **kwargs) 16 | self.target.mode |= Target.Mode.arm_thumb 17 | 18 | def reg_add_imm(self, reg, value): 19 | if value < 256: 20 | return ['add %s, #%d' % (reg, value)] 21 | 22 | temp_reg = self.TEMP_REG[self.REGISTER_WIDTH[reg]] 23 | if reg is temp_reg: 24 | raise ValueError('Cannot perform large reg_add on temporary register') 25 | 26 | return ['ldr %s, =%d' % (temp_reg, value), 27 | 'add %s, %s' % (temp_reg, reg), 28 | 'mov %s, %s' % (reg, temp_reg)] 29 | 30 | def reg_sub_imm(self, reg, value): 31 | if value < 256: 32 | return ['sub %s, #%d' % (reg, value)] 33 | 34 | temp_reg = self.TEMP_REG[self.REGISTER_WIDTH[reg]] 35 | if reg is temp_reg: 36 | raise ValueError('Cannot perform large reg_add on temporary register') 37 | 38 | return ['ldr %s, =%d' % (temp_reg, -value), 39 | 'add %s, %s' % (temp_reg, reg), 40 | 'mov %s, %s' % (reg, temp_reg)] 41 | 42 | def reg_load_imm(self, reg, value): 43 | if -256 < value < 0: 44 | return ['mov %s, #%d' % (reg, -value), 45 | 'neg %s, %s' % (reg, reg)] 46 | else: 47 | return super(ARMThumb, self).reg_load_imm(reg, value) 48 | 49 | def reg_load_offset(self, reg, value): 50 | return self.reg_load_imm(reg, value) + \ 51 | ['add %s, %s' % (reg, self.OFFSET_REG)] 52 | -------------------------------------------------------------------------------- /pwnypack/shellcode/arm/thumb_mixed.py: -------------------------------------------------------------------------------- 1 | from pwnypack.shellcode.arm.thumb import ARMThumb 2 | 3 | 4 | __all__ = ['ARMThumbMixed'] 5 | 6 | 7 | class ARMThumbMixed(ARMThumb): 8 | """ 9 | Environment that targets a generic, unrestricted ARM architecture that 10 | switches to the Thumb instruction set. 11 | """ 12 | 13 | PREAMBLE = [ 14 | '.global _start', 15 | '.arm', 16 | '_start:', 17 | '\tadd r0, pc, #1', 18 | '\tbx r0', 19 | '.thumb', 20 | '__thumbcode:', 21 | ] 22 | -------------------------------------------------------------------------------- /pwnypack/shellcode/mutable_data.py: -------------------------------------------------------------------------------- 1 | import six 2 | 3 | 4 | def gnu_as_mutable_data_finalizer(get_pc, comment_char, align=True): 5 | def data_finalizer(env, code, data): 6 | return (get_pc(env, code) if data or env.buffers else []) + code + [ 7 | '', 8 | '.pool', 9 | ] + ([ 10 | '.align', 11 | ] if align else []) + [ 12 | '__data:', 13 | ] + [ 14 | '\t.byte %s %s %r' % ( 15 | ', '.join(hex(b) for b in six.iterbytes(datum)), 16 | comment_char, 17 | orig_datum, 18 | ) 19 | for datum, (_, orig_datum) in six.iteritems(data) 20 | ] 21 | return data_finalizer 22 | -------------------------------------------------------------------------------- /pwnypack/shellcode/ops.py: -------------------------------------------------------------------------------- 1 | __all__ = ['LoadRegister'] 2 | 3 | 4 | class LoadRegister(object): 5 | def __init__(self, register, value): 6 | self.register = register 7 | self.value = value 8 | 9 | def __repr__(self): 10 | return 'LoadRegister(%r, %r)' % (self.register, self.value) 11 | 12 | 13 | class SyscallInvoke(object): 14 | def __init__(self, syscall_def, args): 15 | self.syscall_def = syscall_def 16 | self.args = args 17 | 18 | def __repr__(self): 19 | return '%s(%s)' % (self.syscall_def.name, ', '.join(repr(a) for a in self.args)) 20 | -------------------------------------------------------------------------------- /pwnypack/shellcode/stack_data.py: -------------------------------------------------------------------------------- 1 | import six 2 | 3 | from pwnypack.packing import U 4 | 5 | 6 | def _load_push(env, value): 7 | temp_reg = env.TEMP_REG[env.target.bits] 8 | return env.reg_load(temp_reg, value) + \ 9 | env.reg_push(temp_reg) 10 | 11 | 12 | def stack_data_finalizer(stack_align, push_strategy=_load_push): 13 | def proxy(env, code, data): 14 | reg_width = env.target.bits // 8 15 | 16 | data = (b''.join(six.iterkeys(data)))[::-1] 17 | data_size = len(data) 18 | 19 | data_adjust = reg_width - (data_size % reg_width) 20 | if data_adjust != reg_width: 21 | data += b'\xff' * data_adjust 22 | data_size += data_adjust 23 | 24 | buffer_size = sum(buffer.length for buffer in env.buffers) 25 | stack_size = buffer_size + data_size 26 | stack_adjust = stack_align - (stack_size % stack_align) 27 | if stack_adjust != stack_align: 28 | stack_size += stack_adjust 29 | 30 | push_code = env.reg_sub(env.STACK_REG, stack_size - data_size) 31 | 32 | while data: 33 | chunk, data = data[:reg_width], data[reg_width:] 34 | push_code.extend(push_strategy(env, U(chunk[::-1], target=env.target))) 35 | 36 | push_code.extend(env.reg_load(env.OFFSET_REG, env.STACK_REG)) 37 | if data_adjust != reg_width: 38 | push_code.extend(env.reg_add(env.OFFSET_REG, data_adjust)) 39 | 40 | return ['\t%s' % line for line in push_code] + code 41 | return proxy 42 | -------------------------------------------------------------------------------- /pwnypack/shellcode/translate.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | 3 | import six 4 | 5 | from pwnypack.shellcode.ops import SyscallInvoke, LoadRegister 6 | from pwnypack.shellcode.types import Register, Offset, Buffer 7 | from pwnypack import bytecode as bc 8 | 9 | 10 | __all__ = ['translate', 'fragment'] 11 | 12 | 13 | class Fragment(object): 14 | def __init__(self, f): 15 | self.f = f 16 | 17 | def __call__(self, env, *args, **kwargs): 18 | return translate(env, self.f, *args, **kwargs) 19 | 20 | 21 | def fragment(f): 22 | """ 23 | Decorator to turn a function into a shellcode fragment that can be called 24 | as a function from within a translated function. 25 | 26 | Arguments: 27 | f(callable): The function to mark as a shellcode fragment. 28 | 29 | Returns: 30 | callable: The decorated shellcode fragment. 31 | """ 32 | 33 | return Fragment(f) 34 | 35 | 36 | def translate(env, func, *args, **kwargs): 37 | """ 38 | Given a shellcode environment, a function and its parameters, translate 39 | the function to a list of shellcode operations ready to be compiled or 40 | assembled using :meth:`~pwnypack.shellcode.base.BaseEnvironment.compile` 41 | or :meth:`~pwnypack.shellcode.base.BaseEnvironment.assemble`. 42 | 43 | Arguments: 44 | env(~pwnypack.shellcode.base.Base): An instance of a shellcode 45 | environment. 46 | func(callable): The function to translate to shellcode. 47 | args(...): The positional arguments for the function. 48 | kwargs(...): The keyword arguments for the function. 49 | 50 | Returns: 51 | list: The high-level shellcode operations. 52 | """ 53 | 54 | func_code = six.get_function_code(func) 55 | func_globals = dict(__builtins__) 56 | func_globals.update(six.get_function_globals(func)) 57 | 58 | ops = bc.disassemble(func_code.co_code) 59 | 60 | program = [] 61 | 62 | f_args = inspect.getcallargs(func, *args, **kwargs) 63 | variables = dict( 64 | (func_code.co_varnames.index(arg_name), arg_value) 65 | for arg_name, arg_value in six.iteritems(f_args) 66 | ) 67 | 68 | stack = [] 69 | for op in ops: 70 | if op.name == 'LOAD_CONST': 71 | stack.append(func_code.co_consts[op.arg]) 72 | 73 | elif op.name == 'LOAD_GLOBAL': 74 | global_name = func_code.co_names[op.arg] 75 | stack.append(getattr(env, global_name, func_globals.get(global_name))) 76 | 77 | elif op.name == 'LOAD_FAST': 78 | var_name = func_code.co_varnames[op.arg] 79 | stack.append(getattr(env, var_name, variables.get(op.arg))) 80 | 81 | elif op.name == 'BUILD_LIST': 82 | items = stack[-op.arg:] 83 | del stack[-op.arg:] 84 | stack.append(items) 85 | 86 | elif op.name == 'LOAD_ATTR': 87 | obj = stack.pop() 88 | stack.append(getattr(obj, func_code.co_names[op.arg])) 89 | 90 | elif op.name == 'CALL_FUNCTION': 91 | nargs = op.arg & 0xff 92 | nkwargs = op.arg >> 8 93 | 94 | if nkwargs: 95 | f_kwargs = dict(zip(stack[-nkwargs * 2::2], stack[-nkwargs * 2 + 1::2])) 96 | del stack[-nkwargs * 2:] 97 | else: 98 | f_kwargs = {} 99 | 100 | if nargs: 101 | f_args = stack[-nargs:] 102 | del stack[-nargs:] 103 | else: 104 | f_args = [] 105 | 106 | f = stack.pop() 107 | if isinstance(f, Fragment): 108 | stack.append(f(env, *f_args, **f_kwargs)) 109 | else: 110 | stack.append(f(*f_args, **f_kwargs)) 111 | 112 | elif op.name == 'STORE_FAST': 113 | value = stack.pop() 114 | var_name = func_code.co_varnames[op.arg] 115 | var = getattr(env, var_name, variables.get(op.arg, None)) 116 | if isinstance(var, Register): 117 | program.append(LoadRegister(var, value)) 118 | else: 119 | variables[op.arg] = value 120 | 121 | elif op.name == 'POP_TOP': 122 | value = stack.pop() 123 | if isinstance(value, SyscallInvoke): 124 | program.append(value) 125 | elif isinstance(value, list): 126 | program.extend(value) 127 | else: 128 | raise ValueError('No idea how to compile %s' % (value,)) 129 | 130 | elif op.name == 'RETURN_VALUE': 131 | stack.pop() 132 | 133 | elif op.name == 'DUP_TOP': 134 | value = stack[-1] 135 | if isinstance(value, SyscallInvoke): 136 | stack.insert(-1, env.SYSCALL_RET_REG) 137 | else: 138 | stack.append(value) 139 | 140 | elif op.name == 'BINARY_SUBSCR': 141 | index = stack.pop() 142 | value = stack.pop() 143 | stack.append(value[index]) 144 | 145 | elif op.name == 'STORE_SUBSCR': 146 | index = stack.pop() 147 | value = stack.pop() 148 | new_value = stack.pop() 149 | var = value[index] 150 | if isinstance(var, Register): 151 | program.append(LoadRegister(var, new_value)) 152 | else: 153 | value[index] = new_value 154 | 155 | elif op.name == 'INPLACE_ADD': 156 | value = stack.pop() 157 | reg = stack.pop() 158 | if not isinstance(reg, Register): 159 | raise TypeError('In-place addition is only supported on registers') 160 | program.extend(env.reg_add(reg, value)) 161 | stack.append(reg) 162 | 163 | elif op.name == 'INPLACE_SUBTRACT': 164 | value = stack.pop() 165 | reg = stack.pop() 166 | if not isinstance(reg, Register): 167 | raise TypeError('In-place subtraction is only supported on registers') 168 | program.extend(env.reg_sub(reg, value)) 169 | stack.append(reg) 170 | 171 | else: 172 | raise RuntimeError('Unsupported opcode: %s' % op.name) 173 | 174 | return program 175 | -------------------------------------------------------------------------------- /pwnypack/shellcode/types.py: -------------------------------------------------------------------------------- 1 | import six 2 | 3 | from pwnypack.shellcode.ops import SyscallInvoke 4 | 5 | 6 | __all__ = ['Register', 'Offset', 'Buffer', 'Array', 'NUMERIC', 'PTR', 'CHARP', 'CHARPP', 'SyscallDef'] 7 | 8 | 9 | class Register(object): 10 | def __init__(self, name): 11 | self.name = name 12 | 13 | def __str__(self): 14 | return self.name 15 | 16 | def __repr__(self): 17 | return '' % self.name.upper() 18 | 19 | 20 | class Offset(int): 21 | def __repr__(self): 22 | return 'Offset(%d)' % self 23 | 24 | 25 | class Buffer(object): 26 | def __init__(self, offset, length): 27 | self.offset = offset 28 | self.length = length 29 | 30 | def __repr__(self): 31 | return 'Buffer(%d@%d)' % (self.length, self.offset) 32 | 33 | 34 | class Array(object): 35 | def __init__(self, item_type): 36 | self.item_type = item_type 37 | 38 | def verify(self, value): 39 | if value is not None: 40 | for item in value: 41 | self.item_type.verify(item) 42 | 43 | 44 | class NUMERIC(object): 45 | @staticmethod 46 | def verify(value): 47 | if not isinstance(value, (six.integer_types, Register, SyscallInvoke)): 48 | raise ValueError('syscall argument not of expected type int') 49 | 50 | 51 | class PTR(object): 52 | @staticmethod 53 | def verify(value): 54 | if not isinstance(value, (type(None), six.integer_types, Register, Offset, Buffer, SyscallInvoke, 55 | six.string_types, six.binary_type, list)): 56 | raise ValueError('syscall argument not of expected type ptr') 57 | 58 | 59 | class CHARP(object): 60 | @staticmethod 61 | def verify(value): 62 | if not isinstance(value, (type(None), six.string_types, six.binary_type, Register, Offset, Buffer)): 63 | raise ValueError('syscall argument not of expected type str/bytes') 64 | 65 | 66 | CHARPP = Array(CHARP) 67 | 68 | 69 | class SyscallDef(object): 70 | def __init__(self, name, *arg_types): 71 | self.name = name 72 | self.arg_types = arg_types 73 | 74 | def __call__(self, *args): 75 | if not len(args) == len(self.arg_types): 76 | raise ValueError('Incorrect number of syscall arguments') 77 | 78 | for arg_type, arg_value in zip(self.arg_types, args): 79 | arg_type.verify(arg_value) 80 | 81 | return SyscallInvoke(self, args) 82 | 83 | def __repr__(self): 84 | if self.arg_types: 85 | def translate_arg_type(t): 86 | if t is NUMERIC: 87 | return 'int' 88 | elif t is PTR: 89 | return 'void *' 90 | elif t is CHARP: 91 | return 'void **' 92 | elif isinstance(t, Array): 93 | return '%s[]' % translate_arg_type(t.item_type) 94 | else: 95 | return repr(t) 96 | 97 | return 'SyscallDef(%s: %s)' % (self.name, ', '.join(translate_arg_type(a) for a in self.arg_types)) 98 | else: 99 | return 'SyscallDef(%s)' % self.name 100 | -------------------------------------------------------------------------------- /pwnypack/shellcode/x86/__init__.py: -------------------------------------------------------------------------------- 1 | from pwnypack.shellcode.base import BaseEnvironment 2 | from pwnypack.shellcode.types import Register 3 | from pwnypack.target import Target 4 | 5 | 6 | __all__ = ['X86'] 7 | 8 | 9 | class X86(BaseEnvironment): 10 | """ 11 | Environment that targets a generic, unrestricted X86 architecture. 12 | """ 13 | 14 | target = Target(arch=Target.Arch.x86, bits=32) #: Target architecture 15 | 16 | # 8-bit registers on X86 17 | AL = Register('al') #: al register 18 | AH = Register('ah') #: ah register 19 | BL = Register('bl') #: bl register 20 | BH = Register('bh') #: bh register 21 | CL = Register('cl') #: cl register 22 | CH = Register('ch') #: ch register 23 | DL = Register('dl') #: dl register 24 | DH = Register('dh') #: dh register 25 | 26 | # 16-bit registers on X86 27 | AX = Register('ax') #: ax register 28 | BX = Register('bx') #: bx register 29 | CX = Register('cx') #: cx register 30 | DX = Register('dx') #: dx register 31 | SI = Register('si') #: si register 32 | DI = Register('di') #: di register 33 | SP = Register('sp') #: sp register 34 | BP = Register('bp') #: bp register 35 | IP = Register('ip') #: ip register 36 | 37 | # 32-bit registers on X86 38 | EAX = Register('eax') #: eax register 39 | EBX = Register('ebx') #: ebx register 40 | ECX = Register('ecx') #: ecx register 41 | EDX = Register('edx') #: edx register 42 | ESI = Register('esi') #: esi register 43 | EDI = Register('edi') #: edi register 44 | ESP = Register('esp') #: esp register 45 | EBP = Register('ebp') #: ebp register 46 | EIP = Register('eip') #: eip register 47 | 48 | REGISTER_WIDTH_MAP = { 49 | 8: (AL, BL, CL, DL, AH, BH, CH, DH), 50 | 16: (AX, BX, CX, DX, SI, DI, BP, SP), 51 | 32: (EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP), 52 | } 53 | 54 | STACK_REG = ESP 55 | OFFSET_REG = EBP 56 | TEMP_REG = { 57 | 32: EAX, 58 | 16: AX, 59 | 8: AL, 60 | } 61 | 62 | @property 63 | def PREAMBLE(self): 64 | return [ 65 | 'BITS %d' % self.target.bits.value, 66 | 'global _start', 67 | '', 68 | '_start:', 69 | ] 70 | 71 | def __init__(self, *args, **kwargs): 72 | super(X86, self).__init__(*args, **kwargs) 73 | 74 | def reg_push(self, reg): 75 | return ['push %s' % reg] 76 | 77 | def reg_pop(self, reg): 78 | return ['pop %s' % reg] 79 | 80 | def reg_add_imm(self, reg, value): 81 | if value == 1: 82 | return ['inc %s' % reg] 83 | return ['add %s, %d' % (reg, value)] 84 | 85 | def reg_sub_imm(self, reg, value): 86 | if value == 1: 87 | return ['dec %s' % reg] 88 | return ['sub %s, %d' % (reg, value)] 89 | 90 | def reg_add_reg(self, reg1, reg2): 91 | return ['add %s, %s' % (reg1, reg2)] 92 | 93 | def reg_sub_reg(self, reg1, reg2): 94 | return ['sub %s, %s' % (reg1, reg2)] 95 | 96 | def reg_load_imm(self, reg, value): 97 | reg_width = self.REGISTER_WIDTH[reg] 98 | if value >= 2 ** reg_width: 99 | raise ValueError('%d does not fit %s' % (value, reg)) 100 | 101 | if not value: 102 | return ['xor %s, %s' % (reg, reg)] 103 | else: 104 | return ['mov %s, %d' % (reg, value)] 105 | 106 | def reg_load_reg(self, dest_reg, src_reg): 107 | return ['mov %s, %s' % (dest_reg, src_reg)] 108 | 109 | def reg_load_offset(self, reg, value): 110 | return ['lea %s, [%s + %d]' % (reg, self.OFFSET_REG, value)] 111 | 112 | def jump_reg(self, reg): 113 | return ['jmp %s' % reg] 114 | -------------------------------------------------------------------------------- /pwnypack/shellcode/x86/mutable_data.py: -------------------------------------------------------------------------------- 1 | try: 2 | from collections import OrderedDict 3 | except ImportError: 4 | from ordereddict import OrderedDict 5 | 6 | import six 7 | 8 | from pwnypack.asm import asm 9 | 10 | 11 | def _pack_data(data): 12 | return ['__data:'] + [ 13 | '\tdb ' + ','.join(hex(b) for b in six.iterbytes(datum)) + ' ; ' + repr(orig_datum) 14 | for datum, (_, orig_datum) in six.iteritems(data) 15 | ] 16 | 17 | 18 | def nasm_mutable_data_finalizer(env, code, data): 19 | """ 20 | Simple data allocation strategy that expects the code to be in a writable 21 | segment. We just append the data to the end of the code. 22 | """ 23 | 24 | if env.target.bits == 32: 25 | get_pc = [ 26 | '\tcall __getpc0', 27 | '__getpc0:', 28 | '\tpop %s' % env.OFFSET_REG, 29 | '\tadd %s, __data - __getpc0' % env.OFFSET_REG, 30 | '__realstart:', 31 | ] 32 | else: 33 | get_pc = ['\tlea %s, [rel __data]' % env.OFFSET_REG] 34 | 35 | if data or env.buffers: 36 | return get_pc + code + _pack_data(data) 37 | else: 38 | return code 39 | 40 | 41 | def nasm_null_safe_mutable_data_finalizer(env, code, data): 42 | """ 43 | Simple data allocation strategy that expects the code to be in a writable 44 | segment. We just append the data to the end of the code. 45 | """ 46 | 47 | if data or env.buffers: 48 | # Determine length of nullify + shellcode and adjust data pointer 49 | xor_offsets = [] 50 | masked_data = OrderedDict() 51 | 52 | for datum, (offset, orig_datum) in six.iteritems(data): 53 | xor_offsets.extend([ 54 | offset + i 55 | for i, b in enumerate(six.iterbytes(datum)) 56 | if b in (0, 10, 13) 57 | ]) 58 | 59 | masked_datum = b''.join([ 60 | six.int2byte(b) if b not in (0, 10, 13) 61 | else six.int2byte(b ^ 0xff) 62 | for b in six.iterbytes(datum) 63 | ]) 64 | masked_data[masked_datum] = (offset, orig_datum) 65 | 66 | if xor_offsets: 67 | # Build code to restore NUL, \r and \n 68 | temp_reg = env.TEMP_REG[env.target.bits] 69 | null_code = env.reg_load(env.BL, 255) + \ 70 | env.reg_load(temp_reg, env.OFFSET_REG) 71 | 72 | last_offset = 0 73 | for offset in xor_offsets: 74 | offset -= last_offset 75 | null_code.extend( 76 | env.reg_add(temp_reg, offset) + 77 | ['xor [%s], bl' % temp_reg] 78 | ) 79 | last_offset += offset 80 | code = ['\t%s' % line for line in null_code] + code 81 | data = masked_data 82 | 83 | code_len = len(asm('\n'.join(code), target=env.target)) 84 | adjust_ebp = env.reg_add(env.OFFSET_REG, code_len) 85 | 86 | return [ 87 | '\tjmp __getpc1', 88 | '__getpc0:', 89 | '\tpop %s' % env.OFFSET_REG, 90 | ] + [ 91 | '\t%s' % line for line in adjust_ebp 92 | ] + [ 93 | '\tjmp __realstart', 94 | '__getpc1:', 95 | '\tcall __getpc0', 96 | '__realstart:', 97 | ] + code + _pack_data(data) 98 | else: 99 | return code 100 | -------------------------------------------------------------------------------- /pwnypack/shellcode/x86/null_safe.py: -------------------------------------------------------------------------------- 1 | from pwnypack.codec import find_xor_mask 2 | from pwnypack.packing import P, U 3 | from pwnypack.shellcode.x86 import X86 4 | 5 | 6 | __all__ = ['X86NullSafe'] 7 | 8 | 9 | class X86NullSafe(X86): 10 | HALF_REG = {} 11 | for pair in ((X86.EAX, X86.AX, X86.AL), (X86.EBX, X86.BX, X86.BL), 12 | (X86.ECX, X86.CX, X86.CL), (X86.EDX, X86.DX, X86.DL), 13 | (X86.ESI, X86.SI), (X86.EDI, X86.DI), (X86.EBP, X86.BP), 14 | (X86.ESP, X86.SP)): 15 | for reg, half in zip(pair, pair[1:]): 16 | HALF_REG[reg] = half 17 | del pair, reg, half 18 | 19 | HIGH_REG = { 20 | X86.AX: X86.AH, 21 | X86.BX: X86.BH, 22 | X86.CX: X86.CH, 23 | X86.DX: X86.DH, 24 | } 25 | 26 | def _reg_add_sub_imm(self, insn, insn_1, reg, value): 27 | reg_width = self.REGISTER_WIDTH[reg] 28 | temp_reg = self.TEMP_REG[reg_width] 29 | 30 | if not value: 31 | return [] 32 | elif value < 3: 33 | return ['%s %s' % (insn_1, reg)] * value 34 | elif value in (10, 13): 35 | return ['%s %s, %d' % (insn, reg, value - 1), 36 | '%s %s' % (insn_1, reg)] 37 | elif value < 128 and value not in (10, 13): 38 | return ['%s %s, %d' % (insn, reg, value)] 39 | elif reg is not temp_reg: 40 | return self.reg_load_imm(temp_reg, value) + \ 41 | ['%s %s, %s' % (insn, reg, temp_reg)] 42 | else: 43 | return ['push %s' % reg] + \ 44 | self.reg_load_imm(reg, value) + [ 45 | '%s [%s], %s' % (insn, self.STACK_REG, reg), 46 | 'pop %s' % reg, 47 | ] 48 | 49 | def reg_add_imm(self, reg, value): 50 | return self._reg_add_sub_imm('add', 'inc', reg, value) 51 | 52 | def reg_sub_imm(self, reg, value): 53 | return self._reg_add_sub_imm('sub', 'dec', reg, value) 54 | 55 | def reg_add_reg(self, reg1, reg2): 56 | return ['add %s, %s' % (reg1, reg2)] 57 | 58 | def reg_sub_reg(self, reg1, reg2): 59 | return ['sub %s, %s' % (reg1, reg2)] 60 | 61 | def reg_load_imm(self, reg, value): 62 | orig_reg = reg 63 | orig_reg_width = reg_width = self.REGISTER_WIDTH[reg] 64 | 65 | value &= 2 ** reg_width - 1 66 | 67 | # 0 value, always use xor 68 | if not value: 69 | return ['xor %s, %s' % (reg, reg)] 70 | 71 | preamble = [] 72 | 73 | # reduce size until smallest addressable sub-register 74 | while reg in self.HALF_REG and value < 2 ** (reg_width // 2): 75 | if not preamble: 76 | if reg_width <= 32: 77 | preamble = ['xor %s, %s' % (reg, reg)] 78 | else: 79 | # xor eax, eax is zero extended and yields a shorter opcode 80 | preamble = ['xor %s, %s' % (self.HALF_REG[reg], self.HALF_REG[reg])] 81 | 82 | reg = self.HALF_REG[reg] 83 | reg_width //= 2 84 | 85 | zero = preamble + (['xor %s, %s' % (reg, reg)] if not preamble else []) 86 | 87 | # Fast path direct load where higher 8 bit of 16 bit register is addressable 88 | if reg in self.HIGH_REG and value < 0x10000 and not value & 0xff: 89 | reg = self.HIGH_REG[reg] 90 | reg_width //= 2 91 | value >>= 8 92 | 93 | # Find a xor solution to compose this value without \0, \r or \n 94 | xor_solution = [ 95 | U(s, bits=reg_width) 96 | for s in find_xor_mask(P(value, bits=reg_width)) 97 | ] 98 | 99 | # Find a solution for when value < 256 and is \0, \r or \n. 100 | if value & 0xff in (0, 10, 13): 101 | value += 1 102 | postamble = ['dec %s' % reg] 103 | else: 104 | postamble = [] 105 | 106 | # Value contains no NUL, \r or \n bytes, load directly 107 | if reg_width == 8 or len(xor_solution) == 1: 108 | return preamble + ['mov %s, %d' % (reg, value)] + postamble 109 | 110 | # Fast path direct load of 7 bit in non 8 bit addressable registers 111 | elif value < 0x80: 112 | # 32bit register yields smallest opcode 113 | if orig_reg_width == 64: 114 | reg = self.HALF_REG[orig_reg] 115 | elif orig_reg_width == 32: 116 | reg = orig_reg 117 | 118 | if value == 1: 119 | return zero + ['inc %s' % reg] + postamble 120 | else: 121 | return zero + ['or %s, %d' % (reg, value)] + postamble 122 | 123 | # Use xor to mask NUL bytes 124 | else: 125 | result = preamble 126 | 127 | if reg_width <= 32: 128 | # Use xor r, imm. Not suitable for 64bit, xor reg, imm64 does not exist 129 | result.append('mov %s, %d' % (reg, xor_solution[0])) 130 | for xor_value in xor_solution[1:]: 131 | result.append('xor %s, %d' % (reg, xor_value)) 132 | 133 | elif reg is not self.TEMP_REG[reg_width]: 134 | # Use the temporary register to compose our solution 135 | temp_reg = self.TEMP_REG[reg_width] 136 | result.append('mov %s, %d' % (reg, xor_solution[0])) 137 | for xor_value in xor_solution[1:]: 138 | result.extend([ 139 | 'mov %s, %d' % (temp_reg, xor_value), 140 | 'xor %s, %s' % (reg, temp_reg), 141 | ]) 142 | 143 | else: 144 | # We're loading the temporary register, use the stack 145 | result.extend([ 146 | 'mov %s, %d' % (reg, xor_solution[0]), 147 | 'push %s' % reg, 148 | ]) 149 | for xor_value in xor_solution[1:]: 150 | result.extend([ 151 | 'mov %s, %d' % (reg, xor_value), 152 | 'xor [%s], %s' % (self.STACK_REG, reg), 153 | ]) 154 | result.append('pop %s' % reg) 155 | return result 156 | 157 | def reg_load_offset(self, reg, value): 158 | return self.reg_load(reg, int(value)) + \ 159 | ['add %s, %s' % (reg, self.OFFSET_REG)] 160 | -------------------------------------------------------------------------------- /pwnypack/shellcode/x86/stack_data.py: -------------------------------------------------------------------------------- 1 | from pwnypack.shellcode.stack_data import stack_data_finalizer 2 | 3 | 4 | __all__ = ['x86_stack_data_finalizer'] 5 | 6 | 7 | def _push_imm32(_, chunk): 8 | return ['push dword %d' % chunk] 9 | 10 | 11 | def x86_stack_data_finalizer(stack_align): 12 | return stack_data_finalizer(stack_align, _push_imm32) 13 | -------------------------------------------------------------------------------- /pwnypack/shellcode/x86_64/__init__.py: -------------------------------------------------------------------------------- 1 | from pwnypack.shellcode.types import Register 2 | from pwnypack.shellcode.x86 import X86 3 | from pwnypack.target import Target 4 | 5 | 6 | __all__ = ['X86_64'] 7 | 8 | 9 | class X86_64(X86): 10 | """ 11 | Environment that targets a generic, unrestricted X86_64 architecture. 12 | """ 13 | 14 | target = Target(arch=Target.Arch.x86, bits=64) #: Target architecture 15 | 16 | # 8-bit registers on X86_64 17 | R8B = Register('r8b') #: r8b register 18 | R9B = Register('r9b') #: r9b register 19 | R10B = Register('r10b') #: r10b register 20 | R11B = Register('r11b') #: r11b register 21 | R12B = Register('r12b') #: r12b register 22 | R13B = Register('r13b') #: r13b register 23 | R14B = Register('r14b') #: r14b register 24 | R15B = Register('r15b') #: r15b register 25 | 26 | # 16-bit registers on X86_64 27 | R8W = Register('r8w') #: r8w register 28 | R9W = Register('r9w') #: r9w register 29 | R10W = Register('r10w') #: r10w register 30 | R11W = Register('r11w') #: r11w register 31 | R12W = Register('r12w') #: r12w register 32 | R13W = Register('r13w') #: r13w register 33 | R14W = Register('r14w') #: r14w register 34 | R15W = Register('r15w') #: r15w register 35 | 36 | # 32-bit registers on X86_64 37 | R8D = Register('r8d') #: r8d register 38 | R9D = Register('r9d') #: r9d register 39 | R10D = Register('r10d') #: r10d register 40 | R11D = Register('r11d') #: r11d register 41 | R12D = Register('r12d') #: r12d register 42 | R13D = Register('r13d') #: r13d register 43 | R14D = Register('r14d') #: r14d register 44 | R15D = Register('r16d') #: r16d register 45 | 46 | # 64-bit registers on X86_64 47 | RAX = Register('rax') #: rax register 48 | RBX = Register('rbx') #: rbx register 49 | RCX = Register('rcx') #: rcx register 50 | RDX = Register('rdx') #: rdx register 51 | RSI = Register('rsi') #: rsi register 52 | RDI = Register('rdi') #: rdi register 53 | RSP = Register('rsp') #: rsp register 54 | RBP = Register('rbp') #: rbp register 55 | RIP = Register('rip') #: rip register 56 | R8 = Register('r8') #: r8 register 57 | R9 = Register('r9') #: r9 register 58 | R10 = Register('r10') #: r10 register 59 | R11 = Register('r11') #: r11 register 60 | R12 = Register('r12') #: r12 register 61 | R13 = Register('r13') #: r13 register 62 | R14 = Register('r14') #: r14 register 63 | R15 = Register('r15') #: r15 register 64 | 65 | REGISTER_WIDTH_MAP = X86.REGISTER_WIDTH_MAP.copy() 66 | REGISTER_WIDTH_MAP[8] += (R8B, R9B, R10B, R11B, R12B, R13B, R14B, R15B) 67 | REGISTER_WIDTH_MAP[16] += (R8D, R9D, R10D, R11D, R12D, R13D, R14D, R15D) 68 | REGISTER_WIDTH_MAP[32] += (R8W, R9W, R10W, R11W, R12W, R13W, R14W, R15W) 69 | REGISTER_WIDTH_MAP[64] = (RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, R8, R9, R10, R11, R12, R13, R14, R15) 70 | 71 | STACK_REG = RSP 72 | OFFSET_REG = RBP 73 | TEMP_REG = X86.TEMP_REG.copy() 74 | TEMP_REG[64] = RAX 75 | -------------------------------------------------------------------------------- /pwnypack/shellcode/x86_64/null_safe.py: -------------------------------------------------------------------------------- 1 | from pwnypack.shellcode.x86.null_safe import X86NullSafe 2 | from pwnypack.shellcode.x86_64 import X86_64 3 | 4 | 5 | __all__ = ['X86_64NullSafe'] 6 | 7 | 8 | class X86_64NullSafe(X86_64, X86NullSafe): 9 | HALF_REG = X86NullSafe.HALF_REG.copy() 10 | 11 | for pair in ((X86_64.RAX, X86_64.EAX), (X86_64.RBX, X86_64.EBX), (X86_64.RCX, X86_64.ECX), (X86_64.RDX, X86_64.EDX), 12 | (X86_64.RSI, X86_64.ESI), (X86_64.RDI, X86_64.EDI), (X86_64.RBP, X86_64.EBP), (X86_64.RSP, X86_64.ESP), 13 | (X86_64.R8, X86_64.R8D, X86_64.R8W, X86_64.R8B), 14 | (X86_64.R9, X86_64.R9D, X86_64.R9W, X86_64.R9B), 15 | (X86_64.R10, X86_64.R10D, X86_64.R10W, X86_64.R10B), 16 | (X86_64.R11, X86_64.R11D, X86_64.R11W, X86_64.R11B), 17 | (X86_64.R12, X86_64.R12D, X86_64.R12W, X86_64.R12B), 18 | (X86_64.R13, X86_64.R13D, X86_64.R13W, X86_64.R13B), 19 | (X86_64.R14, X86_64.R14D, X86_64.R14W, X86_64.R14B), 20 | (X86_64.R15, X86_64.R15D, X86_64.R15W, X86_64.R15B)): 21 | for reg, half in zip(pair, pair[1:]): 22 | HALF_REG[reg] = half 23 | del pair, reg, half 24 | -------------------------------------------------------------------------------- /pwnypack/shellcode/x86_64/stack_data.py: -------------------------------------------------------------------------------- 1 | from pwnypack.shellcode.stack_data import stack_data_finalizer 2 | 3 | 4 | def _load_push(env, value): 5 | return env.reg_load(env.RBX, value) + \ 6 | env.reg_push(env.RBX) 7 | 8 | 9 | def x86_64_null_safe_stack_data_finalizer(stack_align): 10 | return stack_data_finalizer(stack_align, _load_push) 11 | -------------------------------------------------------------------------------- /pwnypack/target.py: -------------------------------------------------------------------------------- 1 | """ 2 | The :class:`Target` class describes the architecture of a targeted machine, 3 | executable or environment. It encodes the generic architecture, the word 4 | size, the byte order and an architecture dependant mode. 5 | 6 | It is used throughout *pwnypack* to determine how data should be interpreted 7 | or emitted. 8 | """ 9 | 10 | 11 | import platform 12 | from enum import IntEnum, Enum 13 | import sys 14 | 15 | 16 | __all__ = [ 17 | 'Target', 18 | 'target', 19 | ] 20 | 21 | 22 | class Target(object): 23 | class Arch(Enum): 24 | """ 25 | Describes the general architecture of a target. 26 | """ 27 | 28 | x86 = 'x86' #: X86 architecture. 29 | arm = 'arm' #: ARM architecture. 30 | unknown = 'unknown' #: Any other architecture. 31 | 32 | class Bits(IntEnum): 33 | """ 34 | The target architecture's word size. 35 | """ 36 | 37 | bits_32 = 32 #: 32 bit word size. 38 | bits_64 = 64 #: 64 bit word size. 39 | 40 | class Endian(IntEnum): 41 | """ 42 | The target architecture's byte order. 43 | """ 44 | 45 | little = 0 #: Little endian. 46 | big = 1 #: Big endian. 47 | 48 | class Mode(IntEnum): 49 | """ 50 | Architecture dependant mode flags. 51 | """ 52 | 53 | arm_v8 = 1 << 0 #: Use ARM V8 instruction set 54 | arm_thumb = 1 << 1 #: Use ARM Thumb instruction set 55 | arm_m_class = 1 << 2 #: Use ARMv7-M instruction set 56 | 57 | _DEFAULT_ARCH = { 58 | 'i386': Arch.x86, 59 | 'x86_64': Arch.x86, 60 | } 61 | 62 | _VALID_BITS = {} 63 | 64 | _DEFAULT_BITS = { 65 | Arch.x86: Bits.bits_32, 66 | Arch.arm: Bits.bits_32, 67 | } 68 | 69 | _VALID_ENDIAN = { 70 | Arch.x86: [Endian.little], 71 | } 72 | 73 | _DEFAULT_ENDIAN = { 74 | Arch.x86: Endian.little, 75 | Arch.arm: Endian.little, 76 | } 77 | 78 | _arch = None 79 | _bits = None 80 | _endian = None 81 | _mode = None 82 | 83 | def __init__(self, arch=None, bits=None, endian=None, mode=0): 84 | if arch is None: 85 | arch = self._DEFAULT_ARCH.get(platform.machine(), Target.Arch.unknown) 86 | 87 | if bits is None: 88 | bits = Target.Bits(64 if platform.architecture()[0] == '64bit' else 32) 89 | 90 | if endian is None: 91 | endian = Target.Endian.__members__[sys.byteorder] 92 | 93 | self.arch = arch 94 | self.bits = bits 95 | self.endian = endian 96 | self.mode = mode 97 | 98 | @property 99 | def arch(self): 100 | """ 101 | The target's architecture. One of :class:`Target.Arch`. 102 | """ 103 | return self._arch 104 | 105 | @arch.setter 106 | def arch(self, arch): 107 | if arch is None: 108 | arch = Target.Arch.unknown 109 | self._arch = Target.Arch(arch) 110 | 111 | @property 112 | def bits(self): 113 | """ 114 | The target architecture word size. One of :class:`Target.Bits`. 115 | """ 116 | if self._bits is None: 117 | value = self._DEFAULT_BITS.get(self.arch) 118 | if value is None: 119 | raise NotImplementedError('Could not determine the default word size of %s architecture.' % self.arch) 120 | return value 121 | else: 122 | return self._bits 123 | 124 | @bits.setter 125 | def bits(self, value): 126 | if value is None: 127 | self._bits = None 128 | else: 129 | self._bits = Target.Bits(value) 130 | assert self._bits in self._VALID_BITS.get( 131 | self.arch, 132 | [Target.Bits.bits_32, Target.Bits.bits_64] 133 | ), '%s not supported on %s' % (self._bits, self._arch) 134 | 135 | @property 136 | def endian(self): 137 | """ 138 | The target architecture byte order. One of :class:`Target.Endian`. 139 | """ 140 | if self._endian is None: 141 | value = self._DEFAULT_ENDIAN[self.arch] 142 | if value is None: 143 | raise NotImplementedError('Could not determine the default byte order of %s architecture.' % self.arch) 144 | return value 145 | else: 146 | return self._endian 147 | 148 | @endian.setter 149 | def endian(self, endian): 150 | if endian is None: 151 | self._endian = None 152 | else: 153 | self._endian = Target.Endian(endian) 154 | assert self._endian in self._VALID_ENDIAN.get( 155 | self.arch, 156 | [Target.Endian.little, Target.Endian.big] 157 | ), '%s not supported on %s' % (self._endian, self._arch) 158 | 159 | @property 160 | def mode(self): 161 | """ 162 | The target architecture dependant flags. OR'ed values of :class:`Target.Mode`. 163 | """ 164 | return self._mode 165 | 166 | @mode.setter 167 | def mode(self, value): 168 | self._mode = int(value) 169 | 170 | def assume(self, other): 171 | """ 172 | Assume the identity of another target. This can be useful to make the 173 | global target assume the identity of an ELF executable. 174 | 175 | Arguments: 176 | other(:class:`Target`): The target whose identity to assume. 177 | 178 | Example: 179 | >>> from pwny import * 180 | >>> target.assume(ELF('my-executable')) 181 | """ 182 | 183 | self._arch = other._arch 184 | self._bits = other._bits 185 | self._endian = other._endian 186 | self._mode = other._mode 187 | 188 | def __repr__(self): 189 | return '%s(arch=%s,bits=%s,endian=%s,mode=%s)' % ( 190 | self.__class__.__name__, 191 | self.arch.name, 192 | self.bits.value, 193 | self.endian.name, 194 | self.mode 195 | ) 196 | 197 | 198 | target = Target() 199 | """ 200 | The global, default target. If no targeting information is provided to a 201 | function, this is the target that will be used. 202 | """ 203 | -------------------------------------------------------------------------------- /pwnypack/util.py: -------------------------------------------------------------------------------- 1 | """ 2 | The util module contains various utility functions. 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import argparse 8 | import binascii 9 | import re 10 | import sys 11 | 12 | from six.moves import range 13 | 14 | import pwnypack.codec 15 | import pwnypack.main 16 | 17 | __all__ = [ 18 | 'cycle', 19 | 'cycle_find', 20 | 'reghex', 21 | ] 22 | 23 | 24 | def deBruijn(n, k): 25 | """ 26 | An implementation of the FKM algorithm for generating the de Bruijn 27 | sequence containing all k-ary strings of length n, as described in 28 | "Combinatorial Generation" by Frank Ruskey. 29 | """ 30 | 31 | a = [ 0 ] * (n + 1) 32 | 33 | def gen(t, p): 34 | if t > n: 35 | for v in a[1:p + 1]: 36 | yield v 37 | else: 38 | a[t] = a[t - p] 39 | 40 | for v in gen(t + 1, p): 41 | yield v 42 | 43 | for j in range(a[t - p] + 1, k): 44 | a[t] = j 45 | for v in gen(t + 1, t): 46 | yield v 47 | 48 | return gen(1, 1) 49 | 50 | 51 | def cycle(length, width=4): 52 | """ 53 | Generate a de Bruijn sequence of a given length (and width). A de Bruijn 54 | sequence is a set of varying repetitions where each sequence of *n* 55 | characters is unique within the sequence. This type of sequence can be 56 | used to easily find the offset to the return pointer when exploiting a 57 | buffer overflow. 58 | 59 | Args: 60 | length(int): The length of the sequence to generate. 61 | width(int): The width of each element in the sequence. 62 | 63 | Returns: 64 | str: The sequence. 65 | 66 | Example: 67 | >>> from pwny import * 68 | >>> cycle(80) 69 | AAAABAAACAAADAAAEAAAFAAAGAAAHAAAIAAAJAAAKAAALAAAMAAANAAAOAAAPAAAQAAARAAASAAATAAA 70 | """ 71 | 72 | iter = deBruijn(width, 26) 73 | return ''.join([chr(ord('A') + next(iter)) for i in range(length)]) 74 | 75 | 76 | def cycle_find(key, width=4): 77 | """ 78 | Given an element of a de Bruijn sequence, find its index in that sequence. 79 | 80 | Args: 81 | key(str): The piece of the de Bruijn sequence to find. 82 | width(int): The width of each element in the sequence. 83 | 84 | Returns: 85 | int: The index of ``key`` in the de Bruijn sequence. 86 | """ 87 | 88 | key_len = len(key) 89 | buf = '' 90 | 91 | it = deBruijn(width, 26) 92 | 93 | for i in range(key_len): 94 | buf += chr(ord('A') + next(it)) 95 | 96 | if buf == key: 97 | return 0 98 | 99 | for i, c in enumerate(it): 100 | buf = buf[1:] + chr(ord('A') + c) 101 | if buf == key: 102 | return i + 1 103 | 104 | return -1 105 | 106 | 107 | REGHEX_PATTERN = r'(([a-fA-F0-9]{2})|(([?.])(\{\d+\})?)|(\*|\+)|\s+)' 108 | reghex_check = re.compile(REGHEX_PATTERN + '+') 109 | reghex_regex = re.compile(REGHEX_PATTERN) 110 | 111 | 112 | def reghex(pattern): 113 | """ 114 | Compile a regular hexpression (a short form regular expression subset 115 | specifically designed for searching for binary strings). 116 | 117 | A regular hexpression consists of hex tuples interspaced with control 118 | characters. The available control characters are: 119 | 120 | - ``?``: Any byte (optional). 121 | - ``.``: Any byte (required). 122 | - ``?{n}``: A set of 0 up to *n* bytes. 123 | - ``.{n}``: A set of exactly *n* bytes. 124 | - ``*``: Any number of bytes (or no bytes at all). 125 | - ``+``: Any number of bytes (at least one byte). 126 | 127 | Args: 128 | pattern(str): The reghex pattern. 129 | 130 | Returns: 131 | regexp: A regular expression as returned by ``re.compile()``. 132 | """ 133 | 134 | if not reghex_check.match(pattern): 135 | raise SyntaxError('Invalid reghex pattern.') 136 | 137 | b_pattern = b'' 138 | 139 | for match in reghex_regex.finditer(pattern): 140 | _, match_hex, _, match_char, match_char_len, match_star_plus = match.groups() 141 | if match_hex: 142 | b_pattern += re.escape(pwnypack.codec.dehex(match_hex)) 143 | elif match_char: 144 | if match_char == '?': 145 | if match_char_len is None: 146 | b_pattern += b'.?' 147 | else: 148 | b_pattern += ('.{0,%d}?' % int(match_char_len[1:-1])).encode('ascii') 149 | else: 150 | if match_char_len is None: 151 | b_pattern += b'.' 152 | else: 153 | b_pattern += b'.' * int(match_char_len[1:-1]) 154 | elif match_star_plus: 155 | b_pattern += b'.' + match_star_plus.encode('ascii') + b'?' 156 | 157 | try: 158 | return re.compile(b_pattern) 159 | except (TypeError, binascii.Error, re.error): 160 | raise SyntaxError('Invalid reghex pattern.') 161 | 162 | 163 | @pwnypack.main.register('cycle') 164 | def cycle_app(parser, cmd, args): # pragma: no cover 165 | """ 166 | Generate a de Bruijn sequence of a given length. 167 | """ 168 | 169 | parser.add_argument('-w', '--width', type=int, default=4, help='the length of the cycled value') 170 | parser.add_argument('length', type=int, help='the cycle length to generate') 171 | args = parser.parse_args(args) 172 | return cycle(args.length, args.width) 173 | 174 | 175 | @pwnypack.main.register('cycle-find') 176 | def cycle_find_app(_parser, cmd, args): # pragma: no cover 177 | """ 178 | Find the first position of a value in a de Bruijn sequence. 179 | """ 180 | 181 | parser = argparse.ArgumentParser( 182 | prog=_parser.prog, 183 | description=_parser.description, 184 | ) 185 | parser.add_argument('-w', '--width', type=int, default=4, help='the length of the cycled value') 186 | parser.add_argument('value', help='the value to determine the position of, read from stdin if missing', nargs='?') 187 | args = parser.parse_args(args) 188 | index = cycle_find(pwnypack.main.string_value_or_stdin(args.value), args.width) 189 | if index == -1: 190 | print('Not found.') 191 | sys.exit(1) 192 | else: 193 | print('Found at position: %d' % index) 194 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 3 | 4 | [pytest] 5 | addopts = --cov-report term-missing --cov=pwnypack 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from setuptools.command.test import test as TestCommand 3 | import sys 4 | import os 5 | 6 | 7 | __version__ = '0.9.0' 8 | 9 | 10 | class PyTest(TestCommand): 11 | user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")] 12 | 13 | def initialize_options(self): 14 | TestCommand.initialize_options(self) 15 | self.pytest_args = [] 16 | 17 | def finalize_options(self): 18 | TestCommand.finalize_options(self) 19 | self.test_args = [] 20 | self.test_suite = True 21 | 22 | def run_tests(self): 23 | import pytest 24 | errno = pytest.main(self.pytest_args) 25 | sys.exit(errno) 26 | 27 | 28 | def read_file(filename): 29 | try: 30 | with open(os.path.join(os.path.dirname(__file__), filename)) as f: 31 | return f.read() 32 | except IOError: 33 | return '' 34 | 35 | 36 | setup( 37 | setup_requires=['setuptools>=17.1'], 38 | 39 | name='pwnypack', 40 | packages=find_packages(), 41 | version=__version__, 42 | description='Official Certified Edible Dinosaurs CTF toolkit.', 43 | long_description=read_file('README.rst') + '\n' + read_file('changelog.rst'), 44 | author='Ingmar Steen', 45 | author_email='iksteen@gmail.com', 46 | url='https://github.com/edibledinos/pwnypack/', 47 | download_url='https://github.com/edibledinos/pwnypack/tarball/v%s' % __version__, 48 | install_requires=['six', 'kwonly-args'], 49 | extras_require={ 50 | ':python_version<"2.7"': ['counter', 'ordereddict', 'argparse'], 51 | ':python_version<"3.4"': ['enum34'], 52 | ':python_version<"3.3"': ['shutilwhich'], 53 | 'asm': ['keystone-engine'], 54 | 'disasm': ['capstone'], 55 | 'pwnbook:python_version>="2.7"': ['jupyter'], 56 | 'rop': ['capstone'], 57 | 'shell:python_version>="2.7"': ['ipython'], 58 | 'ssh:python_version>="2.7"': ['paramiko'], 59 | 'all': ['capstone', 'keystone-engine'], 60 | 'all:python_version>="2.7"': ['ipython', 'jupyter', 'paramiko'], 61 | }, 62 | tests_require=['mock', 'pytest-cov', 'pytest'], 63 | cmdclass = {'test': PyTest}, 64 | entry_points={ 65 | 'console_scripts': [ 66 | 'pwny=pwnypack.main:main', 67 | ], 68 | }, 69 | keywords=['wargame', 'ctf'], 70 | classifiers=[ 71 | 'Environment :: Console', 72 | 'Intended Audience :: Developers', 73 | 'Intended Audience :: Science/Research', 74 | 'Intended Audience :: System Administrators', 75 | 'License :: OSI Approved :: MIT License', 76 | 'Operating System :: OS Independent', 77 | 'Programming Language :: Python', 78 | 'Programming Language :: Python :: 2', 79 | 'Programming Language :: Python :: 3', 80 | 'Topic :: Security', 81 | 'Topic :: Security :: Cryptography', 82 | ], 83 | ) 84 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import pwny 3 | 4 | 5 | @pytest.fixture(autouse=True) 6 | def target(): 7 | pwny.target.assume(pwny.Target(arch=pwny.Target.Arch.x86, bits=pwny.Target.Bits.bits_32)) 8 | -------------------------------------------------------------------------------- /tests/test_asm.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import pwny 4 | 5 | 6 | target_x86_32 = pwny.Target('x86', 32) 7 | target_x86_64 = pwny.Target('x86', 64) 8 | target_arm_32_le = pwny.Target('arm', 32, pwny.Target.Endian.little) 9 | target_arm_32_be = pwny.Target('arm', 32, pwny.Target.Endian.big) 10 | target_armv7m_32_le = pwny.Target('arm', 32, pwny.Target.Endian.little, mode=pwny.Target.Mode.arm_m_class) 11 | target_armv7m_32_be = pwny.Target('arm', 32, pwny.Target.Endian.big, mode=pwny.Target.Mode.arm_m_class) 12 | target_arm_64_le = pwny.Target('arm', 64, pwny.Target.Endian.little) 13 | target_arm_64_be = pwny.Target('arm', 64, pwny.Target.Endian.big) 14 | target_unknown_32 = pwny.Target('unknown', 32, pwny.Target.Endian.little) 15 | 16 | 17 | ASM_TESTS = [ 18 | # Note: set up sets default arch to x86 32bit 19 | (None, pwny.AsmSyntax.nasm, 'mov al,[0xced]', b'\xa0\xed\x0c\x00\x00'), 20 | (target_x86_32, None, 'mov al,[0xced]', b'\xa0\xed\x0c\x00\x00'), 21 | (target_x86_32, pwny.AsmSyntax.nasm, 'mov al,[0xced]', b'\xa0\xed\x0c\x00\x00'), 22 | (target_x86_32, pwny.AsmSyntax.att, 'movb 0xced, %al', b'\xa0\xed\x0c\x00\x00'), 23 | (target_x86_32, pwny.AsmSyntax.intel, 'mov al, byte ptr [0xced]', b'\xa0\xed\x0c\x00\x00'), 24 | 25 | (target_x86_64, None, 'mov al,[0xced]', b'\x8a\x04%\xed\x0c\x00\x00'), 26 | (target_x86_64, pwny.AsmSyntax.nasm, 'mov al,[0xced]', b'\x8a\x04%\xed\x0c\x00\x00'), 27 | (target_x86_64, pwny.AsmSyntax.att, 'movb 0xced, %al', b'\x8a\x04%\xed\x0c\x00\x00'), 28 | (target_x86_64, pwny.AsmSyntax.intel, 'mov al, byte ptr [0xced]', b'\x8a\x04%\xed\x0c\x00\x00'), 29 | 30 | (target_arm_32_le, None, 'add r0, r1, #0', b'\x00\x00\x81\xe2'), 31 | (target_arm_32_be, None, 'add r0, r1, #0', b'\xe2\x81\x00\x00'), 32 | (target_armv7m_32_le, None, 'push {r0}', b'\x01\xb4'), 33 | (target_armv7m_32_be, None, 'push {r0}', b'\xb4\x01'), 34 | 35 | (target_arm_64_le, None, 'add x0, x1, #0', b' \x00\x00\x91'), 36 | # The output of as/ld and capstone disagree. Assume a failure will happen. 37 | pytest.mark.xfail()((target_arm_64_be, None, 'add x0, x1, #0', b'\x91\x00\x00 ')), 38 | ] 39 | 40 | 41 | @pytest.mark.parametrize(('test_target', 'syntax', 'source', 'result'), ASM_TESTS) 42 | def test_asm(test_target, syntax, source, result, target): 43 | try: 44 | output = pwny.asm(source, syntax=syntax, target=test_target) 45 | except RuntimeError: 46 | # Toolchain wasn't found. Unfortunate, but unavoidable on travis-ci atm. 47 | pytest.skip('No suitable binutils was found for %s' % target) 48 | assert output == result, 'Got %r, expected %r' % (output, result) 49 | 50 | 51 | @pytest.mark.xfail(raises=SyntaxError) 52 | def test_asm_syntax_error(): 53 | pwny.asm('mov ced, 3', target=target_x86_32) 54 | 55 | 56 | @pytest.mark.xfail(raises=NotImplementedError) 57 | def test_asm_unsupported_target(): 58 | pwny.asm('mov al, [0xced]', target=target_unknown_32) 59 | 60 | 61 | @pytest.mark.parametrize(('test_target', 'syntax', 'result', 'source'), ASM_TESTS) 62 | def test_disasm(test_target, syntax, source, result, target): 63 | output = pwny.disasm(source, syntax=syntax, target=test_target) 64 | assert output == [result], 'Got %r, expected %r' % (output, result) 65 | 66 | 67 | @pytest.mark.xfail(raises=NotImplementedError) 68 | def test_disasm_unsupported_target(): 69 | pwny.disasm(b'\x5f', target=target_unknown_32) 70 | -------------------------------------------------------------------------------- /tests/test_codec.py: -------------------------------------------------------------------------------- 1 | import pwny 2 | 3 | 4 | def test_xor_int(): 5 | assert pwny.xor(61, b'fooo') == b'[RRR' 6 | 7 | 8 | def test_xor_str(): 9 | assert pwny.xor(b'abcd', b'fooo') == b'\x07\r\x0c\x0b' 10 | assert pwny.xor(b'abcd', b'fooofooo') == b'\x07\r\x0c\x0b\x07\r\x0c\x0b' 11 | 12 | 13 | def test_rot13(): 14 | assert pwny.rot13('whax') == 'junk' 15 | 16 | 17 | def test_caesar(): 18 | assert pwny.caesar(1, 'abcXYZ') == 'bcdYZA' 19 | 20 | 21 | def test_enhex(): 22 | assert pwny.enhex(b'ABCD') == '41424344' 23 | 24 | 25 | def test_dehex(): 26 | assert pwny.dehex('41424344') == b'ABCD' 27 | 28 | 29 | def test_enb64(): 30 | assert pwny.enb64(b'ABCD') == 'QUJDRA==' 31 | 32 | 33 | def test_deb64(): 34 | assert pwny.deb64('QUJDRA==') == b'ABCD' 35 | 36 | 37 | def test_deurlform(): 38 | assert pwny.deurlform('foo=bar&baz=quux&baz=corge') == {'foo': ['bar'], 'baz': ['quux', 'corge']} 39 | 40 | 41 | def test_enurlform(): 42 | assert pwny.enurlform((('foo', 'bar'), ('baz', ['quux', 'corge']))) == 'foo=bar&baz=quux&baz=corge' 43 | 44 | 45 | def test_enurlquote(): 46 | assert pwny.enurlquote('Foo Bar/Baz') == 'Foo%20Bar/Baz' 47 | 48 | 49 | def test_enurlquote_plus(): 50 | assert pwny.enurlquote('Foo Bar/Baz', plus=True) == 'Foo+Bar%2FBaz' 51 | 52 | 53 | def test_deurlquote(): 54 | assert pwny.deurlquote('Foo%20Bar%2FBaz') == 'Foo Bar/Baz' 55 | 56 | 57 | def test_deurlquote_no_plus(): 58 | assert pwny.deurlquote('Foo+Bar%2FBaz') == 'Foo+Bar/Baz' 59 | 60 | 61 | def test_deurlquote_plus(): 62 | assert pwny.deurlquote('Foo+Bar%2FBaz', True) == 'Foo Bar/Baz' 63 | 64 | 65 | def test_frequency(): 66 | assert pwny.frequency('ABCD') == {'A': 1, 'B': 1, 'C': 1, 'D': 1} 67 | -------------------------------------------------------------------------------- /tests/test_elf.py: -------------------------------------------------------------------------------- 1 | import six 2 | import mock 3 | import pytest 4 | 5 | import pwny 6 | 7 | 8 | headers = [ 9 | { 10 | 'data': b'\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00>\x00\x01\x00\x00\x00@\x04@\x00\x00' 11 | b'\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00p\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x008\x00\t' 12 | b'\x00@\x00\x1e\x00\x1b\x00', 13 | 'header': { 14 | 'arch': pwny.Target.Arch.x86, 15 | 'bits': 64, 16 | 'endian': pwny.Target.Endian.little, 17 | 'abi_version': 0, 18 | 'entry': 4195392, 19 | 'flags': 0, 20 | 'hsize': 64, 21 | 'osabi': pwny.ELF.OSABI.system_v, 22 | 'phentsize': 56, 23 | 'phnum': 9, 24 | 'phoff': 64, 25 | 'shentsize': 64, 26 | 'shnum': 30, 27 | 'shoff': 4464, 28 | 'shstrndx': 27, 29 | 'type': pwny.ELF.Type.executable 30 | }, 31 | }, 32 | { 33 | 'data': b'\x7fELF\x02\x01\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00>\x00\x01\x00\x00\x00N\x0f@\x00\x00' 34 | b'\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00\xa8\x1d\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x008\x00' 35 | b'\x06\x00@\x00\x1f\x00\x1c\x00', 36 | 'header': { 37 | 'arch': pwny.Target.Arch.x86, 38 | 'bits': 64, 39 | 'endian': pwny.Target.Endian.little, 40 | 'abi_version': 0, 41 | 'entry': 4198222, 42 | 'flags': 0, 43 | 'hsize': 64, 44 | 'osabi': pwny.ELF.OSABI.linux, 45 | 'phentsize': 56, 46 | 'phnum': 6, 47 | 'phoff': 64, 48 | 'shentsize': 64, 49 | 'shnum': 31, 50 | 'shoff': 794024, 51 | 'shstrndx': 28, 52 | 'type': pwny.ELF.Type.executable, 53 | }, 54 | }, 55 | { 56 | 'data': b'\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x03\x00\x01\x00\x00\x00 \x83\x04\x08' 57 | b'4\x00\x00\x00L\x11\x00\x00\x00\x00\x00\x004\x00 \x00\t\x00(\x00\x1e\x00\x1b\x00\x06\x00\x00\x004\x00' 58 | b'\x00\x004\x80\x04\x08', 59 | 'header': { 60 | 'arch':pwny.Target.Arch.x86, 61 | 'bits': 32, 62 | 'endian': pwny.Target.Endian.little, 63 | 'abi_version': 0, 64 | 'entry': 134513440, 65 | 'flags': 0, 66 | 'hsize': 52, 67 | 'osabi': pwny.ELF.OSABI.system_v, 68 | 'phentsize': 32, 69 | 'phnum': 9, 70 | 'phoff': 52, 71 | 'shentsize': 40, 72 | 'shnum': 30, 73 | 'shoff': 4428, 74 | 'shstrndx': 27, 75 | 'type': pwny.ELF.Type.executable, 76 | }, 77 | }, 78 | { 79 | 'data': b'\x7fELF\x01\x01\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x03\x00\x01\x00\x00\x00\n\x8d\x04\x08' 80 | b'4\x00\x00\x00\xf0 \n\x00\x00\x00\x00\x004\x00 \x00\x06\x00(\x00\x1f\x00\x1c\x00\x01\x00\x00\x00\x00' 81 | b'\x00\x00\x00\x00\x80\x04\x08', 82 | 'header': { 83 | 'arch': pwny.Target.Arch.x86, 84 | 'bits': 32, 85 | 'endian': pwny.Target.Endian.little, 86 | 'abi_version': 0, 87 | 'entry': 134515978, 88 | 'flags': 0, 89 | 'hsize': 52, 90 | 'osabi': pwny.ELF.OSABI.linux, 91 | 'phentsize': 32, 92 | 'phnum': 6, 93 | 'phoff': 52, 94 | 'sections': [], 95 | 'shentsize': 40, 96 | 'shnum': 31, 97 | 'shoff': 663792, 98 | 'shstrndx': 28, 99 | 'strings': None, 100 | 'type': pwny.ELF.Type.executable, 101 | }, 102 | } 103 | ] 104 | 105 | 106 | @pytest.mark.parametrize('header', headers) 107 | def test_elf_header_parse(header): 108 | data, values = header['data'], header['header'] 109 | elf = pwny.ELF() 110 | elf._parse_header(data) 111 | for key, value in values.items(): 112 | assert getattr(elf, key, value) == value, '%s != %r (%r)' % (key, value, getattr(elf, key)) 113 | 114 | 115 | def test_elf_parse_section_invalid_type(): 116 | section_fmt = 'IIIIIIIIII' 117 | section_fmt_size = pwny.pack_size(section_fmt) 118 | section = pwny.pack( 119 | section_fmt, 120 | 1, 121 | 0x5fffffff, 122 | 0, 123 | 0, 124 | 0, 125 | 0, 126 | 0, 127 | 0, 128 | 0, 129 | section_fmt_size, 130 | ) 131 | elf = pwny.ELF() 132 | elf.bits = 32 133 | section = elf.SectionHeader(elf, section) 134 | assert section.type == pwny.ELF.SectionHeader.Type.unknown 135 | 136 | 137 | def test_elf_parse_file_32(): 138 | b = six.BytesIO() 139 | 140 | header_fmt = '4sBBBBB7sHHIIIIIHHHHHH' 141 | header_fmt_size = pwny.pack_size(header_fmt) 142 | 143 | section_fmt = 'IIIIIIIIII' 144 | section_fmt_size = pwny.pack_size(section_fmt) 145 | 146 | strings_section = b'\x00strings\x00' 147 | 148 | b.write(pwny.pack( 149 | header_fmt, 150 | b'\x7fELF', 151 | 1, 152 | 1, 153 | 1, 154 | pwny.ELF.OSABI.linux.value, 155 | 0, 156 | b'\x00' * 7, 157 | pwny.ELF.Type.executable.value, 158 | pwny.ELF.Machine.i386.value, 159 | 1, 160 | 0, 161 | 0, 162 | header_fmt_size, 163 | 0, 164 | header_fmt_size, 165 | 0, 166 | 0, 167 | section_fmt_size, 168 | 1, 169 | 0, 170 | )) 171 | 172 | b.write(pwny.pack( 173 | section_fmt, 174 | 1, 175 | pwny.ELF.SectionHeader.Type.null.value, 176 | 0, 177 | 0, 178 | header_fmt_size + section_fmt_size, 179 | len(strings_section), 180 | 0, 181 | 0, 182 | 0, 183 | section_fmt_size, 184 | )) 185 | 186 | b.write(strings_section) 187 | b.seek(0) 188 | 189 | elf = pwny.ELF(b) 190 | 191 | for key, value in { 192 | 'arch': pwny.Target.Arch.x86, 193 | 'bits': 32, 194 | 'endian': pwny.Target.Endian.little, 195 | 'abi_version': 0, 196 | 'entry': 0, 197 | 'flags': 0, 198 | 'hsize': header_fmt_size, 199 | 'osabi': pwny.ELF.OSABI.linux, 200 | 'phentsize': 0, 201 | 'phnum': 0, 202 | 'phoff': 0, 203 | 'shentsize': section_fmt_size, 204 | 'shnum': 1, 205 | 'shoff': header_fmt_size, 206 | 'shstrndx': 0, 207 | 'type': pwny.ELF.Type.executable, 208 | }.items(): 209 | assert getattr(elf, key) == value, '%s != %r (%r)' % (key, value, getattr(elf, key)) 210 | 211 | assert len(elf.section_headers) == 1 212 | 213 | 214 | def test_elf_parse_file_64(): 215 | b = six.BytesIO() 216 | 217 | header_fmt = '4sBBBBB7sHHIQQQIHHHHHH' 218 | header_fmt_size = pwny.pack_size(header_fmt) 219 | 220 | section_fmt = 'IIQQQQIIQQ' 221 | section_fmt_size = pwny.pack_size(section_fmt) 222 | 223 | strings_section = b'\x00strings\x00' 224 | 225 | b.write(pwny.pack( 226 | header_fmt, 227 | b'\x7fELF', 228 | 2, 229 | 1, 230 | 1, 231 | pwny.ELF.OSABI.linux.value, 232 | 0, 233 | b'\x00' * 7, 234 | pwny.ELF.Type.executable.value, 235 | pwny.ELF.Machine.x86_64.value, 236 | 1, 237 | 0, 238 | 0, 239 | header_fmt_size, 240 | 0, 241 | header_fmt_size, 242 | 0, 243 | 0, 244 | section_fmt_size, 245 | 1, 246 | 0, 247 | )) 248 | 249 | b.write(pwny.pack( 250 | section_fmt, 251 | 1, 252 | pwny.ELF.SectionHeader.Type.null.value, 253 | 0, 254 | 0, 255 | header_fmt_size + section_fmt_size, 256 | len(strings_section), 257 | 0, 258 | 0, 259 | 0, 260 | section_fmt_size, 261 | )) 262 | 263 | b.write(strings_section) 264 | b.seek(0) 265 | 266 | elf = pwny.ELF(b) 267 | 268 | for key, value in { 269 | 'arch': pwny.Target.Arch.x86, 270 | 'bits': 64, 271 | 'endian': pwny.Target.Endian.little, 272 | 'abi_version': 0, 273 | 'entry': 0, 274 | 'flags': 0, 275 | 'hsize': header_fmt_size, 276 | 'osabi': pwny.ELF.OSABI.linux, 277 | 'phentsize': 0, 278 | 'phnum': 0, 279 | 'phoff': 0, 280 | 'shentsize': section_fmt_size, 281 | 'shnum': 1, 282 | 'shoff': header_fmt_size, 283 | 'shstrndx': 0, 284 | 'type': pwny.ELF.Type.executable, 285 | }.items(): 286 | assert getattr(elf, key) == value, '%s != %r' % (key, value) 287 | 288 | assert len(elf.section_headers) == 1 289 | 290 | 291 | def test_elf_parse_file_open(): 292 | b = six.BytesIO() 293 | 294 | header_fmt = '4sBBBBB7sHHIIIIIHHHHHH' 295 | header_fmt_size = pwny.pack_size(header_fmt) 296 | 297 | b.write(pwny.pack( 298 | header_fmt, 299 | b'\x7fELF', 300 | 1, 301 | 1, 302 | 1, 303 | pwny.ELF.OSABI.linux.value, 304 | 0, 305 | b'\x00' * 7, 306 | pwny.ELF.Type.executable.value, 307 | pwny.ELF.Machine.i386.value, 308 | 1, 309 | 0, 310 | 0, 311 | header_fmt_size, 312 | 0, 313 | 0, 314 | 0, 315 | 0, 316 | 0, 317 | 0, 318 | 0, 319 | )) 320 | 321 | b.seek(0) 322 | 323 | with mock.patch('pwnypack.elf.open', create=True) as mock_open: 324 | mock_open.return_value = b 325 | pwny.ELF('test.elf') 326 | -------------------------------------------------------------------------------- /tests/test_packing.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import pwny 4 | 5 | 6 | target_little_endian = pwny.Target(arch=pwny.Target.Arch.unknown, endian=pwny.Target.Endian.little) 7 | target_big_endian = pwny.Target(arch=pwny.Target.Arch.unknown, endian=pwny.Target.Endian.big) 8 | 9 | 10 | def test_pack(): 11 | assert pwny.pack('I', 0x41424344) == b'DCBA' 12 | 13 | 14 | def test_pack_format_with_endian(): 15 | assert pwny.pack('>I', 0x41424344) == b'ABCD' 16 | 17 | 18 | def test_pack_explicit_endian(): 19 | assert pwny.pack('I', 0x41424344, endian=pwny.Target.Endian.big) == b'ABCD' 20 | 21 | 22 | def test_pack_explicit_target(): 23 | assert pwny.pack('I', 0x41424344, target=target_big_endian) == b'ABCD' 24 | 25 | 26 | @pytest.mark.xfail(raises=NotImplementedError) 27 | def test_pack_invalid_endian(): 28 | pwny.pack('I', 1, endian='invalid') 29 | 30 | 31 | def test_unpack(): 32 | assert pwny.unpack('I', b'DCBA') == (0x41424344,) 33 | 34 | 35 | def test_unpack_format_with_endian(): 36 | assert pwny.unpack('>I', b'ABCD') == (0x41424344,) 37 | 38 | 39 | def test_unpack_explicit_endian(): 40 | assert pwny.unpack('I', b'ABCD', endian=pwny.Target.Endian.big) == (0x41424344,) 41 | 42 | 43 | def test_unpack_explicit_target(): 44 | assert pwny.unpack('I', b'ABCD', target=target_big_endian) == (0x41424344,) 45 | 46 | 47 | @pytest.mark.xfail(raises=NotImplementedError) 48 | def test_unpack_invalid_endian(): 49 | pwny.unpack('I', 'AAAA', endian='invalid') 50 | 51 | 52 | def test_pack_size(): 53 | # This tests both pack_size in general as well as not padding the byte. 54 | assert pwny.pack_size('bq') == 9 55 | 56 | 57 | short_signed_data = [ 58 | [8, -0x7f, b'\x81'], 59 | [16, -0x7fff, b'\x80\x01'], 60 | [32, -0x7fffffff, b'\x80\x00\x00\x01'], 61 | [64, -0x7fffffffffffffff, b'\x80\x00\x00\x00\x00\x00\x00\x01'], 62 | ] 63 | 64 | 65 | short_unsigned_data = [ 66 | [8, 0x61, b'a'], 67 | [16, 0x6162, b'ab'], 68 | [32, 0x61626364, b'abcd'], 69 | [64, 0x6162636465666768, b'abcdefgh'], 70 | ] 71 | 72 | 73 | def test_short_form_pack(): 74 | for width, num, bytestr in short_signed_data: 75 | f = 'p%d' % width 76 | yield check_short_form_pack, f, num, bytestr[::-1] 77 | yield check_short_form_pack_endian, f, num, bytestr[::-1], pwny.Target.Endian.little 78 | yield check_short_form_pack_endian, f, num, bytestr, pwny.Target.Endian.big 79 | 80 | for width, num, bytestr in short_unsigned_data: 81 | f = 'P%d' % width 82 | yield check_short_form_pack, f, num, bytestr[::-1] 83 | yield check_short_form_pack_endian, f, num, bytestr[::-1], pwny.Target.Endian.little 84 | yield check_short_form_pack_endian, f, num, bytestr, pwny.Target.Endian.big 85 | 86 | 87 | def test_short_form_unpack(): 88 | for width, num, bytestr in short_signed_data: 89 | f = 'u%d' % width 90 | yield check_short_form_unpack, f, num, bytestr[::-1] 91 | yield check_short_form_unpack_endian, f, num, bytestr[::-1], pwny.Target.Endian.little 92 | yield check_short_form_unpack_endian, f, num, bytestr, pwny.Target.Endian.big 93 | 94 | for width, num, bytestr in short_unsigned_data: 95 | f = 'U%d' % width 96 | yield check_short_form_unpack, f, num, bytestr[::-1] 97 | yield check_short_form_unpack_endian, f, num, bytestr[::-1], pwny.Target.Endian.little 98 | yield check_short_form_unpack_endian, f, num, bytestr, pwny.Target.Endian.big 99 | 100 | 101 | def test_pointer_pack(): 102 | yield check_short_form_pack, 'p', -66052, b'\xfc\xfd\xfe\xff' 103 | yield check_short_form_pack_endian, 'p', -66052, b'\xfc\xfd\xfe\xff', pwny.Target.Endian.little 104 | yield check_short_form_pack_endian, 'p', -66052, b'\xff\xfe\xfd\xfc', pwny.Target.Endian.big 105 | 106 | yield check_short_form_pack, 'P', 4294901244, b'\xfc\xfd\xfe\xff' 107 | yield check_short_form_pack_endian, 'P', 4294901244, b'\xfc\xfd\xfe\xff', pwny.Target.Endian.little 108 | yield check_short_form_pack_endian, 'P', 4294901244, b'\xff\xfe\xfd\xfc', pwny.Target.Endian.big 109 | 110 | 111 | def test_pointer_unpack(): 112 | yield check_short_form_unpack, 'u', -66052, b'\xfc\xfd\xfe\xff' 113 | yield check_short_form_unpack_endian, 'u', -66052, b'\xfc\xfd\xfe\xff', pwny.Target.Endian.little 114 | yield check_short_form_unpack_endian, 'u', -66052, b'\xff\xfe\xfd\xfc', pwny.Target.Endian.big 115 | 116 | yield check_short_form_unpack, 'U', 4294901244, b'\xfc\xfd\xfe\xff' 117 | yield check_short_form_unpack_endian, 'U', 4294901244, b'\xfc\xfd\xfe\xff', pwny.Target.Endian.little 118 | yield check_short_form_unpack_endian, 'U', 4294901244, b'\xff\xfe\xfd\xfc', pwny.Target.Endian.big 119 | 120 | 121 | def check_short_form_pack(f, num, bytestr): 122 | assert getattr(pwny, f)(num) == bytestr 123 | 124 | 125 | def check_short_form_pack_endian(f, num, bytestr, endian): 126 | assert getattr(pwny, f)(num, endian=endian) == bytestr 127 | 128 | 129 | def check_short_form_unpack(f, num, bytestr): 130 | assert getattr(pwny, f)(bytestr) == num 131 | 132 | 133 | def check_short_form_unpack_endian(f, num, bytestr, endian): 134 | assert getattr(pwny, f)(bytestr, endian=endian) == num 135 | -------------------------------------------------------------------------------- /tests/test_php.py: -------------------------------------------------------------------------------- 1 | from pwnypack.php import php_serialize, PhpObject 2 | 3 | 4 | def test_php_serialize(): 5 | assert php_serialize([b'foo', u'bar', 42, 2.5, True, None, {'a': 'b'}]) == \ 6 | b'a:7:{i:0;s:3:"foo";i:1;s:3:"bar";i:2;i:42;i:3;d:2.5;i:4;b:1;i:5;N;i:6;a:1:{s:1:"a";s:1:"b";}}' 7 | 8 | 9 | def test_php_object_name(): 10 | o = PhpObject('Zend\\Object') 11 | assert php_serialize(o) == b'O:11:"Zend\\Object":0:{}' 12 | 13 | 14 | def test_php_object_property(): 15 | o = PhpObject('Test', {'a': 42}) 16 | assert php_serialize(o) == b'O:4:"Test":1:{s:1:"a";i:42;}' 17 | 18 | 19 | def test_php_object_public_property(): 20 | o = PhpObject('Test', {'public a': 42}) 21 | assert php_serialize(o) == b'O:4:"Test":1:{s:1:"a";i:42;}' 22 | 23 | 24 | def test_php_object_protected_property(): 25 | o = PhpObject('Test', {'protected a': 42}) 26 | assert php_serialize(o) == b'O:4:"Test":1:{s:4:"\0*\0a";i:42;}' 27 | 28 | 29 | def test_php_object_private_property(): 30 | o = PhpObject('Test', {'private a': 42}) 31 | assert php_serialize(o) == b'O:4:"Test":1:{s:7:"\0Test\0a";i:42;}' 32 | 33 | 34 | def test_php_object_get_item(): 35 | o = PhpObject('', {'a': 42}) 36 | assert o['a'] == 42 37 | 38 | 39 | def test_php_object_get_item_public(): 40 | o = PhpObject('Test', {'public a': 42}) 41 | assert o['a'] == 42 42 | assert o['public a'] == 42 43 | 44 | 45 | def test_php_object_get_item_protected(): 46 | o = PhpObject('Test', {'protected a': 42}) 47 | assert o['protected a'] == 42 48 | assert o['\0*\0a'] == 42 49 | 50 | 51 | def test_php_object_get_item_private(): 52 | o = PhpObject('Test', {'private a': 42}) 53 | assert o['private a'] == 42 54 | assert o['\0Test\0a'] == 42 55 | -------------------------------------------------------------------------------- /tests/test_pickle.py: -------------------------------------------------------------------------------- 1 | import pwny 2 | from six.moves import cPickle 3 | 4 | 5 | def func_to_invoke(a): 6 | return a 7 | 8 | 9 | def test_pickle_invoke(): 10 | data = pwny.pickle_invoke(func_to_invoke, 8) 11 | assert cPickle.loads(data) == 8 12 | 13 | 14 | def test_pickle_func(): 15 | def func_to_invoke_2(a): 16 | return a 17 | 18 | data = pwny.pickle_func(func_to_invoke_2, 8) 19 | 20 | del func_to_invoke_2 21 | 22 | assert cPickle.loads(data) == 8 23 | -------------------------------------------------------------------------------- /tests/test_shellcode.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | 4 | from pwny import * 5 | 6 | 7 | @pytest.mark.xfail(sys.version_info < (2, 7), 8 | reason="inspect.getcallargs new in python 2.7") 9 | def test_shellcode_translate(): 10 | @sc.LinuxX86Mutable.translate() 11 | def shellcode(): 12 | buf = alloc_buffer(64) 13 | reg_add(SYSCALL_RET_REG, 127) 14 | reg_add(SYSCALL_RET_REG, 8192) 15 | reg_sub(SYSCALL_RET_REG, 127) 16 | reg_sub(SYSCALL_RET_REG, 8192) 17 | sys_read(0, buf, buf.length) 18 | sys_write(1, buf, buf.length) 19 | sys_setresuid(sys_geteuid(), sys_geteuid(), sys_geteuid()) 20 | sys_execve(u'/bin/sh', [u'/bin/sh', u'-c', buf, None], None) 21 | shellcode() 22 | 23 | 24 | SHELLCODE_ENVS = [ 25 | (sc.LinuxX86Mutable, ()), 26 | (sc.LinuxX86MutableNullSafe, ()), 27 | 28 | (sc.LinuxX86Stack, ()), 29 | (sc.LinuxX86StackNullSafe, ()), 30 | 31 | (sc.LinuxX86_64Mutable, ()), 32 | (sc.LinuxX86_64MutableNullSafe, ()), 33 | 34 | (sc.LinuxX86_64Stack, ()), 35 | (sc.LinuxX86_64StackNullSafe, ()), 36 | 37 | (sc.LinuxARMMutable, ()), 38 | (sc.LinuxARMMutable, (Target.Endian.little,)), 39 | (sc.LinuxARMMutable, (Target.Endian.big,)), 40 | 41 | (sc.LinuxARMStack, ()), 42 | (sc.LinuxARMStack, (Target.Endian.little,)), 43 | (sc.LinuxARMStack, (Target.Endian.big,)), 44 | 45 | (sc.LinuxARMThumbMutable, ()), 46 | (sc.LinuxARMThumbMutable, (Target.Endian.little,)), 47 | (sc.LinuxARMThumbMutable, (Target.Endian.big,)), 48 | 49 | (sc.LinuxARMThumbStack, ()), 50 | (sc.LinuxARMThumbStack, (Target.Endian.little,)), 51 | (sc.LinuxARMThumbStack, (Target.Endian.big,)), 52 | 53 | (sc.LinuxARMThumbMixedMutable, ()), 54 | (sc.LinuxARMThumbMixedMutable, (Target.Endian.little,)), 55 | (sc.LinuxARMThumbMixedMutable, (Target.Endian.big,)), 56 | 57 | (sc.LinuxARMThumbMixedStack, ()), 58 | (sc.LinuxARMThumbMixedStack, (Target.Endian.little,)), 59 | (sc.LinuxARMThumbMixedStack, (Target.Endian.big,)), 60 | 61 | (sc.LinuxAArch64Mutable, ()), 62 | (sc.LinuxAArch64Mutable, (Target.Endian.little,)), 63 | (sc.LinuxAArch64Mutable, (Target.Endian.big,)), 64 | ] 65 | 66 | 67 | @pytest.mark.parametrize(('env_type', 'env_args'), SHELLCODE_ENVS) 68 | def test_shellcode_env_compile_simple(env_type, env_args): 69 | env = env_type(*env_args) 70 | env.compile([ 71 | env.sys_exit(0), 72 | ]) 73 | 74 | 75 | @pytest.mark.parametrize(('env_type', 'env_args'), SHELLCODE_ENVS) 76 | @pytest.mark.xfail(raises=RuntimeError, 77 | reason='proper binutils missing on CI system') 78 | def test_shellcode_env_assemble_simple(env_type, env_args): 79 | env = env_type(*env_args) 80 | env.assemble([ 81 | env.sys_exit(0), 82 | ]) 83 | 84 | 85 | @pytest.mark.parametrize(('env_type', 'env_args'), SHELLCODE_ENVS) 86 | def test_shellcode_env_compile_complex(env_type, env_args): 87 | env = env_type(*env_args) 88 | buf = env.alloc_buffer(64) 89 | env.compile([ 90 | sc.LoadRegister(env.SYSCALL_REG, 0xdeadcafe), 91 | env.sys_read(0, buf, buf.length - 1), 92 | env.sys_write(1, buf, buf.length - 1), 93 | env.sys_setresuid(env.sys_geteuid(), env.sys_geteuid(), env.sys_geteuid()), 94 | env.sys_execve(u'/bin/sh', [u'/bin/sh', u'-c', buf, None], None), 95 | ]) 96 | 97 | 98 | @pytest.mark.parametrize(('env_type', 'env_args'), SHELLCODE_ENVS) 99 | @pytest.mark.xfail(raises=RuntimeError, 100 | reason='proper binutils missing on CI system') 101 | def test_shellcode_env_assemble_complex(env_type, env_args): 102 | env = env_type(*env_args) 103 | buf = env.alloc_buffer(64) 104 | env.assemble([ 105 | sc.LoadRegister(env.SYSCALL_REG, 0xdeadcafe), 106 | env.sys_read(0, buf, buf.length - 1), 107 | env.sys_write(1, buf, buf.length - 1), 108 | env.sys_setresuid(env.sys_geteuid(), env.sys_geteuid(), env.sys_geteuid()), 109 | env.sys_execve(u'/bin/sh', [u'/bin/sh', u'-c', buf, None], None), 110 | ]) 111 | -------------------------------------------------------------------------------- /tests/test_target.py: -------------------------------------------------------------------------------- 1 | import mock 2 | import pytest 3 | 4 | import pwny 5 | 6 | 7 | def test_default_arch_x86(): 8 | with mock.patch('platform.machine') as platform_mock: 9 | platform_mock.return_value = 'i386' 10 | assert pwny.Target().arch is pwny.Target.Arch.x86 11 | 12 | 13 | def test_default_arch_x86_64(): 14 | with mock.patch('platform.machine') as platform_mock: 15 | platform_mock.return_value = 'x86_64' 16 | assert pwny.Target().arch is pwny.Target.Arch.x86 17 | 18 | 19 | def test_default_arch_unknown(): 20 | with mock.patch('platform.machine') as platform_mock: 21 | platform_mock.return_value = 'unknown' 22 | assert pwny.Target().arch is pwny.Target.Arch.unknown 23 | 24 | 25 | def test_default_arch_32bit(): 26 | with mock.patch('platform.architecture') as platform_mock: 27 | platform_mock.return_value = ('32bit',) 28 | assert pwny.Target().bits is pwny.Target.Bits.bits_32 29 | 30 | 31 | def test_default_arch_64bit(): 32 | with mock.patch('platform.architecture') as platform_mock: 33 | platform_mock.return_value = ('64bit',) 34 | assert pwny.Target().bits is pwny.Target.Bits.bits_64 35 | 36 | 37 | def test_set_arch(): 38 | with mock.patch('platform.architecture') as platform_mock: 39 | platform_mock.return_value = ('64bit',) 40 | target = pwny.Target(arch=pwny.Target.Arch.x86) 41 | assert target.arch is pwny.Target.Arch.x86 42 | 43 | 44 | def test_default_endian(): 45 | assert pwny.Target().endian is pwny.Target.Endian.little 46 | 47 | 48 | def test_set_endian(): 49 | target = pwny.Target(arch=pwny.Target.Arch.unknown, endian=pwny.Target.Endian.big) 50 | assert target.endian is pwny.Target.Endian.big 51 | 52 | 53 | def test_default_bits_x86(): 54 | target = pwny.Target(arch=pwny.Target.Arch.x86) 55 | assert target.bits == 32 56 | 57 | 58 | @pytest.mark.xfail(raises=NotImplementedError) 59 | def test_default_bits_unsupported(): 60 | target = pwny.Target(arch=pwny.Target.Arch.unknown) 61 | _ = target.bits 62 | 63 | 64 | def test_set__bits(): 65 | target = pwny.Target(arch=pwny.Target.Arch.x86, bits=64) 66 | assert target.bits == 64 67 | 68 | 69 | @pytest.mark.xfail(raises=ValueError) 70 | def test_set_invalid_bits(): 71 | pwny.Target(bits=33) 72 | 73 | 74 | def test_target_assume(): 75 | target = pwny.Target() 76 | target.assume(pwny.Target(arch=pwny.Target.Arch.arm, endian=pwny.Target.Endian.little, bits=64, mode=2)) 77 | assert target.arch is pwny.Target.Arch.arm and \ 78 | target.endian == pwny.Target.Endian.little and \ 79 | target.bits == 64 and \ 80 | target.mode == 2 81 | -------------------------------------------------------------------------------- /tests/test_util.py: -------------------------------------------------------------------------------- 1 | import pwny 2 | 3 | 4 | def test_cycle(): 5 | assert pwny.cycle(64) == 'AAAABAAACAAADAAAEAAAFAAAGAAAHAAAIAAAJAAAKAAALAAAMAAANAAAOAAAPAAA' 6 | 7 | 8 | def test_cycle_width(): 9 | assert pwny.cycle(64, width=2) == 'AABACADAEAFAGAHAIAJAKALAMANAOAPAQARASATAUAVAWAXAYAZBBCBDBEBFBGBH' 10 | 11 | 12 | def test_cycle_find(): 13 | assert pwny.cycle_find('PAAA') == 60 14 | 15 | 16 | def test_cycle_find_start(): 17 | assert pwny.cycle_find('AAAA') == 0 18 | 19 | 20 | def test_cycle_find_not_found(): 21 | assert pwny.cycle_find('\x00', width=1) == -1 22 | 23 | 24 | def test_reghex_pattern_char(): 25 | assert pwny.reghex('28').match(b'(') is not None 26 | -------------------------------------------------------------------------------- /tools/build_py_internals_docs.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | from __future__ import print_function 4 | 5 | import sys 6 | import os 7 | 8 | import six 9 | 10 | if __name__ == '__main__': 11 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) 12 | from pwnypack.py_internals import PY_INTERNALS 13 | versions = sorted(version for version in six.iterkeys(PY_INTERNALS) if version is not None) 14 | 15 | print(''':mod:`~pwnypack.py_internals` -- Python internals 16 | ================================================= 17 | 18 | .. automodule:: pwnypack.py_internals 19 | 20 | .. autofunction:: get_py_internals 21 | ''') 22 | 23 | for version in versions: 24 | print(''' .. autodata:: PY_{0} 25 | :annotation: = {{...}} 26 | '''.format(version)) 27 | 28 | print(''' .. autodata:: PY_INTERNALS 29 | :annotation: = {{{}}}'''.format(', '.join('{0}: PY_{0}'.format(version) for version in versions))) 30 | -------------------------------------------------------------------------------- /upload.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | if [ -z "$1" ]; then 4 | echo "$0 " 5 | exit 1 6 | fi 7 | 8 | python setup.py sdist bdist_wheel upload -r "$1" 9 | --------------------------------------------------------------------------------