Compare commits

...
88 Commits
Author SHA1 Message Date
Zach Riggle 74f475170d Bump version to 3.12.2 2019-01-07 15:21:39 -06:00
Kyle Zeng 0dc0a9acc0 check qemu_path correctly and give out a reasonable error msg (#1243) 2019-01-07 14:42:24 -06:00
Zach Riggle 774ed3b85e Do not use intervaltree 3.xx (#1242)
In the future, we may want to update Pwntools to use the newer version.

In the meantime, pin to 2.xx
2018-12-28 23:19:54 -06:00
joesixpack666 292b81af17 close ssh_channel properly in download() (#1208) (#1209)
* close ssh_channel properly in download() (#1208)

* Update ssh.py
2018-10-17 13:14:20 -05:00
Zach Riggle 90056fbeca Version bump to 3.12.1 2018-09-17 17:11:45 -05:00
Zach Riggle 60df8107c0 corefile: Update NT_XXX type names for pyelftools==0.25 (#1198)
* corefile: Update NT_XXX type names for pyelftools==0.25

* travis: Do not use Sphinx version 1.8.0, it fails with recursive errors

* corefile: Fix stack detection and Mapping.__contains__ logic

* corefile: Better detection of AT_EXECFN when NT_AUXV does not have it

* corefile: Avoid trying to parse corrupt stacks

* Fix doctests for new warning

* log: Fix warning_once caching
2018-09-17 17:04:40 -05:00
Ricky Zhou 7467402ecf Work around renamed ENUM_P_TYPE variables in elftools 0.25 and up. 2018-09-17 14:07:24 -05:00
Zach Riggle 80e12c7031 Switch FTP servers to ftp.ubuntu.com
Fixes Gallopsled/pwntools#1172
2018-07-02 16:57:16 -05:00
pwn f4159bdad4 check if ":" exists before index(':') (#1159)
* check if ":" exists before index(':')

* Update proc.py
2018-06-04 11:13:03 -05:00
Zach Riggle 26e6bce81a Fix broken versions of package dependencies (#1162)
Fixes Gallopsled/pwntools#971
Fixes Gallopsled/pwntools#1154
Fixes Gallopsled/pwntools#1160
2018-05-31 15:22:45 -05:00
Inndy 6af09645e7 Prevent crash from not existed cache file in ssh.checksec() (#1150)
* Prevent crash from not existed cache file in ssh.checksec()

* Use path.exists instead of exception handling

* Fix space

* Update ssh.py
2018-05-30 14:35:23 -05:00
Bruce Chen 146348f514 fix wrong gadget selection in setRegisters() (#1118) (#1156)
* fix wrong gadget selection in setRegisters() (#1118)

* Fix detection logic

* Update rop.py

* add test cases for filtering out the bad instructions in rop.py
2018-05-30 13:31:33 -05:00
Michael Rodler fe6619b336 fixed called to qemu.ld_prefix passing env dict as target (#1152) 2018-05-25 16:21:57 -05:00
Bruce Chen 5ae13e8c8c switch to trusty build in travis ci and fix some test cases (#1155) 2018-05-25 14:38:06 -05:00
Alexey Vishnyakov 67473560c7 Check libc- as well (#1131) 2018-04-02 14:03:38 -05:00
Tyler Lambert 1cb0502157 Replace string format char (#1125)
* replace string format char

* add parentheses
2018-03-19 16:26:03 -05:00
Zach Riggle 9604f7847e Fix tests relying on broken autocontrib version (#1121) 2018-02-28 15:09:35 -06:00
Mathias Svensson c888a8cbe0 Release 3.12.0 2018-02-22 16:42:25 +01:00
Mathias Svensson 9f9d884790 Merge branch 'beta' into stable 2018-02-22 13:54:37 +01:00
Mathias Svensson 39bc5808ea Update CHANGELOG from dev 2018-02-22 13:54:19 +01:00
Zach Riggle 31f7c61c1f Merge remote-tracking branch 'stable' into beta 2018-02-20 14:49:38 -06:00
Zach Riggle 2a73356c78 Fix test breakage caused by Sphinx deprecation (#1113)
* Fix test breakage caused by Sphinx deprecation

* Fix missing import in RST

Closes #1112
2018-02-20 14:49:09 -06:00
david942j 25c84daa8a Fix usage of syscall open in shellcraft templates (#1106) 2018-02-09 12:53:00 -06:00
Christophe GRENIER 3ff7a9e940 Fix for very old openssl: try sha256 and sha256sum before "openssl sha256" (#1068)
Check for sha256 and sha256sum before trying openssl sha256 as "openssl sha256" doesn't return an error code in some old versions of openssl (OpenSSL 0.9.8g 19 Oct 2007 at least) that doesn't support sha256.
2018-01-23 16:58:56 -08:00
Mathias Svensson e50b978398 Release 3.12.0beta0 2018-01-03 08:21:05 +01:00
Mathias Svensson d689805690 Release 3.11.0 2018-01-03 08:19:05 +01:00
Zach Riggle 953e73aa37 Update changelog for 3.11 and 3.12 2018-01-03 08:03:28 +01:00
Mathias Svensson ee9f9fa7de Merge branch 'stable' into beta 2018-01-03 08:02:50 +01:00
Zach Riggle 35789287d8 Update changelog for 3.11 and 3.12 2018-01-02 12:53:30 -06:00
Zach Riggle 7a1ab829eb Expose pwnlib.tubes.server.server in the toplevel 2018-01-02 12:47:43 -06:00
Benno Fünfstück 46a1c44fc9 elf.py: fix variable name typo (fixes dt_rpath undefined var) (#1008) 2018-01-02 11:36:08 -06:00
Zach Riggle 899baec704 Android bootldr extraction, gdb fixes (#1085)
* Add sysroot= argument to gdb.debug, expose via 'pwn debug'

Also sets a sane default sysroot for Android debugging

* Add missing newline for "set deatch-on-fork off"

Introduced in e89a9b66ac

* Add support for extracting Android boot.img

* Fix incorrect variable in 'pwn debug'

* Ensure "file /path/to/file" is the first command
2017-12-21 15:18:38 -06:00
Disconnect3d 932cb57bf9 Better error message when gdbserver fails on LD_PRELOAD (#1083)
When one passes a `LD_PRELOAD` which cannot be preloaded the `process_created` string might end up looking like this:
```
"ERROR: ld.so: object '/bin/bash' from LD_PRELOAD cannot be preloaded (cannot dynamically load executable): ignored.\n"
```

When we end up in such situation, pwndbg shown unintuitive error:
```
$ python -c 'from pwn import *; gdb.debug("/bin/ls", env={"LD_PRELOAD": "/bin/bash"})'
[+] Starting local process '/usr/bin/gdbserver': pid 20691
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "pwnlib/context/__init__.py", line 1349, in setter
    return function(*a, **kw)
  File "pwnlib/gdb.py", line 397, in debug
    port = _gdbserver_port(gdbserver, ssh)
  File "pwnlib/gdb.py", line 234, in _gdbserver_port
    gdbserver.pid   = int(process_created.split()[-1], 0)
ValueError: invalid literal for int() with base 0: 'ignored.'
```

After this patch it will show:
```
$ python -c 'from pwn import *; gdb.debug("/bin/ls", env={"LD_PRELOAD": "/bin/bash"})'
[+] Starting local process '/usr/bin/gdbserver': pid 20863
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "pwnlib/context/__init__.py", line 1349, in setter
    return function(*a, **kw)
  File "pwnlib/gdb.py", line 402, in debug
    port = _gdbserver_port(gdbserver, ssh)
  File "pwnlib/gdb.py", line 236, in _gdbserver_port
    'Failed to spawn process under gdbserver. gdbserver error message: %s' % process_created
ValueError: Failed to spawn process under gdbserver. gdbserver error message: ERROR: ld.so: object '/bin/bash' from LD_PRELOAD cannot be preloaded (cannot dynamically load executable): ignored.

[*] Stopped process '/bin/ls' (pid 20863)
```
2017-12-12 14:22:43 -06:00
Zach Riggle 6a61617c73 Fix emulation of fault_addr (#1066)
As @bennofs noticed, our detection logic was incorrect:

"This is not a good test if I understand the issue correctly.

From my tests, it seems that fault_addr was only null before this patch if it
would've been >= 2**47.

This is because on 64 bit, that's where kernel memory is mapped so we get
"protection fault" instead of "segfault".

A protection fault does not have the same siginfo as a normal segfault:
si_code will be SI_KERNEL = 0x80 and there won't be a fault_addr

(see http://elixir.free-electrons.com/linux/v4.14-rc8/source/kernel/signal.c#L1052)"
2017-12-11 17:48:25 -06:00
Zach Riggle 6944b664e8 Switch server to ftp.ubuntu.org (#1084)
Currently ftp.debian.org is down on port 21

(cherry picked from commit ec5185c0c4)
2017-12-11 17:47:21 -06:00
Devin Neal ab8879afa3 Add server module (#1067)
* add server module

* modify example

* fix travis errors

* add queuing and blocking, fix requests

* add server.rst, change accepter to daemon

* fix import in server.rst

* added server entry in sockets.rst

* fix import in sockets.rst
2017-12-11 17:27:51 -06:00
bannsec 515f678f2d Adding GNU Screen Support (#1076)
* Adding GNU Screen support

* Adding doc

Fixes Gallopsled/pwntools#1070
2017-12-07 15:25:52 -06:00
bannsec c3afdd681d Adding non-default gdb handling (#1074)
Fixes Gallopsled/pwntools#1071
2017-12-07 15:25:27 -06:00
Owlz 5d537a6189 Fix typo on Gadget.addr -> Gadget.address 2017-12-04 14:30:41 -06:00
Zach Riggle 9ba966ddba Switch server to ftp.ubuntu.org
Currently ftp.debian.org is down on port 21
2017-12-04 14:30:41 -06:00
Zach Riggle 6ea5722f5b Add a comment to dup shellcodes 2017-11-10 17:00:25 -06:00
Zach Riggle 4e24f665b8 Use hex() for some ROP values (#1050)
* Use hex() for some ROP values

* Call str() on everything else

* Fix one more doctest
2017-11-01 16:15:25 -05:00
Zach Riggle 34cedc96d8 Remove old / unused lines in ABI 2017-11-01 16:14:40 -05:00
Zach Riggle e8065440c2 Use badbytes for ROP.search_iter 2017-11-01 16:14:40 -05:00
Zach Riggle de34906b4c Better handling for Corefiles with unsupported architectures 2017-11-01 16:14:40 -05:00
Zach Riggle e89a9b66ac Handle debugging under Android better with 'pwn debug' (#1053)
* Handle debugging under Android better with 'pwn debug'

Previously, when using --process or --pid, we would terminate the gdbserver instance
when pwntools exited.  Now we wait for the user to tell us to exit.

* Handle launching Android processes by filename

* Use the --multi option with gdbserver; disable detach-on-fork

* Intentionally break doctest to make sure its running

* Revert "Intentionally break doctest to make sure its running"

This reverts commit 00578ad177.
2017-10-30 14:17:47 -05:00
cpupower 64acece61b Copy env using .copy method to preserve order (OrderedDict) (#1061)
* Copy env in process._validate using .copy method to keep the order in case of OrderedDict

* Use supplied env object to preserve order in case of OrderedDict
2017-10-30 14:12:45 -05:00
Mathias Svensson bb9004c298 Begin working on 3.12.0 2017-10-25 12:23:09 +02:00
Mathias Svensson 712295f3c5 Release 3.11.0beta0 2017-10-25 12:06:47 +02:00
Mathias Svensson cca1083f0b Release 3.10.0 2017-10-25 12:01:59 +02:00
Mathias Svensson 9dacd1b5d9 Merge branch 'stable' into beta 2017-10-25 11:56:07 +02:00
Zach Riggle d9f5a5b009 Update CHANGELOG.md 2017-10-23 23:48:53 -05:00
Zach Riggle 74a5876c0d Fix corefile environment variables and speed up ELF.string() (#1055)
* Fix corefile environment variables and speed up ELF.string()

* Even more doctests

* Fix doctest arch

* No longer split on = since it isn't necessary

* Fix arch again

* Delete intermediate corefiles

* Do not flag for extended testing when the commit SHA is what matches
2017-10-23 23:45:11 -05:00
Zach Riggle 9d6b4881fe Fix path checking in ssh_process.corefile for edge cases (#1058)
Fixes #1047
2017-10-23 15:37:48 -05:00
Zach Riggle 91025408a4 Fix incorrect variable in log statement (#1057)
Fixes #1056
2017-10-23 14:39:12 -05:00
Zach Riggle b53e58a840 Use "file /path/to/file" when debugging processes 2017-10-18 14:52:13 -05:00
Zach Riggle 438096f97f Remove doctest prefixes, these are just examples 2017-10-18 14:41:49 -05:00
Zach Riggle 7bab49e7b9 Update GDB docs and examples 2017-10-15 15:31:39 -05:00
Zach Riggle 412d345964 QEMU-related enhancements (#1052)
* Add support for finding QEMU's LD_PREFIX

* Refactor pwnlib.qemu and add official documentation

* Add more documentation, support for sysroot

* More docs, binfmt info

* Fix variable name overload

* Fix missed refactor

* Check for empty/null env

* Add qemu.rst to index
2017-10-12 21:41:36 -05:00
Zach Riggle b088f46988 Update CHANGELOG 2017-10-12 11:26:10 -05:00
Zach Riggle 8f3839d69e Add cyclic default args to context (#1049)
* Add cyclic default args to context

Specifically, this adds context.cyclic_alphabet and context.cyclic_size

This helps cut down on writing "n=8" all the time for users who use 8-byte
subsequences on 64-bit architectures.

Exposing the default alphabet means that users can avoid "bad bytes" in
cyclic patterns which are generated by pwntools internally (e.g. ROP padding)

Some pwnlib.util.packing docstrings were changed to clarify that word_size is
in bits.

The doctests in pwnlib.util.cyclic were updated and extended to have more
and better doctests, as well as better exaples.

* Add an example showing an alphabet with NUL bytes

This is important because old code would use pack(..., "all") when an integer
value was provided, instead of using the correct width.
2017-10-11 19:08:31 -05:00
Zach Riggle 5a81e71368 Update README 2017-10-11 16:59:37 -05:00
Zach Riggle 1577263334 Overhaul ROP.setRegisters to support more complex cases needed by AMD64 (#1044)
* Overhaul ROP.setRegisters to support more complex cases needed by AMD64

Additionally, have a separate cache for ROPs composed of multiple ELF files

* Better presentation for stack padding slots

* Remove unnecessary stack adjustment on amd64

For amd64, it is possible that len(stackArguments) == 0.

* Add a doctest for amd64 register dependency stuff

* More i386 adjust optimizations

* Use nextGadgetAddr as the adjust value

* Always use padding for the last "ret" adjustment

* Better dump display

* Update doctests for new ROP dump() enhancements

* Add badchars support for ROP gadgets

* More doctest fixes, also ensure ROP.search does not return gadgets ending in e.g. int 0x80
2017-10-11 16:57:38 -05:00
Mathias Svensson 7ad5a4e34e Merge branch 'beta' into dev 2017-10-05 09:59:45 +02:00
Mathias Svensson 513682373e Release 3.10.0beta2 2017-10-05 09:44:33 +02:00
Zach Riggle 559ffa7c9e Merge branch 'beta' into dev 2017-10-01 16:56:31 -05:00
Zach Riggle 2512026b3c Merge branch 'stable' into beta 2017-10-01 16:56:09 -05:00
Zach Riggle 518ed71488 Better error message on gdb.debug when qemu-user is missing 2017-10-01 12:27:23 -05:00
Zach Riggle 26d60cbf63 Expose icache_flush for the ARM alphanumeric encoder 2017-10-01 12:27:04 -05:00
Zach Riggle 6577f24d3a Add --shared to shellcraft 2017-09-29 16:15:26 -05:00
Mathias Svensson 4a7ff431b9 Merge branch 'beta' into dev 2017-09-28 11:41:59 +02:00
Mathias Svensson 716c67d993 Release 3.10.0beta1 2017-09-28 11:37:08 +02:00
Zach Riggle 9ed8ab6d4f Reduce log spam when downloading the Android NDK 2017-09-26 12:22:16 -05:00
Zach Riggle 19f7268701 Reduce log spam when downloading the Android SDK 2017-09-26 12:21:04 -05:00
Zach Riggle e4d919d6bf Update README.md for 3.9.1 release 2017-09-26 12:16:57 -05:00
Zach Riggle 93d9a2cb5e Merge branch 'beta' into dev 2017-09-26 12:13:56 -05:00
Zach Riggle 69f8cc2c47 Merge branch 'stable' into beta 2017-09-26 12:12:20 -05:00
Zach Riggle 47d5cf7da1 Make some docs warnings go away and add some accessor methodds (#1035) 2017-09-22 16:23:47 -07:00
Zach Riggle a7263688db Make 'pwn template' better (#1032)
* Slightly enhance template and advertise for the feature itself in scripts

Add single-dash to the "very good" characters in shell escaping, to avoid
the need to single-quote things like --host and --port.

* Add --quiet option for pwn template, move gdbscript down a bit

* Slightly better docs for 'pwn template'

* Add checksec information directly to 'pwn template' output
2017-09-21 18:55:03 -07:00
Zach Riggle 630c4651fa Synthesize fault_addr on amd64 if it is zero. (#1031)
* Synthesize fault_addr on amd64 if it is zero.

Fixes Gallopsled/pwntool#1018

* Add a doctest for fault_addr on amd64

* Fix doctest
2017-09-21 17:53:32 -07:00
Zach Riggle 54c47b3daa Try to cut down on the crazy verbosity of logging (#1036)
* Prevent Sphinx from dumping all level=INFO logging output to stdout.

See Gallopsled/pwntools#1036 for more information.

* Filter out Paramiko logs too, apply filter to all root handlers during testing

* Remove unused import
2017-09-21 17:50:53 -07:00
Zach Riggle ebbe917713 Fix some unicode problems in readline (#1029)
Technically this introduces issues with e.g. 'unhex ffdf | python foo.py' if
foo.py uses .interactive().  However, that was already broken for other reasons.

Fixes #1004
2017-09-16 20:11:58 -05:00
Mathias Svensson b62804eff6 Begin working on 3.11.0 2017-09-11 19:13:20 +02:00
Mathias Svensson ec98dc1246 Release 3.10.0beta0 2017-09-11 18:59:06 +02:00
Zach Riggle 7cd3c7b300 Merge branch 'beta' into dev 2017-09-08 12:41:42 -05:00
Benno Fünfstück 5831796f6f Add configurable gdbinit (#1007)
* fix typo in comment

* new config option: context.gdbinit

This allows setting the default gdbinit for GDB, which is helpful when you want
to enable some GDB extension only when using pwntools and not by default.
2017-09-06 12:56:55 -05:00
Mathias Svensson 3e33180655 Update CHANGELOG 2017-07-29 13:26:21 +02:00
Mathias Svensson e09543c098 Start working on 3.10 2017-07-29 13:02:55 +02:00
57 changed files with 1502 additions and 313 deletions

No files matched your search

+2 -1
View File
@@ -1,4 +1,4 @@
dist: precise
dist: trusty
language: python
addons:
apt:
@@ -19,6 +19,7 @@ cache:
- usr
- /home/travis/virtualenv/python2.7.13/lib/python2.7/site-packages/
- /home/travis/virtualenv/python2.7.13/bin/
sudo: required
python:
- "2.7"
before_install:
+75 -5
View File
@@ -9,9 +9,12 @@ The table below shows which release corresponds to each branch, and what date th
| Version | Branch | Release Date |
| ---------------- | -------- | ---------------------- |
| [3.11.0](#3110) | `dev` | Dec 2, 2017 (planned)
| [3.10.0](#3100) | `beta` | Oct 21, 2017 (planned)
| [3.9.2](#392) | `stable` | Oct 5, 2017
| [3.14.0](#3130) | `dev` | Jun 12, 2018 (planned)
| [3.13.0](#3130) | `beta` | Mar 31, 2018 (planned)
| [3.12.0](#3120) | `stable` | Feb 22, 2018
| [3.11.0](#3110) | | Jan 3, 2018
| [3.10.0](#3100) | | Oct 25, 2017
| [3.9.2](#392) | | Oct 5, 2017
| [3.9.1](#391) | | Sep 28, 2017
| [3.9.0](#390) | | Sep 11, 2017
| [3.8.0](#380) | | Jul 29, 2017
@@ -39,13 +42,80 @@ 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.14.0
To be released on Jun 12, 2018.
## 3.13.0
To be released on Mar 31, 2018.
- [#1104][1104] Add `DynELF.dump()` for dumping remote ELF files
- [#1101][1101] Set `context.os` via `context.binary`, useful for Android exploitation
- [5fdc08][5fdc08] Work around broken `pidof` on Android
- [63dfed][63dfed] Print warning when Corefile deletion fails instead of throwing an exception
- [#1094][1094] Make hexdump output alignment more consistent
- [#1096][1096] `flat()` and `fit()` are now the same function
- [#1068][1068] Work around very old OpenSSL versions which don't have sha256 support *AND* don't exit with an error code when trying to use it
[1104]: https://github.com/Gallopsled/pwntools/pull/1104
[1101]: https://github.com/Gallopsled/pwntools/pull/1101
[1094]: https://github.com/Gallopsled/pwntools/pull/1094
[1096]: https://github.com/Gallopsled/pwntools/pull/1096
[1068]: https://github.com/Gallopsled/pwntools/pull/1068
[5fdc08]: https://github.com/Gallopsled/pwntools/commit/5fdc08
[63dfed]: https://github.com/Gallopsled/pwntools/commit/63dfed
## 3.12.0
- [#1083][1083] Better error messages for `gdb` when `LD_PRELOAD` is incorrect
- [#1085][1085] Add support for extracting Android `BOOTLDR!` images
- [#1075][1075] Add support for detecting GNU Screen for `run_in_new_terminal`
- [#1074][1074] Add support for running `pwntools-gdb` wrapper script instead of `gdb`
- [#1067][1067] Add `pwnlib.tubes.server` module, which adds a reusable `server` listener
- [#1063][1063] Add support for labels in `fit()`, allowing dynamic contents to be injected. (This feature is really cool, check out the pull request!)
[1083]: https://github.com/Gallopsled/pwntools/pull/1083
[1085]: https://github.com/Gallopsled/pwntools/pull/1085
[1075]: https://github.com/Gallopsled/pwntools/pull/1075
[1074]: https://github.com/Gallopsled/pwntools/pull/1074
[1067]: https://github.com/Gallopsled/pwntools/pull/1067
[1063]: https://github.com/Gallopsled/pwntools/pull/1063
## 3.11.0
To be released on Dec 2, 2017.
- [#1044][1044] Enhancements to ROP
+ Much better support for 64-bit Intel (amd64) ROP
+ ROP gadget selection is optimized to favor multi-pops instead of multiple single-pop gadgets
+ Added support for blacklisting byte values in ROP gadget addresses
- [#1049][1049] Enhancements to `cyclic`
+ `context` now has two additional attributes, `cyclic_alphabet` and `cyclic_length`, which correspond to the arguments `alphabet` and `n` to `cyclic()` and `cyclic_find()` and related routines.
+ The motivation for this change is to allow setting the `alphabet` globally, so that any padding / patterns generated internally to pwntools can be controlled. The specific motivation is blacklisting values in ROP padding.
- [#1052][1052] Enhancements for detecting `QEMU_LD_PREFIX` used by QEMU user-mode emulation for sysroots
- [#1035][1035] Minor documentation changes
- [#1032][1032] Enhancements to `pwn template`
- [#1031][1031] More accurate `Coredump.fault_addr` on amd64
- [#1084][1084] Fix broken tests due to `ftp.debian.org` going down
[1044]: https://github.com/Gallopsled/pwntools/pull/1044
[1049]: https://github.com/Gallopsled/pwntools/pull/1049
[1052]: https://github.com/Gallopsled/pwntools/pull/1052
[1035]: https://github.com/Gallopsled/pwntools/pull/1035
[1032]: https://github.com/Gallopsled/pwntools/pull/1032
[1031]: https://github.com/Gallopsled/pwntools/pull/1031
[1084]: https://github.com/Gallopsled/pwntools/pull/1084
## 3.10.0
To be released on Oct 21, 2017.
- [#1007][1007] Add support for setting a `gdbinit` file in the context
- [#1055][1055] Fixes for `Corefile` stack parsing, speed up `ELF.string()`
- [#1057][1057] Fix a variable name typo in `DynELF` logging which results in an exception being thrown
- [#1058][1058] Fix an edge case in `ssh_process.exe`
[1007]: https://github.com/Gallopsled/pwntools/pull/1007
[1055]: https://github.com/Gallopsled/pwntools/pull/1055
[1057]: https://github.com/Gallopsled/pwntools/pull/1057
[1058]: https://github.com/Gallopsled/pwntools/pull/1058
## 3.9.2
+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.9.2-green.svg?style=flat)](https://pypi.python.org/pypi/pwntools/)
[![PyPI](https://img.shields.io/badge/pypi-v3.12.1-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)
+2 -1
View File
@@ -1,5 +1,5 @@
sphinxcontrib-napoleon
sphinxcontrib-autoprogram==0.1.2
sphinxcontrib-autoprogram==0.1.4
isort
paramiko>=1.15.2
mako>=1.0.0
@@ -14,3 +14,4 @@ pygments>=2.0
sphinx_rtd_theme
doc2dash
coveralls
sphinx<1.8.0
-2
View File
@@ -24,8 +24,6 @@ sys.path.insert(0, os.path.abspath('../..'))
import pwnlib
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
+1 -1
View File
@@ -8,7 +8,7 @@
: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.
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:
+1
View File
@@ -1,5 +1,6 @@
.. testsetup:: *
from glob import glob
from pwn import *
:mod:`pwnlib.elf.corefile` --- Core Files
+1 -1
View File
@@ -13,7 +13,7 @@ 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`
- :mod:`pwnlib.context`
- :data:`pwnlib.context.context`
- Responsible for most of the pwntools convenience settings
- Set `context.log_level = 'debug'` when troubleshooting your exploit
+1
View File
@@ -61,6 +61,7 @@ Each of the ``pwntools`` modules is documented here.
log
memleak
protocols
qemu
replacements
rop
rop/*
+1 -1
View File
@@ -27,7 +27,7 @@ This exposes a standard interface to talk to processes, sockets, serial ports,
and all manner of things, along with some nifty helpers for common tasks.
For example, remote connections via :mod:`pwnlib.tubes.remote`.
>>> conn = remote('ftp.debian.org',21)
>>> conn = remote('ftp.ubuntu.com',21)
>>> conn.recvline() # doctest: +ELLIPSIS
'220 ...'
>>> conn.send('USER anonymous\r\n')
+10
View File
@@ -0,0 +1,10 @@
.. testsetup:: *
from pwn import *
:mod:`pwnlib.qemu` --- QEMU Utilities
==========================================
.. automodule:: pwnlib.qemu
:members:
+7
View File
@@ -1,6 +1,7 @@
.. testsetup:: *
from pwn import *
from pwnlib.tubes.server import server
:mod:`pwnlib.tubes.sock` --- Sockets
===========================================================
@@ -23,3 +24,9 @@
.. autoclass:: pwnlib.tubes.listen.listen
:members:
:show-inheritance:
.. automodule:: pwnlib.tubes.server
.. autoclass:: pwnlib.tubes.server.server
:members:
:show-inheritance:
+1
View File
@@ -1,5 +1,6 @@
.. testsetup:: *
from pwn import *
from pwnlib.util.cyclic import *
+1
View File
@@ -43,6 +43,7 @@ from pwnlib.tubes.listen import listen
from pwnlib.tubes.process import process, PTY, PIPE, STDOUT
from pwnlib.tubes.remote import remote, tcp, udp, connect
from pwnlib.tubes.serialtube import serialtube
from pwnlib.tubes.server import server
from pwnlib.tubes.ssh import ssh
from pwnlib.tubes.tube import tube
from pwnlib.ui import *
-5
View File
@@ -124,8 +124,3 @@ linux_aarch64_sigreturn = SigreturnABI('sp', ['x8'], 16, 0)
windows_i386 = ABI('esp', [], 4, 0)
windows_amd64 = ABI('rsp', ['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)
+1
View File
@@ -820,6 +820,7 @@ echo $PATH | while read -d: directory; do
[ -x "$directory/{name}" ] || continue;
echo -n "$directory/{name}\\x00";
done
[ -x "{name}" ] && echo -n "$PWD/{name}\\x00"
'''.format(name=name)
which_cmd = which_cmd.strip()
+103
View File
@@ -0,0 +1,103 @@
from __future__ import unicode_literals
import ctypes
import io
import os
import sys
class img_info(ctypes.Structure):
_fields_ = [
('name', ctypes.c_char * 64),
('size', ctypes.c_uint32)
]
class bootloader_images_header(ctypes.Structure):
_fields_ = [
('magic', ctypes.c_char * 8),
('num_images', ctypes.c_uint32),
('start_offset', ctypes.c_uint32),
('bootldr_size', ctypes.c_uint32),
]
def __init__(self, *a, **kw):
super(bootloader_images_header, self).__init__(*a, **kw)
if self.magic != self.MAGIC:
raise ValueError("Incorrect magic (%r, expected %r)" % (self.magic, self.MAGIC))
MAGIC = 'BOOTLDR!'
class BootloaderImage(object):
def __init__(self, data):
"""Android Bootloader image
Arguments:
data(str): Binary data from the image file.
"""
self.data = data
self.header = bootloader_images_header.from_buffer_copy(data)
imgarray = ctypes.ARRAY(img_info, self.header.num_images)
self.img_info = imgarray.from_buffer_copy(data, ctypes.sizeof(self.header))
def extract(self, index_or_name):
"""extract(index_or_name) -> bytes
Extract the contents of an image.
Arguments:
index_or_name(str,int): Either an image index or name.
Returns:
Contents of the image.
"""
if isinstance(index_or_name, int):
index = index_or_name
else:
for i in range(len(self.img_info)):
if self.img_info[i].name == index_or_name:
index = i
break
else:
raise ValueError("Invalid img name: %r" % index_or_name)
if index >= len(self.img_info):
raise ValueError("index out of range (%s, max %s)" % (index, len(self.img_info)))
offset = self.header.start_offset
for i in range(index):
offset += self.img_info[i].size
return self.data[offset:offset + self.img_info[index].size]
def extract_all(self, path):
"""extract_all(path)
Extracts all images to the provided path. The filenames are taken
from the image name, with '.img' appended.
"""
if not os.path.isdir(path):
raise ValueError("%r does not exist or is not a directory" % path)
for img in self.img_info:
imgpath = os.path.join(path, img.name + '.img')
with open(imgpath, 'wb+') as f:
data = self.extract(img.name)
f.write(data)
def __str__(self):
rv = []
rv.append("Bootloader")
rv.append(" Magic: %r" % self.header.magic)
rv.append(" Offset: %#x" % self.header.start_offset)
rv.append(" Size: %#x" % self.header.bootldr_size)
rv.append(" Images: %s" % self.header.num_images)
for img in self.img_info:
rv.append(" Name: %s" % img.name)
rv.append(" Size: %#x" % img.size)
rv.append(" Data: %r..." % self.extract(img.name)[:32])
return '\n'.join(rv)
if __name__ == '__main__':
# Easy sanity checking
b = BootloaderImage(open(sys.argv[1]).read())
print(b)
+37 -7
View File
@@ -30,13 +30,25 @@ parser.add_argument(
help = 'The os/architecture/endianness/bits the shellcode will run in (default: linux/i386), choose from: %s' % common.choices,
)
parser.add_argument(
'--exec', type=file, dest='executable',
'--exec',
# NOTE: Type cannot be "file" because we may be referring to a remote
# file, or a file on an Android device.
type=str,
dest='executable',
help='File to debug'
)
parser.add_argument(
'--process', metavar='PROCESS_NAME',
help='Name of the process to attach to (e.g. "bash")'
)
parser.add_argument(
'--sysroot', metavar='SYSROOT',
type=str,
default='',
help="GDB sysroot path"
)
def main(args):
gdbscript = ''
@@ -47,23 +59,41 @@ def main(args):
context.device = adb.wait_for_device()
if args.executable:
context.binary = ELF(args.executable.name)
target = context.binary.path
if os.path.exists(args.executable):
context.binary = ELF(args.executable)
target = context.binary.path
# This path does nothing, but avoids the "print_usage()"
# path below.
elif context.os == 'android':
target = args.executable
elif args.pid:
target = int(args.pid)
elif args.process:
if context.os == 'android':
target = adb.pidof(process)
target = adb.pidof(args.process)
else:
target = pidof(process)
target = pidof(args.process)
# pidof() returns a list
if not target:
log.error("Could not find a PID for %r", args.process)
target = target[0]
else:
parser.print_usage()
return 1
if args.pid or args.process:
gdb.attach(target, gdbscript=gdbscript)
pid = gdb.attach(target, gdbscript=gdbscript, sysroot=args.sysroot)
# Since we spawned the gdbserver process, and process registers an
# atexit handler to close itself, gdbserver will be terminated when
# we exit. This will manifest as a "remote connected ended" or
# similar error message. Hold it open for the user.
log.info("GDB connection forwarding will terminate when you press enter")
pause()
else:
gdb.debug(target, gdbscript=gdbscript).interactive()
gdb.debug(target, gdbscript=gdbscript, sysroot=args.sysroot).interactive()
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
+8 -2
View File
@@ -164,6 +164,12 @@ p.add_argument(
help='List available shellcodes, optionally provide a filter'
)
p.add_argument(
'-s', '--shared',
action='store_true',
help='Generated ELF is a shared library'
)
def get_template(name):
func = shellcraft
for attr in name.split('.'):
@@ -296,11 +302,11 @@ def main(args):
if not args.avoid:
code = read(make_elf_from_assembly(assembly, vma=vma))
code = read(make_elf_from_assembly(assembly, vma=vma, shared=args.shared))
else:
code = asm(assembly)
code = encode(code, args.avoid)
code = make_elf(code, vma=vma)
code = make_elf(code, vma=vma, shared=args.shared)
# code = read(make_elf(encode(asm(code), args.avoid)))
else:
code = encode(asm(assembly), args.avoid)
+3 -1
View File
@@ -19,6 +19,7 @@ parser.add_argument('--port', help='Remote port / SSH port', type=int)
parser.add_argument('--user', help='SSH Username')
parser.add_argument('--pass', help='SSH Password', dest='password')
parser.add_argument('--path', help='Remote path of file on SSH server')
parser.add_argument('--quiet', help='Less verbose template comments', action='store_true')
def main(args):
cache = None
@@ -49,7 +50,8 @@ def main(args):
args.port,
args.user,
args.password,
args.path)
args.path,
args.quiet)
# Fix Mako formatting bs
output = re.sub('\n\n\n', '\n\n', output)
+47 -1
View File
@@ -150,7 +150,7 @@ class _Tls_DictStack(threading.local, _DictStack):
def _validator(validator):
"""
Validator that tis tightly coupled to the implementation
Validator that is tightly coupled to the implementation
of the classes here.
This expects that the object has a ._tls property which
@@ -337,9 +337,12 @@ class ContextType(object):
'binary': None,
'bits': 32,
'buffer_size': 4096,
'cyclic_alphabet': string.ascii_lowercase,
'cyclic_size': 4,
'delete_corefiles': False,
'device': os.getenv('ANDROID_SERIAL', None) or None,
'endian': 'little',
'gdbinit': "",
'kernel': None,
'log_level': logging.INFO,
'log_file': _devnull(),
@@ -1231,6 +1234,49 @@ class ContextType(object):
"""
return bool(v)
@_validator
def gdbinit(self, value):
"""Path to the gdbinit that is used when running GDB locally.
This is useful if you want pwntools-launched GDB to include some additional modules,
like PEDA but you do not want to have GDB include them by default.
The setting will only apply when GDB is launched locally since remote hosts may not have
the necessary requirements for the gdbinit.
If set to an empty string, GDB will use the default `~/.gdbinit`.
Default value is ``""``.
"""
return str(value)
@_validator
def cyclic_alphabet(self, alphabet):
"""Cyclic alphabet.
Default value is `string.ascii_lowercase`.
"""
# Do not allow multiple occurrences
if len(set(alphabet)) != len(alphabet):
raise AttributeError("cyclic alphabet cannot contain duplicates")
return str(alphabet)
@_validator
def cyclic_size(self, size):
"""Cyclic pattern size.
Default value is `4`.
"""
size = int(size)
if size > self.bytes:
raise AttributeError("cyclic pattern size cannot be larger than word size")
return size
#*************************************************************************
# ALIASES
#*************************************************************************
+83 -47
View File
@@ -1,13 +1,19 @@
<%page args="binary, host=None, port=None, user=None, password=None, remote_path=None"/>\
<%page args="binary, host=None, port=None, user=None, password=None, remote_path=None, quiet=False"/>\
<%
import os
import sys
from pwnlib.context import context as ctx
from pwnlib.elf.elf import ELF
from pwnlib.util.sh_string import sh_string
from elftools.common.exceptions import ELFError
import os
argv = list(sys.argv)
argv[0] = os.path.basename(argv[0])
try:
if binary:
ctx.binary = ELF(binary, checksec=False)
ctx.binary = ELF(binary, checksec=False)
except ELFError:
pass
@@ -28,9 +34,15 @@ binary_repr = repr(binary)
%>\
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
%if not quiet:
# This exploit template was generated via:
# $ ${' '.join(map(sh_string, argv))}
%endif
from pwn import *
%if not quiet:
# Set up pwntools for the correct architecture
%endif
%if ctx.binary:
exe = context.binary = ELF(${binary_repr})
<% binary_repr = 'exe.path' %>
@@ -40,6 +52,7 @@ exe = ${binary_repr}
<% binary_repr = 'exe' %>
%endif
%if not quiet:
# Many built-in settings can be controlled on the command-line and show up
# in "args". For example, to dump all data sent/received, and disable ASLR
# for all created processes...
@@ -47,6 +60,7 @@ exe = ${binary_repr}
%if host or port or user:
# ./exploit.py GDB HOST=example.com PORT=4141
%endif
%endif
%if host:
host = args.HOST or ${repr(host)}
%endif
@@ -61,10 +75,60 @@ password = args.PASSWORD or ${repr(password)}
remote_path = ${repr(remote_path)}
%endif
%if ssh:
# Connect to the remote SSH server
shell = None
if not args.LOCAL:
shell = ssh(user, host, port, password)
shell.set_working_directory(symlink=True)
%endif
%if host:
def local(argv=[], *a, **kw):
'''Execute the target binary locally'''
if args.GDB:
return gdb.debug([${binary_repr}] + argv, gdbscript=gdbscript, *a, **kw)
else:
return process([${binary_repr}] + argv, *a, **kw)
def remote(argv=[], *a, **kw):
%if ssh:
'''Execute the target binary on the remote host'''
if args.GDB:
return gdb.debug([remote_path] + argv, gdbscript=gdbscript, ssh=shell, *a, **kw)
else:
return shell.process([remote_path] + argv, *a, **kw)
%else:
'''Connect to the process on the remote host'''
io = connect(host, port)
if args.GDB:
gdb.attach(io, gdbscript=gdbscript)
return io
%endif
%endif
%if host:
def start(argv=[], *a, **kw):
'''Start the exploit against the target.'''
if args.LOCAL:
return local(argv, *a, **kw)
else:
return remote(argv, *a, **kw)
%else:
def start(argv=[], *a, **kw):
'''Start the exploit against the target.'''
if args.GDB:
return gdb.debug([${binary_repr}] + argv, gdbscript=gdbscript, *a, **kw)
else:
return process([${binary_repr}] + argv, *a, **kw)
%endif
%if exe or remote_path:
%if not quiet:
# Specify your GDB script here for debugging
# GDB will be launched if the exploit is run via e.g.
# ./exploit.py GDB
%endif
gdbscript = '''
%if ctx.binary:
%if 'main' in ctx.binary.symbols:
@@ -77,56 +141,27 @@ continue
'''.format(**locals())
%endif
%if ssh:
# Connect to the remote SSH server
shell = None
if not args.LOCAL:
shell = ssh(user, host, port, password)
shell.set_working_directory(symlink=True)
%endif
%if host:
# Execute the target binary locally
def local(argv=[], *a, **kw):
if args.GDB:
return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
else:
return process([exe.path] + argv, *a, **kw)
%if ssh:
# Execute the target binary on the remote host
%else:
# Connect to the process on the remote host
%endif
def remote(argv=[], *a, **kw):
%if ssh:
if args.GDB:
return gdb.debug([remote_path] + argv, gdbscript=gdbscript, ssh=shell, *a, **kw)
else:
return shell.process([remote_path] + argv, *a, **kw)
%else:
io = connect(host, port)
if args.GDB:
gdb.attach(io, gdbscript=gdbscript)
return io
%endif
%endif
%if host:
start = local if args.LOCAL else remote
%else:
def start(argv=[], *a, **kw):
if args.GDB:
return gdb.debug([${binary_repr}] + argv, gdbscript=gdbscript, *a, **kw)
else:
return process([${binary_repr}] + argv, *a, **kw)
%endif
%if not quiet:
#===========================================================
# EXPLOIT GOES HERE
#===========================================================
%else:
# -- Exploit goes here --
%endif
%if ctx.binary and not quiet:
# ${'%-10s%s-%s-%s' % ('Arch:',
ctx.binary.arch,
ctx.binary.bits,
ctx.binary.endian)}
%for line in ctx.binary.checksec(color=False).splitlines():
# ${line}
%endfor
%endif
io = start()
%if not quiet:
# shellcode = asm(shellcraft.sh())
# payload = fit({
# 32: 0xdeadbeef,
@@ -135,5 +170,6 @@ io = start()
# io.send(payload)
# flag = io.recv(...)
# log.success(flag)
%endif
io.interactive()
+1 -1
View File
@@ -447,7 +447,7 @@ class DynELF(object):
if debug:
w.status("r_debug.linkmap")
linkmap = self.leak.field(debug, r_debug.r_map)
w.status("r_debug.linkmap %#x" % result)
w.status("r_debug.linkmap %#x" % linkmap)
if not linkmap:
w.failure("Could not find DT_PLTGOT or DT_DEBUG")
+190 -38
View File
@@ -87,6 +87,7 @@ from pwnlib.tubes.process import process
from pwnlib.tubes.ssh import ssh_channel
from pwnlib.tubes.tube import tube
from pwnlib.util.fiddling import b64d
from pwnlib.util.fiddling import enhex
from pwnlib.util.fiddling import unhex
from pwnlib.util.misc import read
from pwnlib.util.misc import write
@@ -223,6 +224,8 @@ class Mapping(object):
return self._core.read(item, 1)
def __contains__(self, item):
if isinstance(item, Mapping):
return (self.start <= item.start) and (item.stop <= self.stop)
return self.start <= item < self.stop
def find(self, sub, start=None, end=None):
@@ -389,7 +392,7 @@ class Corefile(ELF):
>>> core.vdso.data[:4]
'\x7fELF'
>>> core.libc # docteset: +ELLIPSIS
>>> core.libc # doctest: +ELLIPSIS
Mapping('/lib/x86_64-linux-gnu/libc-...', ...)
The corefile also contains a :attr:`.Corefile.stack` property, which gives
@@ -421,7 +424,7 @@ class Corefile(ELF):
>>> s = ssh('travis', 'example.pwnme')
>>> _ = s.set_working_directory()
>>> elf = ELF.from_assembly('int3')
>>> elf = ELF.from_assembly(shellcraft.trap())
>>> path = s.upload(elf.path)
>>> _ =s.chmod('+x', path)
>>> io = s.process(path)
@@ -429,6 +432,56 @@ class Corefile(ELF):
-1
>>> io.corefile.signal == signal.SIGTRAP # doctest: +SKIP
True
Make sure fault_addr synthesis works for amd64 on ret.
>>> context.clear(arch='amd64')
>>> elf = ELF.from_assembly('push 1234; ret')
>>> io = elf.process()
>>> io.wait()
>>> io.corefile.fault_addr
1234
Tests:
These are extra tests not meant to serve as examples.
Corefile.getenv() works correctly, even if the environment variable's
value contains embedded '='. Corefile is able to find the stack, even
if the stack pointer doesn't point at the stack.
>>> elf = ELF.from_assembly(shellcraft.crash())
>>> io = elf.process(env={'FOO': 'BAR=BAZ'})
>>> io.wait()
>>> core = io.corefile
>>> core.getenv('FOO')
'BAR=BAZ'
>>> core.sp == 0
True
>>> core.sp in core.stack
False
Corefile gracefully handles the stack being filled with garbage, including
argc / argv / envp being overwritten.
>>> context.clear(arch='i386')
>>> assembly = '''
... LOOP:
... mov dword ptr [esp], 0x41414141
... pop eax
... jmp LOOP
... '''
>>> elf = ELF.from_assembly(assembly)
>>> io = elf.process()
>>> io.wait()
>>> core = io.corefile
[!] End of the stack is corrupted, skipping stack parsing (got: 4141414141414141)
>>> core.argc, core.argv, core.env
(0, [], {})
>>> core.stack.data.endswith('AAAA')
True
>>> core.fault_addr == core.sp
True
"""
_fill_gaps = False
@@ -454,6 +507,9 @@ class Corefile(ELF):
#: variable.
#:
#: Note: Use with the :meth:`.ELF.string` method to extract them.
#:
#: Note: If FOO=BAR is in the environment, self.env['FOO'] is the
#: address of the string "BAR\x00".
self.env = {}
#: :class:`list`: List of addresses of arguments on the stack.
@@ -493,36 +549,40 @@ class Corefile(ELF):
for segment in self.segments:
if not isinstance(segment, elftools.elf.segments.NoteSegment):
continue
# Note that older versions of pyelftools (<=0.24) are missing enum values
# for NT_PRSTATUS, NT_PRPSINFO, NT_AUXV, etc.
# For this reason, we have to check if note.n_type is any of several values.
for note in iter_notes(segment):
# Try to find NT_PRSTATUS. Note that pyelftools currently
# mis-identifies the enum name as 'NT_GNU_ABI_TAG'.
# Try to find NT_PRSTATUS.
if prstatus_type and \
note.n_descsz == ctypes.sizeof(prstatus_type) and \
note.n_type == 'NT_GNU_ABI_TAG':
note.n_type in ('NT_GNU_ABI_TAG', 'NT_PRSTATUS'):
self.NT_PRSTATUS = note
self.prstatus = prstatus_type.from_buffer_copy(note.n_desc)
# Try to find NT_PRPSINFO
# Note that pyelftools currently mis-identifies the enum name
# as 'NT_GNU_BUILD_ID'
if note.n_descsz == ctypes.sizeof(prpsinfo_type) and \
note.n_type == 'NT_GNU_BUILD_ID':
if prpsinfo_type and \
note.n_descsz == ctypes.sizeof(prpsinfo_type) and \
note.n_type in ('NT_GNU_ABI_TAG', 'NT_PRPSINFO'):
self.NT_PRPSINFO = note
self.prpsinfo = prpsinfo_type.from_buffer_copy(note.n_desc)
# Try to find NT_SIGINFO so we can see the fault
if note.n_type == 0x53494749:
if note.n_type in (0x53494749, 'NT_SIGINFO'):
self.NT_SIGINFO = note
self.siginfo = siginfo_type.from_buffer_copy(note.n_desc)
# Try to find the list of mapped files
if note.n_type == constants.NT_FILE:
if note.n_type in (constants.NT_FILE, 'NT_FILE'):
with context.local(bytes=self.bytes):
self._parse_nt_file(note)
# Try to find the auxiliary vector, which will tell us
# where the top of the stack is.
if note.n_type == constants.NT_AUXV:
if note.n_type in (constants.NT_AUXV, 'NT_AUXV'):
self.NT_AUXV = note
with context.local(bytes=self.bytes):
self._parse_auxv(note)
@@ -531,19 +591,13 @@ class Corefile(ELF):
if self.stack and self.mappings:
for mapping in self.mappings:
if mapping.stop == self.stack:
if self.stack in mapping or self.stack == mapping.stop:
mapping.name = '[stack]'
self.stack = mapping
break
else:
for mapping in self.mappings:
if self.stack in mapping:
mapping.name = '[stack]'
self.stack = mapping
break
else:
log.warn('Could not find the stack!')
self.stack = None
log.warn('Could not find the stack!')
self.stack = None
with context.local(bytes=self.bytes, log_level='warn'):
try:
@@ -664,7 +718,22 @@ class Corefile(ELF):
@property
def signal(self):
""":class:`int`: Signal which caused the core to be dumped."""
""":class:`int`: Signal which caused the core to be dumped.
Example:
>>> elf = ELF.from_assembly(shellcraft.trap())
>>> io = elf.process()
>>> io.wait()
>>> io.corefile.signal == signal.SIGTRAP
True
>>> elf = ELF.from_assembly(shellcraft.crash())
>>> io = elf.process()
>>> io.wait()
>>> io.corefile.signal == signal.SIGSEGV
True
"""
if self.siginfo:
return int(self.siginfo.si_signo)
if self.prstatus:
@@ -675,11 +744,49 @@ class Corefile(ELF):
""":class:`int`: Address which generated the fault, for the signals
SIGILL, SIGFPE, SIGSEGV, SIGBUS. This is only available in native
core dumps created by the kernel. If the information is unavailable,
this returns the address of the instruction pointer."""
if self.siginfo:
return int(self.siginfo.sigfault_addr)
this returns the address of the instruction pointer.
return getattr(self, 'pc', 0)
Example:
>>> elf = ELF.from_assembly('mov eax, 0xdeadbeef; jmp eax', arch='i386')
>>> io = elf.process()
>>> io.wait()
>>> io.corefile.fault_addr == io.corefile.eax == 0xdeadbeef
True
"""
if not self.siginfo:
return getattr(self, 'pc', 0)
fault_addr = int(self.siginfo.sigfault_addr)
# The fault_addr is zero if the crash occurs due to a
# "protection fault", e.g. a dereference of 0x4141414141414141
# because this is technically a kernel address.
#
# A protection fault does not set "fault_addr" in the siginfo.
# (http://elixir.free-electrons.com/linux/v4.14-rc8/source/kernel/signal.c#L1052)
#
# Since a common use for corefiles is to spray the stack with a
# cyclic pattern to find the offset to get control of $PC,
# check for a "ret" instruction ("\xc3").
#
# If we find a RET at $PC, extract the "return address" from the
# top of the stack.
if fault_addr == 0 and self.siginfo.si_code == 0x80:
try:
code = self.read(self.pc, 1)
RET = '\xc3'
if code == RET:
fault_addr = self.unpack(self.sp)
except Exception:
# Could not read $rsp or $rip
pass
return fault_addr
# No embedded siginfo structure, so just return the
# current instruction pointer.
@property
def _pc_register(self):
@@ -722,8 +829,8 @@ class Corefile(ELF):
fields = [
repr(self.path),
'%-10s %s' % ('Arch:', gnu_triplet),
'%-10s %#x' % ('%s:' % self._pc_register.upper(), getattr(self, 'pc', 0)),
'%-10s %#x' % ('%s:' % self._sp_register.upper(), getattr(self, 'sp', 0)),
'%-10s %#x' % ('%s:' % self._pc_register.upper(), self.pc or 0),
'%-10s %#x' % ('%s:' % self._sp_register.upper(), self.sp or 0),
]
if self.exe and self.exe.name:
@@ -790,18 +897,25 @@ class Corefile(ELF):
if not stack:
return
# If the stack does not end with zeroes, something is very wrong.
if not stack.data.endswith('\x00' * 8):
log.warn_once("End of the stack is corrupted, skipping stack parsing (got: %s)",
enhex(self.data[-8:]))
return
# AT_EXECFN is the start of the filename, e.g. '/bin/sh'
# Immediately preceding is a NULL-terminated environment variable string.
# We want to find the beginning of it
if self.at_execfn:
address = self.at_execfn-1
else:
log.debug('No AT_EXECFN')
if not self.at_execfn:
address = stack.stop
address -= 2*self.bytes
address -= 1
address = stack.rfind('\x00', None, address)
address += 1
self.at_execfn = address
address = self.at_execfn-1
# Sanity check!
try:
@@ -848,11 +962,31 @@ class Corefile(ELF):
# Now we can fill in the environment
env_pointer_data = stack[start_of_envp:p_last_env_addr+self.bytes]
for pointer in unpack_many(env_pointer_data):
end = stack.find('=', last_env_addr)
if pointer in stack and end in stack:
name = stack[pointer:end]
self.env[name] = pointer
# If the stack is corrupted, the pointer will be outside of
# the stack.
if pointer not in stack:
continue
try:
name_value = self.string(pointer)
except Exception:
continue
name, value = name_value.split('=', 1)
# "end" points at the byte after the null terminator
end = pointer + len(name_value) + 1
# Do not mark things as environment variables if they point
# outside of the stack itself, or we had to cross into a different
# mapping (after the stack) to read it.
# This may occur when the entire stack is filled with non-NUL bytes,
# and we NULL-terminate on a read failure in .string().
if end not in stack:
continue
self.env[name] = pointer + len(name) + len('=')
# May as well grab the arguments off the stack as well.
# argc comes immediately before argv[0] on the stack, but
@@ -904,15 +1038,32 @@ class Corefile(ELF):
Returns:
:class:`str`: The contents of the environment variable.
Example:
>>> elf = ELF.from_assembly(shellcraft.trap())
>>> io = elf.process(env={'GREETING': 'Hello!'})
>>> io.wait()
>>> io.corefile.getenv('GREETING')
'Hello!'
"""
if name not in self.env:
log.error("Environment variable %r not set" % name)
return self.string(self.env[name]).split('=',1)[-1]
return self.string(self.env[name])
@property
def registers(self):
""":class:`dict`: All available registers in the coredump."""
""":class:`dict`: All available registers in the coredump.
Example:
>>> elf = ELF.from_assembly('mov eax, 0xdeadbeef;' + shellcraft.trap(), arch='i386')
>>> io = elf.process()
>>> io.wait()
>>> io.corefile.registers['eax'] == 0xdeadbeef
True
"""
if not self.prstatus:
return {}
@@ -1020,6 +1171,7 @@ class CorefileFinder(object):
new_path = 'core.%i' % core_pid
if core_pid > 0 and new_path != self.core_path:
write(new_path, self.read(self.core_path))
self.unlink(self.core_path)
self.core_path = new_path
# Check the PID
+50 -19
View File
@@ -51,15 +51,21 @@ from elftools.elf.constants import P_FLAGS
from elftools.elf.constants import SHN_INDICES
from elftools.elf.descriptions import describe_e_type
from elftools.elf.elffile import ELFFile
from elftools.elf.enums import ENUM_P_TYPE
from elftools.elf.gnuversions import GNUVerDefSection
from elftools.elf.relocation import RelocationSection
from elftools.elf.sections import SymbolTableSection
from elftools.elf.segments import InterpSegment
# See https://github.com/Gallopsled/pwntools/issues/1189
try:
from elftools.elf.enums import ENUM_P_TYPE
except ImportError:
from elftools.elf.enums import ENUM_P_TYPE_BASE as ENUM_P_TYPE
import intervaltree
from pwnlib import adb
from pwnlib import qemu
from pwnlib.asm import *
from pwnlib.context import LocalContext
from pwnlib.context import context
@@ -68,7 +74,6 @@ from pwnlib.elf.config import parse_kconfig
from pwnlib.elf.datatypes import constants
from pwnlib.elf.plt import emulate_plt_instructions
from pwnlib.log import getLogger
from pwnlib.qemu import get_qemu_arch
from pwnlib.term import text
from pwnlib.tubes.process import process
from pwnlib.util import misc
@@ -261,6 +266,15 @@ class ELF(ELFFile):
self.arch = 'mips64'
self.bits = 64
# Is this a native binary? Should we be checking QEMU?
try:
with context.local(arch=self.arch):
#: Whether this ELF should be able to run natively
self.native = context.native
except AttributeError:
# The architecture may not be supported in pwntools
self.native = False
self._address = 0
if self.elftype != 'DYN':
for seg in self.iter_segments_by_type('PT_LOAD'):
@@ -409,10 +423,10 @@ class ELF(ELFFile):
import pwnlib.gdb
return pwnlib.gdb.debug([self.path] + argv, *a, **kw)
def _describe(self):
def _describe(self, *a, **kw):
log.info_once('\n'.join((repr(self.path),
'%-10s%s-%s-%s' % ('Arch:', self.arch, self.bits, self.endian),
self.checksec())))
self.checksec(*a, **kw))))
def __repr__(self):
return "ELF(%r)" % self.path
@@ -630,10 +644,11 @@ class ELF(ELFFile):
if os.path.exists(lib):
continue
qemu_lib = '/etc/qemu-binfmt/%s/%s' % (get_qemu_arch(arch=self.arch), lib)
if os.path.exists(qemu_lib):
libs[os.path.realpath(qemu_lib)] = libs.pop(lib)
if not self.native:
ld_prefix = qemu.ld_prefix()
qemu_lib = os.path.exists(os.path.join(ld_prefix, lib))
if qemu_lib:
libs[os.path.realpath(qemu_lib)] = libs.pop(lib)
self.libs = libs
@@ -1499,9 +1514,9 @@ class ELF(ELFFile):
if not dt_runpath:
return None
return self.dynamic_string(dt_rpath.entry.d_ptr)
return self.dynamic_string(dt_runpath.entry.d_ptr)
def checksec(self, banner=True):
def checksec(self, banner=True, color=True):
"""checksec(banner=True)
Prints out information in the binary, similar to ``checksec.sh``.
@@ -1509,9 +1524,9 @@ class ELF(ELFFile):
Arguments:
banner(bool): Whether to print the path to the ELF binary.
"""
red = text.red
green = text.green
yellow = text.yellow
red = text.red if color else str
green = text.green if color else str
yellow = text.yellow if color else str
res = []
@@ -1686,16 +1701,33 @@ class ELF(ELFFile):
return packing.unpack(self.read(address, context.bytes), *a, **kw)
def string(self, address):
"""Reads a null-terminated string from the specified ``address``"""
"""string(address) -> str
Reads a null-terminated string from the specified ``address``
Returns:
A ``str`` with the string contents (NUL terminator is omitted),
or an empty string if no NUL terminator could be found.
"""
data = ''
while True:
c = self.read(address, 1)
read_size = 0x1000
partial_page = address & 0xfff
if partial_page:
read_size -= partial_page
c = self.read(address, read_size)
if not c:
return ''
if c == '\x00':
return data
data += c
address += 1
if '\x00' in c:
return data[:data.index('\x00')]
address += len(c)
def flat(self, address, *a, **kw):
"""Writes a full array of values to the specified address.
@@ -1740,4 +1772,3 @@ class ELF(ELFFile):
return
log.error("Could not find PT_GNU_STACK, stack should already be executable")
+3 -5
View File
@@ -14,11 +14,9 @@ class ArmEncoder(Encoder):
arch = 'arm'
blacklist = {chr(c) for c in range(256) if chr(c) in (string.ascii_letters + string.digits)}
icache_flush = 1
def __call__(self, input, avoid, pcreg=None):
icache_flush = 1
# If randomization is disabled, ensure that the seed
# is always the same for the builder.
state = random.getstate()
@@ -29,9 +27,9 @@ class ArmEncoder(Encoder):
b = builder.builder()
enc_data = b.enc_data_builder(input)
dec_loop = b.DecoderLoopBuilder(icache_flush)
dec_loop = b.DecoderLoopBuilder(self.icache_flush)
enc_dec_loop = b.encDecoderLoopBuilder(dec_loop)
dec = b.DecoderBuilder(dec_loop, icache_flush)
dec = b.DecoderBuilder(dec_loop, self.icache_flush)
output,dec = b.buildInit(dec);
+115 -21
View File
@@ -100,6 +100,7 @@ import time
from pwnlib import adb
from pwnlib import atexit
from pwnlib import elf
from pwnlib import qemu
from pwnlib import tubes
from pwnlib.asm import _bfdname
from pwnlib.asm import make_elf
@@ -107,7 +108,6 @@ 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
@@ -127,10 +127,19 @@ def debug_assembly(asm, gdbscript=None, vma=None):
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.
**kwargs: Override any :obj:`pwnlib.context.context` values.
Returns:
:class:`.process`
Example:
.. code-block:: python
assembly = shellcraft.echo("Hello world!\n")
io = gdb.debug_assembly(assembly)
io.recvline()
# 'Hello world!'
"""
tmp_elf = make_elf_from_assembly(asm, vma=vma, extract=False)
os.chmod(tmp_elf, 0777)
@@ -153,10 +162,20 @@ def debug_shellcode(data, gdbscript=None, vma=None):
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: Override any :obj:`pwnlib.context.context` values.
Returns:
:class:`.process`
Example:
.. code-block:: python
assembly = shellcraft.echo("Hello world!\n")
shellcode = asm(assembly)
io = gdb.debug_shellcode(shellcode)
io.recvline()
# 'Hello world!'
"""
if isinstance(data, unicode):
log.error("Shellcode is cannot be unicode. Did you mean debug_assembly?")
@@ -210,7 +229,7 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None):
orig_args = args
gdbserver_args = [gdbserver]
gdbserver_args = [gdbserver, '--multi']
if context.aslr:
gdbserver_args += ['--no-disable-randomization']
else:
@@ -230,6 +249,12 @@ def _gdbserver_port(gdbserver, ssh):
# Process /bin/bash created; pid = 14366
# Listening on port 34816
process_created = gdbserver.recvline()
if process_created.startswith('ERROR:'):
raise ValueError(
'Failed to spawn process under gdbserver. gdbserver error message: %s' % process_created
)
gdbserver.pid = int(process_created.split()[-1], 0)
listening_on = ''
@@ -268,7 +293,7 @@ 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, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, **kwargs):
"""debug(args) -> tube
Launch a GDB server with the specified command line,
@@ -280,6 +305,8 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, **kwargs):
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.
sysroot(str): Foreign-architecture sysroot, used for QEMU-emulated binaries
and Android targets.
Returns:
:class:`.process` or :class:`.ssh_channel`: A tube connected to the target process
@@ -290,14 +317,6 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, **kwargs):
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.
@@ -322,6 +341,22 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, **kwargs):
- Generally, you just add a few ``continue`` commands until things are set up
the way you want it to be.
Examples:
.. code-block:: python
# Create a new process, and stop it at 'main'
io = gdb.debug('bash', '''
break main
continue
''')
# Send a command to Bash
io.sendline("echo hello")
# Interact with the process
io.interactive()
.. code-block:: python
# Create a new process, and stop it at 'main'
@@ -336,6 +371,12 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, **kwargs):
continue
''')
# Send a command to Bash
io.sendline("echo hello")
# Interact with the process
io.interactive()
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.
@@ -345,12 +386,18 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, **kwargs):
shell = ssh('passcode', 'pwnable.kr', 2222, password='guest')
# Start a process on the server
bash = gdb.debug(['bash'],
io = gdb.debug(['bash'],
ssh=shell,
gdbscript='''
break main
continue
''')
# Send a command to Bash
io.sendline("echo hello")
# Interact with the process
io.interactive()
"""
if isinstance(args, (int, tubes.process.process, tubes.ssh.ssh_channel)):
log.error("Use gdb.attach() to debug a running process")
@@ -365,6 +412,7 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, **kwargs):
runner = _get_runner(ssh)
which = _get_which(ssh)
gdbscript = gdbscript or ''
if context.noptrace:
log.warn_once("Skipping debugger since context.noptrace==True")
@@ -374,7 +422,15 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, **kwargs):
args = _gdbserver_args(args=args, which=which)
else:
qemu_port = random.randint(1024, 65535)
args = [get_qemu_user(), '-g', str(qemu_port)] + args
qemu_user = qemu.user_path()
sysroot = sysroot or qemu.ld_prefix(env=env)
if not qemu_user:
log.error("Cannot debug %s binaries without appropriate QEMU binaries" % context.arch)
args = [qemu_user, '-g', str(qemu_port)] + args
# Use a sane default sysroot for Android
if not sysroot and context.os == 'android':
sysroot = 'remote:/'
# Make sure gdbserver/qemu is installed
if not which(args[0]):
@@ -383,6 +439,8 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, **kwargs):
exe = exe or which(orig_args[0])
if not exe:
log.error("%s does not exist" % orig_args[0])
else:
gdbscript = 'file %s\n%s' % (exe, gdbscript)
# Start gdbserver/qemu
# (Note: We override ASLR here for the gdbserver process itself.)
@@ -401,7 +459,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, gdbscript=gdbscript, need_ptrace_scope = False, ssh=ssh, sysroot=sysroot)
# gdbserver outputs a message when a client connects
garbage = gdbserver.recvline(timeout=1)
@@ -425,8 +483,13 @@ def binary():
Returns:
str: Path to the appropriate ``gdb`` binary to use.
Example:
>>> gdb.binary() # doctest: +SKIP
'/usr/bin/gdb'
"""
gdb = misc.which('gdb')
gdb = misc.which('pwntools-gdb') or misc.which('gdb')
if not context.native:
multiarch = misc.which('gdb-multiarch')
@@ -443,7 +506,7 @@ def binary():
return gdb
@LocalContext
def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_args = None, ssh = None):
def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_args = None, ssh = None, sysroot = None):
"""attach(target, gdbscript = None, exe = None, arch = None, ssh = None) -> None
Start GDB in a new terminal and attach to `target`.
@@ -455,6 +518,8 @@ def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_a
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.
sysroot(str): Foreign-architecture sysroot, used for QEMU-emulated binaries
and Android targets.
Returns:
PID of the GDB process (or the window which it is running in).
@@ -479,6 +544,8 @@ def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_a
This will use the GDB installed on the remote machine.
If a password is required to connect, the ``sshpass`` program must be installed.
Examples:
.. code-block:: python
# Attach directly to pid 1234
@@ -552,11 +619,17 @@ def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_a
if gdbscript and not gdbscript.endswith('\n'):
gdbscript += '\n'
# Use a sane default sysroot for Android
if not sysroot and context.os == 'android':
sysroot = 'remote:/'
# gdb script to run before `gdbscript`
pre = ''
if not context.native:
pre += 'set endian %s\n' % context.endian
pre += 'set architecture %s\n' % get_gdb_arch()
if sysroot:
pre += 'set sysroot %s\n' % sysroot
if context.os == 'android':
pre += 'set gnutarget ' + _bfdname() + '\n'
@@ -611,9 +684,18 @@ def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_a
pid = pids[0]
elif isinstance(target, tubes.process.process):
pid = proc.pidof(target)[0]
exe = exe or target.executable
elif isinstance(target, tuple) and len(target) == 2:
host, port = target
pre += 'target remote %s:%d\n' % (host, port)
if context.os != 'android':
pre += 'target remote %s:%d\n' % (host, port)
else:
# Android debugging is done over gdbserver, which can't follow
# new inferiors (tldr; follow-fork-mode child) unless it is run
# in extended-remote mode.
pre += 'target extended-remote %s:%d\n' % (host, port)
pre += 'set detach-on-fork off\n'
def findexe():
for spid in proc.pidof(target):
@@ -640,12 +722,20 @@ 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')
if exe:
# The 'file' statement should go first
pre = 'file %s\n%s' % (exe, pre)
cmd = binary()
if gdb_args:
cmd += ' '
cmd += ' '.join(gdb_args)
if context.gdbinit:
cmd += ' -nh ' # ignore ~/.gdbinit
cmd += ' -x %s ' % context.gdbinit # load custom gdbinit
cmd += ' -q '
if exe and context.native:
@@ -666,7 +756,11 @@ def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_a
gdbserver = runner(gdb_cmd)
port = _gdbserver_port(gdbserver, None)
host = context.adb_host
pre += 'target remote %s:%i' % (context.adb_host, port)
pre += 'target extended-remote %s:%i\n' % (context.adb_host, port)
# gdbserver on Android sets 'detach-on-fork on' which breaks things
# when you're trying to debug anything that forks.
pre += 'set detach-on-fork off\n'
gdbscript = pre + (gdbscript or '')
@@ -694,7 +788,7 @@ def ssh_gdb(ssh, argv, gdbscript = None, arch = None, **kwargs):
argv = [argv]
exe = argv[0]
argv = ["gdbserver", "127.0.0.1:0"] + argv
argv = ["gdbserver", "--multi", "127.0.0.1:0"] + argv
# Download the executable
local_exe = os.path.basename(exe)
+1 -1
View File
@@ -3,7 +3,7 @@ from os.path import basename
from docutils import nodes
from docutils import statemachine
from sphinx.util.compat import Directive
from docutils.parsers.rst import Directive
try:
from StringIO import StringIO
+1 -1
View File
@@ -363,7 +363,7 @@ class Logger(object):
"""
m = message % args
if m not in self._one_time_warnings:
if self.isEnabledFor(logging.INFO):
if self.isEnabledFor(logging.WARNING):
self._one_time_warnings.add(m)
self._log(logging.WARNING, message, args, kwargs, 'warning_once')
+112 -7
View File
@@ -1,5 +1,81 @@
"""Run foreign-architecture binaries
Overview
--------
So you want to exploit ARM binaries on your Intel PC?
Pwntools has a good level of integration with QEMU user-mode emulation,
in order to run, debug, and pwn foreign architecture binaries.
In general, everything magic happens "behind the scenes", and pwntools
attempts to make your life easier.
When using :class:`.process.process`, pwntools will attempt to blindly
execute the binary, in case your system is configured to use ``binfmt-misc``.
If this fails, pwntools will attempt to manually launch the binary under
qemu user-mode emulation. Preference is given to statically-linked variants,
i.e. ``qemu-arm-static`` will be selected before ``qemu-arm``.
Debugging
~~~~~~~~~
When debugging binaries with :func:`.gdb.debug`, pwntools automatically adds
the appropriate command-line flags to QEMU to start its GDB stub, and
automatically informs GDB of the correct architecture and sysroot.
Sysroot
~~~~~~~
You can override the default sysroot by setting the ``QEMU_LD_PREFIX``
environment variable. This affects where ``qemu`` will look for files when
``open()`` is called, e.g. when the linker is attempting to resolve ``libc.so``.
Required Setup
--------------
For Ubuntu 16.04 and newer, the setup is relatively straightforward for most
architectures.
First, install the QEMU emulator itself. If your binary is statically-linked,
thsi is sufficient.
$ sudo apt-get install qemu-user
If your binary is dynamically linked, you need to install libraries like libc.
Generally, this package is named ``libc6-$ARCH-cross``, e.g. ``libc-mips-cross``.
ARM comes in both soft-float and hard-float variants, e.g. ``armhf``.
$ sudo apt-get install libc6-arm64-cross
If your binary relies on additional libraries, you can generally find them
easily with ``apt-cache search``. For example, if it's a C++ binary it
may require ``libstdc++``.
$ apt-cache search 'libstdc++' | grep arm64
Any other libraries that you require you'll have to find some other way.
Telling QEMU Where Libraries Are
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The libraries are now installed on your system at e.g. ``/usr/aarch64-linux-gnu``.
QEMU does not know where they are, and expects them to be at e.g. ``/etc/qemu-binfmt/aarch64``.
If you try to run your library now, you'll probably see an error about ``libc.so.6`` missing.
Create the ``/etc/qemu-binfmt`` directory if it does not exist, and create a symlink to
the appropriate path.
$ sudo mkdir /etc/qemu-binfmt
$ sudo ln -s /usr/aarch64-linux-gnu /etc/qemu-binfmt/aarch64
Now QEMU should be able to run the libraries.
"""
from __future__ import absolute_import
import os
from pwnlib.context import LocalContext
from pwnlib.context import context
from pwnlib.log import getLogger
@@ -8,14 +84,14 @@ from pwnlib.util import misc
log = getLogger(__name__)
@LocalContext
def get_qemu_arch():
def archname():
"""
Returns the name which QEMU uses for the currently selected
architecture.
>>> get_qemu_arch()
>>> pwnlib.qemu.archname()
'i386'
>>> get_qemu_arch(arch='powerpc')
>>> pwnlib.qemu.archname(arch='powerpc')
'ppc'
"""
return {
@@ -31,17 +107,17 @@ def get_qemu_arch():
}.get((context.arch, context.endian), context.arch)
@LocalContext
def get_qemu_user():
def user_path():
"""
Returns the path to the QEMU-user binary for the currently
selected architecture.
>>> get_qemu_user()
>>> pwnlib.qemu.user_path()
'qemu-i386-static'
>>> get_qemu_user(arch='thumb')
>>> pwnlib.qemu.user_path(arch='thumb')
'qemu-arm-static'
"""
arch = get_qemu_arch()
arch = archname()
normal = 'qemu-' + arch
static = normal + '-static'
@@ -52,3 +128,32 @@ def get_qemu_user():
return normal
log.warn_once("Neither %r nor %r are available" % (normal, static))
@LocalContext
def ld_prefix(path=None, env=None):
"""Returns the linker prefix for the selected qemu-user binary
>>> pwnlib.qemu.ld_prefix(arch='arm')
'/etc/qemu-binfmt/arm'
"""
if path is None:
path = user_path()
# Did we explicitly specify the path in an environment variable?
if env and 'QEMU_LD_PREFIX' in env:
return env['QEMU_LD_PREFIX']
if 'QEMU_LD_PREFIX' in os.environ:
return os.environ['QEMU_LD_PREFIX']
# Cyclic imports!
from pwnlib.tubes.process import process
with context.quiet:
with process([path, '--help'], env=env) as io:
line = io.recvline_regex('QEMU_LD_PREFIX *=')
name, libpath = line.split('=', 1)
return libpath.strip()
+9 -1
View File
@@ -233,4 +233,12 @@ class Call(object):
args.extend(map(repr, arg.values))
else:
args.append(arg)
return '%s(%s)' % (self.name or fmt % self.target, ', '.join(map(str, args)))
name = self.name or (fmt % self.target)
arg_str = []
for arg in args:
if isinstance(arg, (int,long)) and arg > 0x100:
arg_str.append(hex(arg))
else:
arg_str.append(str(arg))
return '%s(%s)' % (name, ', '.join(arg_str))
+254 -70
View File
@@ -40,7 +40,7 @@ standard Linux ABIs.
0x0004: 0x64636261
0x0008: 0x2
0x000c: 0xdeadbeef read(4, 5, 6)
0x0010: 'eaaa' <pad>
0x0010: 'eaaa' <return address>
0x0014: 0x4 arg0
0x0018: 0x5 arg1
0x001c: 0x6 arg2
@@ -55,19 +55,18 @@ The stack is automatically adjusted for the next frame
0x0004: 0x64636261
0x0008: 0x2
0x000c: 0xdeadbeef read(4, 5, 6)
0x0010: 0x10000000 <adjust: add esp, 0x10; ret>
0x0010: 0x10000000 <adjust @0x24> add esp, 0x10; ret
0x0014: 0x4 arg0
0x0018: 0x5 arg1
0x001c: 0x6 arg2
0x0020: 'iaaa' <pad>
0x0024: 0xdecafbad write(7, 8, 9)
0x0028: 0x10000000 <adjust: add esp, 0x10; ret>
0x0028: 0x10000000 <adjust @0x3c> add esp, 0x10; ret
0x002c: 0x7 arg0
0x0030: 0x8 arg1
0x0034: 0x9 arg2
0x0038: 'oaaa' <pad>
0x003c: 0xfeedface exit()
0x0040: 'qaaa' <pad>
ROP Example
-------------------
@@ -106,20 +105,19 @@ Finally, let's build our ROP stack
>>> rop.write(c.STDOUT_FILENO, binary.symbols['flag'], 8)
>>> rop.exit()
>>> print rop.dump()
0x0000: 0x10000012 write(STDOUT_FILENO, 268435494, 8)
0x0004: 0x1000000e <adjust: add esp, 0x10; ret>
0x0000: 0x10000012 write(STDOUT_FILENO, 0x10000026, 8)
0x0004: 0x1000000e <adjust @0x18> add esp, 0x10; ret
0x0008: 0x1 arg0
0x000c: 0x10000026 flag
0x0010: 0x8 arg2
0x0014: 'faaa' <pad>
0x0018: 0x1000002f exit()
0x001c: 'haaa' <pad>
The raw data from the ROP stack is available via `str`.
>>> raw_rop = str(rop)
>>> print enhex(raw_rop)
120000100e000010010000002600001008000000666161612f00001068616161
120000100e000010010000002600001008000000666161612f000010
Let's try it out!
@@ -128,6 +126,63 @@ Let's try it out!
>>> print p.recvall(timeout=5)
The flag
ROP Example (amd64)
-------------------
For amd64 binaries, the registers are loaded off the stack. Pwntools can do
basic reasoning about simple "pop; pop; add; ret"-style gadgets, and satisfy
requirements so that everything "just works".
>>> context.clear(arch='amd64')
>>> assembly = 'pop rdx; pop rdi; pop rsi; add rsp, 0x20; ret; target: ret'
>>> binary = ELF.from_assembly(assembly)
>>> rop = ROP(binary)
>>> rop.target(1,2,3)
>>> print rop.dump()
0x0000: 0x10000000 pop rdx; pop rdi; pop rsi; add rsp, 0x20; ret
0x0008: 0x3 [arg2] rdx = 3
0x0010: 0x1 [arg0] rdi = 1
0x0018: 0x2 [arg1] rsi = 2
0x0020: 'iaaajaaa' <pad 0x20>
0x0028: 'kaaalaaa' <pad 0x18>
0x0030: 'maaanaaa' <pad 0x10>
0x0038: 'oaaapaaa' <pad 0x8>
0x0040: 0x10000008 target
>>> rop.target(1)
>>> print rop.dump()
0x0000: 0x10000000 pop rdx; pop rdi; pop rsi; add rsp, 0x20; ret
0x0008: 0x3 [arg2] rdx = 3
0x0010: 0x1 [arg0] rdi = 1
0x0018: 0x2 [arg1] rsi = 2
0x0020: 'iaaajaaa' <pad 0x20>
0x0028: 'kaaalaaa' <pad 0x18>
0x0030: 'maaanaaa' <pad 0x10>
0x0038: 'oaaapaaa' <pad 0x8>
0x0040: 0x10000008 target
0x0048: 0x10000001 pop rdi; pop rsi; add rsp, 0x20; ret
0x0050: 0x1 [arg0] rdi = 1
0x0058: 'waaaxaaa' <pad rsi>
0x0060: 'yaaazaab' <pad 0x20>
0x0068: 'baabcaab' <pad 0x18>
0x0070: 'daabeaab' <pad 0x10>
0x0078: 'faabgaab' <pad 0x8>
0x0080: 0x10000008 target
Pwntools will also filter out some bad instructions while setting the registers
( e.g. syscall, int 0x80... )
>>> assembly = 'syscall; pop rdx; pop rsi; ret ; pop rdi ; int 0x80; pop rsi; pop rdx; ret ; pop rdi ; ret'
>>> binary = ELF.from_assembly(assembly)
>>> rop = ROP(binary)
>>> rop.call(0xdeadbeef, [1, 2, 3])
>>> print rop.dump()
0x0000: 0x1000000b pop rdi; ret
0x0008: 0x1 [arg0] rdi = 1
0x0010: 0x10000008 pop rsi; pop rdx; ret
0x0018: 0x2 [arg1] rsi = 2
0x0020: 0x3 [arg2] rdx = 3
0x0028: 0xdeadbeef
ROP + Sigreturn
-----------------------
@@ -160,7 +215,7 @@ That's all there is to it.
>>> print rop.dump()
0x0000: 0x1000000e pop eax; ret
0x0004: 0x77
0x0004: 0x77 [arg0] eax = SYS_sigreturn
0x0008: 0x1000000b int 0x80
0x000c: 0x0 gs
0x0010: 0x0 fs
@@ -197,8 +252,10 @@ from __future__ import absolute_import
import collections
import copy
import hashlib
import itertools
import os
import re
import string
import sys
import tempfile
@@ -215,9 +272,9 @@ from pwnlib.rop.call import CurrentStackPointer
from pwnlib.rop.call import NextGadgetAddress
from pwnlib.rop.call import StackAdjustment
from pwnlib.rop.gadgets import Gadget
from pwnlib.util import cyclic
from pwnlib.util import lists
from pwnlib.util import packing
from pwnlib.util.cyclic import cyclic
from pwnlib.util.packing import *
log = getLogger(__name__)
@@ -228,6 +285,8 @@ class Padding(object):
"""
Placeholder for exactly one pointer-width of padding.
"""
def __init__(self, name='<pad>'):
self.name = name
class DescriptiveStack(list):
"""
@@ -306,16 +365,16 @@ class ROP(object):
>>> r.execve(4, 5, 6)
>>> print r.dump()
0x0000: 0x10001234 funcname(1, 2)
0x0004: 0x10000003 <adjust: add esp, 0x10; ret>
0x0004: 0x10000003 <adjust @0x18> add esp, 0x10; ret
0x0008: 0x1 arg0
0x000c: 0x2 arg1
0x0010: 'eaaa' <pad>
0x0014: 'faaa' <pad>
0x0018: 0x10001234 funcname(3)
0x001c: 0x10000007 <adjust: pop eax; ret>
0x001c: 0x10000007 <adjust @0x24> pop eax; ret
0x0020: 0x3 arg0
0x0024: 0x10000007 pop eax; ret
0x0028: 0x77
0x0028: 0x77 [arg0] eax = SYS_sigreturn
0x002c: 0x10000000 int 0x80
0x0030: 0x0 gs
0x0034: 0x0 fs
@@ -344,16 +403,16 @@ class ROP(object):
>>> r.execve(4, 5, 6)
>>> print r.dump()
0x8048000: 0x10001234 funcname(1, 2)
0x8048004: 0x10000003 <adjust: add esp, 0x10; ret>
0x8048004: 0x10000003 <adjust @0x8048018> add esp, 0x10; ret
0x8048008: 0x1 arg0
0x804800c: 0x2 arg1
0x8048010: 'eaaa' <pad>
0x8048014: 'faaa' <pad>
0x8048018: 0x10001234 funcname(3)
0x804801c: 0x10000007 <adjust: pop eax; ret>
0x804801c: 0x10000007 <adjust @0x8048024> pop eax; ret
0x8048020: 0x3 arg0
0x8048024: 0x10000007 pop eax; ret
0x8048028: 0x77
0x8048028: 0x77 [arg0] eax = SYS_sigreturn
0x804802c: 0x10000000 int 0x80
0x8048030: 0x0 gs
0x8048034: 0x0 fs
@@ -375,25 +434,24 @@ class ROP(object):
0x8048074: 0x0 esp_at_signal
0x8048078: 0x2b ss
0x804807c: 0x0 fpstate
>>> elf = ELF.from_assembly('ret')
>>> r = ROP(elf)
>>> r.ret.address == 0x10000000
True
>>> r = ROP(elf, badchars='\x00')
>>> r.gadgets == {}
True
>>> r.ret is None
True
"""
#: List of individual ROP gadgets, ROP calls, SROP frames, etc.
#: This is intended to be the highest-level abstraction that we can muster.
_chain = []
#: List of ELF files which are available for mining gadgets
elfs = []
#: Stack address where the first byte of the ROP chain lies, if known.
base = 0
#: Whether or not the ROP chain directly sets the stack pointer to a value
#: which is not contiguous
migrated = False
def __init__(self, elfs, base = None, **kwargs):
def __init__(self, elfs, base = None, badchars = '', **kwargs):
"""
Arguments:
elfs(list): List of :class:`.ELF` objects for mining
base(int): Stack address where the first byte of the ROP chain lies, if known.
badchars(str): Characters which should not appear in ROP gadget addresses.
"""
import ropgadget
@@ -402,10 +460,24 @@ class ROP(object):
elfs = [elfs]
elif isinstance(elfs, (str, unicode)):
elfs = [ELF(elfs)]
self.elfs = elfs
#: List of individual ROP gadgets, ROP calls, SROP frames, etc.
#: This is intended to be the highest-level abstraction that we can muster.
self._chain = []
#: List of ELF files which are available for mining gadgets
self.elfs = elfs
#: Stack address where the first byte of the ROP chain lies, if known.
self.base = base
#: Whether or not the ROP chain directly sets the stack pointer to a value
#: which is not contiguous
self.migrated = False
#: Characters which should not appear in ROP gadget addresses.
self._badchars = set(badchars)
self.__load()
@staticmethod
@@ -415,24 +487,102 @@ class ROP(object):
def setRegisters(self, registers):
"""
Returns an OrderedDict of addresses/values which will set the specified
register context.
Returns an list of addresses/values which will set the specified register context.
Arguments:
registers(dict): Dictionary of ``{register name: value}``
Returns:
An OrderedDict of ``{register: sequence of gadgets, values, etc.}``.
A list of tuples, ordering the stack.
Each tuple is in the form of ``(value, name)`` where ``value`` is either a
gadget address or literal value to go on the stack, and ``name`` is either
a string name or other item which can be "unresolved".
Note:
This is basically an implementation of the Set Cover Problem, which is
NP-hard. This means that we will take polynomial time N**2, where N is
the number of gadgets. We can reduce runtime by discarding useless and
inferior gadgets ahead of time.
"""
reg_order = collections.OrderedDict()
if not registers:
return []
for reg, value in registers.items():
gadget = self.find_gadget(['pop ' + reg, 'ret'])
if not gadget:
log.error("can't set %r" % reg)
reg_order[reg] = [gadget, value]
regset = set(registers)
return reg_order
bad_instructions = set(('syscall', 'sysenter', 'int 0x80'))
# Collect all gadgets which use these registers
# Also collect the "best" gadget for each combination of registers
gadgets = []
best_gadgets = {}
for gadget in self.gadgets.values():
# Do not use gadgets which doesn't end with 'ret'
if gadget.insns[-1] != 'ret':
continue
# Do not use gadgets which contain 'syscall' or 'int'
if set(gadget.insns) & bad_instructions:
continue
touched = tuple(regset & set(gadget.regs))
if not touched:
continue
old = best_gadgets.get(touched, gadget)
# if we have a new gadget for the touched registers, choose it
# if the new gadget requires less stack space, choose it
# if both gadgets require same stack space, choose the one with less instructions
if (old is gadget) \
or (old.move > gadget.move) \
or (old.move == gadget.move and len(old.insns) > len(gadget.insns)):
best_gadgets[touched] = gadget
winner = None
budget = 999999999
for num_gadgets in range(len(registers)):
for combo in itertools.combinations(best_gadgets.values(), 1+num_gadgets):
# Is this better than what we can already do?
cost = sum((g.move for g in combo))
if cost > budget:
continue
# Does it hit all of the registers we want?
coverage = set(sum((g.regs for g in combo), [])) & regset
if coverage != regset:
continue
# It is better than what we had, and hits all of the registers.
winner = combo
budget = cost
if not winner:
log.error("Could not satisfy setRegisters(%r)", registers)
# We have our set of "winner" gadgets, let's build a stack!
stack = []
for gadget in winner:
moved = 8 # Account for the gadget itself
goodregs = set(gadget.regs) & regset
name = ",".join(goodregs)
stack.append((gadget.address, gadget))
for r in gadget.regs:
moved += 8
if r in registers:
stack.append((registers[r], r))
else:
stack.append((Padding('<pad %s>' % r), r))
for slot in range(moved, gadget.move, context.bytes):
left = gadget.move - slot
stack.append((Padding('<pad %#x>' % left), 'stack padding'))
return stack
def resolve(self, resolvable):
"""Resolves a symbol to an address
@@ -486,8 +636,12 @@ class ROP(object):
''
"""
# Ensure we don't generate a cyclic pattern which contains badchars
alphabet = ''.join(c for c in string.ascii_lowercase if c not in self._badchars)
if count:
return cyclic.cyclic(offset + count)[-count:]
return cyclic(offset + count, alphabet=alphabet)[-count:]
return ''
def describe(self, object):
@@ -575,13 +729,17 @@ class ROP(object):
stack.describe(self.describe(slot))
registers = dict(zip(slot.abi.register_arguments, slot.args))
setRegisters = self.setRegisters(registers)
for register, gadgets in setRegisters.items():
value = registers[register]
description = self.describe(value) or 'arg%i' % slot.args.index(value)
stack.describe('set %s = %s' % (register, description))
stack.extend(gadgets)
for value, name in self.setRegisters(registers):
if name in registers:
index = slot.abi.register_arguments.index(name)
description = self.describe(value) or value
stack.describe('[arg%s] %s = %s' % (index, name, description))
elif isinstance(name, Gadget):
stack.describe('; '.join(name.insns))
elif isinstance(name, str):
stack.describe(name)
stack.append(value)
if address != stack.next:
stack.describe(slot.name)
@@ -599,24 +757,33 @@ class ROP(object):
# properly, but likely also need to adjust the stack past the
# arguments.
if slot.abi.returns:
if remaining:
fix_size = (1 + len(stackArguments))
fix_bytes = fix_size * context.bytes
adjust = self.search(move = fix_bytes)
if not adjust:
log.error("Could not find gadget to adjust stack by %#x bytes" % fix_bytes)
# Save off the address of the next gadget
if remaining or stackArguments:
nextGadgetAddr = stack.next
nextGadgetAddr = stack.next + adjust.move
# If there were arguments on the stack, we need to stick something
# in the slot where the return address goes.
if len(stackArguments) > 0:
if remaining:
fix_size = (1 + len(stackArguments))
fix_bytes = fix_size * context.bytes
adjust = self.search(move = fix_bytes)
stack.describe('<adjust: %s>' % self.describe(adjust))
stack.append(adjust.address)
if not adjust:
log.error("Could not find gadget to adjust stack by %#x bytes" % fix_bytes)
for pad in range(fix_bytes, adjust.move, context.bytes):
stackArguments.append(Padding())
else:
stack.describe('<pad>')
stack.append(Padding())
nextGadgetAddr += adjust.move
stack.describe('<adjust @%#x> %s' % (nextGadgetAddr, self.describe(adjust)))
stack.append(adjust.address)
for pad in range(fix_bytes, adjust.move, context.bytes):
stackArguments.append(Padding())
# We could not find a proper "adjust" gadget, but also didn't need one.
else:
stack.append(Padding("<return address>"))
for i, argument in enumerate(stackArguments):
@@ -657,7 +824,7 @@ class ROP(object):
elif isinstance(slot, Padding):
stack[i] = self.generatePadding(i * context.bytes, context.bytes)
stack.describe('<pad>', slot_address)
stack.describe(slot.name, slot_address)
elif isinstance(slot, Gadget):
stack[i] = slot.address
@@ -835,13 +1002,22 @@ class ROP(object):
"""Returns: Raw bytes of the ROP chain"""
return self.chain()
def __get_cachefile_name(self, elf):
basename = os.path.basename(elf.file.name)
sha256 = hashlib.sha256(elf.get_data()).hexdigest()
def __get_cachefile_name(self, files):
"""Given an ELF or list of ELF objects, return a cache file for the set of files"""
cachedir = os.path.join(tempfile.gettempdir(), 'pwntools-rop-cache')
if not os.path.exists(cachedir):
os.mkdir(cachedir)
return os.path.join(cachedir, sha256)
if isinstance(files, ELF):
files = [files]
hashes = []
for elf in self.elfs:
sha256 = hashlib.sha256(elf.get_data()).hexdigest()
hashes.append(sha256)
return os.path.join(cachedir, '_'.join(hashes))
def __cache_load(self, elf):
filename = self.__get_cachefile_name(elf)
@@ -871,7 +1047,7 @@ class ROP(object):
#
pop = re.compile(r'^pop (.{3})')
add = re.compile(r'^add .sp, (\S+)$')
add = re.compile(r'^add [er]sp, (\S+)$')
ret = re.compile(r'^ret$')
leave = re.compile(r'^leave$')
int80 = re.compile(r'int +0x80')
@@ -950,6 +1126,11 @@ class ROP(object):
}[context.bytes]
for addr, insns in gadgets.items():
# Filter out gadgets by address against badchars
if set(pack(addr)) & self._badchars:
continue
sp_move = 0
regs = []
for insn in insns:
@@ -999,7 +1180,10 @@ class ROP(object):
regs = set(regs or ())
for addr, gadget in self.gadgets.items():
if gadget.move < move: continue
addr_bytes = set(pack(gadget.address))
if addr_bytes & self._badchars: continue
if gadget.insns[-1] != 'ret': continue
if gadget.move < move: continue
if not (regs <= set(gadget.regs)): continue
yield gadget
+3 -1
View File
@@ -40,6 +40,8 @@ mips = {
'$31': 31, '$ra': 31,
}
mips_list = list(mips)
arm = map('r{}'.format, range(13))
arm += ["sp", "lr", "pc", "cpsr"]
@@ -211,7 +213,7 @@ def current():
'arm': arm,
'thumb': arm,
'aarch64': aarch64,
'mips': mips,
'mips': mips_list,
'powerpc': powerpc
}[context.arch]
@@ -1,7 +1,7 @@
<%
from pwnlib import shellcraft
%>
<%page args="filename, mode='O_RDONLY'"/>
<%page args="filename, flags='O_RDONLY', mode=0"/>
<%docstring>
Opens a file
</%docstring>
@@ -9,4 +9,4 @@ Opens a file
AT_FDCWD=-100
%>
${shellcraft.pushstr(filename)}
${shellcraft.syscall('SYS_openat', AT_FDCWD, 'sp', mode, 0)}
${shellcraft.syscall('SYS_openat', AT_FDCWD, 'sp', flags, mode)}
@@ -8,5 +8,5 @@ Opens a file and writes its contents to the specified file descriptor.
</%docstring>
${pushstr(filename)}
${syscall('SYS_open', 'rsp', 0, 'O_RDONLY')}
${syscall('SYS_open', 'rsp', 'O_RDONLY', 0)}
${syscall('SYS_sendfile', fd, 'rax', 0, 0x7fffffff)}
@@ -10,7 +10,7 @@ Args: [sock (imm/reg) = rbp]
after = common.label("after")
%>
/* dup() file descriptor ${sock} into stdin/stdout/stderr */
${dup}:
${amd64.mov('rbp', sock)}
@@ -15,5 +15,5 @@ Example:
</%docstring>
${arm.pushstr(filename)}
${arm.linux.open('sp', 0, int(constants.O_RDONLY))}
${arm.linux.open('sp', int(constants.O_RDONLY), 0)}
${arm.linux.sendfile(fd, 'r0', 0, 0x7fffffff)}
@@ -19,5 +19,5 @@ label = common.label("sendfile_loop")
%>
${sc.pushstr(filename)}
${sc.open('esp', 0, 'O_RDONLY')}
${sc.open('esp', 'O_RDONLY', 0)}
${sc.sendfile(fd, 'eax', 0, 0x7fffffff)}
@@ -11,6 +11,7 @@ Args: [sock (imm/reg) = ebp]
looplabel = common.label("loop")
%>
/* dup() file descriptor ${sock} into stdin/stdout/stderr */
${dup}:
${mov('ebx', sock)}
${mov('ecx', 3)}
@@ -22,5 +22,5 @@ label = common.label("sendfile_loop")
%>
${mips.pushstr(filename)}
${mips.open('$sp', 0, int(constants.O_RDONLY))}
${mips.open('$sp', int(constants.O_RDONLY), 0)}
${mips.sendfile(fd, '$v0', 0, 0x7fffffff)}
+10 -1
View File
@@ -1,4 +1,13 @@
<% from pwnlib.shellcraft import mips %>
<%docstring>Execute /bin/sh</%docstring>
<%docstring>Execute /bin/sh
Example:
>>> p = run_assembly(shellcraft.mips.linux.sh())
>>> p.sendline('echo Hello')
>>> p.recv()
'Hello\n'
</%docstring>
${mips.execve('//bin/sh', ['sh'], {})}
@@ -20,6 +20,6 @@ label = common.label("sendfile_loop")
%>
${thumb.pushstr(filename)}
${thumb.linux.open('sp', 0, constants.O_RDONLY)}
${thumb.linux.open('sp', constants.O_RDONLY, 0)}
${thumb.mov('r5', 'r0')}
${thumb.linux.sendfile(fd, 'r5', 0, 0x7fffffff)}
@@ -9,6 +9,7 @@ Args: [sock (imm/reg) = r6]
dup = common.label("dup")
looplabel = common.label("loop")
%>
/* dup() file descriptor ${sock} into stdin/stdout/stderr */
${dup}:
${mov('r1', 2)}
${mov('r7', 'SYS_dup2')}
+2 -1
View File
@@ -15,8 +15,9 @@ from pwnlib.tubes import listen
from pwnlib.tubes import process
from pwnlib.tubes import remote
from pwnlib.tubes import serialtube
from pwnlib.tubes import server
from pwnlib.tubes import sock
from pwnlib.tubes import ssh
from pwnlib.tubes import tube
__all__ = ['tube', 'sock', 'remote', 'listen', 'process', 'serialtube', 'ssh']
__all__ = ['tube', 'sock', 'remote', 'listen', 'process', 'serialtube', 'server', 'ssh']
+34 -16
View File
@@ -16,9 +16,9 @@ import subprocess
import time
import tty
from pwnlib import qemu
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.qemu import get_qemu_user
from pwnlib.timeout import Timeout
from pwnlib.tubes.tube import tube
from pwnlib.util.hashes import sha256file
@@ -108,10 +108,6 @@ class process(tube):
alarm(int):
Set a SIGALRM alarm timeout on the process.
Attributes:
proc(subprocess)
Examples:
>>> p = process('python2')
@@ -238,7 +234,7 @@ class process(tube):
raise TypeError('Must provide argv or set context.binary')
#: `subprocess.Popen` object
#: :class:`subprocess.Popen` object that backs this process
self.proc = None
if not shell:
@@ -436,21 +432,21 @@ class process(tube):
# Determine what architecture the binary is, and find the
# appropriate qemu binary to run it.
qemu = get_qemu_user(arch=binary.arch)
qemu_path = qemu.user_path(arch=binary.arch)
if not qemu:
if not qemu_path:
raise exception
qemu = which(qemu)
if qemu:
self._qemu = qemu
qemu_path = which(qemu_path)
if qemu_path:
self._qemu = qemu_path
args = [qemu]
args = [qemu_path]
if self.argv:
args += ['-0', self.argv[0]]
args += ['--']
return [args, qemu]
return [args, qemu_path]
# If we get here, we couldn't run the binary directly, and
# we don't have a qemu which can run it.
@@ -569,7 +565,7 @@ class process(tube):
#
# Create a duplicate so we can modify it safely
env = dict(os.environ if env is None else env)
env = (os.environ if env is None else env).copy()
for k,v in env.items():
if not isinstance(k, (str, unicode)):
@@ -879,7 +875,7 @@ class process(tube):
from pwnlib.elf import ELF
for lib, address in self.libs().items():
if 'libc.so' in lib:
if 'libc.so' in lib or 'libc-' in lib:
e = ELF(lib)
e.address = address
return e
@@ -935,7 +931,7 @@ class process(tube):
Example:
>>> e = ELF('/bin/sh')
>>> e = ELF('/bin/bash')
>>> p = process(e.path)
In order to make sure there's not a race condition against
@@ -957,3 +953,25 @@ class process(tube):
with open('/proc/%i/mem' % self.pid, 'rb') as mem:
mem.seek(address)
return mem.read(count) or None
@property
def stdin(self):
"""Shorthand for ``self.proc.stdin``
See: :obj:`.process.proc`
"""
return self.proc.stdin
@property
def stdout(self):
"""Shorthand for ``self.proc.stdout``
See: :obj:`.process.proc`
"""
return self.proc.stdout
@property
def stderr(self):
"""Shorthand for ``self.proc.stderr``
See: :obj:`.process.proc`
"""
return self.proc.stderr
+1 -1
View File
@@ -23,7 +23,7 @@ class remote(sock):
typ: The string "tcp" or "udp" or an integer to pass to :func:`socket.getaddrinfo`.
timeout: A positive number, None or the string "default".
ssl(bool): Wrap the socket with SSL
sock(socket): Socket to inherit, rather than connecting
sock(socket.socket): Socket to inherit, rather than connecting
Examples:
+162
View File
@@ -0,0 +1,162 @@
from __future__ import absolute_import
import errno
import socket
import threading
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.tubes.sock import sock
from pwnlib.tubes.remote import remote
log = getLogger(__name__)
class server(sock):
r"""Creates an TCP or UDP-server to listen for connections. It supports
both IPv4 and IPv6.
Arguments:
port(int): The port to connect to.
Defaults to a port auto-selected by the operating system.
bindaddr(str): The address to bind to.
Defaults to ``0.0.0.0`` / `::`.
fam: The string "any", "ipv4" or "ipv6" or an integer to pass to :func:`socket.getaddrinfo`.
typ: The string "tcp" or "udp" or an integer to pass to :func:`socket.getaddrinfo`.
callback: A function to be started on incoming connections. It should take a :class:`pwnlib.tubes.remote` as its only argument.
Examples:
>>> s = server(8888)
>>> client_conn = remote('localhost', s.lport)
>>> server_conn = s.next_connection()
>>> client_conn.sendline('Hello')
>>> server_conn.recvline()
'Hello\n'
>>> def cb(r):
... client_input = r.readline()
... r.send(client_input[::-1])
...
>>> t = server(8889, callback=cb)
>>> client_conn = remote('localhost', t.lport)
>>> client_conn.sendline('callback')
>>> client_conn.recv()
'\nkcabllac'
"""
#: Local port
lport = 0
#: Local host
lhost = None
#: Socket type (e.g. socket.SOCK_STREAM)
type = None
#: Socket family
family = None
#: Socket protocol
protocol = None
#: Canonical name of the listening interface
canonname = None
#: Sockaddr structure that is being listened on
sockaddr = None
_accepter = None
def __init__(self, port=0, bindaddr = "0.0.0.0", fam = "any", typ = "tcp",
callback = None, blocking = False, *args, **kwargs):
super(server, self).__init__(*args, **kwargs)
port = int(port)
fam = {socket.AF_INET: 'ipv4',
socket.AF_INET6: 'ipv6'}.get(fam, fam)
fam = self._get_family(fam)
typ = self._get_type(typ)
if fam == socket.AF_INET6 and bindaddr == '0.0.0.0':
bindaddr = '::'
h = self.waitfor('Trying to bind to %s on port %d' % (bindaddr, port))
for res in socket.getaddrinfo(bindaddr, port, fam, typ, 0, socket.AI_PASSIVE):
self.family, self.type, self.proto, self.canonname, self.sockaddr = res
if self.type not in [socket.SOCK_STREAM, socket.SOCK_DGRAM]:
continue
h.status("Trying %s" % self.sockaddr[0])
listen_sock = socket.socket(self.family, self.type, self.proto)
listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listen_sock.bind(self.sockaddr)
self.lhost, self.lport = listen_sock.getsockname()[:2]
if self.type == socket.SOCK_STREAM:
listen_sock.listen(1)
break
else:
h.failure()
self.error("Could not bind to %s on port %d" % (bindaddr, port))
h.success()
self.sock = listen_sock
self.connections_waiting = threading.Event()
self.connections = []
def accepter():
while True:
h = self.waitfor('Waiting for connections on %s:%s' % (self.lhost, self.lport))
while True:
try:
if self.type == socket.SOCK_STREAM:
sock, rhost = listen_sock.accept()
else:
data, rhost = listen_sock.recvfrom(4096)
listen_sock.connect(rhost)
sock = listen_sock
sock.unrecv(data)
sock.settimeout(self.timeout)
break
except socket.error as e:
if e.errno == errno.EINTR:
continue
h.failure()
self.exception("Socket failure while waiting for connection")
sock = None
return
self.rhost, self.rport = rhost[:2]
r = remote(self.rhost, self.rport, sock = sock)
h.success('Got connection from %s on port %d' % (self.rhost, self.rport))
if callback:
if not blocking:
t = context.Thread(target = callback, args = (r,))
t.daemon = True
t.start()
else:
callback(r)
else:
self.connections.append(r)
if not self.connections_waiting.is_set():
self.connections_waiting.set()
self._accepter = context.Thread(target = accepter)
self._accepter.daemon = True
self._accepter.start()
def next_connection(self):
if not self.connections_waiting.is_set():
self.connections_waiting.wait()
conn = self.connections.pop(0)
if not self.connections:
self.connections_waiting.clear()
return conn
def close(self):
# since `close` is scheduled to run on exit we must check that we got
# a connection or the program will hang in the `join` call above
if self._accepter and self._accepter.is_alive():
return
super(server, self).close()
+11 -6
View File
@@ -341,7 +341,8 @@ class ssh_process(ssh_channel):
libs = self.parent.libs(self.executable)
for lib in libs:
if self.executable in lib:
# Cannot just check "executable in lib", see issue #1047
if lib.endswith(self.executable):
return pwnlib.elf.elf.ELF(lib)
@@ -873,6 +874,8 @@ os.chdir(%(cwd)r)
if env is not None:
os.environ.clear()
os.environ.update(env)
else:
env = os.environ
def is_exe(path):
return os.path.isfile(path) and os.access(path, os.X_OK)
@@ -960,7 +963,7 @@ except Exception:
%(func_src)s
apply(%(func_name)s, %(func_args)r)
os.execve(exe, argv, os.environ)
os.execve(exe, argv, env)
""" % locals()
script = script.strip()
@@ -1301,7 +1304,7 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
return misc.parse_ldd_output(data)
def _get_fingerprint(self, remote):
cmd = '(openssl sha256 || sha256 || sha256sum) 2>/dev/null < '
cmd = '(sha256 || sha256sum || openssl sha256) 2>/dev/null < '
cmd = cmd + sh_string(remote)
data, status = self.run_to_end(cmd)
@@ -1616,7 +1619,6 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
self.error('%r does not exist' % file_or_directory)
def download(self, file_or_directory, local=None):
"""download(file_or_directory, local=None)
@@ -1630,7 +1632,10 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
if not self.sftp:
self.error("Cannot determine remote file type without SFTP")
if 0 == self.system('test -d ' + sh_string(file_or_directory)).wait():
with self.system('test -d ' + sh_string(file_or_directory)) as io:
is_dir = io.wait()
if 0 == is_dir:
self.download_dir(file_or_directory, local)
else:
self.download_file(file_or_directory, local)
@@ -1991,7 +1996,7 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
if value is not None:
with open(path, 'w+') as f:
f.write(value)
else:
elif os.path.exists(path):
with open(path, 'r+') as f:
return f.read()
+121 -21
View File
@@ -2,15 +2,15 @@ from __future__ import absolute_import
import string
from pwnlib.context import context
from pwnlib.context import context, LocalContext
from pwnlib.log import getLogger
from pwnlib.util import packing
log = getLogger(__name__)
# Taken from https://en.wikipedia.org/wiki/De_Bruijn_sequence but changed to a generator
def de_bruijn(alphabet = string.ascii_lowercase, n = None):
"""de_bruijn(alphabet = string.ascii_lowercase, n = 4) -> generator
def de_bruijn(alphabet = None, n = None):
"""de_bruijn(alphabet = None, n = None) -> generator
Generator for a sequence of unique substrings of length `n`. This is implemented using a
De Bruijn Sequence over the given `alphabet`.
@@ -21,8 +21,10 @@ def de_bruijn(alphabet = string.ascii_lowercase, n = None):
alphabet: List or string to generate the sequence over.
n(int): The length of subsequences that should be unique.
"""
if alphabet is None:
alphabet = context.cyclic_alphabet
if n is None:
n = 4
n = context.cyclic_size
k = len(alphabet)
a = [0] * k * n
def db(t, p):
@@ -42,8 +44,8 @@ def de_bruijn(alphabet = string.ascii_lowercase, n = None):
return db(1,1)
def cyclic(length = None, alphabet = string.ascii_lowercase, n = None):
"""cyclic(length = None, alphabet = string.ascii_lowercase, n = 4) -> list/str
def cyclic(length = None, alphabet = None, n = None):
"""cyclic(length = None, alphabet = None, n = None) -> list/str
A simple wrapper over :func:`de_bruijn`. This function returns at most
`length` elements.
@@ -56,17 +58,68 @@ def cyclic(length = None, alphabet = string.ascii_lowercase, n = None):
alphabet: List or string to generate the sequence over.
n(int): The length of subsequences that should be unique.
Notes:
The maximum length is `len(alphabet)**n`.
The default values for `alphabet` and `n` restrict the total space to ~446KB.
If you need to generate a longer cyclic pattern, provide a longer `alphabet`,
or if possible a larger `n`.
Example:
>>> cyclic(alphabet = "ABC", n = 3)
'AAABAACABBABCACBACCBBBCBCCC'
Cyclic patterns are usually generated by providing a specific `length`.
>>> cyclic(20)
'aaaabaaacaaadaaaeaaa'
>>> cyclic(32)
'aaaabaaacaaadaaaeaaafaaagaaahaaa'
The `alphabet` and `n` arguments will control the actual output of the pattern
>>> cyclic(20, alphabet=string.ascii_uppercase)
'AAAABAAACAAADAAAEAAA'
>>> cyclic(20, n=8)
'aaaaaaaabaaaaaaacaaa'
>>> cyclic(20, n=2)
'aabacadaeafagahaiaja'
The size of `n` and `alphabet` limit the maximum length that can be generated.
Without providing `length`, the entire possible cyclic space is generated.
>>> cyclic(alphabet = "ABC", n = 3)
'AAABAACABBABCACBACCBBBCBCCC'
>>> cyclic(length=512, alphabet = "ABC", n = 3)
Traceback (most recent call last):
...
PwnlibException: Can't create a pattern length=512 with len(alphabet)==3 and n==3
The `alphabet` can be set in `context`, which is useful for circumstances
when certain characters are not allowed. See :obj:`.context.cyclic_alphabet`.
>>> context.cyclic_alphabet = "ABC"
>>> cyclic(10)
'AAAABAAACA'
The original values can always be restored with:
>>> context.clear()
The following just a test to make sure the length is correct.
>>> alphabet, n = range(30), 3
>>> len(alphabet)**n, len(cyclic(alphabet = alphabet, n = n))
(27000, 27000)
"""
if n is None:
n = 4
n = context.cyclic_size
if alphabet is None:
alphabet = context.cyclic_alphabet
if len(alphabet) ** n < length:
log.error("Can't create a pattern length=%i with len(alphabet)==%i and n==%i" \
@@ -84,8 +137,9 @@ def cyclic(length = None, alphabet = string.ascii_lowercase, n = None):
else:
return out
def cyclic_find(subseq, alphabet = string.ascii_lowercase, n = None):
"""cyclic_find(subseq, alphabet = string.ascii_lowercase, n = None) -> int
@LocalContext
def cyclic_find(subseq, alphabet = None, n = None):
"""cyclic_find(subseq, alphabet = None, n = None) -> int
Calculates the position of a substring into a De Bruijn sequence.
@@ -103,25 +157,71 @@ def cyclic_find(subseq, alphabet = string.ascii_lowercase, n = None):
integer. If an integer is provided it will be packed as a
little endian integer.
alphabet: List or string to generate the sequence over.
By default, uses :obj:`.context.cyclic_alphabet`.
n(int): The length of subsequences that should be unique.
By default, uses :obj:`.context.cyclic_size`.
Examples:
Let's generate an example cyclic pattern.
>>> cyclic(16)
'aaaabaaacaaadaaa'
Note that 'baaa' starts at offset 4. The `cyclic_find` routine shows us this:
>>> cyclic_find('baaa')
4
The *default* length of a subsequence generated by `cyclic` is `4`.
If a longer value is submitted, it is automatically truncated to four bytes.
>>> cyclic_find('baaacaaa')
4
If you provided e.g. `n=8` to `cyclic` to generate larger subsequences,
you must explicitly provide that argument.
>>> cyclic_find('baaacaaa', n=8)
3515208
We can generate a large cyclic pattern, and grab a subset of it to
check a deeper offset.
>>> cyclic_find(cyclic(1000)[514:518])
514
Instead of passing in the byte representation of the pattern, you can
also pass in the integer value. Note that this is sensitive to the
selected endianness via `context.endian`.
>>> cyclic_find(0x61616162)
4
"""
if isinstance(subseq, (int, long)):
width = 'all' if n is None else n * 8
subseq = packing.pack(subseq, width, 'little', False)
>>> cyclic_find(0x61616162, endian='big')
1
if n is None and len(subseq) != 4:
log.warn_once("cyclic_find() expects 4-byte subsequences by default, you gave %r\n" % subseq \
+ "Unless you specified cyclic(..., n=%i), you probably just want the first 4 bytes.\n" % len(subseq) \
+ "Truncating the data at 4 bytes. Specify cyclic_find(..., n=%i) to override this." % len(subseq))
subseq = subseq[:4]
You can use anything for the cyclic pattern, including non-printable
characters.
>>> cyclic_find(0x00000000, alphabet=unhex('DEADBEEF00'))
621
"""
if n is None:
n = context.cyclic_size
if isinstance(subseq, (int, long)):
subseq = packing.pack(subseq, bytes=n)
if len(subseq) != n:
log.warn_once("cyclic_find() expects %i-byte subsequences by default, you gave %r\n"\
+ "Unless you specified cyclic(..., n=%i), you probably just want the first 4 bytes.\n"\
+ "Truncating the data at 4 bytes. Specify cyclic_find(..., n=%i) to override this.",
n, subseq, len(subseq), len(subseq))
subseq = subseq[:n]
if alphabet is None:
alphabet = context.cyclic_alphabet
if any(c not in alphabet for c in subseq):
return -1
+2 -2
View File
@@ -534,8 +534,8 @@ def iter_except(func, exception):
the end.
Arguments:
func: The function to call.
exception(exception): The exception that signals the end. Other
func(callable): The function to call.
exception(Exception): The exception that signals the end. Other
exceptions will not be caught.
Returns:
+5
View File
@@ -190,6 +190,8 @@ def run_in_new_terminal(command, terminal = None, args = None):
variable), ``x-terminal-emulator`` is used.
- If tmux is detected (by the presence of the ``$TMUX`` environment
variable), a new pane will be opened.
- If GNU Screen is detected (by the presence of the ``$STY`` environment
variable), a new screen will be opened.
Arguments:
command (str): The command to run.
@@ -219,6 +221,9 @@ def run_in_new_terminal(command, terminal = None, args = None):
elif 'TMUX' in os.environ and which('tmux'):
terminal = 'tmux'
args = ['splitw']
elif 'STY' in os.environ and which('screen'):
terminal = 'screen'
args = ['-t','pwntools-gdb','bash','-c']
if not terminal:
log.error('Could not find a terminal binary to use. Set context.terminal to your terminal.')
+7 -7
View File
@@ -58,7 +58,7 @@ def pack(number, word_size = None, endianness = None, sign = None, **kwargs):
Arguments:
number (int): Number to convert
word_size (int): Word size of the converted integer or the string 'all'.
word_size (int): Word size of the converted integer or the string 'all' (in bits).
endianness (str): Endianness of the converted integer ("little"/"big")
sign (str): Signedness of the converted integer (False/True)
kwargs: Anything that can be passed to context.local
@@ -171,7 +171,7 @@ def unpack(data, word_size = None):
Arguments:
number (int): String to convert
word_size (int): Word size of the converted integer or the string "all".
word_size (int): Word size of the converted integer or the string "all" (in bits).
endianness (str): Endianness of the converted integer ("little"/"big")
sign (str): Signedness of the converted integer (False/True)
kwargs: Anything that can be passed to context.local
@@ -237,7 +237,7 @@ def unpack_many(data, word_size = None):
Args
number (int): String to convert
word_size (int): Word size of the converted integers or the string "all".
word_size (int): Word size of the converted integers or the string "all" (in bits).
endianness (str): Endianness of the converted integer ("little"/"big")
sign (str): Signedness of the converted integer (False/True)
kwargs: Anything that can be passed to context.local
@@ -366,7 +366,7 @@ def make_packer(word_size = None, sign = None, **kwargs):
faster to call this function, since it will then use a specialized version.
Arguments:
word_size (int): The word size to be baked into the returned packer or the string all.
word_size (int): The word size to be baked into the returned packer or the string all (in bits).
endianness (str): The endianness to be baked into the returned packer. ("little"/"big")
sign (str): The signness to be baked into the returned packer. ("unsigned"/"signed")
kwargs: Additional context flags, for setting by alias (e.g. ``endian=`` rather than index)
@@ -429,7 +429,7 @@ def make_unpacker(word_size = None, endianness = None, sign = None, **kwargs):
faster to call this function, since it will then use a specialized version.
Arguments:
word_size (int): The word size to be baked into the returned packer.
word_size (int): The word size to be baked into the returned packer (in bits).
endianness (str): The endianness to be baked into the returned packer. ("little"/"big")
sign (str): The signness to be baked into the returned packer. ("unsigned"/"signed")
kwargs: Additional context flags, for setting by alias (e.g. ``endian=`` rather than index)
@@ -525,7 +525,7 @@ def flat(*args, **kwargs):
preprocessor (function): Gets called on every element to optionally
transform the element before flattening. If :const:`None` is
returned, then the original value is uded.
word_size (int): Word size of the converted integer.
word_size (int): Word size of the converted integer (in bits).
endianness (str): Endianness of the converted integer ("little"/"big").
sign (str): Signedness of the converted integer (False/True)
@@ -577,7 +577,7 @@ def fit(pieces=None, **kwargs):
preprocessor (function): Gets called on every element to optionally
transform the element before flattening. If :const:`None` is
returned, then the original value is used.
word_size (int): Word size of the converted integer.
word_size (int): Word size of the converted integer (in bits).
endianness (str): Endianness of the converted integer ("little"/"big").
sign (str): Signedness of the converted integer (False/True)
+2
View File
@@ -245,6 +245,8 @@ def status(pid):
try:
with open('/proc/%d/status' % pid) as fd:
for line in fd:
if ':' not in line:
continue
i = line.index(':')
key = line[:i]
val = line[i + 2:-1] # initial :\t and trailing \n
+1 -1
View File
@@ -393,7 +393,7 @@ def sh_string(s):
return "''"
chars = set(s)
very_good = set(string.ascii_letters + string.digits + "_+.,/")
very_good = set(string.ascii_letters + string.digits + "_+.,/-")
# Alphanumeric can always just be used verbatim.
if chars <= very_good:
+1 -1
View File
@@ -1 +1 @@
__version__ = '3.9.2'
__version__ = '3.12.2'
+4 -3
View File
@@ -45,7 +45,7 @@ for filename in glob.glob('pwnlib/commandline/*'):
install_requires = ['paramiko>=1.15.2',
'mako>=1.0.0',
'pyelftools>=0.2.4',
'capstone',
'capstone>=3.0.5rc2', # See Gallopsled/pwntools#971, Gallopsled/pwntools#1160
'ropgadget>=5.3',
'pyserial>=2.7',
'requests>=2.0',
@@ -57,7 +57,8 @@ install_requires = ['paramiko>=1.15.2',
'pypandoc',
'packaging',
'psutil>=3.3.0',
'intervaltree',
'intervaltree<3.0', # See Gallopsled/pwntools#1238
'sortedcontainers<2.0', # See Gallopsled/pwntools#1154
'unicorn']
# Check that the user has installed the Python development headers
@@ -86,7 +87,7 @@ except Exception as e:
setup(
name = 'pwntools',
packages = find_packages(),
version = '3.9.2',
version = '3.12.2',
data_files = [('',
glob.glob('*.md') + glob.glob('*.txt')),
],
+4 -4
View File
@@ -11,7 +11,7 @@ local_deb_extract()
install_deb()
{
version=${2:-zesty}
version=${2:-artful}
package=$1
echo "Installing $package"
INDEX="http://packages.ubuntu.com/en/$version/amd64/$package/download"
@@ -90,7 +90,7 @@ setup_android_emulator()
elif [[ -n "$TRAVIS_TAG" ]]; then
echo "TRAVIS_TAG ($TRAVIS_TAG) indicates a new relase"
echo "Forcing Android Emulator installation"
elif (git log --stat "$TRAVIS_COMMIT_RANGE" | grep -iE "android|adb"); then
elif (git log --stat "$TRAVIS_COMMIT_RANGE" | grep -iE "android|adb" | grep -v "commit "); then
echo "Found Android-related commits, forcing Android Emulator installation"
else
# In order to avoid running the doctests that require the Android
@@ -119,7 +119,7 @@ setup_android_emulator()
else
if [ ! -f android-sdk/android ]; then
# Install the SDK, which gives us the 'android' and 'emulator' commands
wget https://dl.google.com/android/android-sdk_r24.4.1-linux.tgz
wget -nv https://dl.google.com/android/android-sdk_r24.4.1-linux.tgz
tar xf android-sdk_r24.4.1-linux.tgz
rm -f android-sdk_r24.4.1-linux.tgz
@@ -137,7 +137,7 @@ setup_android_emulator()
# Install the NDK, which is required for adb.compile()
NDK_VERSION=android-ndk-r12b
if [ ! -f android-ndk/ndk-build ]; then
wget https://dl.google.com/android/repository/$NDK_VERSION-linux-x86_64.zip
wget -nv https://dl.google.com/android/repository/$NDK_VERSION-linux-x86_64.zip
unzip -q android-ndk-*.zip
rm -f android-ndk-*.zip