Compare commits

..
1 Commits
Author SHA1 Message Date
Mathias Svensson fef4483e22 Release 3.3.3 2017-01-10 23:35:11 +01:00
3562 changed files with 24881 additions and 46779 deletions

No files matched your search

+1 -6
View File
@@ -13,7 +13,6 @@ addons:
- mksh
- zsh
- pandoc
- gdb
cache:
- pip
- directories:
@@ -23,15 +22,11 @@ cache:
python:
- "2.7"
before_install:
- echo $-
- source travis/install.sh
- source travis/ssh_setup.sh
- echo $-
install:
- pip install --upgrade pip
- pip install --upgrade appdirs # https://github.com/ActiveState/appdirs/issues/89#issuecomment-282326570
- pip install --upgrade --editable .
- pip install --upgrade --requirement docs/requirements.txt
- pip install --requirement docs/requirements.txt
before_script:
- PWNLIB_NOTERM=1 python -c 'from pwn import *; print pwnlib.term.term_mode'
- PWNLIB_NOTERM=1 python -c 'from pwn import *; print pwnlib.term.term_mode'
+8 -152
View File
@@ -9,15 +9,9 @@ The table below shows which release corresponds to each branch, and what date th
| Version | Branch | Release Date |
| ---------------- | -------- | ---------------------- |
| [3.7.0](#350) | `dev` | Jun 17, 2017 (planned)
| [3.6.0](#350) | `beta` | May 6, 2017 (planned)
| [3.5.1](#351) | `stable` | Apr 15, 2017
| [3.5.0](#350) | | Mar 26, 2017
| [3.4.1](#341) | | Feb 17, 2017
| [3.4.0](#340) | | Feb 13, 2017
| [3.3.4](#334) | | Jan 12, 2016
| [3.3.3](#333) | | Jan 10, 2016
| [3.3.2](#332) | | Jan 10, 2016
| [3.5.0](#350) | `dev` | Mar 18, 2017 (planned)
| [3.4.0](#340) | `beta` | Feb 4, 2017 (planned)
| [3.3.2](#332) | `stable` | Jan 10, 2016
| [3.3.1](#331) | | Jan 10, 2016
| [3.3.0](#330) | | Dec 24, 2016
| [3.2.1](#321) | | Dec 24, 2016
@@ -31,151 +25,13 @@ The table below shows which release corresponds to each branch, and what date th
| [3.0.0](#300) | | Aug 20, 2016
| [2.2.0](#220) | | Jan 5, 2015
## 3.7.0
To be on Jun 17, 2017.
## 3.6.0
To be released on May 6, 2017.
- [#895][895] Added a Dockerfile to simplify testing setup and allow testing on OSX
- [#897][897] Fixed some incorrect AArch64 syscals
- [#893][893] Added the `pwnlib.config` module
+ Configuration options can now be set in `~/.pwn.conf`
+ This replaces the old, **undocumented** mechanism for changing logging colors. Only @br0ns and @ebeip90 were likely using this.
+ More information is available in the documentation [here](http://docs.pwntools.com/en/dev/config.html).
- [#899][899] Pwntools now uses Unicorn Engine to emulate PLT instructions to ensure correct mapping of PIE / RELRO binaries.
- [#904][904] Enhancements to the accuracy of the `pwn checksec` command.
- [#905][905] Added a `pwn debug` command-line utility which automates the process of `gdb.attach(process(...))` to spawn GDB
+ More information is available in the documentation [here](http://docs.pwntools.com/en/dev/commandline.html#pwn-debug)
- [#919][919] Added a `pwn template` command-line utility to simplify the process of bootstrapping a new exploit.
+ More information is available in the documentation [here](http://docs.pwntools.com/en/dev/commandline.html#pwn-template).
[895]: https://github.com/Gallopsled/pwntools/pull/895
[897]: https://github.com/Gallopsled/pwntools/pull/897
[893]: https://github.com/Gallopsled/pwntools/pull/893
[899]: https://github.com/Gallopsled/pwntools/pull/899
[904]: https://github.com/Gallopsled/pwntools/pull/904
[905]: https://github.com/Gallopsled/pwntools/pull/905
[919]: https://github.com/Gallopsled/pwntools/pull/919
## 3.5.1
- [#945][945] Speed up ssh via caching checksec results (fixes [#944][944])
- [#950][950] Fixes a bug where setting `context.arch` does not have an effect on `adb.compile()` output architecture
[944]: https://github.com/Gallopsled/pwntools/issues/944
[945]: https://github.com/Gallopsled/pwntools/pull/945
[950]: https://github.com/Gallopsled/pwntools/pull/950
## 3.5.0
- [b584ca3][b584ca3] Fixed an issue running `setup.py` on ARM
- [#822][822] Enabled relative leaks with `MemLeak`
+ This should be useful for e.g. heap-relative leaks
- [#832][832] Changed all internal imports to use absolute imports (no functional changes)
- [a12d0b6][a12d0b6] Move `STDOUT`, `PIPE`, `PTY` constants to globals
+ `process(..., stdin=process.PTY)` --> `process(..., stdin=PTY)`
- [#828][828] Use `PR_SET_PTRACER` for all `process()` and `ssh.process()` instances
+ This simplifies debugging on systems with YAMA ptrace enabled
- Various documentation enhancements
+ In particular, the [gdb][gdb], [elf][elf], and [ssh][ssh] docs are much better
- [#833][833] Performance enhancements for `adb` module
- [d0267f3][d0267f3] `packing.fit()` now treats large offsets as cyclic patterns (e.g. `0x61616161` behaves the same as `"aaaa"`)
- [#835][835] Added `ssh.checksec`
+ Reports the kernel version and other relevant information on connection
- [#857][857] Slightly shortened `execve` shellcode
- [300f8e0][300f8e0] Slightly speed up processing of large ELF files
- [#861][861] Adds support for extracting `IKCONFIG` configs from Linux kernel images, and extends `checksec` to report on any insecure configurations discovered
- [#871][871] Moves all of the basic syscall templates to `shellcraft/common` and exposes them via symlinks. Closed [#685][685]
+ Should not have any visible effects from any documented APIs
+ `shellcraft.arch.os.syscall_function()` still works the same
+ We now have the ability to differentiate between the `connect` syscall, and a TCP `connect` helper
- [#887][887] `sh_string` now returns a quoted empty string `''` rather than just an empty string
- [#839][839] Exposes a huge amount of functionality via corefiles which was not previously availble. See the [docs][corefile_docs] for examples.
+ `process().corefile` will automatically instantiate a Corefile for the process
+ QEMU-emulated processes are supported
+ Native processes are supported, including extraction of coredumps from `apport` crash logs
+ Native processes can be dumped *while running*, in a manner similar to `GDB`'s `gcore` script
- [#875][857] Added [documentation][aarch64] (and tests) for AArch64 shellcode
- [#882][882] The `ROP` class now respects `context.bytes` instead of using the hard-coded value of `4` (fixed [#879][879])
- [#869][869] Added several fields to the `process` class (`uid`, `gid`, `suid`, `sgid`) which are recorded at execution time, based on the file permissions
- [#868][868] Changed the way that `ssh.process()` works internally, and it now returns a more specialized class, `ssh_process`.
+ Added `ssh_process.corefile` for fetching remote corefiles
+ Added `ssh_process.ELF` for getting an ELF of the remote executable
+ The `uid`, `gid`, and `suid`, and `sgid` which are recorded at execution time, based on the file permissions
- [#865][865] Fixes `ELF.read` to support contiguous memory reads across non-contiguous file-backed segments
- [#862][862] Adds a `symlink=` argument to `ssh.set_working_directory`, which will automatically symlink all of the files in the "old" working directory into the "new" working directory
[ssh]: http://docs.pwntools.com/en/dev/tubes/ssh.html
[gdb]: http://docs.pwntools.com/en/dev/gdb.html
[elf]: http://docs.pwntools.com/en/dev/elf.html
[corefile_docs]: http://docs.pwntools.com/en/dev/elf/corefile.html
[aarch64]: http://docs.pwntools.com/en/dev/shellcraft/aarch64.html
[685]: https://github.com/Gallopsled/pwntools/pull/685
[822]: https://github.com/Gallopsled/pwntools/pull/822
[828]: https://github.com/Gallopsled/pwntools/pull/828
[832]: https://github.com/Gallopsled/pwntools/pull/832
[833]: https://github.com/Gallopsled/pwntools/pull/833
[835]: https://github.com/Gallopsled/pwntools/pull/835
[839]: https://github.com/Gallopsled/pwntools/pull/839
[857]: https://github.com/Gallopsled/pwntools/pull/857
[861]: https://github.com/Gallopsled/pwntools/pull/861
[862]: https://github.com/Gallopsled/pwntools/pull/862
[865]: https://github.com/Gallopsled/pwntools/pull/865
[868]: https://github.com/Gallopsled/pwntools/pull/868
[869]: https://github.com/Gallopsled/pwntools/pull/869
[871]: https://github.com/Gallopsled/pwntools/pull/871
[875]: https://github.com/Gallopsled/pwntools/pull/857
[879]: https://github.com/Gallopsled/pwntools/issues/879
[882]: https://github.com/Gallopsled/pwntools/pull/882
[887]: https://github.com/Gallopsled/pwntools/pull/887
[b584ca3]: https://github.com/Gallopsled/pwntools/commit/b584ca3
[a12d0b6]: https://github.com/Gallopsled/pwntools/commit/a12d0b6
[d0267f3]: https://github.com/Gallopsled/pwntools/commit/d0267f3
[300f8e0]: https://github.com/Gallopsled/pwntools/commit/300f8e0
## 3.4.1
- [#894][894] Fix a bug when using `gdb.debug()` over ssh.
- [e021f57][e021f57] Fix a bug ([#891][891]) in `rop` when needing to insert padding to fix alignment
[e021f57]: https://github.com/Gallopsled/pwntools/commit/e021f57
[894]: https://github.com/Gallopsled/pwntools/pull/894
[891]: https://github.com/Gallopsled/pwntools/issues/891
To be released on Mar 18, 2017.
## 3.4.0
- [#800][800] Add `shell=` option to `ssh.process()`
- [#806][806] Add `context.buffer_size` for fine-tuning `tube` performance
+ Also adds `buffer_fill_size=` argument for all tubes
- [b83a6c7][b83a6c7] Fix undocumented `process.leak` function
- [546061e][546061e] Modify `coredump_filter` of all spawned processes, so that core dumps are more complete
- [#809][809] Add several functions to `adb` (`unlink`, `mkdir`, `makedirs`, `isdir`, `exists`)
- [#817][817] Make disconnection detection more robust
[800]: https://github.com/Gallopsled/pwntools/pull/800
[806]: https://github.com/Gallopsled/pwntools/pull/806
[809]: https://github.com/Gallopsled/pwntools/pull/809
[817]: https://github.com/Gallopsled/pwntools/pull/817
[5d9792f]: https://github.com/Gallopsled/pwntools/commit/5d9792f
[b83a6c7]: https://github.com/Gallopsled/pwntools/commit/b83a6c7
[546061e]: https://github.com/Gallopsled/pwntools/commit/546061e
## 3.3.4
- [#850][850] and [#846][846] fix issues with `hexdump` and the `phd` command-line utility, when using pipes (e.g. `echo foo | phd`)
- [#852][852] Fixes register ordering in `regsort`
- [#853][853] Fixes the registers restored in `shellcraft.amd64.popad`
[846]: https://github.com/gallopsled/pwntools/pull/846
[850]: https://github.com/gallopsled/pwntools/pull/850
[852]: https://github.com/gallopsled/pwntools/pull/852
[853]: https://github.com/gallopsled/pwntools/pull/853
To be released on Feb 4, 2017.
## 3.3.3
@@ -285,7 +141,7 @@ Multiple bug fixes.
- [1e414af][1e414af] Added `connect()` alias for `remote()`
+ For example, `io=connect('google.com', 80)`
+ This also works with `tcp(...)` and `udp(...)` aliases
- [869ec42][869ec42] Added `ssh.read()` and `ssh.write()` aliases
- [869ec42][869ec42] Added `ssh.read()` and `ssh.write()` aiases
- [2af55c9][2af55c9] `AdbDevice` objects exposed via e.g. `adb.devices()` now offer scoped access to all `adb` module properties
+ It is now possible to e.g. `map(lambda d: d.process(['id']).recvall(), adb.devices())`
@@ -372,7 +228,7 @@ Android support via a new `adb` module, `context.device`, `context.adb_host`, an
- Added `context.log_file` setting for sending logs to a file. This can be set with the `LOG_FILE` magic command-line option.
- Added `context.noptrace` setting for disabling actions which require `ptrace` support. This is useful for turning all `gdb.debug` and `gdb.attach` options into no-ops, and can be set via the `NOPTRACE` magic command-line option.
- Added `context.proxy` which hooks all connections and sends them to a SOCKS4/SOCKS5. This can be set via the `PROXY` magic command-line option.
- Added `context.randomize` to control randomization of settings like XOR keys and register ordering (default off).
- Added `context.randomize` to control randommization of settings like XOR keys and register ordering (default off).
- Added `context.terminal` for setting how to launch commands in a new terminal.
#### DynELF and MemLeak Module
@@ -385,7 +241,7 @@ Android support via a new `adb` module, `context.device`, `context.adb_host`, an
#### Encoders Module
- Added a `pwnlib.encoders` module for assembled-shellcode encoders/decoders
- Includes position-independent basic XOR encoders
- Includes position-indepentent basic XOR encoders
- Includes position-independent delta encoders
- Includes non-position-independent alphanumeric encoders for Intel
- Includes position-independent alphanumeric encoders for ARM/Thumb
+1 -1
View File
@@ -2,6 +2,6 @@ graft examples
graft extra
include *.md *.txt *.sh *.yml MANIFEST.in
recursive-include docs *.rst *.png Makefile *.py *.txt
recursive-include pwnlib *.py *.asm *.rst *.md *.txt *.sh __doc__
recursive-include pwnlib *.py *.asm *.rst *.md *.txt *.sh
recursive-include pwn *.py *.asm *.rst *.md *.txt *.sh
recursive-exclude *.pyc
+2 -3
View File
@@ -2,7 +2,7 @@
![pwntools logo](https://github.com/Gallopsled/pwntools/blob/stable/docs/source/logo.png?raw=true)
[![Docs](https://readthedocs.org/projects/pwntools/badge/?version=stable)](https://docs.pwntools.com/)
[![PyPI](https://img.shields.io/badge/pypi-v3.5.1-green.svg?style=flat)](https://pypi.python.org/pypi/pwntools/)
[![PyPI](https://img.shields.io/badge/pypi-v3.3.3-green.svg?style=flat)](https://pypi.python.org/pypi/pwntools/)
[![Travis](https://travis-ci.org/Gallopsled/pwntools.svg)](https://travis-ci.org/Gallopsled/pwntools)
[![Coveralls](https://img.shields.io/coveralls/Gallopsled/pwntools/dev.svg)](https://coveralls.io/github/Gallopsled/pwntools?branch=dev)
[![Twitter](https://img.shields.io/badge/twitter-pwntools-4099FF.svg?style=flat)](https://twitter.com/pwntools)
@@ -38,8 +38,7 @@ Most of the functionality of pwntools is self-contained and Python-only. You sh
```sh
apt-get update
apt-get install python2.7 python-pip python-dev git libssl-dev libffi-dev build-essential
pip install --upgrade pip
apt-get install python2.7 python-pip python-dev git libssl-dev
pip install --upgrade pwntools
```
-10
View File
@@ -13,13 +13,3 @@
.. automodule:: pwnlib.asm
:members:
Internal Functions
=========================================
These are only included so that their tests are run.
You should never need these.
.. autofunction:: pwnlib.asm.dpkg_search_for_binutils
.. autofunction:: pwnlib.asm.print_binutils_instructions
+3 -15
View File
@@ -44,10 +44,6 @@ extensions = [
'sphinxcontrib.napoleon'
]
# Napoleon settings
napoleon_use_ivar = True
napoleon_use_rtype = False
doctest_global_setup = '''
import sys, os
os.environ['PWNLIB_NOTERM'] = '1'
@@ -56,7 +52,6 @@ import pwnlib
pwnlib.context.context.reset_local()
pwnlib.context.ContextType.defaults['log_level'] = 'ERROR'
pwnlib.context.ContextType.defaults['randomize'] = False
pwnlib.util.fiddling.default_style = {}
pwnlib.term.text.when = 'never'
pwnlib.log.install_default_handler()
pwnlib.log.rootlogger.setLevel(1)
@@ -72,13 +67,12 @@ class stdout(object):
pwnlib.context.ContextType.defaults['log_console'] = stdout()
'''
autoclass_content = 'both'
autodoc_member_order = 'groupwise' # 'alphabetical'
autodoc_member_order = 'alphabetical'
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
doctest_test_doctest_blocks = 'true'
doctest_test_doctest_blocks = True
# The suffix of source filenames.
source_suffix = '.rst'
@@ -237,7 +231,7 @@ latex_documents = [
]
intersphinx_mapping = {'python': ('https://docs.python.org/2.7', None),
'paramiko': ('https://paramiko-docs.readthedocs.org/en/2.1/', None)}
'paramiko': ('https://paramiko-docs.readthedocs.org/en/1.15/', None)}
# The name of an image file (relative to this directory) to place at the top of
# the title page.
@@ -353,9 +347,3 @@ if build_dash:
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# otherwise, readthedocs.org uses their theme by default, so no need to specify it
# -- Customization to Sphinx autodoc generation --------------------------------------------
import sphinx.ext.autodoc
sphinx.ext.autodoc
-22
View File
@@ -3,32 +3,10 @@
from pwn import *
import logging
log = pwnlib.log.getLogger('pwnlib.context')
context.clear()
:mod:`pwnlib.context` --- Setting runtime variables
=====================================================
Many settings in ``pwntools`` are controlled via the global variable :data:`.context, such as the selected target operating system, architecture, and bit-width.
In general, exploits will start with something like:
.. code-block:: python
from pwn import *
context.arch = 'amd64'
Which sets up everything in the exploit for exploiting a 64-bit Intel binary.
The recommended method is to use ``context.binary`` to automagically set all of the appropriate values.
.. code-block:: python
from pwn import *
context.binary = './challenge-binary'
Module Members
=====================================================
.. autodata:: pwnlib.context.context
.. autoclass:: pwnlib.context.ContextType
+3 -11
View File
@@ -3,16 +3,8 @@
from pwnlib.elf import *
from pwnlib.util.misc import which
:mod:`pwnlib.elf` --- ELF Executables and Libraries
===================================================
:mod:`pwnlib.elf` --- Working with ELF binaries
===============================================
.. automodule:: pwnlib.elf
ELF Modules
-------------------
.. toctree::
elf/elf
elf/config
elf/corefile
:members:
-10
View File
@@ -1,10 +0,0 @@
.. testsetup:: *
from pwn import *
from pwnlib.elf.config import parse_kconfig
:mod:`pwnlib.elf.config` --- Kernel Config Parsing
===========================================================
.. automodule:: pwnlib.elf.config
:members:
-15
View File
@@ -1,15 +0,0 @@
.. testsetup:: *
from pwn import *
:mod:`pwnlib.elf.corefile` --- Core Files
===========================================================
.. automodule:: pwnlib.elf.corefile
.. autoclass:: pwnlib.elf.corefile.Corefile
:members:
:show-inheritance:
.. autoclass:: pwnlib.elf.corefile.Mapping
:members:
-27
View File
@@ -1,27 +0,0 @@
.. testsetup:: *
from pwn import *
:mod:`pwnlib.elf.elf` --- ELF Files
===========================================================
.. automodule:: pwnlib.elf.elf
.. autoclass:: pwnlib.elf.elf.ELF
:members:
:show-inheritance:
:inherited-members:
:exclude-members: address_offsets,
get_data,
get_dwarf_info,
get_section,
get_segment,
has_dwarf_info,
iter_sections,
iter_segments
.. autoclass:: pwnlib.elf.elf.Function
:members:
.. autoclass:: pwnlib.elf.elf.dotdict
:members:
-4
View File
@@ -1,7 +1,3 @@
.. testsetup:: *
from pwn import *
:mod:`pwnlib.gdb` --- Working with GDB
======================================
+6 -6
View File
@@ -13,11 +13,11 @@ Which imports a bazillion things into the global namespace to make your life eas
This is a quick list of most of the objects and routines imported, in rough order of importance and frequency of use.
- :obj:`.context`
- ``context``
- :data:`pwnlib.context.context`
- Responsible for most of the pwntools convenience settings
- Set `context.log_level = 'debug'` when troubleshooting your exploit
- Scope-aware, so you can disable logging for a subsection of code via :meth:`.ContextType.local`
- Scope-aware, so you can disable logging for a subsection of code via ``pwnlib.context.ContextType.local``
- ``remote``, ``listen``, ``ssh``, ``process``
- :mod:`pwnlib.tubes`
- Super convenient wrappers around all of the common functionality for CTF challenges
@@ -57,11 +57,11 @@ This is a quick list of most of the objects and routines imported, in rough orde
- ``gdb.debug`` and ``gdb.attach``
- :mod:`pwnlib.gdb`
- Launch a binary under GDB and pop up a new terminal to interact with it. Automates setting breakpoints and makes iteration on exploits MUCH faster.
- Alternately, attach to a running process given a PID, :mod:`pwnlib.tubes` object, or even just a socket that's connected to it
- Alternately, attach to a running process given a PID, ``pwnlib.tubes`` object, or even just a socket that's connected to it
- ``args``
- Dictionary containing all-caps command-line arguments for quick access
- Dictionary contining all-caps command-line arguments for quick access
- Run via ``python foo.py REMOTE=1`` and ``args['REMOTE'] == '1'``.
- Can also control logging verbosity and terminal fanciness
- Can also control logging verbosity and terminal fancyness
- `NOTERM`
- `SILENT`
- `DEBUG`
@@ -78,7 +78,7 @@ This is a quick list of most of the objects and routines imported, in rough orde
- It's the new ``getch``
- ``safeeval``
- :mod:`pwnlib.util.safeeval`
- Functions for safely evaluating python code without nasty side-effects.
- Functions for safely evalutaing python code without nasty side-effects.
These are all pretty self explanatory, but are useful to have in the global namespace.
-1
View File
@@ -58,7 +58,6 @@ Each of the ``pwntools`` modules is documented here.
gdb
log
memleak
protocols
replacements
rop
rop/*
+1 -3
View File
@@ -24,9 +24,7 @@ pwntools is available as a ``pip`` package.
.. code-block:: bash
$ apt-get update
$ apt-get install python2.7 python-pip python-dev git libssl-dev libffi-dev build-essential
$ pip install --upgrade pip
$ apt-get install python2.7 python2.7-dev python-pip
$ pip install --upgrade pwntools
Development
+2 -2
View File
@@ -212,8 +212,8 @@ Stop hard-coding things! Look them up at runtime with :mod:`pwnlib.elf`.
You can even patch and save the files.
>>> e = ELF('/bin/cat')
>>> e.read(e.address, 4)
'\x7fELF'
>>> e.read(e.address+1, 3)
'ELF'
>>> e.asm(e.address, 'ret')
>>> e.save('/tmp/quiet-cat')
>>> disasm(file('/tmp/quiet-cat','rb').read(1))
-15
View File
@@ -1,15 +0,0 @@
:mod:`pwnlib.protocols` --- Wire Protocols
=============================================
.. automodule:: pwnlib.protocols
Supported Protocols
-------------------
.. toctree::
:maxdepth: 3
:glob:
protocols/*
-10
View File
@@ -1,10 +0,0 @@
.. testsetup:: *
from pwn import *
from pwnlib.protocols.adb import AdbClient
:mod:`pwnlib.protocols.adb` --- ADB Protocol Implementation
===========================================================
.. automodule:: pwnlib.protocols.adb
:members:
-19
View File
@@ -1,19 +0,0 @@
.. testsetup:: *
from pwn import *
context.clear(arch='aarch64')
:mod:`pwnlib.shellcraft.aarch64` --- Shellcode for AArch64
===========================================================
:mod:`pwnlib.shellcraft.aarch64`
--------------------------------
.. automodule:: pwnlib.shellcraft.aarch64
:members:
:mod:`pwnlib.shellcraft.aarch64.linux`
--------------------------------------
.. automodule:: pwnlib.shellcraft.aarch64.linux
:members:
+1 -1
View File
@@ -27,4 +27,4 @@ Types of Tubes
.. autoclass:: pwnlib.tubes.tube.tube()
:members:
:exclude-members: recv_raw, send_raw, settimeout_raw,
can_recv_raw, shutdown_raw, connected_raw,
can_recv_raw
+1 -2
View File
@@ -1,7 +1,6 @@
.. testsetup:: *
from pwn import *
from pwnlib.util.fiddling import *
from pwnlib.util.fiddling import *
:mod:`pwnlib.util.fiddling` --- Utilities bit fiddling
-31
View File
@@ -1,31 +0,0 @@
from pwn import *
context.arch='aarch64'
frame = SigreturnFrame()
registers = ['x%i' % i for i in range(0, 31)]
registers += ['sp', 'pc']
for index, register in enumerate(registers):
setattr(frame, register, index)
assembly = '\n'.join([
shellcraft.read(constants.STDIN_FILENO, 'sp', 1024),
shellcraft.syscall(constants.SYS_rt_sigreturn)
])
binary = ELF.from_assembly(assembly)
io = binary.process()
io.flat(frame)
io.wait()
assert io.poll() == -11
corefile = io.corefile
for index, register in enumerate(registers):
value = getattr(corefile, register)
if index != value:
log.error("%s != %i (%i)" % (register, index, value))
else:
log.success("%s == %i" % (register, value))
-32
View File
@@ -1,32 +0,0 @@
from pwn import *
context.arch='amd64'
frame = SigreturnFrame()
registers = ['rax', 'rbx', 'rcx', 'rdx',
'rdi', 'rsi', 'rbp', 'rsp',
'rip',
'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15']
for index, register in enumerate(registers):
setattr(frame, register, index)
assembly = '\n'.join([
shellcraft.read(constants.STDIN_FILENO, 'rsp', 1024),
shellcraft.sigreturn()
])
binary = ELF.from_assembly(assembly)
io = binary.process()
io.flat(frame)
io.wait()
assert io.poll() == -11
corefile = io.corefile
for index, register in enumerate(registers):
value = getattr(corefile, register)
if index != value:
log.error("%s != %i (%i)" % (register, index, value))
else:
log.success("%s == %i" % (register, value))
-32
View File
@@ -1,32 +0,0 @@
from pwn import *
context.arch='arm'
frame = SigreturnFrame()
registers = ['r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7',
'r8', 'r9', 'r10', 'fp', 'ip', 'sp', 'pc', 'lr']
for index, register in enumerate(registers):
setattr(frame, register, index)
assembly = '\n'.join([
shellcraft.read(constants.STDIN_FILENO, 'sp', 1024),
shellcraft.sigreturn()
])
binary = ELF.from_assembly(assembly)
io = binary.process()
io.flat(frame)
io.wait()
assert io.poll() == -11
corefile = io.corefile
for index, register in enumerate(registers):
value = getattr(corefile, register)
if index != value:
log.error("%s != %i (%i)" % (register, index, value))
else:
log.success("%s == %i" % (register, value))
-31
View File
@@ -1,31 +0,0 @@
from pwn import *
context.kernel='amd64'
frame = SigreturnFrame()
registers = ['eax', 'ebx', 'ecx', 'edx',
'edi', 'esi', 'ebp', 'esp',
'eip']
for index, register in enumerate(registers):
setattr(frame, register, index)
assembly = '\n'.join([
shellcraft.read(constants.STDIN_FILENO, 'esp', 1024),
shellcraft.sigreturn()
])
binary = ELF.from_assembly(assembly)
io = binary.process()
io.flat(frame)
io.wait()
assert io.poll() == -11
corefile = io.corefile
for index, register in enumerate(registers):
value = getattr(corefile, register)
if index != value:
log.error("%s != %i (%i)" % (register, index, value))
else:
log.success("%s == %i" % (register, value))
+58
View File
@@ -0,0 +1,58 @@
############################################################
# Dockerfile to build Pwntools container
# Based on Ubuntu
############################################################
FROM ubuntu:trusty
MAINTAINER Maintainer Gallopsled et al.
RUN apt-get update && apt-get install -y software-properties-common
RUN apt-add-repository --yes ppa:pwntools/binutils
RUN apt-get update
RUN apt-get install -y \
binutils \
binutils-aarch64-linux-gnu \
binutils-arm-linux-gnueabihf \
curl \
gdb \
gdbserver \
gdb-multiarch \
git \
libc6-i386 \
libc6-arm64-cross \
libc6-armhf-cross \
libc6-dev-i386 \
libffi-dev \
libglib2.0-dev \
libncurses5-dev \
libncursesw5-dev \
ltrace \
python-dev \
python-pip \
python3-pip \
python2.7 \
qemu \
qemu-user-static \
strace \
tmux \
wget
RUN pip install --upgrade ipython
RUN useradd -m zerocool
RUN usermod -U zerocool
RUN mkdir -p /etc/qemu-binfmt
RUN ln -s /usr/aarch64-linux-gnu /etc/qemu-binfmt/aarch64
RUN ln -s /usr/arm-linux-gnueabihf /etc/qemu-binfmt/arm
RUN pip install --upgrade pip pycparser psutil python-ptrace
RUN pip3 install --upgrade pip pycparser psutil python-ptrace
RUN pip install --upgrade git+https://github.com/Gallopsled/pwntools.git
RUN locale-gen en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
RUN chown -R zerocool.zerocool /home/zerocool
RUN chsh -s /bin/bash zerocool
-13
View File
@@ -1,13 +0,0 @@
subdirs=xenial stable $(wildcard */)
makefiles=$(wildcard */Makefile)
tags=stable beta dev
all: $(makefiles) $(subdirs)
$(subdirs):
docker build -t pwntools/pwntools:$(shell basename $(@)) "$@"
$(makefiles):
make -C $(shell dirname $@)
.PHONY: all $(subdirs) $(makefiles)
-6
View File
@@ -1,6 +0,0 @@
FROM pwntools/pwntools:stable
USER root
RUN pip install --upgrade git+https://github.com/Gallopsled/pwntools@beta
RUN PWNLIB_NOTERM=1 pwn update
USER pwntools
-6
View File
@@ -1,6 +0,0 @@
FROM pwntools/pwntools:stable
USER root
RUN pip install --upgrade git+https://github.com/Gallopsled/pwntools@dev
RUN PWNLIB_NOTERM=1 pwn update
USER pwntools
-26
View File
@@ -1,26 +0,0 @@
############################################################
# Dockerfile to build Pwntools container
# Based on Ubuntu
############################################################
FROM kalilinux/kali-linux-docker
MAINTAINER Maintainer Gallopsled et al.
RUN apt-get update
RUN apt-get install -y build-essential
RUN apt-get install -y git
RUN apt-get install -y libssl-dev
RUN apt-get install -y libffi-dev
RUN apt-get install -y python2.7
RUN apt-get install -y python-pip
RUN apt-get install -y python-dev
RUN pip install --upgrade pip
RUN python -m pip install --upgrade pwntools
RUN PWNLIB_NOTERM=1 pwn update
RUN apt-get install -y sudo
RUN useradd -m pwntools
RUN passwd --delete --unlock pwntools
RUN echo "pwntools ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/pwntools
USER pwntools
WORKDIR /home/pwntools
-8
View File
@@ -1,8 +0,0 @@
all: Dockerfile
Dockerfile: ../stable/Dockerfile FORCE
sed "s|ubuntu:xenial|kalilinux/kali-linux-docker|" < "$<" > "$@"
FORCE:
.PHONY: all
-26
View File
@@ -1,26 +0,0 @@
############################################################
# Dockerfile to build Pwntools container
# Based on Ubuntu
############################################################
FROM ubuntu:precise
MAINTAINER Maintainer Gallopsled et al.
RUN apt-get update
RUN apt-get install -y build-essential
RUN apt-get install -y git
RUN apt-get install -y libssl-dev
RUN apt-get install -y libffi-dev
RUN apt-get install -y python2.7
RUN apt-get install -y python-pip
RUN apt-get install -y python-dev
RUN pip install --upgrade pip
RUN python -m pip install --upgrade pwntools
RUN PWNLIB_NOTERM=1 pwn update
RUN apt-get install -y sudo
RUN useradd -m pwntools
RUN passwd --delete --unlock pwntools
RUN echo "pwntools ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/pwntools
USER pwntools
WORKDIR /home/pwntools
-8
View File
@@ -1,8 +0,0 @@
all: Dockerfile
Dockerfile: ../xenial/Dockerfile FORCE
sed "s|ubuntu:xenial|ubuntu:precise|" < "$<" > "$@"
FORCE:
.PHONY: all
-26
View File
@@ -1,26 +0,0 @@
############################################################
# Dockerfile to build Pwntools container
# Based on Ubuntu
############################################################
FROM ubuntu:xenial
MAINTAINER Maintainer Gallopsled et al.
RUN apt-get update
RUN apt-get install -y build-essential
RUN apt-get install -y git
RUN apt-get install -y libssl-dev
RUN apt-get install -y libffi-dev
RUN apt-get install -y python2.7
RUN apt-get install -y python-pip
RUN apt-get install -y python-dev
RUN pip install --upgrade pip
RUN python -m pip install --upgrade pwntools
RUN PWNLIB_NOTERM=1 pwn update
RUN apt-get install -y sudo
RUN useradd -m pwntools
RUN passwd --delete --unlock pwntools
RUN echo "pwntools ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/pwntools
USER pwntools
WORKDIR /home/pwntools
-8
View File
@@ -1,8 +0,0 @@
all: Dockerfile
Dockerfile: ../xenial/Dockerfile FORCE
cp "$<" "$@"
FORCE:
.PHONY: all
-26
View File
@@ -1,26 +0,0 @@
############################################################
# Dockerfile to build Pwntools container
# Based on Ubuntu
############################################################
FROM ubuntu:trusty
MAINTAINER Maintainer Gallopsled et al.
RUN apt-get update
RUN apt-get install -y build-essential
RUN apt-get install -y git
RUN apt-get install -y libssl-dev
RUN apt-get install -y libffi-dev
RUN apt-get install -y python2.7
RUN apt-get install -y python-pip
RUN apt-get install -y python-dev
RUN pip install --upgrade pip
RUN python -m pip install --upgrade pwntools
RUN PWNLIB_NOTERM=1 pwn update
RUN apt-get install -y sudo
RUN useradd -m pwntools
RUN passwd --delete --unlock pwntools
RUN echo "pwntools ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/pwntools
USER pwntools
WORKDIR /home/pwntools
-8
View File
@@ -1,8 +0,0 @@
all: Dockerfile
Dockerfile: ../xenial/Dockerfile FORCE
sed "s|ubuntu:xenial|ubuntu:trusty|" < "$<" > "$@"
FORCE:
.PHONY: all
-26
View File
@@ -1,26 +0,0 @@
############################################################
# Dockerfile to build Pwntools container
# Based on Ubuntu
############################################################
FROM ubuntu:xenial
MAINTAINER Maintainer Gallopsled et al.
RUN apt-get update
RUN apt-get install -y build-essential
RUN apt-get install -y git
RUN apt-get install -y libssl-dev
RUN apt-get install -y libffi-dev
RUN apt-get install -y python2.7
RUN apt-get install -y python-pip
RUN apt-get install -y python-dev
RUN pip install --upgrade pip
RUN python -m pip install --upgrade pwntools
RUN PWNLIB_NOTERM=1 pwn update
RUN apt-get install -y sudo
RUN useradd -m pwntools
RUN passwd --delete --unlock pwntools
RUN echo "pwntools ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/pwntools
USER pwntools
WORKDIR /home/pwntools
+1 -3
View File
@@ -1,7 +1,5 @@
# Promote useful stuff to toplevel
from __future__ import absolute_import
from pwn.toplevel import *
from .toplevel import *
pwnlib.args.initialize()
pwnlib.log.install_default_handler()
+5 -5
View File
@@ -24,15 +24,15 @@ from pwnlib.context import Thread
from pwnlib.context import context
from pwnlib.dynelf import DynELF
from pwnlib.encoders import *
from pwnlib.elf.corefile import Core, Corefile, Coredump
from pwnlib.elf.elf import ELF, load
from pwnlib.elf import Core
from pwnlib.elf import ELF
from pwnlib.elf import load
from pwnlib.encoders import *
from pwnlib.exception import PwnlibException
from pwnlib.gdb import attach, debug, debug_assembly, debug_shellcode
from pwnlib.flag import *
from pwnlib.fmtstr import FmtStr, fmtstr_payload
from pwnlib.log import getLogger
from pwnlib.memleak import MemLeak, RelativeMemLeak
from pwnlib.memleak import MemLeak
from pwnlib.regsort import *
from pwnlib.replacements import *
from pwnlib.rop import ROP
@@ -40,7 +40,7 @@ from pwnlib.rop.srop import SigreturnFrame
from pwnlib.runner import *
from pwnlib.timeout import Timeout
from pwnlib.tubes.listen import listen
from pwnlib.tubes.process import process, PTY, PIPE, STDOUT
from pwnlib.tubes.process import process
from pwnlib.tubes.remote import remote, tcp, udp, connect
from pwnlib.tubes.serialtube import serialtube
from pwnlib.tubes.ssh import ssh
+1 -3
View File
@@ -1,8 +1,6 @@
from __future__ import absolute_import
import importlib
from pwnlib.version import __version__
from .version import __version__
version = __version__
+24 -49
View File
@@ -1,8 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from pwnlib.context import LocalContext
from pwnlib.context import context
from .context import LocalContext
from .context import context
class ABI(object):
@@ -26,8 +24,7 @@ class ABI(object):
#: Indicates that this ABI returns to the next address on the slot
returns = True
def __init__(self, stack, regs, align, minimum):
self.stack = stack
def __init__(self, regs, align, minimum):
self.register_arguments = regs
self.arg_alignment = align
self.stack_minimum = minimum
@@ -35,52 +32,35 @@ class ABI(object):
@staticmethod
@LocalContext
def default():
if context.os == 'android':
context.os = 'linux'
return {
(32, 'i386', 'linux'): linux_i386,
(64, 'aarch64', 'linux'): linux_aarch64,
(64, 'amd64', 'linux'): linux_amd64,
(32, 'arm', 'linux'): linux_arm,
(32, 'thumb', 'linux'): linux_arm,
(32, 'mips', 'linux'): linux_mips,
(32, 'i386', 'windows'): windows_i386,
(64, 'amd64', 'windows'): windows_amd64,
(32, 'powerpc', 'linux'): linux_ppc,
(64, 'powerpc', 'linux'): linux_ppc64,
}[(context.bits, context.arch, context.os)]
@staticmethod
@LocalContext
def syscall():
if context.os == 'android':
context.os = 'linux'
return {
(32, 'i386', 'linux'): linux_i386_syscall,
(64, 'amd64', 'linux'): linux_amd64_syscall,
(64, 'aarch64', 'linux'): linux_aarch64_syscall,
(32, 'arm', 'linux'): linux_arm_syscall,
(32, 'thumb', 'linux'): linux_arm_syscall,
(32, 'mips', 'linux'): linux_mips_syscall,
(64, 'aarch64', 'linux'): linux_aarch64_syscall,
(32, 'powerpc', 'linux'): linux_ppc_syscall,
(64, 'powerpc', 'linux'): linux_ppc64_syscall,
}[(context.bits, context.arch, context.os)]
@staticmethod
@LocalContext
def sigreturn():
if context.os == 'android':
context.os = 'linux'
return {
(32, 'i386', 'linux'): linux_i386_sigreturn,
(64, 'amd64', 'linux'): linux_amd64_sigreturn,
(32, 'arm', 'linux'): linux_arm_sigreturn,
(32, 'thumb', 'linux'): linux_arm_sigreturn,
(64, 'aarch64', 'linux'): linux_aarch64_sigreturn,
}[(context.bits, context.arch, context.os)]
class SyscallABI(ABI):
@@ -88,9 +68,9 @@ class SyscallABI(ABI):
The syscall ABI treats the syscall number as the zeroth argument,
which must be loaded into the specified register.
"""
def __init__(self, *a, **kw):
super(SyscallABI, self).__init__(*a, **kw)
self.syscall_register = self.register_arguments[0]
def __init__(self, register_arguments, *a, **kw):
super(SyscallABI, self).__init__(register_arguments, *a, **kw)
self.syscall_register = register_arguments[0]
class SigreturnABI(SyscallABI):
"""
@@ -101,31 +81,26 @@ class SigreturnABI(SyscallABI):
returns = False
linux_i386 = ABI('esp', [], 4, 0)
linux_amd64 = ABI('rsp', ['rdi','rsi','rdx','rcx','r8','r9'], 8, 0)
linux_arm = ABI('sp', ['r0', 'r1', 'r2', 'r3'], 8, 0)
linux_aarch64 = ABI('sp', ['x0', 'x1', 'x2', 'x3'], 16, 0)
linux_mips = ABI('$sp', ['$a0','$a1','$a2','$a3'], 4, 0)
linux_ppc = ABI('sp', ['r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10'], 4, 0)
linux_ppc64 = ABI('sp', ['r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10'], 8, 0)
linux_i386 = ABI([], 4, 0)
linux_amd64 = ABI(['rdi','rsi','rdx','rcx','r8','r9'], 8, 0)
linux_arm = ABI(['r0', 'r1', 'r2', 'r3'], 8, 0)
linux_aarch64 = ABI(['x0', 'x1', 'x2', 'x3'], 16, 0)
linux_mips = ABI(['$a0','$a1','$a2','$a3'], 4, 0)
linux_i386_syscall = SyscallABI('esp', ['eax', 'ebx', 'ecx', 'edx', 'esi', 'edi', 'ebp'], 4, 0)
linux_amd64_syscall = SyscallABI('rsp', ['rax', 'rdi', 'rsi', 'rdx', 'r10', 'r8', 'r9'], 8, 0)
linux_arm_syscall = SyscallABI('sp', ['r7', 'r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6'], 4, 0)
linux_aarch64_syscall = SyscallABI('sp', ['x8', 'x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6'], 16, 0)
linux_mips_syscall = SyscallABI('$sp', ['$v0','$a0','$a1','$a2','$a3'], 4, 0)
linux_ppc_syscall = ABI('sp', ['r0', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9'], 4, 0)
linux_ppc64_syscall = ABI('sp', ['r0', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9'], 8, 0)
linux_i386_syscall = SyscallABI(['eax', 'ebx', 'ecx', 'edx', 'esi', 'edi', 'ebp'], 4, 0)
linux_amd64_syscall = SyscallABI(['rax','rdi', 'rsi', 'rdx', 'r10', 'r8', 'r9'], 8, 0)
linux_arm_syscall = SyscallABI(['r7', 'r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6'], 4, 0)
linux_aarch64_syscall = SyscallABI(['x8', 'x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6'], 16, 0)
linux_mips_syscall = ABI(['$v0', '$a0','$a1','$a2','$a3'], 4, 0)
linux_i386_sigreturn = SigreturnABI('esp', ['eax'], 4, 0)
linux_amd64_sigreturn = SigreturnABI('rsp', ['rax'], 4, 0)
linux_arm_sigreturn = SigreturnABI('sp', ['r7'], 4, 0)
linux_aarch64_sigreturn = SigreturnABI('sp', ['x8'], 16, 0)
linux_i386_sigreturn = SigreturnABI(['eax'], 4, 0)
linux_amd64_sigreturn = SigreturnABI(['rax'], 4, 0)
linux_arm_sigreturn = SigreturnABI(['r7'], 4, 0)
windows_i386 = ABI('esp', [], 4, 0)
windows_amd64 = ABI('rsp', ['rcx','rdx','r8','r9'], 32, 32)
windows_i386 = ABI([], 4, 0)
windows_amd64 = ABI(['rcx','rdx','r8','r9'], 32, 32)
# Fake ABIs used by SROP
linux_i386_srop = ABI('esp', ['eax'], 4, 0)
linux_amd64_srop = ABI('rsp', ['rax'], 4, 0)
linux_arm_srop = ABI('sp', ['r7'], 4, 0)
linux_i386_srop = ABI(['eax'], 4, 0)
linux_amd64_srop = ABI(['rax'], 4, 0)
linux_arm_srop = ABI(['r7'], 4, 0)
+2 -4
View File
@@ -1,4 +1,2 @@
from __future__ import absolute_import
from pwnlib.adb.adb import *
from pwnlib.adb.protocol import Client
from .adb import *
from .protocol import Client
+69 -264
View File
@@ -1,7 +1,6 @@
"""Provides utilities for interacting with Android devices via the Android Debug Bridge.
Using Android Devices with Pwntools
-----------------------------------
# Using Android Devices with Pwntools
Pwntools tries to be as easy as possible to use with Android devices.
@@ -30,7 +29,7 @@ instance.
error("Could not find any shamus!")
Once a device is selected, you can operate on it with any of the functions in
the :mod:`pwnlib.adb` module.
the ``pwnlib.adb`` module.
.. code-block:: python
@@ -45,8 +44,6 @@ the :mod:`pwnlib.adb` module.
adb.write('/data/local/tmp/foo', 'my data')
"""
from __future__ import absolute_import
import functools
import glob
import logging
@@ -60,14 +57,14 @@ import time
import dateutil.parser
from pwnlib import atexit
from pwnlib import tubes
from pwnlib.context import LocalContext
from pwnlib.context import context
from pwnlib.device import Device
from pwnlib.log import getLogger
from pwnlib.protocols.adb import AdbClient
from pwnlib.util import misc
from .. import atexit
from .. import tubes
from ..context import LocalContext
from ..context import context
from ..device import Device
from ..log import getLogger
from ..util import misc
from .protocol import Client
log = getLogger(__name__)
@@ -88,10 +85,10 @@ def adb(argv, *a, **kw):
return tubes.process.process(context.adb + argv, *a, **kw).recvall()
@context.quietfunc
@context.quiet
def devices(serial=None):
"""Returns a list of ``Device`` objects corresponding to the connected devices."""
with AdbClient() as c:
with Client() as c:
lines = c.devices(long=True)
result = []
@@ -146,7 +143,7 @@ def root():
log.info("Enabling root on %s" % context.device)
with context.quiet:
with AdbClient() as c:
with Client() as c:
reply = c.root()
if 'already running as root' in reply:
@@ -175,7 +172,7 @@ def reboot(wait=True):
"""
log.info('Rebooting device %s' % context.device)
with AdbClient() as c:
with Client() as c:
c.reboot()
if wait:
@@ -188,7 +185,7 @@ def reboot_bootloader():
"""
log.info('Rebooting %s to bootloader' % context.device)
with AdbClient() as c:
with Client() as c:
c.reboot_bootloader()
@with_device
@@ -379,7 +376,7 @@ def wait_for_device(kick=False):
>>> device = adb.wait_for_device()
"""
with log.waitfor("Waiting for device to come online") as w:
with AdbClient() as c:
with Client() as c:
if kick:
try:
c.reconnect()
@@ -390,7 +387,7 @@ def wait_for_device(kick=False):
if context.device:
serial = str(context.device)
with AdbClient() as c:
with Client() as c:
c.wait_for_device(serial)
for device in devices():
@@ -418,7 +415,7 @@ def disable_verity():
with log.waitfor("Disabling dm-verity on %s" % context.device) as w:
root()
with AdbClient() as c:
with Client() as c:
reply = c.disable_verity()
if 'Verity already disabled' in reply:
@@ -437,7 +434,7 @@ def remount():
disable_verity()
root()
with AdbClient() as c:
with Client() as c:
reply = c.remount()
if 'remount succeeded' not in reply:
@@ -448,7 +445,7 @@ def unroot():
"""Restarts adbd as AID_SHELL."""
log.info("Unrooting %s" % context.device)
with context.quiet:
with AdbClient() as c:
with Client() as c:
reply = c.unroot()
if '0006closed' == reply:
@@ -511,14 +508,10 @@ def push(local_path, remote_path):
local_path(str): Path to the local file to push.
remote_path(str): Path or directory to store the file on the device.
Returns:
Remote path of the file.
Example:
>>> write('./filename', 'contents')
>>> adb.push('./filename', '/data/local/tmp')
'/data/local/tmp/filename'
>>> _=adb.push('./filename', '/data/local/tmp')
>>> adb.read('/data/local/tmp/filename')
'contents'
>>> adb.push('./filename', '/does/not/exist')
@@ -533,7 +526,7 @@ def push(local_path, remote_path):
msg += ' (%s)' % context.device
with log.waitfor(msg) as w:
with AdbClient() as c:
with Client() as c:
# We need to discover whether remote_path is a directory or not.
# If we cannot stat the full path, assume it's a path-plus-filename,
@@ -553,13 +546,10 @@ def push(local_path, remote_path):
if stat.S_ISDIR(mode):
remote_path = os.path.join(remote_path, remote_filename)
c.write(remote_path,
misc.read(local_path),
callback=_create_adb_push_pull_callback(w))
return remote_path
@context.quietfunc
return c.write(remote_path,
misc.read(local_path),
callback=_create_adb_push_pull_callback(w))
@context.quiet
@with_device
def read(path, target=None, callback=None):
"""Download a file from the device, and extract its contents.
@@ -569,7 +559,7 @@ def read(path, target=None, callback=None):
target(str): Optional, location to store the file.
Uses a temporary file by default.
callback(callable): See the documentation for
``adb.protocol.AdbClient.read``.
``adb.protocol.Client.read``.
Examples:
@@ -580,7 +570,7 @@ def read(path, target=None, callback=None):
...
PwnlibException: Could not stat '/does/not/exist'
"""
with AdbClient() as c:
with Client() as c:
stat = c.stat(path)
if not stat:
log.error('Could not stat %r' % path)
@@ -591,7 +581,7 @@ def read(path, target=None, callback=None):
return data
@context.quietfunc
@context.quiet
@with_device
def write(path, data=''):
"""Create a file on the device with the provided contents.
@@ -609,158 +599,14 @@ def write(path, data=''):
misc.write(temp.name, data)
push(temp.name, path)
@context.quietfunc
@with_device
def mkdir(path):
"""Create a directory on the target device.
Note:
Silently succeeds if the directory already exists.
Arguments:
path(str): Directory to create.
Examples:
>>> adb.mkdir('/')
>>> path = '/data/local/tmp/mkdir_test'
>>> adb.exists(path)
False
>>> adb.mkdir(path)
>>> adb.exists(path)
True
>>> adb.mkdir('/init')
Traceback (most recent call last):
...
PwnlibException: mkdir failed for /init, File exists
"""
if not path.startswith('/'):
log.error("Must provide an absolute path: %r" % path)
with AdbClient() as c:
st = c.stat(path)
# Don't re-create existing directories
if st and stat.S_ISDIR(st['mode']):
return
result = process(['mkdir', path]).recvall()
# Any output at all is an error
if result:
log.error(result)
@context.quietfunc
@with_device
def makedirs(path):
"""Create a directory and all parent directories on the target device.
Note:
Silently succeeds if the directory already exists.
Examples:
>>> adb.makedirs('/data/local/tmp/this/is/a/directory/heirarchy')
>>> adb.listdir('/data/local/tmp/this/is/a/directory')
['heirarchy']
"""
if path != '/':
makedirs(os.path.dirname(path))
mkdir(path)
@context.quietfunc
@with_device
def exists(path):
"""Return :const:`True` if ``path`` exists on the target device.
Examples:
>>> adb.exists('/')
True
>>> adb.exists('/init')
True
>>> adb.exists('/does/not/exist')
False
"""
with AdbClient() as c:
return bool(c.stat(path))
@context.quietfunc
@with_device
def isdir(path):
"""Return :const:`True` if ``path`` is a on the target device.
Examples:
>>> adb.isdir('/')
True
>>> adb.isdir('/init')
False
>>> adb.isdir('/does/not/exist')
False
"""
with AdbClient() as c:
st = c.stat(path)
return bool(st and stat.S_ISDIR(st['mode']))
@context.quietfunc
@with_device
def unlink(path, recursive=False):
"""Unlinks a file or directory on the target device.
Examples:
>>> adb.unlink("/does/not/exist")
Traceback (most recent call last):
...
PwnlibException: Could not unlink '/does/not/exist': Does not exist
>>> filename = '/data/local/tmp/unlink-test'
>>> adb.write(filename, 'hello')
>>> adb.exists(filename)
True
>>> adb.unlink(filename)
>>> adb.exists(filename)
False
>>> adb.mkdir(filename)
>>> adb.write(filename + '/contents', 'hello')
>>> adb.unlink(filename)
Traceback (most recent call last):
...
PwnlibException: Cannot delete non-empty directory '/data/local/tmp/unlink-test' without recursive=True
>>> adb.unlink(filename, recursive=True)
>>> adb.exists(filename)
False
"""
with AdbClient() as c:
st = c.stat(path)
if not st:
log.error("Could not unlink %r: Does not exist" % path)
# If the directory is not empty, do not delete it
if isdir(path) and c.list(path) and not recursive:
log.error("Cannot delete non-empty directory %r without recursive=True" % path)
flags = '-rf' if recursive else '-r'
output = c.execute(['rm', flags, path]).recvall()
if output:
log.error(output)
@with_device
def process(argv, *a, **kw):
"""Execute a process on the device.
See :class:`pwnlib.tubes.process.process` documentation for more info.
See ``pwnlib.tubes.process.process`` documentation for more info.
Returns:
A :class:`pwnlib.tubes.process.process` tube.
A ``process`` tube.
Examples:
@@ -777,7 +623,7 @@ def process(argv, *a, **kw):
if argv != [argv[0]]: message += ' argv=%r ' % argv
with log.progress(message) as p:
return AdbClient().execute(argv)
return Client().execute(argv)
@with_device
def interactive(**kw):
@@ -790,59 +636,27 @@ def shell(**kw):
return process(['sh', '-i'], **kw)
@with_device
def which(name, all = False, *a, **kw):
"""Retrieves the full path to a binary in ``$PATH`` on the device
def which(name):
"""Retrieves the full path to a binary in ``PATH`` on the device
Arguments:
name(str): Binary name
all(bool): Whether to return all paths, or just the first
*a: Additional arguments for :func:`.adb.process`
**kw: Additional arguments for :func:`.adb.process`
Returns:
Either a path, or list of paths
Example:
>>> adb.which('sh')
'/system/bin/sh'
>>> adb.which('sh', all=True)
['/system/bin/sh']
>>> adb.which('foobar') is None
True
>>> adb.which('foobar', all=True)
[]
>>> adb.which('sh')
'/system/bin/sh'
"""
# Unfortunately, there is no native 'which' on many phones.
which_cmd = '''
echo $PATH | while read -d: directory; do
[ -x "$directory/{name}" ] || continue;
echo -n "$directory/{name}\\x00";
IFS=:
BINARY=%s
P=($PATH)
for path in "${P[@]}"; do \
if [ -e "$path/$BINARY" ]; then \
echo "$path/$BINARY";
break
fi
done
'''.format(name=name)
''' % name
which_cmd = which_cmd.strip()
data = process(['sh','-c', which_cmd], *a, **kw).recvall()
result = []
for path in data.split('\x00'):
# Skip empty entries
if not path:
continue
# Return the first entry if all=False
if not all:
return path
# Accumulate all entries if all=True
result.append(path)
if all:
return result
return None
return process(['sh','-c',which_cmd]).recvall().strip()
@with_device
def whoami():
@@ -855,7 +669,7 @@ def forward(port):
start_forwarding = adb(['forward', tcp_port, tcp_port])
atexit.register(lambda: adb(['forward', '--remove', tcp_port]))
@context.quietfunc
@context.quiet
@with_device
def logcat(stream=False):
"""Reads the system log file.
@@ -863,12 +677,12 @@ def logcat(stream=False):
By default, causes logcat to exit after reading the file.
Arguments:
stream(bool): If :const:`True`, the contents are streamed rather than
read in a one-shot manner. Default is :const:`False`.
stream(bool): If ``True``, the contents are streamed rather than
read in a one-shot manner. Default is ``False``.
Returns:
If ``stream`` is :const:`False`, returns a string containing the log data.
Otherwise, it returns a :class:`pwnlib.tubes.tube.tube` connected to the log output.
If ``stream`` is ``False``, returns a string containing the log data.
Otherwise, it returns a ``tube`` connected to the log output.
"""
if stream:
@@ -944,17 +758,18 @@ def listdir(directory='/'):
Otherwise, less files may be returned due to restrictive SELinux
policies on adbd.
"""
return list(sorted(AdbClient().list(directory)))
return list(sorted(Client().list(directory)))
@with_device
def fastboot(args, *a, **kw):
"""Executes a fastboot command.
Returns:
The command output.
"""
argv = ['fastboot', '-s', str(context.device)] + list(args)
return tubes.process.process(argv, *a, **kw).recvall()
serial = context.device
if not serial:
log.error("Unknown device")
return tubes.process.process(['fastboot', '-s', serial] + list(args), **kw).recvall()
@with_device
def fingerprint():
@@ -979,7 +794,7 @@ def unlock_bootloader():
Note:
This requires physical interaction with the device.
"""
AdbClient().reboot_bootloader()
Client().reboot_bootloader()
fastboot(['oem', 'unlock'])
fastboot(['continue'])
@@ -991,7 +806,7 @@ class Kernel(object):
return self.symbols['_text']
@property
@context.quietfunc
@context.quiet
def symbols(self):
"""Returns a dictionary of kernel symbols"""
result = {}
@@ -1003,7 +818,7 @@ class Kernel(object):
return result
@property
@context.quietfunc
@context.quiet
def kallsyms(self):
"""Returns the raw output of kallsyms"""
if not self._kallsyms:
@@ -1014,20 +829,20 @@ class Kernel(object):
return self._kallsyms
@property
@context.quietfunc
@context.quiet
def version(self):
"""Returns the kernel version of the device."""
root()
return read('/proc/version').strip()
@property
@context.quietfunc
@context.quiet
def cmdline(self):
root()
return read('/proc/cmdline').strip()
@property
@context.quietfunc
@context.quiet
def lastmsg(self):
root()
if 'last_kmsg' in listdir('/proc'):
@@ -1197,24 +1012,14 @@ def compile(source):
if not project:
# Realistically this should inherit from context.arch, but
# this works for now.
abi = 'armeabi-v7a'
sdk = '21'
abi = {
'aarch64': 'arm64-v8a',
'amd64': 'x86_64',
'arm': 'armeabi-v7a',
'i386': 'x86',
'mips': 'mips',
'mips64': 'mips64',
}.get(context.arch, None)
# If we have an attached device, use its settings.
if context.device:
abi = str(properties.ro.product.cpu.abi)
sdk = str(properties.ro.build.version.sdk)
if abi is None:
log.error("Unknown CPU ABI")
project = _generate_ndk_project(source, abi, sdk)
# Remove any output files
@@ -1251,7 +1056,7 @@ class Partition(object):
def walk(top, topdown=True):
join = os.path.join
isdir = lambda x: stat.S_ISDIR(x['mode'])
client = AdbClient()
client = Client()
names = client.list(top)
dirs, nondirs = [], []
@@ -1289,15 +1094,15 @@ def readlink(path):
class Partitions(object):
@property
@context.quietfunc
@context.quiet
def by_name_dir(self):
return next(find('/dev/block/platform','by-name'))
@context.quietfunc
@context.quiet
def __dir__(self):
return list(self)
@context.quietfunc
@context.quiet
@with_device
def __iter__(self):
root()
@@ -1306,7 +1111,7 @@ class Partitions(object):
for name in listdir(self.by_name_dir):
yield name
@context.quietfunc
@context.quiet
@with_device
def __getattr__(self, attr):
for name in self:
@@ -1376,7 +1181,7 @@ def uninstall(package, *arguments):
with context.quiet:
return process(['pm','uninstall',package] + list(arguments)).recvall()
@context.quietfunc
@context.quiet
def packages():
"""Returns a list of packages installed on the system"""
packages = process(['pm', 'list', 'packages']).recvall()
+551 -3
View File
@@ -1,5 +1,553 @@
"""
This file exists only for backward compatibility
Implementation of the Android Debug Bridge (ADB) protocol, as far as Binjitsu needs it.
Documentation is available here:
https://android.googlesource.com/platform/system/core/+/master/adb/protocol.txt
"""
from pwnlib.protocols.adb import AdbClient
Client = AdbClient
import functools
import stat
import time
from ..context import context
from ..log import Logger
from ..log import getLogger
from ..tubes.listen import listen
from ..tubes.process import process
from ..tubes.remote import remote
from ..util.lists import group
from ..util.misc import size
from ..util.packing import p32
from ..util.proc import pidof
from ..util.sh_string import sh_string
log = getLogger(__name__)
def pack(val):
return '%04x' % val
def unpack(val):
return int(val, 16)
OKAY = "OKAY"
FAIL = "FAIL"
class Message(object):
"""An ADB hex-length-prefixed message"""
def __init__(self, string):
self.string = string
def __str__(self):
return ('%04x' % len(self.string)) + self.string
def __flat__(self):
return str(self)
class Connection(remote):
"""Connection to the ADB server"""
def __init__(self, host, port, level=None, *a, **kw):
super(Connection, self).__init__(host, port, level=level, *a, **kw)
self._executable = None
self._argv = None
self._pid = None
self._cwd = None
self._env = None
def close(self):
with context.quiet:
super(Connection, self).close()
def adb_send(self, message):
self.send(str(Message(message)))
return self.recvn(4)
def adb_unpack(self):
return unpack(self.recvn(4))
def flat(self, *a, **kw):
kw.setdefault('word_size', 32)
return super(Connection, self).flat(*a, **kw)
class Process(Connection):
"""Duck-typed ``tubes.remote`` object to add properties of a ``tubes.process``"""
class Client(Logger):
"""ADB Client"""
def __init__(self, level=None):
super(Client, self).__init__()
if level is not None:
self.setLevel(level)
self.host = context.adb_host
self.port = context.adb_port
self._c = None
@property
def c(self):
"""Client's connection to the ADB server"""
if not self._c:
try:
self._c = Connection(self.host, self.port, level=self.level)
except Exception:
# If the connection fails, try starting a server on that port
# as long as it's the *default* port.
if self.host == context.defaults['adb_host'] \
and self.port == context.defaults['adb_port']:
log.warn("Could not connect to ADB server, trying to start it")
process(context.adb + ['start-server']).recvall()
else:
log.exception('Could not connect to ADB server')
# Final attempt...
if not self._c:
self._c = Connection(self.host, self.port, level=self.level)
return self._c
def _autoclose(fn):
"""Decorator which automatically closes the connection to the ADB server
after calling the decorated function."""
@functools.wraps(fn)
def wrapper(self, *a, **kw):
rv = fn(self, *a, **kw)
if self._c:
self._c.close()
self._c = None
return rv
return wrapper
def _with_transport(fn):
"""Decorator which automatically selects a device transport before calling
the decorated function, and closes the connection afterward."""
@functools.wraps(fn)
def wrapper(self, *a, **kw):
self.transport()
rv = fn(self, *a, **kw)
if self._c:
self._c.close()
self._c = None
return rv
return wrapper
def send(self, *a, **kw):
"""Sends data to the ADB server"""
return self.c.adb_send(*a, **kw)
def unpack(self, *a, **kw):
"""Receives a hex-ascii packed integer from the ADB server"""
return self.c.adb_unpack(*a, **kw)
def recvl(self):
"""Receives a length-prefixed data buffer from the ADB server"""
length = self.c.adb_unpack()
return self.c.recvn(length)
@_autoclose
def kill(self):
"""Kills the remote ADB server"
>>> c=adb.protocol.Client()
>>> c.kill()
The server is automatically re-started on the next request,
if the default host/port are used.
>>> c.version() > (4,0)
True
"""
try:
self.send('host:kill')
except EOFError:
pass
@_autoclose
def version(self):
"""
Returns:
Tuple containing the ``(major, minor)`` version from the ADB server
Example:
>>> adb.protocol.Client().version() # doctest: +SKIP
(4, 36)
"""
response = self.send('host:version')
if response == OKAY:
return (self.c.adb_unpack(), self.c.adb_unpack())
self.error("Could not fetch version")
@_autoclose
def devices(self, long=False):
"""
Arguments:
long(bool): If ``True``, fetch the long-format listing.
Returns:
String representation of all available devices.
"""
msg = 'host:devices'
if long:
msg += '-l'
response = self.send(msg)
if response == 'OKAY':
return self.recvl()
self.error("Could not enumerate devices")
@_autoclose
def track_devices(self):
"""
Returns:
Generator which returns a short-format listing of available
devices each time a device state changes.
"""
self.send('host:track-devices')
while True:
yield self.recvl()
def transport(self, serial=None):
"""Sets the Transport on the rmeote device.
Examples:
>>> adb.protocol.Client().transport()
"""
msg = 'host:transport:%s' % (serial or context.device)
if self.send(msg) == FAIL:
self.error("Could not set transport to %r" % serial)
@_autoclose
@_with_transport
def execute(self, argv):
r"""Executes a program on the device.
Returns:
A ``tube`` which is connected to the process.
Examples:
>>> adb.protocol.Client().execute(['echo','hello']).recvall()
'hello\n'
"""
self.transport(context.device)
if isinstance(argv, str):
argv = [argv]
argv = list(map(sh_string, argv))
cmd = 'exec:%s' % (' '.join(argv))
if OKAY == self.send(cmd):
rv = self._c
self._c = None
return rv
def _basic_wrapper(string):
@_autoclose
@_with_transport
def wrapper(self):
self.send(string)
return self.c.recvall()
return wrapper
@_autoclose
@_with_transport
def remount(self):
self.send('remount:')
return self.c.recvall()
@_autoclose
@_with_transport
def root(self):
self.send('root:')
return self.c.recvall()
@_autoclose
@_with_transport
def unroot(self):
self.send('unroot:')
return self.c.recvall()
@_autoclose
@_with_transport
def disable_verity(self):
self.send('disable-verity:')
return self.c.recvall()
@_autoclose
@_with_transport
def enable_verity(self):
self.send('enable-verity:')
return self.c.recvall()
@_autoclose
@_with_transport
def reconnect(self):
self.send('reconnect:')
return self.c.recvall()
@_autoclose
@_with_transport
def reboot(self):
self.send('reboot:')
return self.c.recvall()
@_autoclose
@_with_transport
def reboot_bootloader(self):
self.send('reboot:bootloader')
return self.c.recvall()
@_autoclose
def wait_for_device(self, serial=''):
response = self.send('host-serial:%s:wait-for-any-device' % serial)
# The first OKAY is that the command was understood
if response != 'OKAY':
self.error("An error occurred while waiting for device with serial %r" % serial)
# The second OKAY is that the device is available
response = self.c.recvn(4)
if response != 'OKAY':
self.error("An error occurred while waiting for device with serial %r" % serial)
def _sync(fn):
"""Decorator which enters 'sync:' mode to the selected transport,
then invokes the decorated funciton."""
@functools.wraps(fn)
def wrapper(self, *a, **kw):
rv = None
if FAIL != self.send('sync:'):
rv = fn(self, *a, **kw)
return rv
return wrapper
def list(self, path):
"""Execute the ``LIST`` command of the ``SYNC`` API.
Arguments:
path(str): Path of the directory to list.
Return:
A dictionary, where the keys are relative filenames,
and the values are a dictionary containing the same
values as ``stat()`` supplies.
Note:
In recent releases of Android (e.g. 7.0), the domain that
adbd executes from does not have access to everything that
the shell user does.
Because of this, while the shell user can get listings of
e.g. the root directory ('/'), adbd cannot.
The SYNC APIs are executed within the adbd context, not the
shell user context.
This issue is not a problem if the phone is rooted via
'adb root', since adbd then runs in the ``su`` domain.
Examples:
>>> pprint(adb.Client().list('/data/user'))
{'0': {'mode': 41471, 'size': 11, 'time': ...}}
>>> adb.Client().list('/does/not/exist')
Traceback (most recent call last):
...
PwnlibException: Cannot list directory '/does/not/exist': Does not exist
"""
st = self.stat(path)
if not st:
log.error("Cannot list directory %r: Does not exist" % path)
if not stat.S_ISDIR(st['mode']):
log.error("Cannot list directory %r: Path is not a directory" % path)
return self._list(path)
@_with_transport
@_sync
def _list(self, path):
self.c.flat('LIST', len(path), path)
files = {}
while True:
response = self.c.recvn(4)
if response == 'DONE':
break
if response != 'DENT':
self.error('Unexpected response: %r' % response)
mode = self.c.u32()
size = self.c.u32()
time = self.c.u32()
name = self.c.recvn(self.c.u32())
# Ignore the current directory and parent
if name in ('', '.', '..'):
continue
files[name] = {'mode': mode,
'size': size,
'time': time}
return files
@_with_transport
@_sync
def stat(self, path):
"""Execute the ``STAT`` command of the ``SYNC`` API.
Arguments:
path(str): Path to the file to stat.
Return:
On success, a dictionary mapping the values returned.
If the file cannot be ``stat()``ed, None is returned.
Example:
>>> expected = {'mode': 16749, 'size': 0, 'time': 0}
>>> adb.protocol.Client().stat('/proc') == expected
True
>>> adb.protocol.Client().stat('/does/not/exist') == None
True
"""
self.c.flat('STAT', len(path), path)
if self.c.recvn(4) != 'STAT':
self.error("An error occured while attempting to STAT a file.")
mode = self.c.u32()
size = self.c.u32()
time = self.c.u32()
if (mode,size,time) == (0,0,0):
return None
return {'mode': mode, 'size': size, 'time': time}
def write(self, path, data, mode=0o755, timestamp=None, callback=None):
"""Execute the ``WRITE`` command of the ``SYNC`` API.
Arguments:
path(str): Path to the file to write
data(str): Data to write to the file
mode(int): File mode to set (e.g. ``0o755``)
timestamp(int): Unix timestamp to set the file date to
callback(callable): Callback function invoked as data
is written. Arguments provided are:
- File path
- All data
- Expected size of all data
- Current chunk
- Expected size of chunk
"""
# We must ensure that 'path' is not a directory
# Writing to a directory is supported, but creates a temporary file
st = self.stat(path)
if st and stat.S_ISDIR(st['mode']):
log.error("Cannot write to %r: Path is a directory" % path)
return self._write(path, data, mode=0o755, timestamp=None, callback=None)
@_with_transport
@_sync
def _write(self, path, data, mode=0o755, timestamp=None, callback=None):
path += ',' + str(mode)
self.c.flat('SEND', len(path), path)
sent = 0
# Data needs to be broken up into chunks!
for chunk in group(0x10000, data):
self.c.flat('DATA', len(chunk), chunk)
if callback:
callback(path, data[:sent], len(data), chunk, len(chunk))
sent += len(chunk)
# Send completion notification and timestamp
if timestamp is None:
timestamp = int(time.time())
self.c.flat('DONE', timestamp)
result = self.c.recvn(4)
if result != 'OKAY':
log.error("Sync write failed: %r (expected OKAY)" % result)
return
@_with_transport
@_sync
def read(self, path, filesize=0, callback=lambda *a: True):
"""Execute the ``READ`` command of the ``SYNC`` API.
Arguments:
path(str): Path to the file to read
filesize(int): Size of the file, in bytes. Optional.
callback(callable): Callback function invoked as data
becomes available. Arguments provided are:
- File path
- All data
- Expected size of all data
- Current chunk
- Expected size of chunk
Return:
The data received as a string.
"""
self.c.send('RECV' + p32(len(path)) + path)
# Accumulate all data here
all_data = ''
while True:
magic = self.c.recvn(4)
# adbd says there is no more data to send
if magic == 'DONE':
break
if magic == 'FAIL':
self.error('Could not read file %r: Got FAIL.' % path)
# did we expect to be done?
if magic != 'DATA':
self.error('Error after file read: %r (expected DATA)' % magic)
# receive all of the data in the chunk
chunk_size = self.c.u32()
chunk_data = ''
while len(chunk_data) != chunk_size:
chunk_data += self.c.recv(chunk_size - len(chunk_data))
if callback:
callback(path,
all_data,
filesize,
chunk_data,
chunk_size)
# add the chunk onto what we have
all_data += chunk_data
zero = self.c.u32()
if zero != 0:
self.error('Error after file read: %r (expected ZERO)' % zero)
return all_data
@_with_transport
def forward(self, device, host_proto, host_port, device_proto, device_port):
self.send('host:forward:%s:%s;%s:%s' % (host_proto, host_port, device_proto, device_port))
self.c.recvall()
def __enter__(self, *a, **kw):
return self
@_autoclose
def __exit__(self, *a, **kw): pass
def proxy(port=9999):
"""Starts an ADB proxy on the specified port, for debugging purposes."""
l = listen(port)
l.wait_for_connection()
r = remote(context.adb_host, context.adb_port, level='debug')
l <> r
+5 -19
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python2
"""
Pwntools exposes several magic command-line arguments and environment
@@ -17,8 +16,8 @@ The easiest example is to enable more verbose debugging. Just set ``DEBUG``.
$ python exploit.py DEBUG
These arguments are automatically extracted, regardless of their name, and
exposed via :mod:`pwnlib.args.args`, which is exposed as the global variable
:data:`args`. Arguments which ``pwntools`` reserves internally are not exposed
exposed via ``pwnlib.args.args``, which is exposed as the global variable
``args``. Arguments which ``pwntools`` reserves internally are not exposed
this way.
.. code-block:: bash
@@ -40,31 +39,18 @@ not specified evaluate to an empty string.
The full list of supported "magic arguments" and their effects are listed
below.
"""
from __future__ import absolute_import
import collections
import logging
import os
import string
import sys
from pwnlib import term
from pwnlib.context import context
from . import term
from .context import context
term_mode = True
args = collections.defaultdict(str)
env_prefix = 'PWNLIB_'
free_form = True
# Check to see if we were invoked as one of the 'pwn xxx' scripts.
# If so, we don't want to remove e.g. "SYS_" from the end of the command
# line, as this breaks things like constgrep.
import pwnlib.commandline
basename = os.path.basename(sys.argv[0])
if basename == 'pwn' or basename in pwnlib.commandline.__all__:
free_form = False
def isident(s):
"""
@@ -186,7 +172,7 @@ def initialize():
sys.argv.remove(orig)
hooks[arg](value)
elif free_form and isident(arg):
elif isident(arg):
sys.argv.remove(orig)
args[arg] = value
+84 -149
View File
@@ -39,8 +39,6 @@ Disassembly
' 0: b8 0b 00 00 00 mov eax,0xb'
"""
from __future__ import absolute_import
import errno
import os
import platform
@@ -55,11 +53,11 @@ from glob import glob
from os import environ
from os import path
from pwnlib import atexit
from pwnlib import shellcraft
from pwnlib.context import LocalContext
from pwnlib.context import context
from pwnlib.log import getLogger
from . import atexit
from . import shellcraft
from .context import LocalContext
from .context import context
from .log import getLogger
log = getLogger(__name__)
@@ -73,11 +71,6 @@ def dpkg_search_for_binutils(arch, util):
Returns:
A list of candidate package names.
::
>>> pwnlib.asm.dpkg_search_for_binutils('aarch64', 'as')
['binutils-aarch64-linux-gnu']
"""
# Example output:
@@ -104,16 +97,6 @@ def dpkg_search_for_binutils(arch, util):
def print_binutils_instructions(util, context):
"""On failure to find a binutils utility, inform the user of a way
they can get it easily.
Doctest:
>>> context.clear(arch = 'amd64')
>>> pwnlib.asm.print_binutils_instructions('as', context)
Traceback (most recent call last):
...
PwnlibException: Could not find 'as' installed for ContextType(arch = 'amd64', bits = 64, endian = 'little')
Try installing binutils for this architecture:
$ sudo apt-get install binutils
"""
# This links to our instructions on how to manually install binutils
# for several architectures.
@@ -190,12 +173,10 @@ def which_binutils(util):
for arch in arches:
for gutil in utils:
# e.g. objdump
if arch is None:
pattern = gutil
if arch is None: pattern = gutil
# e.g. aarch64-linux-gnu-objdump
else:
pattern = '%s*linux*-%s' % (arch,gutil)
else: pattern = '%s*linux*-%s' % (arch,gutil)
for dir in environ['PATH'].split(':'):
res = sorted(glob(path.join(dir, pattern)))
@@ -399,14 +380,16 @@ def cpp(shellcode):
Examples:
>>> cpp("mov al, SYS_setresuid", arch = "i386", os = "linux")
'mov al, 164\n'
>>> cpp("weee SYS_setresuid", arch = "arm", os = "linux")
'weee (0+164)\n'
>>> cpp("SYS_setresuid", arch = "thumb", os = "linux")
'(0+164)\n'
>>> cpp("SYS_setresuid", os = "freebsd")
'311\n'
.. doctest::
>>> cpp("mov al, SYS_setresuid", arch = "i386", os = "linux")
'mov al, 164\n'
>>> cpp("weee SYS_setresuid", arch = "arm", os = "linux")
'weee (0+164)\n'
>>> cpp("SYS_setresuid", arch = "thumb", os = "linux")
'(0+164)\n'
>>> cpp("SYS_setresuid", os = "freebsd")
'311\n'
"""
arch = context.arch
os = context.os
@@ -423,39 +406,17 @@ def cpp(shellcode):
return _run(cmd, code).strip('\n').rstrip() + '\n'
@LocalContext
def make_elf_from_assembly(assembly,
vma=None,
extract=False,
shared=False,
strip=False,
**kwargs):
r"""make_elf_from_assembly(assembly, vma=None, extract=None, shared=False, strip=False, **kwargs) -> str
Builds an ELF file with the specified assembly as its executable code.
This differs from :func:`.make_elf` in that all ELF symbols are preserved,
such as labels and local variables. Use :func:`.make_elf` if size matters.
Additionally, the default value for ``extract`` in :func:`.make_elf` is
different.
Note:
This is effectively a wrapper around :func:`.asm`. with setting
``extract=False``, ``vma=0x10000000``, and marking the resulting
file as executable (``chmod +x``).
Note:
ELF files created with `arch=thumb` will prepend an ARM stub
which switches to Thumb mode.
def make_elf_from_assembly(assembly, vma = None, extract=False, shared = False):
r"""
Builds an ELF file with the specified assembly as its
executable code.
Arguments:
assembly(str): Assembly code to build into an ELF
assembly(str): Assembly
vma(int): Load address of the binary
(Default: ``0x10000000``, or ``0`` if ``shared=True``)
extract(bool): Extract the full ELF data from the file.
(Default: ``False``)
shared(bool): Create a shared library
(Default: ``False``)
kwargs(dict): Arguments to pass to :func:`.asm`.
extract(bool): Whether to return the data extracted from the file created,
or the path to it.
Returns:
@@ -464,9 +425,6 @@ def make_elf_from_assembly(assembly,
Example:
This example shows how to create a shared library, and load it via
``LD_PRELOAD``.
>>> context.clear()
>>> context.arch = 'amd64'
>>> sc = 'push rbp; mov rbp, rsp;'
@@ -475,18 +433,6 @@ def make_elf_from_assembly(assembly,
>>> solib = make_elf_from_assembly(sc, shared=1)
>>> subprocess.check_output(['echo', 'World'], env={'LD_PRELOAD': solib})
'Hello\nWorld\n'
The same thing can be done with :func:`.make_elf`, though the sizes
are different. They both
>>> file_a = make_elf(asm('nop'), extract=True)
>>> file_b = make_elf_from_assembly('nop', extract=True)
>>> file_a[:4] == file_b[:4]
True
>>> len(file_a) < 0x200
True
>>> len(file_b) > 0x1000
True
"""
if shared and vma:
log.error("Cannot specify a VMA for a shared library.")
@@ -503,44 +449,38 @@ def make_elf_from_assembly(assembly,
if not assembly.startswith(to_thumb):
assembly = to_thumb + assembly
result = asm(assembly, vma = vma, shared = shared, extract = False, **kwargs)
path = asm(assembly, vma = vma, extract = extract, shared = shared)
if not extract:
os.chmod(result, 0755)
else:
with open(result, 'rb') as io:
result = io.read()
os.chmod(path, 0755)
return result
return path
@LocalContext
def make_elf(data,
vma=None,
strip=True,
extract=True,
shared=False):
r"""make_elf(data, vma=None, strip=True, extract=True, shared=False, **kwargs) -> str
Builds an ELF file with the specified binary data as its executable code.
def make_elf(data, vma = None, strip=True, extract=True, shared=False):
r"""
Builds an ELF file with the specified binary data as its
executable code.
Arguments:
data(str): Assembled code
vma(int): Load address for the ELF file
strip(bool): Strip the resulting ELF file. Only matters if ``extract=False``.
(Default: ``True``)
extract(bool): Extract the assembly from the ELF file.
If ``False``, the path of the ELF file is returned.
(Default: ``True``)
shared(bool): Create a Dynamic Shared Object (DSO, i.e. a ``.so``)
which can be loaded via ``dlopen`` or ``LD_PRELOAD``.
Examples:
This example creates an i386 ELF that just does
execve('/bin/sh',...).
>>> context.clear(arch='i386')
>>> context.clear()
>>> context.arch = 'i386'
>>> context.bits = 32
>>> filename = tempfile.mktemp()
>>> bin_sh = '6a68682f2f2f73682f62696e89e331c96a0b5899cd80'.decode('hex')
>>> filename = make_elf(bin_sh, extract=False)
>>> data = make_elf(bin_sh)
>>> with open(filename,'wb+') as f:
... f.write(data)
... f.flush()
>>> os.chmod(filename,0777)
>>> p = process(filename)
>>> p.sendline('echo Hello; exit')
>>> p.recvline()
@@ -608,37 +548,38 @@ def make_elf(data,
@LocalContext
def asm(shellcode, vma = 0, extract = True, shared = False):
r"""asm(code, vma = 0, extract = True, shared = False, ...) -> str
r"""asm(code, vma = 0, extract = True, ...) -> str
Runs :func:`cpp` over a given shellcode and then assembles it into bytes.
To see which architectures or operating systems are supported,
look in :mod:`pwnlib.contex`.
Assembling shellcode requires that the GNU assembler is installed
for the target architecture.
See :doc:`Installing Binutils </install/binutils>` for more information.
To support all these architecture, we bundle the GNU assembler
and objcopy with pwntools.
Arguments:
shellcode(str): Assembler code to assemble.
vma(int): Virtual memory address of the beginning of assembly
extract(bool): Extract the raw assembly bytes from the assembled
file. If :const:`False`, returns the path to an ELF file
with the assembly embedded.
shared(bool): Create a shared object.
kwargs(dict): Any attributes on :data:`.context` can be set, e.g.set
``arch='arm'``.
shellcode(str): Assembler code to assemble.
vma(int): Virtual memory address of the beginning of assembly
extract(bool): Extract the raw assembly bytes from the assembled
file. If ``False``, returns the path to an ELF file
with the assembly embedded.
Kwargs:
Any arguments/properties that can be set on ``context``
Examples:
>>> asm("mov eax, SYS_select", arch = 'i386', os = 'freebsd')
'\xb8]\x00\x00\x00'
>>> asm("mov eax, SYS_select", arch = 'amd64', os = 'linux')
'\xb8\x17\x00\x00\x00'
>>> asm("mov rax, SYS_select", arch = 'amd64', os = 'linux')
'H\xc7\xc0\x17\x00\x00\x00'
>>> asm("mov r0, #SYS_select", arch = 'arm', os = 'linux', bits=32)
'R\x00\xa0\xe3'
.. doctest::
>>> asm("mov eax, SYS_select", arch = 'i386', os = 'freebsd')
'\xb8]\x00\x00\x00'
>>> asm("mov eax, SYS_select", arch = 'amd64', os = 'linux')
'\xb8\x17\x00\x00\x00'
>>> asm("mov rax, SYS_select", arch = 'amd64', os = 'linux')
'H\xc7\xc0\x17\x00\x00\x00'
>>> asm("mov r0, #SYS_select", arch = 'arm', os = 'linux', bits=32)
'R\x00\xa0\xe3'
"""
result = ''
@@ -670,20 +611,11 @@ def asm(shellcode, vma = 0, extract = True, shared = False):
ldflags = ['-z', 'execstack', '-o', step3, step2]
if vma:
ldflags += ['--section-start=.shellcode=%#x' % vma,
'--entry=%#x' % vma]
'--entry=%#x' % vma,
'-z', 'max-page-size=4096',
'-z', 'common-page-size=4096']
elif shared:
ldflags += ['-shared', '-init=_start']
# In order to ensure that we generate ELF files with 4k pages,
# and not e.g. 65KB pages (AArch64), force the page size.
# This is a result of GNU Gold being silly.
#
# Introduced in commit dd58f409 without supporting evidence,
# this shouldn't do anything except keep consistent page granularity
# across architectures.
ldflags += ['-z', 'max-page-size=4096',
'-z', 'common-page-size=4096']
_run(linker + ldflags)
elif file(step2,'rb').read(4) == '\x7fELF':
@@ -735,21 +667,24 @@ def disasm(data, vma = 0, byte = True, offset = True, instructions = True):
Examples:
>>> print disasm('b85d000000'.decode('hex'), arch = 'i386')
0: b8 5d 00 00 00 mov eax,0x5d
>>> print disasm('b85d000000'.decode('hex'), arch = 'i386', byte = 0)
0: mov eax,0x5d
>>> print disasm('b85d000000'.decode('hex'), arch = 'i386', byte = 0, offset = 0)
mov eax,0x5d
>>> print disasm('b817000000'.decode('hex'), arch = 'amd64')
0: b8 17 00 00 00 mov eax,0x17
>>> print disasm('48c7c017000000'.decode('hex'), arch = 'amd64')
0: 48 c7 c0 17 00 00 00 mov rax,0x17
>>> print disasm('04001fe552009000'.decode('hex'), arch = 'arm')
0: e51f0004 ldr r0, [pc, #-4] ; 0x4
4: 00900052 addseq r0, r0, r2, asr r0
>>> print disasm('4ff00500'.decode('hex'), arch = 'thumb', bits=32)
0: f04f 0005 mov.w r0, #5
.. doctest::
>>> print disasm('b85d000000'.decode('hex'), arch = 'i386')
0: b8 5d 00 00 00 mov eax,0x5d
>>> print disasm('b85d000000'.decode('hex'), arch = 'i386', byte = 0)
0: mov eax,0x5d
>>> print disasm('b85d000000'.decode('hex'), arch = 'i386', byte = 0, offset = 0)
mov eax,0x5d
>>> print disasm('b817000000'.decode('hex'), arch = 'amd64')
0: b8 17 00 00 00 mov eax,0x17
>>> print disasm('48c7c017000000'.decode('hex'), arch = 'amd64')
0: 48 c7 c0 17 00 00 00 mov rax,0x17
>>> print disasm('04001fe552009000'.decode('hex'), arch = 'arm')
0: e51f0004 ldr r0, [pc, #-4] ; 0x4
4: 00900052 addseq r0, r0, r2, asr r0
>>> print disasm('4ff00500'.decode('hex'), arch = 'thumb', bits=32)
0: f04f 0005 mov.w r0, #5
>>>
"""
result = ''
+1 -2
View File
@@ -2,13 +2,12 @@
Analogous to atexit, this module allows the programmer to register functions to
be run if an unhandled exception occurs.
"""
from __future__ import absolute_import
import sys
import threading
import traceback
from pwnlib.context import context
from .context import context
__all__ = ['register', 'unregister']
+1 -2
View File
@@ -7,13 +7,12 @@ this replacement module also defines :func:`unregister`.
This module also fixes a the issue that exceptions raised by an exit handler is
printed twice when the standard :mod:`atexit` is used.
"""
from __future__ import absolute_import
import sys
import threading
import traceback
from pwnlib.context import context
from .context import context
__all__ = ['register', 'unregister']
-19
View File
@@ -1,20 +1 @@
#!/usr/bin/env python2
__all__ = [
'asm',
'checksec',
'common',
'constgrep',
'cyclic',
'disasm',
'elfdiff',
'elfpatch',
'errno',
'hex',
'main',
'phd',
'pwnstrip',
'scramble',
'shellcraft',
'unhex',
'update'
]
+2 -3
View File
@@ -1,11 +1,10 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import argparse
import sys
from pwn import *
from pwnlib.commandline import common
from . import common
parser = common.parser_commands.add_parser(
'asm',
+2 -3
View File
@@ -1,11 +1,10 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import argparse
import sys
from pwn import *
from pwnlib.commandline import common
from . import common
parser = common.parser_commands.add_parser(
'checksec',
+2 -6
View File
@@ -1,15 +1,12 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import argparse
import os
import re
import pwnlib
pwnlib.args.free_form = False
from pwn import *
from pwnlib.commandline import common
from . import common
p = common.parser_commands.add_parser(
'constgrep',
@@ -78,7 +75,6 @@ def main(args):
else:
matcher = re.compile(args.regex)
# Evaluate the given constant
if args.constant:
try:
+2 -6
View File
@@ -1,15 +1,11 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import argparse
import string
import sys
import pwnlib
pwnlib.args.free_form = False
from pwn import *
from pwnlib.commandline import common
from . import common
parser = common.parser_commands.add_parser(
'cyclic',
+2 -6
View File
@@ -1,15 +1,11 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import argparse
import string
import sys
import pwnlib
pwnlib.args.free_form = False
from pwn import *
from pwnlib.commandline import common
from . import common
parser = common.parser_commands.add_parser(
'disasm',
+2 -6
View File
@@ -1,17 +1,13 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import shutil
from argparse import ArgumentParser
from subprocess import CalledProcessError
from subprocess import check_output
from tempfile import NamedTemporaryFile
import pwnlib
pwnlib.args.free_form = False
from pwn import *
from pwnlib.commandline import common
from . import common
def dump(objdump, path):
+2 -6
View File
@@ -1,14 +1,10 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import argparse
import sys
import pwnlib
pwnlib.args.free_form = False
from pwn import *
from pwnlib.commandline import common
from . import common
p = common.parser_commands.add_parser(
'elfpatch',
+1 -3
View File
@@ -1,9 +1,7 @@
from __future__ import absolute_import
import argparse
import os
from pwnlib.commandline import common
from . import common
parser = common.parser_commands.add_parser(
'errno',
+1 -3
View File
@@ -1,10 +1,8 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import argparse
import sys
from pwnlib.commandline import common
from . import common
parser = common.parser_commands.add_parser(
'hex',
+18 -20
View File
@@ -1,25 +1,23 @@
from __future__ import absolute_import
import sys
from pwnlib.commandline import asm
from pwnlib.commandline import checksec
from pwnlib.commandline import common
from pwnlib.commandline import constgrep
from pwnlib.commandline import cyclic
from pwnlib.commandline import disasm
from pwnlib.commandline import elfdiff
from pwnlib.commandline import elfpatch
from pwnlib.commandline import errno
from pwnlib.commandline import hex
from pwnlib.commandline import phd
from pwnlib.commandline import pwnstrip
from pwnlib.commandline import scramble
from pwnlib.commandline import shellcraft
from pwnlib.commandline import unhex
from pwnlib.commandline import update
from pwnlib.commandline.common import parser
from pwnlib.context import context
from . import asm
from . import checksec
from . import common
from . import constgrep
from . import cyclic
from . import disasm
from . import elfdiff
from . import elfpatch
from . import errno
from . import hex
from . import phd
from . import pwnstrip
from . import scramble
from . import shellcraft
from . import unhex
from . import update
from .common import parser
from ..context import context
commands = {
'asm': asm.main,
+2 -6
View File
@@ -1,15 +1,11 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import argparse
import os
import sys
import pwnlib
pwnlib.args.free_form = False
from pwn import *
from pwnlib.commandline import common
from . import common
parser = common.parser_commands.add_parser(
'phd',
+2 -6
View File
@@ -1,12 +1,8 @@
from __future__ import absolute_import
import argparse
import pwnlib
pwnlib.args.free_form = False
from pwn import *
from pwnlib.commandline import common
from . import common
p = common.parser_commands.add_parser(
'pwnstrip',
+2 -6
View File
@@ -1,13 +1,9 @@
from __future__ import absolute_import
import argparse
import sys
import pwnlib
pwnlib.args.free_form = False
from pwn import *
from pwnlib.commandline import common
from . import common
parser = common.parser_commands.add_parser(
'scramble',
+3 -7
View File
@@ -1,16 +1,12 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import argparse
import os
import sys
import types
import pwnlib
pwnlib.args.free_form = False
from pwn import *
from pwnlib.commandline import common
from . import common
# ____ _ _ _ __ _
@@ -172,7 +168,7 @@ def get_template(name):
def is_not_a_syscall_template(name):
template_src = shellcraft._get_source(name)
return '/syscalls' not in template_src
return 'man 2' not in read(template_src)
def main(args):
if args.list:
+1 -3
View File
@@ -1,11 +1,9 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import argparse
import sys
from string import whitespace
from pwnlib.commandline import common
from . import common
parser = common.parser_commands.add_parser(
'unhex',
+3 -7
View File
@@ -1,13 +1,9 @@
#!/usr/bin/env python2
from __future__ import absolute_import
import subprocess
import pwnlib
pwnlib.args.free_form = False
from pwn import *
from pwnlib.commandline import common
from . import common
p = common.parser_commands.add_parser(
'update',
@@ -25,7 +21,7 @@ p.add_argument('--pre', action='store_true', help='''
def main(a):
result = pwnlib.update.perform_check(prerelease=a.pre)
if a.install:
subprocess.check_call(result, shell=False)
subprocess.check_call(result, shell=True)
if __name__ == '__main__':
pwnlib.common.main(__file__)
+3 -10
View File
@@ -40,15 +40,13 @@ Example:
4
"""
from __future__ import absolute_import
import importlib
import sys
from types import ModuleType
from pwnlib.constants.constant import Constant
from pwnlib.context import context
from pwnlib.util import safeeval
from ..context import context
from ..util import safeeval
from .constant import Constant
class ConstantsModule(ModuleType):
@@ -139,11 +137,6 @@ class ConstantsModule(ModuleType):
if not isinstance(string, str):
return string
simple = getattr(self, string, None)
if simple is not None:
return simple
key = context.os, context.arch
if key not in self._env_store:
self._env_store[key] = {key: getattr(self, key) for key in dir(self) if not key.endswith('__')}
+17 -61
View File
@@ -4,8 +4,6 @@
Implements context management so that nested/scoped contexts and threaded
contexts work properly and as expected.
"""
from __future__ import absolute_import
import collections
import functools
import logging
@@ -21,8 +19,8 @@ import time
import socks
from pwnlib.device import Device
from pwnlib.timeout import Timeout
from ..device import Device
from ..timeout import Timeout
_original_socket = socket.socket
@@ -263,7 +261,7 @@ class ContextType(object):
r"""
Class for specifying information about the target machine.
Intended for use as a pseudo-singleton through the global
variable :data:`.context`, available via
variable ``pwnlib.context.context``, available via
``from pwn import *`` as ``context``.
The context is usually specified at the top of the Python file for clarity. ::
@@ -332,8 +330,6 @@ class ContextType(object):
'aslr': True,
'binary': None,
'bits': 32,
'buffer_size': 4096,
'delete_corefiles': False,
'device': os.getenv('ANDROID_SERIAL', None) or None,
'endian': 'little',
'kernel': None,
@@ -341,7 +337,6 @@ class ContextType(object):
'log_file': _devnull(),
'log_console': sys.stdout,
'randomize': False,
'rename_corefiles': True,
'newline': '\n',
'noptrace': False,
'os': 'linux',
@@ -532,13 +527,12 @@ class ContextType(object):
def quiet(self, function=None):
"""Disables all non-error logging within the enclosed scope,
*unless* the debugging level is set to 'debug' or lower."""
level = 'error'
if context.log_level <= logging.DEBUG:
level = None
return self.local(function, log_level=level)
if not function:
level = 'error'
if context.log_level <= logging.DEBUG:
level = None
return self.local(function, log_level=level)
def quietfunc(self, function):
"""Similar to :attr:`quiet`, but wraps a whole function."""
@functools.wraps(function)
def wrapper(*a, **kw):
level = 'error'
@@ -568,7 +562,6 @@ class ContextType(object):
Examples:
>>> # Default value
>>> context.clear()
>>> context.arch == 'i386'
True
>>> context.arch = 'arm'
@@ -695,7 +688,7 @@ class ContextType(object):
"""
ASLR settings for new processes.
If :const:`False`, attempt to disable ASLR in all processes which are
If ``False``, attempt to disable ASLR in all processes which are
created via ``personality`` (``setarch -R``) and ``setrlimit``
(``ulimit -s unlimited``).
@@ -762,7 +755,7 @@ class ContextType(object):
"""
# Cyclic imports... sorry Idolf.
from pwnlib.elf import ELF
from ..elf import ELF
if not isinstance(binary, ELF):
binary = ELF(binary)
@@ -991,7 +984,7 @@ class ContextType(object):
Can be set to any non-string truthy value, or the specific string
values ``'signed'`` or ``'unsigned'`` which are converted into
:const:`True` and :const:`False` correspondingly.
``True`` and ``False`` correspondingly.
Examples:
@@ -1127,7 +1120,7 @@ class ContextType(object):
self.os = device.os or self.os
elif isinstance(device, str):
device = Device(device)
elif device is not None:
else:
raise AttributeError("device must be either a Device object or a serial number as a string")
return device
@@ -1153,21 +1146,12 @@ class ContextType(object):
return command
@_validator
def buffer_size(self, size):
"""Internal buffer size to use for :class:`pwnlib.tubes.tube.tube` objects.
This is not the maximum size of the buffer, but this is the amount of data
which is passed to each raw ``read`` syscall (or equivalent).
"""
return int(size)
@property
def cache_dir(self):
"""Directory used for caching data.
Note:
May be either a path string, or :const:`None`.
May be either a path string, or ``None``.
Example:
@@ -1201,31 +1185,6 @@ class ContextType(object):
return cache
@_validator
def delete_corefiles(self, v):
"""Whether pwntools automatically deletes corefiles after exiting.
This only affects corefiles accessed via :attr:`.process.corefile`.
Default value is ``False``.
"""
return bool(v)
@_validator
def rename_corefiles(self, v):
"""Whether pwntools automatically renames corefiles.
This is useful for two things:
- Prevent corefiles from being overwritten, if ``kernel.core_pattern``
is something simple like ``"core"``.
- Ensure corefiles are generated, if ``kernel.core_pattern`` uses ``apport``,
which refuses to overwrite any existing files.
This only affects corefiles accessed via :attr:`.process.corefile`.
Default value is ``True``.
"""
return bool(v)
#*************************************************************************
# ALIASES
@@ -1301,14 +1260,11 @@ class ContextType(object):
Thread = Thread
#: Global :class:`.ContextType` object, used to store commonly-used pwntools settings.
#:
#: Global ``context`` object, used to store commonly-used pwntools settings.
#: In most cases, the context is used to infer default variables values.
#: For example, :func:`.asm` can take an ``arch`` parameter as a
#: keyword argument.
#:
#: If it is not supplied, the ``arch`` specified by ``context`` is used instead.
#:
#: For example, :meth:`pwnlib.asm.asm` can take an ``os`` parameter as a
#: keyword argument. If it is not supplied, the ``os`` specified by
#: ``context`` is used instead.
#: Consider it a shorthand to passing ``os=`` and ``arch=`` to every single
#: function call.
context = ContextType()
-10
View File
@@ -1,10 +0,0 @@
ROOT=$(shell git rev-parse --show-toplevel)
TEMPLATE_DIR="$ROOT/pwnlib/shellcraft/templates/common/linux/syscalls"
all: generate.py functions.py
python generate.py "$(ROOT)/pwnlib/shellcraft/templates/common/linux/syscalls"
functions.py:
wget https://raw.githubusercontent.com/zachriggle/functions/master/functions.py
.phony: all
-283
View File
@@ -1,283 +0,0 @@
#!/usr/bin/env python2
import argparse
import os
from pwnlib import constants
from pwnlib.context import context
# github.com/zachriggle/functions
from functions import functions, Function, Argument
ARCHITECTURES = ['i386', 'amd64', 'arm', 'aarch64', 'mips']
HEADER = '''
<%
import collections
import pwnlib.abi
import pwnlib.constants
import pwnlib.shellcraft
%>
'''
DOCSTRING = '''
<%docstring>{name}({arguments_comma_separated}) -> str
Invokes the syscall {name}.
See 'man 2 {name}' for more information.
Arguments:
{arg_docs}
Returns:
{return_type}
</%docstring>
'''
ARGUMENTS = """
<%page args="{arguments_default_values}"/>
"""
CALL = """
<%
abi = pwnlib.abi.ABI.syscall()
stack = abi.stack
regs = abi.register_arguments[1:]
allregs = pwnlib.shellcraft.registers.current()
can_pushstr = {string_arguments!r}
can_pushstr_array = {array_arguments!r}
argument_names = {argument_names!r}
argument_values = [{arguments_comma_separated!s}]
# Load all of the arguments into their destination registers / stack slots.
register_arguments = dict()
stack_arguments = collections.OrderedDict()
string_arguments = dict()
dict_arguments = dict()
array_arguments = dict()
syscall_repr = []
for name, arg in zip(argument_names, argument_values):
if arg is not None:
syscall_repr.append('%s=%r' % (name, arg))
# If the argument itself (input) is a register...
if arg in allregs:
index = argument_names.index(name)
if index < len(regs):
target = regs[index]
register_arguments[target] = arg
elif arg is not None:
stack_arguments[index] = arg
# The argument is not a register. It is a string value, and we
# are expecting a string value
elif name in can_pushstr and isinstance(arg, str):
string_arguments[name] = arg
# The argument is not a register. It is a dictionary, and we are
# expecting K:V paris.
elif name in can_pushstr_array and isinstance(arg, dict):
array_arguments[name] = ['%s=%s' % (k,v) for (k,v) in arg.items()]
# The arguent is not a register. It is a list, and we are expecting
# a list of arguments.
elif name in can_pushstr_array and isinstance(arg, (list, tuple)):
array_arguments[name] = arg
# The argument is not a register, string, dict, or list.
# It could be a constant string ('O_RDONLY') for an integer argument,
# an actual integer value, or a constant.
else:
index = argument_names.index(name)
if index < len(regs):
target = regs[index]
register_arguments[target] = arg
elif arg is not None:
stack_arguments[target] = arg
# Some syscalls have different names on various architectures.
# Determine which syscall number to use for the current architecture.
for syscall in {syscalls!r}:
if hasattr(pwnlib.constants, syscall):
break
else:
raise Exception("Could not locate any syscalls: %r" % syscalls)
%>
/* {name}(${{', '.join(syscall_repr)}}) */
%for name, arg in string_arguments.items():
${{pwnlib.shellcraft.pushstr(arg, append_null=('\\x00' not in arg))}}
${{pwnlib.shellcraft.mov(regs[argument_names.index(name)], abi.stack)}}
%endfor
%for name, arg in array_arguments.items():
${{pwnlib.shellcraft.pushstr_array(regs[argument_names.index(name)], arg)}}
%endfor
%for name, arg in stack_arguments.items():
${{pwnlib.shellcraft.push(arg)}}
%endfor
${{pwnlib.shellcraft.setregs(register_arguments)}}
${{pwnlib.shellcraft.syscall(syscall)}}
"""
def can_be_constant(arg):
if arg.derefcnt == 0:
return True
def can_be_string(arg):
if arg.type == 'char' and arg.derefcnt == 1:
return True
if arg.type == 'void' and arg.derefcnt == 1:
return True
def can_be_array(arg):
if arg.type == 'char' and arg.derefcnt == 2:
return True
if arg.type == 'void' and arg.derefcnt == 2:
return True
def fix_bad_arg_names(func, arg):
if arg.name == 'str':
return 'str_'
if arg.name == 'len':
return 'length'
if arg.name == 'repr':
return 'repr_'
if func.name == 'open' and arg.name == 'vararg':
return 'mode'
return arg.name
def get_arg_default(arg):
return 0
def fix_rt_syscall_name(name):
if name.startswith('rt_'):
return name[3:]
return name
def fix_syscall_names(name):
# Do not use old_mmap
if name == 'SYS_mmap':
return ['SYS_mmap2', name]
# Some arches don't have vanilla sigreturn
if name.endswith('_sigreturn'):
return ['SYS_sigreturn', 'SYS_rt_sigreturn']
return [name]
def main(target):
for arch in ARCHITECTURES:
with context.local(arch=arch):
generate_one(target)
def generate_one(target):
SYSCALL_NAMES = [c for c in dir(constants) if c.startswith('SYS_')]
for syscall in SYSCALL_NAMES:
name = syscall[4:]
# Skip anything with uppercase
if name.lower() != name:
print 'Skipping %s' % name
continue
# Skip anything that starts with 'unused' or 'sys' after stripping
if name.startswith('unused'):
print 'Skipping %s' % name
continue
function = functions.get(name, None)
if name.startswith('rt_'):
name = name[3:]
# If we can't find a function, just stub it out with something
# that has a vararg argument.
if function is None:
print 'Stubbing out %s' % name
args = [Argument('int', 0, 'vararg')]
function = Function('long', 0, name, args)
# Some syscalls have different names on different architectures,
# or are superceded. We try to do the "best" thing at runtime.
syscalls = fix_syscall_names(syscall)
# Set up the argument string for Mako
argument_names = []
argument_defaults = []
#
for arg in function.args:
argname = fix_bad_arg_names(function, arg)
default = get_arg_default(arg)
# Mako is unable to use *vararg and *kwarg, so we just stub in
# a whole bunch of additional arguments.
if argname == 'vararg':
for j in range(5):
argname = 'vararg_%i' % j
argument_names.append(argname)
argument_defaults.append('%s=%s' % (argname, None))
break
argument_names.append(argname)
argument_defaults.append('%s=%s' % (argname, default))
arguments_default_values = ', '.join(argument_defaults)
arguments_comma_separated = ', '.join(argument_names)
string_arguments = []
array_arguments = []
arg_docs = []
for arg in function.args:
if can_be_array(arg):
array_arguments.append(arg.name)
if can_be_string(arg):
string_arguments.append(arg.name)
argname = arg.name
argtype = str(arg.type) + ('*' * arg.derefcnt)
arg_docs.append(
' {argname}({argtype}): {argname}'.format(argname=argname,
argtype=argtype))
return_type = str(function.type) + ('*' * function.derefcnt)
arg_docs = '\n'.join(arg_docs)
template_variables = {
'name': name,
'arg_docs': arg_docs,
'syscalls': syscalls,
'arguments_default_values': arguments_default_values,
'arguments_comma_separated': arguments_comma_separated,
'return_type': return_type,
'string_arguments': string_arguments,
'array_arguments': array_arguments,
'argument_names': argument_names,
}
lines = [
HEADER,
DOCSTRING.format(**template_variables),
ARGUMENTS.format(**template_variables),
CALL.format(**template_variables)
]
with open(os.path.join(target, name + '.asm'), 'wt+') as f:
f.write('\n'.join(map(str.strip, lines)))
if __name__ == '__main__':
p = argparse.ArgumentParser()
p.add_argument('target_directory')
args = p.parse_args()
main(args.target_directory)
@@ -7,7 +7,6 @@ import re
import urllib
from bs4 import BeautifulSoup
from pwn import *
uas = set()
+11 -13
View File
@@ -47,22 +47,20 @@ Example
DynELF
"""
from __future__ import absolute_import
import ctypes
from elftools.elf.enums import ENUM_D_TAG
from pwnlib import elf
from pwnlib import libcdb
from pwnlib.context import context
from pwnlib.elf import ELF
from pwnlib.elf import constants
from pwnlib.log import getLogger
from pwnlib.memleak import MemLeak
from pwnlib.util.fiddling import enhex
from pwnlib.util.packing import unpack
from pwnlib.util.web import wget
from . import elf
from . import libcdb
from .context import context
from .elf import ELF
from .elf import constants
from .log import getLogger
from .memleak import MemLeak
from .util.fiddling import enhex
from .util.packing import unpack
from .util.web import wget
log = getLogger(__name__)
sizeof = ctypes.sizeof
@@ -514,7 +512,7 @@ class DynELF(object):
If set to ``'libc'``, ``'libc.so'`` is assumed.
Returns:
Address of the named symbol, or :const:`None`.
Address of the named symbol, or ``None``.
"""
result = None
+4 -12
View File
@@ -1,14 +1,6 @@
"""
Most exploitable CTF challenges are provided in the Executable and Linkable
Format (``ELF``). Generally, it is very useful to be able to interact with
these files to extract data such as function addresses, ROP gadgets, and
writable page addresses.
"""
from __future__ import absolute_import
from pwnlib.elf.corefile import Core
from pwnlib.elf.datatypes import *
from pwnlib.elf.elf import ELF
from pwnlib.elf.elf import load
from .corefile import Core
from .datatypes import *
from .elf import ELF
from .elf import load
__all__ = ['load', 'ELF', 'Core'] + sorted(filter(lambda x: not x.startswith('_'), datatypes.__dict__.keys()))
-258
View File
@@ -1,258 +0,0 @@
"""
Kernel-specific ELF functionality
"""
class KernelConfig(object):
def __init__(self, name, title, requires=[], excludes=[], minver=0, maxver=99):
#: Name of the configuration option
self.name = name
#: Section to which the configuration point belongs
self.title = title
#: List of configuration items, one of which must be present,
#: for this checker to be used.
self.requires = set(requires)
#: List of configuration items, NONE of which must be present,
#: for this checker to be used.
self.excludes = set(excludes)
#: Kernel version that this check should be enforced on
self.minver = map(int, str(minver).split('.'))
self.maxver = map(int, str(maxver).split('.'))
def relevant(self, config):
# If any of the excluded options are ENABLED,
# this config is not relevant.
if self.excludes:
for value in self.excludes & set(config):
if config.get(value, False):
return False
# If any of the required options are PRESENT but DISABLED,
# this config is not relevant.
if self.requires:
for value in self.requires & set(config):
if config.get(value, False):
return True
# We did not find a required value
return False
# If we are not in the correct version range, bail
if 'version' in config:
version = config['version']
if not (self.minver <= version < self.maxver):
return False
return True
def check(self, value):
raise NotImplementedError()
def __call__(self, config):
"""__call__(config) -> str
Check whether the configuration point is set correctly.
Arguments:
config(dict): Dictionary of all configuration points
Returns:
A tuple (result, message) where result is whether the
option is correctly configured, and message is an optional
message describing the error.
"""
if not self.relevant(config):
return (True, '')
return self.check(config.get(self.name, None))
class Enabled(KernelConfig):
def __init__(self, name, **kw):
super(Enabled, self).__init__(name, 'Not Enabled', **kw)
def check(self, config):
return (bool(config), '')
class EnabledIfPresent(Enabled):
def check(self, config):
if config is None:
return (True, '')
return super(EnabledIfPresent, self).check(config)
class Disabled(KernelConfig):
def __init__(self, name, **kw):
super(Disabled, self).__init__(name, 'Not Disabled', **kw)
def check(self, config):
return (not bool(config), '')
class Minimum(KernelConfig):
def __init__(self, name, minimum, **kw):
super(Minimum, self).__init__(name, 'Misconfigured', **kw)
self.minimum = minimum
def check(self, value):
if value is None or value < self.minimum:
return (False, "Should be at least {}".format(self.minimum))
return (True, '')
kernel_configuration = [
# Enabled
Enabled('STRICT_DEVMEM', requires=['DEVMEM']),
Enabled('IO_STRICT_DEVMEM', requires=['DEVMEM']),
Enabled('SYN_COOKIES'),
Enabled('DEBUG_CREDENTIALS'),
Enabled('DEBUG_NOTIFIERS'),
Enabled('DEBUG_LIST'),
Enabled('SECCOMP'),
Enabled('SECCOMP_FILTER', minver=3.5, requires=['HAVE_ARCH_SECCOMP_FILTER']),
Enabled('SECURITY'),
# Enabled('SECURITY_YAMA'),
Enabled('HARDENED_USERCOPY', minver=4.8),
Enabled('SLAB_FREELIST_RANDOM', minver=4.7),
# Enabled('SLUB_DEBUG'),
# Enabled('PAGE_POISONING'),
# Enabled('PAGE_POISONING_NO_SANITY'),
# Enabled('PAGE_POISONING_ZERO'),
Enabled('CC_STACKPROTECTOR', excludes=['CC_STACKPROTECTOR_STRONG']),
Enabled('CC_STACKPROTECTOR_STRONG', minver=3.14),
Enabled('PANIC_ON_OOPS', minver=3.5),
Enabled('DEBUG_SET_MODULE_RONX', requires=['MODULES']),
Enabled('DEBUG_RODATA'),
Enabled('SECURITY_KPTR_RESTRICT'),
Enabled('SECURITY_PERF_EVENTS_RESTRICT'),
Enabled('SECURITY_DMESG_RESTRICT'),
Enabled('SECURITY_NETWORK'),
Enabled('SECURITY_SELINUX'),
EnabledIfPresent('MSM_KERNEL_PROTECT'),
# Not really security relevant, but come on guys
Enabled('COREDUMP'),
# Disabled
Disabled('ACPI_CUSTOM_METHOD'),
Disabled('COMPAT_BRK'),
Disabled('DEVKMEM'),
Disabled('DEVMEM'),
Disabled('COMPAT_VDSO'),
Disabled('KEXEC'),
Disabled('BINFMT_MISC'),
Disabled('LEGACY_PTYS'),
Disabled('MODULES'),
Disabled('KEXEC'),
Disabled('SECURITY_SELINUX_DISABLE'),
Disabled('MISC_FILESYSTEMS'),
Disabled('SCSI_TGT'),
Disabled('SCSI_CONSTANTS'),
Disabled('SCSI_LOGGING'),
Disabled('SCSI_SCAN_ASYNC'),
Disabled('CONFIG_MEDIA_RADIO_SUPPORT'),
Disabled('CONFIG_PFT'),
Disabled('CONFIG_SYSVIPC'),
# Permits reloading the kernel from disk
Disabled('HIBERNATION'),
# Prior to v4.1, assists heap memory attacks; best to keep interface disabled
Disabled('INET_DIAG', maxver=4.1),
# x86-specific
Enabled('HIGHMEM64G', requires=['X86_32']),
Enabled('X86_PAE', requires=['X86_32']),
Disabled('LEGACY_VSYSCALL_NONE', requires=['X86_32', 'X86_64'], minver=4.4),
Disabled('CONFIG_IA32_EMULATION'),
Disabled('X86_X32'),
Disabled('MODIFY_LDT_SYSCALL'),
Minimum('DEFAULT_MMAP_MIN_ADDR', 65536, requires=['X86_32', 'X86_64']),
Enabled('RANDOMIZE_BASE', excludes=['ARM']),
Enabled('RANDOMIZE_MEMORY', requires=['X86_64']),
# ARM specific
Enabled('VMSPLIT_3G', requires=['ARM']),
Disabled('STRICT_MEMORY_RWX', requires=['ARM', 'ARM64']),
Minimum('DEFAULT_MMAP_MIN_ADDR', 32768, requires=['ARM', 'ARM64']),
Minimum('ARCH_MMAP_RND_BITS', 16, requires=['ARM']),
Minimum('ARCH_MMAP_RND_BITS', 24, requires=['ARM64']),
Minimum('ARCH_MMAP_RND_COMPAT_BITS', 16, requires=['ARM64']),
Enabled('CPU_SW_DOMAIN_PAN', requires=['ARM'], minver=4.3),
Enabled('CONFIG_ARM64_PAN', requires=['ARM64'], minver=4.3),
Disabled('OABI_COMPAT'),
# Only relevant of MODULES are enabled
Enabled('MODULE_SIG', requires=['MODULES']),
Enabled('MODULE_SIG_FORCE', requires=['MODULES']),
Enabled('MODULE_SIG_ALL', requires=['MODULES']),
Enabled('MODULE_SIG_SHA512', requires=['MODULES']),
Enabled('MODULE_SIG_KEY', requires=['MODULES']),
]
def parse_kconfig(data):
"""Parses configuration data from a kernel .config.
Arguments:
data(str): Configuration contents.
Returns:
A :class:`dict` mapping configuration options.
"Not set" is converted into ``None``, ``y`` and ``n`` are converted
into :class:`bool`. Numbers are converted into :class:`int`.
All other values are as-is.
Each key has ``CONFIG_`` stripped from the beginning.
Examples:
>>> parse_kconfig('FOO=3')
{'FOO': 3}
>>> parse_kconfig('FOO=y')
{'FOO': True}
>>> parse_kconfig('FOO=n')
{'FOO': False}
>>> parse_kconfig('FOO=bar')
{'FOO': 'bar'}
>>> parse_kconfig('# FOO is not set')
{'FOO': None}
"""
config = {}
NOT_SET=' is not set'
if not data:
return
for line in data.splitlines():
# Not set? Then set to None.
if NOT_SET in line:
line = line.split(NOT_SET, 1)[0]
name = line.strip('# ')
config[name] = None
# Set to a value? Extract it
if '=' in line:
k, v = line.split('=', 1)
# Boolean conversions
if v == 'y':
v = True
elif v == 'n':
v = False
else:
# Integer conversions
try:
v = int(v, 0)
except ValueError:
pass
config[k] = v
# Strip off all of the CONFIG_ prefixes
config = ({k.replace('CONFIG_', ''): v for k,v in config.items()})
return config
+64 -991
View File
File diff suppressed because it is too large. Load diff
-82
View File
@@ -533,44 +533,6 @@ class user_regs_struct_amd64(ctypes.Structure):
assert ctypes.sizeof(user_regs_struct_amd64) == 0xd8
class user_regs_struct_arm(ctypes.Structure):
_fields_ = [('r%i' % i, ctypes.c_uint32) for i in range(18)]
@property
def cpsr(self):
return self.r16
@property
def pc(self):
return self.r15
@property
def lr(self):
return self.r14
@property
def sp(self):
return self.r13
@property
def ip(self):
return self.r12
@property
def fp(self):
return self.r11
class user_regs_struct_aarch64(ctypes.Structure):
_fields_ = [('x%i' % i, ctypes.c_uint64) for i in range(31)] \
+ [('sp', ctypes.c_uint64),
('pc', ctypes.c_uint64),
('pstate', ctypes.c_uint64)]
@property
def lr(self):
return self.x30
def __getattr__(self, name):
if name.startswith('r'):
name = 'x' + name[1:]
return getattr(name) & 0xffffffff
class elf_prstatus_i386(ctypes.Structure):
_fields_ = generate_prstatus_common(32, user_regs_struct_i386)
@@ -582,53 +544,9 @@ class elf_prstatus_amd64(ctypes.Structure):
assert ctypes.sizeof(elf_prstatus_amd64) == 0x150
class elf_prstatus_arm(ctypes.Structure):
_fields_ = generate_prstatus_common(32, user_regs_struct_arm)
class elf_prstatus_aarch64(ctypes.Structure):
_fields_ = generate_prstatus_common(64, user_regs_struct_aarch64)
class Elf32_auxv_t(ctypes.Structure):
_fields_ = [('a_type', ctypes.c_uint32),
('a_val', ctypes.c_uint32),]
class Elf64_auxv_t(ctypes.Structure):
_fields_ = [('a_type', ctypes.c_uint64),
('a_val', ctypes.c_uint64),]
def generate_prspinfo(long):
return [
('pr_state', byte),
('pr_sname', char),
('pr_zomb', byte),
('pr_nice', byte),
('pr_flag', long),
('pr_uid', ctypes.c_ushort),
('pr_gid', ctypes.c_ushort),
('pr_pid', ctypes.c_int),
('pr_ppid', ctypes.c_int),
('pr_pgrp', ctypes.c_int),
('pr_sid', ctypes.c_int),
('pr_fname', char * 16),
('pr_psargs', char * 80)
]
class elf_prspinfo_32(ctypes.Structure):
_fields_ = generate_prspinfo(Elf32_Addr)
class elf_prspinfo_64(ctypes.Structure):
_fields_ = generate_prspinfo(Elf64_Addr)
def generate_siginfo(int_t, long_t):
class siginfo_t(ctypes.Structure):
_fields_ = [('si_signo', int_t),
('si_errno', int_t),
('si_code', int_t),
('sigfault_addr', long_t),
('sigfault_trapno', int_t)]
return siginfo_t
class elf_siginfo_32(generate_siginfo(ctypes.c_uint32, ctypes.c_uint32)):
pass
class elf_siginfo_64(generate_siginfo(ctypes.c_uint32, ctypes.c_uint64)):
pass
+180 -684
View File
File diff suppressed because it is too large. Load diff
+11 -13
View File
@@ -2,16 +2,14 @@
"""
Encode shellcode to avoid input filtering and impress your friends!
"""
from __future__ import absolute_import
from pwnlib.encoders import amd64
from pwnlib.encoders import arm
from pwnlib.encoders import i386
from pwnlib.encoders import mips
from pwnlib.encoders.encoder import Encoder
from pwnlib.encoders.encoder import alphanumeric
from pwnlib.encoders.encoder import encode
from pwnlib.encoders.encoder import line
from pwnlib.encoders.encoder import null
from pwnlib.encoders.encoder import printable
from pwnlib.encoders.encoder import scramble
from . import amd64
from . import arm
from . import i386
from . import mips
from .encoder import Encoder
from .encoder import alphanumeric
from .encoder import encode
from .encoder import line
from .encoder import null
from .encoder import printable
from .encoder import scramble
+1 -3
View File
@@ -1,3 +1 @@
from __future__ import absolute_import
from pwnlib.encoders.amd64 import delta
from . import delta
+1 -3
View File
@@ -1,6 +1,4 @@
from __future__ import absolute_import
from pwnlib.encoders.i386.delta import i386DeltaEncoder
from ..i386.delta import i386DeltaEncoder
class amd64DeltaEncoder(i386DeltaEncoder):
+2 -4
View File
@@ -1,4 +1,2 @@
from __future__ import absolute_import
from pwnlib.encoders.arm import alphanumeric
from pwnlib.encoders.arm import xor
from . import alphanumeric
from . import xor
+4 -5
View File
@@ -1,13 +1,12 @@
from __future__ import absolute_import
import binascii
import random
import string
import sys
from pwnlib.context import context
from pwnlib.encoders.arm.alphanumeric import builder
from pwnlib.encoders.encoder import Encoder
import builder
from ....context import context
from ...encoder import Encoder
class ArmEncoder(Encoder):
+7 -9
View File
@@ -1,12 +1,10 @@
from __future__ import absolute_import
from pwnlib import shellcraft
from pwnlib.asm import asm
from pwnlib.context import context
from pwnlib.encoders.encoder import Encoder
from pwnlib.util.fiddling import xor_key
from pwnlib.util.lists import group
from pwnlib.util.packing import u8
from ... import shellcraft
from ...asm import asm
from ...context import context
from ...util.fiddling import xor_key
from ...util.lists import group
from ...util.packing import u8
from ..encoder import Encoder
class ArmXorEncoder(Encoder):
+4 -6
View File
@@ -1,15 +1,13 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
import collections
import random
import re
import string
from pwnlib.context import LocalContext
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.util.fiddling import hexdump
from ..context import LocalContext
from ..context import context
from ..log import getLogger
from ..util.fiddling import hexdump
log = getLogger(__name__)
+2 -4
View File
@@ -1,4 +1,2 @@
from __future__ import absolute_import
from pwnlib.encoders.i386 import delta
from pwnlib.encoders.i386 import xor
from . import delta
from . import xor
+5 -7
View File
@@ -1,14 +1,12 @@
from __future__ import absolute_import
import collections
from random import choice
from random import randint
from pwnlib.asm import asm
from pwnlib.asm import disasm
from pwnlib.context import context
from pwnlib.encoders.encoder import Encoder
from pwnlib.util.fiddling import hexdump
from ...asm import asm
from ...asm import disasm
from ...context import context
from ...util.fiddling import hexdump
from ..encoder import Encoder
'''
+6 -7
View File
@@ -10,14 +10,13 @@
#; This program is distributed in the hope that it will be useful,
#; but WITHOUT ANY WARRANTY; without even the implied warranty of
#; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
from __future__ import absolute_import
from pwnlib import shellcraft
from pwnlib.asm import asm
from pwnlib.context import context
from pwnlib.encoders.encoder import Encoder
from pwnlib.util.fiddling import xor_pair
from pwnlib.util.lists import group
from ... import shellcraft
from ...asm import asm
from ...context import context
from ...util.fiddling import xor_pair
from ...util.lists import group
from ..encoder import Encoder
# Note shellcode assumes it's based at ecx
+1 -3
View File
@@ -1,3 +1 @@
from __future__ import absolute_import
from pwnlib.encoders.mips import xor
from . import xor
+5 -6
View File
@@ -23,13 +23,12 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from __future__ import absolute_import
from pwnlib import asm
from pwnlib import shellcraft
from pwnlib.context import context
from pwnlib.encoders.encoder import Encoder
from pwnlib.util.fiddling import xor_key
from ... import shellcraft
from ...asm import asm
from ...context import context
from ...util.fiddling import xor_key
from ..encoder import Encoder
decoders = {
'little': ''.join([
+1 -3
View File
@@ -1,6 +1,4 @@
from __future__ import absolute_import
from pwnlib.flag.flag import submit_flag
from .flag import submit_flag
__all__ = [
'submit_flag',
+3 -5
View File
@@ -1,12 +1,10 @@
"""Describes a way to submit a key to a key server.
"""
from __future__ import absolute_import
import os
from pwnlib.args import args
from pwnlib.log import getLogger
from pwnlib.tubes.remote import remote
from ..args import args
from ..log import getLogger
from ..tubes.remote import remote
env_server = args.get('FLAG_HOST', 'flag-submission-server').strip()
env_port = args.get('FLAG_PORT', '31337').strip()
+1 -1
View File
@@ -40,7 +40,7 @@ Examples:
...
>>> autofmt = FmtStr(exec_fmt)
>>> offset = autofmt.offset
>>> p = process(program, stderr=PIPE)
>>> p = process(program, stderr=subprocess.PIPE)
>>> addr = unpack(p.recv(4))
>>> payload = fmtstr_payload(offset, {addr: 0x1337babe})
>>> p.sendline(payload)
+107 -428
View File
@@ -1,136 +1,33 @@
# -*- coding: utf-8 -*-
"""
During exploit development, it is frequently useful to debug the
target binary under GDB.
Pwntools makes this easy-to-do with a handful of helper routines, designed
to make your exploit-debug-update cycles much faster.
Useful Functions
----------------
- :func:`attach` - Attach to an existing process
- :func:`debug` - Start a new process under a debugger, stopped at the first instruction
- :func:`debug_shellcode` - Build a binary with the provided shellcode, and start it under a debugger
Debugging Tips
--------------
The :func:`attach` and :func:`debug` functions will likely be your bread and
butter for debugging.
Both allow you to provide a script to pass to GDB when it is started, so that
it can automatically set your breakpoints.
Attaching to Processes
~~~~~~~~~~~~~~~~~~~~~~
To attach to an existing process, just use :func:`attach`. It is surprisingly
versatile, and can attach to a :class:`.process` for simple
binaries, or will automatically find the correct process to attach to for a
forking server, if given a :class:`.remote` object.
Spawning New Processes
~~~~~~~~~~~~~~~~~~~~~~
Attaching to processes with :func:`attach` is useful, but the state the process
is in may vary. If you need to attach to a process very early, and debug it from
the very first instruction (or even the start of ``main``), you instead should use
:func:`debug`.
When you use :func:`debug`, the return value is a :class:`.tube` object
that you interact with exactly like normal.
Tips and Troubleshooting
------------------------
``NOPTRACE`` magic argument
~~~~~~~~~~~~~~~~~~~~~~~~~~~
It's quite cumbersom to comment and un-comment lines containing `attach`.
You can cause these lines to be a no-op by running your script with the
``NOPTRACE`` argument appended, or with ``PWNLIB_NOPTRACE=1`` in the environment.
::
$ python exploit.py NOPTRACE
[+] Starting local process '/bin/bash': Done
[!] Skipping debug attach since context.noptrace==True
...
Kernel Yama ptrace_scope
~~~~~~~~~~~~~~~~~~~~~~~~
The Linux kernel v3.4 introduced a security mechanism called ``ptrace_scope``,
which is intended to prevent processes from debugging eachother unless there is
a direct parent-child relationship.
This causes some issues with the normal Pwntools workflow, since the process
heirarchy looks like this:
::
python ---> target
`--> gdb
Note that ``python`` is the parent of ``target``, not ``gdb``.
In order to avoid this being a problem, Pwntools uses the function
``prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY)``. This disables Yama
for any processes launched by Pwntools via :class:`.process` or via
:meth:`.ssh.process`.
Older versions of Pwntools did not perform the ``prctl`` step, and
required that the Yama security feature was disabled systemwide, which
requires ``root`` access.
Member Documentation
===============================
"""
from __future__ import absolute_import
import os
import random
import re
import shlex
import tempfile
import time
from pwnlib import adb
from pwnlib import atexit
from pwnlib import elf
from pwnlib import tubes
from pwnlib.asm import _bfdname
from pwnlib.asm import make_elf
from pwnlib.asm import make_elf_from_assembly
from pwnlib.context import LocalContext
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.qemu import get_qemu_user
from pwnlib.util import misc
from pwnlib.util import proc
from . import adb
from . import atexit
from . import elf
from . import tubes
from .asm import _bfdname
from .asm import make_elf
from .asm import make_elf_from_assembly
from .context import LocalContext
from .context import context
from .log import getLogger
from .qemu import get_qemu_user
from .util import misc
from .util import proc
log = getLogger(__name__)
@LocalContext
def debug_assembly(asm, gdbscript=None, vma=None):
"""debug_assembly(asm, gdbscript=None, vma=None) -> tube
Creates an ELF file, and launches it under a debugger.
def debug_assembly(asm, execute=None, vma=None):
"""
Creates an ELF file, and launches it with GDB.
This is identical to debug_shellcode, except that
any defined symbols are available in GDB, and it
saves you the explicit call to asm().
Arguments:
asm(str): Assembly code to debug
gdbscript(str): Script to run in GDB
vma(int): Base address to load the shellcode at
**kwargs: Override any :obj:`.context` values.
Returns:
:class:`.process`
"""
tmp_elf = make_elf_from_assembly(asm, vma=vma, extract=False)
os.chmod(tmp_elf, 0777)
@@ -142,21 +39,19 @@ def debug_assembly(asm, gdbscript=None, vma=None):
adb.push(tmp_elf, android_path)
tmp_elf = android_path
return debug(tmp_elf, gdbscript=gdbscript, arch=context.arch)
return debug(tmp_elf, execute=execute, arch=context.arch)
@LocalContext
def debug_shellcode(data, gdbscript=None, vma=None):
def debug_shellcode(data, execute=None, vma=None):
"""
Creates an ELF file, and launches it under a debugger.
Creates an ELF file, and launches it with GDB.
Arguments:
data(str): Assembled shellcode bytes
gdbscript(str): Script to run in GDB
vma(int): Base address to load the shellcode at
**kwargs: Override any :obj:`.context` values.
kwargs(dict): Arguments passed to context (e.g. arch='arm')
Returns:
:class:`.process`
A ``process`` tube connected to the shellcode on stdin/stdout/stderr.
"""
if isinstance(data, unicode):
log.error("Shellcode is cannot be unicode. Did you mean debug_assembly?")
@@ -170,7 +65,7 @@ def debug_shellcode(data, gdbscript=None, vma=None):
adb.push(tmp_elf, android_path)
tmp_elf = android_path
return debug(tmp_elf, gdbscript=gdbscript, arch=context.arch)
return debug(tmp_elf, execute=execute, arch=context.arch)
def _gdbserver_args(pid=None, path=None, args=None, which=None):
"""_gdbserver_args(pid=None, path=None) -> list
@@ -268,90 +163,24 @@ def _get_runner(ssh=None):
else: return tubes.process.process
@LocalContext
def debug(args, gdbscript=None, exe=None, ssh=None, env=None, **kwargs):
def debug(args, execute=None, exe=None, ssh=None, env=None, **kwargs):
"""debug(args) -> tube
Launch a GDB server with the specified command line,
and launches GDB to attach to it.
Arguments:
args(list): Arguments to the process, similar to :class:`.process`.
gdbscript(str): GDB script to run.
exe(str): Path to the executable on disk
env(dict): Environment to start the binary in
ssh(:class:`.ssh`): Remote ssh session to use to launch the process.
args: Same args as passed to pwnlib.tubes.process
ssh: Remote ssh session to use to launch the process.
Automatically sets up port forwarding so that gdb runs locally.
Returns:
:class:`.process` or :class:`.ssh_channel`: A tube connected to the target process
Notes:
The debugger is attached automatically, and you can debug everything
from the very beginning. This requires that both ``gdb`` and ``gdbserver``
are installed on your machine.
.. code-block:: python
# Create a new process, and stop it at 'main'
io = gdb.debug('bash', '''
break main
continue
''')
When GDB opens via :func:`debug`, it will initially be stopped on the very first
instruction of the dynamic linker (``ld.so``) for dynamically-linked binaries.
Only the target binary and the linker will be loaded in memory, so you cannot
set breakpoints on shared library routines like ``malloc`` since ``libc.so``
has not even been loaded yet.
There are several ways to handle this:
1. Set a breakpoint on the executable's entry point (generally, ``_start``)
- This is only invoked after all of the required shared libraries
are loaded.
- You can generally get the address via the GDB command ``info file``.
2. Use pending breakpoints via ``set breakpoint pending on``
- This has the side-effect of setting breakpoints for **every** function
which matches the name. For ``malloc``, this will generally set a
breakpoint in the executable's PLT, in the linker's internal ``malloc``,
and eventaully in ``libc``'s malloc.
3. Wait for libraries to be loaded with ``set stop-on-solib-event 1``
- There is no way to stop on any specific library being loaded, and sometimes
multiple libraries are loaded and only a single breakpoint is issued.
- Generally, you just add a few ``continue`` commands until things are set up
the way you want it to be.
.. code-block:: python
# Create a new process, and stop it at 'main'
io = gdb.debug('bash', '''
# Wait until we hit the main executable's entry point
break _start
continue
# Now set breakpoint on shared library routines
break malloc
break free
continue
''')
You can use :func:`debug` to spawn new processes on remote machines as well,
by using the ``ssh=`` keyword to pass in your :class:`.ssh` instance.
.. code-block:: python
# Connect to the SSH server
shell = ssh('passcode', 'pwnable.kr', 2222, password='guest')
# Start a process on the server
bash = gdb.debug(['bash'],
ssh=shell,
gdbscript='''
break main
continue
''')
A tube connected to the target process
"""
if context.noptrace:
log.warn_once("Skipping debugger since context.noptrace==True")
return tubes.process.process(args, executable=exe, env=env)
if isinstance(args, (int, tubes.process.process, tubes.ssh.ssh_channel)):
log.error("Use gdb.attach() to debug a running process")
@@ -366,10 +195,6 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, **kwargs):
runner = _get_runner(ssh)
which = _get_which(ssh)
if context.noptrace:
log.warn_once("Skipping debugger since context.noptrace==True")
return runner(args, executable=exe, env=env)
if ssh or context.native or (context.os == 'android'):
args = _gdbserver_args(args=args, which=which)
else:
@@ -401,7 +226,7 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, **kwargs):
if not ssh and context.os == 'android':
host = context.adb_host
attach((host, port), exe=exe, gdbscript=gdbscript, need_ptrace_scope = False, ssh=ssh)
attach((host, port), exe=exe, execute=execute, need_ptrace_scope = False)
# gdbserver outputs a message when a client connects
garbage = gdbserver.recvline(timeout=1)
@@ -420,146 +245,75 @@ def get_gdb_arch():
'thumb': 'arm'
}.get(context.arch, context.arch)
def binary():
"""binary() -> str
Returns:
str: Path to the appropriate ``gdb`` binary to use.
"""
gdb = misc.which('gdb')
if not context.native:
multiarch = misc.which('gdb-multiarch')
if multiarch:
return multiarch
log.warn_once('Cross-architecture debugging usually requires gdb-multiarch\n' \
'$ apt-get install gdb-multiarch')
if not gdb:
log.error('GDB is not installed\n'
'$ apt-get install gdb')
return gdb
@LocalContext
def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_args = None, ssh = None):
"""attach(target, gdbscript = None, exe = None, arch = None, ssh = None) -> None
def attach(target, execute = None, exe = None, need_ptrace_scope = True):
"""attach(target, execute = None, exe = None, arch = None) -> None
Start GDB in a new terminal and attach to `target`.
:func:`pwnlib.util.proc.pidof` is used to find the PID of `target` except
when `target` is a ``(host, port)``-pair. In that case `target` is assumed
to be a GDB server.
If it is running locally and `exe` is not given we will try to find the path
of the target binary from parsing the command line of the program running
the GDB server (e.g. qemu or gdbserver). Notice that if the PID is known
(when `target` is not a GDB server) `exe` will be read from
``/proc/<pid>/exe``.
If `gdb-multiarch` is installed we use that or 'gdb' otherwise.
Arguments:
target: The target to attach to.
gdbscript(:obj:`str` or :obj:`file`): GDB script to run after attaching.
exe(str): The path of the target binary.
arch(str): Architechture of the target binary. If `exe` known GDB will
detect the architechture automatically (if it is supported).
gdb_args(list): List of additional arguments to pass to GDB.
target: The target to attach to.
execute (str or file): GDB script to run after attaching.
exe (str): The path of the target binary.
arch (str): Architechture of the target binary. If `exe` known GDB will
detect the architechture automatically (if it is supported).
Returns:
PID of the GDB process (or the window which it is running in).
Notes:
The ``target`` argument is very robust, and can be any of the following:
:obj:`int`
PID of a process
:obj:`str`
Process name. The youngest process is selected.
:obj:`tuple`
Host, port pair of a listening ``gdbserver``
:class:`.process`
Process to connect to
:class:`.sock`
Connected socket. The executable on the other end of the connection is attached to.
Can be any socket type, including :class:`.listen` or :class:`.remote`.
:class:`.ssh_channel`
Remote process spawned via :meth:`.ssh.process`.
This will use the GDB installed on the remote machine.
If a password is required to connect, the ``sshpass`` program must be installed.
.. code-block:: python
# Attach directly to pid 1234
gdb.attach(1234)
.. code-block:: python
# Attach to the youngest "bash" process
gdb.attach('bash')
.. code-block:: python
# Start a process
bash = process('bash')
# Attach the debugger
gdb.attach(bash, '''
set follow-fork-mode child
break execve
continue
''')
# Interact with the process
bash.sendline('whoami')
.. code-block:: python
# Start a forking server
server = process(['socat', 'tcp-listen:1234,fork,reuseaddr', 'exec:/bin/sh'])
# Connect to the server
io = remote('localhost', 1234)
# Connect the debugger to the server-spawned process
gdb.attach(io, '''
break exit
continue
''')
# Talk to the spawned 'sh'
io.sendline('exit')
.. code-block:: python
# Connect to the SSH server
shell = ssh('bandit0', 'bandit.labs.overthewire.org', password='bandit0')
# Start a process on the server
cat = shell.process(['cat'])
# Attach a debugger to it
gdb.attach(cat, '''
break exit
continue
''')
# Cause `cat` to exit
cat.close()
"""
:const:`None`
"""
if context.noptrace:
log.warn_once("Skipping debug attach since context.noptrace==True")
return
# if gdbscript is a file object, then read it; we probably need to run some
# if execute is a file object, then read it; we probably need to run some
# more gdb script anyway
if isinstance(gdbscript, file):
with gdbscript:
gdbscript = gdbscript.read()
if execute:
if isinstance(execute, file):
fd = execute
execute = fd.read()
fd.close()
# enable gdb.attach(p, 'continue')
if gdbscript and not gdbscript.endswith('\n'):
gdbscript += '\n'
if execute and not execute.endswith('\n'):
execute += '\n'
# gdb script to run before `gdbscript`
# gdb script to run before `execute`
pre = ''
if not context.native:
if not misc.which('gdb-multiarch'):
log.warn_once('Cross-architecture debugging usually requires gdb-multiarch\n' \
'$ apt-get install gdb-multiarch')
pre += 'set endian %s\n' % context.endian
pre += 'set architecture %s\n' % get_gdb_arch()
if context.os == 'android':
pre += 'set gnutarget ' + _bfdname() + '\n'
else:
# If ptrace_scope is set and we're not root, we cannot attach to a
# running process.
# We assume that we do not need this to be set if we are debugging on
# a different architecture (e.g. under qemu-user).
try:
ptrace_scope = open('/proc/sys/kernel/yama/ptrace_scope').read().strip()
if need_ptrace_scope and ptrace_scope != '0' and os.geteuid() != 0:
msg = 'Disable ptrace_scope to attach to running processes.\n'
msg += 'More info: https://askubuntu.com/q/41629'
log.warning(msg)
return
except IOError:
pass
# let's see if we can find a pid to attach to
pid = None
@@ -586,13 +340,11 @@ def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_a
shell = target.parent
tmpfile = shell.mktemp()
gdbscript = 'shell rm %s\n%s' % (tmpfile, gdbscript)
shell.upload_data(gdbscript or '', tmpfile)
execute = 'shell rm %s\n%s' % (tmpfile, execute)
shell.upload_data(execute or '', tmpfile)
cmd = ['ssh', '-C', '-t', '-p', str(shell.port), '-l', shell.user, shell.host]
if shell.password:
if not misc.which('sshpass'):
log.error("sshpass must be installed to debug ssh processes")
cmd = ['sshpass', '-p', shell.password] + cmd
if shell.keyfile:
cmd += ['-i', shell.keyfile]
@@ -625,8 +377,6 @@ def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_a
return os.path.join(proc.cwd(spid), exe)
exe = exe or findexe()
elif isinstance(target, elf.corefile.Corefile):
pre += 'target core %s\n' % target.path
else:
log.error("don't know how to attach to target: %r" % target)
@@ -640,20 +390,19 @@ def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_a
if not pid and not exe:
log.error('could not find target process')
cmd = binary()
if gdb_args:
cmd += ' '
cmd += ' '.join(gdb_args)
cmd = None
for p in ('gdb-multiarch', 'gdb'):
if misc.which(p):
cmd = p
break
else:
log.error('no gdb installed')
cmd += ' -q '
if exe and context.native:
if ssh:
ssh.download_file(exe)
exe = os.path.basename(exe)
if not os.path.isfile(exe):
log.error('No such file: %s' % exe)
log.error('no such file: %s' % exe)
cmd += ' "%s"' % exe
if pid and not context.os == 'android':
@@ -668,40 +417,38 @@ def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_a
host = context.adb_host
pre += 'target remote %s:%i' % (context.adb_host, port)
gdbscript = pre + (gdbscript or '')
execute = pre + (execute or '')
if gdbscript:
if execute:
tmp = tempfile.NamedTemporaryFile(prefix = 'pwn', suffix = '.gdb',
delete = False)
log.debug('Wrote gdb script to %r\n%s' % (tmp.name, gdbscript))
gdbscript = 'shell rm %s\n%s' % (tmp.name, gdbscript)
log.debug('Wrote gdb script to %r\n%s' % (tmp.name, execute))
execute = 'shell rm %s\n%s' % (tmp.name, execute)
tmp.write(gdbscript)
tmp.write(execute)
tmp.close()
cmd += ' -x "%s"' % (tmp.name)
log.info('running in new terminal: %s' % cmd)
gdb_pid = misc.run_in_new_terminal(cmd)
misc.run_in_new_terminal(cmd)
if pid and context.native:
proc.wait_for_debugger(pid)
return pid
return gdb_pid
def ssh_gdb(ssh, argv, gdbscript = None, arch = None, **kwargs):
if not isinstance(argv, (list, tuple)):
argv = [argv]
exe = argv[0]
argv = ["gdbserver", "127.0.0.1:0"] + argv
def ssh_gdb(ssh, process, execute = None, arch = None, **kwargs):
if isinstance(process, (list, tuple)):
exe = process[0]
process = ["gdbserver", "127.0.0.1:0"] + process
else:
exe = process
process = "gdbserver 127.0.0.1:0 " + process
# Download the executable
local_exe = os.path.basename(exe)
ssh.download_file(ssh.which(exe), local_exe)
ssh.download_file(exe, local_exe)
# Run the process
c = ssh.process(argv, **kwargs)
c = ssh.run(process, **kwargs)
# Find the port for the gdb server
c.recvuntil('port ')
@@ -713,7 +460,7 @@ def ssh_gdb(ssh, argv, gdbscript = None, arch = None, **kwargs):
l = tubes.listen.listen(0)
forwardport = l.lport
attach(('127.0.0.1', forwardport), gdbscript, local_exe, arch, ssh=ssh)
attach(('127.0.0.1', forwardport), execute, local_exe, arch)
l.wait_for_connection() <> ssh.connect_remote('127.0.0.1', gdbport)
return c
@@ -754,8 +501,8 @@ def find_module_addresses(binary, ssh=None, ulimit=False):
Arguments:
binary(str): Path to the binary on the remote server
ssh(pwnlib.tubes.tube): SSH connection through which to load the libraries.
If left as :const:`None`, will use a :class:`pwnlib.tubes.process.process`.
ulimit(bool): Set to :const:`True` to run "ulimit -s unlimited" before GDB.
If left as ``None``, will use a ``pwnlib.tubes.process.process``.
ulimit(bool): Set to ``True`` to run "ulimit -s unlimited" before GDB.
Returns:
A list of pwnlib.elf.ELF objects, with correct base addresses.
@@ -842,71 +589,3 @@ def find_module_addresses(binary, ssh=None, ulimit=False):
rv.append(lib)
return rv
def corefile(process):
r"""Drops a core file for the process.
Arguments:
process: Process to dump
Returns:
:class:`.Core`: The generated core file
"""
if context.noptrace:
log.warn_once("Skipping corefile since context.noptrace==True")
return
corefile_path = './core.%s.%i' % (os.path.basename(process.executable),
process.pid)
# Due to https://sourceware.org/bugzilla/show_bug.cgi?id=16092
# will disregard coredump_filter, and will not dump private mappings.
if version() < (7,11):
log.warn_once('The installed GDB (%s) does not emit core-dumps which '
'contain all of the data in the process.\n'
'Upgrade to GDB >= 7.11 for better core-dumps.' % binary())
# This is effectively the same as what the 'gcore' binary does
gdb_args = ['-batch',
'-q',
'--nx',
'-ex', '"set pagination off"',
'-ex', '"set height 0"',
'-ex', '"set width 0"',
'-ex', '"set use-coredump-filter on"',
'-ex', '"generate-core-file %s"' % corefile_path,
'-ex', 'detach']
with context.local(terminal = ['sh', '-c']):
with context.quiet:
pid = attach(process, gdb_args=gdb_args)
os.waitpid(pid, 0)
return elf.corefile.Core(corefile_path)
def version(program='gdb'):
"""Gets the current GDB version.
Note:
Requires that GDB version meets the following format:
``GNU gdb (GDB) 7.12``
Returns:
tuple: A tuple containing the version numbers
Example:
>>> (7,0) <= gdb.version() <= (8,0)
True
"""
program = misc.which(program)
expr = r'([0-9]+\.?)+'
with tubes.process.process([program, '--version'], level='error') as gdb:
version = gdb.recvline()
versions = re.search(expr, version).group()
return tuple(map(int, versions.split('.')))
+10 -12
View File
@@ -1,23 +1,21 @@
"""
Fetch a LIBC binary based on some heuristics.
"""
from __future__ import absolute_import
import codecs
import json
import os
import tempfile
import urlparse
from pwnlib.context import context
from pwnlib.elf import ELF
from pwnlib.log import getLogger
from pwnlib.util.fiddling import b64d
from pwnlib.util.fiddling import hexdump
from pwnlib.util.misc import read
from pwnlib.util.misc import write
from pwnlib.util.safeeval import const
from pwnlib.util.web import wget
from .context import context
from .elf import ELF
from .log import getLogger
from .util.fiddling import b64d
from .util.fiddling import hexdump
from .util.misc import read
from .util.misc import write
from .util.safeeval import const
from .util.web import wget
log = getLogger(__name__)
@@ -35,7 +33,7 @@ def search_by_build_id(hex_encoded_id):
Hex-encoded Build ID (e.g. 'ABCDEF...') of the library
Returns:
Path to the downloaded library on disk, or :const:`None`.
Path to the downloaded library on disk, or ``None``.
"""
cache = cache_dir + '-libc.so.' + hex_encoded_id
+11 -14
View File
@@ -40,7 +40,7 @@ Pwnlib Developers
-----------------
A module-specific logger can be imported into the module via::
from pwnlib.log import getLogger
from .log import getLogger
log = getLogger(__name__)
This provides an easy way to filter logging programmatically
@@ -91,7 +91,10 @@ unless :data:`context.log_level` is also set to ``'DEBUG'``. Other handlers
will however see the extra log records generated by the ``'pwnlib.tubes.ssh'``
logger.
"""
from __future__ import absolute_import
__all__ = [
'getLogger', 'install_default_handler', 'rootlogger'
]
import ConfigParser
import logging
@@ -102,18 +105,12 @@ import sys
import threading
import time
from pwnlib import term
from pwnlib.context import Thread
from pwnlib.context import context
from pwnlib.exception import PwnlibException
from pwnlib.term import spinners
from pwnlib.term import text
__all__ = [
'getLogger', 'install_default_handler', 'rootlogger'
]
from . import term
from .context import Thread
from .context import context
from .exception import PwnlibException
from .term import spinners
from .term import text
# list of prefixes to use for the different message types. note that the `text`
# module won't add any escape codes if `pwnlib.context.log_console.isatty()` is `False`
+41 -65
View File
@@ -1,13 +1,11 @@
from __future__ import absolute_import
import ctypes
import functools
import string
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.util.packing import pack
from pwnlib.util.packing import unpack
from .context import context
from .log import getLogger
from .util.packing import pack
from .util.packing import unpack
log = getLogger(__name__)
@@ -33,52 +31,46 @@ class MemLeak(object):
Example:
>>> import pwnlib
>>> binsh = pwnlib.util.misc.read('/bin/sh')
>>> @pwnlib.memleak.MemLeak
... def leaker(addr):
... print "leaking 0x%x" % addr
... return binsh[addr:addr+4]
>>> leaker.s(0)[:4]
leaking 0x0
leaking 0x4
'\\x7fELF'
>>> leaker[:4]
'\\x7fELF'
>>> hex(leaker.d(0))
'0x464c457f'
>>> hex(leaker.clearb(1))
'0x45'
>>> hex(leaker.d(0))
leaking 0x1
'0x464c457f'
>>> @pwnlib.memleak.MemLeak
... def leaker_nonulls(addr):
... print "leaking 0x%x" % addr
... if addr & 0xff == 0:
... return None
... return binsh[addr:addr+4]
>>> leaker_nonulls.d(0) == None
leaking 0x0
True
>>> leaker_nonulls[0x100:0x104] == binsh[0x100:0x104]
leaking 0x100
leaking 0xff
leaking 0x103
True
.. doctest:: leaker
>>> memory = {-4+i: c for i,c in enumerate('wxyzABCDE')}
>>> def relative_leak(index):
... return memory.get(index, None)
>>> leak = pwnlib.memleak.MemLeak(relative_leak, relative = True)
>>> leak[-1:2]
'zAB'
>>> import pwnlib
>>> binsh = pwnlib.util.misc.read('/bin/sh')
>>> @pwnlib.memleak.MemLeak
... def leaker(addr):
... print "leaking 0x%x" % addr
... return binsh[addr:addr+4]
>>> leaker.s(0)[:4]
leaking 0x0
leaking 0x4
'\\x7fELF'
>>> leaker[:4]
'\\x7fELF'
>>> hex(leaker.d(0))
'0x464c457f'
>>> hex(leaker.clearb(1))
'0x45'
>>> hex(leaker.d(0))
leaking 0x1
'0x464c457f'
>>> @pwnlib.memleak.MemLeak
... def leaker_nonulls(addr):
... print "leaking 0x%x" % addr
... if addr & 0xff == 0:
... return None
... return binsh[addr:addr+4]
>>> leaker_nonulls.d(0) == None
leaking 0x0
True
>>> leaker_nonulls[0x100:0x104] == binsh[0x100:0x104]
leaking 0x100
leaking 0xff
leaking 0x103
True
"""
def __init__(self, f, search_range = 20, reraise = True, relative = False):
def __init__(self, f, search_range = 20, reraise = True):
self.leak = f
self.search_range = search_range
self.reraise = reraise
self.relative = relative
# Map of address: byte for all bytes received
self.cache = {}
@@ -172,9 +164,9 @@ class MemLeak(object):
Leak ``n`` consecutive bytes starting at ``addr``.
Returns:
A string of length ``n``, or :const:`None`.
A string of length ``n``, or ``None``.
"""
if not self.relative and addr < 0:
if addr < 0:
return None
addresses = [addr+i for i in xrange(n)]
@@ -614,19 +606,3 @@ class MemLeak(object):
return result
return MemLeak(string_wrapper)
# Aliases for convenience
u64 = q
u32 = d
u16 = w
u8 = b
p64 = setq
p32 = setd
p16 = setw
p8 = setb
class RelativeMemLeak(MemLeak):
def __init__(self, *a, **kw):
kw.setdefault('relative', True)
super(RelativeMemLeak, self).__init__(*a, **kw)
View File
Whitespace-only changes.
-578
View File
@@ -1,578 +0,0 @@
"""
Implementation of the Android Debug Bridge (ADB) protocol.
Documentation is available here_.
.. _here: https://android.googlesource.com/platform/system/core/+/master/adb/protocol.txt
"""
from __future__ import absolute_import
import logging
import functools
import stat
import time
from pwnlib.context import context
from pwnlib.log import Logger
from pwnlib.log import getLogger
from pwnlib.tubes.listen import listen
from pwnlib.tubes.process import process
from pwnlib.tubes.remote import remote
from pwnlib.util.lists import group
from pwnlib.util.misc import size
from pwnlib.util.packing import p32
from pwnlib.util.proc import pidof
from pwnlib.util.sh_string import sh_string
log = getLogger(__name__)
def pack(val):
return '%04x' % val
def unpack(val):
return int(val, 16)
OKAY = "OKAY"
FAIL = "FAIL"
class Message(object):
"""An ADB hex-length-prefixed message"""
def __init__(self, string):
self.string = string
def __str__(self):
return ('%04x' % len(self.string)) + self.string
def __flat__(self):
return str(self)
class Connection(remote):
"""Connection to the ADB server"""
def __init__(self, host, port, level=None, *a, **kw):
super(Connection, self).__init__(host, port, level=level, *a, **kw)
self._executable = None
self._argv = None
self._pid = None
self._cwd = None
self._env = None
def close(self):
with context.quiet:
super(Connection, self).close()
def adb_send(self, message):
self.send(str(Message(message)))
return self.recvn(4)
def adb_unpack(self):
return unpack(self.recvn(4))
def flat32(self, *a, **kw):
kw.setdefault('word_size', 32)
return super(Connection, self).flat(*a, **kw)
class Process(Connection):
"""Duck-typed ``tubes.remote`` object to add properties of a ``tubes.process``"""
class AdbClient(Logger):
"""ADB Client"""
def __init__(self, level=None):
super(AdbClient, self).__init__()
if level is not None:
self.setLevel(level)
self.host = context.adb_host
self.port = context.adb_port
self._c = None
@property
def c(self):
"""AdbClient's connection to the ADB server"""
if not self._c:
try:
# Squelch the first '[ERROR] Could not connect to localhost on port 5037'
level = self.level
with context.quiet:
if not self.isEnabledFor(logging.INFO):
level = logging.FATAL
self._c = Connection(self.host, self.port, level=level)
except Exception:
# If the connection fails, try starting a server on that port
# as long as it's the *default* port.
if self.host == context.defaults['adb_host'] \
and self.port == context.defaults['adb_port']:
log.warn("Could not connect to ADB server, trying to start it")
process(context.adb + ['start-server']).recvall()
else:
log.exception('Could not connect to ADB server (%s:%s)' % \
(self.host, self.port))
# Final attempt...
if not self._c:
self._c = Connection(self.host, self.port, level=self.level)
return self._c
def _autoclose(fn):
"""Decorator which automatically closes the connection to the ADB server
after calling the decorated function."""
@functools.wraps(fn)
def wrapper(self, *a, **kw):
rv = fn(self, *a, **kw)
if self._c:
self._c.close()
self._c = None
return rv
return wrapper
def _with_transport(fn):
"""Decorator which automatically selects a device transport before calling
the decorated function, and closes the connection afterward."""
@functools.wraps(fn)
def wrapper(self, *a, **kw):
self.transport()
rv = fn(self, *a, **kw)
if self._c:
self._c.close()
self._c = None
return rv
return wrapper
def send(self, *a, **kw):
"""Sends data to the ADB server"""
return self.c.adb_send(*a, **kw)
def unpack(self, *a, **kw):
"""Receives a hex-ascii packed integer from the ADB server"""
return self.c.adb_unpack(*a, **kw)
def recvl(self):
"""Receives a length-prefixed data buffer from the ADB server"""
length = self.c.adb_unpack()
return self.c.recvn(length)
@_autoclose
def kill(self):
"""Kills the remote ADB server"
>>> c=pwnlib.protocols.adb.AdbClient()
>>> c.kill()
The server is automatically re-started on the next request,
if the default host/port are used.
>>> c.version() > (4,0)
True
"""
try:
self.send('host:kill')
except EOFError:
pass
@_autoclose
def version(self):
"""
Returns:
Tuple containing the ``(major, minor)`` version from the ADB server
Example:
>>> pwnlib.protocols.adb.AdbClient().version() # doctest: +SKIP
(4, 36)
"""
response = self.send('host:version')
if response == OKAY:
return (self.c.adb_unpack(), self.c.adb_unpack())
self.error("Could not fetch version")
@_autoclose
def devices(self, long=False):
"""
Arguments:
long(bool): If :const:`True`, fetch the long-format listing.
Returns:
String representation of all available devices.
"""
msg = 'host:devices'
if long:
msg += '-l'
response = self.send(msg)
if response == 'OKAY':
return self.recvl()
self.error("Could not enumerate devices")
@_autoclose
def track_devices(self):
"""
Returns:
Generator which returns a short-format listing of available
devices each time a device state changes.
"""
self.send('host:track-devices')
while True:
yield self.recvl()
def transport(self, serial=None):
"""Sets the Transport on the rmeote device.
Examples:
>>> pwnlib.protocols.adb.AdbClient().transport()
"""
# If no serial was explicitly provided, try the current device
if not serial and context.device:
serial = context.device
# Select the appropriate (or any) device
if serial:
# Extract the serial, str(Device) --> serial
serial = str(serial)
msg = 'host:transport:%s' % serial
else:
msg = 'host:transport-any'
if self.send(msg) == FAIL:
if serial:
self.error("Could not set transport to %r" % serial)
else:
self.error("Could not set transport 'any'")
@_autoclose
@_with_transport
def execute(self, argv):
r"""Executes a program on the device.
Returns:
A :class:`pwnlib.tubes.tube.tube` which is connected to the process.
Examples:
>>> pwnlib.protocols.adb.AdbClient().execute(['echo','hello']).recvall()
'hello\n'
"""
self.transport(context.device)
if isinstance(argv, str):
argv = [argv]
argv = list(map(sh_string, argv))
cmd = 'exec:%s' % (' '.join(argv))
if OKAY == self.send(cmd):
rv = self._c
self._c = None
return rv
def _basic_wrapper(string):
@_autoclose
@_with_transport
def wrapper(self):
self.send(string)
return self.c.recvall()
return wrapper
@_autoclose
@_with_transport
def remount(self):
self.send('remount:')
return self.c.recvall()
@_autoclose
@_with_transport
def root(self):
self.send('root:')
return self.c.recvall()
@_autoclose
@_with_transport
def unroot(self):
self.send('unroot:')
return self.c.recvall()
@_autoclose
@_with_transport
def disable_verity(self):
self.send('disable-verity:')
return self.c.recvall()
@_autoclose
@_with_transport
def enable_verity(self):
self.send('enable-verity:')
return self.c.recvall()
@_autoclose
@_with_transport
def reconnect(self):
self.send('reconnect:')
return self.c.recvall()
@_autoclose
@_with_transport
def reboot(self):
self.send('reboot:')
return self.c.recvall()
@_autoclose
@_with_transport
def reboot_bootloader(self):
self.send('reboot:bootloader')
return self.c.recvall()
@_autoclose
def wait_for_device(self, serial=''):
response = self.send('host-serial:%s:wait-for-any-device' % serial)
# The first OKAY is that the command was understood
if response != 'OKAY':
self.error("An error occurred while waiting for device with serial %r" % serial)
# The second OKAY is that the device is available
response = self.c.recvn(4)
if response != 'OKAY':
self.error("An error occurred while waiting for device with serial %r" % serial)
def _sync(fn):
"""Decorator which enters 'sync:' mode to the selected transport,
then invokes the decorated funciton."""
@functools.wraps(fn)
def wrapper(self, *a, **kw):
rv = None
if FAIL != self.send('sync:'):
rv = fn(self, *a, **kw)
return rv
return wrapper
def list(self, path):
"""Execute the ``LIST`` command of the ``SYNC`` API.
Arguments:
path(str): Path of the directory to list.
Return:
A dictionary, where the keys are relative filenames,
and the values are a dictionary containing the same
values as ``stat()`` supplies.
Note:
In recent releases of Android (e.g. 7.0), the domain that
adbd executes from does not have access to everything that
the shell user does.
Because of this, while the shell user can get listings of
e.g. the root directory ('/'), adbd cannot.
The SYNC APIs are executed within the adbd context, not the
shell user context.
This issue is not a problem if the phone is rooted via
'adb root', since adbd then runs in the ``su`` domain.
Examples:
>>> pprint(AdbClient().list('/data/user'))
{'0': {'mode': 41471, 'size': 11, 'time': ...}}
>>> AdbClient().list('/does/not/exist')
Traceback (most recent call last):
...
PwnlibException: Cannot list directory '/does/not/exist': Does not exist
"""
st = self.stat(path)
if not st:
log.error("Cannot list directory %r: Does not exist" % path)
if not stat.S_ISDIR(st['mode']):
log.error("Cannot list directory %r: Path is not a directory" % path)
return self._list(path)
@_with_transport
@_sync
def _list(self, path):
self.c.flat32('LIST', len(path), path)
files = {}
while True:
response = self.c.recvn(4)
if response == 'DONE':
break
if response != 'DENT':
self.error('Unexpected response: %r' % response)
mode = self.c.u32()
size = self.c.u32()
time = self.c.u32()
name = self.c.recvn(self.c.u32())
# Ignore the current directory and parent
if name in ('', '.', '..'):
continue
files[name] = {'mode': mode,
'size': size,
'time': time}
return files
@_with_transport
@_sync
def stat(self, path):
"""Execute the ``STAT`` command of the ``SYNC`` API.
Arguments:
path(str): Path to the file to stat.
Return:
On success, a dictionary mapping the values returned.
If the file cannot be ``stat()``ed, None is returned.
Example:
>>> expected = {'mode': 16749, 'size': 0, 'time': 0}
>>> pwnlib.protocols.adb.AdbClient().stat('/proc') == expected
True
>>> pwnlib.protocols.adb.AdbClient().stat('/does/not/exist') == None
True
"""
self.c.flat32('STAT', len(path), path)
if self.c.recvn(4) != 'STAT':
self.error("An error occured while attempting to STAT a file.")
mode = self.c.u32()
size = self.c.u32()
time = self.c.u32()
if (mode,size,time) == (0,0,0):
return None
return {'mode': mode, 'size': size, 'time': time}
def write(self, path, data, mode=0o755, timestamp=None, callback=None):
"""Execute the ``WRITE`` command of the ``SYNC`` API.
Arguments:
path(str): Path to the file to write
data(str): Data to write to the file
mode(int): File mode to set (e.g. ``0o755``)
timestamp(int): Unix timestamp to set the file date to
callback(callable): Callback function invoked as data
is written. Arguments provided are:
- File path
- All data
- Expected size of all data
- Current chunk
- Expected size of chunk
"""
# We must ensure that 'path' is not a directory
# Writing to a directory is supported, but creates a temporary file
st = self.stat(path)
if st and stat.S_ISDIR(st['mode']):
log.error("Cannot write to %r: Path is a directory" % path)
return self._write(path, data, mode=0o755, timestamp=None, callback=None)
@_with_transport
@_sync
def _write(self, path, data, mode=0o755, timestamp=None, callback=None):
path += ',' + str(mode)
self.c.flat32('SEND', len(path), path)
sent = 0
# Data needs to be broken up into chunks!
for chunk in group(0x10000, data):
self.c.flat32('DATA', len(chunk), chunk)
if callback:
callback(path, data[:sent], len(data), chunk, len(chunk))
sent += len(chunk)
# Send completion notification and timestamp
if timestamp is None:
timestamp = int(time.time())
self.c.flat32('DONE', timestamp)
result = self.c.recvn(4)
if result != 'OKAY':
log.error("Sync write failed: %r (expected OKAY)" % result)
return
@_with_transport
@_sync
def read(self, path, filesize=0, callback=lambda *a: True):
"""Execute the ``READ`` command of the ``SYNC`` API.
Arguments:
path(str): Path to the file to read
filesize(int): Size of the file, in bytes. Optional.
callback(callable): Callback function invoked as data
becomes available. Arguments provided are:
- File path
- All data
- Expected size of all data
- Current chunk
- Expected size of chunk
Return:
The data received as a string.
"""
self.c.send('RECV' + p32(len(path)) + path)
# Accumulate all data here
all_data = ''
while True:
magic = self.c.recvn(4)
# adbd says there is no more data to send
if magic == 'DONE':
break
if magic == 'FAIL':
self.error('Could not read file %r: Got FAIL.' % path)
# did we expect to be done?
if magic != 'DATA':
self.error('Error after file read: %r (expected DATA)' % magic)
# receive all of the data in the chunk
chunk_size = self.c.u32()
chunk_data = ''
while len(chunk_data) != chunk_size:
chunk_data += self.c.recv(chunk_size - len(chunk_data))
if callback:
callback(path,
all_data,
filesize,
chunk_data,
chunk_size)
# add the chunk onto what we have
all_data += chunk_data
zero = self.c.u32()
if zero != 0:
self.error('Error after file read: %r (expected ZERO)' % zero)
return all_data
@_with_transport
def forward(self, device, host_proto, host_port, device_proto, device_port):
self.send('host:forward:%s:%s;%s:%s' % (host_proto, host_port, device_proto, device_port))
self.c.recvall()
def __enter__(self, *a, **kw):
return self
@_autoclose
def __exit__(self, *a, **kw): pass
def proxy(port=9999):
"""Starts an ADB proxy on the specified port, for debugging purposes."""
l = listen(port)
l.wait_for_connection()
r = remote(context.adb_host, context.adb_port, level='debug')
l <> r
+4 -6
View File
@@ -1,9 +1,7 @@
from __future__ import absolute_import
from pwnlib.context import LocalContext
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.util import misc
from .context import LocalContext
from .context import context
from .log import getLogger
from .util import misc
log = getLogger(__name__)
+6 -12
View File
@@ -2,15 +2,13 @@
"""
Topographical sort
"""
from __future__ import absolute_import
from collections import OrderedDict
from collections import defaultdict
from random import randint
from random import shuffle
from pwnlib.context import context
from pwnlib.log import getLogger
from .context import context
from .log import getLogger
log = getLogger(__name__)
@@ -123,7 +121,7 @@ def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
If a dependency cycle is encountered, one of the following will
occur:
- If ``xchg`` is :const:`True`, it is assumed that dependency cyles can
- If ``xchg`` is ``True``, it is assumed that dependency cyles can
be broken by swapping the contents of two register (a la the
``xchg`` instruction on i386).
- If ``xchg`` is not set, but not all destination registers in
@@ -167,8 +165,8 @@ def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
as a temporary register, and later overwritten with its final
value.
If ``xchg`` is :const:`True` and it is used to break a dependency cycle,
then ``reg_name`` will be :const:`None` and ``value`` will be a tuple
If ``xchg`` is ``True`` and it is used to break a dependency cycle,
then ``reg_name`` will be ``None`` and ``value`` will be a tuple
of the instructions to swap.
Example:
@@ -226,8 +224,6 @@ def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
('mov', 'b', 'c'),
('mov', 'c', 'x'),
('mov', 'x', '1')]
>>> regsort({'a': 'b', 'b': 'c'}, ['a','b','c'], xchg=0)
[('mov', 'a', 'b'), ('mov', 'b', 'c')]
"""
if randomize is None:
randomize = context.randomize
@@ -337,9 +333,7 @@ def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
# If XCHG is expressly disabled, and there is no temporary register,
# try to see if there is any register which can be used as a temp
# register instead.
#
# If there are no cycles, there's no need for a temporary register.
if in_cycle and not (xchg or tmp):
if not (xchg or tmp):
for reg in in_out:
if not depends_on_cycle(reg, in_out, in_cycle):
tmp = reg
Loaded 100 of 3562 files, more files were not shown because too many files have changed in this diff. Show more