Compare commits

..
52 Commits
Author SHA1 Message Date
Rahulandrahul05ranjan 459dfd7e35 fix(regsort): drop self-assignments through register aliases (#2795)
* fix(regsort): drop self-assignments through register aliases

The self-assignment drop compared register names only (k != v), so an
assignment through an alias (e.g. {'ax': 'eax'} with registers.native32)
was not recognized as a no-op.  It then surfaced as a spurious
1-register dependency cycle, which either raised PwnlibException with
xchg=False or emitted a useless 'mov' of a register into its own alias.

With xchg=False and other registers present, the bogus cycle also
forced the temporary-register code path, reordering unrelated moves and
silently leaving registers with wrong values (see the linked issue).

Compare the mapped names instead, like the rest of the function.

Fixes #2794

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

* docs(changelog): add regsort register-alias self-assignment fix entry

---------

Co-authored-by: Rahul Ranjan <[email protected]>
2026-10-30 16:20:25 +01:00
peace-maker d8d47977ac gdb.corefile: Run gdb directly to prevent detach race (#2787)
* gdb.corefile: Run gdb directly to prevent detach race

`attach()` waits for the debugger to attach by polling the `TracerPid` regularly. `corefile()` runs `detach` in gdb after dumping the core, so the debugger might attach and detach between two `TracerPid` polls and `wait_for_debugger()` thinks gdb exited before attaching and returns `None` as gdb's pid is no longer valid.

Later `corefile()` ran `psutil.Process(pid).wait()` with `pid = None` which effectively waited on the Python process itself to exit.
This caused a hang in the doctest CI sometimes, which was killed by the watchdog timer eventually.

Run `gdb` directly and just observe the exit code instead of relying on the more advanced `attach` logic for longer running debug sessions.

* Update CHANGELOG
2026-10-26 06:15:28 +00:00
Max Freedom Pollardandpeace-maker 3daf28b6be fix(packing): dd() raises TypeError on a bytearray destination (#2772)
* fix(packing): dd() raises TypeError on a bytearray destination

dd() documents bytearray as a supported destination type, but
dd(bytearray(b'Hello!'), b'?', skip=5) raises
"TypeError: 'bytes' object cannot be interpreted as an integer".

pwnlib/util/packing.py:1108 handled list and bytearray destinations in a
single branch and filled both with list(map(p8, bytearray(src))). p8()
returns a length-1 bytes object, which is what a list destination is
documented to hold (see the existing dd(list('Hello!'), (63,), skip=5)
doctest), but a bytearray slice assignment needs an iterable of int, so
every bytearray destination crashed.

Assign the byte string src directly when the destination is a bytearray.
Every src branch above the destination dispatch (packing.py:1039-1089)
has already reduced src to a byte string, so the slice assignment is
always valid. This also makes a bytearray destination agree with the
bytes and str destinations, which already copy src verbatim.

Add a doctest covering the returned value and the in-place update of the
caller's bytearray.

* Add changelog entry for #2772

---------

Co-authored-by: peace-maker <[email protected]>
2026-10-24 12:15:03 +02:00
h4kk4p4kk4andpeace-maker f4b3e6e1c4 fix(elf): prevent elf.libc returning the executable when its name starts with libc[.-] (#2781)
Co-authored-by: peace-maker <[email protected]>
2026-10-24 12:12:47 +02:00
Max Freedom PollardandPeace-Maker 2b39684912 Disable debuginfod doctests in libcdb.unstrip_libc on stable (#2773)
* Disable debuginfod tests in libcdb.unstrip_libc

The external debuginfod servers are really slow or won't respond at all like debuginfod.ubuntu.com.
The caching in CI doesn't help since we only cache it for 3 month and the query fails afterwards most of the time. We cache the negative response too which doesn't help if the service is flakey itself :D

(cherry picked from commit 86cafce196)

* Add changelog entry for #2773

---------

Co-authored-by: Peace-Maker <[email protected]>
2026-10-21 20:04:53 +02:00
peace-maker 7e5e79d00f Allow running with Unicorn 2.1.[34] but throw when emulating MIPS (#2721)
* Allow running with Unicorn 2.1.[34] but throw when emulating MIPS

Unicorn 2.1.5 wasn't released for a while and 2.1.2 doesn't have binary wheels for Python 3.14.
This made installing pwntools on newer Python versions more annoying since you had to build Unicorn yourself.

The only problem with the blocked versions is MIPS emulation, so only avoid that while allowing to use Unicorn 2.1.3 or 2.1.4 for other architectures.

Refs #2621

* Update CHANGELOG
2026-04-27 18:23:01 +02:00
0xDanielandpeace-maker e551b4ae56 fix: apply force_to_bytes to readline result in non-term_mode path (#2707)
Co-authored-by: peace-maker <[email protected]>
2026-04-07 11:28:48 +02:00
43ef1b494c Fix _build_date() crash on non-standard locale date strings (#2701)
* Fix _build_date() crash on non-standard locale date strings (#2513)

Some Android devices return locale-dependent strings in ro.build.date
(e.g. '2017年 11月 14日 星期二 09:55:07 CST' for Chinese locale) that
dateutil.parser.parse() cannot handle, causing wait_for_device() to
crash with a ParserError.

Fix: prefer ro.build.date.utc (integer epoch timestamp) which is
locale-independent and available on all Android devices. Fall back
to ro.build.date with error handling if the UTC property is missing.

* Add CHANGELOG entry for #2513 fix

* Simplify _build_date(): prefer ro.build.date.utc, keep minimal fallback

Address review: ro.build.date.utc is set by the AOSP build system
and available on all standard Android devices. Simplified comments
to reflect this is the primary path, with ro.build.date kept only
as a fallback for non-standard builds.

* Move CHANGELOG entry

* Fix Python 2.7 compatibility

* Disable intro doctest connecting to `ftp.ubuntu.com`

The FTP server is throttled in CI or just really slow. We already test the basic tcp tubes in the `remote` tests.

---------

Co-authored-by: coder999999999 <[email protected]>
Co-authored-by: peace-maker <[email protected]>
2026-04-07 11:09:01 +02:00
be92aa1b4c fix: pad bytes fields to correct field size in FileStructure (#2694)
* fix: pad bytes fields to correct field size in FileStructure

When setting bytes fields (e.g. _IO_save_base) on FileStructure, the
value was padded to context.bytes instead of the actual field size from
self.length[val]. This caused struct.pack errors for fields whose size
differs from the pointer size.

Fixes #2693

* Cleanup test and add CHANGELOG

---------

Co-authored-by: nightcityblade <[email protected]>
Co-authored-by: Peace-Maker <[email protected]>
2026-04-05 20:37:31 +00:00
Peace-Maker 07f3caadc4 Stay with pylint < 4.0 until recursion errors are fixed
https://github.com/pylint-dev/pylint/issues/9190
2025-10-16 16:17:08 +02:00
Peace-Maker 3ad86ec5ad Release 4.15.0 2025-10-12 16:48:05 +02:00
Arusekk b5076d9262 Merge branch 'stable' into beta 2025-09-26 09:54:54 +02:00
ArusekkandHackrrr 77b4f06b07 docs: warn that .libs/.libc/.maps are dangerous (#2626)
Co-authored-by: Hackrrr <[email protected]>
2025-09-26 07:36:34 +00:00
Arusekk 4968499d11 ffix: ugly ELF docs (whitespace only) 2025-09-26 09:20:53 +02:00
Peace-Maker f38991bc02 Merge branch 'stable' into beta 2025-09-16 21:41:00 +02:00
peace-maker d0e6ee4b37 Fix regression: gdbserver can't handle command-line argument containing whitespace (#2624)
* Fix regression: gdbserver can't handle command-line argument containing whitespace

The `--no-startup-with-shell` argument was passed in an attempt to
fix gdbserver startup with `SHELL=/bin/fish`. (#2377) It caused a regression
which disallowed passing arguments containing whitespace to the debuggee in `gdb.debug`.

gdbserver would exit with a message:
"can't handle command-line argument containing whitespace"

There is a 3 second timeout waiting for gdbserver to startup and
printing a message with a workaround already suggesting to set
`SHELL=/bin/bash` to allow gdbserver to launch. This has to be
our best effort to guide users of such shells until we find time
to investigate a better solution.

Fixes #2550

* Update CHANGELOG
2025-09-16 21:06:29 +02:00
peace-makerandArusekk 48ea09b27f Fix lookup of binutils for mipsel architecture (#2612)
* Fix lookup of binutils for `mipsel` architecture

The endianness is encoded in the architecture name for mips targets which wasn't respected while looking for the correct binutils binary.

Sync the CI and Dockerfile binutils packages while we're at it.

Fixes #2591

* Update CHANGELOG

* still fall back to the other endianness

---------

Co-authored-by: Arusekk <[email protected]>
2025-09-11 13:09:28 +00:00
Arusekk 9f22a2f202 Merge branch 'stable' into beta 2025-09-11 14:55:56 +02:00
Arusekk 7413a3da31 Exclude Unicorn versions segfaulting on MIPS PLT (#2621)
Fixes #2619
2025-09-11 12:55:51 +00:00
Peace-Maker 95aac6e367 Merge branch 'stable' into beta 2025-09-05 13:59:45 +02:00
Peace-Maker 9ed93a5281 Fix race condition in process mapping tests
We could try to lookup the mapped libraries before they are actually loaded in the process.
Wait until the program is up and running before trying to fetch the memory maps.
2025-09-05 13:14:57 +02:00
peace-maker 3579fb1384 Fix log level of child remotes of server tube (#2609)
* Fix log level of child remotes of `server` tube

The log level of the `server` tube wasn't passed
on to the `remote` wrappers created upon receiving
a new child connection. This caused the connection
closed message to be logged even when the server
object was created with `level='error'`.

* Update CHANGELOG
2025-09-05 12:25:49 +02:00
Peace-Maker 485bf0a0b7 Merge branch 'stable' into beta 2025-08-28 14:36:00 +02:00
William Tan b133b76b4e Allow setting debugger path via context.gdb_binary (#2602)
* Allow setting debugger path via context

* Update changelog
2025-08-19 10:30:05 +02:00
ffc46d277a Fix poll error in process.libs() and clean up maps parsing (#2579)
Co-authored-by: Raccoon <raccoon@42>
Co-authored-by: peace-maker <[email protected]>
2025-08-09 14:47:57 +00:00
Peace-Maker dd3a5f6974 libcdb: Fix cache handling of debuginfo
When trying multiple debuginfod servers, the first one to fail would save the failed attempt and block the remaining servers to be tried.

Only save that we couldn't find the debuginfo if we tried all the configured servers.
2025-08-06 14:32:08 +02:00
Peace-Maker 9a275b2db0 Fix libcdb cache in CI
Requests to debuginfod.elfutils.org had the wrong SNI in their request.

Cache requests to debuginfod.ubuntu.com too.
2025-08-06 12:16:10 +02:00
Peace-Maker 1c28222fe6 Merge remote-tracking branch 'stable' into beta-staging 2025-08-05 21:47:13 +02:00
peace-maker b93c5ace10 Ignore colored_traceback error when TERM is unset (#2596)
* Ignore colored_traceback error when TERM is unset

Fixes using pwntools in non-interactive sage scripts with newer colored_traceback versions.

Fixes #2594

* Update CHANGELOG
2025-08-05 21:25:21 +02:00
peace-maker da9797c1c2 Support longer function names in Windows getexport shellcode (#2587)
* Support longer function names in Windows getexport shellcode

Previously, an arbitrary name length limit of 8 characters was in place due to the lack of a proper strcmp loop. Implement a cheap strcmp for longer function names.
This trashes a bunch of registers and we might be able to optimize it further to reduce the damage.

Use a unique label too to avoid redefinition when using the shellcraft template twice in the same shellcode. Use the mov shellcraft template for the destination register to avoid an instruction if the result is already in the desired register.

* Update CHANGELOG
2025-08-05 14:55:35 +00:00
Shordenandpeace-maker 6dfe8b6f72 Use unicorn on macOS w/ SIP enabled (#2593)
* __ensure_memory_to_run_unicorn: don't request wx memory

macOS systems with SIP enabled prohibit mmaping WX memory.
unicorn doesn't run into this issue because it uses the MAP_JIT
protection flag.

See also:
https://github.com/unicorn-engine/unicorn/blob/803fe5a47770360d1942de72600d3b295b521e31/qemu/accel/tcg/translate-all.c#L1021-L1032
https://github.com/pwndbg/pwndbg/blob/2f6b5bbaea484391f55850a09a16efda161498f2/pwndbg/aglib/disasm/__init__.py#L305-L316

* update changelog

---------

Co-authored-by: peace-maker <[email protected]>
2025-08-05 11:39:28 +00:00
peace-maker 425221fb30 Add Ubuntu and Debian debuginfod servers (#2595)
* Add Ubuntu and Debian debuginfod servers

Most CTF challenges are hosted on Ubuntu or Debian [citation needed], so check their debuginfod servers before trying the federating one as a fallback.

This is required since the elfutils.org server seems down for maintenance occasionally.

* Update CHANGELOG
2025-08-05 13:02:00 +02:00
Chaitanya RahalkarandPeace-Maker 004a6dab88 Add automatic detection of statically linked binaries in detect_missing_binaries function. (#2570)
* Add automatic detection of statically linked binaries in `detect_missing_binaries` function.

* Add entry for PR #2570 to CHANGELOG.md: fix `pwn template` for statically linked binaries.

* Remove partial test implementation

The commands should be tested end-to-end instead of using doctests (later).

---------

Co-authored-by: Peace-Maker <[email protected]>
2025-07-11 13:10:40 +02:00
Arusekk 6098f28bfa Merge branch 'stable' into beta 2025-04-02 18:00:52 +02:00
Arusekk 1205b1aaa3 regsort: respect register aliases (#2576)
Closes #2481
2025-04-02 15:57:59 +00:00
tesujiandArusekk ce6d2ca847 Fix invalid parsing of loader error message in elf.libc (#2567)
For example, on my machine:

  > ./main_no_flag
  ./main_no_flag: /lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.32' not found (required by ./main_no_flag)

Co-authored-by: Arusekk <[email protected]>
2025-04-02 14:17:30 +02:00
Peace-Maker 9e0ca3a415 Move changelog entry to stable log 2025-03-30 12:21:08 +02:00
Daniel Kilimnik b60b2e897f SSH: fix download/upload with -1 exit status (#2545)
* SSH: fix download/upload with -1 exit status

* Add Changelog
2025-03-30 10:19:42 +00:00
Arusekk 2c3e783f96 Release 4.15.0beta1 2025-03-24 13:21:16 +01:00
Arusekk 15bdd4a766 Release 4.14.1 2025-03-24 13:01:50 +01:00
Arusekk 7c95e838f4 Merge branch 'stable' into beta 2025-03-24 12:15:36 +01:00
Maxime Meignan 92aa64ce01 Fixed bound on memcpy loop in thumb,arm,aarch64 (#2552) 2025-03-24 10:30:14 +00:00
peace-maker ae4bb08c3e Fix syntax highlighting of multiline comments in ASM (#2562)
Enable DOT_ALL next to the default MULTILINE regex flags to match newlines too with the `.*?`.
```
/* this works */
/*
this is not highlighted as a comment
*/
```
2025-03-21 20:55:23 +00:00
6739525ebe Show ELF symbols defined to value 0 (#2451)
Lets say we have an ELF with the following symbols

```
Symbol table '.symtab' contains 5 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000035     0 NOTYPE  LOCAL  DEFAULT    2 aaaa
     2: 0000000000000022     0 NOTYPE  LOCAL  DEFAULT    2 bbbb
     3: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT    2 cccc
     4: 0000000000000054     0 NOTYPE  GLOBAL DEFAULT    2 dddd
```

Then pwnlib's ELF(binary).symbols will be `{'aaaa': 53, 'bbbb': 34, 'dddd': 84}`.
This is missing the symbol `cccc`, because its value is 0.

This change checks the name instead of the value, because the value can
be 0 if the symbol points to the beginning.

The new and correct value of pwnlib's ELF(binary).symbols will be
`{'aaaa': 53, 'cccc': 0, 'bbbb': 34, 'dddd': 84}`.

* Do not unresolve zeros
* Do not add undefined symbols

Co-authored-by: peace-maker <[email protected]>
Co-authored-by: Arusekk <[email protected]>
2025-03-21 11:34:33 +01:00
Arusekk d10b7aee4c Exclude broken Unicorn version (#2565)
Closes #2560
2025-03-19 08:47:55 +00:00
Arusekk aeb4c26bf0 Fix parsing corefile with missing auxv (#2559)
Closes #2543
2025-03-03 16:01:05 +00:00
Peace-Maker ec6b8cd87c Merge branch 'stable' into beta 2025-01-26 23:47:08 +01:00
Meetesh SainiandPeace-Maker b2345f214e fix: update apport coredump path handling for CorefileFinder (#2518)
* fix: update apport coredump path handling for CorefileFinder

* chore: update comment on Apport core path handling

* Update CHANGELOG

---------

Co-authored-by: Peace-Maker <[email protected]>
2025-01-26 22:28:14 +00:00
peace-maker e9d9bd9ddb Fix installation on Python 3.5 and lower on stable (#2533)
* Fix installation on Python 3.5 and lower on stable

The unix-ar package requires Python 3.6. Only install it on Python 3.6 then and use the Python 2 fallback for Python <= 3.5 too.

* Update CHANGELOG
2025-01-26 23:18:16 +01:00
Peace-Maker d7a7376eca Merge branch 'stable' into beta 2025-01-15 20:34:56 +01:00
Peace-Maker 9472ec1e93 Fix building docs on stable
Pin to Python 3.12.
2025-01-15 16:49:27 +01:00
peace-maker e4b939dc1a Poll before reading /proc/maps in process.libs() (#2516)
Try to avoid race condition of reading memory maps before libraries are loaded.
2025-01-15 16:49:02 +01:00
40 changed files with 615 additions and 255 deletions

No files matched your search

+6 -2
View File
@@ -22,8 +22,9 @@ jobs:
- 3001:3001 # https://libc.rip/ proxy cache
- 3002:3002 # http://archive.ubuntu.com/ proxy cache
- 3003:3003 # https://gitlab.com/ proxy cache
- 3004:3004 # https://debuginfod.ubuntu.com/ proxy cache
env:
DEBUGINFOD_URLS: http://localhost:3000/
DEBUGINFOD_URLS: "http://localhost:3000/ http://localhost:3004/"
PWN_LIBCRIP_URL: http://localhost:3001/
PWN_UBUNTU_ARCHIVE_URL: http://localhost:3002/
PWN_GITLAB_LIBCDB_URL: http://localhost:3003/
@@ -120,6 +121,7 @@ jobs:
binutils-aarch64-linux-gnu \
binutils-arm-linux-gnueabihf \
binutils-mips-linux-gnu \
binutils-mipsel-linux-gnu \
binutils-msp430 \
binutils-powerpc-linux-gnu \
binutils-s390x-linux-gnu \
@@ -148,6 +150,8 @@ jobs:
pip install --upgrade wheel build
pip install --upgrade flake8 appdirs
pip install --upgrade --editable .
# Pin to known working version until 2.1.5 is out. https://github.com/unicorn-engine/unicorn/issues/2134
pip install 'unicorn==2.1.2'
- name: Sanity checks
run: PWNLIB_NOTERM=1 python -bb -c 'from pwn import *; print(pwnlib.term.term_mode)'
@@ -283,7 +287,7 @@ jobs:
run: |
pip install --upgrade pip
pip install --upgrade --editable .
- name: Sanity checks
run: |
python -bb -c 'from pwn import *'
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
- name: PyLint
run: |
set -x
pip install pylint
pip install 'pylint<4'
pip install --upgrade -e .
run_pylint() { pylint --exit-zero --errors-only pwnlib -f parseable | cut -d ' ' -f2- | sed 's/line [0-9]\+/line XXXX/g'; }
run_pylint > current.txt
+1 -1
View File
@@ -5,7 +5,7 @@ version: 2
build:
os: ubuntu-22.04
tools:
python: "3"
python: "3.12"
sphinx:
configuration: docs/source/conf.py
+124 -7
View File
@@ -10,8 +10,9 @@ The table below shows which release corresponds to each branch, and what date th
| Version | Branch | Release Date |
| ---------------- | -------- | ---------------------- |
| [5.0.0](#500-dev) | `dev` |
| [4.15.0](#4150-beta) | `beta` |
| [4.14.0](#4140-stable) | `stable` | Jan 15, 2025
| [4.15.0](#4150-stable) | `stable` | Oct 12, 2025
| [4.14.1](#4141) | | Mar 24, 2025
| [4.14.0](#4140) | | Jan 15, 2025
| [4.13.1](#4131) | | Sep 29, 2024
| [4.13.0](#4130) | | Aug 12, 2024
| [4.12.0](#4120) | | Feb 22, 2024
@@ -74,8 +75,80 @@ The table below shows which release corresponds to each branch, and what date th
## 5.0.0 (`dev`)
- [#2598][2598] aarch64: Fix ABI definition
- [#2419][2419] riscv: avoid compressed instructions (if you need compressed, use .option rvc)
- [#2551][2551] Detect when kitty is being used as terminal
- [#2519][2519] Drop Python 2.7 support / Require Python 3.10
- [#2507][2507] Add `+LINUX` and `+WINDOWS` doctest options and start proper testing on Windows
- [#2522][2522] Support starting a kitty debugging window with the 'kitten' command
- [#2524][2524] Raise EOFError during `process.recv` when stdout closes on Windows
- [#2526][2526] Properly make use of extra arguments in `packing` utilities. `sign` parameter requires keyword syntax to specify it.
- [#2517][2517] Allow to passthru kwargs on `ssh.__getattr__` convenience function to fix SSH motd problems
- [#2530][2530] Do NOT error when passing directory arguments in `checksec` commandline tool.
- [#2529][2529] Add LoongArch64 support
- [#2506][2506] ROP: fix `ROP(ELF(exe)).leave` is `None` in some ELF
- [#2504][2504] doc: add example case for `tuple` (host, port pair) in `gdb.attach`
- [#2546][2546] ssh: Allow passing disabled_algorithms keyword argument from ssh to paramiko
- [#2538][2538] Add `ssh -L` / `ssh.connect_remote()` workaround when `AllowTcpForwarding` is disabled
- [#2574][2574] Allow creating an ELF from in-memory bytes
- [#2575][2575] Detect when Terminator is being used as terminal
- [#2578][2578] Add gnome-terminal, Alacritty, Ttilix for run_in_new_terminal
- [#2590][2590] Add support for finding corefiles under WSL2
- [#2496][2496] Add linux ko file search support
- [#2542][2542] Decode `_IO_*` flags in `FileStructure` member
- [#2592][2592] pwnlib.config: Fix customization of `context.timeout`
- [#2608][2608] Abort on `libcdb file libc.so --unstrip` if eu-unstrip is not installed
- [#2611][2611] Cleanup `pwnlib.lexer` exports and imports
- [#2610][2610] Fix `log.progress` ignoring `context.log_console`
- [#2615][2615] tube/process: Fix redirecting stderr to stdout on Windows
[2598]: https://github.com/Gallopsled/pwntools/pull/2598
[2419]: https://github.com/Gallopsled/pwntools/pull/2419
[2551]: https://github.com/Gallopsled/pwntools/pull/2551
[2519]: https://github.com/Gallopsled/pwntools/pull/2519
[2507]: https://github.com/Gallopsled/pwntools/pull/2507
[2522]: https://github.com/Gallopsled/pwntools/pull/2522
[2524]: https://github.com/Gallopsled/pwntools/pull/2524
[2526]: https://github.com/Gallopsled/pwntools/pull/2526
[2517]: https://github.com/Gallopsled/pwntools/pull/2517
[2530]: https://github.com/Gallopsled/pwntools/pull/2530
[2529]: https://github.com/Gallopsled/pwntools/pull/2529
[2506]: https://github.com/Gallopsled/pwntools/pull/2506
[2504]: https://github.com/Gallopsled/pwntools/pull/2504
[2546]: https://github.com/Gallopsled/pwntools/pull/2546
[2538]: https://github.com/Gallopsled/pwntools/pull/2538
[2574]: https://github.com/Gallopsled/pwntools/pull/2574
[2575]: https://github.com/Gallopsled/pwntools/pull/2575
[2578]: https://github.com/Gallopsled/pwntools/pull/2578
[2590]: https://github.com/Gallopsled/pwntools/pull/2590
[2496]: https://github.com/Gallopsled/pwntools/pull/2496
[2542]: https://github.com/Gallopsled/pwntools/pull/2542
[2592]: https://github.com/Gallopsled/pwntools/pull/2592
[2608]: https://github.com/Gallopsled/pwntools/pull/2608
[2611]: https://github.com/Gallopsled/pwntools/pull/2611
[2610]: https://github.com/Gallopsled/pwntools/pull/2610
[2615]: https://github.com/Gallopsled/pwntools/pull/2615
## 4.15.1
- [#2694][2694] fix: pad bytes fields to correct field size in FileStructure
- [#2701][2701] Fix `adb._build_date()` crash on devices with non-standard locale date strings
- [#2721][2721] Allow running with Unicorn 2.1.[34] but throw when emulating MIPS
- [#2773][2773] Disable debuginfod doctests in libcdb.unstrip_libc
- [#2772][2772] fix(packing): `dd()` no longer crashes on a `bytearray` destination
- [#2787][2787] gdb.corefile: Run gdb directly to prevent detach race
- [#2795][2795] regsort: Fix self-assignment detection through register aliases
[2694]: https://github.com/Gallopsled/pwntools/pull/2694
[2701]: https://github.com/Gallopsled/pwntools/pull/2701
[2721]: https://github.com/Gallopsled/pwntools/pull/2721
[2773]: https://github.com/Gallopsled/pwntools/pull/2773
[2772]: https://github.com/Gallopsled/pwntools/pull/2772
[2787]: https://github.com/Gallopsled/pwntools/pull/2787
[2795]: https://github.com/Gallopsled/pwntools/pull/2795
## 4.15.0 (`stable`)
## 4.15.0 (`beta`)
- [#2508][2508] Ignore a warning when compiling with asm on nix
- [#2471][2471] Properly close spawned kitty window
- [#2358][2358] Cache output of `asm()`
@@ -93,6 +166,19 @@ The table below shows which release corresponds to each branch, and what date th
- [#2502][2502] Fix loading ELF files without valid .dynamic section
- [#2476][2476] Deprecate 'keepends' argument in favor of 'drop' in `tube.recvline*`
- [#2364][2364] Deprecate direct commandline scripts invocation and exclude nonsense ones
- [#2570][2570] Fix `pwn template` to skip Docker library extraction for statically linked binaries
- [#2545][2545] SSH: fix download/upload with -1 exit status
- [#2567][2567] Fix mistakenly parsing of ld-linux error messages.
- [#2576][2576] regsort: respect register aliases
- [#2595][2595] libcdb: Add Ubuntu and Debian debuginfod servers to default list
- [#2593][2593] Use unicorn on macOS w/ SIP enabled
- [#2587][2587] Support longer function names in Windows `getexport` shellcode
- [#2596][2596] Ignore `colored_traceback` error when TERM envvar is unset
- [#2579][2579] Fix poll error in `process.libs()` and clean up maps parsing
- [#2602][2602] Allow setting debugger path via context.gdb_binary
- [#2609][2609] Fix log level of child remotes of `server` tube
- [#2612][2612] Fix lookup of binutils for `mipsel` architecture
- [#2624][2624] Fix regression: gdbserver can't handle command-line argument containing whitespace
[2508]: https://github.com/Gallopsled/pwntools/pull/2508
[2471]: https://github.com/Gallopsled/pwntools/pull/2471
@@ -111,8 +197,39 @@ The table below shows which release corresponds to each branch, and what date th
[2502]: https://github.com/Gallopsled/pwntools/pull/2502
[2476]: https://github.com/Gallopsled/pwntools/pull/2476
[2364]: https://github.com/Gallopsled/pwntools/pull/2364
[2570]: https://github.com/Gallopsled/pwntools/pull/2570
[2545]: https://github.com/Gallopsled/pwntools/pull/2545
[2567]: https://github.com/Gallopsled/pwntools/pull/2567
[2576]: https://github.com/Gallopsled/pwntools/pull/2576
[2595]: https://github.com/Gallopsled/pwntools/pull/2595
[2593]: https://github.com/Gallopsled/pwntools/pull/2593
[2587]: https://github.com/Gallopsled/pwntools/pull/2587
[2596]: https://github.com/Gallopsled/pwntools/pull/2596
[2579]: https://github.com/Gallopsled/pwntools/pull/2579
[2602]: https://github.com/Gallopsled/pwntools/pull/2602
[2609]: https://github.com/Gallopsled/pwntools/pull/2609
[2612]: https://github.com/Gallopsled/pwntools/pull/2612
[2624]: https://github.com/Gallopsled/pwntools/pull/2624
## 4.14.0 (`stable`)
## 4.14.1
- [#2451][2451] Show symbols defined to value 0 (start of file)
- [#2533][2533] Fix installation on Python 3.5 and lower
- [#2518][2518] fix: update apport coredump path handling for CorefileFinder
- [#2552][2552] Fix memcpy shellcraft template off-by-one
- [#2559][2559] Fix parsing corefile with missing auxv
- [#2562][2562] Fix syntax highlighting of multiline comments in ASM
- [#2565][2565] Exclude broken Unicorn
[2451]: https://github.com/Gallopsled/pwntools/pull/2451
[2533]: https://github.com/Gallopsled/pwntools/pull/2533
[2518]: https://github.com/Gallopsled/pwntools/pull/2518
[2552]: https://github.com/Gallopsled/pwntools/pull/2552
[2559]: https://github.com/Gallopsled/pwntools/pull/2559
[2562]: https://github.com/Gallopsled/pwntools/pull/2562
[2565]: https://github.com/Gallopsled/pwntools/pull/2565
## 4.14.0
- [#2356][2356] Add local libc database provider for libcdb
- [#2360][2360] Add offline parameter for `search_by_hash` series function
@@ -183,7 +300,7 @@ The table below shows which release corresponds to each branch, and what date th
- [#2308][2308] Fix WinExec shellcraft to make sure it's 16 byte aligned
- [#2279][2279] Make `pwn template` always set context.binary
- [#2310][2310] Add support to start a process on Windows
- [#2335][2335] Add lookup optimizations in DynELF
- [#2335][2335] Add lookup optimizations in DynELF
- [#2334][2334] Speed up disasm commandline tool with colored output
- [#2328][2328] Lookup using $PATHEXT file extensions in `which` on Windows
- [#2189][2189] Explicitly define p64/u64 functions for IDE support
@@ -270,7 +387,7 @@ The table below shows which release corresponds to each branch, and what date th
## 4.11.0
- [#2185][2185] make fmtstr module able to create payload without $ notation
- [#2185][2185] make fmtstr module able to create payload without $ notation
- [#2103][2103] Add search for libc binary by leaked function addresses `libcdb.search_by_symbol_offsets()`
- [#2177][2177] Support for RISC-V 64-bit architecture
- [#2186][2186] Enhance `ELF.nx` and `ELF.execstack`
@@ -545,7 +662,7 @@ In memoriam — [Zach Riggle][zach] — long time contributor and maintainer of
[fecf9f]: http://github.com/Gallopsled/pwntools/commit/fecf9f
[1454]: https://github.com/Gallopsled/pwntools/pull/1454
## 4.1.7 (`stable`)
## 4.1.7
- [#1615][1615] Fix aarch64 pushstr and pushstr_array
+1 -1
View File
@@ -19,6 +19,6 @@ psutil
requests>=2.5.1
ropgadget>=5.3
sphinx==1.8.6; python_version<'3'
sphinx>=7.0.0; python_version>='3'
sphinx>=8.1.3, <9; python_version>='3'
sphinx_rtd_theme
sphinxcontrib-autoprogram<=0.1.5
+2
View File
@@ -34,6 +34,8 @@ This exposes a standard interface to talk to processes, sockets, serial ports,
and all manner of things, along with some nifty helpers for common tasks.
For example, remote connections via :mod:`pwnlib.tubes.remote`.
::
>>> conn = remote('ftp.ubuntu.com',21)
>>> conn.recvline() # doctest: +ELLIPSIS
b'220 ...'
+2
View File
@@ -32,8 +32,10 @@ RUN apt-get update \
binutils-aarch64-linux-gnu \
binutils-mips-linux-gnu \
binutils-mipsel-linux-gnu \
binutils-msp430 \
binutils-powerpc-linux-gnu \
binutils-powerpc64-linux-gnu \
binutils-s390x-linux-gnu \
binutils-sparc64-linux-gnu \
binutils-riscv64-linux-gnu \
tmux \
+9 -1
View File
@@ -89,7 +89,15 @@ try:
except ImportError:
pass
else:
colored_traceback.add_hook()
try:
colored_traceback.add_hook()
except Exception:
# Exception: curses.error
# colored_traceback (curses.setupterm()) fails if TERM is unset.
# This is not critical, so we just ignore it.
# We cannot import `curses` for `curses.error` because it is not
# available on all platforms (e.g. Windows).
pass
# Equivalence with the default behavior of "from import *"
# __all__ = [x for x in tuple(globals()) if not x.startswith('_')]
+21 -1
View File
@@ -1275,8 +1275,28 @@ properties = Property()
def _build_date():
"""Returns the build date in the form YYYY-MM-DD as a string"""
import datetime
# Use ro.build.date.utc (integer epoch seconds) which is set by the
# AOSP build system and available on all standard Android devices.
# This avoids ro.build.date which is locale-dependent and can contain
# non-ASCII characters that dateutil cannot parse. See #2513.
utc = getprop('ro.build.date.utc')
if utc and utc.strip().isdigit():
try:
as_datetime = datetime.datetime.fromtimestamp(int(utc.strip()), dateutil.tz.UTC)
return as_datetime.strftime('%Y-%b-%d')
except (OSError, OverflowError, ValueError):
pass
# Fallback for non-standard builds missing ro.build.date.utc.
as_string = getprop('ro.build.date')
as_datetime = dateutil.parser.parse(as_string)
if not as_string:
return ''
try:
as_datetime = dateutil.parser.parse(as_string)
except (ValueError, OverflowError):
return as_string
return as_datetime.strftime('%Y-%b-%d')
def find_ndk_project_root(source):
+14 -3
View File
@@ -170,6 +170,8 @@ def which_binutils(util, check_version=False):
'.../bin/arm-...-as'
>>> which_binutils('as', arch='powerpc') #doctest: +ELLIPSIS
'.../bin/powerpc...-as'
>>> which_binutils('as', arch='mips', endianness='little') #doctest: +ELLIPSIS
'.../bin/mipsel...-as'
>>> which_binutils('as', arch='msp430') #doctest: +SKIP
...
Traceback (most recent call last):
@@ -178,6 +180,12 @@ def which_binutils(util, check_version=False):
"""
arch = context.arch
# Handle endianness-specific naming for mips/mips64
arch = {
('mips', 'little'): 'mipsel',
('mips64', 'little'): 'mips64el',
}.get((arch, context.endianness), arch)
# Fix up pwntools vs Debian triplet naming, and account
# for 'thumb' being its own pwntools architecture.
arches = [arch] + {
@@ -185,11 +193,14 @@ def which_binutils(util, check_version=False):
'i386': ['x86_64', 'amd64'],
'i686': ['x86_64', 'amd64'],
'amd64': ['x86_64', 'i386'],
'mips64': ['mips'],
'mips': ['mipsel'],
'mipsel': ['mips'],
'mips64': ['mips', 'mipsel'],
'mips64el': ['mipsel', 'mips'],
'powerpc64': ['powerpc'],
'sparc64': ['sparc'],
'riscv32': ['riscv32', 'riscv64', 'riscv'],
'riscv64': ['riscv64', 'riscv32', 'riscv'],
'riscv32': ['riscv64', 'riscv'],
'riscv64': ['riscv32', 'riscv'],
}.get(arch, [])
# If one of the candidate architectures matches the native
+25 -1
View File
@@ -115,6 +115,18 @@ def get_docker_image_libraries():
return libc_basename, ld_basename
def detect_missing_binaries(args):
"""Automatically detects challenge binaries and libraries in the current directory.
This function scans the current directory for executable files, libc, and ld libraries.
If a Dockerfile is present and no libraries are found, it attempts to extract them from
the Docker image, but only if the binary is not statically linked.
Args:
args: Argument namespace containing exe and libc attributes.
Returns:
tuple: A pair of (executable_path, libc_path) where either may be None if not found.
"""
log.info("Automatically detecting challenge binaries...")
# look for challenge binary, libc, and ld in current directory
exe, libc, ld = args.exe, args.libc, None
@@ -138,7 +150,19 @@ def detect_missing_binaries(args):
elif len(other_files) > 1:
log.warning("Failed to find challenge binary. There are multiple binaries in the current directory: %s", other_files)
if has_dockerfile and exe and not (libc or ld):
# Check if the binary is statically linked before trying to extract libraries from Docker
is_statically_linked = False
if exe:
try:
binary = ELF(exe, checksec=False)
is_statically_linked = binary.statically_linked
if is_statically_linked:
log.info("Binary is statically linked, no need for external libraries")
except Exception as e:
log.warning("Could not check if binary is statically linked: %s", e)
# Only extract libraries from Docker if the binary is not statically linked
if has_dockerfile and exe and not (libc or ld) and not is_statically_linked:
libc, ld = get_docker_image_libraries()
if exe != args.exe:
+15
View File
@@ -359,6 +359,7 @@ class ContextType(object):
'encoding': 'auto',
'endian': 'little',
'gdbinit': "",
'gdb_binary': "",
'kernel': None,
'local_libcdb': "/var/lib/libc-database",
'log_level': logging.INFO,
@@ -1539,6 +1540,20 @@ class ContextType(object):
"""
return str(value)
@_validator
def gdb_binary(self, value):
"""Path to the binary that is used when running GDB locally.
This is useful when you have multiple versions of gdb installed or the gdb binary is
called something different.
If set to an empty string, pwntools will try to search for a reasonable gdb binary from
the path.
Default value is ``""``.
"""
return str(value)
@_validator
def cyclic_alphabet(self, alphabet):
"""Cyclic alphabet.
+17 -1
View File
@@ -555,6 +555,9 @@ class Corefile(ELF):
# Pointer to the entry point
self.at_entry = 0
# Pointer to the vdso
self.at_sysinfo_ehdr = None
try:
super(Corefile, self).__init__(*a, **kw)
except IOError:
@@ -611,6 +614,8 @@ class Corefile(ELF):
if not self.stack and self.mappings:
self.stack = self.mappings[-1].stop
if self.mappings[-1].start == 0xffffffffff600000 and len(self.mappings) > 1:
self.stack = self.mappings[-2].stop
if self.stack and self.mappings:
for mapping in self.mappings:
@@ -1510,7 +1515,18 @@ class CorefileFinder(object):
# should be unique enough that we can just glob.
boot_id = read('/proc/sys/kernel/random/boot_id').strip().decode()
path = self.exe.replace('/', '_')
# Use the absolute path of the executable
# Apport uses the executable's path to determine the core dump filename
#
# Reference source:
# https://github.com/canonical/apport/blob/4bbb179b8f92989bf7c1ee3692074f35d70ef3e8/data/apport#L110
# https://github.com/canonical/apport/blob/4bbb179b8f92989bf7c1ee3692074f35d70ef3e8/apport/fileutils.py#L599
#
# Apport calls `get_core_path` with `options.executable_path`, which corresponds to
# the executable's pathname, as specified by the `%E` placeholder
# in the core pattern (see `man core` and `apport --help`).
path = os.path.abspath(self.exe).replace('/', '_').replace('.', '_')
# Format the name
corefile_name = 'core.{path}.{uid}.{boot_id}.{pid}.*'.format(
+118 -80
View File
@@ -715,29 +715,56 @@ class ELF(ELFFile):
@property
def libs(self):
"""Dictionary of ``{path: address}`` for every library loaded for this ELF."""
"""Dictionary of ``{path: address}`` for every library loaded for this ELF.
.. warning::
Getting this attribute actually runs the executable.
Make sure that you trust the binary you are exploiting.
If it adds itself as ``DT_NEEDED``, has overlapping segments,
ambiguous headers, or employs text relocations, it can run arbitrary
code even though you are just inspecting it.
Running is the only reliable way to ensure all the libraries are
loaded from the correct paths, because some of them may change
loading logic.
Exploitability first noticed at CWTE CTF 2025.
"""
if self._libs is None:
self._populate_libraries()
return self._libs
@property
def maps(self):
"""Dictionary of ``{name: address}`` for every mapping in this ELF's address space."""
"""Dictionary of ``{name: address}`` for every mapping in this ELF's address space.
.. warning::
Getting this attribute actually runs the executable.
Make sure that you trust the binary you are exploiting
(see :attr:`.ELF.libs`).
"""
if self._maps is None:
self._populate_libraries()
return self._maps
@property
def libc(self):
""":class:`.ELF`: If this :class:`.ELF` imports any libraries which contain ``'libc[.-]``,
""":class:`.ELF`: If this :class:`.ELF` imports any libraries which contain ``/libc[.-]``,
and we can determine the appropriate path to it on the local
system, returns a new :class:`.ELF` object pertaining to that library.
Prints the `checksec` output of the library if it was printed for the original ELF too.
If not found, the value will be :const:`None`.
.. warning::
Getting this attribute actually runs the executable.
Make sure that you trust the binary you are exploiting
(see :attr:`.ELF.libs`).
"""
for lib in self.libs:
if '/libc.' in lib or '/libc-' in lib:
if ('/libc.' in lib or '/libc-' in lib) and lib != self.path:
return ELF(lib, self._print_checksec)
def _populate_libraries(self):
@@ -852,6 +879,13 @@ class ELF(ELFFile):
log.warn_once("Injected /proc/self/maps code did not execute correctly")
return {}
# Sometimes the original binary already fail to run, for example, in case glibc mismatches.
try:
int(data.split('-', 1)[0], 16)
except ValueError:
log.warn_once("Cannot execute `%s` to get /proc/self/maps", self.path)
return {}
# Swap in the original ELF name
data = data.replace(path, self.path)
@@ -919,10 +953,9 @@ class ELF(ELFFile):
continue
for symbol in _iter_symbols(section):
value = symbol.entry.st_value
if not value:
if not symbol.name or symbol.entry.st_shndx == 'SHN_UNDEF':
continue
self.symbols[symbol.name] = value
self.symbols[symbol.name] = symbol.entry.st_value
def _populate_synthetic_symbols(self):
"""Adds symbols from the GOT and PLT to the symbols dictionary.
@@ -1791,131 +1824,136 @@ class ELF(ELFFile):
\\* Hardware limitations are ignored.
If ``READ_IMPLIES_EXEC`` is set, then `all readable pages are executable`__.
.. __: https://github.com/torvalds/linux/blob/v6.3/fs/binfmt_elf.c#L1008-L1009
.. code-block:: c
if (elf_read_implies_exec(loc->elf_ex, executable_stack))
current->personality |= READ_IMPLIES_EXEC;
.. __: https://github.com/torvalds/linux/blob/v6.3/fs/binfmt_elf.c#L1008-L1009
.. code-block:: c
if (elf_read_implies_exec(loc->elf_ex, executable_stack))
current->personality |= READ_IMPLIES_EXEC;
.. [#x86_5.7]
`source <https://github.com/torvalds/linux/blob/v5.7/arch/x86/include/asm/elf.h#L285-L286>`__
.. code-block:: c
.. code-block:: c
#define elf_read_implies_exec(ex, executable_stack) \\
(executable_stack != EXSTACK_DISABLE_X)
#define elf_read_implies_exec(ex, executable_stack) \\
(executable_stack != EXSTACK_DISABLE_X)
.. [#x86_5.8]
`source <https://github.com/torvalds/linux/blob/v5.8/arch/x86/include/asm/elf.h#L305-L306>`__
.. code-block:: c
.. code-block:: c
#define elf_read_implies_exec(ex, executable_stack) \\
(mmap_is_ia32() && executable_stack == EXSTACK_DEFAULT)
#define elf_read_implies_exec(ex, executable_stack) \\
(mmap_is_ia32() && executable_stack == EXSTACK_DEFAULT)
`mmap_is_ia32()`__:
.. __: https://github.com/torvalds/linux/blob/v5.8/arch/x86/include/asm/elf.h#L318-L321
.. code-block:: c
`mmap_is_ia32()`__:
/*
* True on X86_32 or when emulating IA32 on X86_64
*/
static inline int mmap_is_ia32(void)
.. __: https://github.com/torvalds/linux/blob/v5.8/arch/x86/include/asm/elf.h#L318-L321
.. code-block:: c
/*
* True on X86_32 or when emulating IA32 on X86_64
*/
static inline int mmap_is_ia32(void)
.. [#arm_5.7]
`source <https://github.com/torvalds/linux/blob/v5.7/arch/arm/kernel/elf.c#L85-L92>`__
.. code-block:: c
.. code-block:: c
int arm_elf_read_implies_exec(int executable_stack)
{
if (executable_stack != EXSTACK_DISABLE_X)
return 1;
if (cpu_architecture() < CPU_ARCH_ARMv6)
return 1;
return 0;
}
int arm_elf_read_implies_exec(int executable_stack)
{
if (executable_stack != EXSTACK_DISABLE_X)
return 1;
if (cpu_architecture() < CPU_ARCH_ARMv6)
return 1;
return 0;
}
.. [#arm_5.8]
`source <https://github.com/torvalds/linux/blob/v5.8/arch/arm/kernel/elf.c#L104-L111>`__
.. code-block:: c
.. code-block:: c
int arm_elf_read_implies_exec(int executable_stack)
{
if (executable_stack == EXSTACK_DEFAULT)
return 1;
if (cpu_architecture() < CPU_ARCH_ARMv6)
return 1;
return 0;
}
int arm_elf_read_implies_exec(int executable_stack)
{
if (executable_stack == EXSTACK_DEFAULT)
return 1;
if (cpu_architecture() < CPU_ARCH_ARMv6)
return 1;
return 0;
}
.. [#mips_5.17]
`source <https://github.com/torvalds/linux/blob/v5.17/arch/mips/kernel/elf.c#L329-L342>`__
.. code-block:: c
.. code-block:: c
int mips_elf_read_implies_exec(void *elf_ex, int exstack)
{
if (exstack != EXSTACK_DISABLE_X) {
/* The binary doesn't request a non-executable stack */
return 1;
}
if (!cpu_has_rixi) {
/* The CPU doesn't support non-executable memory */
return 1;
}
return 0;
int mips_elf_read_implies_exec(void *elf_ex, int exstack)
{
if (exstack != EXSTACK_DISABLE_X) {
/* The binary doesn't request a non-executable stack */
return 1;
}
if (!cpu_has_rixi) {
/* The CPU doesn't support non-executable memory */
return 1;
}
return 0;
}
.. [#mips_5.18]
`source <https://github.com/torvalds/linux/blob/v5.18/arch/mips/kernel/elf.c#L329-L336>`__
.. code-block:: c
.. code-block:: c
int mips_elf_read_implies_exec(void *elf_ex, int exstack)
{
/*
* Set READ_IMPLIES_EXEC only on non-NX systems that
* do not request a specific state via PT_GNU_STACK.
*/
return (!cpu_has_rixi && exstack == EXSTACK_DEFAULT);
}
int mips_elf_read_implies_exec(void *elf_ex, int exstack)
{
/*
* Set READ_IMPLIES_EXEC only on non-NX systems that
* do not request a specific state via PT_GNU_STACK.
*/
return (!cpu_has_rixi && exstack == EXSTACK_DEFAULT);
}
.. [#powerpc]
`source <https://github.com/torvalds/linux/blob/v6.3/arch/powerpc/include/asm/elf.h#L82-L108>`__
.. code-block:: c
.. code-block:: c
#ifdef __powerpc64__
/* stripped */
# define elf_read_implies_exec(ex, exec_stk) (is_32bit_task() ? \\
(exec_stk == EXSTACK_DEFAULT) : 0)
#else
# define elf_read_implies_exec(ex, exec_stk) (exec_stk == EXSTACK_DEFAULT)
#endif /* __powerpc64__ */
#ifdef __powerpc64__
/* stripped */
# define elf_read_implies_exec(ex, exec_stk) (is_32bit_task() ? \\
(exec_stk == EXSTACK_DEFAULT) : 0)
#else
# define elf_read_implies_exec(ex, exec_stk) (exec_stk == EXSTACK_DEFAULT)
#endif /* __powerpc64__ */
.. [#ia64]
`source <https://github.com/torvalds/linux/blob/v6.3/arch/ia64/include/asm/elf.h#L203-L204>`__
.. code-block:: c
.. code-block:: c
#define elf_read_implies_exec(ex, executable_stack) \\
((executable_stack!=EXSTACK_DISABLE_X) && ((ex).e_flags & EF_IA_64_LINUX_EXECUTABLE_STACK) != 0)
#define elf_read_implies_exec(ex, executable_stack) \\
((executable_stack!=EXSTACK_DISABLE_X) && ((ex).e_flags & EF_IA_64_LINUX_EXECUTABLE_STACK) != 0)
EF_IA_64_LINUX_EXECUTABLE_STACK__:
.. __: https://github.com/torvalds/linux/blob/v6.3/arch/ia64/include/asm/elf.h#L33
EF_IA_64_LINUX_EXECUTABLE_STACK__:
.. code-block:: c
.. __: https://github.com/torvalds/linux/blob/v6.3/arch/ia64/include/asm/elf.h#L33
#define EF_IA_64_LINUX_EXECUTABLE_STACK 0x1 /* is stack (& heap) executable by default? */
.. code-block:: c
#define EF_IA_64_LINUX_EXECUTABLE_STACK 0x1 /* is stack (& heap) executable by default? */
.. [#the_rest]
`source <https://github.com/torvalds/linux/blob/v6.3/include/linux/elf.h#L13>`__
.. code-block:: c
.. code-block:: c
# define elf_read_implies_exec(ex, have_pt_gnu_stack) 0
# define elf_read_implies_exec(ex, have_pt_gnu_stack) 0
"""
if not self.executable:
return True
+8 -1
View File
@@ -65,7 +65,7 @@ def __ensure_memory_to_run_unicorn():
from mmap import mmap, MAP_ANON, MAP_PRIVATE, PROT_EXEC, PROT_READ, PROT_WRITE
mm = mmap(
-1, 1024 * 1024 * 1024, MAP_PRIVATE | MAP_ANON, PROT_WRITE | PROT_READ | PROT_EXEC
-1, 1024 * 1024 * 1024, MAP_PRIVATE | MAP_ANON, PROT_WRITE | PROT_READ
)
mm.close()
except OSError:
@@ -78,6 +78,13 @@ def __ensure_memory_to_run_unicorn():
def prepare_unicorn_and_context(elf, got, address, data):
import unicorn as U
if elf.arch in ('mips', 'mips64'):
# Unicorn 2.1.3 and 2.1.4 have a crashing bug when trying to emulate MIPS.
# https://github.com/unicorn-engine/unicorn/issues/2134
if U.UC_VERSION_MAJOR == 2 and U.UC_VERSION_MINOR == 1 and U.UC_VERSION_PATCH in (3, 4):
raise OSError("Unicorn Engine 2.1.3 and 2.1.4 have a crashing bug when emulating MIPS, please install an earlier or later version: https://github.com/unicorn-engine/unicorn/issues/2134")
__ensure_memory_to_run_unicorn()
# Instantiate the emulator with the correct arguments for the current
+14 -2
View File
@@ -156,6 +156,18 @@ class FileStructure(object):
_wide_data: 0xdeadbeef
unknown2: 0x0
vtable: 0x0}
Bytes fields are padded to the correct field size.
For example, unknown2 is 56 bytes on i386, so a short value gets
zero-padded to 56, not to context.bytes (4):
>>> context.clear(arch='i386')
>>> fileStr2 = FileStructure(null=0)
>>> fileStr2.vtable = 0x561859f0
>>> old_len = len(bytes(fileStr2))
>>> fileStr2.unknown2 = b'AB'
>>> len(bytes(fileStr2)) == old_len
True
"""
vars_=[]
@@ -186,7 +198,7 @@ class FileStructure(object):
structure = b''
for val in self.vars_:
if isinstance(getattr(self, val), bytes):
structure += getattr(self, val).ljust(context.bytes, b'\x00')
structure += getattr(self, val).ljust(self.length[val], b'\x00')
else:
if self.length[val] > 0:
structure += pack(getattr(self, val), self.length[val]*8)
@@ -215,7 +227,7 @@ class FileStructure(object):
structure = b''
for val in self.vars_:
if isinstance(getattr(self, val), bytes):
structure += getattr(self, val).ljust(context.bytes, b'\x00')
structure += getattr(self, val).ljust(self.length[val], b'\x00')
else:
structure += pack(getattr(self, val), self.length[val]*8)
if val == v:
+30 -24
View File
@@ -143,7 +143,6 @@ from __future__ import division
import os
import platform
import psutil
import random
import re
import six
@@ -353,10 +352,6 @@ def _gdbserver_args(pid=None, path=None, port=0, gdbserver_args=None, args=None,
gdbserver_args += ['--wrapper', python_wrapper_script, '--']
elif env is not None:
gdbserver_args += ['--wrapper', which('env'), '-i'] + env_args + ['--']
# --no-startup-with-shell is required for forking shells like SHELL=/bin/fish
# https://github.com/Gallopsled/pwntools/issues/2377
else:
gdbserver_args += ['--no-startup-with-shell']
gdbserver_args += ['localhost:%d' % port]
gdbserver_args += args
@@ -750,6 +745,12 @@ def binary():
>>> gdb.binary() # doctest: +SKIP
'/usr/bin/gdb'
"""
if context.gdb_binary:
gdb = misc.which(context.gdb_binary)
if not gdb:
log.warn_once('Path to gdb binary `{}` not found'.format(context.gdb_binary))
return gdb
gdb = misc.which('pwntools-gdb') or misc.which('gdb')
if not context.native:
@@ -1520,28 +1521,33 @@ def corefile(process):
'contain all of the data in the process.\n'
'Upgrade to GDB >= 7.11 for better core-dumps.' % binary())
# This is effectively the same as what the 'gcore' binary does
gdb_args = ['-batch',
'-q',
'-nx',
'-ex', 'set pagination off',
'-ex', 'set height 0',
'-ex', 'set width 0',
'-ex', 'set use-coredump-filter on',
'-ex', 'generate-core-file %s' % corefile_path,
'-ex', 'detach']
# This is effectively the same as what the 'gcore' binary does.
# Run GDB directly instead of going through attach(), which has to
# race GDB to observe the tracer before it already detached again.
cmd = [binary(),
'-batch',
'-q',
'-nx',
'-ex', 'set pagination off',
'-ex', 'set height 0',
'-ex', 'set width 0',
'-ex', 'set use-coredump-filter on',
'-ex', 'attach %d' % process.pid,
'-ex', 'generate-core-file %s' % corefile_path,
'-ex', 'detach']
with context.local(terminal = ['sh', '-c']):
with context.quiet:
pid = attach(process, gdb_args=gdb_args)
log.debug("Got GDB pid %d", pid)
try:
psutil.Process(pid).wait()
except psutil.Error:
pass
with context.quiet:
gdb = tubes.process.process(cmd, stdout=tubes.process.PIPE)
gdb.wait_for_close(timeout=60)
timed_out = gdb.poll() is None
output = gdb.recvallS(timeout=0)
if timed_out:
log.error("GDB did not finish dumping process %d within 60 seconds", process.pid)
if not os.path.exists(corefile_path):
log.error("Could not generate a corefile for process %d", process.pid)
log.error("Could not generate a corefile for process %d (maybe check /proc/sys/kernel/yama/ptrace_scope)\n%s",
process.pid, output)
return elf.corefile.Core(corefile_path)
+1 -1
View File
@@ -95,7 +95,7 @@ class PwntoolsLexer(RegexLexer):
'whitespace': [
(r'\n', Text),
(r'\s+', Text),
(r'/\*.*?\*/', Comment),
(r'(?ms)/\*.*?\*/', Comment),
(r';.*$', Comment)
],
'punctuation': [
+44 -55
View File
@@ -9,6 +9,7 @@ import time
import six
import tempfile
import struct
import sys
from pwnlib.context import context
from pwnlib.elf import ELF
@@ -65,6 +66,8 @@ MAP_TYPES = {
}
DEBUGINFOD_SERVERS = [
'https://debuginfod.ubuntu.com/',
'https://debuginfod.debian.net/',
'https://debuginfod.elfutils.org/',
]
@@ -293,49 +296,6 @@ def search_by_hash(search_target, search_type='build_id', unstrip=True, offline_
return cache
def _search_debuginfo_by_hash(base_url, hex_encoded_id):
"""
Given a hex-encoded build_id, attempt to download a matching debuginfo from the debuginfod server.
>>> debuginfo_file = _search_debuginfo_by_hash(DEBUGINFOD_SERVERS[0], 'd1704d25fbbb72fa95d517b883131828c0883fe9')
>>> debuginfo_file is not None
True
>>> 'main_arena' in ELF(debuginfo_file).symbols
True
"""
# Deferred import because it's slow
import requests
from six.moves import urllib
# 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
# We searched for this buildid before, but didn't find anything.
if cache is None:
return None
# 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, search_target, search_type):
"""
Check if there already is an ELF file for this hash in the cache.
@@ -394,15 +354,15 @@ def unstrip_libc(filename):
>>> libc = ELF(filename)
>>> 'main_arena' in libc.symbols
False
>>> unstrip_libc(filename)
>>> unstrip_libc(filename) # doctest: +SKIP
True
>>> libc = ELF(filename)
>>> hex(libc.symbols.main_arena)
>>> hex(libc.symbols.main_arena) # doctest: +SKIP
'0x219c80'
>>> unstrip_libc(pwnlib.data.elf.get('test-x86'))
False
>>> filename = search_by_build_id('d1704d25fbbb72fa95d517b883131828c0883fe9', unstrip=True)
>>> 'main_arena' in ELF(filename).symbols
>>> 'main_arena' in ELF(filename).symbols # doctest: +SKIP
True
"""
if not which('eu-unstrip'):
@@ -420,16 +380,45 @@ def unstrip_libc(filename):
log.debug('Trying debuginfod servers: %r', DEBUGINFOD_SERVERS)
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
# Deferred import because it's slow
import requests
from six.moves import urllib
hex_encoded_id = enhex(libc.buildid)
# Check if we tried this buildid before.
cache, cache_valid = _check_elf_cache('libcdb_dbg', hex_encoded_id, 'build_id')
if not cache_valid:
# Cached negative result so we don't look for missing debug info everytime.
if cache is None:
return False
else:
for server_url in DEBUGINFOD_SERVERS:
# Try to find separate debuginfo.
url = '/buildid/{}/debuginfo'.format(hex_encoded_id)
url = urllib.parse.urljoin(server_url, url)
data = b""
log.debug("Downloading data from debuginfod: %s", url)
try:
data = wget(url, timeout=20)
# Try next server if we didn't 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, server_url)
continue
break
except requests.RequestException as e:
log.warn_once("Failed to fetch libc debuginfo for build_id %s from %s: %s", hex_encoded_id, server_url, e)
else:
write(cache, data or b'')
log.warn_once('Couldn\'t find debug info for libc with build_id %s on any debuginfod server.', enhex(libc.buildid))
return False
# Save whatever we got to the cache
write(cache, data or b'')
# Add debug info to given libc binary inplace.
p = process(['eu-unstrip', '-o', filename, filename, libc_dbg])
p = process(['eu-unstrip', '-o', filename, filename, cache])
output = p.recvall()
p.close()
@@ -498,7 +487,7 @@ def _extract_tarfile(cache_dir, data_filename, tarball):
def _extract_debfile(cache_dir, package_filename, package):
# Extract data.tar in the .deb archive.
if six.PY2:
if sys.version_info < (3, 6):
if not which('ar'):
log.error('Missing command line tool "ar" to extract .deb archive. Please install "ar" first.')
+51 -16
View File
@@ -15,7 +15,7 @@ from pwnlib.log import getLogger
log = getLogger(__name__)
def check_cycle(reg, assignments):
def check_cycle(reg, assignments, mapping=None):
"""Walk down the assignment list of a register,
return the path walked if it is encountered again.
@@ -37,31 +37,38 @@ def check_cycle(reg, assignments):
>>> check_cycle('a', {'a': 'b', 'b': 'c', 'c': 'd', 'd': 'a'})
['a', 'b', 'c', 'd']
"""
return check_cycle_(reg, assignments, [])
if mapping is None:
mapping = {}
return check_cycle_(reg, assignments, [], mapping)
def check_cycle_(reg, assignments, path):
target = assignments[reg]
def check_cycle_(reg, assignments, path, mapping):
target = assignments.get(reg)
if target is None:
real_reg = mapping.get(reg, reg)
reg, target = next((k, v) for k,v in assignments.items() if mapping.get(k, k) == real_reg)
path.append(reg)
real_target = mapping.get(target, target)
# No cycle, some other value (e.g. 1)
if target not in assignments:
if all(real_target != mapping.get(k, k) for k in assignments):
return []
# Found a cycle
if target in path:
if any(real_target == mapping.get(k, k) for k in path):
# Does the cycle *start* with target?
# This determines whether the original register is
# in the cycle, or just depends on registers in one.
if target == path[0]:
if real_target == mapping.get(path[0], path[0]):
return path
# Just depends on one.
return []
# Recurse
return check_cycle_(target, assignments, path)
return check_cycle_(target, assignments, path, mapping)
def extract_dependencies(reg, assignments):
def extract_dependencies(reg, assignments, mapping=None):
"""Return a list of all registers which directly
depend on the specified register.
@@ -77,7 +84,9 @@ def extract_dependencies(reg, assignments):
['b', 'c']
"""
# sorted() is only for determinism
return sorted([k for k,v in assignments.items() if v == reg])
if mapping is None:
mapping = {}
return sorted([k for k,v in assignments.items() if mapping.get(v, v) == mapping.get(reg, reg)])
def resolve_order(reg, deps):
@@ -110,7 +119,7 @@ def depends_on_cycle(reg, assignments, in_cycles):
reg = assignments.get(reg, None)
return False
def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
def regsort(in_out, all_regs, mapping = None, tmp = None, xchg = True, randomize = None):
"""
Sorts register dependencies.
@@ -181,6 +190,15 @@ def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
>>> regsort({'a': 1, 'b': 2}, R)
[('mov', 'a', 1), ('mov', 'b', 2)]
>>> M = {'ax': 'eax', 'eax': 'eax', 'ebx': 'ebx'}
Registers which are assigned one of their own aliases
are dropped, like same-name self-assignments.
>>> regsort({'ax': 'eax'}, M)
[]
>>> regsort({'ax': 'eax'}, M, xchg=False)
[]
>>> regsort({'a': 'b', 'b': 'a'}, R)
[('xchg', 'a', 'b')]
>>> regsort({'a': 'b', 'b': 'a'}, R, tmp='X') #doctest: +NORMALIZE_WHITESPACE
@@ -233,16 +251,24 @@ def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
if randomize is None:
randomize = context.randomize
if mapping is None:
if hasattr(all_regs, 'keys'):
mapping = all_regs
else:
mapping = {}
sentinel = object()
# Drop all registers which will be set to themselves.
#
# For example, {'eax': 'eax'}
in_out = {k:v for k,v in in_out.items() if k != v}
# This must go through the mapping, so that {'ax': 'eax'} is
# recognized as a self-assignment as well.
in_out = {k:v for k,v in in_out.items() if mapping.get(k, k) != mapping.get(v, v)}
# Collapse constant values
#
# For eaxmple, {'eax': 0, 'ebx': 0} => {'eax': 0, 'ebx': 'eax'}
# For example, {'eax': 0, 'ebx': 0} => {'eax': 0, 'ebx': 'eax'}
v_k = defaultdict(list)
for k,v in sorted(in_out.items()):
if v not in all_regs and v != 0:
@@ -263,7 +289,9 @@ def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
# which are also 'outputs'.
#
# For example, {'eax': 1, 'ebx': 2, 'ecx': 'edx'}
if not any(v in in_out for k,v in in_out.items()):
inps = {mapping.get(k, k) for k in in_out}
outs = {mapping.get(v, v) for v in in_out.values()}
if not inps & outs:
result = [('mov', k,in_out[k]) for k in sorted(in_out)]
if randomize:
@@ -280,7 +308,7 @@ def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
# Output: {'A': [], 'B': ['A', 'C'], 'C': []}
#
# In this case, both A and C must be set before B.
deps = {r: extract_dependencies(r, in_out) for r in in_out}
deps = {r: extract_dependencies(r, in_out, mapping) for r in in_out}
# Final result which will be returned
result = []
@@ -299,7 +327,7 @@ def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
while cycle_candidates:
reg = cycle_candidates[0]
cycle = check_cycle(reg, in_out)
cycle = check_cycle(reg, in_out, mapping)
if cycle:
if randomize:
@@ -395,6 +423,10 @@ def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
if tmp:
for cycle in cycles:
if len(cycle) == 1:
[reg] = cycle
result.append(('mov', reg, in_out[reg]))
continue
first = cycle[0]
last = cycle[-1]
@@ -411,6 +443,9 @@ def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
else:
for cycle in cycles:
size = len(cycle)
if size == 1:
[reg] = cycle
result.append(('mov', reg, in_out[reg]))
for i in range(size-1):
result.append(('xchg', cycle[i], cycle[(i+1) % size]))
+1 -1
View File
@@ -841,7 +841,7 @@ class ROP(object):
"""
if isinstance(object, enums):
return str(object)
if isinstance(object, six.integer_types):
if isinstance(object, six.integer_types) and object:
return self.unresolve(object)
if isinstance(object, (bytes, six.text_type)):
return repr(object)
+4 -4
View File
@@ -116,10 +116,10 @@ i386_ordered = [
['rbx', 'ebx', 'bx', 'bl'],
['rcx', 'ecx', 'cx', 'cl'],
['rdx', 'edx', 'dx', 'dl'],
['rdi', 'edi', 'di'],
['rsi', 'esi', 'si'],
['rbp', 'ebp', 'bp'],
['rsp', 'esp', 'sp'],
['rdi', 'edi', 'di', 'dil'],
['rsi', 'esi', 'si', 'sil'],
['rbp', 'ebp', 'bp', 'bpl'],
['rsp', 'esp', 'sp', 'spl'],
['r8', 'r8d', 'r8w', 'r8b'],
['r9', 'r9d', 'r9w', 'r9b'],
['r10', 'r10d', 'r10w', 'r10b'],
@@ -16,4 +16,4 @@ ${memcpy_loop}:
ldrb w3, [x1], #1
strb w3, [x0], #1
subs x2, x2, #1
bge ${memcpy_loop}
bgt ${memcpy_loop}
+2 -2
View File
@@ -51,8 +51,8 @@ Example:
xor eax, eax
mov ax, 0xc0c0
>>> print(shellcraft.amd64.mov('rdi', 0xff).rstrip())
mov edi, 0x1010101 /* 255 == 0xff */
xor edi, 0x10101fe
xor edi, edi
mov dil, 0xff
>>> print(shellcraft.amd64.mov('rax', 0xdead00ff).rstrip())
mov eax, 0x1010101 /* 3735879935 == 0xdead00ff */
xor eax, 0xdfac01fe
@@ -55,7 +55,7 @@ if isinstance(eax, six.integer_types) and isinstance(edx, six.integer_types) and
cdq = True
reg_context.pop('rdx')
sorted_regs = regsort(reg_context, registers.amd64)
sorted_regs = regsort(reg_context, registers.amd64, registers.native64)
%>
% if not sorted_regs:
/* setregs noop */
@@ -1,24 +1,30 @@
<%
from pwnlib.shellcraft import amd64, pretty
from pwnlib.shellcraft import amd64, common, pretty
from pwnlib.util.packing import u64, _need_bytes
%>
<%docstring>Find the address of an exported function in a dll
by manually iterating through the PE export table.
The dll must be `b'kernel32.dll'` or `b'ntdll.dll'` at the moment.
Behavior is undefined if the function is not found.
Args:
function_name (str): The name of the function to find.
dll(str): The name of the DLL to find the function in.
function_name (bytes): The name of the function to find.
dll(bytes): The name of the DLL to find the function in.
dest (str): The register to load the function address into.
</%docstring>
<%page args="function_name,dll='kernel32.dll',dest='rax'"/>
<%page args="function_name,dll=b'kernel32.dll',dest='rax'"/>
<%
function_name = _need_bytes(function_name)
dll = _need_bytes(dll)
if len(function_name) > 8:
raise ValueError('function_name must be <= 8 bytes')
assert dll == b'kernel32.dll'
assert dll in (b'kernel32.dll', b'ntdll.dll')
find_function_loop = common.label('find_function').strip()
%>
% if dll == b'kernel32.dll':
${amd64.windows.kernel32base('rbx')} /* rbx = kernel32.dll PE base */
% elif dll == b'ntdll.dll':
${amd64.windows.ntdllbase('rbx')} /* rbx = ntdll.dll PE base */
% endif
mov r8d, [rbx + 0x3c]
mov rdx, r8
add rdx, rbx
@@ -26,25 +32,42 @@ assert dll == b'kernel32.dll'
add rdx, r9
mov r8d, [rdx]
add r8, rbx /* r8 = export table */
mov esi, [r8 + 0x20]
add rsi, rbx /* rsi = names table */
xor rcx, rcx
mov edx, [r8 + 0x20]
add rdx, rbx /* rdx = names table */
% if len(function_name) <= 8:
mov r9, ${pretty(u64(function_name.ljust(8, b'\x00')))}
% else:
${amd64.pushstr(function_name)}
mov r9, rsp
% endif
push r8
xor r8, r8
/* Loop through the names table */
FindFunction:
inc rcx
mov eax, [rsi + rcx * 4]
${find_function_loop}:
inc r8
mov eax, [rdx + r8 * 4]
add rax, rbx
## TODO: implement strcmp properly for function names > 8 bytes
## strcmp
% if len(function_name) <= 8:
cmp qword ptr [rax], r9
jnz FindFunction
% else:
${amd64.setregs({
'rdi': 'rax',
'rsi': 'r9',
'rcx': len(function_name),
})}
repe cmpsb
movzx rax, byte ptr [rsi-1]
movzx rcx, byte ptr [rdi-1]
sub rax, rcx
% endif
jnz ${find_function_loop}
## Assume we find the function, we'll crash walking past the
## end of the names table otherwise.
mov rcx, r8
pop r8
mov esi, [r8 + 0x24]
add rsi, rbx /* rsi = ordinals table */
mov cx, [rsi + rcx * 2]
@@ -52,4 +75,4 @@ assert dll == b'kernel32.dll'
add rsi, rbx /* rsi = address table */
mov eax, [rsi + rcx * 4]
add rax, rbx /* rax = function address */
mov ${dest}, rax
${amd64.mov(dest, 'rax')}
@@ -11,7 +11,7 @@ Args:
dll(str): The name of the DLL to find the function in.
dest (str): The register to load the function address into.
</%docstring>
<%page args="function_name,dll='kernel32.dll',dest='rax'"/>
<%page args="function_name,dll=b'kernel32.dll',dest='rax'"/>
<%
function_name = _need_bytes(function_name)
dll = _need_bytes(dll)
@@ -25,4 +25,4 @@ assert dll == b'kernel32.dll'
sub rsp, 0x30
call rdi
add rsp, ${pretty(0x30+align(8, len(function_name)))}
mov ${dest}, rax
${amd64.mov(dest, 'rax')}
+1 -1
View File
@@ -16,4 +16,4 @@ ${memcpy_loop}:
ldrb r3, [r1], #1
strb r3, [r0], #1
subs r2, r2, #1
bge ${memcpy_loop}
bgt ${memcpy_loop}
+2 -5
View File
@@ -58,12 +58,9 @@ Example:
>>> print(shellcraft.i386.mov('eax', 0xdead00ff).rstrip())
mov eax, -0xdead00ff
neg eax
>>> print(shellcraft.i386.mov('eax', 0xc0).rstrip())
xor eax, eax
mov al, 0xc0
>>> print(shellcraft.i386.mov('edi', 0xc0).rstrip())
mov edi, -0xc0
neg edi
xor edi, edi
mov dil, 0xc0
>>> print(shellcraft.i386.mov('eax', 0xc000).rstrip())
xor eax, eax
mov ah, 0xc000 >> 8
+1 -1
View File
@@ -54,7 +54,7 @@ sorted_regs = regsort(reg_context, registers.i386)
% if not sorted_regs:
/* setregs noop */
% else:
% for how, src, dst in regsort(reg_context, registers.i386):
% for how, src, dst in regsort(reg_context, registers.i386, registers.native32):
% if how == 'xchg':
xchg ${src}, ${dst}
% else:
+1 -1
View File
@@ -16,4 +16,4 @@ ${memcpy_loop}:
ldrb r3, [r1], #1
strb r3, [r4], #1
subs r2, r2, #1
bge ${memcpy_loop}
bgt ${memcpy_loop}
+1 -1
View File
@@ -380,7 +380,7 @@ def readline(_size=-1, prompt='', float=True, priority=10):
from pwnlib.term import term_mode
if not term_mode:
six.print_(prompt, end='', flush=True)
return getattr(sys.stdin, 'buffer', sys.stdin).readline(_size).rstrip(b'\n')
return force_to_bytes(getattr(sys.stdin, 'buffer', sys.stdin).readline(_size)).rstrip(b'\n')
show_suggestions = False
eof = False
if prompt:
+13 -14
View File
@@ -1259,6 +1259,9 @@ class process(tube):
>>> from pwn import *
>>> p = process(['cat'])
>>> p.send(b'meow')
>>> p.recvuntil(b'meow')
b'meow'
>>> libc_size = p.lib_size(p.libc.path)
>>> hex(libc_size) # doctest: +SKIP
'0x1d5000'
@@ -1325,8 +1328,7 @@ class process(tube):
by the process to the address it is loaded at in the process' address
space.
"""
from pwnlib.util.proc import memory_maps
maps_raw = memory_maps(self.pid)
maps_raw = self.poll() is None and self.maps()
if not maps_raw:
import pwnlib.elf.elf
@@ -1335,23 +1337,15 @@ class process(tube):
return pwnlib.elf.elf.ELF(self.executable).maps
# Enumerate all of the libraries actually loaded right now.
maps = {}
libs = {}
for mapping in maps_raw:
path = mapping.path
if os.sep not in path: continue
path = os.path.realpath(path)
if path not in maps:
maps[path]=0
if path not in libs:
libs[path] = mapping.addr
for lib in maps:
path = os.path.realpath(lib)
for mapping in maps_raw:
if mapping.path == path:
address = mapping.addr.split('-')[0]
maps[lib] = int(address, 16)
break
return maps
return libs
@property
def libc(self):
@@ -1364,7 +1358,12 @@ class process(tube):
Example:
>>> p = process("/bin/cat")
>>> p.send(b"meow")
>>> p.recvuntil(b"meow")
b'meow'
>>> libc = p.libc
>>> libc is not None
True
>>> libc # doctest: +SKIP
ELF('/lib64/libc-...so')
>>> p.close()
+1 -1
View File
@@ -127,7 +127,7 @@ class server(sock):
return
self.rhost, self.rport = rhost[:2]
r = remote(self.rhost, self.rport, sock = sock)
r = remote(self.rhost, self.rport, sock = sock, level = self.level)
h.success('Got connection from %s on port %d' % (self.rhost, self.rport))
if callback:
if not blocking:
+10 -2
View File
@@ -1359,7 +1359,11 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
update(len(data), total)
result = c.wait()
if result != 0:
if result == -1:
self.warn_once("Could not verify success of file download %r, no error code" % (remote))
if result != 0 and result != -1:
h.failure('Could not download file %r (%r)' % (remote, result))
return
@@ -1539,7 +1543,11 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
s.shutdown('send')
data = s.recvall()
result = s.wait()
if result != 0:
if result == -1:
self.warn_once("Could not verify success of file upload %r, no error code" % (remote))
if result != 0 and result != -1:
self.error("Could not upload file %r (%r)\n%s" % (remote, result, data))
def upload_file(self, filename, remote = None):
+11 -1
View File
@@ -988,6 +988,11 @@ def dd(dst, src, count = 0, skip = 0, seek = 0, truncate = False):
('H', 'e', 'l', 'l', 'o', b'?')
>>> dd(list('Hello!'), (63,), skip = 5)
['H', 'e', 'l', 'l', 'o', b'?']
>>> buf = bytearray(b'Hello!')
>>> dd(buf, b'?', skip = 5)
bytearray(b'Hello?')
>>> buf
bytearray(b'Hello?')
>>> _ = open('/tmp/foo', 'w').write('A' * 10)
>>> dd(open('/tmp/foo'), open('/dev/zero'), skip = 3, count = 4).read()
'AAA\\x00\\x00\\x00\\x00AAA'
@@ -1105,7 +1110,12 @@ def dd(dst, src, count = 0, skip = 0, seek = 0, truncate = False):
dst = real_dst
elif isinstance(dst, (list, bytearray)):
dst[skip : skip + len(src)] = list(map(p8, bytearray(src)))
# A bytearray slice takes a byte string, while a list slice takes the
# single-byte bytes objects that p8() returns.
if isinstance(dst, bytearray):
dst[skip : skip + len(src)] = src
else:
dst[skip : skip + len(src)] = list(map(p8, bytearray(src)))
if truncate:
while len(dst) > truncate:
dst.pop()
+1 -1
View File
@@ -1 +1 @@
__version__ = '4.15.0beta0'
__version__ = '4.15.0'
+1 -1
View File
@@ -56,7 +56,7 @@ dependencies = [
"colored_traceback<0.4; python_version < '3'",
"colored_traceback; python_version >= '3'",
"pathlib2; python_version < '3.4'",
"unix-ar; python_version >= '3'",
"unix-ar; python_version >= '3.6'",
"zstandard",
]
+1 -1
View File
@@ -87,7 +87,7 @@ if not os.path.exists(PythonH):
sys.exit(-1)
setup(
version = '4.15.0beta0',
version = '4.15.0',
data_files = [('pwntools-doc',
glob.glob('*.md') + glob.glob('*.txt')),
],
+17
View File
@@ -15,6 +15,7 @@ http {
location / {
proxy_set_header Host debuginfod.elfutils.org;
proxy_ssl_server_name on;
proxy_cache_revalidate on;
proxy_cache_key $scheme://$host$uri$is_args$query_string;
proxy_cache_valid 200 404 12w;
@@ -29,6 +30,7 @@ http {
location / {
proxy_set_header Host libc.rip;
proxy_ssl_server_name on;
proxy_cache_methods GET HEAD POST;
proxy_cache_revalidate on;
proxy_cache_key $scheme://$host$uri$is_args$query_string$request_body;
@@ -66,4 +68,19 @@ http {
proxy_pass https://gitlab.com/;
}
}
server {
listen 3004;
proxy_cache my_cache;
location / {
proxy_set_header Host debuginfod.ubuntu.com;
proxy_ssl_server_name on;
proxy_cache_revalidate on;
proxy_cache_key $scheme://$host$uri$is_args$query_string;
proxy_cache_valid 200 404 12w;
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504 http_429;
proxy_pass https://debuginfod.ubuntu.com/;
}
}
}