Compare commits

...
73 Commits
Author SHA1 Message Date
Mathias Svensson 2f7f0de984 Release 3.3.4 2017-01-12 23:40:13 +01:00
Zach Riggle 16b507e197 Change hexdump_iter to not rely on the length of its input file (#850)
We cannot determine the length of the file if passed e.g. a tube or a pipe or PTY or character device (e.g. stdin or /dev/null).

Supercedes: 9c2f0f4e
(cherry picked from commit 14f717b7b4)
2017-01-12 17:20:15 -05:00
Zach Riggle 53f041a4d2 Fix shellcraft.amd64.popad registers (#853)
Fixes Gallopsled/pwntools#848
2017-01-12 16:47:44 -05:00
Zach Riggle b2a5b76cf7 Do not perform temporary register selection if we don't need to (#852)
Fixes Gallopsled/pwntools#847
2017-01-12 16:36:49 -05:00
Zach Riggle 9c2f0f4e89 Fix hexdump_iter for actual file objects, like those used by phd (#846) 2017-01-11 15:06:11 -05:00
Mathias Svensson d1edd275d9 Release 3.3.3 2017-01-10 23:52:50 +01:00
Zach Riggle cb4991c41d Fix regression in amd64.mov with 32-bit values >= 0x80000000 (#843)
Fixes Gallopsled/pwntools#841
Caused by: Gallopsled/pwntools#504 (9ca377c)
2017-01-10 15:56:50 -05:00
Mathias Svensson 323975f343 Release 3.3.2 2017-01-10 18:28:24 +01:00
Zach Riggle f65c258956 Fix hexdump regression in 3.3.1 (#840) 2017-01-10 12:15:34 -05:00
Mathias Svensson d55874a6d3 Release 3.3.1 2017-01-10 16:53:24 +01:00
Zach Riggle 0426d498d9 Resolve feature regression in hexdump(..., cyclic=True)
This code wasnt updated when the code was changed to operate on file descriptors.

Additionally, there was a functional regression, that the first line would be skipped.

Fixes: Gallopsled/pwntools#836
Caused by: Gallopsled/pwntools#695 (d65991d)
2017-01-09 21:59:19 -05:00
Zach Riggle 54e722819b Defer attempts to start ADB server until we actually fail to connect (#833)
* Defer attempts to start ADB server until we actually fail to connect

* Upgrade to a warning message; fix .version() multiple calls with _autoclose

* Add doctest which tests the kill/restart behavior
2017-01-05 12:38:51 -05:00
Mathias Svensson 5a370162e5 Release 3.3.0 2016-12-24 11:26:18 +01:00
Mathias Svensson 253ab78e4c Merge branch 'stable' into beta 2016-12-24 11:25:12 +01:00
Mathias Svensson d39f9a145e Release 3.2.1 2016-12-24 10:53:02 +01:00
Zach Riggle 1b76a27bd5 Merge remote-tracking branch 'upstream/stable' into beta-staging 2016-12-22 16:26:31 -05:00
Zach Riggle 87491bf6dc Fix install script for new branches, where TRAVIS_COMMIT_RANGE is empty 2016-12-22 14:40:43 -05:00
Zach Riggle b30835ec5b Fix regression introduced in 386e68691f 2016-12-22 14:03:09 -05:00
Zach Riggle 332c9d2031 Merge remote-tracking branch 'upstream/stable' into beta-staging 2016-12-15 14:52:39 -06:00
Zach Riggle 036397c861 Fix adb.uninstall() typo (#816)
Closes Gallopsled/pwntools#783
2016-12-15 14:52:25 -06:00
Zach Riggle dcf03fd44d Bugfix for None data from field (#815) 2016-12-15 14:13:36 -06:00
Zach Riggle df54bf2bb3 Dont barf if home directory is not writable (#814)
* Handle the home directory being read-only / cache directory being unavailable

Fixes Gallopsled/pwntools#799

* Add doctest for context.cache_dir
2016-12-15 14:04:45 -06:00
Zach Riggle 386e68691f Multiple ADB performance and functionality fixes (#811)
* Fix ADB wait_for_device with ANDROID_SERIAL set, and fix performance

* Fix QEMU usage for native binaries

* Fix adb.write and adb.listdir operating on the wrong type of object

* Fix failing unit test with new behavior in adb.protocol.Client.list

* Fix adb server startup issue

* Rework deferred initialization to work around class-scope superclass attributes, and add test for AdbDevice
2016-12-14 17:34:18 -06:00
Zach Riggle 22f170abc7 Check the name before attempting to call startswith() (#805) 2016-12-14 16:47:38 -06:00
Zach Riggle 350610fbee Fix error message with StrictHostKeyChecking (#813) 2016-12-14 16:37:05 -06:00
Zach Riggle c7b3852eae Fix exception thrown under 32-bit Python interpreters. (#804)
Fixes Gallopsled/pwntools#518
2016-12-14 16:26:29 -06:00
Zach Riggle eaf6402198 Fix adb.partitions listing (#802)
* Fix adb.partitions listing

* Add adb.walk and adb.find functions to ensure adb.partitions works on devices without a find binary

* Add adb.readlink to simplify logic for reading symlinks
2016-12-14 15:37:47 -06:00
Ricky Zhou 2f597f41d4 Properly handle exceptions in remote._connect. (#793)
Previously, if getaddrinfo or socket.socket threw an exception,
_connect would return without ever calling h.success or h.failure. As a
result, progress indicators would continue to update indefinitely.

Fix this by using a with statement instead.

(Also remove an unused variable and fix a typo.)
2016-11-28 14:45:20 -07:00
Mathias Svensson 620364dd1d Fixed a link in CHANGELOG.md 2016-11-12 21:10:05 +01:00
Mathias Svensson 76c21548db Release 3.3.0beta0 2016-11-12 19:33:58 +01:00
Mathias Svensson ad71d9235b Release 3.2.0 2016-11-12 13:43:56 +01:00
Mathias Svensson 2e5f8061b2 Merge branch 'stable' into beta 2016-11-12 12:59:46 +01:00
Zach Riggle 099404277e Enhance documentation and error logging for ssh.process preexec_fn usage (#787)
Also fix other incorrect log usage

Fixes Gallopsled/pwntools#786
2016-11-12 12:58:24 +01:00
Zach Riggle 30c34b785a Fix ROP.call(Function(...)) breaking dump()
Fixes #770
2016-11-10 14:23:28 -08:00
Zach Riggle 6c304d3511 Add a bit more docs to the ADB module 2016-11-10 13:50:15 -08:00
Zach Riggle dc297e17ae Fix docstring commit [skip ci] 2016-11-07 18:52:12 -08:00
Zach Riggle fa402ceb57 Add adb.uptime and adb.boot_time 2016-11-07 17:53:52 -08:00
Zach Riggle 483adb8e2d Update ISSUE_TEMPLATE.md 2016-11-07 12:05:00 -08:00
Zach Riggle 76fe04e356 Fix log message error in adb.uninstall 2016-11-01 16:14:55 -07:00
Justin Steven 82312baba0 Add cyclic_metasploit() and cyclic_metasploit_find() (#785)
* Add cyclic_metasploit and cyclic_metasploit_find

* Stop using mutable default arguments
2016-10-26 17:19:19 -07:00
Zach Riggle d101a775aa Fix adb.uninstall() typo
Closes Gallopsled/pwntools#783
2016-10-26 10:25:34 -07:00
Mathias Svensson 5fda6580e8 Expose the BitPolynom (#755) 2016-10-23 22:38:50 +02:00
Mathias Svensson 9185437626 Merge branch 'beta' into dev 2016-10-23 11:35:03 +02:00
Mathias Svensson bde40c36ae Merge branch 'stable' into beta 2016-10-23 11:12:35 +02:00
Mathias Svensson 894b17e74a Release 3.1.1 2016-10-23 11:11:39 +02:00
Mathias Svensson 33b734cbc6 Added a doctest for MemLeak.struct 2016-10-23 11:11:32 +02:00
Morten Brøns-Pedersen 3e8454412e Bugfix in MemLeak.struct. (#768)
The function didn't work due to `sizeof` not being defined.  This PR imports
`ctypes` and uses `ctypes.sizeof`.
2016-10-23 10:53:04 +02:00
Zach Riggle 420f99c5d0 Automatic update checking should not log to stdout. (#774)
This breaks piping into files for all of the command-line utilities.
2016-10-20 15:41:00 -07:00
Zach Riggle 98052e4166 Add an error to cyclic() if the requested length is too long (#782)
$ cyclic 999999999
[ERROR] Can't create a pattern length=999999999 with len(alphabet)==26 and n==4

Fixes Gallopsled/pwntools#781
2016-10-19 09:18:41 -07:00
Mathias Svensson e971856c74 Merge branch 'beta' into dev 2016-10-18 23:09:45 +02:00
Mathias Svensson 09ee73c31d Merge branch 'stable' into beta 2016-10-18 22:58:20 +02:00
Zach Riggle 0c4d1365e9 Fix documentation generation since autoprogram broke Python2 2016-10-18 22:57:32 +02:00
Mathias Svensson b3f941c3f5 Merge branch 'beta' into dev 2016-10-18 22:42:29 +02:00
Justin Steven 67e11a9aeb Fix typos in cyclic.py's docstrings (#775) 2016-10-18 13:26:42 -07:00
Zach Riggle fd731154ff Make process.argv optional, using context.binary 2016-10-15 14:29:13 -07:00
Isaac 906bc94a5f fix typo in TESTING.md (#769) 2016-10-15 13:31:42 +02:00
bstub db03e7de10 Adding escaped hex output format to shellcraft -f option (#765)
* adding output format for python hex-notation

* cleaned up implementation

* changed format identifier to {a} --escaped

* {a] was taken by asm, changed format identifier to {d} --escaped

* adding {e}lf to help
2016-10-14 13:13:02 -07:00
Morten Brøns-Pedersen 97e03fd29f Bugfix in MemLeak.struct. (#768)
The function didn't work due to `sizeof` not being defined.  This PR imports
`ctypes` and uses `ctypes.sizeof`.
2016-10-14 13:11:18 -07:00
Zach Riggle 09b8bb93a7 Fix regressions introduced by f9133b1680 2016-10-14 13:10:13 -07:00
Zach Riggle f9133b1680 Add STDERR argument, and write docs for args module (#760)
Add STDERR argument, and write docs for args module

Fixes Gallopsled/pwntools#759
2016-10-10 19:13:45 -07:00
Mathias Svensson 8aa21bd99a Merge branch 'beta' into dev 2016-10-11 03:06:21 +02:00
Zach Riggle 9af20c778f Fix incorrect usage of argv in unhex 2016-10-10 17:55:13 -07:00
Mathias Svensson d1621dca87 Merge branch 'beta' into dev 2016-10-10 15:50:53 +02:00
Mathias Svensson 39970c3847 Merge branch 'beta' into dev 2016-10-10 14:59:41 +02:00
Zach Riggle 8d11fb8594 Fix wait-for-device with kick=True re-using the same client (#757) 2016-10-09 20:00:00 -07:00
Zach Riggle aec3fa6879 Remove all Github update checks (#761)
These were nice-to-have, but as long as PyPi is updated and functioning
properly, we don't need to check Github.

Fixes Gallopsled/pwntools#756
2016-10-07 15:30:38 -07:00
Zach Riggle ab6602407e Move issue/pr templates to .github 2016-10-07 15:18:58 -07:00
Zach Riggle 42ab0c985d Sort inputs with isort 2016-10-05 15:51:05 -07:00
Zach Riggle b215828133 Make tube.stream() more robust 2016-10-05 15:49:41 -07:00
Zach Riggle e3af4ce6a7 Update changelog 2016-10-05 15:44:13 -07:00
Zach Riggle 96c6edcc37 Update changelog 2016-10-05 15:44:03 -07:00
Zach Riggle b198ec8633 Add tube.stream() 2016-10-05 15:43:04 -07:00
Mathias Svensson c8a87381e8 Merge branch 'beta' into dev 2016-10-02 20:38:08 +02:00
42 changed files with 1016 additions and 197 deletions

No files matched your search

@@ -6,4 +6,4 @@ When reporting an issue, be sure that you are running the latest released versio
Please verify that your issue occurs on 64-bit Ubuntu 14.04. You can use the Dockerfile in `extra/docker/Dockerfile` to save time.
If possible, provide a proof-of-concept which demonstrates the problem. Include any binaries or scripts necessary to reproduce the issue.
If possible, provide a proof-of-concept which demonstrates the problem. Include any binaries or scripts necessary to reproduce the issue, and please include the full debug output via setting the environment variable `PWNLIB_DEBUG=1`.
File renamed without changes.
+119 -3
View File
@@ -9,9 +9,17 @@ The table below shows which release corresponds to each branch, and what date th
| Version | Branch | Release Date |
| ---------------- | -------- | ---------------------- |
| [3.3.0](#330) | `dev` | Dec 24, 2016 (planned)
| [3.2.0](#320) | `beta` | Nov 12, 2016 (planned)
| [3.1.0](#310) | `stable` | Oct 2, 2016
| [3.5.0](#350) | `dev` | Mar 18, 2017 (planned)
| [3.4.0](#340) | `beta` | Feb 4, 2017 (planned)
| [3.3.4](#334) | `stable` | Jan 12, 2016
| [3.3.3](#333) | | Jan 10, 2016
| [3.3.2](#332) | | Jan 10, 2016
| [3.3.1](#331) | | Jan 10, 2016
| [3.3.0](#330) | | Dec 24, 2016
| [3.2.1](#321) | | Dec 24, 2016
| [3.2.0](#320) | | Nov 12, 2016
| [3.1.1](#311) | | Oct 23, 2016
| [3.1.0](#310) | | Oct 2, 2016
| [3.0.4](#304) | | Sept 19, 2016
| [3.0.3](#303) | | Sept 18, 2016
| [3.0.2](#302) | | Sept 6, 2016
@@ -19,9 +27,108 @@ 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.5.0
To be released on Mar 18, 2017.
## 3.4.0
To be released on Feb 4, 2017.
## 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
## 3.3.3
- [#843][843] fixed a bug in `amd64.mov`.
[843]: https://github.com/gallopsled/pwntools/pull/843
## 3.3.2
- [#840][840] fixed a regression introduced by [#837][837].
[840]: https://github.com/gallopsled/pwntools/pull/840
## 3.3.1
- [#833][833] Fixed a performance-impacting bug in the adb module.
- [#837][837] Fixed a bug([#836][836]) causing `hexdump(cyclic=True)` to throw an exception.
[833]: https://github.com/Gallopsled/pwntools/pull/833
[837]: https://github.com/Gallopsled/pwntools/pull/837
[836]: https://github.com/Gallopsled/pwntools/issues/836
## 3.3.0
- [b198ec8][b198ec8] Added `tube.stream()` function, which is like `tube.interact()` without a prompt or keyboard input.
+ Effectively, this is similar to `cat file` and just prints data as fast as it is received.
- [aec3fa6][aec3fa6] Disable update checks against GitHub
+ These checks frequently broke due to GitHub query limits
- [#757][757] Fixed `adb.wait_for_device()` re-use of the same connection
- [f9133b1][f9133b1] Add a `STDERR` magic argument to make logging go to `stderr` instead of `stdout`
+ Usage is e.g. `python foo.py STDERR` or `PWNLIB_STDERR=1 python foo.py`
+ Also adds `context.log_console` to log to any file or terminal
- [67e11a9][67e11a9] Add faster error checking to `cyclic()` when provided very large values
- [5fda658][5fda658] Expose BitPolynom in `globals()`
- [#765][765] Added `-d` option for hex-escaped output for `shellcraft` command-line tool
- [#772][772] Fixed bash completion regressions
- [30c34b7][30c34b7] Fix `ROP.call()` with `Function` objects from `ELF.functions`
- [fa402ce][fa402ce] Add `adb.uptime` and `adb.boot_time`
- [82312ba][82312ba] Add `cyclic_metasploit` and `cyclic_metasploit_find`
[757]: https://github.com/Gallopsled/pwntools/pull/757
[765]: https://github.com/Gallopsled/pwntools/pull/765
[772]: https://github.com/Gallopsled/pwntools/pull/772
[b198ec8]: https://github.com/Gallopsled/pwntools/commit/b198ec8
[aec3fa6]: https://github.com/Gallopsled/pwntools/commit/aec3fa6
[f9133b1]: https://github.com/Gallopsled/pwntools/commit/f9133b1
[67e11a9]: https://github.com/Gallopsled/pwntools/commit/67e11a9
[5fda658]: https://github.com/Gallopsled/pwntools/commit/5fda658
[30c34b7]: https://github.com/Gallopsled/pwntools/commit/30c34b7
[fa402ce]: https://github.com/Gallopsled/pwntools/commit/fa402ce
[82312ba]: https://github.com/Gallopsled/pwntools/commit/82312ba
## 3.2.1
Multiple bug fixes.
- [#783][783] Fix `adb.uninstall` typo
- [#787][787] Added error handling for `ssh.process` argument `preexec_fn`
- [#793][793] Fixed progress message in `remote()` when connections failed
- [#802][802] Fixed partition listing in `adb.partitions`, which accidentally shelled out to the `adb` binary
- [#804][804] Fix error message for 32-bit distributions
- [#805][805] Fix exception in `Core.segments` when a segment has no name
- [#811][811] Fixes and performance improvements for `adb.wait_for_device()`
- [#813][813] Fixed a release script
- [#814][814] Fixed exceptions thrown if the `$HOME` directory is not writable
- [#815][815] Properly handle `None` in `MemLeak`
[783]: https://github.com/Gallopsled/pwntools/pull/783
[787]: https://github.com/Gallopsled/pwntools/pull/787
[793]: https://github.com/Gallopsled/pwntools/pull/793
[802]: https://github.com/Gallopsled/pwntools/pull/802
[804]: https://github.com/Gallopsled/pwntools/pull/804
[805]: https://github.com/Gallopsled/pwntools/pull/805
[811]: https://github.com/Gallopsled/pwntools/pull/811
[813]: https://github.com/Gallopsled/pwntools/pull/813
[814]: https://github.com/Gallopsled/pwntools/pull/814
[815]: https://github.com/Gallopsled/pwntools/pull/815
## 3.2.0
- [#695][695] Fixed a performance regression in `phd`.
- [452605e][452605e] Fixed [#629][629] related to correct removal of temporary files.
- [ea94ee4][ea94ee4] Disallows semi-colons in for the `run_in_terminal` function, since it did not work properly in all cases.
- [6376d07][6376d07] Added the mips shellcode `pushstr_array`.
- [#700][700] Added missing MIPS shellcode documentation to readthedocs, and enabled unit tests
- [#701][701] Command line tools refactored to have a common `pwn` entry point.
+ Added an option to *not* install the traditional `asm`, `disasm`, `checksec`, etc scripts
@@ -51,6 +158,8 @@ The table below shows which release corresponds to each branch, and what date th
- [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())`
[629]: https://github.com/Gallopsled/pwntools/issues/629
[695]: https://github.com/Gallopsled/pwntools/pull/695
[700]: https://github.com/Gallopsled/pwntools/pull/700
[701]: https://github.com/Gallopsled/pwntools/pull/701
@@ -60,11 +169,18 @@ The table below shows which release corresponds to each branch, and what date th
[709]: https://github.com/Gallopsled/pwntools/pull/709
[705]: https://github.com/Gallopsled/pwntools/pull/705
[703]: https://github.com/Gallopsled/pwntools/pull/703
[452605e]: https://github.com/Gallopsled/pwntools/commit/452605e854f4870ef5ccfdf7fb110dfd75c50feb
[ea94ee4]: https://github.com/Gallopsled/pwntools/commit/ea94ee4ca5a8060567cc9bd0dc33796a89ad0b95
[6376d07]: https://github.com/Gallopsled/pwntools/commit/6376d072660fb2250f48bd22629bbd7e3c61c758
[1e414af]: https://github.com/Gallopsled/pwntools/commit/1e414afbeb3a01242f4918f111febaa63b640eb7
[869ec42]: https://github.com/Gallopsled/pwntools/commit/869ec42082b4b98958dfe85103da9b101dde7daa
[4893819]: https://github.com/Gallopsled/pwntools/commit/4893819b4c23182da570e2f4ea4c14d73af2c0df
[2af55c9]: https://github.com/Gallopsled/pwntools/commit/2af55c9bc382eca23f89bc0abc7a07c075521f94
## 3.1.1
Fixed a bug in `MemLeak.struct` (PR: #768).
## 3.1.0
A number of smaller bugfixes and documentation tweaks.
+1 -1
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.1.0-green.svg?style=flat)](https://pypi.python.org/pypi/pwntools/)
[![PyPI](https://img.shields.io/badge/pypi-v3.3.4-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)
+1 -1
View File
@@ -5,7 +5,7 @@ Pwntools makes extensive use of unit tests and integration tests to ensure every
## Test Suite
To run the test suite, it is beste to use Ubuntu 12.04 or 14.04, and run the following commands. **Be aware** that this will add a user to the machine, and create a public key for SSH login!
To run the test suite, it is best to use Ubuntu 12.04 or 14.04, and run the following commands. **Be aware** that this will add a user to the machine, and create a public key for SSH login!
```sh
bash travis/install.sh
+1 -1
View File
@@ -1,5 +1,5 @@
sphinxcontrib-napoleon
sphinxcontrib-autoprogram
sphinxcontrib-autoprogram==0.1.2
isort
paramiko>=1.15.2
mako>=1.0.0
+9
View File
@@ -0,0 +1,9 @@
.. testsetup:: *
from pwn import *
:mod:`pwnlib.args` --- Magic Command-Line Arguments
=====================================================
.. automodule:: pwnlib.args
:members:
+10
View File
@@ -55,6 +55,16 @@ pwnlib.context.ContextType.defaults['randomize'] = False
pwnlib.term.text.when = 'never'
pwnlib.log.install_default_handler()
pwnlib.log.rootlogger.setLevel(1)
# Sphinx modifies sys.stdout, and context.log_terminal has
# a reference to the original instance. We need to update
# it for logging to be captured.
class stdout(object):
def __getattr__(self, name):
return getattr(sys.stdout, name)
def __setattr__(self, name, value):
return setattr(sys.stdout, name, value)
pwnlib.context.ContextType.defaults['log_console'] = stdout()
'''
autodoc_member_order = 'alphabetical'
+1
View File
@@ -43,6 +43,7 @@ Each of the ``pwntools`` modules is documented here.
:glob:
adb
args
asm
atexception
atexit
+2 -1
View File
@@ -13,4 +13,5 @@ if not platform.architecture()[0].startswith('64'):
See Gallopsled/pwntools#518 for more information."""
log.warn_once('Pwntools does not support 32-bit Python. Use a 64-bit release.')
pwnlib.update.check_automatically()
with context.local(log_console=sys.stderr):
pwnlib.update.check_automatically()
+1
View File
@@ -51,6 +51,7 @@ from pwnlib.util import iters
from pwnlib.util import net
from pwnlib.util import proc
from pwnlib.util import safeeval
from pwnlib.util.crc import BitPolynom
from pwnlib.util.cyclic import *
from pwnlib.util.fiddling import *
from pwnlib.util.getdents import *
+157 -17
View File
@@ -10,12 +10,38 @@ If you have multiple devices, you have a handful of options to select one, or it
over the devices.
First and most important is the ``context.device`` property, which declares the "currently"
seelcted device in any scope. It can be set manually to a serial number, or to a ``Device``
selected device in any scope. It can be set manually to a serial number, or to a ``Device``
instance.
.. code-block:: python
>>> context.device = ''
# Take the first available device
context.device = adb.wait_for_device()
# Set a device by serial number
context.device = 'ZX1G22LH8S'
# Set a device by its product name
for device in adb.devices():
if device.product == 'shamu':
break
else:
error("Could not find any shamus!")
Once a device is selected, you can operate on it with any of the functions in
the ``pwnlib.adb`` module.
.. code-block:: python
# Get a process listing
print adb.process(['ps']).recvall()
# Fetch properties
print adb.properties.ro.build.fingerprint
# Read and write files
print adb.read('/proc/version')
adb.write('/data/local/tmp/foo', 'my data')
"""
import functools
@@ -162,8 +188,46 @@ def reboot_bootloader():
with Client() as c:
c.reboot_bootloader()
@with_device
def uptime():
"""uptime() -> float
Returns:
Uptime of the device, in seconds
"""
up, idle = map(float, read('/proc/uptime').split())
return up
@with_device
def boot_time():
"""boot_time() -> int
Returns:
Boot time of the device, in Unix time, rounded to the
nearest second.
"""
for line in read('/proc/stat').splitlines():
name, value = line.split(None, 1)
if name == 'btime':
return int(value)
class AdbDevice(Device):
"""Encapsulates information about a connected device."""
"""Encapsulates information about a connected device.
Example:
>>> device = adb.wait_for_device()
>>> device.arch
'arm'
>>> device.bits
32
>>> device.os
'android'
>>> device.product
'sdk_phone_armv7'
>>> device.serial
'emulator-5554'
"""
def __init__(self, serial, type, port=None, product='unknown', model='unknown', device='unknown', features=None, **kw):
self.serial = serial
self.type = type
@@ -176,13 +240,45 @@ class AdbDevice(Device):
if product == 'unknown':
return
with context.local(device=serial):
# Deferred fields
self._initialized = False
self._arch = None
self._bits = None
self._endian = None
self._avd = None
@property
def arch(self):
self.__do_deferred_initialization()
return self._arch
@property
def avd(self):
self.__do_deferred_initialization()
return self._avd
@property
def bits(self):
self.__do_deferred_initialization()
return self._bits
@property
def endian(self):
self.__do_deferred_initialization()
return self._endian
def __do_deferred_initialization(self):
if self._initialized:
return
with context.local(device=self.serial):
abi = str(properties.ro.product.cpu.abi)
context.clear()
context.arch = str(abi)
self.arch = context.arch
self.bits = context.bits
self.endian = context.endian
self._arch = context.arch
self._bits = context.bits
self._endian = context.endian
if self.port == 'emulator':
emulator, port = self.serial.split('-')
@@ -195,7 +291,8 @@ class AdbDevice(Device):
self.avd = r.recvline().strip()
except:
pass
# r = remote('localhost')
self._initialized = True
def __str__(self):
return self.serial
@@ -296,7 +393,9 @@ def wait_for_device(kick=False):
for device in devices():
if context.device == device:
return device
break
if not serial:
break
else:
log.error("Could not find any devices")
@@ -850,7 +949,7 @@ _android_mk_template = '''
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := poc
LOCAL_MODULE := %(local_module)s
LOCAL_SRC_FILES := %(local_src_files)s
include $(BUILD_EXECUTABLE)
@@ -880,6 +979,8 @@ def _generate_ndk_project(file_list, abi='arm-v7a', platform_version=21):
# Create the directories
# Populate Android.mk
local_module = os.path.basename(file_list[0])
local_module, _ = os.path.splitext(local_module)
local_src_files = ' '.join(list(map(os.path.basename, file_list)))
Android_mk = os.path.join(jni_directory, 'Android.mk')
with open(Android_mk, 'w+') as f:
@@ -914,7 +1015,7 @@ def compile(source):
abi = 'armeabi-v7a'
sdk = '21'
# If we have an atatched device, use its settings.
# 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)
@@ -951,12 +1052,51 @@ class Partition(object):
with log.waitfor('Fetching %r partition (%s)' % (self.name, self.path)):
return read(self.path)
@with_device
def walk(top, topdown=True):
join = os.path.join
isdir = lambda x: stat.S_ISDIR(x['mode'])
client = Client()
names = client.list(top)
dirs, nondirs = [], []
for name, metadata in names.items():
if isdir(metadata):
dirs.append(name)
else:
nondirs.append(name)
if topdown:
yield top, dirs, nondirs
for name in dirs:
new_path = join(top, name)
for x in walk(new_path, topdown):
yield x
if not topdown:
yield top, dirs, nondirs
@with_device
def find(top, name):
for root, dirs, files in walk(top):
if name in files or name in dirs:
yield os.path.join(root, name)
@with_device
def readlink(path):
path = process(['readlink', path]).recvall()
# Readlink will emit a single newline
# We can't use the '-n' flag since old versions don't support it
if path.endswith('\n'):
path = path[:-1]
return path
class Partitions(object):
@property
@context.quiet
def by_name_dir(self):
cmd = ['shell','find /dev/block/platform -type d -name by-name']
return adb(cmd).strip()
return next(find('/dev/block/platform','by-name'))
@context.quiet
def __dir__(self):
@@ -972,6 +1112,7 @@ class Partitions(object):
yield name
@context.quiet
@with_device
def __getattr__(self, attr):
for name in self:
if name == attr:
@@ -982,7 +1123,7 @@ class Partitions(object):
path = os.path.join(self.by_name_dir, name)
# Find the actual path of the device
devpath = process(['readlink', '-n', path]).recvall()
devpath = readlink(path)
devname = os.path.basename(devpath)
# Get the size of the partition
@@ -1036,13 +1177,12 @@ def uninstall(package, *arguments):
package(str): Name of the package to uninstall (e.g. ``'com.foo.MyPackage'``)
arguments: Supplementary arguments to ``'pm install'``, e.g. ``'-k'``.
"""
with log.progress("Installing package {}".format(package)):
with log.progress("Uninstalling package {}".format(package)):
with context.quiet:
return process(['pm','uninstall',apk] + list(arguments)).recvall()
return process(['pm','uninstall',package] + list(arguments)).recvall()
@context.quiet
def packages():
"""Returns a list of packages installed on the system"""
packages = process(['pm', 'list', 'packages']).recvall()
return [line.split('package:', 1)[-1] for line in packages.splitlines()]
+71 -13
View File
@@ -5,6 +5,7 @@ Documentation is available here:
https://android.googlesource.com/platform/system/core/+/master/adb/protocol.txt
"""
import functools
import stat
import time
from ..context import context
@@ -42,14 +43,7 @@ class Message(object):
class Connection(remote):
"""Connection to the ADB server"""
def __init__(self, host, port, level=None, *a, **kw):
# Try to make sure ADB is running if it's on the default host and port.
if host == context.defaults['adb_host'] \
and port == context.defaults['adb_port']:
process(context.adb + ['start-server'], level='error').wait_for_close()
with context.quiet:
super(Connection, self).__init__(host, port, level=level, *a, **kw)
super(Connection, self).__init__(host, port, level=level, *a, **kw)
self._executable = None
self._argv = None
@@ -90,6 +84,20 @@ class Client(Logger):
@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
@@ -134,12 +142,23 @@ class Client(Logger):
@_autoclose
def kill(self):
"""Kills the remote ADB server"""
"""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:
@@ -296,8 +315,6 @@ class Client(Logger):
return rv
return wrapper
@_with_transport
@_sync
def list(self, path):
"""Execute the ``LIST`` command of the ``SYNC`` API.
@@ -328,8 +345,23 @@ class Client(Logger):
>>> 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:
@@ -389,9 +421,35 @@ class Client(Logger):
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):
def _write(self, path, data, mode=0o755, timestamp=None, callback=None):
path += ',' + str(mode)
self.c.flat('SEND', len(path), path)
+79 -20
View File
@@ -1,5 +1,43 @@
#!/usr/bin/env python2
"""
Pwntools exposes several magic command-line arguments and environment
variables when operating in `from pwn import *` mode.
The arguments extracted from the command-line and removed from ``sys.argv``.
Arguments can be set by appending them to the command-line, or setting
them in the environment prefixed by ``PWNLIB_``.
The easiest example is to enable more verbose debugging. Just set ``DEBUG``.
.. code-block:: bash
$ PWNLIB_DEBUG=1 python exploit.py
$ python exploit.py DEBUG
These arguments are automatically extracted, regardless of their name, and
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
$ python -c 'from pwn import *; print args' A=1 B=Hello HOST=1.2.3.4 DEBUG
defaultdict(<type 'str'>, {'A': '1', 'HOST': '1.2.3.4', 'B': 'Hello'})
This is very useful for conditional code, for example determining whether to
run an exploit locally or to connect to a remote server. Arguments which are
not specified evaluate to an empty string.
.. code-block:: python
if args['REMOTE']:
io = remote('exploitme.com', 4141)
else:
io = process('./pwnable')
The full list of supported "magic arguments" and their effects are listed
below.
"""
import collections
import logging
@@ -42,46 +80,67 @@ def asbool(s):
else:
raise ValueError('must be integer or boolean: %r' % s)
def set_log_level(x):
def LOG_LEVEL(x):
"""Sets the logging verbosity used via ``context.log_level``,
e.g. ``LOG_LEVEL=debug``.
"""
with context.local(log_level=x):
context.defaults['log_level']=context.log_level
def set_log_file(x):
def LOG_FILE(x):
"""Sets a log file to be used via ``context.log_file``, e.g.
``LOG_FILE=./log.txt``"""
context.log_file=x
def set_log_level_error(x):
set_log_level('error')
def SILENT(x):
"""Sets the logging verbosity to ``error`` which silences most
output."""
LOG_LEVEL('error')
def set_log_level_debug(x):
set_log_level('debug')
def DEBUG(x):
"""Sets the logging verbosity to ``debug`` which displays much
more information, including logging each byte sent by tubes."""
LOG_LEVEL('debug')
def set_noterm(v):
def NOTERM(v):
"""Disables pretty terminal settings and animations."""
if asbool(v):
global term_mode
term_mode = False
def set_timeout(v):
def TIMEOUT(v):
"""Sets a timeout for tube operations (in seconds) via
``context.timeout``, e.g. ``TIMEOUT=30``"""
context.defaults['timeout'] = int(v)
def set_randomize(v):
def RANDOMIZE(v):
"""Enables randomization of various pieces via ``context.randomize``"""
context.defaults['randomize'] = asbool(v)
def set_aslr(v):
def NOASLR(v):
"""Disables ASLR via ``context.aslr``"""
context.defaults['aslr'] = not asbool(v)
def set_noptrace(v):
def NOPTRACE(v):
"""Disables facilities which require ``ptrace`` such as ``gdb.attach()``
statements, via ``context.noptrace``."""
context.defaults['noptrace'] = asbool(v)
def STDERR(v):
"""Sends logging to ``stderr`` by default, instead of ``stdout``"""
context.log_console = sys.stderr
hooks = {
'LOG_LEVEL': set_log_level,
'LOG_FILE': set_log_file,
'DEBUG': set_log_level_debug,
'NOTERM': set_noterm,
'SILENT': set_log_level_error,
'RANDOMIZE': set_randomize,
'TIMEOUT': set_timeout,
'NOASLR': set_aslr,
'NOPTRACE': set_noptrace,
'LOG_LEVEL': LOG_LEVEL,
'LOG_FILE': LOG_FILE,
'DEBUG': DEBUG,
'NOTERM': NOTERM,
'SILENT': SILENT,
'RANDOMIZE': RANDOMIZE,
'TIMEOUT': TIMEOUT,
'NOASLR': NOASLR,
'NOPTRACE': NOPTRACE,
'STDERR': STDERR,
}
def initialize():
-2
View File
@@ -5,8 +5,6 @@ import sys
import pwnlib
from pwnlib.context import context
pwnlib.log.console.stream = sys.stderr
choices = map(str, [16,32,64])
choices += list(context.oses)
choices += list(context.architectures)
+5 -1
View File
@@ -1,3 +1,5 @@
import sys
from . import asm
from . import checksec
from . import common
@@ -15,6 +17,7 @@ from . import shellcraft
from . import unhex
from . import update
from .common import parser
from ..context import context
commands = {
'asm': asm.main,
@@ -36,7 +39,8 @@ commands = {
def main():
args = parser.parse_args()
commands[args.command](args)
with context.local(log_console = sys.stderr):
commands[args.command](args)
if __name__ == '__main__':
main()
+4 -2
View File
@@ -57,9 +57,10 @@ p.add_argument(
'p',
'i', 'hexii',
'e', 'elf',
'd', 'escaped',
'default'],
default = 'default',
help = 'Output format (default: hex), choose from {r}aw, {s}tring, {c}-style array, {h}ex string, hex{i}i, {a}ssembly code, {p}reprocssed code',
help = 'Output format (default: hex), choose from {e}lf, {r}aw, {s}tring, {c}-style array, {h}ex string, hex{i}i, {a}ssembly code, {p}reprocssed code, escape{d} hex string',
)
p.add_argument(
@@ -329,7 +330,8 @@ def main(args):
code = pwnlib.util.fiddling.enhex(code) + '\n'
elif args.format in ['i', 'hexii']:
code = hexii(code) + '\n'
elif args.format in ['d', 'escaped']:
code = ''.join('\\x%02x' % ord(c) for c in code) + '\n'
if not sys.stdin.isatty():
args.out.write(sys.stdin.read())
+66 -2
View File
@@ -10,6 +10,7 @@ import logging
import os
import platform
import socket
import stat
import string
import subprocess
import sys
@@ -334,6 +335,7 @@ class ContextType(object):
'kernel': None,
'log_level': logging.INFO,
'log_file': _devnull(),
'log_console': sys.stdout,
'randomize': False,
'newline': '\n',
'noptrace': False,
@@ -922,6 +924,24 @@ class ContextType(object):
value.flush()
return value
@_validator
def log_console(self, stream):
"""
Sets the default logging console target.
Examples:
>>> context.log_level = 'warn'
>>> log.warn("Hello")
[!] Hello
>>> context.log_console=open('/dev/null', 'w')
>>> log.warn("Hello")
>>> context.clear()
"""
if isinstance(stream, str):
stream = open(stream, 'wt')
return stream
@property
def mask(self):
return (1 << self.bits) - 1
@@ -1107,8 +1127,13 @@ class ContextType(object):
@property
def adb(self):
"""Returns an argument array for connecting to adb."""
command = ['adb']
"""Returns an argument array for connecting to adb.
Unless ``$ADB_PATH`` is set, uses the default ``adb`` binary in ``$PATH``.
"""
ADB_PATH = os.environ.get('ADB_PATH', 'adb')
command = [ADB_PATH]
if self.adb_host != self.defaults['adb_host']:
command += ['-H', self.adb_host]
@@ -1121,6 +1146,45 @@ class ContextType(object):
return command
@property
def cache_dir(self):
"""Directory used for caching data.
Note:
May be either a path string, or ``None``.
Example:
>>> cache_dir = context.cache_dir
>>> cache_dir is not None
True
>>> os.chmod(cache_dir, 0o000)
>>> context.cache_dir is None
True
>>> os.chmod(cache_dir, 0o755)
>>> cache_dir == context.cache_dir
True
"""
home = os.path.expanduser('~')
if not os.access(home, os.W_OK):
return None
cache = os.path.join(home, '.pwntools-cache')
if not os.path.exists(cache):
try:
os.mkdir(cache)
except OSError:
return None
# Some wargames e.g. pwnable.kr have created dummy directories
# which cannot be modified by the user account (owned by root).
if not os.access(cache, os.W_OK):
return None
return cache
#*************************************************************************
# ALIASES
@@ -7,7 +7,6 @@ import re
import urllib
from bs4 import BeautifulSoup
from pwn import *
uas = set()
+2 -1
View File
@@ -6,6 +6,7 @@ from elftools.common.py3compat import bytes2str
from elftools.common.utils import roundup
from elftools.common.utils import struct_parse
from elftools.construct import CString
from ..context import context
from ..log import getLogger
from ..tubes.tube import tube
@@ -235,7 +236,7 @@ class Core(ELF):
def libc(self):
"""Return the first mapping in libc"""
for m in self.mappings:
if m.name.startswith('libc') and m.name.endswith('.so'):
if m.name and m.name.startswith('libc') and m.name.endswith('.so'):
return m
@property
+2 -1
View File
@@ -539,7 +539,8 @@ class elf_prstatus_i386(ctypes.Structure):
assert ctypes.sizeof(elf_prstatus_i386) == 0x90
class elf_prstatus_amd64(ctypes.Structure):
_fields_ = generate_prstatus_common(64, user_regs_struct_amd64)
_fields_ = generate_prstatus_common(64, user_regs_struct_amd64) \
+ [('padding', ctypes.c_uint32)]
assert ctypes.sizeof(elf_prstatus_amd64) == 0x150
+18 -2
View File
@@ -1,5 +1,6 @@
"""Exposes functionality for manipulating ELF files
"""
import codecs
import mmap
import os
import subprocess
@@ -30,7 +31,18 @@ log = getLogger(__name__)
__all__ = ['load', 'ELF']
Function = namedtuple('Function', 'address size')
class Function(object):
def __init__(self, name, address, size):
self.name = name
self.address = address
self.size = size
def __repr__(self):
return '%s(name=%r, address=%#x, size=%#x)' % (
self.__class__.__name__,
self.name,
self.address,
self.size
)
def load(*args, **kwargs):
"""Compatibility wrapper for pwntools v1"""
@@ -348,11 +360,15 @@ class ELF(ELFFile):
continue
if sym.entry.st_info['type'] == 'STT_FUNC' and sym.entry.st_size != 0:
name = sym.name
try:
name = codecs.encode(name, 'latin-1')
except Exception:
pass
if name not in self.symbols:
continue
addr = self.symbols[name]
size = sym.entry.st_size
self.functions[name] = Function(addr, size)
self.functions[name] = Function(name, addr, size)
def _populate_symbols(self):
"""
+1 -1
View File
@@ -3,8 +3,8 @@
import os
from ..args import args
from ..tubes.remote import remote
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()
+9 -8
View File
@@ -113,7 +113,7 @@ 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 `sys.stderr.isatty()` is `False`
# module won't add any escape codes if `pwnlib.context.log_console.isatty()` is `False`
_msgtype_prefixes = {
'status' : [text.magenta, 'x'],
'success' : [text.bold_green, '+'],
@@ -166,7 +166,6 @@ class Progress(object):
using :meth:`Logger.progress`.
"""
def __init__(self, logger, msg, status, level, args, kwargs):
global _progressid
self._logger = logger
self._msg = msg
self._status = status
@@ -264,7 +263,7 @@ class Logger(object):
# This is a minor hack to permit user-defined classes which inherit
# from a tube (which do not actually reside in the pwnlib library)
# to receive logging abilities that behave as they would expect from
# the resto f the library
# the rest of the library
module = self.__module__
if not module.startswith('pwnlib'):
module = 'pwnlib.' + module
@@ -490,10 +489,14 @@ class Handler(logging.StreamHandler):
logger will not be emitted but rather an animated progress line will be
created.
This handler outputs to ``sys.stderr``.
An instance of this handler is added to the ``'pwnlib'`` logger.
"""
@property
def stream(self):
return context.log_console
@stream.setter
def stream(self, value):
pass
def emit(self, record):
"""
Emit a log record or create/update an animated progress logger
@@ -652,7 +655,7 @@ log_file.setFormatter(logging.Formatter(fmt, iso_8601))
# logger.addHandler(myCoolPitchingHandler)
#
rootlogger = getLogger('pwnlib')
console = Handler(sys.stdout)
console = Handler()
formatter = Formatter()
console.setFormatter(formatter)
@@ -663,8 +666,6 @@ def install_default_handler():
the ``pwnlib`` root logger. This function is automatically called from when
importing :mod:`pwn`.
'''
console.stream = sys.stdout
logger = logging.getLogger('pwnlib')
if console not in logger.handlers:
+14 -1
View File
@@ -1,3 +1,4 @@
import ctypes
import functools
import string
@@ -96,8 +97,18 @@ class MemLeak(object):
struct(class): A ctypes structure to be instantiated with leaked data
Return Value:
An instance of the provided struct class, with the leaked data decoded
Examples:
>>> @pwnlib.memleak.MemLeak
... def leaker(addr):
... return "A"
>>> e = leaker.struct(0, pwnlib.elf.Elf32_Phdr)
>>> hex(e.p_paddr)
'0x41414141'
"""
size = sizeof(struct)
size = ctypes.sizeof(struct)
data = self.n(address, size)
obj = struct.from_buffer_copy(data)
return obj
@@ -118,6 +129,8 @@ class MemLeak(object):
size = obj.size
offset = obj.offset
data = self.n(address + offset, size)
if not data:
return None
return unpack(data, size*8)
def field_compare(self, address, obj, expected):
+5 -1
View File
@@ -224,6 +224,8 @@ 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
@@ -333,7 +335,9 @@ 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 not (xchg or tmp):
#
# If there are no cycles, there's no need for a temporary register.
if in_cycle and not (xchg or tmp):
for reg in in_out:
if not depends_on_cycle(reg, in_out, in_cycle):
tmp = reg
+3
View File
@@ -700,6 +700,9 @@ class ROP(object):
# If we can find a function with that name, just call it
if isinstance(resolvable, str):
addr = self.resolve(resolvable)
elif hasattr(resolvable, 'name') and hasattr(resolvable, 'address'):
addr = resolvable.address
resolvable = str(resolvable.name)
else:
addr = resolvable
resolvable = ''
+7 -1
View File
@@ -1,6 +1,8 @@
import os
from collections import defaultdict
from ..context import context
__all__ = ['make_function']
loaded = {}
@@ -33,10 +35,14 @@ def init_mako():
render_global = IsInside()
cache = context.cache_dir
if cache:
cache = os.path.join(cache, 'mako')
curdir = os.path.dirname(os.path.abspath(__file__))
lookup = TemplateLookup(
directories = [os.path.join(curdir, 'templates')],
module_directory = os.path.expanduser('~/.pwntools-cache/mako')
module_directory = cache
)
# The purpose of this definition is to create a new Tag.
+31 -15
View File
@@ -61,17 +61,26 @@ Example:
mov rax, 0x1010110dfac01fe
xor [rsp], rax
pop rax
>>> with context.local(os = 'linux'):
... print shellcraft.amd64.mov('eax', 'SYS_read').rstrip()
xor eax, eax /* (SYS_read) */
>>> with context.local(os = 'freebsd'):
... print shellcraft.amd64.mov('eax', 'SYS_read').rstrip()
push (SYS_read) /* 3 */
pop rax
>>> with context.local(os = 'linux'):
... print shellcraft.amd64.mov('eax', 'PROT_READ | PROT_WRITE | PROT_EXEC').rstrip()
push (PROT_READ | PROT_WRITE | PROT_EXEC) /* 7 */
pop rax
>>> print shellcraft.amd64.mov('rax', 0xffffffff).rstrip()
mov eax, 0xffffffff
>>> print shellcraft.amd64.mov('rax', 0x7fffffff).rstrip()
mov eax, 0x7fffffff
>>> print shellcraft.amd64.mov('rax', 0x80010101).rstrip()
mov eax, 0x80010101
>>> print shellcraft.amd64.mov('rax', 0x80000000).rstrip()
mov eax, 0x1010101 /* 2147483648 == 0x80000000 */
xor eax, 0x81010101
>>> with context.local(os = 'linux'):
... print shellcraft.amd64.mov('eax', 'SYS_read').rstrip()
xor eax, eax /* (SYS_read) */
>>> with context.local(os = 'freebsd'):
... print shellcraft.amd64.mov('eax', 'SYS_read').rstrip()
push (SYS_read) /* 3 */
pop rax
>>> with context.local(os = 'linux'):
... print shellcraft.amd64.mov('eax', 'PROT_READ | PROT_WRITE | PROT_EXEC').rstrip()
push (PROT_READ | PROT_WRITE | PROT_EXEC) /* 7 */
pop rax
Args:
dest (str): The destination register.
@@ -99,7 +108,6 @@ if get_register(src):
if dest.size == 64 and src.size <= 32:
dest = get_register(dest.native32)
src_size = src.size
else:
with ctx.local(arch = 'amd64'):
src = eval(src)
@@ -107,9 +115,9 @@ else:
if not dest.fits(src):
log.error("cannot mov %s, %r: dest is smaller than src" % (dest, src))
src_size = bits_required(src)
orig_dest = dest
if dest.size == 64 and src_size <= 32:
if dest.size == 64 and bits_required(src) <= 32:
dest = get_register(dest.native32)
# Calculate the packed version
@@ -117,7 +125,15 @@ else:
# Calculate the unsigned and signed versions
srcu = packing.unpack(srcp, dest.size, sign=False)
srcs = packing.unpack(srcp, dest.size, sign=True)
# N.B.: We may have downsized the register for e.g. mov('rax', 0xffffffff)
# In this case, srcp is now a 4-byte packed value, which will expand
# to "-1", which isn't correct.
if orig_dest.size != dest.size:
srcs = src
else:
srcs = packing.unpack(srcp, dest.size, sign=True)
%>\
% if is_register(src):
% if src == dest:
+2 -2
View File
@@ -6,8 +6,8 @@ in the same order.
pop rdi
pop rsi
pop rbp
pop rsp
pop rbp
pop rbx /* add rsp, 8 */
pop rbx
pop rdx
pop rcx
pop rax
+59 -27
View File
@@ -3,6 +3,7 @@ import errno
import fcntl
import logging
import os
import platform
import pty
import resource
import select
@@ -190,6 +191,9 @@ class process(tube):
>>> io = process(['sh','-c','sleep 10; exit 7'], alarm=2)
>>> io.poll(block=True) == -signal.SIGALRM
True
>>> binary = ELF.from_assembly('nop', arch='mips')
>>> p = process(binary.path)
"""
PTY = PTY
@@ -197,7 +201,7 @@ class process(tube):
#: Have we seen the process stop?
_stop_noticed = False
def __init__(self, argv,
def __init__(self, argv = None,
shell = False,
executable = None,
cwd = None,
@@ -217,6 +221,14 @@ class process(tube):
alarm = None):
super(process, self).__init__(timeout, level = level)
# Permit using context.binary
if argv is None:
if context.binary:
argv = [context.binary.path]
else:
raise TypeError('Must provide argv or set context.binary')
#: `subprocess.Popen` object
self.proc = None
@@ -276,7 +288,7 @@ class process(tube):
with self.progress(message) as p:
if not self.aslr:
log.warn_once("ASLR is disabled!")
self.warn_once("ASLR is disabled!")
# In the event the binary is a foreign architecture,
# and binfmt is not installed (e.g. when running on
@@ -286,12 +298,6 @@ class process(tube):
executables = [executable]
exception = None
try:
if not context.native:
qemu = get_qemu_user()
prefixes.append(([qemu], qemu))
except: pass
for prefix, executable in prefixes:
try:
self.proc = subprocess.Popen(args = prefix + argv,
@@ -308,11 +314,7 @@ class process(tube):
except OSError as exception:
if exception.errno != errno.ENOEXEC:
raise
else:
try:
raise exception
except:
log.exception(str(prefixes))
prefixes.append(self.__on_enoexec(exception))
if self.pty is not None:
if stdin is slave:
@@ -349,7 +351,7 @@ class process(tube):
resource.setrlimit(resource.RLIMIT_STACK, (-1, -1))
except:
log.exception("Could not disable ASLR")
self.exception("Could not disable ASLR")
# Assume that the user would prefer to have core dumps.
resource.setrlimit(resource.RLIMIT_CORE, (-1, -1))
@@ -373,13 +375,43 @@ class process(tube):
self.preexec_fn()
def __on_enoexec(self, exception):
"""We received an 'exec format' error (ENOEXEC)
This implies that the user tried to execute e.g.
an ARM binary on a non-ARM system, and does not have
binfmt helpers installed for QEMU.
"""
# Get the ELF binary for the target executable
with context.quiet:
# XXX: Cyclic imports :(
from ..elf import ELF
binary = ELF(self.executable)
# If we're on macOS, this will never work. Bail now.
# if platform.mac_ver()[0]:
# self.error("Cannot run ELF binaries on macOS")
# Determine what architecture the binary is, and find the
# appropriate qemu binary to run it.
qemu = get_qemu_user(arch=binary.arch)
if qemu:
args = [qemu]
if self.argv:
args += ['-0', self.argv[0]]
args += ['--']
return [args, qemu]
# If we get here, we couldn't run the binary directly, and
# we don't have a qemu which can run it.
self.exception(exception)
@property
def program(self):
"""Alias for ``executable``, for backward compatibility"""
return self.executable
@staticmethod
def _validate(cwd, executable, argv, env):
def _validate(self, cwd, executable, argv, env):
"""
Perform extended validation on the executable path, argv, and envp.
@@ -398,14 +430,14 @@ class process(tube):
argv = [argv]
if not all(isinstance(arg, (str, unicode)) for arg in argv):
log.error("argv must be strings: %r" % argv)
self.error("argv must be strings: %r" % argv)
# Create a duplicate so we can modify it
argv = list(argv or [])
for i, arg in enumerate(argv):
if '\x00' in arg[:-1]:
log.error('Inappropriate nulls in argv[%i]: %r' % (i, arg))
self.error('Inappropriate nulls in argv[%i]: %r' % (i, arg))
argv[i] = arg.rstrip('\x00')
@@ -417,7 +449,7 @@ class process(tube):
#
if not executable:
if not argv:
log.error("Must specify argv or executable")
self.error("Must specify argv or executable")
executable = argv[0]
# Do not change absolute paths to binaries
@@ -437,11 +469,11 @@ class process(tube):
executable = os.path.join(cwd, executable)
if not os.path.exists(executable):
log.error("%r does not exist" % executable)
self.error("%r does not exist" % executable)
if not os.path.isfile(executable):
log.error("%r is not a file" % executable)
self.error("%r is not a file" % executable)
if not os.access(executable, os.X_OK):
log.error("%r is not marked as executable (+x)" % executable)
self.error("%r is not marked as executable (+x)" % executable)
#
# Validate environment
@@ -455,13 +487,13 @@ class process(tube):
for k,v in env.items():
if not isinstance(k, (str, unicode)):
log.error('Environment keys must be strings: %r' % k)
self.error('Environment keys must be strings: %r' % k)
if not isinstance(k, (str, unicode)):
log.error('Environment values must be strings: %r=%r' % (k,v))
self.error('Environment values must be strings: %r=%r' % (k,v))
if '\x00' in k[:-1]:
log.error('Inappropriate nulls in env key: %r' % (k))
self.error('Inappropriate nulls in env key: %r' % (k))
if '\x00' in v[:-1]:
log.error('Inappropriate nulls in env value: %r=%r' % (k, v))
self.error('Inappropriate nulls in env value: %r=%r' % (k, v))
env[k.rstrip('\x00')] = v.rstrip('\x00')
@@ -771,7 +803,7 @@ class process(tube):
"""
# If it's running under qemu-user, don't leak anything.
if 'qemu-' in os.path.realpath('/proc/%i/exe' % self.pid):
log.error("Cannot use leaker on binaries under QEMU.")
self.error("Cannot use leaker on binaries under QEMU.")
with open('/proc/%i/mem' % self.pid, 'rb') as mem:
mem.seek(address)
+17 -25
View File
@@ -108,38 +108,30 @@ class remote(sock):
sock = None
timeout = self.timeout
h = self.waitfor('Opening connection to %s on port %d' % (self.rhost, self.rport))
with self.waitfor('Opening connection to %s on port %d' % (self.rhost, self.rport)) as h:
for res in socket.getaddrinfo(self.rhost, self.rport, fam, typ, 0, socket.AI_PASSIVE):
self.family, self.type, self.proto, _canonname, sockaddr = res
for res in socket.getaddrinfo(self.rhost, self.rport, fam, typ, 0, socket.AI_PASSIVE):
self.family, self.type, self.proto, _canonname, sockaddr = res
if self.type not in [socket.SOCK_STREAM, socket.SOCK_DGRAM]:
continue
if self.type not in [socket.SOCK_STREAM, socket.SOCK_DGRAM]:
continue
h.status("Trying %s" % sockaddr[0])
h.status("Trying %s" % sockaddr[0])
sock = socket.socket(self.family, self.type, self.proto)
sock = socket.socket(self.family, self.type, self.proto)
if timeout != None and timeout <= 0:
sock.setblocking(0)
else:
sock.setblocking(1)
sock.settimeout(timeout)
if timeout != None and timeout <= 0:
sock.setblocking(0)
else:
sock.setblocking(1)
sock.settimeout(timeout)
try:
sock.connect(sockaddr)
break
except socket.error:
pass
else:
h.failure()
try:
sock.connect(sockaddr)
return sock
except socket.error:
pass
self.error("Could not connect to %s on port %d" % (self.rhost, self.rport))
h.success()
return sock
@classmethod
def fromsocket(cls, socket):
"""
+29 -7
View File
@@ -291,7 +291,7 @@ class ssh_channel(sock):
"""Retrieve the address of an environment variable in the remote process.
"""
if not hasattr(self, 'argv'):
log.error("Can only call getenv() on ssh_channel objects created with ssh.process")
self.error("Can only call getenv() on ssh_channel objects created with ssh.process")
argv0 = self.argv[0]
@@ -668,8 +668,12 @@ class ssh(Timeout, Logger):
See ``stdin``.
preexec_fn(callable):
Function which is executed on the remote side before execve().
This **MUST** be a self-contained function -- it must perform
all of its own imports, and cannot refer to variables outside
its scope.
preexec_args(object):
Argument passed to ``preexec_fn``.
This **MUST** only consist of native Python objects.
raw(bool):
If ``True``, disable TTY control code interpretation.
aslr(bool):
@@ -716,6 +720,23 @@ class ssh(Timeout, Logger):
''
>>> s.process('/usr/bin/env', env={'A':'B'}).recvall()
'A=B\n'
>>> s.process('false', preexec_fn=1234)
Traceback (most recent call last):
...
PwnlibException: preexec_fn must be a function
>>> s.process('false', preexec_fn=lambda: 1234)
Traceback (most recent call last):
...
PwnlibException: preexec_fn cannot be a lambda
>>> def uses_globals():
... foo = bar
>>> print s.process('false', preexec_fn=uses_globals).recvall().strip() # doctest: +ELLIPSIS
Traceback (most recent call last):
...
NameError: global name 'bar' is not defined
"""
if not argv and not executable:
self.error("Must specify argv or executable")
@@ -767,15 +788,16 @@ class ssh(Timeout, Logger):
# Allow the user to provide a self-contained function to run
def func(): pass
func = preexec_fn or func
func_src = inspect.getsource(func).strip()
func_name = func.__name__
func_args = preexec_args
if not isinstance(func, types.FunctionType):
log.error("preexec_fn must be a function")
if func_name == (lambda: 0).__name__:
log.error("preexec_fn cannot be a lambda")
self.error("preexec_fn must be a function")
func_name = func.__name__
if func_name == (lambda: 0).__name__:
self.error("preexec_fn cannot be a lambda")
func_src = inspect.getsource(func).strip()
setuid = setuid if setuid is None else bool(setuid)
script = r"""
@@ -1454,7 +1476,7 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
if os.path.isdir(file_or_directory):
return self.upload_dir(file_or_directory, remote)
log.error('%r does not exist' % file_or_directory)
self.error('%r does not exist' % file_or_directory)
def download(self, file_or_directory, remote=None):
+29
View File
@@ -826,6 +826,35 @@ class tube(Timeout, Logger):
while t.is_alive():
t.join(timeout = 0.1)
def stream(self, line_mode=True):
"""stream()
Receive data until the tube exits, and print it to stdout.
Similar to :func:`interactive`, except that no input is sent.
Similar to ``print tube.recvall()`` except that data is printed
as it is received, rather than after all data is received.
Arguments:
line_mode(bool): Whether to receive line-by-line or raw data.
Returns:
All data printed.
"""
buf = Buffer()
function = self.recvline if line_mode else self.recv
try:
while True:
buf.add(function())
sys.stdout.write(buf.data[-1])
except KeyboardInterrupt:
pass
except EOFError:
pass
return buf.get()
def clean(self, timeout = 0.05):
"""clean(timeout = 0.05)
+22 -4
View File
@@ -53,7 +53,11 @@ def available_on_pypi(prerelease=current_version.is_prerelease):
def cache_file():
"""Returns the path of the file used to cache update data, and ensures that it exists."""
cache_dir = os.path.expanduser('~/.pwntools-cache')
cache_dir = context.cache_dir
if not cache_dir:
return None
cache_file = os.path.join(cache_dir, 'update')
if not os.path.isdir(cache_dir):
@@ -66,12 +70,23 @@ def cache_file():
def last_check():
"""Return the date of the last check"""
return os.path.getmtime(cache_file())
cache = cache_file()
if cache:
return os.path.getmtime(cache_file())
# Fallback
return time.time()
def should_check():
"""Return True if we should check for an update"""
if read(cache_file()).strip() == 'never':
filename = cache_file()
if not filename:
return False
if read(filename).strip() == 'never':
return False
return time.time() > (last_check() + update_freq)
def perform_check(prerelease=current_version.is_prerelease):
@@ -109,7 +124,10 @@ def perform_check(prerelease=current_version.is_prerelease):
where = None
command = None
os.utime(cache_file(), None)
cache = cache_file()
if cache:
os.utime(cache, None)
if best == current_version:
log.info("You have the latest version of Pwntools (%s)" % best)
+92 -11
View File
@@ -1,10 +1,10 @@
"""Module for calculating CRC-sums.
Contains all crc implementations know on the interwebz. For most implementations it
contains only the core crc algorithm and not e.g. padding schemes.
Contains all crc implementations know on the interwebz. For most implementations
it contains only the core crc algorithm and not e.g. padding schemes.
It is horribly slow, as implements a naive algorithm working direclty on
bit polynomials.
bit polynomials. This class is exposed as `BitPolynom`.
The current algorithm is super-linear and takes about 4 seconds to calculate
the crc32-sum of ``'A'*40000``.
@@ -18,15 +18,70 @@ import types
from . import known
from .. import fiddling
from .. import packing
from .. import safeeval
class BitPolynom(object):
"""Class for representing GF(2)[X], i.e. the field of polynomials over
GF(2).
In practice the polynomials are represented as numbers such that `x**n`
corresponds to `1 << n`. In this representation calculations are easy: Just
do everything as normal, but forget about everything the carries.
Addition becomes xor and multiplication becomes carry-less multiplication.
Examples:
>>> p1 = BitPolynom("x**3 + x + 1")
>>> p1
BitPolynom('x**3 + x + 1')
>>> int(p1)
11
>>> p1 == BitPolynom(11)
True
>>> p2 = BitPolynom("x**2 + x + 1")
>>> p1 + p2
BitPolynom('x**3 + x**2')
>>> p1 * p2
BitPolynom('x**5 + x**4 + 1')
>>> p1 / p2
BitPolynom('x + 1')
>>> p1 % p2
BitPolynom('x')
>>> d, r = divmod(p1, p2)
>>> d * p2 + r == p1
True
>>> BitPolynom(-1)
Traceback (most recent call last):
...
ValueError: Polynomials cannot be negative: -1
>>> BitPolynom('y')
Traceback (most recent call last):
...
ValueError: Not a valid polynomial: y
"""
def __init__(self, n):
if not isinstance(n, (int, long)):
raise TypeError("Polynomial must be called with an integer or a list")
if n < 0:
raise ValueError("Polynomials cannot be negative: %d" % n)
self.n = n
if isinstance(n, (str, unicode)):
self.n = 0
x = BitPolynom(2)
try:
for p in n.split('+'):
k = safeeval.values(p.strip(), {'x': x, 'X': x})
assert isinstance(k, (BitPolynom, int, long))
assert k >= 0
self.n ^= int(k)
except (ValueError, NameError, AssertionError):
raise ValueError("Not a valid polynomial: %s" % n)
elif isinstance(n, (int, long)):
if n >= 0:
self.n = n
else:
raise ValueError("Polynomials cannot be negative: %d" % n)
else:
raise TypeError("Polynomial must be called with a string or integer")
def __int__(self):
return self.n
@@ -126,7 +181,30 @@ class BitPolynom(object):
def __rrshift__(self, other):
return BitPolynom(int(other) >> int(self))
def __pow__(self, other):
r = BitPolynom(1)
for _ in range(other):
r *= self
return r
def degree(self):
"""Returns the degree of the polynomial.
Examples:
>>> BitPolynom(0).degree()
0
>>> BitPolynom(1).degree()
0
>>> BitPolynom(2).degree()
1
>>> BitPolynom(7).degree()
2
>>> BitPolynom((1 << 10) - 1).degree()
9
>>> BitPolynom(1 << 10).degree()
10
"""
return max(0, int(self).bit_length()-1)
def __repr__(self):
@@ -134,17 +212,20 @@ class BitPolynom(object):
return '0'
out = []
for n in range(self.degree(), 0, -1):
for n in range(self.degree(), 1, -1):
if int(self) & (1 << n):
out.append("x**%d" % n)
if int(self) & 2:
out.append("x")
if int(self) & 1:
out.append("1")
return ' + '.join(out)
return 'BitPolynom(%r)' % ' + '.join(out)
class Module(types.ModuleType):
def __init__(self):
super(Module, self).__init__(__name__)
self._cached_crcs = None
self.BitPolynom = BitPolynom
self.__dict__.update({
'__file__' : __file__,
'__package__' : __package__,
@@ -154,7 +235,7 @@ class Module(types.ModuleType):
crcs = known.all_crcs
if attr == '__all__':
return ['generic_crc', 'cksum', 'find_crc_function'] + sorted(crcs.keys())
return ['BitPolynom', 'generic_crc', 'cksum', 'find_crc_function'] + sorted(crcs.keys())
info = crcs.get(attr, None)
if not info:
+99 -5
View File
@@ -43,8 +43,8 @@ def de_bruijn(alphabet = string.ascii_lowercase, n = None):
def cyclic(length = None, alphabet = string.ascii_lowercase, n = None):
"""cyclic(length = None, alphabet = string.ascii_lowercase, n = 4) -> list/str
A simple wrapper over :func:`de_bruijn`. This function returns a
at most `length` elements.
A simple wrapper over :func:`de_bruijn`. This function returns at most
`length` elements.
If the given alphabet is a string, a string is returned from this function. Otherwise
a list is returned.
@@ -66,6 +66,10 @@ def cyclic(length = None, alphabet = string.ascii_lowercase, n = None):
if n is None:
n = 4
if len(alphabet) ** n < length:
log.error("Can't create a pattern length=%i with len(alphabet)==%i and n==%i" \
% (length, len(alphabet), n))
out = []
for ndx, c in enumerate(de_bruijn(alphabet, n)):
if length != None and ndx >= length:
@@ -93,8 +97,8 @@ def cyclic_find(subseq, alphabet = string.ascii_lowercase, n = None):
https://www.sciencedirect.com/science/article/pii/S0012365X00001175
Arguments:
subseq: The subsequence to look for. This can either be a string, a list
or an integer. If an integer is provided it will be packed as a
subseq: The subsequence to look for. This can be a string, a list or an
integer. If an integer is provided it will be packed as a
little endian integer.
alphabet: List or string to generate the sequence over.
n(int): The length of subsequences that should be unique.
@@ -124,8 +128,98 @@ def cyclic_find(subseq, alphabet = string.ascii_lowercase, n = None):
return _gen_find(subseq, de_bruijn(alphabet, n))
def metasploit_pattern(sets = None):
"""metasploit_pattern(sets = [ string.ascii_uppercase, string.ascii_lowercase, string.digits ]) -> generator
Generator for a sequence of characters as per Metasploit Framework's
`Rex::Text.pattern_create` (aka `pattern_create.rb`).
The returned generator will yield up to
``len(sets) * reduce(lambda x,y: x*y, map(len, sets))`` elements.
Arguments:
sets: List of strings to generate the sequence over.
"""
sets = sets or [ string.ascii_uppercase, string.ascii_lowercase, string.digits ]
offsets = [ 0 ] * len(sets)
offsets_indexes_reversed = list(reversed(range(len(offsets))))
while True:
for i, j in zip(sets, offsets):
yield i[j]
# increment offsets with cascade
for i in offsets_indexes_reversed:
offsets[i] = (offsets[i] + 1) % len(sets[i])
if offsets[i] != 0:
break
# finish up if we've exhausted the sequence
if offsets == [ 0 ] * len(sets):
return
def cyclic_metasploit(length = None, sets = None):
"""cyclic_metasploit(length = None, sets = [ string.ascii_uppercase, string.ascii_lowercase, string.digits ]) -> str
A simple wrapper over :func:`metasploit_pattern`. This function returns a
string of length `length`.
Arguments:
length: The desired length of the string or None if the entire sequence is desired.
sets: List of strings to generate the sequence over.
Example:
>>> cyclic_metasploit(32)
'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab'
>>> cyclic_metasploit(sets = ["AB","ab","12"])
'Aa1Aa2Ab1Ab2Ba1Ba2Bb1Bb2'
>>> cyclic_metasploit()[1337:1341]
'5Bs6'
>>> len(cyclic_metasploit())
20280
"""
sets = sets or [ string.ascii_uppercase, string.ascii_lowercase, string.digits ]
out = []
for ndx, c in enumerate(metasploit_pattern(sets)):
if length != None and ndx >= length:
break
else:
out.append(c)
out = ''.join(out)
if len(out) < length:
log.error("Can't create a pattern of length %i with sets of lengths %s. Maximum pattern length is %i." \
% (length, map(len, sets), len(out)))
return ''.join(out)
def cyclic_metasploit_find(subseq, sets = None):
"""cyclic_metasploit_find(subseq, sets = [ string.ascii_uppercase, string.ascii_lowercase, string.digits ]) -> int
Calculates the position of a substring into a Metasploit Pattern sequence.
Arguments:
subseq: The subsequence to look for. This can be a string or an
integer. If an integer is provided it will be packed as a
little endian integer.
sets: List of strings to generate the sequence over.
Examples:
>>> cyclic_metasploit_find(cyclic_metasploit(1000)[514:518])
514
>>> cyclic_metasploit_find(0x61413161)
4
"""
sets = sets or [ string.ascii_uppercase, string.ascii_lowercase, string.digits ]
if isinstance(subseq, (int, long)):
subseq = packing.pack(subseq, 'all', 'little', False)
return _gen_find(subseq, metasploit_pattern(sets))
def _gen_find(subseq, generator):
"""Returns the first position of subseq in the generator or -1 if there is no such position."""
"""Returns the first position of `subseq` in the generator or -1 if there is no such position."""
subseq = list(subseq)
pos = 0
saved = []
+37 -15
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import base64
import random
import os
import re
import string
import StringIO
@@ -10,7 +11,7 @@ from . import packing
from ..context import context
from ..log import getLogger
from ..term import text
from .cyclic import cyclic
from .cyclic import de_bruijn
from .cyclic import cyclic_find
log = getLogger(__name__)
@@ -559,13 +560,15 @@ default_style = {
}
cyclic_pregen = ''
de_bruijn_gen = de_bruijn()
def sequential_lines(a,b):
return (a+b) in cyclic_pregen
def update_cyclic_pregenerated(size):
global cyclic_pregen
cyclic_pregen = cyclic(size)
while size > len(cyclic_pregen):
cyclic_pregen += de_bruijn_gen.next()
def hexdump_iter(fd, width=16, skip=True, hexii=False, begin=0, style=None,
highlight=None, cyclic=False):
@@ -627,30 +630,49 @@ def hexdump_iter(fd, width=16, skip=True, hexii=False, begin=0, style=None,
return hbyte, abyte
cache = [style_byte(chr(b)) for b in range(256)]
if cyclic:
update_cyclic_pregenerated(len(s))
numb = 0
while True:
offset = begin + numb
chunk = fd.read(width)
# If a tube is passed in as fd, it will raise EOFError when it runs
# out of data, unlike a file or StringIO object, which return an empty
# string.
try:
chunk = fd.read(width)
except EOFError:
chunk = ''
# We have run out of data, exit the loop
if chunk == '':
break
# Advance the cursor by the number of bytes we actually read
numb += len(chunk)
# Update the cyclic pattern in case
if cyclic:
update_cyclic_pregenerated(numb)
# If this chunk is the same as the last unique chunk,
# use a '*' instead.
if skip and \
(last_unique == chunk or \
(cyclic and sequential_lines(last_unique, chunk))):
if skip and last_unique:
same_as_last_line = (last_unique == chunk)
lines_are_sequential = (cyclic and sequential_lines(last_unique, chunk))
last_unique = chunk
if not skipping:
yield '*'
skipping = True
continue
# Chunk is unique, save for next iteration
last_unique = chunk
if same_as_last_line or lines_are_sequential:
# If we have not already printed a "*", do so
if not skipping:
yield '*'
skipping = True
# Move on to the next chunk
continue
# Chunk is unique, no longer skipping
skipping = False
last_unique = chunk
# Generate contents for line
hexbytes = ''
+1 -1
View File
@@ -1 +1 @@
__version__ = '3.2.0beta4'
__version__ = '3.3.4'
+1 -1
View File
@@ -83,7 +83,7 @@ except ImportError:
setup(
name = 'pwntools',
packages = find_packages(),
version = '3.2.0beta4',
version = '3.3.4',
data_files = [('',
glob.glob('*.md') + glob.glob('*.txt')),
],
+7 -1
View File
@@ -72,7 +72,13 @@ setup_android_emulator()
# If we are running on Travis CI, and there were no changes to Android
# or ADB code, then we do not need the emulator
if [ -n "$TRAVIS" ]; then
if ! (git log --stat "$TRAVIS_COMMIT_RANGE" | grep -E "android|adb"); then
if [ -z "$TRAVIS_COMMIT_RANGE" ]; then
echo "TRAVIS_COMMIT_RANGE is empty, forcing Android Emulator installation"
elif ! git show "$TRAVIS_COMMIT_RANGE" ; then
echo "TRAVIS_COMMIT_RANGE is invalid, forcing Android Emulator installation"
elif (git log --stat "$TRAVIS_COMMIT_RANGE" | grep -iE "android|adb"); then
echo "Found Android-related commits, forcing Android Emulator installation"
else
# In order to avoid running the doctests that require the Android
# emulator, while still leaving the code intact, we remove the
# RST file that Sphinx searches.
+1 -1
View File
@@ -11,5 +11,5 @@ if [ "$TRAVIS_BRANCH" = "stable" ] && [ ! -z "$TRY_KEY" ]; then
ssh-add ~/.ssh/try_key
# Update!
ssh [email protected] -- 'docker/update'
ssh -o "StrictHostKeyChecking no" [email protected] -- 'docker/update'
fi