Compare commits

...
84 Commits
Author SHA1 Message Date
peace-maker 8bc6f6cd2b Merge pull request #2796 from peace-maker/stable_backports
Backport bugfixes from 5.0 to stable
2026-11-08 08:49:07 +01:00
peace-maker 455c8c02c3 Backport: Test on Python 3.15 (#2792)
* Test on Python 3.15

* Use `RLIM_INFINITY` instead of `-1` constant for rlimit modification

Python 3.15 warns against using negative values instead of the constant:

DeprecationWarning: Use RLIM_INFINITY instead of negative limit value.

It has been available since Python 2.

* Update CHANGELOG
2026-10-30 20:49:30 +01:00
Carlos Luna-Pena dbc618de0d Backport: fix #2737: pwnlib.shellcraft submodule imports on python 3.12+ (#2739)
* shellcraft: migrate lazy importer to find_spec for py3.12+

* shellcraft: changelog entry + mypy baseline sync for #2739
2026-10-30 20:49:06 +01:00
peace-maker 34b8345d1d Backport: Support paginated libc.rip API requests (#2767)
* Support paginated libc.rip API requests

libc.rip used to truncate the result set but supports pagination now. Fetch all results reliably now by requesting all pages.

Fixes #2651

* Update CHANGELOG
2026-10-30 20:49:05 +01:00
Peace-Maker 771c7f81ac Backport: Skip ssh.ibt doctest since some runners appear to support it now 2026-10-30 20:49:05 +01:00
Taran 8d69dc6142 Backport: fix(srop): correct amd64 SigreturnFrame uc_sigmask offset (#2762)
* fix(srop): correct amd64 SigreturnFrame uc_sigmask offset

* changelog: add entry for #2762
2026-10-30 20:49:05 +01:00
peace-maker 3911a6a12e Backport: Fix libcdb CI cache not forwarding URI path (#2749)
The nginx docs say that you have to specify the $request_uri manually when a variable is used in the `proxy_pass` value.
Since we just put `/` at the end, all requests were rewritten to `/` instead of the intended e.g. `/api/find` endpoints.

https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass

This broke in #2718 but didn't manifest right away since the responses were still cached in CI. Now after the cache expired the fetch failed.
2026-10-30 20:49:05 +01:00
ChrisJr404andChris a5f3873194 Backport: safeeval: allow LIST_APPEND and SET_ADD opcodes (Python 3.14) (#2722)
* safeeval: allow LIST_APPEND and SET_ADD opcodes (Python 3.14)

* changelog: add entry for #2722 (safeeval Python 3.14 opcodes)

* ci: retrigger after transient libcdb network failure

---------

Co-authored-by: Chris (ChrisJr404) <[email protected]>
2026-10-30 20:49:05 +01:00
peace-maker 481704bfd8 Backport: ssh: Discard stderr of lsb_release for Distro lookup (#2704)
* ssh: Discard stderr of `lsb_release`

The "No LSB modules are available." spam interferes with parsing the output for the initial checksec distro parsing when connecting to a new host.

* Update CHANGELOG
2026-10-30 20:49:05 +01:00
zackary 5196a1a0f0 Backport: fix: delay upstream DNS resolution to prevent startup failures (#2718)
Use nginx variable pattern (set $upstream + proxy_pass https://$upstream)
to defer DNS resolution until first request instead of at config parse time.

This fixes the occasional 'host not found in upstream' error in CI when
nginx container starts before Docker DNS is fully available.

Fixes #2712
2026-10-30 20:49:05 +01:00
Peace-Maker be705326a7 Backport: libcdb CI cache: Resolve upstream hostnames on request
Instead of trying to resolve all of them when nginx starts and exit
if any of them fail to resolve.

[emerg] 1#1: host not found in upstream "debuginfod.elfutils.org" in /etc/nginx/nginx.conf:23
2026-10-30 20:49:05 +01:00
Peace-Maker c7e6ef37c8 Backport: Cache gitlab.com libcdb responses in CI
The reponses set a cloudflare `_cfuvid` cookie and had a cache time of 60 seconds.
nginx doesn't cache responses with a `Set-Cookie` header by default,
so ignore the cookie and override the cache time to 12 weeks.

Fixes #2494
2026-10-30 20:49:05 +01:00
Arusekk 210faac75b Backport: no atexit, OS will suffice 2026-10-30 20:49:05 +01:00
peace-maker 8e0e4c6c90 Backport: Close SSH client connection when authentication failed (#2688)
* Close SSH client when authentication failed

The TCP connection stayed open after an exception was thrown
during authentication, even when a `with`-statement guarding the call.
Close the connection before re-raising the exception.

Fixes #2678

* Update CHANGELOG
2026-10-30 20:49:05 +01:00
peace-maker bbac6660b2 Backport: Fix tty and raw arguments in ssh.process (#2699)
* Fix `tty` and `raw` arguments in ssh.process

The arguments were unused and not passed through to the underlying `ssh_process` class.

Fixes #2038

* Add tty tests

* Update CHANGELOG

* Fix buffered process environment output without tty
2026-10-30 20:49:05 +01:00
Rin 2a986f12c1 Backport: refactor: replace unsafe eval with ast.literal_eval in ROP cache loading (#2677)
* refactor: replace unsafe eval with ast.literal_eval in ROP cache loading

* docs: add PR link to CHANGELOG reference section

* docs: restore full CHANGELOG and correctly add PR #2677 reference
2026-10-30 20:49:05 +01:00
peace-maker b60ed9928d Backport: Fix typo causing doctests not getting skipped (#2681)
Downloading all those libcs slowed down the CI a lot.
2026-10-30 20:49:05 +01:00
peace-maker b1391984d1 Backport: asm: try native binutils before fallback architectures (#2669)
* asm: try native binutils before fallback architectures

Some binutils builds support multiple architectures,
like the x86_64 assembler supports i386 too in most
package builds and vice versa. We support this by trying
the other binutils as well when looking for the right tool.
That support is optional though and there are packages
that don't include the "cross" compilation.

When assembling for the host's native architecture, we
still prefer the explicitly named `x86_64-linux-gnu-as`
instead of the native `as` binary. That native binary is
tried as a last resort after looking for any other explictly
named binary. If the fallback package is installed but
compiled without the cross-compiler support, we'd still
find the `i386-as` binary while looking for `x86_64-as`
before trying the native `as` binary.
That would fail to assemble.

This patch moves the native `as` binary forward in the list
to try it before checking the other fallback architecture ones.

Fixes #2509

* Update CHANGELOG
2026-10-30 20:49:05 +01:00
Th3S dd747e1062 Backport: fix(libcdb-cli): return early if no matched libc found (#2646)
* Fix: return early if no matched libc found

* Update CHANGELOG
2026-10-30 20:49:05 +01:00
peace-maker 78fccc055f Backport: Remove "pip" as explicit dependency (#2642)
It looks like this was done to work around a bug in an old pip version years ago.
It caused problems when freezing dependencies which unnecessarily forced pip to the version used at freeze time when installing again.

Fixes #2625
2026-10-30 20:49:05 +01:00
OBarronCS 4f50b45586 Backport: Add aarch64_be qemu mapping (#2653) 2026-10-30 20:49:05 +01:00
a1c7d07db8 Backport: GitHub Actions: Test on Python 3.14 (#2627)
* GitHub Actions: Test on Python 3.14

Python v3.14 -- October 7th
* https://www.python.org/download/pre-releases
* https://www.python.org/downloads/release/python-3140rc3
* https://github.com/actions/setup-python/blob/main/docs/advanced-usage.md#allow-pre-releases

* Allow new LOAD_SMALL_INT opcode in safeeval

* Support lambdas in mbruteforce

* Drop pwnlib.util.iters.lookahead

---------

Co-authored-by: Peace-Maker <[email protected]>
Co-authored-by: Arusekk <[email protected]>
2026-10-30 20:49:05 +01:00
Arusekk 6691e8476a Backport: template: error message and fallback on no docker 2026-10-30 20:49:05 +01:00
peace-maker a8bcdf6f8d Backport: Fix log.progress ignoring context.log_console (#2610)
* Fix `log.progress` ignoring `context.log_console`

When setting `context.log_console` to something other than stdout, spinners while using `log.progress` would still be printed to stdout.
Redirect them to the stream specified by `context.log_console` and don't animate if it's not a tty.

Fixes #1702

* Update CHANGELOG
2026-10-30 20:49:05 +01:00
k4lizen bf5e4df220 Backport: Fix aarch64 ABI (#2598)
* upd aarch64 abi

* changelog
2026-10-30 20:49:05 +01:00
Shordenandpeace-maker 68b5bbf16b Backport: Support context.timeout in ~/.pwn.conf (#2592)
* Add float to supported config types

This change functionally just allows setting `context.timeout` in
~/.pwn.conf.

* update changelog

---------

Co-authored-by: peace-maker <[email protected]>
2026-10-30 20:49:05 +01:00
Arusekk 9a7e197754 Backport: Fix double exception on gaierror
Closes #2581
2026-10-30 20:49:05 +01:00
Zach Hanson 96c3ede156 Backport: Fix a bug that incorrectly reports GNU_STACK missing when its not. (#2493) 2026-10-30 20:49:05 +01:00
RocketDev f2eba7c8fd Backport: ROP: fix ROP(ELF(exe)).leave is None in some ELF (#2506)
* ROP: fix `self.leave is None` in some libc

When `pop rbp; pop rsp; ret;` exists in libc, it is chosen instead of
`leave`. Add a specific `order` to filter out the former one.

* add changelog for #2506

* ROP: add sp_move when `pop rsp`

* CHANGELOG: mv #2506 to `5.0.0`
2026-10-30 20:49:05 +01:00
peace-maker 1c7d917432 Backport: CI: Test on Python 3.13 (#2534) 2026-10-30 20:49:05 +01:00
tesujiandpeace-maker 1553670f9d Backport: checksec: Do NOT error when passing directory arguments to commandline tool (#2530)
I often use `checksec *` to lazily avoid typing filenames in a directory.
If the directory contains any other sub-dirs, the command fails.
With this patch, checksec will silently skip dir paths. There's still
TOCTOU issue but I don't think checksec do anything important enough
to explicitly use try/catch to account for that.

Co-authored-by: peace-maker <[email protected]>
2026-10-30 20:49:05 +01:00
peace-maker 8f67b7b7b5 Backport: Raise EOFError during process.recv when stdout closes on Windows (#2524)
* Windows: Raise EOFError during process.recv when stdout closes

If the receiving thread terminates while we're waiting for data to arrive we'd be waiting endlessly. Raise EOFError when .recv()ing on a process with stdout closed and no more data queued up from the receiver thread.

* Update CHANGELOG
2026-10-30 20:49:05 +01:00
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
52 changed files with 874 additions and 363 deletions

No files matched your search

+7 -3
View File
@@ -5,7 +5,7 @@ jobs:
test:
strategy:
matrix:
python_version: ['3.10', '3.12']
python_version: ['3.10', '3.12', '3.13', '3.14', '3.15']
os: [ubuntu-latest]
include:
- python_version: '2.7'
@@ -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
+150 -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,106 @@ The table below shows which release corresponds to each branch, and what date th
## 5.0.0 (`dev`)
- [#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
- [#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
- [#2529][2529] Add LoongArch64 support
- [#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
- [#2608][2608] Abort on `libcdb file libc.so --unstrip` if eu-unstrip is not installed
- [#2611][2611] Cleanup `pwnlib.lexer` exports and imports
- [#2615][2615] tube/process: Fix redirecting stderr to stdout on Windows
[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
[2526]: https://github.com/Gallopsled/pwntools/pull/2526
[2517]: https://github.com/Gallopsled/pwntools/pull/2517
[2529]: https://github.com/Gallopsled/pwntools/pull/2529
[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
[2608]: https://github.com/Gallopsled/pwntools/pull/2608
[2611]: https://github.com/Gallopsled/pwntools/pull/2611
[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
- [#2524][2524] Raise EOFError during `process.recv` when stdout closes on Windows
- [#2530][2530] Do NOT error when passing directory arguments in `checksec` commandline tool.
- [#2506][2506] ROP: fix `ROP(ELF(exe)).leave` is `None` in some ELF
- [#2592][2592] pwnlib.config: Fix customization of `context.timeout`
- [#2598][2598] aarch64: Fix ABI definition
- [#2610][2610] Fix `log.progress` ignoring `context.log_console`
- [#2627][2627] Remove `pwnlib.util.iters.lookahead` (broken anyway, fails on Python 3.14+)
- [#2653][2653] Add aarch64_be qemu mapping
- [#2642][2642] Remove `pip` as explicit dependency
- [#2646][2646] fix(libcdb-cli): return early if no matched libc found
- [#2669][2669] asm: try native binutils before fallback architectures
- [#2677][2677] refactor: replace unsafe eval with safeeval.const in ROP cache loading
- [#2699][2699] Fix `tty` and `raw` arguments in `ssh.process()`
- [#2688][2688] Close SSH client connection when authentication failed
- [#2704][2704] ssh: Fix distro lookup on Ubuntu 24.04+
- [#2762][2762] fix(srop): correct amd64 SigreturnFrame `uc_sigmask` offset
- [#2767][2767] Support paginated libc.rip API requests to include all search results
- [#2739][2739] shellcraft: migrate lazy importer to find_spec for Python 3.12+
- [#2792][2792] Fix deprecation warning for not using `RLIM_INFINITY` in `resource.setrlimit` (Python 3.15)
[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
[2524]: https://github.com/Gallopsled/pwntools/pull/2524
[2530]: https://github.com/Gallopsled/pwntools/pull/2530
[2506]: https://github.com/Gallopsled/pwntools/pull/2506
[2592]: https://github.com/Gallopsled/pwntools/pull/2592
[2598]: https://github.com/Gallopsled/pwntools/pull/2598
[2610]: https://github.com/Gallopsled/pwntools/pull/2610
[2627]: https://github.com/Gallopsled/pwntools/pull/2627
[2653]: https://github.com/Gallopsled/pwntools/pull/2653
[2642]: https://github.com/Gallopsled/pwntools/pull/2642
[2646]: https://github.com/Gallopsled/pwntools/pull/2646
[2669]: https://github.com/Gallopsled/pwntools/pull/2669
[2677]: https://github.com/Gallopsled/pwntools/pull/2677
[2699]: https://github.com/Gallopsled/pwntools/pull/2699
[2688]: https://github.com/Gallopsled/pwntools/pull/2688
[2704]: https://github.com/Gallopsled/pwntools/pull/2704
[2762]: https://github.com/Gallopsled/pwntools/pull/2762
[2767]: https://github.com/Gallopsled/pwntools/pull/2767
[2739]: https://github.com/Gallopsled/pwntools/pull/2739
[2792]: https://github.com/Gallopsled/pwntools/pull/2792
## 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 +192,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 +223,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 +326,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 +413,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 +688,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 -2
View File
@@ -8,7 +8,6 @@ intervaltree
isort
mako>=1.0.0
paramiko>=1.15.2
pip>=6.0.8
pyelftools>=0.29, <0.30; python_version<'3'
pyelftools>=0.29; python_version>='3'
pygments>=2.0
@@ -19,6 +18,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('_')]
+1 -1
View File
@@ -142,7 +142,7 @@ class SigreturnABI(SyscallABI):
linux_i386 = ABI('esp', [], 4, 0)
linux_amd64 = ABI('rsp', ['rdi','rsi','rdx','rcx','r8','r9'], 8, 0)
linux_arm = ABI('sp', ['r0', 'r1', 'r2', 'r3'], 8, 0)
linux_aarch64 = ABI('sp', ['x0', 'x1', 'x2', 'x3'], 16, 0)
linux_aarch64 = ABI('sp', ['x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7'], 16, 0)
linux_mips = ABI('$sp', ['$a0','$a1','$a2','$a3'], 4, 0)
linux_ppc = ABI('sp', ['r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10'], 4, 0)
linux_ppc64 = ABI('sp', ['r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10'], 8, 0)
+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):
+30 -10
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,28 +180,44 @@ 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] + {
aliases = {
'thumb': ['arm', 'aarch64'],
'i386': ['x86_64', 'amd64'],
'i686': ['x86_64', 'amd64'],
'amd64': ['x86_64', 'i386'],
'mips64': ['mips'],
'powerpc64': ['powerpc'],
'sparc64': ['sparc'],
'riscv32': ['riscv32', 'riscv64', 'riscv'],
'riscv64': ['riscv64', 'riscv32', 'riscv'],
'amd64': ['x86_64'],
}.get(arch, [])
# Some binutils can support multiple architectures. Try them as fallbacks.
fallback_arches = {
'i386': ['x86_64', 'amd64'],
'amd64': ['i386'],
'arm': ['aarch64'],
'mips': ['mipsel'],
'mipsel': ['mips'],
'mips64': ['mips64el', 'mips', 'mipsel'],
'mips64el': ['mips64', 'mipsel', 'mips'],
'powerpc64': ['powerpc'],
'sparc64': ['sparc'],
'riscv32': ['riscv64', 'riscv'],
'riscv64': ['riscv32', 'riscv'],
}.get(arch, [])
arches = [arch] + aliases + fallback_arches
# If one of the candidate architectures matches the native
# architecture, use that as a last resort.
# architecture, use that as a last resort before trying fallbacks.
machine = platform.machine()
machine = 'i386' if machine == 'i686' else machine
try:
with context.local(arch = machine):
if context.arch in arches:
arches.append(None)
arches = [arch] + aliases + [None] + fallback_arches
except AttributeError:
log.warn_once("Your local binutils won't be used because architecture %r is not supported." % machine)
@@ -212,6 +230,8 @@ def which_binutils(util, check_version=False):
if platform.system() == 'Windows':
utils = [util + '.exe']
# Try the explicit tools for the target architecture first,
# then the native one optionally, then fallbacks.
for arch in arches:
for gutil in utils:
# e.g. objdump
+3 -5
View File
@@ -15,7 +15,6 @@ parser = common.parser_commands.add_parser(
parser.add_argument(
'elf',
nargs='*',
type=argparse.FileType('rb'),
help='Files to check'
)
parser.add_argument(
@@ -23,12 +22,11 @@ parser.add_argument(
nargs='*',
dest='elf2',
metavar='elf',
type=argparse.FileType('rb'),
help='File to check (for compatibility with checksec.sh)'
)
def main(args):
files = args.elf or args.elf2 or []
files = args.elf or args.elf2 or []
if not files:
parser.print_usage()
@@ -36,9 +34,9 @@ def main(args):
for f in files:
try:
e = ELF(f.name)
e = ELF(f)
except Exception as e:
print("{name}: {error}".format(name=f.name, error=e))
print("{name}: {error}".format(name=f, error=e))
if __name__ == '__main__':
common.main(__file__, main)
+3
View File
@@ -240,6 +240,9 @@ def main(args):
symbols = {pairs[i]:pairs[i+1] for i in range(0, len(pairs), 2)}
matched_libcs = libcdb.search_by_symbol_offsets(symbols, offline_only=args.offline_only, return_raw=True)
if not matched_libcs:
return
for libc in matched_libcs:
print_libc_info(libc)
if args.download_libc:
+29 -4
View File
@@ -89,7 +89,7 @@ def get_docker_image_libraries():
if not (libc and ld):
progress.failure("Could not find libraries")
return None, None
progress.status("Copying libraries to current directory")
for filename, basename in zip((libc, ld), (libc_basename, ld_basename)):
cat_command = ["-c", "chroot %s /bin/sh -c '/bin/cat %s'" % (chroot_dir, filename)]
@@ -108,13 +108,26 @@ def get_docker_image_libraries():
write(basename, contents)
except subprocess.CalledProcessError as e:
print(e.stderr.decode())
log.error("docker failed with status: %d" % e.returncode)
print(e.stderr.decode(), file=sys.stderr)
progress.failure("docker failed with status: %d" % e.returncode)
return None, None
progress.success("Retrieved libraries from Docker image")
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 +151,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:
+16 -1
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.
@@ -1733,7 +1748,7 @@ def update_context_defaults(section):
default = ContextType.defaults[key]
if isinstance(default, six.string_types + six.integer_types + (tuple, list, dict)):
if isinstance(default, six.string_types + six.integer_types + (float, tuple, list, dict)):
value = safeeval.expr(value)
else:
log.warn("Unsupported configuration option %r in section %r" % (key, 'context'))
+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(
+119 -81
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
@@ -2085,7 +2123,7 @@ class ELF(ELFFile):
"NX:".ljust(12) + {
True: green("NX enabled"),
False: red("NX disabled"),
None: yellow("NX unknown - GNU_STACK missing"),
None: yellow("NX enabled on new kernels"),
}[self.nx],
"PIE:".ljust(12) + {
True: green("PIE enabled"),
+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': [
+71 -66
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/',
]
@@ -116,16 +119,32 @@ def query_libc_rip(params):
import requests
url = "{}/api/find".format(LIBC_RIP_URL)
try:
result = requests.post(url, json=params, timeout=20)
result.raise_for_status()
if result.status_code != 200:
log.debug("Error: %s", result.text)
# Handle pagination transparently and fetch all results
offset = 0
count = 1
results = []
while offset < count:
try:
pagination_params = {'offset': offset, 'limit': 100}
log.debug("Querying libc.rip at %s (pagination %s) with parameters: %s", url, pagination_params, params)
result = requests.post(url, params=pagination_params, json=params, timeout=20)
result.raise_for_status()
if result.status_code != 200:
log.debug("Error: %s", result.text)
return None
imm_results = result.json()
# non-paginated result. old libc-database API?
if not isinstance(imm_results, dict):
return imm_results
results.extend(imm_results.get('results', []))
count = imm_results.get('total', 0)
offset += imm_results['count']
if not imm_results['has_more']:
break
except requests.RequestException as e:
log.warn_once("Failed to fetch libc info from libc.rip: %s", e)
return None
return result.json()
except requests.RequestException as e:
log.warn_once("Failed to fetch libc info from libc.rip: %s", e)
return None
return results
# https://libc.rip/
def provider_libc_rip(search_target, search_type):
@@ -293,49 +312,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 +370,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 +396,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 +503,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.')
@@ -708,8 +713,8 @@ def search_by_symbol_offsets(symbols, select_index=None, unstrip=True, offline_o
>>> matched_libcs = search_by_symbol_offsets({'__libc_start_main_ret': '7f89ad926550'}, return_as_list=True)
>>> len(matched_libcs) > 1
True
>>> for buildid in matched_libcs: # doctest +SKIP
... libc = ELF(search_by_build_id(buildid)) # doctest +SKIP
>>> for buildid in matched_libcs: # doctest: +SKIP
... libc = ELF(search_by_build_id(buildid)) # doctest: +SKIP
"""
assert search_type in TYPES, search_type
+2 -1
View File
@@ -546,7 +546,8 @@ class Handler(logging.StreamHandler):
# if the record originates from a `Progress` object and term handling
# is enabled we can have animated spinners! so check that
if progress is None or not term.term_mode:
# Don't show spinners if the output is being redirected
if progress is None or not term.term_mode or not os.path.sameopenfile(context.log_console.fileno(), term.term.fd.fileno()):
super(Handler, self).emit(record)
return
+1
View File
@@ -105,6 +105,7 @@ def archname():
('powerpc64', 'little'): 'ppc64le',
('thumb', 'little'): 'arm',
('thumb', 'big'): 'armeb',
('aarch64', 'big'): 'aarch64_be',
}.get((context.arch, context.endian), context.arch)
@LocalContext
+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]))
+13 -7
View File
@@ -390,6 +390,7 @@ from pwnlib.rop.call import Unresolved
from pwnlib.rop.gadgets import Gadget
from pwnlib.util import lists
from pwnlib.util import packing
from pwnlib.util import safeeval
from pwnlib.util.cyclic import cyclic
from pwnlib.util.packing import pack
from pwnlib.util.misc import python_2_bytes_compatible
@@ -841,7 +842,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)
@@ -1272,7 +1273,7 @@ class ROP(object):
filename = self.__get_cachefile_name(elf)
if filename is None or not os.path.exists(filename):
return None
gadgets = eval(open(filename).read())
gadgets = safeeval.const(open(filename).read())
gadgets = {k - elf.load_addr + elf.address:v for k, v in gadgets.items()}
log.info_once('Loaded %s cached gadgets for %r', len(gadgets), elf.file.name)
return gadgets
@@ -1391,6 +1392,8 @@ class ROP(object):
if pop.match(insn):
regs.append(pop.match(insn).group(1))
sp_move += context.bytes
if 'sp' in insn:
sp_move += 9999999
elif add.match(insn):
arg = int(add.match(insn).group(1), 16)
sp_move += arg
@@ -1418,7 +1421,7 @@ class ROP(object):
if not set(['rsp', 'esp']) & set(regs):
self.pivots[sp_move] = addr
leave = self.search(regs=frame_regs, order='regs')
leave = self.search(regs=frame_regs, order='leav')
if leave and leave.regs != frame_regs:
leave = None
self.leave = leave
@@ -1451,7 +1454,7 @@ class ROP(object):
pointer is adjusted.
regs(list): Minimum list of registers which are popped off the
stack.
order(str): Either the string 'size' or 'regs'. Decides how to
order(str): Either the string 'size', 'leav' or 'regs'. Decides how to
order multiple gadgets the fulfill the requirements.
The search will try to minimize the number of bytes popped more than
@@ -1459,7 +1462,9 @@ class ROP(object):
the address.
If ``order == 'size'``, then gadgets are compared lexicographically
by ``(total_moves, total_regs, addr)``, otherwise by ``(total_regs, total_moves, addr)``.
by ``(total_moves, total_regs, addr)``, if ``order == 'regs'``,
then by ``(total_regs, total_moves, addr)``. ``order == 'leav'``
is specifically for ``leave`` insn.
Returns:
A :class:`.Gadget` object
@@ -1471,8 +1476,9 @@ class ROP(object):
# Search for an exact match, save the closest match
key = {
'size': lambda g: (g.move, len(g.regs), g.address),
'regs': lambda g: (len(g.regs), g.move, g.address)
}[order]
'regs': lambda g: (len(g.regs), g.move, g.address),
'leav': lambda g: ('leave' not in g.insns, len(g.regs), g.address)
}[order] # False is prior than True
try:
result = min(matches, key=key)
+5 -5
View File
@@ -185,7 +185,7 @@ registers = {
80: 'r13', 88: 'r14', 96: 'r15', 104: 'rdi', 112: 'rsi', 120: 'rbp', 128: 'rbx',
136: 'rdx', 144: 'rax', 152: 'rcx', 160: 'rsp', 168: 'rip', 176: 'eflags',
184: 'csgsfs', 192: 'err', 200: 'trapno', 208: 'oldmask', 216: 'cr2',
224: '&fpstate', 232: '__reserved', 240: 'sigmask'},
224: '&fpstate', 232: '__reserved', 296: 'sigmask'},
# Reference : http://lxr.free-electrons.com/source/arch/arm/include/uapi/asm/sigcontext.h#L15
'arm' : {0: 'uc_flags', 4: 'uc_link', 8: 'uc_stack.ss_sp', 12: 'uc_stack.ss_flags',
16: 'uc_stack.ss_size', 20: 'trap_no', 24: 'error_code', 28: 'oldmask', 32: 'r0',
@@ -270,15 +270,15 @@ class SigreturnFrame(dict):
>>> context.clear(arch='amd64')
>>> s = SigreturnFrame()
>>> unpack_many(bytes(s))
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0]
>>> assert len(s) == 248
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
>>> assert len(s) == 304
>>> s.rax = 0xa
>>> s.rdi = 0x00601000
>>> s.rsi = 0x1000
>>> s.rdx = 0x7
>>> assert len(bytes(s)) == 248
>>> assert len(bytes(s)) == 304
>>> unpack_many(bytes(s))
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6295552, 4096, 0, 0, 7, 10, 0, 0, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6295552, 4096, 0, 0, 7, 10, 0, 0, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Crafting a SigreturnFrame that calls mprotect on i386
+39 -13
View File
@@ -170,21 +170,47 @@ tether = sys.modules[__name__]
# Create the module structure
shellcraft = module(__name__, '')
class LazyImporter:
def find_module(self, fullname, path=None):
if not fullname.startswith('pwnlib.shellcraft.'):
return None
parts = fullname.split('.')[2:]
cur = shellcraft
for part in parts:
cur = getattr(cur, part, None)
if not isinstance(cur, ModuleType):
if sys.version_info >= (3, 4):
import importlib.abc
import importlib.util
class LazyImporter(importlib.abc.MetaPathFinder, importlib.abc.Loader):
def find_spec(self, fullname, path=None, target=None):
if not fullname.startswith('pwnlib.shellcraft.'):
return None
return self
parts = fullname.split('.')[2:]
cur = shellcraft
for part in parts:
cur = getattr(cur, part, None)
if not isinstance(cur, ModuleType):
return None
def load_module(self, fullname):
return sys.modules[fullname]
return importlib.util.spec_from_loader(fullname, self)
def create_module(self, spec):
# The submodule was already built and registered in sys.modules by
# the getattr walk in find_spec, so hand that object back instead
# of letting the import system make a fresh empty module.
return sys.modules.get(spec.name)
def exec_module(self, module):
pass
else:
class LazyImporter:
def find_module(self, fullname, path=None):
if not fullname.startswith('pwnlib.shellcraft.'):
return None
parts = fullname.split('.')[2:]
cur = shellcraft
for part in parts:
cur = getattr(cur, part, None)
if not isinstance(cur, ModuleType):
return None
return self
def load_module(self, fullname):
return sys.modules[fullname]
sys.meta_path.append(LazyImporter())
+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:
+19 -16
View File
@@ -767,9 +767,13 @@ class process(tube):
if IS_WINDOWS:
with self.countdown(timeout=timeout):
while self.timeout and self._read_queue.empty():
while self.timeout and self._read_queue.empty() and self._read_thread.is_alive():
time.sleep(0.01)
return not self._read_queue.empty()
if not self._read_queue.empty():
return True
if not self._read_thread.is_alive():
raise EOFError
return False
try:
if timeout is None:
@@ -1259,6 +1263,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 +1332,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 +1341,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 +1362,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
@@ -86,7 +86,7 @@ class remote(sock):
except socket.gaierror as e:
if e.errno != socket.EAI_NONAME:
raise
self.error('Could not resolve hostname: %r', host)
self.exception('Could not resolve hostname: %r', host)
if self.sock:
self.settimeout(self.timeout)
self.lhost, self.lport = self.sock.getsockname()[:2]
+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:
+21 -5
View File
@@ -692,6 +692,7 @@ class ssh(Timeout, Logger):
if user and auth_none and str(e) == "No authentication methods available":
self.client.get_transport().auth_none(user)
else:
self.close()
raise
self.transport = self.client.get_transport()
@@ -913,6 +914,13 @@ class ssh(Timeout, Logger):
>>> io.recvline()
b''
>>> io = s.process(['tty'], tty=True)
>>> io.recvline() # doctest: +ELLIPSIS
b'/dev/pts/...\n'
>>> io = s.process(['tty'], tty=False)
>>> io.recvline()
b'not a tty\n'
>>> # Testing that empty argv works
>>> io = s.process([], executable='sh')
>>> io.sendline(b'echo $0')
@@ -970,7 +978,7 @@ class ssh(Timeout, Logger):
script = 'echo PWNTOOLS; for py in python3 python2.7 python2 python; do test -x "$(command -v $py 2>&1)" && echo $py && exec $py -c %s check; done; echo 2' % sh_string(script)
with context.quiet:
python = ssh_process(self, script, tty=True, cwd=cwd, raw=True, level=self.level, timeout=timeout)
python = ssh_process(self, script, tty=tty, cwd=cwd, raw=raw, level=self.level, timeout=timeout)
try:
python.recvline_contains(b'PWNTOOLS') # Magic flag so that any sh/bash initialization errors are swallowed
@@ -1359,7 +1367,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 +1551,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):
@@ -1890,7 +1906,7 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
if not self.which('lsb_release'):
return
with self.process(['lsb_release', '-irs']) as io:
with self.process(['lsb_release', '-irs'], stderr='/dev/null') as io:
lsb_info = io.recvall().strip().decode()
self._platform_info['distro'], self._platform_info['distro_ver'] = lsb_info.split()
except Exception:
@@ -2073,7 +2089,7 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
Example:
>>> s = ssh("travis", "example.pwnme")
>>> s.ibt
>>> s.ibt # doctest: +SKIP
False
"""
if self._ibt is None:
+53 -32
View File
@@ -6,10 +6,12 @@ from __future__ import division
import collections
import copy
import marshal
import multiprocessing
import operator
import random
import time
import types
from itertools import *
from six.moves import map, filter, filterfalse, range, zip, zip_longest
@@ -27,7 +29,6 @@ __all__ = [
'group' ,
'iter_except' ,
'lexicographic' ,
'lookahead' ,
'nth' ,
'pad' ,
'pairwise' ,
@@ -683,37 +684,6 @@ def random_combination_with_replacement(iterable, r):
indices = sorted(random.randrange(n) for i in range(r))
return tuple(pool[i] for i in indices)
def lookahead(n, iterable):
"""lookahead(n, iterable) -> object
Inspects the upcoming element at index `n` without advancing the iterator.
Raises ``IndexError`` if `iterable` has too few elements.
Arguments:
n(int): Index of the element to return.
iterable: An iterable.
Returns:
The element in `iterable` at index `n`.
Examples:
>>> i = count()
>>> lookahead(4, i)
4
>>> next(i)
0
>>> i = count()
>>> nth(4, i)
4
>>> next(i)
5
>>> lookahead(4, i)
10
"""
for value in islice(copy.copy(iterable), n, None):
return value
raise IndexError(n)
def lexicographic(alphabet):
"""lexicographic(alphabet) -> iterator
@@ -877,6 +847,54 @@ def _mbruteforcewrap(func, alphabet, length, method, start, databag):
databag["result"] = res
class CustomPickler:
def __init__(self, *reduction):
self.reduction = reduction
def __reduce__(self):
return self.reduction
def __repr__(self):
return self.__class__.__name__ + repr(self.reduction)
def construct_func(*args):
return types.FunctionType(*args)
class PicklableFunc:
def __init__(self, f):
self.f = f
def __call__(self, *a, **kw):
return self.f(*a, **kw)
def __getattr__(self, a):
return getattr(self.f, a)
def __reduce__(self):
# Constructor types.FunctionType tries to be __main__.function;
# globs could likewise be <module>.__dict__. Neither
# type('FunctionType', (), {'__module__':'types'})
# nor
# def __reduce__(self): return "FunctionType"
# works because of checks for identity :(
globs = CustomPickler(vars, (CustomPickler(__import__, (self.f.__module__ or '__main__',)),))
return type(self), (CustomPickler(
construct_func, # ideally self.f.__class__
# but types.FunctionType tries to be __main__.function
(
CustomPickler(marshal.loads, (marshal.dumps(self.f.__code__),)),
globs,
self.f.__name__,
self.f.__defaults__,
self.f.__closure__,
self.f.__kwdefaults__,
),
self.f.__dict__,
),)
def mbruteforce(func, alphabet, length, method = 'upto', start = None, threads = None):
"""mbruteforce(func, alphabet, length, method = 'upto', start = None, threads = None)
@@ -914,6 +932,9 @@ def mbruteforce(func, alphabet, length, method = 'upto', start = None, threads =
(i2, N2) = start
totalchunks = threads * N2
if isinstance(func, types.FunctionType):
func = PicklableFunc(func)
for i in range(threads):
shareddata[i] = multiprocessing.Manager().dict()
shareddata[i]['result'] = None
+3 -2
View File
@@ -868,6 +868,7 @@ if sys.argv[-1] == 'check':
sys.stdout.write(str(os.getgid()) + "\n")
sys.stdout.write(str(suid) + "\n")
sys.stdout.write(str(sgid) + "\n")
sys.stdout.flush()
getattr(sys.stdout, 'buffer', sys.stdout).write(os.path.realpath(exe) + b'\x00')
sys.stdout.flush()
@@ -886,7 +887,7 @@ if not %(aslr)r:
ADDR_NO_RANDOMIZE = 0x0040000
ctypes.CDLL('libc.so.6').personality(ADDR_NO_RANDOMIZE)
resource.setrlimit(resource.RLIMIT_STACK, (-1, -1))
resource.setrlimit(resource.RLIMIT_STACK, (resource.RLIM_INFINITY, resource.RLIM_INFINITY))
# Attempt to dump ALL core file regions
try:
@@ -897,7 +898,7 @@ except Exception:
# Assume that the user would prefer to have core dumps.
try:
resource.setrlimit(resource.RLIMIT_CORE, (-1, -1))
resource.setrlimit(resource.RLIMIT_CORE, (resource.RLIM_INFINITY, resource.RLIM_INFINITY))
except Exception:
pass
+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()
+7 -3
View File
@@ -4,8 +4,8 @@ _const_codes = [
'POP_TOP','ROT_TWO','ROT_THREE','ROT_FOUR','DUP_TOP',
'BUILD_LIST','BUILD_MAP', 'MAP_ADD', 'BUILD_TUPLE','BUILD_SET',
'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',
'LOAD_CONST','LOAD_SMALL_INT','RETURN_VALUE','STORE_SUBSCR', 'STORE_MAP',
'LIST_TO_TUPLE', 'LIST_APPEND', 'LIST_EXTEND', 'SET_ADD', 'SET_UPDATE', 'DICT_UPDATE', 'DICT_MERGE',
'COPY', 'RESUME', 'RETURN_CONST'
]
@@ -29,7 +29,7 @@ def _get_opcodes(codeobj):
Extract the actual opcodes as a list from a code object
>>> c = compile("[1 + 2, (1,2)]", "", "eval")
>>> _get_opcodes(c) # doctest: +ELLIPSIS
>>> _get_opcodes(c) # doctest: +SKIP
[...100, 100, 103, 83]
"""
import dis
@@ -85,6 +85,10 @@ def const(expr):
Traceback (most recent call last):
...
ValueError: opcode BINARY_ADD not allowed
>>> const("[" + ",".join(str(i) for i in range(40)) + "]") == list(range(40))
True
>>> const("{" + ",".join(str(i) for i in range(40)) + "}") == set(range(40))
True
"""
c = test_expr(expr, _const_codes)
+1 -1
View File
@@ -1 +1 @@
__version__ = '4.15.0beta0'
__version__ = '4.15.0'
+1 -2
View File
@@ -42,7 +42,6 @@ dependencies = [
"ropgadget>=5.3",
"pyserial>=2.7",
"requests>=2.0",
"pip>=6.0.8",
"pygments>=2.0",
"pysocks",
"python-dateutil",
@@ -56,7 +55,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')),
],
+38 -8
View File
@@ -8,18 +8,26 @@ http {
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
access_log /dev/stdout cache_st;
# Use Docker's embedded DNS server to resolve hostnames at runtime
# instead of at startup.
resolver 127.0.0.11 8.8.8.8 ipv6=off valid=30s;
# Set default resolver for all upstreams to avoid startup-time DNS resolution
# This is a workaround for the occasional 'host not found in upstream' error
# in Docker environments where DNS may not be immediately available.
server {
listen 3000;
proxy_cache my_cache;
location / {
proxy_set_header Host debuginfod.elfutils.org;
set $upstream_host "debuginfod.elfutils.org";
proxy_set_header Host $upstream_host;
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.elfutils.org/;
proxy_pass https://$upstream_host$request_uri;
}
}
@@ -28,13 +36,15 @@ http {
proxy_cache my_cache;
location / {
proxy_set_header Host libc.rip;
set $upstream_host "libc.rip";
proxy_set_header Host $upstream_host;
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;
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://libc.rip/;
proxy_pass https://$upstream_host$request_uri;
}
}
@@ -43,12 +53,13 @@ http {
proxy_cache my_cache;
location / {
proxy_set_header Host archive.ubuntu.com;
set $upstream_host "archive.ubuntu.com";
proxy_set_header Host $upstream_host;
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 http://archive.ubuntu.com/;
proxy_pass http://$upstream_host$request_uri;
}
}
@@ -57,13 +68,32 @@ http {
proxy_cache my_cache;
location / {
proxy_set_header Host gitlab.com;
set $upstream_host "gitlab.com";
proxy_set_header Host $upstream_host;
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_ignore_headers Cache-Control Set-Cookie;
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504 http_429;
proxy_pass https://$upstream_host$request_uri;
}
}
server {
listen 3004;
proxy_cache my_cache;
location / {
set $upstream_host "debuginfod.ubuntu.com";
proxy_set_header Host $upstream_host;
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://gitlab.com/;
proxy_pass https://$upstream_host$request_uri;
}
}
}
+15
View File
@@ -0,0 +1,15 @@
#!/bin/sh
docker run \
--name pwntools-test-nginx-cache \
--rm \
-v ./libcdb_nginx_cache.conf:/etc/nginx/nginx.conf:ro \
-p 3000:3000 \
-p 3001:3001 \
-p 3002:3002 \
-p 3003:3003 \
nginx
# curl -v localhost:3000/buildid/8e9fd827446c24067541ac5390e6f527fb5947bb/debuginfo
# curl -v localhost:3003/libcdb/libcdb/raw/master/hashes/build_id/fe136e485814fee2268cf19e5c124ed0f73f4400
# curl -v localhost:3003/libcdb/libcdb/raw/master/libc/libc6-i386-2.18-6/lib32/libc-2.18.so
# curl -v localhost:3003/libcdb/libcdb/raw/master/hashes/build_id/XX