Compare commits

...
71 Commits
Author SHA1 Message Date
Arusekk c45e92d78b Release 4.9.0 2022-12-29 14:08:51 +01:00
Arusekk a76c7b0ca1 Defer import of optional dependency 2022-12-28 22:29:50 +01:00
Arusekk b8fad78d3e Fix binutils version checking and RWX warnings
Fixes #2140
Closes #2098
Closes #2141
2022-12-28 22:19:35 +01:00
Arusekk 8f42bef2b1 Use modern inspect.signature
While at it, drop the hard-to-maintain code.
We lose the signature in py2, but its support is second-class anyway.

Fixes #2145
Closes #2148
2022-12-28 22:13:55 +01:00
Arusekk 020143f0f9 Compatibility with python 3.11 (#2070)
Closes #2057
Closes #2052
2022-12-28 21:46:26 +01:00
Arusekk 416c33ed00 New toolchain, new doctests 2022-12-28 21:46:26 +01:00
Arusekk 3884d606b7 ci: Use default elfutils from 22.04 2022-12-28 21:19:42 +01:00
Arusekk 4628627943 Bump setup-python GH action 2022-12-28 20:15:06 +01:00
Arusekk d86ae5794a Merge branch 'stable' into beta 2022-12-28 19:43:01 +01:00
peace-maker 67d49a693d Fix tube.recvpred() timout argument (#2124)
* Fix tube.recvpred() timout argument

The `timeout` argument wasn't implemented correctly causing the function to hang instead of timing out.

* Update CHANGELOG.md
2022-11-01 19:21:37 +01:00
peace-maker a7cb54931c Fix ROP without a writeable cache directory (#2123)
* Fix ROP without a writeable cache directory

`context.cache_dir` can be None, but the ROP class assumed it would always be a valid path and crash. Disable rop-cache if we can't save it.

Fixes #2072

* Update CHANGELOG.md
2022-10-27 14:33:54 +02:00
Arusekk e450f6c247 Release 4.9.0beta0 2022-04-21 01:14:48 +02:00
Arusekk 493a3e3d92 Release 4.8.0 2022-04-21 01:05:15 +02:00
Arusekk 3e4d4a2115 Allow non-numeric ports with remote() 2022-04-21 00:51:54 +02:00
Arusekk b3b6d52bdd Clean up usages of getattr/__getattr__ (#2028) 2022-04-21 00:38:24 +02:00
sourqueandLMS57 6ba46d4eec Add the 'raw' option to SSH shell initialization (#2037)
* Add the 'raw' option to SSH shell initialization

This permits the use of the SSH shell as an arbitrary TCP tunnel by
bypassing the startup environmental checks.

Co-authored-by: LMS57 <[email protected]>

* Add changelog items

* Add assignment to self variable

Co-authored-by: LMS57 <[email protected]>
2022-04-21 00:37:25 +02:00
PaulandArusekk 5af2d92100 Quote file and core path in generated GDB script (#2033)
* Quote file and core path in generated GDB script

* Update changelog

Co-authored-by: Arusekk <[email protected]>
2022-04-21 00:36:03 +02:00
3fadc67327 clean ioerror (#2039)
* clean ioerror

* enforce stricter checks on broken pipe

* Apply suggestions from code review

Co-authored-by: caprinux <[email protected]>
Co-authored-by: Arusekk <[email protected]>
2022-04-21 00:32:40 +02:00
peace-maker 91c1b20c7b Detect Windows Terminal and split gdb into new pane (#2056)
Split the terminal window similar to tmux debugging experience when we're running in Windows Terminal instead of the plain old console host.
2022-04-20 23:46:42 +02:00
Justin StevenandArusekk 0351219903 Add js_escape and js_unescape (#1979)
* Add js_escape and js_unescape

* Add entry for js packing functions

* Use hardcoded data for doctest

* Fix js_escape data iteration for Python 2

* Move js packing functions to util.fiddling

* Apply suggestions from code review

Co-authored-by: Arusekk <[email protected]>
2022-04-20 17:49:00 +02:00
peace-makerandArusekk 4b067d2ec5 Add libcdb commandline tool (#1975)
* Add libcdb commandline tool

Allows to dump information about a local libc binary or lookup matching libc versions based on a build hash or symbol offsets (querying https://libc.rip).

It's possible to download and unstrip a matching libc binary from the command line.

* Fix Python 2 compatibility

* Remove main_arena heuristic

* Update CHANGELOG

Co-authored-by: Arusekk <[email protected]>
2022-04-20 16:35:36 +02:00
Heap Crash d9f9e7ca6f Add documentation on the rop.register=foo assignment option (#1924)
* Add documentation on the rop.register=foo assignment option

* Add looking up ROP gadgets by name as well, and manual ROP construction
2022-04-20 16:34:30 +02:00
Emanuel HauptandArusekk 9e55d8f4c8 Add badge packaging status badge (#2054)
* Add badge packaging status badge

Add a repology badge to show packaging status.

* Update README.md

Use shields.io everywhere

Co-authored-by: Arusekk <[email protected]>
2022-04-20 15:56:37 +02:00
Arusekk 97a760a0dd Merge branch 'beta' into dev 2022-04-20 15:47:08 +02:00
Arusekk f491bc6a22 Merge branch 'stable' into beta 2022-04-20 15:46:34 +02:00
Arusekk cde1b89615 Release 4.7.1 2022-04-20 15:37:20 +02:00
zup 17fc0aeaad fix double decoding of bytestring when logging (#2051) 2022-04-20 13:20:40 +00:00
maybe-sybr c93bdfeed8 fix: Fallback to temp dir if cache does not exist (#1784)
This fixes a misbehaviour in environments where `XDG_CACHE_DIR` is not
set and `~/.cache` does not exist, e.g. in the docker image. We simply
attempt to create a temporary directory which will be registered with
`atexit` to be deleted on exit using `shutil.rmtree()`. A Python 3
approach for future would be to use a `TemporaryDirectory` instead which
would get cleaned up when the held object gets dropped out of the TLS
stack.

It is still possible for the `context.cache_dir` to be `None` if the
default directory exists but is not writable, which means some code
which uses this property is liable to blow up in such a circumstance.
2022-04-20 12:09:41 +00:00
TestzeroandT3stzer0 4fd08b0b00 fix packing error (#2040)
Co-authored-by: T3stzer0 <[email protected]>
2022-04-20 12:00:35 +00:00
Spencer Pogorzelski 8dd566b9e9 Fix env command not found in gdb.debug (#2007) 2022-04-20 11:47:57 +00:00
Sebastiano MarianiandArusekk fca2929de0 Fix download_dir method when using an ssh tube (#1961)
Signed-off-by: Sebastiano Mariani <[email protected]>
Co-authored-by: Arusekk <[email protected]>
2022-04-20 10:51:21 +00:00
lucasg d67bdbc550 [asm] pwn.disam can return <text+0x0> instead of <text> (#1912)
Unit test:

```
(pwntools_mips_bug) lucasg@home:/tmp/pwntools$ git show
commit 78c6b8c2a9 (HEAD -> dev, origin/dev, origin/HEAD)
Merge: e3a5ac58 d7139997
Author: heapcrash <[email protected]>
Date:   Mon May 31 16:06:34 2021 -0500

    Merge branch 'beta' into dev

(pwntools_mips_bug) lucasg@home:/tmp/pwntools$ python
Python 3.7.3 (default, Jan 22 2021, 20:04:44)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pwn, binascii
>>> print(pwn.disasm(binascii.unhexlify("000000000d0000000000000000"), vma=0x00000000, arch="mips"))
   0:   00000000        nop
   4:   0000000d        break
   8:   00000000        nop
        ...
>>> print(pwn.disasm(binascii.unhexlify("000000000d0000000000000000"), vma=0x80000000, arch="mips"))
[ERROR] Could not find .text in objdump output:

    /tmp/pwn-disasm-77tu42pr/step2:     file format elf32-tradlittlemips

    Disassembly of section .text:

    80000000 <.text+0x0>:
    80000000:   00000000        nop
    80000004:   0000000d        break
    80000008:   00000000        nop
        ...
[ERROR] An error occurred while disassembling:
    b'\x00\x00\x00\x00\r\x00\x00\x00\x00\x00\x00\x00\x00'
    Traceback (most recent call last):
      File "/tmp/pwntools/pwnlib/asm.py", line 814, in disasm
        log.error('Could not find .text in objdump output:\n%s' % output0)
      File "/tmp/pwntools/pwnlib/log.py", line 424, in error
        raise PwnlibException(message % args)
    pwnlib.exception.PwnlibException: Could not find .text in objdump output:

    /tmp/pwn-disasm-77tu42pr/step2:     file format elf32-tradlittlemips

    Disassembly of section .text:

    80000000 <.text+0x0>:
    80000000:   00000000        nop
    80000004:   0000000d        break
    80000008:   00000000        nop
        ...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/tmp/pwntools/pwnlib/context/__init__.py", line 1524, in setter
    return function(*a, **kw)
  File "/tmp/pwntools/pwnlib/asm.py", line 818, in disasm
    log.exception("An error occurred while disassembling:\n%s" % data)
  File "/tmp/pwntools/pwnlib/asm.py", line 814, in disasm
    log.error('Could not find .text in objdump output:\n%s' % output0)
  File "/tmp/pwntools/pwnlib/log.py", line 424, in error
    raise PwnlibException(message % args)
pwnlib.exception.PwnlibException: Could not find .text in objdump output:

/tmp/pwn-disasm-77tu42pr/step2:     file format elf32-tradlittlemips

Disassembly of section .text:

80000000 <.text+0x0>:
80000000:       00000000        nop
80000004:       0000000d        break
80000008:       00000000        nop
        ...
```
2022-04-20 10:25:58 +00:00
Alan YeeandArusekk c3894c24de Make ELF symbol dict accept bytes (#1973)
DynElf symbol lookup uses bytes as key

Fixes #1951

Co-authored-by: Arusekk <[email protected]>
2022-04-20 09:48:13 +00:00
Arusekk 208b11df69 Avoid name clash of plain and android-related test 2022-04-20 11:14:45 +02:00
Will 0a860bd964 Fix incorrect disasm output (#2031)
Use objdump --wide option to ensure we don't have new lines in large instructions (>7 bytes).
2022-04-20 08:58:08 +00:00
0x3c3e 15efc3f1dc Cast int target pid to str (#2012) 2022-04-20 08:24:06 +00:00
gogo2464 2d0f03b66c Update Sphinx in order to fix jinja2 deprecated dependency. (#2053) 2022-04-20 08:44:02 +02:00
Justin Steven d7e9d313a2 Fix hexdump docstring. s should by bytes. (#1982) 2022-04-20 00:27:25 +02:00
Roderick 7a246e7887 fix Gallopsled/pwntools#2044 (#2045)
Specify the distro name when pop up a debugging window in wsl
2022-04-20 00:16:10 +02:00
QuqqU c04dbdb9e9 Correct a return type in Docstring (#2055) 2022-04-19 23:25:45 +02:00
Arusekk fc372d17a2 rop: Improve density (#2048)
When the last gadget has no arguments and the gadget before has stack
arguments, put the last gadget directly in its return address slot
(instead of a stack adjustment gadget).
2022-04-19 23:25:23 +02:00
gogo2464 79986d8262 Update Sphinx in order to fix jinja2 deprecated dependency. (#2053) 2022-04-13 17:49:01 +02:00
tkmikan ef698d4562 change Buffer's parent class to object (#2035)
* change Buffer's parent class to object

* update CHANGELOG.md
2022-02-15 17:31:01 +01:00
Swapnadeep Som 1238fc359e Fix UPX packer identifier (#1947)
Fixes #1855
Closes #1947
2022-02-01 11:53:12 +01:00
Arusekk 79d150061c Merge branch 'beta' into dev 2022-02-01 11:09:35 +01:00
peace-maker 2198b74df9 Fix CI after Groovy Gorilla went away for libc unstrip test (#2025)
* Fix CI after Groovy Gorilla went away for libc unstrip test

Build elfutils 0.181 from source since we can't use builds
from a newer ubuntu version anymore.

* Install python wheels in CI
2022-02-01 11:07:39 +01:00
TomasGl bf37947a99 Use a more reliable way to split the KDE konsole (#2026)
* Use a more reliable way to split the KDE konsole

* Changed xml parsing to pure parsing

* Update CHANGELOG.md
2022-02-01 11:01:31 +01:00
peace-maker 1fe9b3e693 Fix ELF.libc_start_main_return with glibc 2.34 (#2027)
* Fix ELF.libc_start_main_return with glibc 2.34

The actual call to `main` was split out into a new `__libc_start_call_main` function in glibc 2.34.

Update the heuristic to find main's return address by looking into all direct calls in `__libc_start_main` until the old heuristic matches.

The other direct calls could be e.g. `atexit` which doesn't call `exit`.

* Update CHANGELOG.md
2022-01-31 18:41:42 +01:00
peace-maker 762591bf35 Fix CI after Groovy Gorilla went away for libc unstrip test (#2025)
* Fix CI after Groovy Gorilla went away for libc unstrip test

Build elfutils 0.181 from source since we can't use builds
from a newer ubuntu version anymore.

* Install python wheels in CI
2022-01-28 11:58:23 +01:00
TomasGl 04f401c70e Support KDE Konsole in run_in_new_terminal function (#2023)
* Support KDE Konsole in run_in_new_terminal function

* Rewrite '.stdout.read()' to '.communicate()[0]'
2022-01-26 00:07:20 +01:00
jammychiou1 84b51fc1a5 Update filepointer.py (#2022)
Fix typo in documentation: "defination" to "definition"
2022-01-23 22:37:58 +01:00
tkmikan 1573957cc8 fix tube's debug output of same byte compression (#2011)
* fix tube's debug output of same byte compression

* Update CHANGELOG.md

* move code to `log.maybe_hexdump`

* update doc string
2022-01-08 13:22:38 +01:00
Arusekk 038e50fa0c Replace reserved argument names in syscalls (#1998)
Just stick to the replaced names everywhere.
This actually only broke linkat(2) and symlinkat(2),
but it touches many len -> length renames as well
for the sake of consistency.
Fixes #1964
2022-01-04 11:05:08 +01:00
Disconnect3d 58d6aea217 Add arch alias: x86-64 -> amd64 (#1967)
While we have an alias for "x86_64" some software (like Pwndbg) uses the "x86-64" string.
2021-11-21 23:54:24 +01:00
Arusekk 4c78e188ce Begin working on 4.9.0 2021-11-15 12:49:32 +01:00
Arusekk 9d8060496c Release 4.8.0beta0 2021-11-15 12:44:33 +01:00
Arusekk 7ea1f1d492 Merge branch 'beta' into dev 2021-11-15 12:25:01 +01:00
Arusekk da8bf2667d Fix CI failures (#1997)
Ubuntu, why are you doing this to us?
I seriously mean that Ubuntu is the most silly distro.

Like, they probably updated their docker (or VM) images last week,
then Github got it, and we started getting failures.
They totally changed the behaviour of that 'apport' thing,
and it now drops core files in /var/lib/apport/coredump
instead of the current working directory.

Why is that even... ugh.  Nevermind.  It works now, at least.
But for how long?
2021-11-15 12:14:18 +01:00
Arusekk a37bce2bdc Display nice error message on unavailable ret2csu (#1991)
Fixes #1941
2021-11-14 15:20:59 +01:00
Arusekk 3dd0b24cc3 Add cat2 to shellcraft (#1995)
* Introduce shellcraft.linux.cat2 to avoid sendfile

Fixes #1871

* Switch CAT_PROC_MAPS_EXIT to shellcraft.linux.cat2
2021-11-14 15:20:43 +01:00
Arusekk a8c8c28f00 Fix CI failures (#1994)
* Try to fix build failures by updating sphinx

See sphinx-doc/sphinx#9811

* Does not work with py2, so pin old docutils
2021-11-11 17:53:45 +01:00
Justin Steven 315a462603 Make cyclic_find work with large int values (#1981)
* Make cyclic_find work with large int values

* Add entry for cyclic_find() large int fix

* Tweak cyclic_find() large int warning
2021-11-11 14:59:05 +01:00
ElJeffeandArusekk ddeddd455e Fix: TypeError: can't concat str to bytes (#1953)
* Fix: TypeError: can't concat str to bytes

sh_string returns a string, but two lines try to concat it with bytes, which throws a TypeError.  Either convert them all to bytes or all to string.

* Update to use packing._need_bytes

I just followed the format from surrounding conversions.  Hopefully that works correctly.

* Update pwnlib/tubes/ssh.py

Co-authored-by: Arusekk <[email protected]>

* Conversion to bytes

* Going for higher level conversion

Co-authored-by: Arusekk <[email protected]>
2021-11-01 00:35:10 +01:00
Alan Yee d3140ed983 Using native features of built-in functions over built-in operations config.py (#1980)
Use lazy logging and use second argument of `.get()` instead of relying on `or`
2021-11-01 00:33:32 +01:00
Arusekk fc250a33a1 Add missing copyright notice to ARM alphanumeric (#1955)
Closes #1954
2021-10-07 09:33:34 +02:00
peace-makerandArusekk 92911cbabd libcdb: Load debug info and unstrip libc binary (#1828)
* libcdb: Load debug info and unstrip libc binary

The debug info is obtained from a debuginfod server maintained by systemtap.org. elfutils is used to unstrip the binary and merge the debug info back.

Try to unstrip every libc binary downloaded by the libcdb module by default. #1818

* Install elfutils on CI

* Update CHANGELOG.md

* Add more tests

* Fix negative test

* Raise an exception when eu-unstrip fails

This will hopefully get logged during doctests.

* Install elfutils 0.181 from groovy

Use APT Pinning to select the newer elfutils version from groovy instead of the buggy 0.176 version available in focal.

* Fix missing import of requests

pylint?

Co-authored-by: Arusekk <[email protected]>
2021-10-03 10:51:30 +02:00
Alan Yee a9715d8c74 Use lazy logging instead of native string interpolation (#1972)
https://docs.python.org/3/howto/logging.html#logging-variable-data
2021-10-01 07:48:57 +02:00
Darin Mao bd12d1874f Fix KB, MB, GB globals (#1926) 2021-07-29 19:17:17 +02:00
clubby789 11b8537d17 Fix validation of log level name for Python 3 (#1939)
* Fix validation of log level name for Python 3

* Add changelog

* Reorder changelog and use try/except
2021-07-29 19:16:36 +02:00
Connor NelsonandArusekk f7283bf2eb Fix logic in wait_for_debugger (#1922)
* Fix logic in `wait_for_debugger`

* Update CHANGELOG.md

* Fix CHANGELOG.md

* Fix CHANGELOG.md

Co-authored-by: Arusekk <[email protected]>
2021-07-15 11:42:42 +02:00
Arusekk d5d7b8385c Begin working on 4.8.0 2021-07-12 23:06:17 +02:00
86 changed files with 1185 additions and 366 deletions

No files matched your search

+1 -1
View File
@@ -3,7 +3,7 @@ name: Android Tests
on: [push, pull_request]
jobs:
test:
android-test:
strategy:
matrix:
python-version: [3.8]
+10 -4
View File
@@ -27,7 +27,7 @@ jobs:
key: ${{ matrix.os }}-cache-pip
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
@@ -59,8 +59,9 @@ jobs:
binutils-s390x-linux-gnu \
binutils-sparc64-linux-gnu \
gcc-multilib \
libc6-dbg
libc6-dbg \
elfutils
- name: Install RPyC for GDB
run: |
sudo apt-get install -y python3-pip
@@ -81,6 +82,7 @@ jobs:
- name: Install dependencies
run: |
pip install --upgrade pip
pip install --upgrade wheel
pip install --upgrade flake8 appdirs
python setup.py egg_info
pip install --upgrade --editable .
@@ -89,7 +91,7 @@ jobs:
run: PWNLIB_NOTERM=1 python -bb -c 'from pwn import *; print(pwnlib.term.term_mode)'
- name: Install documentation dependencies
run: pip install -r docs/requirements.txt
run: pip install -r docs/requirements.txt
- name: Manually install non-broken Unicorn
run: pip install unicorn==1.0.2rc3
@@ -156,6 +158,10 @@ jobs:
pwn constgrep ^MAP_ 0
pwn constgrep -e O_RDWR
pwn libcdb file /lib/x86_64-linux-gnu/libc.so.6
pwn libcdb lookup puts 5f0 __libc_start_main_ret d0a
pwn libcdb hash b229d1da1e161f95e839cf90cded5f719e5de308
- name: Build source and wheel distributions
run: |
python setup.py sdist
+1
View File
@@ -25,6 +25,7 @@ addons:
- binutils-sparc64-linux-gnu
- bash-static
- python3-pip
- elfutils
cache:
- pip
- directories:
+64 -6
View File
@@ -9,9 +9,12 @@ The table below shows which release corresponds to each branch, and what date th
| Version | Branch | Release Date |
| ---------------- | -------- | ---------------------- |
| [4.9.0](#490) | `dev` |
| [4.8.0](#480) | `beta` |
| [4.7.0](#470) | `stable` | Nov 15, 2021
| [4.11.0](#4110) | `dev` |
| [4.10.0](#4100) | `beta` |
| [4.9.0](#490) | `stable` | Dec 29, 2022
| [4.8.0](#480) | | Apr 21, 2022
| [4.7.1](#471) | | Apr 20, 2022
| [4.7.0](#470) | | Nov 15, 2021
| [4.6.0](#460) | | Jul 12, 2021
| [4.5.1](#451) | | May 30, 2021
| [4.5.0](#450) | | Apr 30, 2021
@@ -62,11 +65,49 @@ The table below shows which release corresponds to each branch, and what date th
| [3.0.0](#300) | | Aug 20, 2016
| [2.2.0](#220) | | Jan 5, 2015
## 4.9.0 (`dev`)
## 4.11.0 (`dev`)
## 4.8.0 (`beta`)
## 4.10.0 (`beta`)
- [#2062][2062] make pwn cyclic -l work with entry larger than 4 bytes
- [#2092][2092] shellcraft: dup() is now called dupio() consistently across all supported arches
- [#2093][2093] setresuid() in shellcraft uses current euid by default
- [#2125][2125] Allow tube.recvregex to return capture groups
- [#2144][2144] Removes `p2align 2` `asm()` headers from `x86-32`, `x86-64` and `mips` architectures to avoid inconsistent instruction length when patching binaries
[2062]: https://github.com/Gallopsled/pwntools/pull/2062
[2092]: https://github.com/Gallopsled/pwntools/pull/2092
[2093]: https://github.com/Gallopsled/pwntools/pull/2093
[2125]: https://github.com/Gallopsled/pwntools/pull/2125
[2144]: https://github.com/Gallopsled/pwntools/pull/2144
## 4.9.0 (`stable`)
- [#1975][1975] Add libcdb commandline tool
- [#1979][1979] Add `js_escape()` and `js_unescape()` to `util.fiddling`
- [#2011][2011] Fix tube's debug output of same byte compression
- [#2023][2023] Support KDE Konsole in run_in_new_terminal function
- [#2027][2027] Fix ELF.libc_start_main_return with glibc 2.34
- [#2033][2033] Quote file and core path in generated GDB script
- [#2035][2035] Change Buffer's parent class to object
- [#2037][2037] Allow SSH tunnel to be treated like a TCP socket (with 'raw=True')
- [#2123][2123] Fix ROP without a writeable cache directory
- [#2124][2124] Fix `tube.recvpred()` timeout argument
[1975]: https://github.com/Gallopsled/pwntools/pull/1975
[1979]: https://github.com/Gallopsled/pwntools/pull/1979
[2011]: https://github.com/Gallopsled/pwntools/pull/2011
[2023]: https://github.com/Gallopsled/pwntools/pull/2023
[2027]: https://github.com/Gallopsled/pwntools/pull/2027
[2033]: https://github.com/Gallopsled/pwntools/pull/2033
[2035]: https://github.com/Gallopsled/pwntools/pull/2035
[2037]: https://github.com/Gallopsled/pwntools/pull/2037
[2123]: https://github.com/Gallopsled/pwntools/pull/2123
[2124]: https://github.com/Gallopsled/pwntools/pull/2124
## 4.8.0
- [#1922][1922] Fix logic in `wait_for_debugger`
- [#1828][1828] libcdb: Load debug info and unstrip libc binary
@@ -78,7 +119,24 @@ The table below shows which release corresponds to each branch, and what date th
[1939]: https://github.com/Gallopsled/pwntools/pull/1939
[1981]: https://github.com/Gallopsled/pwntools/pull/1981
## 4.7.0 (`stable`)
## 4.7.1
- [#1784][1784] Use temporary cache directory when persistent cache cannot be used
- [#1973][1973] ELF symbols can be looked up by bytes values
- several bugfixes ([#2012][2012], [#2031][2031], [#1912][1912], [#1961][1961],
[#2007][2007], [#2040][2040], [#2051][2051])
[1784]: https://github.com/Gallopsled/pwntools/pull/1784
[1912]: https://github.com/Gallopsled/pwntools/pull/1912
[1961]: https://github.com/Gallopsled/pwntools/pull/1961
[1973]: https://github.com/Gallopsled/pwntools/pull/1973
[2007]: https://github.com/Gallopsled/pwntools/pull/2007
[2012]: https://github.com/Gallopsled/pwntools/pull/2012
[2031]: https://github.com/Gallopsled/pwntools/pull/2031
[2040]: https://github.com/Gallopsled/pwntools/pull/2040
[2051]: https://github.com/Gallopsled/pwntools/pull/2051
## 4.7.0
- [#1733][1733] Update libc headers -> more syscalls available!
- [#1876][1876] add `self.message` and change `sys.exc_type` to `sys.exec_info()` in PwnlibException
+1
View File
@@ -7,6 +7,7 @@
[![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/Gallopsled/pwntools/Continuous%20Integration/dev?logo=GitHub)](https://github.com/Gallopsled/pwntools/actions?query=workflow%3A%22Continuous+Integration%22+branch%3Adev)
[![Coveralls](https://img.shields.io/coveralls/github/Gallopsled/pwntools/dev?logo=coveralls)](https://coveralls.io/github/Gallopsled/pwntools?branch=dev)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://choosealicense.com/licenses/mit/)
[![Packaging status](https://img.shields.io/repology/repositories/python:pwntools)](https://repology.org/project/python:pwntools/versions)
[![Discord](https://img.shields.io/discord/809590285687980052?label=Discord&style=plastic)](https://discord.gg/96VA2zvjCB)
[![Twitter](https://img.shields.io/twitter/follow/Pwntools)](https://twitter.com/pwntools)
+2 -2
View File
@@ -12,7 +12,7 @@ pypandoc
pyserial>=2.7
requests>=2.5.1
ropgadget>=5.3
sphinx<3.4
sphinx==1.8.6; python_version<'3'
sphinx>=4.5.0; python_version>='3'
sphinx_rtd_theme
sphinxcontrib-napoleon
sphinxcontrib-autoprogram<=0.1.5
+1 -1
View File
@@ -42,8 +42,8 @@ extensions = [
'sphinx.ext.coverage',
'sphinx.ext.todo',
'sphinx.ext.intersphinx',
'sphinx.ext.napoleon',
'sphinxcontrib.autoprogram',
'sphinxcontrib.napoleon'
]
# Disable "info" logging directly to stdout by Sphinx
+1
View File
@@ -16,6 +16,7 @@ RUN apt-get update \
&& locale-gen en_US.UTF-8 \
&& apt-get install -y \
build-essential \
elfutils \
git \
libssl-dev \
libffi-dev \
+4 -1
View File
@@ -384,6 +384,9 @@ class AdbDevice(Device):
>>> adb.getprop(property) == device.getprop(property)
True
"""
if name.startswith('_'):
raise AttributeError(name)
with context.local(device=self):
g = globals()
@@ -1513,7 +1516,7 @@ class Partitions(object):
return iter(names)
def __getattr__(self, attr):
if attr.startswith("_"):
if attr.startswith('_'):
raise AttributeError(attr)
for name in self:
+4 -4
View File
@@ -46,7 +46,7 @@ class BootImage(object):
self.kernel = self.data[PAGE:PAGE+self.kernel_size]
def __getattr__(self, name):
value = getattr(self.header, name, None)
if value is not None:
return value
return getattr(super(BootImage, self), name)
if name.startswith('_'):
raise AttributeError(name)
return getattr(self.header, name)
+5 -4
View File
@@ -114,10 +114,11 @@ class BootloaderImage(object):
return '\n'.join(rv)
def __getattr__(self, name):
value = getattr(self.header, name, None)
if value is not None:
return value
return getattr(super(BootImage, self), name)
if name.startswith('_'):
raise AttributeError(name)
return getattr(self.header, name)
if __name__ == '__main__':
# Easy sanity checking
+2
View File
@@ -62,6 +62,8 @@ from pwnlib.context import context
class PwnlibArgs(collections.defaultdict):
def __getattr__(self, attr):
if attr.startswith('_'):
raise AttributeError(attr)
return self[attr]
args = PwnlibArgs(str)
+48 -28
View File
@@ -136,8 +136,23 @@ Try installing binutils for this architecture:
%(instructions)s
""".strip() % locals())
def check_binutils_version(util):
if util_versions[util]:
return util_versions[util]
result = subprocess.check_output([util, '--version','/dev/null'],
stderr=subprocess.STDOUT, universal_newlines=True)
if 'clang' in result:
log.warn_once('Your binutils is clang-based and may not work!\n'
'Try installing with: https://docs.pwntools.com/en/stable/install/binutils.html\n'
'Reported version: %r', result.strip())
version = re.search(r' (\d+\.\d+)', result).group(1)
util_versions[util] = version = tuple(map(int, version.split('.')))
return version
@LocalContext
def which_binutils(util):
def which_binutils(util, check_version=False):
"""
Finds a binutils in the PATH somewhere.
Expects that the utility is prefixed with the architecture name.
@@ -204,17 +219,23 @@ def which_binutils(util):
for pattern in patterns:
for dir in environ['PATH'].split(':'):
res = sorted(glob(path.join(dir, pattern)))
if res:
return res[0]
for res in sorted(glob(path.join(dir, pattern))):
if check_version:
ver = check_binutils_version(res)
return res, ver
return res
# No dice!
print_binutils_instructions(util, context)
checked_assembler_version = defaultdict(lambda: False)
util_versions = defaultdict(tuple)
def _assembler():
gas = which_binutils('as')
gas, version = which_binutils('as', check_version=True)
if version < (2, 19):
log.warn_once('Your binutils version is too old and may not work!\n'
'Try updating with: https://docs.pwntools.com/en/stable/install/binutils.html\n'
'Reported version: %r', version)
E = {
'big': '-EB',
@@ -246,25 +267,10 @@ def _assembler():
assembler = assemblers.get(context.arch, [gas])
if not checked_assembler_version[gas]:
checked_assembler_version[gas] = True
result = subprocess.check_output([gas, '--version','/dev/null'],
stderr=subprocess.STDOUT, universal_newlines=True)
version = re.search(r' (\d+\.\d+)', result).group(1)
if 'clang' in result:
log.warn_once('Your binutils is clang version and may not work!\n'
'Try install with: https://docs.pwntools.com/en/stable/install/binutils.html\n'
'Reported Version: %r', result.strip())
elif version < '2.19':
log.warn_once('Your binutils version is too old and may not work!\n'
'Try updating with: https://docs.pwntools.com/en/stable/install/binutils.html\n'
'Reported Version: %r', result.strip())
return assembler
def _linker():
ld = [which_binutils('ld')]
ld, _ = which_binutils('ld', check_version=True)
bfd = ['--oformat=' + _bfdname()]
E = {
@@ -276,7 +282,16 @@ def _linker():
'i386': ['-m', 'elf_i386'],
}.get(context.arch, [])
return ld + bfd + [E] + arguments
return [ld] + bfd + [E] + arguments
def _execstack(linker):
ldflags = ['-z', 'execstack']
version = util_versions[linker[0]]
if version >= (2, 39):
return ldflags + ['--no-warn-execstack', '--no-warn-rwx-segments']
return ldflags
def _objcopy():
return [which_binutils('objcopy')]
@@ -595,7 +610,7 @@ def make_elf(data,
_run(assembler + ['-o', step2, step1])
linker_options = ['-z', 'execstack']
linker_options = _execstack(linker)
if vma is not None:
linker_options += ['--section-start=.shellcode=%#x' % vma,
'--entry=%#x' % vma]
@@ -689,7 +704,7 @@ def asm(shellcode, vma = 0, extract = True, shared = False):
shutil.copy(step2, step3)
if vma or not extract:
ldflags = ['-z', 'execstack', '-o', step3, step2]
ldflags = _execstack(linker) + ['-o', step3, step2]
if vma:
ldflags += ['--section-start=.shellcode=%#x' % vma,
'--entry=%#x' % vma]
@@ -771,7 +786,7 @@ def disasm(data, vma = 0, byte = True, offset = True, instructions = True):
>>> print(disasm(unhex('4ff00500'), arch = 'thumb', bits=32))
0: f04f 0005 mov.w r0, #5
>>> print(disasm(unhex('656664676665400F18A4000000000051'), byte=0, arch='amd64'))
0: gs data16 fs data16 rex nop/reserved BYTE PTR gs:[eax+eax*1+0x0]
0: gs data16 fs rex nop WORD PTR gs:[eax+eax*1+0x0]
f: push rcx
>>> print(disasm(unhex('01000000'), arch='sparc64'))
0: 01 00 00 00 nop
@@ -779,6 +794,11 @@ def disasm(data, vma = 0, byte = True, offset = True, instructions = True):
0: 60 00 00 00 nop
>>> print(disasm(unhex('00000000'), arch='mips64'))
0: 00000000 nop
>>> print(disasm(unhex('48b84141414141414100c3'), arch='amd64'))
0: 48 b8 41 41 41 41 41 41 41 00 movabs rax, 0x41414141414141
a: c3 ret
>>> print(disasm(unhex('00000000'), vma=0x80000000, arch='mips'))
80000000: 00000000 nop
"""
result = ''
@@ -790,7 +810,7 @@ def disasm(data, vma = 0, byte = True, offset = True, instructions = True):
bfdarch = _bfdarch()
bfdname = _bfdname()
objdump = _objdump() + ['-d', '--adjust-vma', str(vma), '-b', bfdname]
objdump = _objdump() + ['-w', '-d', '--adjust-vma', str(vma), '-b', bfdname]
objcopy = _objcopy() + [
'-I', 'binary',
'-O', bfdname,
@@ -815,7 +835,7 @@ def disasm(data, vma = 0, byte = True, offset = True, instructions = True):
_run(objcopy + [step1, step2])
output0 = _run(objdump + [step2])
output1 = output0.split('<.text>:\n')
output1 = re.split(r'<\.text(?:\+0x0)?>:\n', output0, flags=re.MULTILINE)
if len(output1) != 2:
log.error('Could not find .text in objdump output:\n%s' % output0)
+247
View File
@@ -0,0 +1,247 @@
#!/usr/bin/env python
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import re
import shutil
import sys
import pwnlib.args
pwnlib.args.free_form = False
from pwn import *
from pwnlib.commandline import common
parser = common.parser_commands.add_parser(
'libcdb',
help = 'Print various information about a libc binary',
description = 'Print various information about a libc binary'
)
libc_commands = parser.add_subparsers(
dest = 'libc_command'
)
lookup_parser = libc_commands.add_parser(
'lookup',
help = 'Lookup a libc version by function offsets',
description = 'Lookup a libc version by function offsets'
)
lookup_parser.add_argument(
'symbol_offset_pairs',
metavar = 'symbol_offset_pairs',
nargs = '+',
help = 'Symbol and offset pairs to lookup matching libc version. Can be any number of pairs to narrow the search. Example: "read 3e0 write 520"'
)
lookup_parser.add_argument(
'--download-libc',
action = 'store_true',
default = False,
help = 'Attempt to download the matching libc.so'
)
lookup_parser.add_argument(
'--unstrip',
action = 'store_true',
default = True,
help = 'Attempt to unstrip the libc binary with debug symbols from a debuginfod server'
)
lookup_parser.add_argument(
'--no-unstrip',
action = 'store_false',
dest = 'unstrip',
help = 'Do NOT attempt to unstrip the libc binary with debug symbols from a debuginfod server'
)
hash_parser = libc_commands.add_parser(
'hash',
help = 'Display information of a libc version given an unique hash',
description = 'Display information of a libc version given an unique hash'
)
hash_parser.add_argument(
'hash_value',
metavar = 'hash_value',
nargs = '+',
help = 'Hex encoded hash value'
)
hash_parser.add_argument(
'-t', '--hash_type',
nargs = '?',
type = str,
choices = ['id', 'buildid', 'md5', 'sha1', 'sha256'],
default = 'buildid',
help = 'The type of the provided hash value. Supported hashtypes: id, buildid, md5, sha1, sha256'
)
hash_parser.add_argument(
'--download-libc',
action = 'store_true',
default = False,
help = 'Attempt to download the matching libc.so'
)
hash_parser.add_argument(
'--unstrip',
action = 'store_true',
default = True,
help = 'Attempt to unstrip the libc binary with debug symbols from a debuginfod server'
)
hash_parser.add_argument(
'--no-unstrip',
action = 'store_false',
dest = 'unstrip',
help = 'Do NOT attempt to unstrip the libc binary with debug symbols from a debuginfod server'
)
file_parser = libc_commands.add_parser(
'file',
help = 'Dump information about a libc binary',
description = 'Dump information about a libc binary'
)
file_parser.add_argument(
'files',
metavar = 'files',
nargs = '+',
help = 'Libc binary to dump'
)
file_parser.add_argument(
'-s', '--symbols',
metavar = 'symbols',
nargs = '*',
help = 'List of symbol offsets to dump in addition to the common ones'
)
file_parser.add_argument(
'-o', '--offset',
metavar = 'offset',
type = str,
help = 'Display all offsets relative to this symbol'
)
file_parser.add_argument(
'--unstrip',
action = 'store_true',
default = False,
help = 'Attempt to unstrip the libc binary inplace with debug symbols from a debuginfod server'
)
common_symbols = ['dup2', 'printf', 'puts', 'read', 'system', 'write']
def find_libc(params):
import requests
url = "https://libc.rip/api/find"
result = requests.post(url, json=params, timeout=20)
log.debug('Request: %s', params)
log.debug('Result: %s', result.json())
if result.status_code != 200 or len(result.json()) == 0:
log.failure("Could not find libc for %s on libc.rip", params)
return []
return result.json()
def print_libc(libc):
log.info('%s', text.red(libc['id']))
log.indented('\t%-20s %s', text.green('BuildID:'), libc['buildid'])
log.indented('\t%-20s %s', text.green('MD5:'), libc['md5'])
log.indented('\t%-20s %s', text.green('SHA1:'), libc['sha1'])
log.indented('\t%-20s %s', text.green('SHA256:'), libc['sha256'])
log.indented('\t%s', text.green('Symbols:'))
for symbol in libc['symbols'].items():
log.indented('\t%25s = %s', symbol[0], symbol[1])
def handle_remote_libc(args, libc):
print_libc(libc)
if args.download_libc:
path = libcdb.search_by_build_id(libc['buildid'], args.unstrip)
if path:
if args.unstrip:
libcdb.unstrip_libc(path)
shutil.copy(path, './{}.so'.format(libc['id']))
def translate_offset(offs, args, exe):
if args.offset:
if args.offset not in exe.symbols:
log.info_once('offset symbol %s not found. ignoring.', args.offset)
return offs
return offs - exe.symbols[args.offset]
return offs
def collect_synthetic_symbols(exe):
available_symbols = ['str_bin_sh']
exe.symbols['str_bin_sh'] = next(exe.search(b'/bin/sh\x00'))
libc_start_main_return = exe.libc_start_main_return
if libc_start_main_return > 0:
exe.symbols['__libc_start_main_ret'] = libc_start_main_return
available_symbols.append('__libc_start_main_ret')
return available_symbols
def main(args):
if len(sys.argv) < 3:
parser.print_usage()
sys.exit()
if args.libc_command == 'lookup':
pairs = args.symbol_offset_pairs
if len(pairs) % 2 != 0:
log.failure('Uneven number of arguments. Please provide "symbol offset" pairs')
return
symbols = {pairs[i]:pairs[i+1] for i in range(0, len(pairs), 2)}
matched_libcs = find_libc({'symbols': symbols})
for libc in matched_libcs:
handle_remote_libc(args, libc)
elif args.libc_command == 'hash':
for hash_value in args.hash_value:
matched_libcs = find_libc({args.hash_type: hash_value})
for libc in matched_libcs:
handle_remote_libc(args, libc)
elif args.libc_command == 'file':
from hashlib import md5, sha1, sha256
for file in args.files:
if not os.path.exists(file) or not os.path.isfile(file):
log.failure('File does not exist %s', args.file)
continue
if args.unstrip:
libcdb.unstrip_libc(file)
exe = ELF(file, checksec=False)
log.info('%s', text.red(os.path.basename(file)))
libc_version = re.search(b'libc[ -](\d+\.\d+)', exe.data)
if libc_version:
log.indented('%-20s %s', text.green('Version:'), libc_version.group(1).decode())
if exe.buildid:
log.indented('%-20s %s', text.green('BuildID:'), enhex(exe.buildid))
log.indented('%-20s %s', text.green('MD5:'), md5(exe.data).hexdigest())
log.indented('%-20s %s', text.green('SHA1:'), sha1(exe.data).hexdigest())
log.indented('%-20s %s', text.green('SHA256:'), sha256(exe.data).hexdigest())
# Always dump the basic list of common symbols
log.indented('%s', text.green('Symbols:'))
synthetic_symbols = collect_synthetic_symbols(exe)
symbols = common_symbols + (args.symbols or []) + synthetic_symbols
symbols.sort()
for symbol in symbols:
if symbol not in exe.symbols:
log.indented('%25s = %s', symbol, text.red('not found'))
else:
log.indented('%25s = %#x', symbol, translate_offset(exe.symbols[symbol], args, exe))
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
+2
View File
@@ -14,6 +14,7 @@ from pwnlib.commandline import elfdiff
from pwnlib.commandline import elfpatch
from pwnlib.commandline import errno
from pwnlib.commandline import hex
from pwnlib.commandline import libcdb
from pwnlib.commandline import phd
from pwnlib.commandline import pwnstrip
from pwnlib.commandline import scramble
@@ -37,6 +38,7 @@ commands = {
'elfpatch': elfpatch.main,
'errno': errno.main,
'hex': hex.main,
'libcdb': libcdb.main,
'phd': phd.main,
'pwnstrip': pwnstrip.main,
'scramble': scramble.main,
+3 -3
View File
@@ -55,8 +55,8 @@ def initialize():
from pwnlib.log import getLogger
log = getLogger(__name__)
xdg_config_home = (os.environ.get('XDG_CONFIG_HOME') or
os.path.expanduser("~/.config"))
xdg_config_home = os.environ.get('XDG_CONFIG_HOME',
os.path.expanduser("~/.config"))
c = configparser.ConfigParser()
c.read(['/etc/pwn.conf',
@@ -65,7 +65,7 @@ def initialize():
for section in c.sections():
if section not in registered_configs:
log.warn("Unknown configuration section %r" % section)
log.warn("Unknown configuration section %r", section)
continue
settings = dict(c.items(section))
registered_configs[section](settings)
+73 -20
View File
@@ -6,17 +6,20 @@ contexts work properly and as expected.
from __future__ import absolute_import
from __future__ import division
import atexit
import collections
import errno
import functools
import logging
import os
import os.path
import platform
import shutil
import six
import socket
import stat
import string
import subprocess
import sys
import tempfile
import threading
import time
@@ -345,6 +348,10 @@ class ContextType(object):
'binary': None,
'bits': 32,
'buffer_size': 4096,
'cache_dir_base': os.environ.get(
'XDG_CACHE_HOME',
os.path.join(os.path.expanduser('~'), '.cache')
),
'cyclic_alphabet': string.ascii_lowercase.encode(),
'cyclic_size': 4,
'delete_corefiles': False,
@@ -762,6 +769,7 @@ class ContextType(object):
# We have to make sure that x86_64 appears before x86 for this to work correctly.
transform = [('ppc64', 'powerpc64'),
('ppc', 'powerpc'),
('x86-64', 'amd64'),
('x86_64', 'amd64'),
('x86', 'i386'),
('i686', 'i386'),
@@ -972,7 +980,10 @@ class ContextType(object):
except AttributeError: pass
# Otherwise, fail
level_names = filter(lambda x: isinstance(x,str), logging._levelNames)
try:
level_names = logging._levelToName.values()
except AttributeError:
level_names = filter(lambda x: isinstance(x,str), logging._levelNames)
permitted = sorted(level_names)
raise AttributeError('log_level must be an integer or one of %r' % permitted)
@@ -1269,6 +1280,21 @@ class ContextType(object):
"""
return int(size)
@_validator
def cache_dir_base(self, new_base):
"""Base directory to use for caching content.
Changing this to a different value will clear the `cache_dir` path
stored in TLS since a new path will need to be generated to respect the
new `cache_dir_base` value.
"""
if new_base != self.cache_dir_base:
del self._tls["cache_dir"]
if os.access(new_base, os.F_OK) and not os.access(new_base, os.W_OK):
raise OSError(errno.EPERM, "Cache base dir is not writable")
return new_base
@property
def cache_dir(self):
"""Directory used for caching data.
@@ -1282,32 +1308,59 @@ class ContextType(object):
>>> cache_dir is not None
True
>>> os.chmod(cache_dir, 0o000)
>>> del context._tls['cache_dir']
>>> context.cache_dir is None
True
>>> os.chmod(cache_dir, 0o755)
>>> cache_dir == context.cache_dir
True
"""
xdg_cache_home = os.environ.get('XDG_CACHE_HOME') or \
os.path.join(os.path.expanduser('~'), '.cache')
try:
# If the TLS already has a cache directory path, we return it
# without any futher checks since it must have been valid when it
# was set and if that has changed, hiding the TOCTOU here would be
# potentially confusing
return self._tls["cache_dir"]
except KeyError:
pass
if not os.access(xdg_cache_home, os.W_OK):
# Attempt to create a Python version specific cache dir and its parents
cache_dirname = '.pwntools-cache-%d.%d' % sys.version_info[:2]
cache_dirpath = os.path.join(self.cache_dir_base, cache_dirname)
try:
os.makedirs(cache_dirpath)
except OSError as exc:
# If we failed for any reason other than the cache directory
# already existing then we'll fall back to a temporary directory
# object which doesn't respect the `cache_dir_base`
if exc.errno != errno.EEXIST:
try:
cache_dirpath = tempfile.mkdtemp(prefix=".pwntools-tmp")
except IOError:
# This implies no good candidates for temporary files so we
# have to return `None`
return None
else:
# Ensure the temporary cache dir is cleaned up on exit. A
# `TemporaryDirectory` would do this better upon garbage
# collection but this is necessary for Python 2 support.
atexit.register(shutil.rmtree, cache_dirpath)
# By this time we have a cache directory which exists but we don't know
# if it is actually writable. Some wargames e.g. pwnable.kr have
# created dummy directories which cannot be modified by the user
# account (owned by root).
if os.access(cache_dirpath, os.W_OK):
# Stash this in TLS for later reuse
self._tls["cache_dir"] = cache_dirpath
return cache_dirpath
else:
return None
cache = os.path.join(xdg_cache_home, '.pwntools-cache-%d.%d' % sys.version_info[:2])
if not os.path.exists(cache):
try:
os.mkdir(cache)
except OSError:
return None
# Some wargames e.g. pwnable.kr have created dummy directories
# which cannot be modified by the user account (owned by root).
if not os.access(cache, os.W_OK):
return None
return cache
@cache_dir.setter
def cache_dir(self, v):
if os.access(v, os.W_OK):
# Stash this in TLS for later reuse
self._tls["cache_dir"] = v
@_validator
def delete_corefiles(self, v):
+31 -22
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python2
#!/usr/bin/env python3
from __future__ import division
import argparse
import keyword
@@ -137,6 +137,7 @@ def can_be_string(arg):
if arg.type == 'void' and arg.derefcnt == 1:
return True
def can_be_array(arg):
if arg.type == 'char' and arg.derefcnt == 2:
return True
@@ -160,11 +161,13 @@ def fix_bad_arg_names(func, arg):
def get_arg_default(arg):
return 0
def fix_rt_syscall_name(name):
if name.startswith('rt_'):
return name[3:]
return name
def fix_syscall_names(name):
# Do not use old_mmap
if name == 'SYS_mmap':
@@ -180,6 +183,7 @@ def main(target):
with context.local(arch=arch):
generate_one(target)
def generate_one(target):
SYSCALL_NAMES = [c for c in dir(constants) if c.startswith('SYS_')]
@@ -214,46 +218,50 @@ def generate_one(target):
# Set up the argument string for Mako
argument_names = []
argument_names_ = []
argument_defaults = []
string_arguments = []
array_arguments = []
arg_docs = []
#
for arg in function.args:
argname = fix_bad_arg_names(function, arg)
argname_ = fix_bad_arg_names(function, arg)
argname = argname_.rstrip('_')
default = get_arg_default(arg)
if can_be_array(arg):
array_arguments.append(argname)
if can_be_string(arg):
string_arguments.append(argname)
argtype = str(arg.type) + ('*' * arg.derefcnt)
arg_docs.append(
' {argname_}({argtype}): {argname}'.format(
argname_=argname_,
argname=argname,
argtype=argtype,
))
# Mako is unable to use *vararg and *kwarg, so we just stub in
# a whole bunch of additional arguments.
if argname == 'vararg':
for j in range(5):
argname = 'vararg_%i' % j
argument_names.append(argname)
argument_names_.append(argname)
argument_defaults.append('%s=%s' % (argname, None))
break
argument_names.append(argname)
argument_defaults.append('%s=%s' % (argname, default))
argument_names_.append(argname_)
argument_defaults.append('%s=%s' % (argname_, default))
arguments_default_values = ', '.join(argument_defaults)
arguments_comma_separated = ', '.join(argument_names)
string_arguments = []
array_arguments = []
arg_docs = []
for arg in function.args:
if can_be_array(arg):
array_arguments.append(arg.name)
if can_be_string(arg):
string_arguments.append(arg.name)
argname = arg.name
argtype = str(arg.type) + ('*' * arg.derefcnt)
arg_docs.append(
' {argname}({argtype}): {argname}'.format(argname=argname,
argtype=argtype))
arguments_comma_separated = ', '.join(argument_names_)
return_type = str(function.type) + ('*' * function.derefcnt)
arg_docs = '\n'.join(arg_docs)
@@ -282,6 +290,7 @@ def generate_one(target):
with open(os.path.join(target, name + '.asm'), 'wt') as f:
f.write('\n'.join(map(str.strip, lines)) + '\n')
if __name__ == '__main__':
p = argparse.ArgumentParser()
p.add_argument('target_directory')
@@ -1,4 +1,4 @@
#!/usr/bin/env python2
#!/usr/bin/env python3
"""Script for downloading lists of user agent strings."""
from __future__ import division
import os
@@ -43,7 +43,7 @@ with log.waitfor('Fetching from from http://www.user-agents.org') as l:
l.success()
with log.waitfor('Parsing list') as l:
for item in soup.__getattr__('user-agents'):
for item in getattr(soup, 'user-agents'):
if item.name == 'user-agent':
ua = item.select('string')[0].string.strip()
try:
+5 -6
View File
@@ -15,7 +15,7 @@ Example
# leaks at least one byte at that address.
def leak(address):
data = p.read(address, 4)
log.debug("%#x => %s" % (address, enhex(data or '')))
log.debug("%#x => %s", address, enhex(data or ''))
return data
# For the sake of this example, let's say that we
@@ -62,8 +62,7 @@ from pwnlib.elf import constants
from pwnlib.log import getLogger
from pwnlib.memleak import MemLeak
from pwnlib.util.fiddling import enhex
from pwnlib.util.packing import unpack, _need_bytes
from pwnlib.util.web import wget
from pwnlib.util.packing import _need_bytes
log = getLogger(__name__)
sizeof = ctypes.sizeof
@@ -562,7 +561,7 @@ class DynELF(object):
else: dynlib = self
if dynlib is None:
log.failure("Could not find %r" % lib)
log.failure("Could not find %r", lib)
return None
#
@@ -573,7 +572,7 @@ class DynELF(object):
self.status("Trying lookup based on Build ID")
build_id = dynlib._lookup_build_id(lib=lib)
if build_id:
log.info("Trying lookup based on Build ID: %s" % build_id)
log.info("Trying lookup based on Build ID: %s", build_id)
path = libcdb.search_by_build_id(build_id)
if path:
with context.local(log_level='error'):
@@ -615,7 +614,7 @@ class DynELF(object):
addr = leak.field(cur, LinkMap.l_addr)
cur = leak.field(cur, LinkMap.l_next)
log.debug('Found %r @ %#x' % (name, addr))
log.debug('Found %r @ %#x', name, addr)
self._bases[name] = addr
+7 -8
View File
@@ -445,7 +445,7 @@ class Corefile(ELF):
will be loaded.
>>> process('bash').corefile.libc # doctest: +ELLIPSIS
Mapping('/.../libc-....so', start=0x..., stop=0x..., size=0x..., flags=..., page_offset=...)
Mapping('.../libc...so...', start=0x..., stop=0x..., size=0x..., flags=..., page_offset=...)
The corefile also contains a :attr:`.stack` property, which gives
us direct access to the stack contents. On Linux, the very top of the stack
@@ -759,7 +759,7 @@ class Corefile(ELF):
@property
def libc(self):
""":class:`Mapping`: First mapping for ``libc.so``"""
expr = r'libc\b.*so$'
expr = r'^libc\b.*so(?:\.6)?$'
for m in self.mappings:
if not m.name:
@@ -1184,14 +1184,13 @@ class Corefile(ELF):
pwnlib.gdb.attach(self, exe=self.exe.path)
def __getattr__(self, attribute):
if self.prstatus:
if hasattr(self.prstatus, attribute):
return getattr(self.prstatus, attribute)
if attribute.startswith('_') or not self.prstatus:
raise AttributeError(attribute)
if hasattr(self.prstatus.pr_reg, attribute):
return getattr(self.prstatus.pr_reg, attribute)
if hasattr(self.prstatus, attribute):
return getattr(self.prstatus, attribute)
return super(Corefile, self).__getattribute__(attribute)
return getattr(self.prstatus.pr_reg, attribute)
# Override routines which don't make sense for Corefiles
def _populate_got(*a): pass
+2 -1
View File
@@ -604,7 +604,8 @@ class user_regs_struct_aarch64(ctypes.Structure):
def __getattr__(self, name):
if name.startswith('r'):
name = 'x' + name[1:]
return getattr(name) & 0xffffffff
return getattr(self, name) & 0xffffffff
raise AttributeError(name)
class elf_prstatus_i386(ctypes.Structure):
_fields_ = generate_prstatus_common(32, user_regs_struct_i386)
+53 -17
View File
@@ -157,6 +157,12 @@ class dotdict(dict):
>>> x.bar.baz
4
"""
def __missing__(self, name):
if isinstance(name, (bytes, bytearray)):
name = packing._decode(name)
return self[name]
raise KeyError(name)
def __getattr__(self, name):
if name in self:
return self[name]
@@ -167,8 +173,7 @@ class dotdict(dict):
if subkeys:
return dotdict(subkeys)
return getattr(super(dotdict, self), name)
raise AttributeError(name)
class ELF(ELFFile):
"""Encapsulates information about an ELF file.
@@ -1076,11 +1081,17 @@ class ELF(ELFFile):
@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.
""":class:`int`: Address of the return address into __libc_start_main from main.
>>> bash = ELF(which('bash'))
>>> libc = bash.libc
>>> libc.libc_start_main_return > 0
True
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
@@ -1104,18 +1115,43 @@ class ELF(ELFFile):
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)
lines = self.functions['__libc_start_main'].disasm().split('\n')
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 find_ret_main_addr(lines, calls):
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
# Starting with glibc-2.34 calling `main` is split out into `__libc_start_call_main`
ret_addr = find_ret_main_addr(lines, calls)
# Pre glibc-2.34 case - `main` is called directly
if ret_addr:
return ret_addr
# `__libc_start_main` -> `__libc_start_call_main` -> `main`
# Find a direct call which calls `exit` once. That's probably `__libc_start_call_main`.
direct_call_pattern = re.compile(r'['+r'|'.join(call_instructions)+r']\s+(0x[0-9a-zA-Z]+)')
for line in calls:
match = direct_call_pattern.search(line[1])
if not match:
continue
target_addr = int(match.group(1), 0)
# `__libc_start_call_main` is usually smaller than `__libc_start_main`, so
# we might disassemble a bit too much, but it's a good dynamic estimate.
callee_lines = self.disasm(target_addr, self.functions['__libc_start_main'].size).split('\n')
callee_calls = [(index, line) for index, line in enumerate(callee_lines) if set(line.split()) & call_instructions]
ret_addr = find_ret_main_addr(callee_lines, callee_calls)
if ret_addr:
return ret_addr
return 0
def search(self, needle, writable = False, executable = False):
"""search(needle, writable = False, executable = False) -> generator
@@ -1753,7 +1789,7 @@ class ELF(ELFFile):
@property
def packed(self):
""":class:`bool`: Whether the current binary is packed with UPX."""
return b'UPX!' in self.get_data()
return b'UPX!' in self.get_data()[:0xFF]
@property
def pie(self):
@@ -1,3 +1,5 @@
# Copyright (c) 2013 Pratik Kumar Sahu, Nagendra Chowdary, Anish Mathuria
# Ported to Python by Gallopsled
from __future__ import division
# +------------------------------------------------------------------------+
@@ -1,3 +1,5 @@
# Copyright (c) 2013 Pratik Kumar Sahu, Nagendra Chowdary, Anish Mathuria
# Ported to Python by Gallopsled
from __future__ import absolute_import
from __future__ import division
@@ -1,3 +1,5 @@
# Copyright (c) 2013 Pratik Kumar Sahu, Nagendra Chowdary, Anish Mathuria
# Ported to Python by Gallopsled
from __future__ import division
from __future__ import absolute_import
@@ -1,3 +1,5 @@
# Copyright (c) 2013 Pratik Kumar Sahu, Nagendra Chowdary, Anish Mathuria
# Ported to Python by Gallopsled
from __future__ import absolute_import
from __future__ import division
@@ -1,3 +1,5 @@
# Copyright (c) 2013 Pratik Kumar Sahu, Nagendra Chowdary, Anish Mathuria
# Ported to Python by Gallopsled
from __future__ import division
import os
+9 -13
View File
@@ -125,7 +125,7 @@ class FileStructure(object):
>>> len(fileStr)
224
The defination for __repr__ orders the structure members and displays then in a dictionary format. It's useful when viewing a structure objet in python/IPython shell
The definition for __repr__ orders the structure members and displays then in a dictionary format. It's useful when viewing a structure objet in python/IPython shell
>>> q=FileStructure(0xdeadbeef)
>>> q
@@ -176,24 +176,20 @@ class FileStructure(object):
def __repr__(self):
structure=[]
for i in self.vars_:
structure.append(" %s: %s" % (i,hex(self.__getattr__(i))))
structure.append(" %s: %s" % (i,hex(getattr(self, i))))
return "{"+ "\n".join(structure)+"}"
def __getattr__(self,item):
if item in FileStructure.__dict__ or item in self.vars_:
return object.__getattribute__(self,item)
log.error("Unknown variable %r" % item)
def __len__(self):
return len(bytes(self))
def __bytes__(self):
structure = b''
for val in self.vars_:
if isinstance(self.__getattr__(val), bytes):
structure += self.__getattr__(val).ljust(context.bytes, b'\x00')
if isinstance(getattr(self, val), bytes):
structure += getattr(self, val).ljust(context.bytes, b'\x00')
else:
structure += pack(self.__getattr__(val), self.length[val]*8)
if self.length[val] > 0:
structure += pack(getattr(self, val), self.length[val]*8)
return structure
def struntil(self,v):
@@ -218,10 +214,10 @@ class FileStructure(object):
return b''
structure = b''
for val in self.vars_:
if isinstance(self.__getattr__(val), bytes):
structure += self.__getattr__(val).ljust(context.bytes, b'\x00')
if isinstance(getattr(self, val), bytes):
structure += getattr(self, val).ljust(context.bytes, b'\x00')
else:
structure += pack(self.__getattr__(val), self.length[val]*8)
structure += pack(getattr(self, val), self.length[val]*8)
if val == v:
break
return structure[:-1]
+5 -5
View File
@@ -303,7 +303,7 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
env_args.append(b'%s=%s' % (key, env.pop(key)))
else:
env_args.append(b'%s=%s' % (key, env[key]))
gdbserver_args += ['--wrapper', 'env', '-i'] + env_args + ['--']
gdbserver_args += ['--wrapper', which('env'), '-i'] + env_args + ['--']
gdbserver_args += ['localhost:0']
gdbserver_args += args
@@ -878,7 +878,7 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
pre += 'set gnutarget ' + _bfdname() + '\n'
if exe and context.os != 'baremetal':
pre += 'file %s\n' % exe
pre += 'file "%s"\n' % exe
# let's see if we can find a pid to attach to
pid = None
@@ -915,7 +915,7 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
cmd = ['sshpass', '-p', shell.password] + cmd
if shell.keyfile:
cmd += ['-i', shell.keyfile]
cmd += ['gdb', '-q', target.executable, target.pid, '-x', tmpfile]
cmd += ['gdb', '-q', target.executable, str(target.pid), '-x', tmpfile]
misc.run_in_new_terminal(cmd)
return
@@ -967,7 +967,7 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
exe = exe or findexe()
elif isinstance(target, elf.corefile.Corefile):
pre += 'target core %s\n' % target.path
pre += 'target core "%s"\n' % target.path
else:
log.error("don't know how to attach to target: %r", target)
@@ -1329,7 +1329,7 @@ def version(program='gdb'):
Example:
>>> (7,0) <= gdb.version() <= (12,0)
>>> (7,0) <= gdb.version() <= (19,0)
True
"""
program = misc.which(program)
+153 -29
View File
@@ -12,9 +12,12 @@ import tempfile
from pwnlib.context import context
from pwnlib.elf import ELF
from pwnlib.log import getLogger
from pwnlib.tubes.process import process
from pwnlib.util.fiddling import b64d
from pwnlib.util.fiddling import enhex
from pwnlib.util.fiddling import hexdump
from pwnlib.util.misc import read
from pwnlib.util.misc import which
from pwnlib.util.misc import write
from pwnlib.util.safeeval import const
from pwnlib.util.web import wget
@@ -22,6 +25,7 @@ from pwnlib.util.web import wget
log = getLogger(__name__)
HASHES = ['build_id', 'sha1', 'sha256', 'md5']
DEBUGINFOD_SERVERS = ['https://debuginfod.systemtap.org/']
# https://gitlab.com/libcdb/libcdb wasn't updated after 2019,
# but still is a massive database of older libc binaries.
@@ -88,29 +92,13 @@ def provider_libc_rip(hex_encoded_id, hash_type):
PROVIDERS = [provider_libcdb, provider_libc_rip]
def search_by_hash(hex_encoded_id, hash_type='build_id'):
def search_by_hash(hex_encoded_id, hash_type='build_id', unstrip=True):
assert hash_type in HASHES, hash_type
# Ensure that the libcdb cache directory exists
cache_dir = os.path.join(context.cache_dir, 'libcdb', hash_type)
if not os.path.isdir(cache_dir):
os.makedirs(cache_dir)
# If we already downloaded the file, and it looks even passingly like
# a valid ELF file, return it.
cache = os.path.join(cache_dir, hex_encoded_id)
if os.path.exists(cache):
log.debug("Found existing cached libc at %r", cache)
data = read(cache)
if data.startswith(b'\x7FELF'):
log.info_once("Using cached data from %r", cache)
return cache
else:
log.info_once("Skipping unavailable libc %s", hex_encoded_id)
return None
cache, cache_valid = _check_elf_cache('libcdb', hex_encoded_id, hash_type)
if cache_valid:
return cache
# Run through all available libc database providers to see if we have a match.
for provider in PROVIDERS:
@@ -128,16 +116,146 @@ def search_by_hash(hex_encoded_id, hash_type='build_id'):
if not data or not data.startswith(b'\x7FELF'):
return None
# Try to find debug info for this libc.
if unstrip:
unstrip_libc(cache)
return cache
def _search_debuginfo_by_hash(base_url, hex_encoded_id):
# Deferred import because it's slow
import requests
from six.moves import urllib
def search_by_build_id(hex_encoded_id):
# Check if we tried this buildid before.
cache, cache_valid = _check_elf_cache('libcdb_dbg', hex_encoded_id, 'build_id')
if cache_valid:
return cache
# Try to find separate debuginfo.
url = '/buildid/{}/debuginfo'.format(hex_encoded_id)
url = urllib.parse.urljoin(base_url, url)
data = b""
log.debug("Downloading data from debuginfod: %s", url)
try:
data = wget(url, timeout=20)
except requests.RequestException as e:
log.warn_once("Failed to fetch libc debuginfo for build_id %s from %s: %s", hex_encoded_id, base_url, e)
# Save whatever we got to the cache
write(cache, data or b'')
# Return ``None`` if we did not get a valid ELF file
if not data or not data.startswith(b'\x7FELF'):
log.warn_once("Could not fetch libc debuginfo for build_id %s from %s", hex_encoded_id, base_url)
return None
return cache
def _check_elf_cache(cache_type, hex_encoded_id, hash_type):
"""
Check if there already is an ELF file for this hash in the cache.
>>> cache, _ = _check_elf_cache('libcdb', '2d1c5e0b85cb06ff47fa6fa088ec22cb6e06074e', 'build_id')
>>> os.unlink(cache) if os.path.exists(cache)
>>> filename = search_by_hash('2d1c5e0b85cb06ff47fa6fa088ec22cb6e06074e', 'build_id', unstrip=False)
>>> hex(ELF(filename).symbols.read)
'0xe56c0'
>>> filename == cache
True
"""
# Ensure that the cache directory exists
cache_dir = os.path.join(context.cache_dir, cache_type, hash_type)
if not os.path.isdir(cache_dir):
os.makedirs(cache_dir)
# If we already downloaded the file, and it looks even passingly like
# a valid ELF file, return it.
cache = os.path.join(cache_dir, hex_encoded_id)
if not os.path.exists(cache):
return cache, False
log.debug("Found existing cached ELF at %r", cache)
data = read(cache)
if not data.startswith(b'\x7FELF'):
log.info_once("Skipping unavailable ELF %s", hex_encoded_id)
return cache, False
log.info_once("Using cached data from %r", cache)
return cache, True
def unstrip_libc(filename):
"""
Given a path to a libc binary, attempt to download matching debug info
and add them back to the given binary.
This modifies the given file.
Arguments:
filename(str):
Path to the libc binary to unstrip.
Returns:
:const:`True` if binary was unstripped, :const:`False` otherwise.
Examples:
>>> filename = search_by_build_id('2d1c5e0b85cb06ff47fa6fa088ec22cb6e06074e', unstrip=False)
>>> libc = ELF(filename)
>>> hex(libc.symbols.read)
'0xe56c0'
>>> 'main_arena' in libc.symbols
False
>>> unstrip_libc(filename)
True
>>> libc = ELF(filename)
>>> hex(libc.symbols.main_arena)
'0x1d57a0'
>>> unstrip_libc(which('python'))
False
>>> filename = search_by_build_id('06a8004be6e10c4aeabbe0db74423ace392a2d6b', unstrip=True)
>>> 'main_arena' in ELF(filename).symbols
True
"""
if not which('eu-unstrip'):
log.warn_once('Couldn\'t find "eu-unstrip" in PATH. Install elfutils first.')
return False
libc = ELF(filename, checksec=False)
if not libc.buildid:
log.warn_once('Given libc does not have a buildid. Cannot look for debuginfo to unstrip.')
return False
for server_url in DEBUGINFOD_SERVERS:
libc_dbg = _search_debuginfo_by_hash(server_url, enhex(libc.buildid))
if libc_dbg:
break
else:
log.warn_once('Couldn\'t find debug info for libc with build_id %s on any debuginfod server.', enhex(libc.buildid))
return False
# Add debug info to given libc binary inplace.
p = process(['eu-unstrip', '-o', filename, filename, libc_dbg])
output = p.recvall()
p.close()
if output:
log.error('Failed to unstrip libc binary: %r', output)
return False
return True
def search_by_build_id(hex_encoded_id, unstrip=True):
"""
Given a hex-encoded Build ID, attempt to download a matching libc from libcdb.
Arguments:
hex_encoded_id(str):
Hex-encoded Build ID (e.g. 'ABCDEF...') of the library
unstrip(bool):
Try to fetch debug info for the libc and apply it to the downloaded file.
Returns:
Path to the downloaded library on disk, or :const:`None`.
@@ -152,15 +270,17 @@ def search_by_build_id(hex_encoded_id):
>>> hex(ELF(filename).symbols.read)
'0xeef40'
"""
return search_by_hash(hex_encoded_id, 'build_id')
return search_by_hash(hex_encoded_id, 'build_id', unstrip)
def search_by_md5(hex_encoded_id):
def search_by_md5(hex_encoded_id, unstrip=True):
"""
Given a hex-encoded md5sum, attempt to download a matching libc from libcdb.
Arguments:
hex_encoded_id(str):
Hex-encoded md5sum (e.g. 'ABCDEF...') of the library
unstrip(bool):
Try to fetch debug info for the libc and apply it to the downloaded file.
Returns:
Path to the downloaded library on disk, or :const:`None`.
@@ -175,15 +295,17 @@ def search_by_md5(hex_encoded_id):
>>> hex(ELF(filename).symbols.read)
'0xeef40'
"""
return search_by_hash(hex_encoded_id, 'md5')
return search_by_hash(hex_encoded_id, 'md5', unstrip)
def search_by_sha1(hex_encoded_id):
def search_by_sha1(hex_encoded_id, unstrip=True):
"""
Given a hex-encoded sha1, attempt to download a matching libc from libcdb.
Arguments:
hex_encoded_id(str):
Hex-encoded sha1sum (e.g. 'ABCDEF...') of the library
unstrip(bool):
Try to fetch debug info for the libc and apply it to the downloaded file.
Returns:
Path to the downloaded library on disk, or :const:`None`.
@@ -198,16 +320,18 @@ def search_by_sha1(hex_encoded_id):
>>> hex(ELF(filename).symbols.read)
'0xeef40'
"""
return search_by_hash(hex_encoded_id, 'sha1')
return search_by_hash(hex_encoded_id, 'sha1', unstrip)
def search_by_sha256(hex_encoded_id):
def search_by_sha256(hex_encoded_id, unstrip=True):
"""
Given a hex-encoded sha256, attempt to download a matching libc from libcdb.
Arguments:
hex_encoded_id(str):
Hex-encoded sha256sum (e.g. 'ABCDEF...') of the library
unstrip(bool):
Try to fetch debug info for the libc and apply it to the downloaded file.
Returns:
Path to the downloaded library on disk, or :const:`None`.
@@ -222,7 +346,7 @@ def search_by_sha256(hex_encoded_id):
>>> hex(ELF(filename).symbols.read)
'0xeef40'
"""
return search_by_hash(hex_encoded_id, 'sha256')
return search_by_hash(hex_encoded_id, 'sha256', unstrip)
@@ -279,4 +403,4 @@ def get_build_id_offsets():
}.get(context.arch, [])
__all__ = ['get_build_id_offsets', 'search_by_build_id', 'search_by_sha1', 'search_by_sha256', 'search_by_md5']
__all__ = ['get_build_id_offsets', 'search_by_build_id', 'search_by_sha1', 'search_by_sha256', 'search_by_md5', 'unstrip_libc']
+15
View File
@@ -99,6 +99,7 @@ import os
import random
import re
import six
import string
import sys
import threading
import time
@@ -401,6 +402,20 @@ class Logger(object):
self.info(pwnlib.util.fiddling.hexdump(message, *args, **kwargs))
def maybe_hexdump(self, message, *args, **kwargs):
"""maybe_hexdump(self, message, *args, **kwargs)
Logs a message using indented. Repeated single byte is compressed, and
unprintable message is hexdumped.
"""
if len(set(message)) == 1 and len(message) > 1:
self.indented('%r * %#x' % (message[:1], len(message)), *args, **kwargs)
elif len(message) == 1 or all(c in string.printable.encode() for c in message):
for line in message.splitlines(True):
self.indented(repr(line), *args, **kwargs)
else:
import pwnlib.util.fiddling
self.indented(pwnlib.util.fiddling.hexdump(message), *args, **kwargs)
def warning(self, message, *args, **kwargs):
"""warning(message, *args, **kwargs)
+7
View File
@@ -246,6 +246,13 @@ class Call(object):
fmt % self.target,
self.args)
def is_flat(self):
if isinstance(self, six.integer_types + (Unresolved,)):
return True
if not isinstance(self, Call):
return False
return bool(self.args or self.stack_arguments_before)
@property
def register_arguments(self):
return dict(zip(self.abi.register_arguments, self.args))
+57 -14
View File
@@ -30,6 +30,7 @@ Other, more complicated gdagets also happen magically
Gadget(0x10000006, ['pop ecx', 'pop ebx', 'ret'], ['ecx', 'ebx'], 0xc)
The easiest way to set up individual registers is to invoke the ``ROP`` object as a callable, with the registers as arguments.
This has the benefit of using multi-pop gadgets to set multiple registers with one gadget.
>>> rop(eax=0x11111111, ecx=0x22222222)
@@ -44,6 +45,37 @@ which corresponds to their offset, which is useful when debuggging your exploit.
0x000c: 0x10000004 pop eax; ret
0x0010: 0x11111111
If you really want to set one register at a time, you can also use the assignment form.
It's generally advised to use the rop(eax=..., ecx=...) form, since there may be an
e.g. ``pop eax; pop ecx; ret`` gadget that can be taken advantage of.
>>> rop = ROP(binary)
>>> rop.eax = 0xdeadf00d
>>> rop.ecx = 0xc01dbeef
>>> rop.raw(0xffffffff)
>>> print(rop.dump())
0x0000: 0x10000004 pop eax; ret
0x0004: 0xdeadf00d
0x0008: 0x10000006 pop ecx; pop ebx; ret
0x000c: 0xc01dbeef
0x0010: b'eaaa' <pad ebx>
0x0014: 0xffffffff
If you just want to FIND a ROP gadget, you can access them as a property on the ``ROP``
object by register name.
>>> rop = ROP(binary)
>>> rop.eax
Gadget(0x10000004, ['pop eax', 'ret'], ['eax'], 0x8)
>>> hex(rop.eax.address)
'0x10000004'
>>> rop.raw(rop.eax)
>>> rop.raw(0x12345678)
>>> print(rop.dump())
0x0000: 0x10000004 pop eax; ret
0x0004: 0x12345678
Let's re-create our ROP object now to show for some other examples.:
>>> rop = ROP(binary)
@@ -92,12 +124,10 @@ The stack is automatically adjusted for the next frame
0x001c: 0x6 arg2
0x0020: b'iaaa' <pad>
0x0024: 0xdecafbad write(7, 8, 9)
0x0028: 0x10000000 <adjust @0x3c> add esp, 0x10; ret
0x0028: 0xfeedface exit()
0x002c: 0x7 arg0
0x0030: 0x8 arg1
0x0034: 0x9 arg2
0x0038: b'oaaa' <pad>
0x003c: 0xfeedface exit()
You can also append complex arguments onto stack when the stack pointer is known.
@@ -188,18 +218,16 @@ Finally, let's build our ROP stack
>>> rop.exit()
>>> print(rop.dump())
0x0000: 0x10000012 write(STDOUT_FILENO, 0x10000026, 8)
0x0004: 0x1000000e <adjust @0x18> add esp, 0x10; ret
0x0004: 0x1000002f exit()
0x0008: 0x1 STDOUT_FILENO
0x000c: 0x10000026 flag
0x0010: 0x8 arg2
0x0014: b'faaa' <pad>
0x0018: 0x1000002f exit()
The raw data from the ROP stack is available via `str`.
The raw data from the ROP stack is available via `r.chain()` (or `bytes(r)`).
>>> raw_rop = rop.chain()
>>> print(enhex(raw_rop))
120000100e000010010000002600001008000000666161612f000010
120000102f000010010000002600001008000000
Let's try it out!
@@ -927,7 +955,7 @@ class ROP(object):
# If there were arguments on the stack, we need to stick something
# in the slot where the return address goes.
if len(stackArguments) > 0:
if remaining:
if remaining and (remaining > 1 or Call.is_flat(chain[-1])):
fix_size = (1 + len(stackArguments))
fix_bytes = fix_size * context.bytes
adjust = self.search(move = fix_bytes)
@@ -944,6 +972,13 @@ class ROP(object):
stackArguments.append(Padding())
# We could not find a proper "adjust" gadget, but also didn't need one.
elif remaining:
_, nxslot = next(iterable)
stack.describe(self.describe(nxslot))
if isinstance(nxslot, Call):
stack.append(nxslot.target)
else:
stack.append(nxslot)
else:
stack.append(Padding("<return address>"))
@@ -1189,6 +1224,9 @@ class ROP(object):
def __get_cachefile_name(self, files):
"""Given an ELF or list of ELF objects, return a cache file for the set of files"""
if context.cache_dir is None:
return None
cachedir = os.path.join(context.cache_dir, 'rop-cache')
if not os.path.exists(cachedir):
os.mkdir(cachedir)
@@ -1205,12 +1243,14 @@ class ROP(object):
@staticmethod
def clear_cache():
"""Clears the ROP gadget cache"""
if context.cache_dir is None:
return
cachedir = os.path.join(context.cache_dir, 'rop-cache')
shutil.rmtree(cachedir)
def __cache_load(self, elf):
filename = self.__get_cachefile_name(elf)
if not os.path.exists(filename):
if filename is None or not os.path.exists(filename):
return None
gadgets = eval(open(filename).read())
gadgets = {k - elf.load_addr + elf.address:v for k, v in gadgets.items()}
@@ -1218,8 +1258,11 @@ class ROP(object):
return gadgets
def __cache_save(self, elf, data):
filename = self.__get_cachefile_name(elf)
if filename is None:
return
data = {k + elf.load_addr - elf.address:v for k, v in data.items()}
open(self.__get_cachefile_name(elf), 'w+').write(repr(data))
open(filename, 'w+').write(repr(data))
def __load(self):
"""Load all ROP gadgets for the selected ELF files"""
@@ -1274,7 +1317,7 @@ class ROP(object):
pass
def __getattr__(self, k):
return self._fd.__getattribute__(k)
return getattr(self._fd, k)
gadgets = {}
for elf in self.elfs:
@@ -1477,8 +1520,6 @@ class ROP(object):
# Find an appropriate, non-library ELF.
# Prioritise non-PIE binaries so we can use _fini
exes = (elf for elf in self.elfs if not elf.library and elf.bits == 64)
if not exes:
log.error('No non-library binaries in [elfs]')
nonpie = csu = None
for elf in exes:
@@ -1488,6 +1529,8 @@ class ROP(object):
nonpie = elf
elif '__libc_csu_init' in elf.symbols:
csu = elf
else:
log.error('No non-library binaries in [elfs]')
if elf.pie:
if nonpie:
+3 -1
View File
@@ -391,7 +391,9 @@ class SigreturnFrame(dict):
self.set_regvalue(attr, value)
def __getattr__(self, attr):
return self[attr]
if attr in self:
return self[attr]
raise AttributeError(attr)
def __bytes__(self):
frame = b""
+11 -43
View File
@@ -2,6 +2,7 @@ from __future__ import absolute_import
from __future__ import division
import os
import sys
from pwnlib.context import context
@@ -109,47 +110,17 @@ def get_context_from_dirpath(directory):
return {'os': osys, 'arch': arch}
def make_function(funcname, filename, directory):
import functools
import inspect
path = os.path.join(directory, filename)
template = lookup_template(path)
args, varargs, keywords, defaults = inspect.getargspec(template.module.render_body)
defaults = defaults or []
if len(defaults) < len(args) and args[0] == 'context':
args.pop(0)
args_used = args[:]
for n, default in enumerate(defaults, len(args) - len(defaults)):
args[n] = '%s = %r' % (args[n], default)
if varargs:
args.append('*' + varargs)
args_used.append('*' + varargs)
if keywords not in ['pageargs', None]:
args.append('**' + keywords)
args_used.append('**' + keywords)
docstring = inspect.cleandoc(template.module.__doc__ or '')
args = ', '.join(args)
args_used = ', '.join(args_used)
local_ctx = get_context_from_dirpath(directory)
# This is a slight hack to get the right signature for the function
# It would be possible to simply create an (*args, **kwargs) wrapper,
# but what would not have the right signature.
# While we are at it, we insert the docstring too
T = r'''
def wrap(template, render_global):
import pwnlib
def %(funcname)s(%(args)s):
%(docstring)r
def res(*args, **kwargs):
with render_global.go_inside() as was_inside:
with pwnlib.context.context.local(**%(local_ctx)s):
lines = template.render(%(args_used)s).split('\n')
with context.local(**local_ctx):
lines = template.render(*args, **kwargs).split('\n')
for i, line in enumerate(lines):
def islabelchar(c):
return c.isalnum() or c == '.' or c == '_'
@@ -168,19 +139,16 @@ def wrap(template, render_global):
return s
else:
return s + '\n'
return %(funcname)s
''' % locals()
g = {}
exec(T, g, g)
wrap = g['wrap']
# Setting _relpath is a slight hack only used to get better documentation
res = wrap(template, render_global)
res._relpath = path
res.__module__ = 'pwnlib.shellcraft.' + os.path.dirname(path).replace('/','.')
import sys, functools
res.__name__ = res.__qualname__ = funcname
res.__doc__ = inspect.cleandoc(template.module.__doc__ or '')
if hasattr(inspect, 'signature'):
sig = inspect.signature(template.module.render_body)
sig = sig.replace(parameters=list(sig.parameters.values())[1:-1])
res.__signature__ = sig
@functools.wraps(res)
def function(*a):
@@ -15,7 +15,7 @@ Arguments:
fd(int): fd
advice(int): advice
offset(loff_t): offset
len(loff_t): len
length(loff_t): length
Returns:
long
</%docstring>
@@ -14,7 +14,7 @@ See 'man 2 bind' for more information.
Arguments:
fd(int): fd
addr(CONST_SOCKADDR_ARG): addr
len(socklen_t): len
length(socklen_t): length
Returns:
int
</%docstring>
@@ -14,7 +14,7 @@ See 'man 2 connect' for more information.
Arguments:
fd(int): fd
addr(CONST_SOCKADDR_ARG): addr
len(socklen_t): len
length(socklen_t): length
Returns:
int
</%docstring>
@@ -15,7 +15,7 @@ Arguments:
fd(int): fd
mode(int): mode
offset(off_t): offset
len(off_t): len
length(off_t): length
Returns:
int
</%docstring>
@@ -14,7 +14,7 @@ See 'man 2 getpeername' for more information.
Arguments:
fd(int): fd
addr(SOCKADDR_ARG): addr
len(socklen_t*): len
length(socklen_t*): length
Returns:
int
</%docstring>
@@ -14,7 +14,7 @@ See 'man 2 getsockname' for more information.
Arguments:
fd(int): fd
addr(SOCKADDR_ARG): addr
len(socklen_t*): len
length(socklen_t*): length
Returns:
int
</%docstring>
@@ -12,7 +12,7 @@ Invokes the syscall ioperm.
See 'man 2 ioperm' for more information.
Arguments:
from(unsigned): from
from_(unsigned): from
num(unsigned): num
turn_on(int): turn_on
Returns:
@@ -28,7 +28,7 @@ Returns:
can_pushstr = []
can_pushstr_array = []
argument_names = ['from_', 'num', 'turn_on']
argument_names = ['from', 'num', 'turn_on']
argument_values = [from_, num, turn_on]
# Load all of the arguments into their destination registers / stack slots.
@@ -12,7 +12,7 @@ Invokes the syscall link.
See 'man 2 link' for more information.
Arguments:
from(char*): from
from_(char*): from
to(char*): to
Returns:
int
@@ -27,7 +27,7 @@ Returns:
can_pushstr = ['from', 'to']
can_pushstr_array = []
argument_names = ['from_', 'to']
argument_names = ['from', 'to']
argument_values = [from_, to]
# Load all of the arguments into their destination registers / stack slots.
@@ -13,7 +13,7 @@ See 'man 2 linkat' for more information.
Arguments:
fromfd(int): fromfd
from(char*): from
from_(char*): from
tofd(int): tofd
to(char*): to
flags(int): flags
@@ -30,7 +30,7 @@ Returns:
can_pushstr = ['from', 'to']
can_pushstr_array = []
argument_names = ['fromfd', 'from_', 'tofd', 'to', 'flags']
argument_names = ['fromfd', 'from', 'tofd', 'to', 'flags']
argument_values = [fromfd, from_, tofd, to, flags]
# Load all of the arguments into their destination registers / stack slots.
@@ -14,7 +14,7 @@ See 'man 2 lookup_dcookie' for more information.
Arguments:
cookie(u64): cookie
buffer(char*): buffer
len(size_t): len
length(size_t): length
Returns:
int
</%docstring>
@@ -13,7 +13,7 @@ See 'man 2 madvise' for more information.
Arguments:
addr(void*): addr
len(size_t): len
length(size_t): length
advice(int): advice
Returns:
int
@@ -13,7 +13,7 @@ See 'man 2 mbind' for more information.
Arguments:
addr(void*): addr
len(unsigned): len
length(unsigned): length
mode(int): mode
nodemask(unsigned*): nodemask
maxnode(unsigned): maxnode
@@ -13,7 +13,7 @@ See 'man 2 mincore' for more information.
Arguments:
start(void*): start
len(size_t): len
length(size_t): length
vec(unsigned*): vec
Returns:
int
@@ -13,7 +13,7 @@ See 'man 2 mlock' for more information.
Arguments:
addr(void*): addr
len(size_t): len
length(size_t): length
Returns:
int
</%docstring>
@@ -13,7 +13,7 @@ See 'man 2 mlock2' for more information.
Arguments:
addr(void*): addr
len(size_t): len
length(size_t): length
flags(int): flags
Returns:
int
@@ -13,7 +13,7 @@ See 'man 2 mmap' for more information.
Arguments:
addr(void*): addr
len(size_t): len
length(size_t): length
prot(int): prot
flags(int): flags
fd(int): fd
@@ -13,7 +13,7 @@ See 'man 2 mprotect' for more information.
Arguments:
addr(void*): addr
len(size_t): len
length(size_t): length
prot(int): prot
Returns:
int
@@ -13,7 +13,7 @@ See 'man 2 msync' for more information.
Arguments:
addr(void*): addr
len(size_t): len
length(size_t): length
flags(int): flags
Returns:
int
@@ -13,7 +13,7 @@ See 'man 2 munlock' for more information.
Arguments:
addr(void*): addr
len(size_t): len
length(size_t): length
Returns:
int
</%docstring>
@@ -13,7 +13,7 @@ See 'man 2 munmap' for more information.
Arguments:
addr(void*): addr
len(size_t): len
length(size_t): length
Returns:
int
</%docstring>
@@ -14,7 +14,7 @@ See 'man 2 open' for more information.
Arguments:
file(char*): file
oflag(int): oflag
vararg(int): vararg
mode(int): mode
Returns:
int
</%docstring>
@@ -15,7 +15,7 @@ Arguments:
bus(unsigned): bus
dfn(unsigned): dfn
off(unsigned): off
len(unsigned): len
length(unsigned): length
buf(void*): buf
Returns:
int
@@ -15,7 +15,7 @@ Arguments:
bus(unsigned): bus
dfn(unsigned): dfn
off(unsigned): off
len(unsigned): len
length(unsigned): length
buf(void*): buf
Returns:
int
@@ -14,7 +14,7 @@ See 'man 2 readlink' for more information.
Arguments:
path(char*): path
buf(char*): buf
len(size_t): len
length(size_t): length
Returns:
ssize_t
</%docstring>
@@ -15,7 +15,7 @@ Arguments:
fd(int): fd
path(char*): path
buf(char*): buf
len(size_t): len
length(size_t): length
Returns:
ssize_t
</%docstring>
@@ -13,7 +13,7 @@ See 'man 2 set_robust_list' for more information.
Arguments:
head(robust_list_head*): head
len(size_t): len
length(size_t): length
Returns:
long
</%docstring>
@@ -13,7 +13,7 @@ See 'man 2 setdomainname' for more information.
Arguments:
name(char*): name
len(size_t): len
length(size_t): length
Returns:
int
</%docstring>
@@ -13,7 +13,7 @@ See 'man 2 sethostname' for more information.
Arguments:
name(char*): name
len(size_t): len
length(size_t): length
Returns:
int
</%docstring>
@@ -16,7 +16,7 @@ Arguments:
offin(off64_t*): offin
fdout(int): fdout
offout(off64_t*): offout
len(size_t): len
length(size_t): length
flags(unsigned): flags
Returns:
ssize_t
@@ -12,7 +12,7 @@ Invokes the syscall symlink.
See 'man 2 symlink' for more information.
Arguments:
from(char*): from
from_(char*): from
to(char*): to
Returns:
int
@@ -27,7 +27,7 @@ Returns:
can_pushstr = ['from', 'to']
can_pushstr_array = []
argument_names = ['from_', 'to']
argument_names = ['from', 'to']
argument_values = [from_, to]
# Load all of the arguments into their destination registers / stack slots.
@@ -12,7 +12,7 @@ Invokes the syscall symlinkat.
See 'man 2 symlinkat' for more information.
Arguments:
from(char*): from
from_(char*): from
tofd(int): tofd
to(char*): to
Returns:
@@ -28,7 +28,7 @@ Returns:
can_pushstr = ['from', 'to']
can_pushstr_array = []
argument_names = ['from_', 'tofd', 'to']
argument_names = ['from', 'tofd', 'to']
argument_values = [from_, tofd, to]
# Load all of the arguments into their destination registers / stack slots.
@@ -14,7 +14,7 @@ See 'man 2 tee' for more information.
Arguments:
fdin(int): fdin
fdout(int): fdout
len(size_t): len
length(size_t): length
flags(unsigned): flags
Returns:
ssize_t
+1 -1
View File
@@ -486,7 +486,7 @@ def init():
def readline(self, size = None):
return readline(size)
def __getattr__(self, k):
return self._fd.__getattribute__(k)
return getattr(self._fd, k)
sys.stdin = Wrapper(sys.stdin)
if six.PY2:
+1 -1
View File
@@ -139,7 +139,7 @@ def init():
def write(self, s):
output(s, frozen = True)
def __getattr__(self, k):
return self._fd.__getattribute__(k)
return getattr(self._fd, k)
if sys.stdout.isatty():
sys.stdout = Wrapper(sys.stdout)
if sys.stderr.isatty():
+3
View File
@@ -89,6 +89,9 @@ class Module(types.ModuleType):
return functools.partial(f, self)
def __getattr__(self, desc):
if desc.startswith('_'):
raise AttributeError(desc)
try:
ds = desc.replace('gray', 'bright_black').split('_')
init = ''
+1 -1
View File
@@ -4,7 +4,7 @@ from __future__ import division
from pwnlib.context import context
class Buffer(Exception):
class Buffer(object):
"""
List of strings with some helper routines.
+14 -13
View File
@@ -78,15 +78,13 @@ class listen(sock):
fam='any', typ='tcp', *args, **kwargs):
super(listen, self).__init__(*args, **kwargs)
port = int(port)
fam = self._get_family(fam)
typ = self._get_type(typ)
if fam == socket.AF_INET and bindaddr == '::':
bindaddr = '0.0.0.0'
h = self.waitfor('Trying to bind to %s on port %d' % (bindaddr, port))
h = self.waitfor('Trying to bind to %s on port %s' % (bindaddr, port))
for res in socket.getaddrinfo(bindaddr, port, fam, typ, 0, socket.AI_PASSIVE):
self.family, self.type, self.proto, self.canonname, self.sockaddr = res
@@ -109,7 +107,7 @@ class listen(sock):
break
else:
h.failure()
self.error("Could not bind to %s on port %d" % (bindaddr, port))
self.error("Could not bind to %s on port %s" % (bindaddr, port))
h.success()
@@ -159,15 +157,18 @@ class listen(sock):
self.sock
return self
def __getattr__(self, key):
if key == 'sock':
self._accepter.join(timeout = self.timeout)
if 'sock' in self.__dict__:
return self.sock
else:
return None
else:
return getattr(super(listen, self), key)
@property
def sock(self):
try:
return self.__dict__['sock']
except KeyError:
pass
self._accepter.join(timeout=self.timeout)
return self.__dict__.get('sock')
@sock.setter
def sock(self, s):
self.__dict__['sock'] = s
def close(self):
# since `close` is scheduled to run on exit we must check that we got
+7 -3
View File
@@ -608,7 +608,7 @@ class process(tube):
"""Permit pass-through access to the underlying process object for
fields like ``pid`` and ``stdin``.
"""
if hasattr(self.proc, attr):
if not attr.startswith('_') and hasattr(self.proc, attr):
return getattr(self.proc, attr)
raise AttributeError("'process' object has no attribute '%s'" % attr)
@@ -740,10 +740,14 @@ class process(tube):
# First check if we are already dead
self.poll()
#close file descriptors
# close file descriptors
for fd in [self.proc.stdin, self.proc.stdout, self.proc.stderr]:
if fd is not None:
fd.close()
try:
fd.close()
except IOError as e:
if e.errno != errno.EPIPE:
raise
if not self._stop_noticed:
try:
+3 -3
View File
@@ -61,7 +61,7 @@ class remote(sock):
*args, **kwargs):
super(remote, self).__init__(*args, **kwargs)
self.rport = int(port)
self.rport = port
self.rhost = host
if sock:
@@ -99,7 +99,7 @@ class remote(sock):
sock = None
timeout = self.timeout
with self.waitfor('Opening connection to %s on port %d' % (self.rhost, self.rport)) as h:
with self.waitfor('Opening connection to %s on port %s' % (self.rhost, self.rport)) as h:
for res in socket.getaddrinfo(self.rhost, self.rport, fam, typ, 0, socket.AI_PASSIVE):
self.family, self.type, self.proto, _canonname, sockaddr = res
@@ -123,7 +123,7 @@ class remote(sock):
raise
except socket.error:
pass
self.error("Could not connect to %s on port %d", self.rhost, self.rport)
self.error("Could not connect to %s on port %s", self.rhost, self.rport)
@classmethod
def fromsocket(cls, socket):
+7 -5
View File
@@ -72,8 +72,9 @@ class sock(tube):
raise
def settimeout_raw(self, timeout):
if getattr(self, 'sock', None):
self.sock.settimeout(timeout)
sock = getattr(self, 'sock', None)
if sock:
sock.settimeout(timeout)
def can_recv_raw(self, timeout):
"""
@@ -161,19 +162,20 @@ class sock(tube):
return True
def close(self):
if not getattr(self, 'sock', None):
sock = getattr(self, 'sock', None)
if not sock:
return
# Mark as closed in both directions
self.closed['send'] = True
self.closed['recv'] = True
self.sock.close()
sock.close()
self.sock = None
self._close_msg()
def _close_msg(self):
self.info('Closed connection to %s port %d', self.rhost, self.rport)
self.info('Closed connection to %s port %s', self.rhost, self.rport)
def fileno(self):
if not self.sock:
+33 -26
View File
@@ -519,13 +519,19 @@ class ssh_listener(sock):
_ = self.sock
return self
def __getattr__(self, key):
if key == 'sock':
while self._accepter.is_alive():
self._accepter.join(timeout = 0.1)
return self.sock
else:
return getattr(super(ssh_listener, self), key)
@property
def sock(self):
try:
return self.__dict__['sock']
except KeyError:
pass
while self._accepter.is_alive():
self._accepter.join(timeout=0.1)
return self.__dict__.get('sock')
@sock.setter
def sock(self, s):
self.__dict__['sock'] = s
class ssh(Timeout, Logger):
@@ -552,8 +558,8 @@ class ssh(Timeout, Logger):
_cwd = '.'
def __init__(self, user=None, host=None, port=22, password=None, key=None,
keyfile=None, proxy_command=None, proxy_sock=None,
level=None, cache=True, ssh_agent=False, ignore_config=False, *a, **kw):
keyfile=None, proxy_command=None, proxy_sock=None, level=None,
cache=True, ssh_agent=False, ignore_config=False, raw=False, *a, **kw):
"""Creates a new ssh connection.
Arguments:
@@ -570,6 +576,7 @@ class ssh(Timeout, Logger):
cache: Cache downloaded files (by hash/size/timestamp)
ssh_agent: If :const:`True`, enable usage of keys via ssh-agent
ignore_config: If :const:`True`, disable usage of ~/.ssh/config and ~/.ssh/authorized_keys
raw: If :const:`True`, assume a non-standard shell and don't probe the environment
NOTE: The proxy_command and proxy_sock arguments is only available if a
fairly new version of paramiko is used.
@@ -600,6 +607,7 @@ class ssh(Timeout, Logger):
self.keyfile = keyfile
self._cachedir = os.path.join(tempfile.gettempdir(), 'pwntools-ssh-cache')
self.cache = cache
self.raw = raw
# Deferred attributes
self._platform_info = {}
@@ -667,6 +675,9 @@ class ssh(Timeout, Logger):
h.success()
if self.raw:
return
self._tried_sftp = False
if self.sftp:
@@ -1523,29 +1534,24 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
local: Local directory
remote: Remote directory
"""
remote = remote or self.cwd
remote = packing._encode(remote or self.cwd)
if self.sftp:
remote = str(self.sftp.normalize(remote))
remote = packing._encode(self.sftp.normalize(remote))
else:
with context.local(log_level='error'):
remote = self.system('readlink -f ' + sh_string(remote))
remote = self.system(b'readlink -f ' + sh_string(remote))
basename = os.path.basename(remote)
local = local or '.'
local = os.path.expanduser(local)
local = local or '.'
local = os.path.expanduser(local)
self.info("Downloading %r to %r" % (basename,local))
self.info("Downloading %r to %r" % (remote, local))
with context.local(log_level='error'):
remote_tar = self.mktemp()
cmd = 'tar -C %s -czf %s %s' % \
cmd = b'tar -C %s -czf %s .' % \
(sh_string(remote),
sh_string(remote_tar),
sh_string(basename))
sh_string(remote_tar))
tar = self.system(cmd)
if 0 != tar.wait():
@@ -1686,10 +1692,8 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
local(str): Local path to store the data.
By default, uses the current directory.
"""
if not self.sftp:
self.error("Cannot determine remote file type without SFTP")
with self.system('test -d ' + sh_string(file_or_directory)) as io:
file_or_directory = packing._encode(file_or_directory)
with self.system(b'test -d ' + sh_string(file_or_directory)) as io:
is_dir = io.wait()
if 0 == is_dir:
@@ -1827,6 +1831,9 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
symlink = os.path.join(self.pwd(), b'*')
if not hasattr(symlink, 'encode') and hasattr(symlink, 'decode'):
symlink = symlink.decode('utf-8')
if isinstance(wd, six.text_type):
wd = packing._need_bytes(wd, 2, 0x80)
if not wd:
wd, status = self.run_to_end('x=$(mktemp -d) && cd $x && chmod +x . && echo $PWD', wd='.')
+20 -19
View File
@@ -19,7 +19,6 @@ from pwnlib.context import context
from pwnlib.log import Logger
from pwnlib.timeout import Timeout
from pwnlib.tubes.buffer import Buffer
from pwnlib.util import fiddling
from pwnlib.util import misc
from pwnlib.util import packing
@@ -155,15 +154,7 @@ class tube(Timeout, Logger):
if data and self.isEnabledFor(logging.DEBUG):
self.debug('Received %#x bytes:' % len(data))
if len(set(data)) == 1 and len(data) > 1:
self.indented('%r * %#x' % (data[0], len(data)), level = logging.DEBUG)
elif all(c in string.printable.encode() for c in data):
for line in data.splitlines(True):
self.indented(repr(line), level = logging.DEBUG)
else:
self.indented(fiddling.hexdump(data), level = logging.DEBUG)
self.maybe_hexdump(data, level=logging.DEBUG)
if data:
self.buffer.add(data)
@@ -204,14 +195,29 @@ class tube(Timeout, Logger):
Returns:
A bytes object containing bytes received from the socket,
or ``''`` if a timeout occurred while waiting.
Examples:
>>> t = tube()
>>> t.recv_raw = lambda n: b'abbbaccc'
>>> pred = lambda p: p.count(b'a') == 2
>>> t.recvpred(pred)
b'abbba'
>>> pred = lambda p: p.count(b'd') > 0
>>> t.recvpred(pred, timeout=0.05)
b''
"""
data = b''
with self.countdown(timeout):
while not pred(data):
if not self.countdown_active():
self.unrecv(data)
return b''
try:
res = self.recv(1)
res = self.recv(1, timeout=timeout)
except Exception:
self.unrecv(data)
return b''
@@ -225,7 +231,7 @@ class tube(Timeout, Logger):
return data
def recvn(self, numb, timeout = default):
"""recvn(numb, timeout = default) -> str
"""recvn(numb, timeout = default) -> bytes
Receives exactly `n` bytes.
@@ -767,13 +773,8 @@ class tube(Timeout, Logger):
if self.isEnabledFor(logging.DEBUG):
self.debug('Sent %#x bytes:' % len(data))
if len(set(data)) == 1:
self.indented('%r * %#x' % (data[0], len(data)))
elif all(c in string.printable.encode() for c in data):
for line in data.splitlines(True):
self.indented(repr(line), level = logging.DEBUG)
else:
self.indented(fiddling.hexdump(data), level = logging.DEBUG)
self.maybe_hexdump(data, level=logging.DEBUG)
self.send_raw(data)
def sendline(self, line=b''):
+32 -4
View File
@@ -197,6 +197,16 @@ def cyclic_find(subseq, alphabet = None, n = None):
>>> cyclic_find(0x61616162, endian='big')
1
Similarly to the case where you can provide a bytes value that is longer
than four bytes, you can provided an integer value that is larger than
what can be held in four bytes. If such a large value is given, it is
automatically truncated.
>>> cyclic_find(0x6161616361616162)
4
>>> cyclic_find(0x6261616163616161, endian='big')
4
You can use anything for the cyclic pattern, including non-printable
characters.
@@ -208,14 +218,32 @@ def cyclic_find(subseq, alphabet = None, n = None):
n = context.cyclic_size
if isinstance(subseq, six.integer_types):
if subseq >= 2**(8*n):
# Assumption: The user has given an integer that is more than 2**(8n) bits, but would otherwise fit within
# a register of size 2**(8m) where m is a multiple of four
notice = ("cyclic_find() expected an integer argument <= {cap:#x}, you gave {gave:#x}\n"
"Unless you specified cyclic(..., n={fits}), you probably just want the first {n} bytes.\n"
"Truncating the data at {n} bytes. Specify cyclic_find(..., n={fits}) to override this.").format(
cap=2**(8*n)-1,
gave=subseq,
# The number of bytes needed to represent subseq, rounded to the next 4
fits=int(round(float(subseq.bit_length()) / 32 + 0.5) * 32) // 8,
n=n,
)
log.warn_once(notice)
if context.endian == 'little':
subseq &= 2**(8*n) - 1
else:
while subseq >= 2**(8*n):
subseq >>= 8*n
subseq = packing.pack(subseq, bytes=n)
subseq = packing._need_bytes(subseq, 2, 0x80)
if len(subseq) != n:
log.warn_once("cyclic_find() expects %i-byte subsequences by default, you gave %r\n"
"Unless you specified cyclic(..., n=%i), you probably just want the first 4 bytes.\n"
"Truncating the data at 4 bytes. Specify cyclic_find(..., n=%i) to override this.",
n, subseq, len(subseq), len(subseq))
log.warn_once("cyclic_find() expected a %i-byte subsequence, you gave %r\n"
"Unless you specified cyclic(..., n=%i), you probably just want the first %d bytes.\n"
"Truncating the data at %d bytes. Specify cyclic_find(..., n=%i) to override this.",
n, subseq, len(subseq), n, n, len(subseq))
subseq = subseq[:n]
if alphabet is None:
+114 -1
View File
@@ -13,9 +13,11 @@ import string
from six import BytesIO
from six.moves import range
from pwnlib.context import LocalNoarchContext
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.term import text
from pwnlib.util import iters
from pwnlib.util import lists
from pwnlib.util import packing
from pwnlib.util.cyclic import cyclic
@@ -765,7 +767,7 @@ def hexdump(s, width=16, skip=True, hexii=False, begin=0, style=None,
Return a hexdump-dump of a string.
Arguments:
s(str): The data to hexdump.
s(bytes): The data to hexdump.
width(int): The number of characters per line
groupsize(int): The number of characters per group
skip(bool): Set to True, if repeated lines should be replaced by a "*"
@@ -971,3 +973,114 @@ def bnot(value, width=None):
width = context.bits
mask = ((1<<width)-1)
return mask ^ value
@LocalNoarchContext
def js_escape(data, padding=context.cyclic_alphabet[0:1], **kwargs):
r"""js_escape(data, padding=context.cyclic_alphabet[0:1], endian = None, **kwargs) -> str
Pack data as an escaped Unicode string for use in JavaScript's `unescape()` function
Arguments:
data (bytes): Bytes to pack
padding (bytes): A single byte to use as padding if data is of uneven length
endian (str): Endianness with which to pack the string ("little"/"big")
Returns:
A string representation of the packed data
>>> js_escape(b'\xde\xad\xbe\xef')
'%uadde%uefbe'
>>> js_escape(b'\xde\xad\xbe\xef', endian='big')
'%udead%ubeef'
>>> js_escape(b'\xde\xad\xbe')
'%uadde%u61be'
>>> js_escape(b'aaaa')
'%u6161%u6161'
"""
data = packing._need_bytes(data)
padding = packing._need_bytes(padding)
if len(padding) != 1:
raise ValueError("Padding must be a single byte")
if len(data) % 2:
data += padding[0:1]
data = bytearray(data)
if context.endian == 'little':
return ''.join('%u{a:02x}{b:02x}'.format(a=a, b=b) for b, a in iters.group(2, data))
else:
return ''.join('%u{a:02x}{b:02x}'.format(a=a, b=b) for a, b in iters.group(2, data))
@LocalNoarchContext
def js_unescape(s, **kwargs):
r"""js_unescape(s, endian = None, **kwargs) -> bytes
Unpack an escaped Unicode string from JavaScript's `escape()` function
Arguments:
s (str): Escaped string to unpack
endian (str): Endianness with which to unpack the string ("little"/"big")
Returns:
A bytes representation of the unpacked data
>>> js_unescape('%uadde%uefbe')
b'\xde\xad\xbe\xef'
>>> js_unescape('%udead%ubeef', endian='big')
b'\xde\xad\xbe\xef'
>>> js_unescape('abc%u4141123')
b'a\x00b\x00c\x00AA1\x002\x003\x00'
>>> data = b'abcdABCD1234!@#$\x00\x01\x02\x03\x80\x81\x82\x83'
>>> js_unescape(js_escape(data)) == data
True
>>> js_unescape('%u4141%u42')
Traceback (most recent call last):
ValueError: Incomplete Unicode token: %u42
>>> js_unescape('%u4141%uwoot%4141')
Traceback (most recent call last):
ValueError: Failed to decode token: %uwoot
>>> js_unescape('%u4141%E4%F6%FC%u4141')
Traceback (most recent call last):
NotImplementedError: Non-Unicode % tokens are not supported: %E4
>>> js_unescape('%u4141%zz%u4141')
Traceback (most recent call last):
ValueError: Bad % token: %zz
"""
s = packing._decode(s)
res = []
p = 0
while p < len(s):
if s[p] == '%':
if s[p+1] == "u":
# Decode Unicode token e.g. %u4142
n = s[p+2:p+6]
if len(n) < 4:
raise ValueError('Incomplete Unicode token: %s' % s[p:])
try:
n = int(n, 16)
except ValueError:
raise ValueError('Failed to decode token: %s' % s[p:p+6])
res.append(packing.p16(n))
p += 6
elif s[p+1] in string.hexdigits and s[p+2] in string.hexdigits:
# Decode Non-Unicode token e.g. %E4
raise NotImplementedError('Non-Unicode %% tokens are not supported: %s' % s[p:p+3])
else:
raise ValueError('Bad %% token: %s' % s[p:p+3])
else:
res.append(packing.p16(ord(s[p])))
p += 1
return b''.join(res)
+64 -9
View File
@@ -100,13 +100,13 @@ def size(n, abbrev = 'B', si = False):
return '%.02fP%s' % (n / base, abbrev)
KB = 1024
MB = 1024 * KB
GB = 1024 * MB
KB = 1000
MB = 1000 * KB
GB = 1000 * MB
KiB = 1000
MiB = 1000 * KB
GiB = 1000 * MB
KiB = 1024
MiB = 1024 * KiB
GiB = 1024 * MiB
def read(path, count=-1, skip=0):
r"""read(path, count=-1, skip=0) -> str
@@ -258,6 +258,8 @@ def run_in_new_terminal(command, terminal=None, args=None, kill_at_exit=True, pr
- If ``$TERM_PROGRAM`` is set, that is used.
- If X11 is detected (by the presence of the ``$DISPLAY`` environment
variable), ``x-terminal-emulator`` is used.
- If KDE Konsole is detected (by the presence of the ``$KONSOLE_VERSION``
environment variable), a terminal will be split.
- If WSL (Windows Subsystem for Linux) is detected (by the presence of
a ``wsl.exe`` binary in the ``$PATH`` and ``/proc/sys/kernel/osrelease``
containing ``Microsoft``), a new ``cmd.exe`` window will be opened.
@@ -297,14 +299,60 @@ def run_in_new_terminal(command, terminal=None, args=None, kill_at_exit=True, pr
elif 'DISPLAY' in os.environ and which('x-terminal-emulator'):
terminal = 'x-terminal-emulator'
args = ['-e']
elif 'KONSOLE_VERSION' in os.environ and which('qdbus'):
qdbus = which('qdbus')
window_id = os.environ['WINDOWID']
konsole_dbus_service = os.environ['KONSOLE_DBUS_SERVICE']
with subprocess.Popen((qdbus, konsole_dbus_service), stdout=subprocess.PIPE) as proc:
lines = proc.communicate()[0].decode().split('\n')
# Iterate over all MainWindows
for line in lines:
parts = line.split('/')
if len(parts) == 3 and parts[2].startswith('MainWindow_'):
name = parts[2]
with subprocess.Popen((qdbus, konsole_dbus_service, '/konsole/' + name,
'org.kde.KMainWindow.winId'), stdout=subprocess.PIPE) as proc:
target_window_id = proc.communicate()[0].decode().strip()
if target_window_id == window_id:
break
else:
log.error('MainWindow not found')
# Split
subprocess.run((qdbus, konsole_dbus_service, '/konsole/' + name,
'org.kde.KMainWindow.activateAction', 'split-view-left-right'), stdout=subprocess.DEVNULL)
# Find new session
with subprocess.Popen((qdbus, konsole_dbus_service, os.environ['KONSOLE_DBUS_WINDOW'],
'org.kde.konsole.Window.sessionList'), stdout=subprocess.PIPE) as proc:
session_list = map(int, proc.communicate()[0].decode().split())
last_konsole_session = max(session_list)
terminal = 'qdbus'
args = [konsole_dbus_service, '/Sessions/{}'.format(last_konsole_session),
'org.kde.konsole.Session.runCommand']
else:
is_wsl = False
if os.path.exists('/proc/sys/kernel/osrelease'):
with open('/proc/sys/kernel/osrelease', 'rb') as f:
is_wsl = b'icrosoft' in f.read()
if is_wsl and which('cmd.exe') and which('wsl.exe') and which('bash.exe'):
terminal = 'cmd.exe'
args = ['/c', 'start', 'bash.exe', '-c']
terminal = 'cmd.exe'
args = ['/c', 'start']
distro_name = os.getenv('WSL_DISTRO_NAME')
# Split pane in Windows Terminal
if 'WT_SESSION' in os.environ and which('wt.exe'):
args.extend(['wt.exe', '-w', '0', 'split-pane', '-d', '.'])
if distro_name:
args.extend(['wsl.exe', '-d', distro_name, 'bash', '-c'])
else:
args.extend(['bash.exe', '-c'])
if not terminal:
log.error('Could not find a terminal binary to use. Set context.terminal to your terminal.')
@@ -359,13 +407,20 @@ os.execve({argv0!r}, {argv!r}, os.environ)
if terminal == 'tmux':
out, _ = p.communicate()
pid = int(out)
elif terminal == 'qdbus':
with subprocess.Popen((qdbus, konsole_dbus_service, '/Sessions/{}'.format(last_konsole_session),
'org.kde.konsole.Session.processId'), stdout=subprocess.PIPE) as proc:
pid = int(proc.communicate()[0].decode())
else:
pid = p.pid
if kill_at_exit:
def kill():
try:
os.kill(pid, signal.SIGTERM)
if terminal == 'qdbus':
os.kill(pid, signal.SIGHUP)
else:
os.kill(pid, signal.SIGTERM)
except OSError:
pass
+1 -1
View File
@@ -1040,7 +1040,7 @@ def _need_text(s, level=1):
if encoding == 'auto':
for encoding in 'ASCII', 'UTF-8', 'ISO-8859-1':
try:
s = s.decode(encoding)
s.decode(encoding)
except UnicodeDecodeError:
pass
else:
+4 -5
View File
@@ -333,9 +333,9 @@ def wait_for_debugger(pid, debugger_pid=None):
t = Timeout()
with t.countdown(timeout=15):
with log.waitfor('Waiting for debugger') as l:
while debugger_pid:
debugger = psutil.Process(debugger_pid)
while t.timeout and tracer(pid) is None:
while t.timeout and tracer(pid) is None:
if debugger_pid:
debugger = psutil.Process(debugger_pid)
try:
debugger.wait(0.01)
except psutil.TimeoutExpired:
@@ -343,8 +343,7 @@ def wait_for_debugger(pid, debugger_pid=None):
else:
debugger_pid = 0
break
else:
while t.timeout and tracer(pid) is None:
else:
time.sleep(0.01)
if tracer(pid):
+2
View File
@@ -6,6 +6,7 @@ _const_codes = [
'BUILD_CONST_KEY_MAP', 'BUILD_STRING',
'LOAD_CONST','RETURN_VALUE','STORE_SUBSCR', 'STORE_MAP',
'LIST_TO_TUPLE', 'LIST_EXTEND', 'SET_UPDATE', 'DICT_UPDATE', 'DICT_MERGE',
'COPY', 'RESUME',
]
_expr_codes = _const_codes + [
@@ -15,6 +16,7 @@ _expr_codes = _const_codes + [
'BINARY_MODULO','BINARY_ADD','BINARY_SUBTRACT',
'BINARY_LSHIFT','BINARY_RSHIFT','BINARY_AND','BINARY_XOR',
'BINARY_OR',
'BINARY_OP',
]
_values_codes = _expr_codes + ['LOAD_NAME']
+1 -1
View File
@@ -1 +1 @@
__version__ = '4.7.0'
__version__ = '4.9.0'
+1 -1
View File
@@ -89,7 +89,7 @@ setup(
name = 'pwntools',
python_requires = '>=2.7',
packages = find_packages(),
version = '4.7.0',
version = '4.9.0',
data_files = [('pwntools-doc',
glob.glob('*.md') + glob.glob('*.txt')),
],